blob: c7832aa2fc2edc301f7191320917a90d9c4c8e5e [file] [log] [blame]
/* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.xmlbeans.impl.store;
import javax.xml.namespace.QName;
import org.apache.xmlbeans.SystemProperties;
import org.apache.xmlbeans.XmlDocumentProperties;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlOptionCharEscapeMap;
import org.apache.xmlbeans.xml.stream.*;
import org.apache.xmlbeans.impl.common.*;
import java.io.Writer;
import java.io.Reader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import org.xml.sax.ContentHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.ConcurrentModificationException;
abstract class Saver
{
static final int ROOT = Cur.ROOT;
static final int ELEM = Cur.ELEM;
static final int ATTR = Cur.ATTR;
static final int COMMENT = Cur.COMMENT;
static final int PROCINST = Cur.PROCINST;
static final int TEXT = Cur.TEXT;
protected abstract boolean emitElement ( SaveCur c, ArrayList attrNames, ArrayList attrValues );
protected abstract void emitFinish ( SaveCur c );
protected abstract void emitText ( SaveCur c );
protected abstract void emitComment ( SaveCur c );
protected abstract void emitProcinst ( SaveCur c );
protected abstract void emitDocType ( String docTypeName, String publicId, String systemId );
protected abstract void emitStartDoc ( SaveCur c );
protected abstract void emitEndDoc ( SaveCur c );
protected void syntheticNamespace ( String prefix, String uri, boolean considerDefault ) { }
Saver ( Cur c, XmlOptions options )
{
assert c._locale.entered();
options = XmlOptions.maskNull( options );
_cur = createSaveCur( c, options );
_locale = c._locale;
_version = _locale.version();
_namespaceStack = new ArrayList();
_uriMap = new HashMap();
_prefixMap = new HashMap();
_attrNames = new ArrayList();
_attrValues = new ArrayList ();
// Define implicit xml prefixed namespace
addMapping( "xml", Locale._xml1998Uri );
if (options.hasOption( XmlOptions.SAVE_IMPLICIT_NAMESPACES ))
{
Map m = (Map) options.get( XmlOptions.SAVE_IMPLICIT_NAMESPACES );
for ( Iterator i = m.keySet().iterator() ; i.hasNext() ; )
{
String prefix = (String) i.next();
addMapping( prefix, (String) m.get( prefix ) );
}
}
// define character map for escaped replacements
if (options.hasOption( XmlOptions.SAVE_SUBSTITUTE_CHARACTERS ))
{
_replaceChar = (XmlOptionCharEscapeMap)
options.get( XmlOptions.SAVE_SUBSTITUTE_CHARACTERS);
}
// If the default prefix has not been mapped, do so now
if (getNamespaceForPrefix( "" ) == null)
{
_initialDefaultUri = new String( "" );
addMapping( "", _initialDefaultUri );
}
if (options.hasOption( XmlOptions.SAVE_AGGRESSIVE_NAMESPACES ) &&
!(this instanceof SynthNamespaceSaver))
{
SynthNamespaceSaver saver = new SynthNamespaceSaver( c, options );
while ( saver.process() )
;
if (!saver._synthNamespaces.isEmpty())
_preComputedNamespaces = saver._synthNamespaces;
}
_useDefaultNamespace =
options.hasOption( XmlOptions.SAVE_USE_DEFAULT_NAMESPACE );
_saveNamespacesFirst = options.hasOption( XmlOptions.SAVE_NAMESPACES_FIRST );
if (options.hasOption( XmlOptions.SAVE_SUGGESTED_PREFIXES ))
_suggestedPrefixes = (Map) options.get( XmlOptions.SAVE_SUGGESTED_PREFIXES);
_ancestorNamespaces = _cur.getAncestorNamespaces();
}
private static SaveCur createSaveCur ( Cur c, XmlOptions options )
{
QName synthName = (QName) options.get( XmlOptions.SAVE_SYNTHETIC_DOCUMENT_ELEMENT );
QName fragName = synthName;
if (fragName == null)
{
fragName =
options.hasOption( XmlOptions.SAVE_USE_OPEN_FRAGMENT )
? Locale._openuriFragment
: Locale._xmlFragment;
}
boolean saveInner =
options.hasOption( XmlOptions.SAVE_INNER ) &&
!options.hasOption( XmlOptions.SAVE_OUTER );
Cur start = c.tempCur();
Cur end = c.tempCur();
SaveCur cur = null;
int k = c.kind();
switch ( k )
{
case ROOT :
{
positionToInner( c, start, end );
if (Locale.isFragment( start, end ))
cur = new FragSaveCur( start, end, fragName );
else if (synthName != null)
cur = new FragSaveCur( start, end, synthName );
else
cur = new DocSaveCur( c );
break;
}
case ELEM :
{
if (saveInner)
{
positionToInner( c, start, end );
cur =
new FragSaveCur(
start, end, Locale.isFragment( start, end ) ? fragName : synthName );
}
else if (synthName != null)
{
positionToInner( c, start, end );
cur = new FragSaveCur( start, end, synthName );
}
else
{
start.moveToCur( c );
end.moveToCur( c );
end.skip();
cur = new FragSaveCur( start, end, null );
}
break;
}
}
if (cur == null)
{
assert k < 0 || k == ATTR || k == COMMENT || k == PROCINST || k == TEXT;
if (k < 0)
{
// Save out ""
start.moveToCur( c );
end.moveToCur( c );
}
else if (k == TEXT)
{
start.moveToCur( c );
end.moveToCur( c );
end.next();
}
else if (saveInner)
{
start.moveToCur( c );
start.next();
end.moveToCur( c );
end.toEnd();
}
else if (k == ATTR)
{
start.moveToCur( c );
end.moveToCur( c );
}
else
{
assert k == COMMENT || k == PROCINST;
start.moveToCur( c );
end.moveToCur( c );
end.skip();
}
cur = new FragSaveCur( start, end, fragName );
}
String filterPI = (String) options.get( XmlOptions.SAVE_FILTER_PROCINST );
if (filterPI != null)
cur = new FilterPiSaveCur( cur, filterPI );
if (options.hasOption( XmlOptions.SAVE_PRETTY_PRINT ))
cur = new PrettySaveCur( cur, options );
start.release();
end.release();
return cur;
}
private static void positionToInner ( Cur c, Cur start, Cur end )
{
assert c.isContainer();
start.moveToCur( c );
if (!start.toFirstAttr())
start.next();
end.moveToCur( c );
end.toEnd();
}
/**
* Test if a character is valid in xml character content. See
* http://www.w3.org/TR/REC-xml#NT-Char
*/
static boolean isBadChar ( char ch )
{
return ! (
Character.isHighSurrogate(ch) ||
Character.isLowSurrogate(ch) ||
(ch >= 0x20 && ch <= 0xD7FF ) ||
(ch >= 0xE000 && ch <= 0xFFFD) ||
(ch >= 0x10000 && ch <= 0x10FFFF) ||
(ch == 0x9) || (ch == 0xA) || (ch == 0xD)
);
}
protected boolean saveNamespacesFirst ( )
{
return _saveNamespacesFirst;
}
protected void enterLocale()
{
_locale.enter();
}
protected void exitLocale()
{
_locale.exit();
}
protected final boolean process ( )
{
assert _locale.entered();
if (_cur == null)
return false;
if (_version != _locale.version())
throw new ConcurrentModificationException( "Document changed during save" );
switch ( _cur.kind() )
{
case ROOT : { processRoot(); break; }
case ELEM : { processElement(); break; }
case - ELEM : { processFinish (); break; }
case TEXT : { emitText ( _cur ); break; }
case COMMENT : { emitComment ( _cur ); _cur.toEnd(); break; }
case PROCINST : { emitProcinst ( _cur ); _cur.toEnd(); break; }
case - ROOT :
{
emitEndDoc(_cur);
_cur.release();
_cur = null;
return true;
}
default : throw new RuntimeException( "Unexpected kind" );
}
_cur.next();
return true;
}
private final void processFinish ( )
{
emitFinish( _cur );
popMappings();
}
private final void processRoot ( )
{
assert _cur.isRoot();
XmlDocumentProperties props = _cur.getDocProps();
String systemId = null;
String docTypeName = null;
if (props != null)
{
systemId = props.getDoctypeSystemId();
docTypeName = props.getDoctypeName();
}
if (systemId != null || docTypeName != null)
{
if (docTypeName == null)
{
_cur.push();
while (!_cur.isElem() && _cur.next())
;
if (_cur.isElem())
docTypeName = _cur.getName().getLocalPart();
_cur.pop();
}
String publicId = props.getDoctypePublicId();
if (docTypeName != null)
{
QName rootElemName = _cur.getName();
if ( rootElemName == null )
{
_cur.push();
while ( !_cur.isFinish() )
{
if (_cur.isElem())
{
rootElemName = _cur.getName();
break;
}
_cur.next();
}
_cur.pop();
}
if ( rootElemName!=null && docTypeName.equals(rootElemName.getLocalPart()) )
{
emitDocType( docTypeName, publicId, systemId );
return;
}
}
}
emitStartDoc(_cur);
}
private final void processElement ( )
{
assert _cur.isElem() && _cur.getName() != null;
QName name = _cur.getName();
// Add a new entry to the frontier. If this element has a name
// which has no namespace, then we must make sure that pushing
// the mappings causes the default namespace to be empty
boolean ensureDefaultEmpty = name.getNamespaceURI().length() == 0;
pushMappings( _cur, ensureDefaultEmpty );
//
// There are four things which use mappings:
//
// 1) The element name
// 2) The element value (qname based)
// 3) Attribute names
// 4) The attribute values (qname based)
//
// 1) The element name (not for starts)
ensureMapping( name.getNamespaceURI(), name.getPrefix(), !ensureDefaultEmpty, false );
//
//
//
_attrNames.clear();
_attrValues.clear();
_cur.push();
attrs:
for ( boolean A = _cur.toFirstAttr() ; A ; A = _cur.toNextAttr() )
{
if (_cur.isNormalAttr())
{
QName attrName = _cur.getName();
_attrNames.add( attrName );
for ( int i = _attrNames.size() - 2 ; i >= 0 ; i-- )
{
if (_attrNames.get( i ).equals( attrName ))
{
_attrNames.remove( _attrNames.size() - 1 );
continue attrs;
}
}
_attrValues.add( _cur.getAttrValue() );
ensureMapping( attrName.getNamespaceURI(), attrName.getPrefix(), false, true );
}
}
_cur.pop();
// If I am doing aggressive namespaces and we're emitting a
// container which can contain content, add the namespaces
// we've computed. Basically, I'm making sure the pre-computed
// namespaces are mapped on the first container which has a name.
if (_preComputedNamespaces != null)
{
for ( Iterator i = _preComputedNamespaces.keySet().iterator() ; i.hasNext() ; )
{
String uri = (String) i.next();
String prefix = (String) _preComputedNamespaces.get( uri );
boolean considerDefault = prefix.length() == 0 && !ensureDefaultEmpty;
ensureMapping( uri, prefix, considerDefault, false );
}
// Set to null so we do this once at the top
_preComputedNamespaces = null;
}
if (emitElement( _cur, _attrNames, _attrValues ))
{
popMappings();
_cur.toEnd();
}
}
//
// Layout of namespace stack:
//
// URI Undo
// URI Rename
// Prefix Undo
// Mapping
//
boolean hasMappings ( )
{
int i = _namespaceStack.size();
return i > 0 && _namespaceStack.get( i - 1 ) != null;
}
void iterateMappings ( )
{
_currentMapping = _namespaceStack.size();
while ( _currentMapping > 0 && _namespaceStack.get( _currentMapping - 1 ) != null )
_currentMapping -= 8;
}
boolean hasMapping ( )
{
return _currentMapping < _namespaceStack.size();
}
void nextMapping ( )
{
_currentMapping += 8;
}
String mappingPrefix ( )
{
assert hasMapping();
return (String) _namespaceStack.get( _currentMapping + 6 );
}
String mappingUri ( )
{
assert hasMapping();
return (String) _namespaceStack.get( _currentMapping + 7 );
}
private final void pushMappings ( SaveCur c, boolean ensureDefaultEmpty )
{
assert c.isContainer();
_namespaceStack.add( null );
c.push();
namespaces:
for ( boolean A = c.toFirstAttr() ; A ; A = c.toNextAttr() )
if (c.isXmlns())
addNewFrameMapping( c.getXmlnsPrefix(), c.getXmlnsUri(), ensureDefaultEmpty );
c.pop();
if (_ancestorNamespaces != null)
{
for ( int i = 0 ; i < _ancestorNamespaces.size() ; i += 2 )
{
String prefix = (String) _ancestorNamespaces.get( i );
String uri = (String) _ancestorNamespaces.get( i + 1 );
addNewFrameMapping( prefix, uri, ensureDefaultEmpty );
}
_ancestorNamespaces = null;
}
if (ensureDefaultEmpty)
{
String defaultUri = (String) _prefixMap.get( "" );
// I map the default to "" at the very beginning
assert defaultUri != null;
if (defaultUri.length() > 0)
addMapping( "", "" );
}
}
private final void addNewFrameMapping ( String prefix, String uri, boolean ensureDefaultEmpty )
{
// If the prefix maps to "", this don't include this mapping 'cause it's not well formed.
// Also, if we want to make sure that the default namespace is always "", then check that
// here as well.
if ((prefix.length() == 0 || uri.length() > 0) &&
(!ensureDefaultEmpty || prefix.length() > 0 || uri.length() == 0))
{
// Make sure the prefix is not already mapped in this frame
for ( iterateMappings() ; hasMapping() ; nextMapping() )
if (mappingPrefix().equals( prefix ))
return;
// Also make sure that the prefix declaration is not redundant
// This has the side-effect of making it impossible to set a
// redundant prefix declaration, but seems that it's better
// to just never issue a duplicate prefix declaration.
if (uri.equals(getNamespaceForPrefix(prefix)))
return;
addMapping( prefix, uri );
}
}
private final void addMapping ( String prefix, String uri )
{
assert uri != null;
assert prefix != null;
// If the prefix being mapped here is already mapped to a uri,
// that uri will either go out of scope or be mapped to another
// prefix.
String renameUri = (String) _prefixMap.get( prefix );
String renamePrefix = null;
if (renameUri != null)
{
// See if this prefix is already mapped to this uri. If
// so, then add to the stack, but there is nothing to rename
if (renameUri.equals( uri ))
renameUri = null;
else
{
int i = _namespaceStack.size();
while ( i > 0 )
{
if (_namespaceStack.get( i - 1 ) == null)
{
i--;
continue;
}
if (_namespaceStack.get( i - 7 ).equals( renameUri ))
{
renamePrefix = (String) _namespaceStack.get( i - 8 );
if (renamePrefix == null || !renamePrefix.equals( prefix ))
break;
}
i -= 8;
}
assert i > 0;
}
}
_namespaceStack.add( _uriMap.get( uri ) );
_namespaceStack.add( uri );
if (renameUri != null)
{
_namespaceStack.add( _uriMap.get( renameUri ) );
_namespaceStack.add( renameUri );
}
else
{
_namespaceStack.add( null );
_namespaceStack.add( null );
}
_namespaceStack.add( prefix );
_namespaceStack.add( _prefixMap.get( prefix ) );
_namespaceStack.add( prefix );
_namespaceStack.add( uri );
_uriMap.put( uri, prefix );
_prefixMap.put( prefix, uri );
if (renameUri != null)
_uriMap.put( renameUri, renamePrefix );
}
private final void popMappings ( )
{
for ( ; ; )
{
int i = _namespaceStack.size();
if (i == 0)
break;
if (_namespaceStack.get( i - 1 ) == null)
{
_namespaceStack.remove( i - 1 );
break;
}
Object oldUri = _namespaceStack.get( i - 7 );
Object oldPrefix = _namespaceStack.get( i - 8 );
if (oldPrefix == null)
_uriMap.remove( oldUri );
else
_uriMap.put( oldUri, oldPrefix );
oldPrefix = _namespaceStack.get( i - 4 );
oldUri = _namespaceStack.get( i - 3 );
if (oldUri == null)
_prefixMap.remove( oldPrefix );
else
_prefixMap.put( oldPrefix, oldUri );
String uri = (String) _namespaceStack.get( i - 5 );
if (uri != null)
_uriMap.put( uri, _namespaceStack.get( i - 6 ) );
// Hahahahahaha -- :-(
_namespaceStack.remove( i - 1 );
_namespaceStack.remove( i - 2 );
_namespaceStack.remove( i - 3 );
_namespaceStack.remove( i - 4 );
_namespaceStack.remove( i - 5 );
_namespaceStack.remove( i - 6 );
_namespaceStack.remove( i - 7 );
_namespaceStack.remove( i - 8 );
}
}
private final void dumpMappings ( )
{
for ( int i = _namespaceStack.size() ; i > 0 ; )
{
if (_namespaceStack.get( i - 1 ) == null)
{
System.out.println( "----------------" );
i--;
continue;
}
System.out.print( "Mapping: " );
System.out.print( _namespaceStack.get( i - 2 ) );
System.out.print( " -> " );
System.out.print( _namespaceStack.get( i - 1 ) );
System.out.println();
System.out.print( "Prefix Undo: " );
System.out.print( _namespaceStack.get( i - 4 ) );
System.out.print( " -> " );
System.out.print( _namespaceStack.get( i - 3 ) );
System.out.println();
System.out.print( "Uri Rename: " );
System.out.print( _namespaceStack.get( i - 5 ) );
System.out.print( " -> " );
System.out.print( _namespaceStack.get( i - 6 ) );
System.out.println();
System.out.print( "UriUndo: " );
System.out.print( _namespaceStack.get( i - 7 ) );
System.out.print( " -> " );
System.out.print( _namespaceStack.get( i - 8 ) );
System.out.println();
System.out.println();
i -= 8;
}
}
private final String ensureMapping (
String uri, String candidatePrefix,
boolean considerCreatingDefault, boolean mustHavePrefix )
{
assert uri != null;
// Can be called for no-namespaced things
if (uri.length() == 0)
return null;
String prefix = (String) _uriMap.get( uri );
if (prefix != null && (prefix.length() > 0 || !mustHavePrefix))
return prefix;
//
// I try prefixes from a number of places, in order:
//
// 1) What was passed in
// 2) The optional suggestions (for uri's)
// 3) The default mapping is allowed
// 4) ns#++
//
if (candidatePrefix != null && candidatePrefix.length() == 0)
candidatePrefix = null;
if (candidatePrefix == null || !tryPrefix( candidatePrefix ))
{
if (_suggestedPrefixes != null &&
_suggestedPrefixes.containsKey( uri ) &&
tryPrefix( (String) _suggestedPrefixes.get( uri ) ))
{
candidatePrefix = (String) _suggestedPrefixes.get( uri );
}
else if (considerCreatingDefault && _useDefaultNamespace && tryPrefix( "" ))
candidatePrefix = "";
else
{
String basePrefix = QNameHelper.suggestPrefix( uri );
candidatePrefix = basePrefix;
for ( int i = 1 ; ; i++ )
{
if (tryPrefix( candidatePrefix ))
break;
candidatePrefix = basePrefix + i;
}
}
}
assert candidatePrefix != null;
syntheticNamespace( candidatePrefix, uri, considerCreatingDefault );
addMapping( candidatePrefix, uri );
return candidatePrefix;
}
protected final String getUriMapping ( String uri )
{
assert _uriMap.get( uri ) != null;
return (String) _uriMap.get( uri );
}
String getNonDefaultUriMapping ( String uri )
{
String prefix = (String) _uriMap.get( uri );
if (prefix != null && prefix.length() > 0)
return prefix;
for ( Iterator keys = _prefixMap.keySet().iterator() ; keys.hasNext() ; )
{
prefix = (String) keys.next();
if (prefix.length() > 0 && _prefixMap.get( prefix ).equals( uri ))
return prefix;
}
assert false : "Could not find non-default mapping";
return null;
}
private final boolean tryPrefix ( String prefix )
{
if (prefix == null || Locale.beginsWithXml( prefix ))
return false;
String existingUri = (String) _prefixMap.get( prefix );
// If the prefix is currently mapped, then try another prefix. A
// special case is that of trying to map the default prefix ("").
// Here, there always exists a default mapping. If this is the
// mapping we found, then remap it anyways. I use != to compare
// strings because I want to test for the specific initial default
// uri I added when I initialized the saver.
if (existingUri != null && (prefix.length() > 0 || existingUri != _initialDefaultUri))
return false;
return true;
}
public final String getNamespaceForPrefix ( String prefix )
{
assert !prefix.equals( "xml" ) || _prefixMap.get( prefix ).equals( Locale._xml1998Uri );
return (String) _prefixMap.get( prefix );
}
protected Map getPrefixMap()
{
return _prefixMap;
}
//
//
//
static final class SynthNamespaceSaver extends Saver
{
LinkedHashMap _synthNamespaces = new LinkedHashMap();
SynthNamespaceSaver ( Cur c, XmlOptions options )
{
super( c, options );
}
protected void syntheticNamespace (
String prefix, String uri, boolean considerCreatingDefault )
{
_synthNamespaces.put( uri, considerCreatingDefault ? "" : prefix );
}
protected boolean emitElement (
SaveCur c, ArrayList attrNames, ArrayList attrValues ) { return false; }
protected void emitFinish ( SaveCur c ) { }
protected void emitText ( SaveCur c ) { }
protected void emitComment ( SaveCur c ) { }
protected void emitProcinst ( SaveCur c ) { }
protected void emitDocType ( String docTypeName, String publicId, String systemId ) { }
protected void emitStartDoc ( SaveCur c ) { }
protected void emitEndDoc ( SaveCur c ) { }
}
//
//
//
static final class TextSaver extends Saver
{
TextSaver ( Cur c, XmlOptions options, String encoding )
{
super( c, options );
boolean noSaveDecl =
options != null && options.hasOption( XmlOptions.SAVE_NO_XML_DECL );
if (options != null && options.hasOption(XmlOptions.SAVE_CDATA_LENGTH_THRESHOLD))
_cdataLengthThreshold = ((Integer)options.get(XmlOptions.SAVE_CDATA_LENGTH_THRESHOLD)).intValue();
if (options != null && options.hasOption(XmlOptions.SAVE_CDATA_ENTITY_COUNT_THRESHOLD))
_cdataEntityCountThreshold = ((Integer)options.get(XmlOptions.SAVE_CDATA_ENTITY_COUNT_THRESHOLD)).intValue();
if (options != null && options.hasOption(XmlOptions.LOAD_SAVE_CDATA_BOOKMARKS) )
_useCDataBookmarks = true;
if (options != null && options.hasOption(XmlOptions.SAVE_PRETTY_PRINT) )
_isPrettyPrint = true;
_in = _out = 0;
_free = 0;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
if (encoding != null && !noSaveDecl)
{
XmlDocumentProperties props = Locale.getDocProps( c, false );
String version = props == null ? null : props.getVersion();
if (version == null)
version = "1.0";
emit( "<?xml version=\"" );
emit( version );
emit( "\" encoding=\"" + encoding + "\"?>" + _newLine );
}
}
protected boolean emitElement ( SaveCur c, ArrayList attrNames, ArrayList attrValues )
{
assert c.isElem();
emit( '<' );
emitName( c.getName(), false );
if (saveNamespacesFirst())
emitNamespacesHelper();
for ( int i = 0 ; i < attrNames.size() ; i++ )
emitAttrHelper( (QName) attrNames.get( i ), (String) attrValues.get( i ) );
if (!saveNamespacesFirst())
emitNamespacesHelper();
if (!c.hasChildren() && !c.hasText())
{
emit( '/', '>' );
return true;
}
else
{
emit( '>' );
return false;
}
}
protected void emitFinish ( SaveCur c )
{
emit( '<', '/' );
emitName( c.getName(), false );
emit( '>' );
}
protected void emitXmlns ( String prefix, String uri )
{
assert prefix != null;
assert uri != null;
emit( "xmlns" );
if (prefix.length() > 0)
{
emit( ':' );
emit( prefix );
}
emit( '=', '\"' );
// TODO - must encode uri properly
emit( uri );
entitizeAttrValue(false);
emit( '"' );
}
private void emitNamespacesHelper ( )
{
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
emit( ' ' );
emitXmlns( mappingPrefix(), mappingUri() );
}
}
private void emitAttrHelper ( QName attrName, String attrValue )
{
emit( ' ' );
emitName( attrName, true );
emit( '=', '\"' );
emit( attrValue );
entitizeAttrValue(true);
emit( '"' );
}
protected void emitText ( SaveCur c )
{
assert c.isText();
// c.isTextCData() is expensive do it only if useCDataBookmarks option is enabled
boolean forceCData = _useCDataBookmarks && c.isTextCData();
emit( c );
entitizeContent( forceCData );
}
protected void emitComment ( SaveCur c )
{
assert c.isComment();
emit( "<!--" );
c.push();
c.next();
emit( c );
c.pop();
entitizeComment();
emit( "-->" );
}
protected void emitProcinst ( SaveCur c )
{
assert c.isProcinst();
emit( "<?" );
// TODO - encoding issues here?
emit( c.getName().getLocalPart() );
c.push();
c.next();
if (c.isText())
{
emit( " " );
emit( c );
entitizeProcinst();
}
c.pop();
emit( "?>" );
}
private void emitLiteral ( String literal )
{
// TODO: systemId production http://www.w3.org/TR/REC-xml/#NT-SystemLiteral
// TODO: publicId production http://www.w3.org/TR/REC-xml/#NT-PubidLiteral
if (literal.indexOf( "\"" ) < 0)
{
emit( '\"' );
emit( literal );
emit( '\"' );
}
else
{
emit( '\'' );
emit( literal );
emit( '\'' );
}
}
protected void emitDocType ( String docTypeName, String publicId, String systemId )
{
assert docTypeName != null;
emit( "<!DOCTYPE " );
emit( docTypeName );
if (publicId == null && systemId != null)
{
emit( " SYSTEM " );
emitLiteral( systemId );
}
else if (publicId != null)
{
emit( " PUBLIC " );
emitLiteral( publicId );
emit( " " );
emitLiteral( systemId );
}
emit( ">" );
emit( _newLine );
}
protected void emitStartDoc ( SaveCur c )
{
}
protected void emitEndDoc ( SaveCur c )
{
}
//
//
//
private void emitName ( QName name, boolean needsPrefix )
{
assert name != null;
String uri = name.getNamespaceURI();
assert uri != null;
if (uri.length() != 0)
{
String prefix = name.getPrefix();
String mappedUri = getNamespaceForPrefix( prefix );
if (mappedUri == null || !mappedUri.equals( uri ))
prefix = getUriMapping( uri );
// Attrs need a prefix. If I have not found one, then there must be a default
// prefix obscuring the prefix needed for this attr. Find it manually.
// NOTE - Consider keeping the currently mapped default URI separate fromn the
// _urpMap and _prefixMap. This way, I would not have to look it up manually
// here
if (needsPrefix && prefix.length() == 0)
prefix = getNonDefaultUriMapping( uri );
if (prefix.length() > 0)
{
emit( prefix );
emit( ':' );
}
}
assert name.getLocalPart().length() > 0;
emit( name.getLocalPart() );
}
private void emit ( char ch )
{
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
preEmit( 1 );
_buf[ _in ] = ch;
_in = (_in + 1) % _buf.length;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
}
private void emit ( char ch1, char ch2 )
{
if( preEmit( 2 ) )
return;
_buf[ _in ] = ch1;
_in = (_in + 1) % _buf.length;
_buf[ _in ] = ch2;
_in = (_in + 1) % _buf.length;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
}
private void emit ( String s )
{
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
int cch = s == null ? 0 : s.length();
if (preEmit( cch ))
return;
int chunk;
if (_in <= _out || cch < (chunk = _buf.length - _in))
{
s.getChars( 0, cch, _buf, _in );
_in += cch;
}
else
{
s.getChars( 0, chunk, _buf, _in );
s.getChars( chunk, cch, _buf, 0 );
_in = (_in + cch) % _buf.length;
}
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
}
private void emit ( SaveCur c )
{
if (c.isText())
{
Object src = c.getChars();
int cch = c._cchSrc;
if (preEmit( cch ))
return;
int chunk;
if (_in <= _out || cch < (chunk = _buf.length - _in))
{
CharUtil.getChars( _buf, _in, src, c._offSrc, cch );
_in += cch;
}
else
{
CharUtil.getChars( _buf, _in, src, c._offSrc, chunk );
CharUtil.getChars( _buf, 0, src, c._offSrc + chunk, cch - chunk );
_in = (_in + cch) % _buf.length;
}
}
else
preEmit( 0 );
}
private boolean preEmit ( int cch )
{
assert cch >= 0;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
_lastEmitCch = cch;
if (cch == 0)
return true;
if (_free <= cch)
resize( cch, -1 );
assert cch <= _free;
int used = getAvailable();
// if we are about to emit and there is noting in the buffer, reset
// the buffer to be at the beginning so as to not grow it anymore
// than needed.
if (used == 0)
{
assert _in == _out;
assert _free == _buf.length;
_in = _out = 0;
}
_lastEmitIn = _in;
_free -= cch;
assert _free >= 0;
assert _buf==null || _free == (_in>=_out ? _buf.length - (_in - _out) : _out - _in ) - cch : "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) - cch) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in - cch ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length - cch) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
return false;
}
private void entitizeContent ( boolean forceCData )
{
assert _free >=0;
if (_lastEmitCch == 0)
return;
int i = _lastEmitIn;
final int n = _buf.length;
boolean hasCharToBeReplaced = false;
int count = 0;
char prevChar = 0;
char prevPrevChar = 0;
for ( int cch = _lastEmitCch ; cch > 0 ; cch-- )
{
char ch = _buf[ i ];
if (ch == '<' || ch == '&')
count++;
else if (prevPrevChar == ']' && prevChar == ']' && ch == '>' )
hasCharToBeReplaced = true;
else if (isBadChar( ch ) || isEscapedChar( ch ) || (!_isPrettyPrint && ch == '\r') )
hasCharToBeReplaced = true;
if (++i == n)
i = 0;
prevPrevChar = prevChar;
prevChar = ch;
}
if (!forceCData && count == 0 && !hasCharToBeReplaced && count<_cdataEntityCountThreshold)
return;
i = _lastEmitIn;
//
// Heuristic for knowing when to save out stuff as a CDATA.
//
if (forceCData || (_lastEmitCch > _cdataLengthThreshold && count > _cdataEntityCountThreshold) )
{
boolean lastWasBracket = _buf[ i ] == ']';
i = replace( i, "<![CDATA[" + _buf[ i ] );
boolean secondToLastWasBracket = lastWasBracket;
lastWasBracket = _buf[ i ] == ']';
if (++i == _buf.length)
i = 0;
for ( int cch = _lastEmitCch - 2 ; cch > 0 ; cch-- )
{
char ch = _buf[ i ];
if (ch == '>' && secondToLastWasBracket && lastWasBracket)
i = replace( i, "]]>><![CDATA[" );
else if (isBadChar( ch ))
i = replace( i, "?" );
else
i++;
secondToLastWasBracket = lastWasBracket;
lastWasBracket = ch == ']';
if (i == _buf.length)
i = 0;
}
emit( "]]>" );
}
else
{
char ch = 0, ch_1 = 0, ch_2;
for ( int cch = _lastEmitCch ; cch > 0 ; cch-- )
{
ch_2 = ch_1;
ch_1 = ch;
ch = _buf[ i ];
if (ch == '<')
i = replace( i, "&lt;" );
else if (ch == '&')
i = replace( i, "&amp;" );
else if (ch == '>' && ch_1 == ']' && ch_2 == ']')
i = replace( i, "&gt;" );
else if (isBadChar( ch ))
i = replace( i, "?" );
else if (!_isPrettyPrint && ch == '\r')
i = replace( i, "&#13;" );
else if (isEscapedChar( ch ))
i = replace( i, _replaceChar.getEscapedString( ch ) );
else
i++;
if (i == _buf.length)
i = 0;
}
}
}
private void entitizeAttrValue ( boolean replaceEscapedChar )
{
if (_lastEmitCch == 0)
return;
int i = _lastEmitIn;
for ( int cch = _lastEmitCch ; cch > 0 ; cch-- )
{
char ch = _buf[ i ];
if (ch == '<')
i = replace( i, "&lt;" );
else if (ch == '&')
i = replace( i, "&amp;" );
else if (ch == '"')
i = replace( i, "&quot;" );
else if (isEscapedChar( ch ))
{
if (replaceEscapedChar)
i = replace( i, _replaceChar.getEscapedString( ch ) );
}
else
i++;
if (i == _buf.length)
i = 0;
}
}
private void entitizeComment ( )
{
if (_lastEmitCch == 0)
return;
int i = _lastEmitIn;
boolean lastWasDash = false;
for ( int cch = _lastEmitCch ; cch > 0 ; cch-- )
{
char ch = _buf[ i ];
if (isBadChar( ch ))
i = replace( i, "?" );
else if (ch == '-')
{
if (lastWasDash)
{
// Replace "--" with "- " to make well formed
i = replace( i, " " );
lastWasDash = false;
}
else
{
lastWasDash = true;
i++;
}
}
else
{
lastWasDash = false;
i++;
}
if (i == _buf.length)
i = 0;
}
// Because I have only replaced chars with single chars,
// _lastEmitIn will still be ok
int offset = (_lastEmitIn + _lastEmitCch - 1) % _buf.length;
if (_buf[ offset ] == '-')
i = replace( offset, " " );
}
private void entitizeProcinst ( )
{
if (_lastEmitCch == 0)
return;
int i = _lastEmitIn;
boolean lastWasQuestion = false;
for ( int cch = _lastEmitCch ; cch > 0 ; cch-- )
{
char ch = _buf[ i ];
if (isBadChar( ch ))
i = replace( i, "?" );
if (ch == '>')
{
// TODO - Had to convert to a space here ... imples not well formed XML
if (lastWasQuestion)
i = replace( i, " " );
else
i++;
lastWasQuestion = false;
}
else
{
lastWasQuestion = ch == '?';
i++;
}
if (i == _buf.length)
i = 0;
}
}
/**
* Test if a character is to be replaced with an escaped value
*/
private boolean isEscapedChar ( char ch )
{
return ( null != _replaceChar && _replaceChar.containsChar( ch ) );
}
private int replace ( int i, String replacement )
{
assert replacement.length() > 0;
int dCch = replacement.length() - 1;
if (dCch == 0)
{
_buf[ i ] = replacement.charAt( 0 );
return i + 1;
}
assert _free >= 0;
if (dCch > _free)
i = resize( dCch, i );
assert _free >= 0;
assert _free >= dCch;
assert getAvailable() > 0;
int charsToCopy = dCch + 1;
if (_out > _in && i >= _out)
{
System.arraycopy( _buf, _out, _buf, _out - dCch, i - _out );
_out -= dCch;
i -= dCch;
}
else
{
assert i < _in;
int availableEndChunk = _buf.length - _in;
if ( dCch <= availableEndChunk )
{
System.arraycopy( _buf, i, _buf, i + dCch, _in - i );
_in = ( _in + dCch) % _buf.length;
}
else if ( dCch <= availableEndChunk + _in - i - 1 )
{
int numToCopyToStart = dCch - availableEndChunk;
System.arraycopy( _buf, _in-numToCopyToStart, _buf, 0, numToCopyToStart );
System.arraycopy( _buf, i+1, _buf, i+1+dCch, _in-i-1-numToCopyToStart);
_in = numToCopyToStart;
}
else
{
int numToCopyToStart = _in - i - 1;
charsToCopy = availableEndChunk + _in - i;
System.arraycopy( _buf, _in-numToCopyToStart, _buf, dCch-charsToCopy+1, numToCopyToStart );
replacement.getChars( charsToCopy, dCch + 1, _buf, 0);
_in = numToCopyToStart + dCch - charsToCopy + 1;
}
}
replacement.getChars( 0, charsToCopy, _buf, i );
_free -= dCch;
assert _free >= 0;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
return (i + dCch + 1) % _buf.length;
}
//
//
//
private int ensure ( int cch )
{
// Even if we're asked to ensure nothing, still try to ensure
// atleast one character so we can determine if we're at the
// end of the stream.
if (cch <= 0)
cch = 1;
int available = getAvailable();
for ( ; available < cch ; available = getAvailable() )
if (!process())
break;
assert available == getAvailable();
// if (available == 0)
// return 0;
return available;
}
int getAvailable ( )
{
return _buf == null ? 0 : _buf.length - _free;
}
private int resize ( int cch, int i )
{
assert _free >= 0;
assert cch > 0;
assert cch >= _free;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
int newLen = _buf == null ? _initialBufSize : _buf.length * 2;
int used = getAvailable();
while ( newLen - used < cch )
newLen *= 2;
char[] newBuf = new char [ newLen ];
if (used > 0)
{
if (_in > _out)
{
assert i == -1 || (i >= _out && i < _in);
System.arraycopy( _buf, _out, newBuf, 0, used );
i -= _out;
}
else
{
assert i == -1 || (i >= _out || i < _in);
System.arraycopy( _buf, _out, newBuf, 0, used - _in );
System.arraycopy( _buf, 0, newBuf, used - _in, _in );
i = i >= _out ? i - _out : i + _out;
}
_out = 0;
_in = used;
_free += newBuf.length - _buf.length;
}
else
{
_free = newBuf.length;
assert _in == 0 && _out == 0;
assert i == -1;
}
_buf = newBuf;
assert _free >= 0;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
return i;
}
public int read ( )
{
if (ensure( 1 ) == 0)
return -1;
assert getAvailable() > 0;
int ch = _buf[ _out ];
_out = (_out + 1) % _buf.length;
_free++;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
return ch;
}
public int read ( char[] cbuf, int off, int len )
{
// Check for end of stream even if there is no way to return
// characters because the Reader doc says to return -1 at end of
// stream.
int n;
if ((n = ensure( len )) == 0)
return -1;
if (cbuf == null || len <= 0)
return 0;
if (n < len)
len = n;
if (_out < _in)
{
System.arraycopy( _buf, _out, cbuf, off, len );
}
else
{
int chunk = _buf.length - _out;
if (chunk >= len)
System.arraycopy( _buf, _out, cbuf, off, len );
else
{
System.arraycopy( _buf, _out, cbuf, off, chunk );
System.arraycopy( _buf, 0, cbuf, off + chunk, len - chunk );
}
}
_out = (_out + len) % _buf.length;
_free += len;
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
assert _free >= 0;
return len;
}
public int write ( Writer writer, int cchMin )
{
while ( getAvailable() < cchMin)
{
if (!process())
break;
}
int charsAvailable = getAvailable();
if (charsAvailable > 0)
{
// I don't want to deal with the circular cases
assert _out == 0;
assert _in >= _out : "_in:" + _in + " < _out:" + _out;
assert _free == _buf.length - _in;
try
{
//System.out.println("-------------\nWriting in corverter: TextSaver.write():1703 " + charsAvailable + " chars\n" + new String(_buf, 0, charsAvailable));
writer.write( _buf, 0, charsAvailable );
writer.flush();
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
_free += charsAvailable;
assert _free >= 0;
_in = 0;
}
assert _buf==null ||
(_out<_in && _free == _buf.length - ( _in - _out ) ) || // data in the middle, free on the edges
(_out>_in && _free == _out - _in ) || // data on the edges, free in the middle
(_out==_in && _free == _buf.length) || // no data, all buffer free
(_out==_in && _free == 0) // buffer full
: "_buf.length:" + _buf.length + " _in:" + _in + " _out:" + _out + " _free:" + _free;
return charsAvailable;
}
public String saveToString ( )
{
// We're gonna build a string. Instead of using StringBuffer, may
// as well use my buffer here. Fill the whole sucker up and
// create a String!
while ( process() )
;
assert _out == 0;
int available = getAvailable();
return available == 0 ? "" : new String( _buf, _out, available );
}
//
//
//
private static final int _initialBufSize = 4096;
private int _cdataLengthThreshold = 32;
private int _cdataEntityCountThreshold = 5;
private boolean _useCDataBookmarks = false;
private boolean _isPrettyPrint = false;
private int _lastEmitIn;
private int _lastEmitCch;
private int _free;
private int _in;
private int _out;
private char[] _buf;
/*
_buf is a circular buffer, useful data is before _in up to _out, there are 2 posible configurations:
1: _in<=_out |data|_in empty _out|data|
2: _out<_in |empty _out|data|_in empty|
_free is used to keep around the remaining empty space in the bufer so assert _buf==null || _free == (_in>=_out ? _buf.length - (_in - _out) : _out - _in ) ;
*/
}
static final class OptimizedForSpeedSaver
extends Saver
{
Writer _w;
private char[] _buf = new char[1024];
static private class SaverIOException
extends RuntimeException
{
SaverIOException(IOException e)
{
super(e);
}
}
OptimizedForSpeedSaver(Cur cur, Writer writer)
{
super(cur, XmlOptions.maskNull(null));
_w = writer;
}
static void save(Cur cur, Writer writer)
throws IOException
{
try
{
Saver saver = new OptimizedForSpeedSaver(cur, writer);
while(saver.process())
{}
}
catch (SaverIOException e)
{
throw (IOException)e.getCause();
}
}
private void emit(String s)
{
try
{
_w.write(s);
}
catch (IOException e)
{
throw new SaverIOException(e);
}
}
private void emit(char c)
{
try
{
_buf[0] = c;
_w.write(_buf, 0, 1);
}
catch (IOException e)
{
throw new SaverIOException(e);
}
}
private void emit(char c1, char c2)
{
try
{
_buf[0] = c1;
_buf[1] = c2;
_w.write(_buf, 0 , 2);
}
catch (IOException e)
{
throw new SaverIOException(e);
}
}
private void emit(char[] buf, int start, int len)
{
try
{
_w.write(buf, start, len);
}
catch (IOException e)
{
throw new SaverIOException(e);
}
}
protected boolean emitElement ( SaveCur c, ArrayList attrNames, ArrayList attrValues )
{
assert c.isElem();
emit( '<' );
emitName( c.getName(), false );
for ( int i = 0 ; i < attrNames.size() ; i++ )
emitAttrHelper( (QName) attrNames.get( i ), (String) attrValues.get( i ) );
if (!saveNamespacesFirst())
emitNamespacesHelper();
if (!c.hasChildren() && !c.hasText())
{
emit( '/', '>' );
return true;
}
else
{
emit( '>' );
return false;
}
}
protected void emitFinish ( SaveCur c )
{
emit( '<', '/' );
emitName( c.getName(), false );
emit( '>' );
}
protected void emitXmlns ( String prefix, String uri )
{
assert prefix != null;
assert uri != null;
emit( "xmlns" );
if (prefix.length() > 0)
{
emit( ':' );
emit( prefix );
}
emit( '=', '\"' );
// TODO - must encode uri properly
emitAttrValue(uri);
emit( '"' );
}
private void emitNamespacesHelper ( )
{
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
emit( ' ' );
emitXmlns( mappingPrefix(), mappingUri() );
}
}
private void emitAttrHelper ( QName attrName, String attrValue )
{
emit( ' ' );
emitName( attrName, true );
emit( '=', '\"' );
emitAttrValue(attrValue);
emit( '"' );
}
protected void emitComment ( SaveCur c )
{
assert c.isComment();
emit( "<!--" );
c.push();
c.next();
emitCommentText( c );
c.pop();
emit( "-->" );
}
protected void emitProcinst ( SaveCur c )
{
assert c.isProcinst();
emit( "<?" );
// TODO - encoding issues here?
emit( c.getName().getLocalPart() );
c.push();
c.next();
if (c.isText())
{
emit( ' ' );
emitPiText( c );
}
c.pop();
emit( "?>" );
}
protected void emitDocType ( String docTypeName, String publicId, String systemId )
{
assert docTypeName != null;
emit( "<!DOCTYPE " );
emit( docTypeName );
if (publicId == null && systemId != null)
{
emit( " SYSTEM " );
emitLiteral( systemId );
}
else if (publicId != null)
{
emit( " PUBLIC " );
emitLiteral( publicId );
emit(' ');
emitLiteral( systemId );
}
emit( '>' );
emit( _newLine );
}
protected void emitStartDoc ( SaveCur c )
{
}
protected void emitEndDoc ( SaveCur c )
{
}
//
//
//
private void emitName ( QName name, boolean needsPrefix )
{
assert name != null;
String uri = name.getNamespaceURI();
assert uri != null;
if (uri.length() != 0)
{
String prefix = name.getPrefix();
String mappedUri = getNamespaceForPrefix( prefix );
if (mappedUri == null || !mappedUri.equals( uri ))
prefix = getUriMapping( uri );
// Attrs need a prefix. If I have not found one, then there must be a default
// prefix obscuring the prefix needed for this attr. Find it manually.
// NOTE - Consider keeping the currently mapped default URI separate fromn the
// _urpMap and _prefixMap. This way, I would not have to look it up manually
// here
if (needsPrefix && prefix.length() == 0)
prefix = getNonDefaultUriMapping( uri );
if (prefix.length() > 0)
{
emit( prefix );
emit( ':' );
}
}
assert name.getLocalPart().length() > 0;
emit( name.getLocalPart() );
}
private void emitAttrValue ( CharSequence attVal)
{
int len = attVal.length();
for ( int i = 0; i<len ; i++ )
{
char ch = attVal.charAt(i);
if (ch == '<')
emit( "&lt;" );
else if (ch == '&')
emit( "&amp;" );
else if (ch == '"')
emit( "&quot;" );
else
emit(ch);
}
}
private void emitLiteral ( String literal )
{
// TODO: systemId production http://www.w3.org/TR/REC-xml/#NT-SystemLiteral
// TODO: publicId production http://www.w3.org/TR/REC-xml/#NT-PubidLiteral
if (literal.indexOf( "\"" ) < 0)
{
emit( '\"' );
emit( literal );
emit( '\"' );
}
else
{
emit( '\'' );
emit( literal );
emit( '\'' );
}
}
protected void emitText ( SaveCur c )
{
assert c.isText();
Object src = c.getChars();
int cch = c._cchSrc;
int off = c._offSrc;
int index = 0;
int indexLimit = 0;
while( index<cch )
{
indexLimit = index + 512 > cch ? cch : index + 512;
CharUtil.getChars( _buf, 0, src, off+index, indexLimit-index );
entitizeAndWriteText(indexLimit-index);
index = indexLimit;
}
}
protected void emitPiText ( SaveCur c )
{
assert c.isText();
Object src = c.getChars();
int cch = c._cchSrc;
int off = c._offSrc;
int index = 0;
int indexLimit = 0;
while( index<cch )
{
indexLimit = index + 512 > cch ? cch : 512;
CharUtil.getChars( _buf, 0, src, off+index, indexLimit );
entitizeAndWritePIText(indexLimit-index);
index = indexLimit;
}
}
protected void emitCommentText ( SaveCur c )
{
assert c.isText();
Object src = c.getChars();
int cch = c._cchSrc;
int off = c._offSrc;
int index = 0;
int indexLimit = 0;
while( index<cch )
{
indexLimit = index + 512 > cch ? cch : 512;
CharUtil.getChars( _buf, 0, src, off+index, indexLimit );
entitizeAndWriteCommentText(indexLimit-index);
index = indexLimit;
}
}
private void entitizeAndWriteText(int bufLimit)
{
int index = 0;
for (int i = 0; i < bufLimit; i++)
{
char c = _buf[i];
switch(c)
{
case '<':
emit(_buf, index, i-index);
emit("&lt;");
index = i+1;
break;
case '&':
emit(_buf, index, i-index);
emit("&amp;");
index = i+1;
break;
}
}
emit(_buf, index, bufLimit-index);
}
private void entitizeAndWriteCommentText ( int bufLimit )
{
boolean lastWasDash = false;
for ( int i=0 ; i<bufLimit ; i++ )
{
char ch = _buf[ i ];
if (isBadChar( ch ))
_buf[i] = '?';
else if (ch == '-')
{
if (lastWasDash)
{
// Replace "--" with "- " to make well formed
_buf[i] = ' ';
lastWasDash = false;
}
else
{
lastWasDash = true;
}
}
else
{
lastWasDash = false;
}
if (i == _buf.length)
i = 0;
}
if (_buf[ bufLimit-1 ] == '-')
_buf[ bufLimit-1 ] = ' ';
emit(_buf, 0, bufLimit);
}
private void entitizeAndWritePIText(int bufLimit)
{
boolean lastWasQuestion = false;
for ( int i=0 ; i<bufLimit ; i++ )
{
char ch = _buf[ i ];
if (isBadChar( ch ))
{
_buf[i] = '?';
ch = '?';
}
if (ch == '>')
{
// Had to convert to a space here ... imples not well formed XML
if (lastWasQuestion)
_buf[i] = ' ';
lastWasQuestion = false;
}
else
{
lastWasQuestion = ch == '?';
}
}
emit(_buf, 0, bufLimit);
}
}
static final class TextReader extends Reader
{
TextReader ( Cur c, XmlOptions options )
{
_textSaver = new TextSaver( c, options, null );
_locale = c._locale;
_closed = false;
}
public void close ( ) throws IOException { _closed = true; }
public boolean ready ( ) throws IOException { return !_closed; }
public int read ( ) throws IOException
{
checkClosed();
if (_locale.noSync()) { _locale.enter(); try { return _textSaver.read(); } finally { _locale.exit(); } }
else synchronized ( _locale ) { _locale.enter(); try { return _textSaver.read(); } finally { _locale.exit(); } }
}
public int read ( char[] cbuf ) throws IOException
{
checkClosed();
if (_locale.noSync()) { _locale.enter(); try { return _textSaver.read( cbuf, 0, cbuf == null ? 0 : cbuf.length ); } finally { _locale.exit(); } }
else synchronized ( _locale ) { _locale.enter(); try { return _textSaver.read( cbuf, 0, cbuf == null ? 0 : cbuf.length ); } finally { _locale.exit(); } }
}
public int read ( char[] cbuf, int off, int len ) throws IOException
{
checkClosed();
if (_locale.noSync()) { _locale.enter(); try { return _textSaver.read( cbuf, off, len ); } finally { _locale.exit(); } }
else synchronized ( _locale ) { _locale.enter(); try { return _textSaver.read( cbuf, off, len ); } finally { _locale.exit(); } }
}
private void checkClosed ( ) throws IOException
{
if (_closed)
throw new IOException( "Reader has been closed" );
}
private Locale _locale;
private TextSaver _textSaver;
private boolean _closed;
}
static final class InputStreamSaver extends InputStream
{
InputStreamSaver ( Cur c, XmlOptions options )
{
_locale = c._locale;
_closed = false;
assert _locale.entered();
options = XmlOptions.maskNull( options );
_outStreamImpl = new OutputStreamImpl();
String encoding = null;
XmlDocumentProperties props = Locale.getDocProps( c, false );
if (props != null && props.getEncoding() != null)
encoding = EncodingMap.getIANA2JavaMapping( props.getEncoding() );
if (options.hasOption( XmlOptions.CHARACTER_ENCODING ))
encoding = (String) options.get( XmlOptions.CHARACTER_ENCODING );
if (encoding != null)
{
String ianaEncoding = EncodingMap.getJava2IANAMapping( encoding );
if (ianaEncoding != null)
encoding = ianaEncoding;
}
if (encoding == null)
encoding = EncodingMap.getJava2IANAMapping( "UTF8" );
String javaEncoding = EncodingMap.getIANA2JavaMapping( encoding );
if (javaEncoding == null)
throw new IllegalStateException( "Unknown encoding: " + encoding );
try
{
_converter = new OutputStreamWriter( _outStreamImpl, javaEncoding );
}
catch ( UnsupportedEncodingException e )
{
throw new RuntimeException( e );
}
_textSaver = new TextSaver( c, options, encoding );
}
public void close ( ) throws IOException
{
_closed = true;
}
private void checkClosed ( ) throws IOException
{
if (_closed)
throw new IOException( "Stream closed" );
}
// Having the gateway here is kinda slow for the single character case. It may be possible
// to only enter the gate when there are no chars in the buffer.
public int read ( ) throws IOException
{
checkClosed();
if (_locale.noSync()) { _locale.enter(); try { return _outStreamImpl.read(); } finally { _locale.exit(); } }
else synchronized ( _locale ) { _locale.enter(); try { return _outStreamImpl.read(); } finally { _locale.exit(); } }
}
public int read ( byte[] bbuf, int off, int len ) throws IOException
{
checkClosed();
if (bbuf == null)
throw new NullPointerException( "buf to read into is null" );
if (off < 0 || off > bbuf.length)
throw new IndexOutOfBoundsException( "Offset is not within buf" );
if (_locale.noSync()) { _locale.enter(); try { return _outStreamImpl.read( bbuf, off, len ); } finally { _locale.exit(); } }
else synchronized ( _locale ) { _locale.enter(); try { return _outStreamImpl.read( bbuf, off, len ); } finally { _locale.exit(); } }
}
private int ensure ( int cbyte )
{
// Even if we're asked to ensure nothing, still try to ensure
// atleast one byte so we can determine if we're at the
// end of the stream.
if (cbyte <= 0)
cbyte = 1;
int bytesAvailable = _outStreamImpl.getAvailable();
for ( ; bytesAvailable < cbyte ;
bytesAvailable = _outStreamImpl.getAvailable() )
{
if (_textSaver.write( _converter, 2048 ) < 2048)
break;
}
bytesAvailable = _outStreamImpl.getAvailable();
// if (bytesAvailable == 0)
// return 0;
return bytesAvailable;
}
public int available()
throws IOException
{
if (_locale.noSync())
{ _locale.enter(); try {
return ensure(1024);
} finally { _locale.exit(); } }
else
synchronized ( _locale )
{ _locale.enter(); try { return ensure(1024); } finally { _locale.exit(); } }
}
private final class OutputStreamImpl extends OutputStream
{
int read ( )
{
if (InputStreamSaver.this.ensure( 1 ) == 0)
return -1;
assert getAvailable() > 0;
int bite = _buf[ _out ];
_out = (_out + 1) % _buf.length;
_free++;
return bite;
}
int read ( byte[] bbuf, int off, int len )
{
// Check for end of stream even if there is no way to return
// characters because the Reader doc says to return -1 at end of
// stream.
int n;
if ((n = ensure( len )) == 0)
return -1;
if (bbuf == null || len <= 0)
return 0;
if (n < len)
len = n;
if (_out < _in)
{
System.arraycopy( _buf, _out, bbuf, off, len );
}
else
{
int chunk = _buf.length - _out;
if (chunk >= len)
System.arraycopy( _buf, _out, bbuf, off, len );
else
{
System.arraycopy( _buf, _out, bbuf, off, chunk );
System.arraycopy(
_buf, 0, bbuf, off + chunk, len - chunk );
}
}
_out = (_out + len) % _buf.length;
_free += len;
//System.out.println("------------------------\nRead out of queue: Saver:2440 InputStreamSaver.read() bbuf " + len + " bytes :\n" + new String(bbuf, off, len));
return len;
}
int getAvailable ( )
{
return _buf == null ? 0 : _buf.length - _free;
}
public void write ( int bite )
{
if (_free == 0)
resize( 1 );
assert _free > 0;
_buf[ _in ] = (byte) bite;
_in = (_in + 1) % _buf.length;
_free--;
}
public void write ( byte[] buf, int off, int cbyte )
{
assert cbyte >= 0;
//System.out.println("---------\nAfter converter, write in queue: OutputStreamImpl.write():Saver:2469 " + cbyte + " bytes \n" + new String(buf, off, cbyte));
if (cbyte == 0)
return;
if (_free < cbyte)
resize( cbyte );
if (_in == _out)
{
assert getAvailable() == 0;
assert _free == _buf.length - getAvailable();
_in = _out = 0;
}
int chunk = _buf.length - _in;
if (_in <= _out || cbyte < chunk)
{
System.arraycopy( buf, off, _buf, _in, cbyte );
_in += cbyte;
}
else
{
System.arraycopy( buf, off, _buf, _in, chunk );
System.arraycopy(
buf, off + chunk, _buf, 0, cbyte - chunk );
_in = (_in + cbyte) % _buf.length;
}
_free -= cbyte;
}
void resize ( int cbyte )
{
assert cbyte > _free : cbyte + " !> " + _free;
int newLen = _buf == null ? _initialBufSize : _buf.length * 2;
int used = getAvailable();
while ( newLen - used < cbyte )
newLen *= 2;
byte[] newBuf = new byte [ newLen ];
if (used > 0)
{
if (_in > _out)
System.arraycopy( _buf, _out, newBuf, 0, used );
else
{
System.arraycopy(
_buf, _out, newBuf, 0, used - _in );
System.arraycopy(
_buf, 0, newBuf, used - _in, _in );
}
_out = 0;
_in = used;
_free += newBuf.length - _buf.length;
}
else
{
_free = newBuf.length;
assert _in == _out;
}
_buf = newBuf;
}
private static final int _initialBufSize = 4096;
private int _free;
private int _in;
private int _out;
private byte[] _buf;
}
private Locale _locale;
private boolean _closed;
private OutputStreamImpl _outStreamImpl;
private TextSaver _textSaver;
private OutputStreamWriter _converter;
}
static final class XmlInputStreamSaver extends Saver
{
XmlInputStreamSaver ( Cur c, XmlOptions options )
{
super( c, options );
}
protected boolean emitElement(SaveCur c, ArrayList attrNames, ArrayList attrValues)
{
assert c.isElem();
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
enqueue( new StartPrefixMappingImpl( mappingPrefix(), mappingUri() ) );
}
StartElementImpl.AttributeImpl lastAttr = null;
StartElementImpl.AttributeImpl attributes = null;
StartElementImpl.AttributeImpl namespaces = null;
for ( int i=0; i<attrNames.size(); i++ )
{
XMLName attXMLName = computeName((QName)attrNames.get(i), this, true);
StartElementImpl.AttributeImpl attr =
new StartElementImpl.NormalAttributeImpl(attXMLName, (String)attrValues.get(i) );
if (attributes == null)
attributes = attr;
else
lastAttr._next = attr;
lastAttr = attr;
}
lastAttr = null;
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
String prefix = mappingPrefix();
String uri = mappingUri();
StartElementImpl.AttributeImpl attr =
new StartElementImpl.XmlnsAttributeImpl(prefix, uri);
if (namespaces == null)
namespaces = attr;
else
lastAttr._next = attr;
lastAttr = attr;
}
QName name = c.getName();
enqueue( new StartElementImpl( computeName(name, this, false), attributes, namespaces, getPrefixMap() ) );
return false; // still need to be called on end element
}
protected void emitFinish(SaveCur c)
{
if (c.isRoot())
enqueue( new EndDocumentImpl( ) );
else
{
XMLName xmlName = computeName(c.getName(), this, false);
enqueue( new EndElementImpl( xmlName ) );
}
emitEndPrefixMappings();
}
protected void emitText(SaveCur c)
{
assert c.isText();
Object src = c.getChars();
int cch = c._cchSrc;
int off = c._offSrc;
enqueue( new CharacterDataImpl( src, cch, off ) );
}
protected void emitComment(SaveCur c)
{
enqueue( new CommentImpl( c.getChars(), c._cchSrc, c._offSrc ) );
}
protected void emitProcinst(SaveCur c)
{
String target = null;
QName name = c.getName();
if (name!=null)
target = name.getLocalPart();
enqueue( new ProcessingInstructionImpl( target, c.getChars(), c._cchSrc, c._offSrc ) );
}
protected void emitDocType( String doctypeName, String publicID, String systemID )
{
enqueue( new StartDocumentImpl( systemID, null, true, null ) ); //todo
}
protected void emitStartDoc ( SaveCur c )
{
emitDocType(null, null, null);
}
protected void emitEndDoc ( SaveCur c )
{
enqueue( new EndDocumentImpl());
}
XMLEvent dequeue ( )
{
if (_out == null)
{
enterLocale();
try
{
if(!process())
return null;
}
finally
{
exitLocale();
}
}
if (_out == null)
return null;
XmlEventImpl e = _out;
if ((_out = _out._next) == null)
_in = null;
return e;
}
private void enqueue ( XmlEventImpl e )
{
assert e._next == null;
if (_in == null)
{
assert _out == null;
_out = _in = e;
}
else
{
_in._next = e;
_in = e;
}
}
//
//
//
protected void emitEndPrefixMappings ( )
{
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
String prevPrefixUri = null; // todo mappingPrevPrefixUri();
String prefix = mappingPrefix();
String uri = mappingUri();
if (prevPrefixUri == null)
enqueue( new EndPrefixMappingImpl( prefix ) );
else
{
enqueue( new ChangePrefixMappingImpl( prefix, uri, prevPrefixUri ) );
}
}
}
//
//
//
private static XMLName computeName ( QName name, Saver saver, boolean needsPrefix )
{
String uri = name.getNamespaceURI();
String local = name.getLocalPart();
assert uri != null;
assert local.length() > 0;
String prefix = null;
if (uri!=null && uri.length() != 0)
{
prefix = name.getPrefix();
String mappedUri = saver.getNamespaceForPrefix( prefix );
if (mappedUri == null || !mappedUri.equals( uri ))
prefix = saver.getUriMapping( uri );
// Attrs need a prefix. If I have not found one, then there must be a default
// prefix obscuring the prefix needed for this attr. Find it manually.
// NOTE - Consider keeping the currently mapped default URI separate fromn the
// _urpMap and _prefixMap. This way, I would not have to look it up manually
// here
if (needsPrefix && prefix.length() == 0)
prefix = saver.getNonDefaultUriMapping( uri );
}
return new XmlNameImpl( uri, local, prefix );
}
private static abstract class XmlEventImpl extends XmlEventBase
{
XmlEventImpl ( int type )
{
super( type );
}
public XMLName getName ( )
{
return null;
}
public XMLName getSchemaType ( )
{
throw new RuntimeException( "NYI" );
}
public boolean hasName ( )
{
return false;
}
public final Location getLocation ( )
{
// (orig v1 comment)TODO - perhaps I can save a location goober sometimes?
return null;
}
XmlEventImpl _next;
}
private static class StartDocumentImpl
extends XmlEventImpl implements StartDocument
{
StartDocumentImpl ( String systemID, String encoding, boolean isStandAlone, String version )
{
super( XMLEvent.START_DOCUMENT );
_systemID = systemID;
_encoding = encoding;
_standAlone = isStandAlone;
_version = version;
}
public String getSystemId ( )
{
return _systemID;
}
public String getCharacterEncodingScheme ( )
{
return _encoding;
}
public boolean isStandalone ( )
{
return _standAlone;
}
public String getVersion ( )
{
return _version;
}
String _systemID;
String _encoding;
boolean _standAlone;
String _version;
}
private static class StartElementImpl
extends XmlEventImpl implements StartElement
{
StartElementImpl ( XMLName name, AttributeImpl attributes, AttributeImpl namespaces, Map prefixMap )
{
super( XMLEvent.START_ELEMENT );
_name = name;
_attributes = attributes;
_namespaces = namespaces;
_prefixMap = prefixMap;
}
public boolean hasName()
{
return true;
}
public XMLName getName ( )
{
return _name;
}
public AttributeIterator getAttributes ( )
{
return new AttributeIteratorImpl( _attributes, null );
}
public AttributeIterator getNamespaces ( )
{
return new AttributeIteratorImpl( null, _namespaces );
}
public AttributeIterator getAttributesAndNamespaces ( )
{
return new AttributeIteratorImpl( _attributes, _namespaces );
}
public Attribute getAttributeByName ( XMLName xmlName )
{
for ( AttributeImpl a = _attributes ; a != null ; a = a._next )
{
if (xmlName.equals( a.getName() ))
return a;
}
return null;
}
public String getNamespaceUri ( String prefix )
{
return (String) _prefixMap.get( prefix == null ? "" : prefix );
}
public Map getNamespaceMap ( )
{
return _prefixMap;
}
private static class AttributeIteratorImpl
implements AttributeIterator
{
AttributeIteratorImpl( AttributeImpl attributes, AttributeImpl namespaces )
{
_attributes = attributes;
_namespaces = namespaces;
}
public Object monitor()
{
return this;
}
public Attribute next ( )
{
synchronized (monitor())
{
checkVersion();
AttributeImpl attr = null;
if (_attributes != null)
{
attr = _attributes;
_attributes = attr._next;
}
else if (_namespaces != null)
{
attr = _namespaces;
_namespaces = attr._next;
}
return attr;
}
}
public boolean hasNext ( )
{
synchronized (monitor())
{
checkVersion();
return _attributes != null || _namespaces != null;
}
}
public Attribute peek ( )
{
synchronized (monitor())
{
checkVersion();
if (_attributes != null)
return _attributes;
else if (_namespaces != null)
return _namespaces;
return null;
}
}
public void skip ( )
{
synchronized (monitor())
{
checkVersion();
if (_attributes != null)
_attributes = _attributes._next;
else if (_namespaces != null)
_namespaces = _namespaces._next;
}
}
private final void checkVersion ( )
{
// if (_version != _root.getVersion())
// throw new IllegalStateException( "Document changed" );
}
// private long _version;
private AttributeImpl _attributes;
private AttributeImpl _namespaces;
}
private static abstract class AttributeImpl implements Attribute
{
/**
* Don't forget to set _name
*/
AttributeImpl ()
{
}
public XMLName getName ( )
{
return _name;
}
public String getType ( )
{
// (from v1 impl) TODO - Make sure throwing away this DTD info is ok.
// (from v1 impl) Is there schema info which can return more useful info?
return "CDATA";
}
public XMLName getSchemaType ( )
{
// (from v1 impl) TODO - Can I return something reasonable here?
return null;
}
AttributeImpl _next;
protected XMLName _name;
}
private static class XmlnsAttributeImpl extends AttributeImpl
{
XmlnsAttributeImpl ( String prefix, String uri )
{
super();
_uri = uri;
String local;
if (prefix.length() == 0)
{
prefix = null;
local = "xmlns";
}
else
{
local = prefix;
prefix = "xmlns";
}
_name = new XmlNameImpl( null, local, prefix );
}
public String getValue ( )
{
return _uri;
}
private String _uri;
}
private static class NormalAttributeImpl extends AttributeImpl
{
NormalAttributeImpl (XMLName name, String value)
{
_name = name;
_value = value;
}
public String getValue ( )
{
return _value;
}
private String _value; // If invalid in the store
}
private XMLName _name;
private Map _prefixMap;
private AttributeImpl _attributes;
private AttributeImpl _namespaces;
}
private static class StartPrefixMappingImpl
extends XmlEventImpl implements StartPrefixMapping
{
StartPrefixMappingImpl ( String prefix, String uri )
{
super( XMLEvent.START_PREFIX_MAPPING );
_prefix = prefix;
_uri = uri;
}
public String getNamespaceUri ( )
{
return _uri;
}
public String getPrefix ( )
{
return _prefix;
}
private String _prefix, _uri;
}
private static class ChangePrefixMappingImpl
extends XmlEventImpl implements ChangePrefixMapping
{
ChangePrefixMappingImpl ( String prefix, String oldUri, String newUri )
{
super( XMLEvent.CHANGE_PREFIX_MAPPING );
_oldUri = oldUri;
_newUri = newUri;
_prefix = prefix;
}
public String getOldNamespaceUri ( )
{
return _oldUri;
}
public String getNewNamespaceUri ( )
{
return _newUri;
}
public String getPrefix ( )
{
return _prefix;
}
private String _oldUri, _newUri, _prefix;
}
private static class EndPrefixMappingImpl
extends XmlEventImpl implements EndPrefixMapping
{
EndPrefixMappingImpl ( String prefix )
{
super( XMLEvent.END_PREFIX_MAPPING );
_prefix = prefix;
}
public String getPrefix ( )
{
return _prefix;
}
private String _prefix;
}
private static class EndElementImpl
extends XmlEventImpl implements EndElement
{
EndElementImpl ( XMLName name )
{
super( XMLEvent.END_ELEMENT );
_name = name;
}
public boolean hasName ( )
{
return true;
}
public XMLName getName ( )
{
return _name;
}
private XMLName _name;
}
private static class EndDocumentImpl
extends XmlEventImpl implements EndDocument
{
EndDocumentImpl ( )
{
super( XMLEvent.END_DOCUMENT );
}
}
private static class TripletEventImpl
extends XmlEventImpl implements CharacterData
{
TripletEventImpl ( int eventType, Object obj, int cch, int off )
{
super(eventType);
_obj = obj;
_cch = cch;
_off = off;
}
public String getContent ( )
{
return CharUtil.getString(_obj, _off, _cch);
}
public boolean hasContent ( )
{
return _cch > 0;
}
private Object _obj;
private int _cch;
private int _off;
}
private static class CharacterDataImpl
extends TripletEventImpl implements CharacterData
{
CharacterDataImpl ( Object obj, int cch, int off )
{
super(XMLEvent.CHARACTER_DATA, obj, cch, off);
}
}
private static class CommentImpl
extends TripletEventImpl implements Comment
{
CommentImpl ( Object obj, int cch, int off )
{
super( XMLEvent.COMMENT, obj, cch, off);
}
}
private static class ProcessingInstructionImpl
extends TripletEventImpl implements ProcessingInstruction
{
ProcessingInstructionImpl ( String target, Object obj, int cch, int off)
{
super( XMLEvent.PROCESSING_INSTRUCTION, obj, cch, off);
_target = target;
}
public String getTarget ( )
{
return _target;
}
public String getData ( )
{
return getContent();
}
private String _target;
}
private XmlEventImpl _in, _out;
}
static final class XmlInputStreamImpl extends GenericXmlInputStream
{
XmlInputStreamImpl ( Cur cur, XmlOptions options )
{
_xmlInputStreamSaver =
new XmlInputStreamSaver( cur, options );
// Make the saver grind away just a bit to throw any exceptions
// related to the inability to create a stream on this xml
_xmlInputStreamSaver.process();
}
protected XMLEvent nextEvent ( ) throws XMLStreamException
{
return _xmlInputStreamSaver.dequeue();
}
private XmlInputStreamSaver _xmlInputStreamSaver;
}
static final class SaxSaver extends Saver
{
SaxSaver ( Cur c, XmlOptions options, ContentHandler ch, LexicalHandler lh )
throws SAXException
{
super( c, options );
_contentHandler = ch;
_lexicalHandler = lh;
_attributes = new AttributesImpl();
_nsAsAttrs = !options.hasOption( XmlOptions.SAVE_SAX_NO_NSDECLS_IN_ATTRIBUTES );
_contentHandler.startDocument();
try
{
while ( process() )
;
}
catch ( SaverSAXException e )
{
throw e._saxException;
}
_contentHandler.endDocument();
}
private class SaverSAXException extends RuntimeException
{
SaverSAXException ( SAXException e )
{
_saxException = e;
}
SAXException _saxException;
}
private String getPrefixedName ( QName name )
{
String uri = name.getNamespaceURI();
String local = name.getLocalPart();
if (uri.length() == 0)
return local;
String prefix = getUriMapping( uri );
if (prefix.length() == 0)
return local;
return prefix + ":" + local;
}
private void emitNamespacesHelper ( )
{
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
String prefix = mappingPrefix();
String uri = mappingUri();
try
{
_contentHandler.startPrefixMapping( prefix, uri );
}
catch ( SAXException e )
{
throw new SaverSAXException( e );
}
if (_nsAsAttrs)
if (prefix == null || prefix.length() == 0)
_attributes.addAttribute( "http://www.w3.org/2000/xmlns/", "xmlns", "xmlns", "CDATA", uri );
else
_attributes.addAttribute( "http://www.w3.org/2000/xmlns/", prefix, "xmlns:" + prefix, "CDATA", uri );
}
}
protected boolean emitElement ( SaveCur c, ArrayList attrNames, ArrayList attrValues )
{
_attributes.clear();
if (saveNamespacesFirst())
emitNamespacesHelper();
for ( int i = 0 ; i < attrNames.size() ; i++ )
{
QName name = (QName) attrNames.get( i );
_attributes.addAttribute(
name.getNamespaceURI(), name.getLocalPart(), getPrefixedName( name ),
"CDATA", (String) attrValues.get( i ) );
}
if (!saveNamespacesFirst())
emitNamespacesHelper();
QName elemName = c.getName();
try
{
_contentHandler.startElement(
elemName.getNamespaceURI(), elemName.getLocalPart(),
getPrefixedName( elemName ), _attributes );
}
catch ( SAXException e )
{
throw new SaverSAXException( e );
}
return false;
}
protected void emitFinish ( SaveCur c )
{
QName name = c.getName();
try
{
_contentHandler.endElement(
name.getNamespaceURI(), name.getLocalPart(), getPrefixedName( name ) );
for ( iterateMappings() ; hasMapping() ; nextMapping() )
_contentHandler.endPrefixMapping( mappingPrefix() );
}
catch ( SAXException e )
{
throw new SaverSAXException( e );
}
}
protected void emitText ( SaveCur c )
{
assert c.isText();
Object src = c.getChars();
try
{
if (src instanceof char[])
{
// Pray the user does not modify the buffer ....
_contentHandler.characters( (char[]) src, c._offSrc, c._cchSrc );
}
else
{
if (_buf == null)
_buf = new char [ 1024 ];
while ( c._cchSrc > 0 )
{
int cch = java.lang.Math.min( _buf.length, c._cchSrc );
CharUtil.getChars( _buf, 0, src, c._offSrc, cch );
_contentHandler.characters( _buf, 0, cch );
c._offSrc += cch;
c._cchSrc -= cch;
}
}
}
catch ( SAXException e )
{
throw new SaverSAXException( e );
}
}
protected void emitComment ( SaveCur c )
{
if (_lexicalHandler != null)
{
c.push();
c.next();
try
{
if (!c.isText())
_lexicalHandler.comment( null, 0, 0 );
else
{
Object src = c.getChars();
if (src instanceof char[])
{
// Pray the user does not modify the buffer ....
_lexicalHandler.comment( (char[]) src, c._offSrc, c._cchSrc );
}
else
{
if (_buf == null || _buf.length < c._cchSrc)
_buf = new char [ java.lang.Math.max( 1024, c._cchSrc ) ];
CharUtil.getChars( _buf, 0, src, c._offSrc, c._cchSrc );
_lexicalHandler.comment( _buf, 0, c._cchSrc );
}
}
}
catch ( SAXException e )
{
throw new SaverSAXException( e );
}
c.pop();
}
}
protected void emitProcinst ( SaveCur c )
{
String target = c.getName().getLocalPart();
c.push();
c.next();
String value = CharUtil.getString( c.getChars(), c._offSrc, c._cchSrc );
c.pop();
try
{
_contentHandler.processingInstruction( c.getName().getLocalPart(), value );
}
catch ( SAXException e )
{
throw new SaverSAXException( e );
}
}
protected void emitDocType ( String docTypeName, String publicId, String systemId )
{
if (_lexicalHandler != null)
{
try
{
_lexicalHandler.startDTD( docTypeName, publicId, systemId );
_lexicalHandler.endDTD();
}
catch ( SAXException e )
{
throw new SaverSAXException( e );
}
}
}
protected void emitStartDoc ( SaveCur c )
{
}
protected void emitEndDoc ( SaveCur c )
{
}
private ContentHandler _contentHandler;
private LexicalHandler _lexicalHandler;
private AttributesImpl _attributes;
private char[] _buf;
private boolean _nsAsAttrs;
}
//
//
//
static abstract class SaveCur
{
final boolean isRoot ( ) { return kind() == ROOT; }
final boolean isElem ( ) { return kind() == ELEM; }
final boolean isAttr ( ) { return kind() == ATTR; }
final boolean isText ( ) { return kind() == TEXT; }
final boolean isComment ( ) { return kind() == COMMENT; }
final boolean isProcinst ( ) { return kind() == PROCINST; }
final boolean isFinish ( ) { return Cur.kindIsFinish( kind() ); }
final boolean isContainer ( ) { return Cur.kindIsContainer( kind() ); }
final boolean isNormalAttr ( ) { return kind() == ATTR && !isXmlns(); }
final boolean skip ( ) { toEnd(); return next(); }
abstract void release ( );
abstract int kind ( );
abstract QName getName ( );
abstract String getXmlnsPrefix ( );
abstract String getXmlnsUri ( );
abstract boolean isXmlns ( );
abstract boolean hasChildren ( );
abstract boolean hasText ( );
abstract boolean isTextCData ( );
abstract boolean toFirstAttr ( );
abstract boolean toNextAttr ( );
abstract String getAttrValue ( );
abstract boolean next ( );
abstract void toEnd ( );
abstract void push ( );
abstract void pop ( );
abstract Object getChars ( );
abstract List getAncestorNamespaces ( );
abstract XmlDocumentProperties getDocProps ( );
int _offSrc;
int _cchSrc;
}
// TODO - saving a fragment need to take namesapces from root and
// reflect them on the document element
private static final class DocSaveCur extends SaveCur
{
DocSaveCur ( Cur c )
{
assert c.isRoot();
_cur = c.weakCur( this );
}
void release ( )
{
_cur.release();
_cur = null;
}
int kind ( ) { return _cur.kind(); }
QName getName ( ) { return _cur.getName(); }
String getXmlnsPrefix ( ) { return _cur.getXmlnsPrefix(); }
String getXmlnsUri ( ) { return _cur.getXmlnsUri(); }
boolean isXmlns ( ) { return _cur.isXmlns(); }
boolean hasChildren ( ) { return _cur.hasChildren(); }
boolean hasText ( ) { return _cur.hasText(); }
boolean isTextCData ( ) { return _cur.isTextCData(); }
boolean toFirstAttr ( ) { return _cur.toFirstAttr(); }
boolean toNextAttr ( ) { return _cur.toNextAttr(); }
String getAttrValue ( ) { assert _cur.isAttr(); return _cur.getValueAsString(); }
void toEnd ( ) { _cur.toEnd(); }
boolean next ( ) { return _cur.next(); }
void push ( ) { _cur.push(); }
void pop ( ) { _cur.pop(); }
List getAncestorNamespaces ( ) { return null; }
Object getChars ( )
{
Object o = _cur.getChars( -1 );
_offSrc = _cur._offSrc;
_cchSrc = _cur._cchSrc;
return o;
}
XmlDocumentProperties getDocProps ( ) { return Locale.getDocProps(_cur, false); }
private Cur _cur;
}
private static abstract class FilterSaveCur extends SaveCur
{
FilterSaveCur ( SaveCur c )
{
assert c.isRoot();
_cur = c;
}
// Can filter anything by root and attributes and text
protected abstract boolean filter ( );
void release ( )
{
_cur.release();
_cur = null;
}
int kind ( ) { return _cur.kind(); }
QName getName ( ) { return _cur.getName(); }
String getXmlnsPrefix ( ) { return _cur.getXmlnsPrefix(); }
String getXmlnsUri ( ) { return _cur.getXmlnsUri(); }
boolean isXmlns ( ) { return _cur.isXmlns(); }
boolean hasChildren ( ) { return _cur.hasChildren(); }
boolean hasText ( ) { return _cur.hasText(); }
boolean isTextCData ( ) { return _cur.isTextCData(); }
boolean toFirstAttr ( ) { return _cur.toFirstAttr(); }
boolean toNextAttr ( ) { return _cur.toNextAttr(); }
String getAttrValue ( ) { return _cur.getAttrValue(); }
void toEnd ( ) { _cur.toEnd(); }
boolean next ( )
{
if (!_cur.next())
return false;
if (!filter())
return true;
assert !isRoot() && !isText() && !isAttr();
toEnd();
return next();
}
void push ( ) { _cur.push(); }
void pop ( ) { _cur.pop(); }
List getAncestorNamespaces ( ) { return _cur.getAncestorNamespaces(); }
Object getChars ( )
{
Object o = _cur.getChars();
_offSrc = _cur._offSrc;
_cchSrc = _cur._cchSrc;
return o;
}
XmlDocumentProperties getDocProps ( ) { return _cur.getDocProps(); }
private SaveCur _cur;
}
private static final class FilterPiSaveCur extends FilterSaveCur
{
FilterPiSaveCur ( SaveCur c, String target )
{
super( c );
_piTarget = target;
}
protected boolean filter ( )
{
return kind() == PROCINST && getName().getLocalPart().equals( _piTarget );
}
private String _piTarget;
}
private static final class FragSaveCur extends SaveCur
{
FragSaveCur ( Cur start, Cur end, QName synthElem )
{
_saveAttr = start.isAttr() && start.isSamePos( end );
_cur = start.weakCur( this );
_end = end.weakCur( this );
_elem = synthElem;
_state = ROOT_START;
_stateStack = new int [ 8 ];
start.push();
computeAncestorNamespaces( start );
start.pop();
}
List getAncestorNamespaces ( )
{
return _ancestorNamespaces;
}
private void computeAncestorNamespaces ( Cur c )
{
_ancestorNamespaces = new ArrayList();
while ( c.toParentRaw() )
{
if (c.toFirstAttr())
{
do
{
if (c.isXmlns())
{
String prefix = c.getXmlnsPrefix();
String uri = c.getXmlnsUri();
// Don't let xmlns:foo="" get used
if (uri.length() > 0 || prefix.length() == 0)
{
_ancestorNamespaces.add( c.getXmlnsPrefix() );
_ancestorNamespaces.add( c.getXmlnsUri() );
}
}
}
while ( c.toNextAttr() );
c.toParent();
}
}
}
//
//
//
void release ( )
{
_cur.release();
_cur = null;
_end.release();
_end = null;
}
int kind ( )
{
switch ( _state )
{
case ROOT_START : return ROOT;
case ELEM_START : return ELEM;
case ELEM_END : return -ELEM;
case ROOT_END : return -ROOT;
}
assert _state == CUR;
return _cur.kind();
}
QName getName ( )
{
switch ( _state )
{
case ROOT_START :
case ROOT_END : return null;
case ELEM_START :
case ELEM_END : return _elem;
}
assert _state == CUR;
return _cur.getName();
}
String getXmlnsPrefix ( )
{
assert _state == CUR && _cur.isAttr();
return _cur.getXmlnsPrefix();
}
String getXmlnsUri ( )
{
assert _state == CUR && _cur.isAttr();
return _cur.getXmlnsUri();
}
boolean isXmlns ( )
{
assert _state == CUR && _cur.isAttr();
return _cur.isXmlns();
}
boolean hasChildren ( )
{
boolean hasChildren = false;
if (isContainer())
{ // is there a faster way to do this?
push();
next();
if (!isText() && !isFinish())
hasChildren = true;
pop();
}
return hasChildren;
}
boolean hasText ( )
{
boolean hasText = false;
if (isContainer())
{
push();
next();
if (isText())
hasText = true;
pop();
}
return hasText;
}
boolean isTextCData ( )
{
return _cur.isTextCData();
}
Object getChars ( )
{
assert _state == CUR && _cur.isText();
Object src = _cur.getChars( -1 );
_offSrc = _cur._offSrc;
_cchSrc = _cur._cchSrc;
return src;
}
boolean next ( )
{
switch ( _state )
{
case ROOT_START :
{
_state = _elem == null ? CUR : ELEM_START;
break;
}
case ELEM_START :
{
if (_saveAttr)
_state = ELEM_END;
else
{
if (_cur.isAttr())
{
_cur.toParent();
_cur.next();
}
if (_cur.isSamePos( _end ))
_state = ELEM_END;
else
_state = CUR;
}
break;
}
case CUR :
{
assert !_cur.isAttr();
_cur.next();
if (_cur.isSamePos( _end ))
_state = _elem == null ? ROOT_END : ELEM_END;
break;
}
case ELEM_END :
{
_state = ROOT_END;
break;
}
case ROOT_END :
return false;
}
return true;
}
void toEnd ( )
{
switch ( _state )
{
case ROOT_START : _state = ROOT_END; return;
case ELEM_START : _state = ELEM_END; return;
case ROOT_END :
case ELEM_END : return;
}
assert _state == CUR && !_cur.isAttr() && !_cur.isText();
_cur.toEnd();
}
boolean toFirstAttr ( )
{
switch ( _state )
{
case ROOT_END :
case ELEM_END :
case ROOT_START : return false;
case CUR : return _cur.toFirstAttr();
}
assert _state == ELEM_START;
if (!_cur.isAttr())
return false;
_state = CUR;
return true;
}
boolean toNextAttr ( )
{
assert _state == CUR;
return !_saveAttr && _cur.toNextAttr();
}
String getAttrValue ( )
{
assert _state == CUR && _cur.isAttr();
return _cur.getValueAsString();
}
void push ( )
{
if (_stateStackSize == _stateStack.length)
{
int[] newStateStack = new int [ _stateStackSize * 2 ];
System.arraycopy( _stateStack, 0, newStateStack, 0, _stateStackSize );
_stateStack = newStateStack;
}
_stateStack [ _stateStackSize++ ] = _state;
_cur.push();
}
void pop ()
{
_cur.pop();
_state = _stateStack [ --_stateStackSize ];
}
XmlDocumentProperties getDocProps ( ) { return Locale.getDocProps(_cur, false); }
//
//
//
private Cur _cur;
private Cur _end;
private ArrayList _ancestorNamespaces;
private QName _elem;
private boolean _saveAttr;
private static final int ROOT_START = 1;
private static final int ELEM_START = 2;
private static final int ROOT_END = 3;
private static final int ELEM_END = 4;
private static final int CUR = 5;
private int _state;
private int[] _stateStack;
private int _stateStackSize;
}
private static final class PrettySaveCur extends SaveCur
{
PrettySaveCur ( SaveCur c, XmlOptions options )
{
_sb = new StringBuffer();
_stack = new ArrayList();
_cur = c;
assert options != null;
_prettyIndent = 2;
if (options.hasOption( XmlOptions.SAVE_PRETTY_PRINT_INDENT ))
{
_prettyIndent =
((Integer) options.get( XmlOptions.SAVE_PRETTY_PRINT_INDENT )).intValue();
}
if (options.hasOption( XmlOptions.SAVE_PRETTY_PRINT_OFFSET ))
{
_prettyOffset =
((Integer) options.get( XmlOptions.SAVE_PRETTY_PRINT_OFFSET )).intValue();
}
if (options.hasOption( XmlOptions.LOAD_SAVE_CDATA_BOOKMARKS ))
{
_useCDataBookmarks = true;
}
}
List getAncestorNamespaces ( ) { return _cur.getAncestorNamespaces(); }
void release ( ) { _cur.release(); }
int kind ( ) { return _txt == null ? _cur.kind() : TEXT; }
QName getName ( ) { assert _txt == null; return _cur.getName(); }
String getXmlnsPrefix ( ) { assert _txt == null; return _cur.getXmlnsPrefix(); }
String getXmlnsUri ( ) { assert _txt == null; return _cur.getXmlnsUri(); }
boolean isXmlns ( ) { return _txt == null ? _cur.isXmlns() : false; }
boolean hasChildren ( ) { return _txt == null ? _cur.hasChildren() : false; }
boolean hasText ( ) { return _txt == null ? _cur.hasText() : false; }
// _cur.isTextCData() is expensive do it only if useCDataBookmarks option is enabled
boolean isTextCData ( ) { return _txt == null ? (_useCDataBookmarks && _cur.isTextCData())
: _isTextCData; }
boolean toFirstAttr ( ) { assert _txt == null; return _cur.toFirstAttr(); }
boolean toNextAttr ( ) { assert _txt == null; return _cur.toNextAttr(); }
String getAttrValue ( ) { assert _txt == null; return _cur.getAttrValue(); }
void toEnd ( )
{
assert _txt == null;
_cur.toEnd();
if (_cur.kind() == -ELEM)
_depth--;
}
boolean next ( )
{
int k;
if (_txt != null)
{
assert _txt.length() > 0;
assert !_cur.isText();
_txt = null;
_isTextCData = false;
k = _cur.kind();
}
else
{
int prevKind = k = _cur.kind();
if (!_cur.next())
return false;
_sb.delete( 0, _sb.length() );
assert _txt == null;
// place any text encountered in the buffer
if (_cur.isText())
{
// _cur.isTextCData() is expensive do it only if useCDataBookmarks option is enabled
_isTextCData = _useCDataBookmarks && _cur.isTextCData();
CharUtil.getString( _sb, _cur.getChars(), _cur._offSrc, _cur._cchSrc );
_cur.next();
trim( _sb );
}
k = _cur.kind();
// Check for non leaf, _prettyIndent < 0 means that the save is all on one line
if (_prettyIndent >= 0 &&
prevKind != COMMENT && prevKind != PROCINST && (prevKind != ELEM || k != -ELEM))
// if (prevKind != COMMENT && prevKind != PROCINST && (prevKind != ELEM || k != -ELEM))
{
if (_sb.length() > 0)
{
_sb.insert( 0, _newLine );
spaces( _sb, _newLine.length(), _prettyOffset + _prettyIndent * _depth );
}
if (k != -ROOT)
{
if (prevKind != ROOT)
_sb.append( _newLine );
int d = k < 0 ? _depth - 1 : _depth;
spaces( _sb, _sb.length(), _prettyOffset + _prettyIndent * d );
}
}
if (_sb.length() > 0)
{
_txt = _sb.toString();
k = TEXT;
}
}
if (k == ELEM)
_depth++;
else if (k == -ELEM)
_depth--;
return true;
}
void push ( )
{
_cur.push();
_stack.add( _txt );
_stack.add( new Integer( _depth ) );
_isTextCData = false;
}
void pop ( )
{
_cur.pop();
_depth = ((Integer) _stack.remove( _stack.size() - 1 )).intValue();
_txt = (String) _stack.remove( _stack.size() - 1 );
_isTextCData = false;
}
Object getChars ( )
{
if (_txt != null)
{
_offSrc = 0;
_cchSrc = _txt.length();
return _txt;
}
Object o = _cur.getChars();
_offSrc = _cur._offSrc;
_cchSrc = _cur._cchSrc;
return o;
}
XmlDocumentProperties getDocProps ( ) { return _cur.getDocProps(); }
static void spaces ( StringBuffer sb, int offset, int count )
{
while ( count-- > 0 )
sb.insert( offset, ' ' );
}
static void trim ( StringBuffer sb )
{
int i;
for ( i = 0 ; i < sb.length() ; i++ )
if (!CharUtil.isWhiteSpace( sb.charAt( i ) ))
break;
sb.delete( 0, i );
for ( i = sb.length() ; i > 0 ; i-- )
if (!CharUtil.isWhiteSpace( sb.charAt( i - 1 ) ))
break;
sb.delete( i, sb.length() );
}
private SaveCur _cur;
private int _prettyIndent;
private int _prettyOffset;
private String _txt;
private StringBuffer _sb;
private int _depth;
private ArrayList _stack;
private boolean _isTextCData = false;
private boolean _useCDataBookmarks = false;
}
//
//
//
private final Locale _locale;
private final long _version;
private SaveCur _cur;
private List _ancestorNamespaces;
private Map _suggestedPrefixes;
protected XmlOptionCharEscapeMap _replaceChar;
private boolean _useDefaultNamespace;
private Map _preComputedNamespaces;
private boolean _saveNamespacesFirst;
private ArrayList _attrNames;
private ArrayList _attrValues;
private ArrayList _namespaceStack;
private int _currentMapping;
private HashMap _uriMap;
private HashMap _prefixMap;
private String _initialDefaultUri;
static final String _newLine =
SystemProperties.getProperty( "line.separator" ) == null
? "\n"
: SystemProperties.getProperty( "line.separator" );
}