/* 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.newstore2; | |
import javax.xml.namespace.QName; | |
import org.apache.xmlbeans.XmlDocumentProperties; | |
import org.apache.xmlbeans.XmlOptions; | |
import org.apache.xmlbeans.impl.common.QNameHelper; | |
import org.apache.xmlbeans.impl.common.EncodingMap; | |
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 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 void syntheticNamespace ( String prefix, String uri, boolean considerDefault ) { } | |
Saver ( Cur c, XmlOptions options ) | |
{ | |
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 ) ); | |
} | |
} | |
// 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); | |
} | |
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 (isFragment( start, end )) | |
{ | |
positionToInner( c, start, end ); | |
cur = new FragSaveCur( start, end, fragName ); | |
} | |
else if (synthName != null) | |
{ | |
positionToInner( c, start, end ); | |
cur = new FragSaveCur( start, end, synthName ); | |
} | |
else | |
cur = new DocSaveCur( c ); | |
break; | |
} | |
case ELEM : | |
{ | |
if (saveInner) | |
{ | |
positionToInner( c, start, end ); | |
boolean isFrag = isFragment( start, end ); | |
cur = new FragSaveCur( start, end, 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.toEnd(); | |
end.next(); | |
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.toEnd(); | |
end.next(); | |
} | |
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(); | |
} | |
private static boolean isFragment ( Cur start, Cur end ) | |
{ | |
assert !end.isAttr(); | |
int numDocElems = 0; | |
while ( ! start.isSamePos( end ) ) | |
{ | |
int k = start.kind(); | |
if (k == ATTR) | |
break; | |
if (k == TEXT && !Locale.isWhiteSpace( start.getString( -1 ))) | |
break; | |
if (k == ELEM && ++numDocElems > 1) | |
break; | |
// Move to next token | |
assert k != ATTR; | |
if (k != TEXT) | |
start.toEnd(); | |
start.next(); | |
} | |
return numDocElems != 1; | |
} | |
protected boolean saveNamespacesFirst ( ) | |
{ | |
return _saveNamespacesFirst; | |
} | |
private final void checkVersion ( ) | |
{ | |
if (_version != _locale.version()) | |
throw new ConcurrentModificationException( "Document changed during save" ); | |
} | |
protected final boolean process ( ) | |
{ | |
assert _locale.entered(); | |
checkVersion(); | |
if (_postPop) | |
{ | |
popMappings(); | |
_postPop = false; | |
} | |
if (_postProcess) | |
{ | |
int k = _cur.kind(); | |
if (k == -ROOT) | |
_done = true; | |
else | |
{ | |
switch ( k ) | |
{ | |
case ROOT : | |
case ELEM : | |
{ | |
if (_skipContainer) | |
{ | |
assert _cur.isElem(); | |
_cur.toEnd(); | |
} | |
_cur.next(); | |
break; | |
} | |
case - ROOT : | |
case - ELEM : | |
case TEXT : | |
_cur.next(); | |
break; | |
case COMMENT : | |
case PROCINST : | |
_cur.toEnd(); | |
_cur.next(); | |
break; | |
default : throw new RuntimeException( "Unexpected kind" ); | |
} | |
} | |
if (_postPop) | |
{ | |
popMappings(); | |
_postPop = false; | |
} | |
_postProcess = false; | |
} | |
if (_done) | |
{ | |
if (_cur != null) | |
{ | |
_cur.release(); | |
_cur = null; | |
} | |
return false; | |
} | |
checkVersion(); | |
switch ( _cur.kind() ) | |
{ | |
case ELEM : { _skipContainer = processElement(); break; } | |
case - ELEM : { emitFinish( _cur ); _postPop = true; break; } | |
case TEXT : { emitText( _cur ); break; } | |
case COMMENT : { emitComment( _cur ); break; } | |
case PROCINST : { emitProcinst( _cur ); break; } | |
case ROOT : | |
case - ROOT : | |
break; | |
default : throw new RuntimeException( "Unexpected kind" ); | |
} | |
_postProcess = true; | |
return true; | |
} | |
private final boolean processElement ( ) | |
{ | |
assert _cur.isElem() && _cur.getName() != null; | |
QName name = _cur.getName(); | |
// TODO - check for doctype to save out here | |
; | |
// 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; | |
} | |
return emitElement( _cur, _attrNames, _attrValues ); | |
} | |
// | |
// 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 ); | |
} | |
String mappingPrevPrefixUri ( ) | |
{ | |
assert hasMapping(); | |
return (String) _namespaceStack.get( _currentMapping + 5 ); | |
} | |
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()) | |
{ | |
String prefix = c.getXmlnsPrefix(); | |
String uri = c.getXmlnsUri(); | |
if (!ensureDefaultEmpty || prefix.length() > 0 || uri.length() == 0) | |
addNewFrameMapping( prefix, uri ); | |
} | |
} | |
c.pop(); | |
List an = _cur.getAncestorNamespaces(); | |
if (c.isRoot() && an != null) | |
{ | |
for ( int i = 0 ; i < an.size() ; i += 2 ) | |
{ | |
String prefix = (String) an.get( i ); | |
String uri = (String) an.get( i + 1 ); | |
if (!ensureDefaultEmpty || prefix.length() > 0 || uri.length() == 0) | |
addNewFrameMapping( prefix, uri ); | |
} | |
} | |
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 ) | |
{ | |
// Make sure the prefix is not already mapped in this frame | |
for ( iterateMappings() ; hasMapping() ; nextMapping() ) | |
if (mappingPrefix().equals( 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 String ensureMapping ( | |
String uri, String candidatePrefix, | |
boolean considerCreatingDefault, boolean mustHavePrefix ) | |
{ | |
assert uri != null; | |
assert uri.length() > 0 || !mustHavePrefix; | |
// 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 ); | |
} | |
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 ); | |
} | |
// | |
// | |
// | |
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 ) { } | |
} | |
// | |
// | |
// | |
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 (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() ); | |
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() ); | |
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(); | |
emit( '"' ); | |
} | |
private void emitNamespacesHelper ( ) | |
{ | |
for ( iterateMappings() ; hasMapping() ; nextMapping() ) | |
{ | |
emit( ' ' ); | |
emitXmlns( mappingPrefix(), mappingUri() ); | |
} | |
} | |
private void emitAttrHelper ( QName attrName, String attrValue ) | |
{ | |
emit( ' ' ); | |
emitName( attrName ); | |
emit( "=\"" ); | |
emit( attrValue ); | |
entitizeAttrValue(); | |
emit( '"' ); | |
} | |
protected void emitText ( SaveCur c ) | |
{ | |
assert c.isText(); | |
emit( c ); | |
entitizeContent(); | |
} | |
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 emitName ( QName name ) | |
{ | |
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 ); | |
if (prefix.length() > 0) | |
{ | |
emit( prefix ); | |
emit( ":" ); | |
} | |
} | |
assert name.getLocalPart().length() > 0; | |
emit( name.getLocalPart() ); | |
} | |
private void emit ( char ch ) | |
{ | |
preEmit( 1 ); | |
_buf[ _in ] = ch; | |
_in = (_in + 1) % _buf.length; | |
} | |
private void emit ( String s ) | |
{ | |
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; | |
} | |
} | |
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; | |
_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; | |
return false; | |
} | |
private void entitizeContent ( ) | |
{ | |
if (_lastEmitCch == 0) | |
return; | |
int i = _lastEmitIn; | |
final int n = _buf.length; | |
boolean hasOutOfRange = false; | |
int count = 0; | |
for ( int cch = _lastEmitCch ; cch > 0 ; cch-- ) | |
{ | |
char ch = _buf[ i ]; | |
if (ch == '<' || ch == '&') | |
count++; | |
else if (isBadChar( ch )) | |
hasOutOfRange = true; | |
if (++i == n) | |
i = 0; | |
} | |
if (count == 0 && !hasOutOfRange) | |
return; | |
i = _lastEmitIn; | |
// | |
// Heuristic for knowing when to save out stuff as a CDATA. | |
// | |
if (_lastEmitCch > 32 && count > 5 && | |
count * 100 / _lastEmitCch > 1) | |
{ | |
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 ; cch > 0 ; cch-- ) | |
{ | |
char ch = _buf[ i ]; | |
if (ch == '>' && secondToLastWasBracket && lastWasBracket) | |
i = replace( i, ">" ); | |
else if (isBadChar( ch )) | |
i = replace( i, "?" ); | |
else | |
i++; | |
secondToLastWasBracket = lastWasBracket; | |
lastWasBracket = ch == ']'; | |
if (i == _buf.length) | |
i = 0; | |
} | |
emit( "]]>" ); | |
} | |
else | |
{ | |
for ( int cch = _lastEmitCch ; cch > 0 ; cch-- ) | |
{ | |
char ch = _buf[ i ]; | |
if (ch == '<') | |
i = replace( i, "<" ); | |
else if (ch == '&') | |
i = replace( i, "&" ); | |
else if (isBadChar( ch )) | |
i = replace( i, "?" ); | |
else | |
i++; | |
if (i == _buf.length) | |
i = 0; | |
} | |
} | |
} | |
private void entitizeAttrValue ( ) | |
{ | |
if (_lastEmitCch == 0) | |
return; | |
int i = _lastEmitIn; | |
for ( int cch = _lastEmitCch ; cch > 0 ; cch-- ) | |
{ | |
char ch = _buf[ i ]; | |
if (ch == '<') | |
i = replace( i, "<" ); | |
else if (ch == '&') | |
i = replace( i, "&" ); | |
else if (ch == '"') | |
i = replace( i, """ ); | |
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 | |
if (_buf[ _lastEmitIn + _lastEmitCch - 1 ] == '-') | |
i = replace( _lastEmitIn + _lastEmitCch - 1, " " ); | |
} | |
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 valid in xml character content. See | |
* http://www.w3.org/TR/REC-xml#NT-Char | |
*/ | |
private boolean isBadChar ( char ch ) | |
{ | |
return ! ( | |
(ch >= 0x20 && ch <= 0xD7FF ) || | |
(ch >= 0xE000 && ch <= 0xFFFD) || | |
(ch >= 0x10000 && ch <= 0x10FFFF) || | |
(ch == 0x9) || (ch == 0xA) || (ch == 0xD) | |
); | |
} | |
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; | |
if (_out > _in && i >= _out) | |
{ | |
System.arraycopy( _buf, _out, _buf, _out - dCch, i - _out ); | |
_out -= dCch; | |
i -= dCch; | |
} | |
else | |
{ | |
assert i < _in; | |
System.arraycopy( _buf, i, _buf, i + dCch, _in - i ); | |
_in += dCch; | |
} | |
replacement.getChars( 0, dCch + 1, _buf, i ); | |
_free -= dCch; | |
assert _free >= 0; | |
return i + dCch + 1; | |
} | |
// | |
// | |
// | |
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; | |
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; | |
return i; | |
} | |
public int read ( ) | |
{ | |
if (ensure( 1 ) == 0) | |
return -1; | |
assert getAvailable() > 0; | |
int ch = _buf[ _out ]; | |
_out = (_out + 1) % _buf.length; | |
_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 _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; | |
try | |
{ | |
writer.write( _buf, 0, charsAvailable ); | |
writer.flush(); | |
} | |
catch ( IOException e ) | |
{ | |
throw new RuntimeException( e ); | |
} | |
_free += charsAvailable; | |
assert _free >= 0; | |
_in = 0; | |
} | |
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 _lastEmitIn; | |
private int _lastEmitCch; | |
private int _free; | |
private int _in; | |
private int _out; | |
private char[] _buf; | |
} | |
static final class TextReader extends Reader | |
{ | |
TextReader ( Cur c, XmlOptions options ) | |
{ | |
_textSaver = new TextSaver( c, options, null ); | |
} | |
public void close ( ) throws IOException { } | |
public boolean ready ( ) throws IOException { return true; } | |
public int read ( ) throws IOException | |
{ | |
return _textSaver.read(); | |
} | |
public int read ( char[] cbuf ) throws IOException | |
{ | |
return _textSaver.read( cbuf, 0, cbuf == null ? 0 : cbuf.length ); | |
} | |
public int read ( char[] cbuf, int off, int len ) throws IOException | |
{ | |
return _textSaver.read( cbuf, off, len ); | |
} | |
private TextSaver _textSaver; | |
} | |
static final class InputStreamSaver extends InputStream | |
{ | |
InputStreamSaver ( Cur c, XmlOptions options ) | |
{ | |
options = XmlOptions.maskNull( options ); | |
_byteBuffer = 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( _byteBuffer, javaEncoding ); | |
} | |
catch ( UnsupportedEncodingException e ) | |
{ | |
throw new RuntimeException( e ); | |
} | |
_textSaver = new TextSaver( c, options, encoding ); | |
} | |
public int read ( ) | |
{ | |
return _byteBuffer.read(); | |
} | |
public int read ( byte[] bbuf, int off, int len ) | |
{ | |
return _byteBuffer.read ( bbuf, off, len ); | |
} | |
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 = _byteBuffer.getAvailable(); | |
for ( ; bytesAvailable < cbyte ; | |
bytesAvailable = _byteBuffer.getAvailable() ) | |
{ | |
if (_textSaver.write( _converter, 2048 ) < 2048) | |
break; | |
} | |
bytesAvailable = _byteBuffer.getAvailable(); | |
if (bytesAvailable == 0) | |
return 0; | |
return bytesAvailable; | |
} | |
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; | |
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; | |
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; | |
if (_in <= _out || cbyte < (chunk = _buf.length - _in)) | |
{ | |
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; | |
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 (_out == _in) | |
System.arraycopy( _buf, 0, newBuf, 0, used ); | |
else 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 == 0 && _out == 0; | |
} | |
_buf = newBuf; | |
} | |
private static final int _initialBufSize = 4096; | |
int _free; | |
int _in; | |
int _out; | |
byte[] _buf; | |
} | |
private OutputStreamImpl _byteBuffer; | |
private TextSaver _textSaver; | |
private OutputStreamWriter _converter; | |
} | |
// static final class SaxSaver extends Saver | |
// { | |
// SaxSaver ( | |
// Root r, Splay s, int p, XmlOptions options, | |
// ContentHandler contentHandler, LexicalHandler lexicalhandler ) | |
// throws SAXException | |
// { | |
// super( r, s, p, options ); | |
// } | |
// } | |
// | |
// | |
// | |
private static abstract class SaveCur | |
{ | |
boolean isRoot ( ) { return kind() == ROOT; } | |
boolean isElem ( ) { return kind() == ELEM; } | |
boolean isAttr ( ) { return kind() == ATTR; } | |
boolean isText ( ) { return kind() == TEXT; } | |
boolean isComment ( ) { return kind() == COMMENT; } | |
boolean isProcinst ( ) { return kind() == PROCINST; } | |
boolean isFinish ( ) { return Cur.kindIsFinish( kind() ); } | |
boolean isContainer ( ) { return Cur.kindIsContainer( kind() ); } | |
boolean isNormalAttr ( ) { return kind() == ATTR && !isXmlns(); } | |
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 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 ( ); | |
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 toFirstAttr ( ) { return _cur.toFirstAttr(); } | |
boolean toNextAttr ( ) { return _cur.toNextAttr(); } | |
String getAttrValue ( ) { assert _cur.isAttr(); return _cur.getValueString(); } | |
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; | |
} | |
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 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; | |
} | |
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()) | |
{ | |
_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()) | |
{ | |
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; | |
} | |
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.getValueString(); | |
} | |
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 ]; | |
} | |
// | |
// | |
// | |
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(); | |
} | |
} | |
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; } | |
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; | |
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()) | |
{ | |
CharUtil.getString( _sb, _cur.getChars(), _cur._offSrc, _cur._cchSrc ); | |
_cur.next(); | |
trim( _sb ); | |
} | |
k = _cur.kind(); | |
// Check for non leaf | |
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 (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 ) ); | |
} | |
void pop ( ) | |
{ | |
_cur.pop(); | |
_depth = ((Integer) _stack.remove( _stack.size() - 1 )).intValue(); | |
_txt = (String) _stack.remove( _stack.size() - 1 ); | |
} | |
Object getChars ( ) | |
{ | |
if (_txt != null) | |
{ | |
_offSrc = 0; | |
_cchSrc = _txt.length(); | |
return _txt; | |
} | |
Object o = _cur.getChars(); | |
_offSrc = _cur._offSrc; | |
_cchSrc = _cur._cchSrc; | |
return o; | |
} | |
final static void spaces ( StringBuffer sb, int offset, int count ) | |
{ | |
while ( count-- > 0 ) | |
sb.insert( offset, ' ' ); | |
} | |
final static void trim ( StringBuffer sb ) | |
{ | |
int i; | |
for ( i = 0 ; i < sb.length() ; i++ ) | |
if (!Locale.isWhiteSpace( sb.charAt( i ) )) | |
break; | |
sb.delete( 0, i ); | |
for ( i = sb.length() ; i > 0 ; i-- ) | |
if (!Locale.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 final Locale _locale; | |
private final long _version; | |
private SaveCur _cur; | |
private boolean _postProcess; | |
private boolean _postPop; | |
private boolean _done; | |
private boolean _skipContainer; | |
private Map _suggestedPrefixes; | |
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 String _newLine = | |
System.getProperty( "line.separator" ) == null | |
? "\n" | |
: System.getProperty( "line.separator" ); | |
} |