blob: d310ab6144289c6f4c5d80190762674e917faef1 [file] [log] [blame]
/* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.xmlbeans.impl.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, "&gt;" );
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, "&lt;" );
else if (ch == '&')
i = replace( i, "&amp;" );
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, "&lt;" );
else if (ch == '&')
i = replace( i, "&amp;" );
else if (ch == '"')
i = replace( i, "&quot;" );
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" );
}