| /* 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); |
| |
| _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 (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.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(); |
| } |
| |
| 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; |
| } |
| |
| 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 : { 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 : |
| { |
| _cur.release(); |
| _cur = null; |
| |
| return false; |
| } |
| |
| default : throw new RuntimeException( "Unexpected kind" ); |
| } |
| |
| _cur.next(); |
| |
| return true; |
| } |
| |
| private final void processFinish ( ) |
| { |
| emitFinish( _cur ); |
| popMappings(); |
| } |
| |
| private final void processElement ( ) |
| { |
| assert _cur.isElem() && _cur.getName() != null; |
| |
| QName name = _cur.getName(); |
| |
| // TODO - check for doctype to save out here |
| // TODO - check for doctype to save out here |
| // 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; |
| } |
| |
| 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 ); |
| } |
| |
| 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(); |
| |
| 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 ); |
| |
| if (!ensureDefaultEmpty || prefix.length() > 0 || uri.length() == 0) |
| addNewFrameMapping( prefix, uri ); |
| } |
| |
| _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 ) |
| { |
| // 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 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 ); |
| } |
| |
| 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 ); |
| |
| _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 int read ( ) |
| { |
| return _outStreamImpl.read(); |
| } |
| |
| public int read ( byte[] bbuf, int off, int len ) |
| { |
| return _outStreamImpl.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 = _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; |
| } |
| |
| 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 _outStreamImpl; |
| 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 |
| { |
| 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 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.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; |
| } |
| |
| 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.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 ]; |
| } |
| |
| // |
| // |
| // |
| |
| 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 (!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 final Locale _locale; |
| private final long _version; |
| |
| private SaveCur _cur; |
| |
| private List _ancestorNamespaces; |
| 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" ); |
| } |