blob: 0975aeb1c5b7a3e924c314c9c343699bb9e913d3 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache
* XMLBeans", nor may "Apache" appear in their name, without prior
* written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 2000-2003 BEA Systems
* Inc., <http://www.bea.com/>. For more information on the Apache Software
* Foundation, please see <http://www.apache.org/>.
*/
package org.apache.xmlbeans.impl.store;
import org.apache.xmlbeans.impl.common.Chars;
import org.apache.xmlbeans.impl.common.EncodingMap;
import org.apache.xmlbeans.impl.common.GenericXmlInputStream;
import org.apache.xmlbeans.impl.common.ValidatorListener;
import org.apache.xmlbeans.impl.common.XmlEventBase;
import org.apache.xmlbeans.impl.common.XmlNameImpl;
import org.apache.xmlbeans.impl.common.QNameHelper;
import org.apache.xmlbeans.impl.store.Splay.Container;
import org.apache.xmlbeans.impl.store.Splay.Xmlns;
import org.apache.xmlbeans.impl.values.NamespaceManager;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlOptions;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.ConcurrentModificationException;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import weblogic.xml.stream.Attribute;
import weblogic.xml.stream.AttributeIterator;
import weblogic.xml.stream.ChangePrefixMapping;
import weblogic.xml.stream.CharacterData;
import weblogic.xml.stream.Comment;
import weblogic.xml.stream.EndDocument;
import weblogic.xml.stream.EndElement;
import weblogic.xml.stream.EndPrefixMapping;
import weblogic.xml.stream.Location;
import weblogic.xml.stream.ProcessingInstruction;
import weblogic.xml.stream.StartDocument;
import weblogic.xml.stream.StartElement;
import weblogic.xml.stream.StartPrefixMapping;
import weblogic.xml.stream.XMLEvent;
import weblogic.xml.stream.XMLName;
import weblogic.xml.stream.XMLStreamException;
public abstract class Saver implements NamespaceManager
{
//
//
//
private final Object monitor()
{
return _root;
}
Saver ( Root r, Splay s, int p, XmlOptions options )
{
assert Root.dv > 0 || s.getRootSlow() == r;
// Input s and p must be normalized already
assert p < s.getEndPos();
_root = r;
_top = _splay = s;
_pos = p;
_version = r.getVersion();
_sb = new StringBuffer();
_attrs = new LinkedHashMap();
_attrNames = new HashSet();
_firstPush = true;
_newLine = System.getProperty( "line.separator" );
if (_newLine == null)
_newLine = "\n";
// Initialize the state of the namespaces
_namespaceStack = new ArrayList();
_uriMap = new HashMap();
_prefixMap = new HashMap();
// Stops the synthesis of this namspace and make for better
// roundtripping
addMapping( "xml", Splay._xml1998Uri );
// Check for implicit namespaces
options = XmlOptions.maskNull( options );
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 (options.hasOption( XmlOptions.SAVE_SUGGESTED_PREFIXES ))
_suggestedPrefixes = (Map) options.get( XmlOptions.SAVE_SUGGESTED_PREFIXES);
// If the default prefix has not been mapped, do so now
if (getNamespaceForPrefix( "" ) == null)
{
_initialDefaultUri = new String( "" );
addMapping( "", _initialDefaultUri );
}
_saveNamespacesFirst = options.hasOption( XmlOptions.SAVE_NAMESPACES_FIRST );
if (_prettyPrint = options.hasOption( XmlOptions.SAVE_PRETTY_PRINT ))
{
_prettyIndent = 2;
if (options.hasOption( XmlOptions.SAVE_PRETTY_PRINT_INDENT ))
{
_prettyIndent =
((Integer) options.get( XmlOptions.SAVE_PRETTY_PRINT_INDENT )).intValue();
}
if (options.hasOption( XmlOptions.SAVE_PRETTY_PRINT_OFFSET ))
{
_prettyOffset =
((Integer) options.get( XmlOptions.SAVE_PRETTY_PRINT_OFFSET )).intValue();
}
}
if (options.hasOption( XmlOptions.SAVE_AGGRESSIVE_NAMESPACES ) &&
!(this instanceof SynthNamespaceSaver))
{
SynthNamespaceSaver saver =
new SynthNamespaceSaver( r, s, p, options );
while ( saver.process() )
;
if (!saver._synthNamespaces.isEmpty())
_preComputedNamespaces = saver._synthNamespaces;
}
_useDefaultNamespace =
options.hasOption( XmlOptions.SAVE_USE_DEFAULT_NAMESPACE );
if (options.hasOption( XmlOptions.SAVE_FILTER_PROCINST ))
{
_filterProcinst =
(String) options.get( XmlOptions.SAVE_FILTER_PROCINST );
}
if (options.hasOption( XmlOptions.SAVE_USE_OPEN_FRAGMENT ))
_fragment = Splay._openuriFragment;
else
_fragment = Splay._xmlFragment;
// Outer overrides inner
_inner =
options.hasOption( XmlOptions.SAVE_INNER ) &&
!options.hasOption( XmlOptions.SAVE_OUTER );
if (_inner && !_top.isDoc())
_synthElem = _fragment;
else if (options.hasOption( XmlOptions.SAVE_SYNTHETIC_DOCUMENT_ELEMENT ))
{
_fragment = _synthElem =
(QName) options.get( XmlOptions.SAVE_SYNTHETIC_DOCUMENT_ELEMENT );
if (_synthElem == null)
throw new IllegalArgumentException( "Null synthetic element" );
}
_preProcess = true;
}
protected final void checkVersion ( )
{
if (_version != _root.getVersion())
throw new ConcurrentModificationException( "Document changed during save" );
}
protected final Root getRoot ( ) { return _root; }
protected final Map getUriMap ( ) { return _uriMap; }
protected final Map getPrefixMap ( ) { return _prefixMap; }
// emitContainer will process leaf contents and the text after a
// start. All other text is processed with emitTextAfter.
protected abstract void emitXmlnsFragment ( Splay s );
protected abstract void emitAttrFragment ( Splay s );
protected abstract void emitTextFragment ( Splay s, int p, int cch );
protected abstract void emitCommentFragment ( Splay s );
protected abstract void emitProcinstFragment ( Splay s );
protected abstract void emitDocType(
String doctypeName, String publicID, String systemID );
protected abstract void emitComment ( Splay s );
protected abstract void emitTextAfter ( Splay s, int p, int cch );
protected abstract void emitEnd ( Splay s, QName name );
protected abstract void emitProcinst ( Splay s );
protected abstract void emitContainer ( Container c, QName name );
// Called when a synthetic prefix is created.
protected void syntheticNamespace (
String prefix, String uri, boolean considerCreatingDefault ) { }
/*
* It is vital that the saver does not modify the tree in the process of
* saving. So, when there is invalid content or attr values which need
* new namespace/prefix mappings, I compute the content/values by passing
* the NamespaceManager implemented by the saver which can cons up mappings
* just for the purposes of saving without modifying the tree by adding
* Xmlns splays.
*
* Thus, I must not, as a byproduct of saving, validate any content/values
* into the tree which call back for namespace mappings which need to be
* created.
*
* Also, I need to compute the valid text (if a splay is invalid) before
* I have closed the attribute list of the enclosing container because
* I will have to persist out these temporary meppings before they are
* referenced by the values which need them.
*/
final String text ( )
{
assert _text != null;
return _text.toString();
}
final boolean noText ( )
{
assert _text != null || _sb.length() == 0;
assert _text == null || _text == _sb;
return _text == null;
}
final void clearText ( )
{
_text = null;
_sb.delete( 0, _sb.length() );
}
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 (!Splay.isWhiteSpace( sb.charAt( i ) ))
break;
sb.delete( 0, i );
for ( i = sb.length() ; i > 0 ; i-- )
if (!Splay.isWhiteSpace( sb.charAt( i - 1 ) ))
break;
sb.delete( i, sb.length() );
}
// Call process until it returns false to save everything.
protected final boolean process ( )
{
synchronized (monitor())
{
checkVersion();
if (_preProcess)
{
_preProcess = false;
Splay s = _splay;
int p = _pos;
_splay = null;
_pos = 0;
// Check for position right before end token. Effectively there
// is nothing to save here, so save out the empty fragment.
if ((p == 0 && s.isFinish()) ||
(s.isLeaf() && p == s.getPosLeafEnd()))
{
assert _splay == null;
processTextFragment( null, 0, 0 );
return true;
}
// Here, if p > 0, then we're saving some text
if (p > 0)
{
assert !s.isLeaf() || p != s.getPosLeafEnd();
processTextFragment( s, p, s.getPostCch( p ) );
assert _splay == null;
return true;
}
if (_inner && (s.isAttr() || s.isComment() || s.isProcinst()))
{
processTextFragment( s, 0, s.getCchValue() );
return true;
}
if (s.isXmlns())
{
processXmlnsFragment( s );
return true;
}
if (s.isAttr())
{
processAttrFragment( s );
return true;
}
if (s.isComment())
{
processCommentFragment( s );
return true;
}
if (s.isProcinst())
{
if (_filterProcinst != null &&
s.getLocal().equals( _filterProcinst ))
{
processTextFragment( null, 0, 0 );
return true;
}
else
processProcinstFragment( s );
return true;
}
assert s.isContainer();
_splay = s;
_endSplay = s.isContainer() ? s.getFinishSplay() : s;
}
// I need to break the processing of a splay into two parts. The
// first part is the processing of the splay and the second part is
// the post processing of the splay. In particular, the post
// processing, among other things, pops the mapping stack. This
// is done so stuff 'saved' during the first part (the pre-process)
// can use the mapping stack. When the next process comes around,
// the mapping stack will then be updated.
if (_postPop)
{
popMappings();
_postPop = false;
}
if (_postProcess)
{
assert _splay != null;
boolean emitted = false;
if (_splay == _endSplay)
_splay = null;
else
{
Splay s = _splay;
_splay = _splay.nextNonAttrSplay();
if (_skipContainerFinish)
{
assert s.isBegin() && !s.isLeaf() && s.getCchAfter() ==0;
assert _splay.isFinish();
_splay = _splay == _endSplay ? null : _splay.nextSplay();
}
if (_skipContainerFinish || s.isLeaf())
{
assert !_postPop;
_postPop = true;
}
if (!s.isDoc())
{
assert noText();
int cchAfter = s.getCchAfter();
if (_prettyPrint)
{
_text = _sb;
if (cchAfter > 0)
{
Root r = getRoot();
r._text.fetch(
_text,
s.getCpForPos( r, s.getPosAfter() ), cchAfter );
trim( _text );
}
Container stop = (Container) _top;
if (!stop.isDoc())
stop = stop.getContainer();
Container c = s.getContainer( s.getPosAfter() );
if (_text.length() > 0)
{
Container p = c;
for ( ; p != stop ; p = p.getContainer() )
spaces( _text, 0, _prettyIndent );
if (_prettyIndent >= 0)
{
_text.insert( 0, _newLine );
spaces( _text, 1, _prettyOffset );
}
}
if (_prettyIndent >= 0)
{
_text.append( _newLine );
spaces( _text, _text.length(), _prettyOffset );
}
Container p = c;
if (s.nextNonAttrSplay().isEnd())
p = p.getContainer();
for ( ; p != null && p != stop; p = p.getContainer() )
spaces( _text, _text.length(), _prettyIndent );
}
if (_text == null ? cchAfter > 0 : _text.length() > 0)
{
emitTextAfter( s, s.getPosAfter(), cchAfter );
emitted = true;
}
clearText();
}
}
_postProcess = false;
// Make sure I only return false if there is *really* nothing more
// to process
if (emitted)
return true;
if (_postPop)
{
popMappings();
_postPop = false;
}
}
if (_splay == null)
return false;
if (_version != getRoot().getVersion())
throw new IllegalStateException( "Document changed" );
_skipContainerFinish = false;
switch ( _splay.getKind() )
{
case Splay.DOC :
case Splay.BEGIN :
{
processContainer( (Container) _splay );
break;
}
case Splay.ROOT :
case Splay.END :
{
processEnd( _splay );
break;
}
case Splay.COMMENT :
{
emitComment( _splay );
break;
}
case Splay.PROCINST :
{
if (_filterProcinst == null ||
!_splay.getLocal().equals( _filterProcinst ))
{
emitProcinst( _splay );
}
break;
}
case Splay.ATTR :
default :
{
assert false: "Unexpected splay kind " + _splay.getKind();
return false;
}
}
_postProcess = true;
return true;
}
}
private final void processContainer ( Container c )
{
assert c.isDoc() || c.isBegin();
QName name =
_synthElem != null && c == _top
? _synthElem
: c.isBegin() ? c.getName() : null;
String nameUri = name == null ? null : name.getNamespaceURI();
// See if there is a doctype to save out
if (c.isDoc() )
{
String systemId = _root._props.getDoctypeSystemId();
String docTypeName = _root._props.getDoctypeName();
if (systemId != null || docTypeName != null)
{
if (docTypeName == null && name != null)
docTypeName = name.getLocalPart();
if (docTypeName == null)
{
XmlCursor xc = _root.createCursor();
if (xc.toFirstChild())
docTypeName = xc.getName().getLocalPart();
xc.dispose();
}
if (docTypeName == null && _fragment != null)
docTypeName = _fragment.getLocalPart();
String publicId = _root._props.getDoctypePublicId();
emitDocType( docTypeName, publicId, systemId );
}
}
// 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 != null && nameUri.length() == 0;
pushMappings( c, 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)
if (name != null)
ensureMapping( nameUri, null, !ensureDefaultEmpty, false );
assert noText();
if (c.isInvalid())
{
// 2) The element value
_text = _sb.append( c.peekType().build_text( this ) );
}
_attrs.clear();
_attrNames.clear();
for ( Splay s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() )
{
if (s.isNormalAttr() &&
(_wantDupAttrs || !_attrNames.contains( s.getName() )))
{
_attrNames.add( s.getName() );
// 3) Attribute name
ensureMapping( s.getUri(), null, false, true );
String invalidValue = null;
if (s.isInvalid())
invalidValue = s.peekType().build_text( this ); // #4
_attrs.put( s, invalidValue );
}
}
// emitContainer handles text only for leaves and starts
if (_prettyPrint && (c.isDoc() || c.isLeaf()))
{
if (_text == null)
{
Root r = getRoot();
r._text.fetch(
_text = _sb,
r.getCp( c ),
c.isLeaf() ? c.getCchValue() : c.getCch() );
}
trim( _text );
if (c.isDoc())
{
spaces( _text, 0, _prettyOffset );
if (_text.length() > _prettyOffset)
{
if (_prettyIndent >= 0)
{
_text.insert( 0, _newLine );
spaces( _text, 1, _prettyOffset );
_text.append( _newLine );
spaces( _text, _text.length(), _prettyOffset );
}
}
}
}
// derived savers may want to test the stuff being stored to
// see if is well formed or not in order to save out a
// fragment. Do this here when emitting the first container if
// that container is the root (if not the root, then we are
// well formed)
if (_wantFragTest && name == null)
{
if ((_text != null && !Splay.isWhiteSpace( _text )) ||
!c.isAfterWhiteSpace( getRoot() ))
{
_needsFrag = true;
}
else
{
assert !c.isLeaf();
Splay s = c.nextSplay();
// Check for leaf anyways -- sometimes it happens!
if (c.isLeaf() || s.isAttr() || hasMappings())
_needsFrag = true;
else
{
boolean sawBegin = false;
Splay cEnd = c.getFinishSplay();
for ( ; s != cEnd ; s = s.nextSplay() )
{
if (s.isBegin())
{
if (sawBegin)
{
_needsFrag = true;
break;
}
sawBegin = true;
s = s.getFinishSplay();
}
if (!s.isAfterWhiteSpace( getRoot() ))
{
_needsFrag = true;
break;
}
}
if (!sawBegin)
_needsFrag = true;
}
}
}
// 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 && (name != null || _needsFrag))
{
for ( Iterator i = _preComputedNamespaces.keySet().iterator() ; i.hasNext() ; )
{
String uri = (String) i.next();
ensureMapping(
uri, null,
_preComputedNamespaces.get( uri ) != null && !ensureDefaultEmpty, false );
}
// Set to null so we do this once at the top
_preComputedNamespaces = null;
}
if (_wantFragTest)
{
// See if I need to gen a fragment for the document
assert name != null || (c.isDoc() && _synthElem == null);
if (name == null)
{
if (_needsFrag)
{
name = _fragment;
ensureFragmentNamespace();
_docElem = name;
}
}
else if (c.isDoc())
_docElem = name;
}
emitContainer( c, name );
clearText();
}
private void processEnd ( Splay s )
{
Container c = s.getContainer();
QName name =
_synthElem != null && c == _top
? _synthElem
: c.isBegin() ? c.getName() : null;
if (_wantFragTest && name == null)
{
boolean isRoot = s.isRoot();
if (!isRoot || _docElem != null)
{
name =
isRoot
? _docElem
: (s.isEnd() ? s.getContainer() : s ).getName();
}
}
emitEnd( _splay, name );
assert !_postPop;
_postPop = true;
}
private final void pushFragmentMappings ( Splay s )
{
// mask the initial frame to hide default mappings
pushMappings(
s == null ? null : s.getContainer(),
_fragment.getNamespaceURI().length() == 0 );
ensureFragmentNamespace();
}
private final void processXmlnsFragment ( Splay s )
{
// Saving a default xmlns mapping is dangerous, just spit out empty
// text
if (s.getLocal().length() == 0)
{
pushFragmentMappings( null );
_text = _sb;
emitTextFragment( s, 0, 0 );
}
else
{
pushFragmentMappings( null );
ensureMapping( s.getUri(), s.getLocal(), false, true );
emitXmlnsFragment( s );
}
}
private final void processCommentFragment ( Splay s )
{
pushFragmentMappings( null );
emitCommentFragment( s );
}
private final void processProcinstFragment ( Splay s )
{
pushFragmentMappings( null );
emitProcinstFragment( s );
}
/**
* This is called only when a single attr is being saved. It is not called
* as a consequence of saving out a container.
*/
private final void processAttrFragment ( Splay s )
{
assert s.isNormalAttr();
pushFragmentMappings( s );
ensureMapping( s.getUri(), null, false, true );
assert noText();
if (s.isInvalid())
{
// TODO - pass StringBuffer to buildText to save object creation
_text = _sb.append( s.peekType().build_text( this ) );
}
emitAttrFragment( s );
clearText();
}
/**
* This is called only when a single chunk of text is to be saved.
* It is not called as a consequence of saving out a container.
*/
private final void processTextFragment ( Splay s, int p, int cch )
{
// Only need to save ancestor namespace mappings if there could be a
// qname here. If the text is of length 0, then there is no qname.
Splay c = null;
if ((_text != null && _text.length() > 0) || (s != null && cch > 0))
c = s.getContainer( p );
pushFragmentMappings( c );
emitTextFragment( s, p, cch );
}
//
// 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 ( Container c, boolean ensureDefaultEmpty )
{
_namespaceStack.add( null );
for ( ; c != null ; c = c.getContainer() )
{
namespaces:
for ( Splay s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() )
{
if (s.isXmlns())
{
Xmlns x = (Xmlns) s;
String prefix = x.getLocal();
String uri = x.getUri();
if (ensureDefaultEmpty &&
prefix.length() == 0 && uri.length() > 0)
{
continue;
}
// Make sure the prefix is not already mapped in
// this frame
for ( iterateMappings() ; hasMapping() ; nextMapping() )
if (mappingPrefix().equals( prefix ))
continue namespaces;
addMapping( prefix, uri );
}
}
// Push all ancestors the first time
if (!_firstPush)
break;
}
if (ensureDefaultEmpty)
{
String defaultUri = (String) _prefixMap.get( "" );
// I map the default to "" at the very beginning
assert defaultUri != null;
if (defaultUri.length() > 0)
addMapping( "", "" );
}
_firstPush = false;
}
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 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 );
}
}
protected final String getUriMapping ( String uri )
{
assert _uriMap.get( uri ) != null;
return (String) _uriMap.get( uri );
}
protected final boolean tryPrefix ( String prefix )
{
if (prefix == null || Splay.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;
}
protected final String ensureMapping (
String uri, String candidatePrefix,
boolean considerCreatingDefault, boolean mustHavePrefix )
{
assert uri != null;
assert candidatePrefix == null || candidatePrefix.length() > 0;
// 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 || !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;
}
public final String find_prefix_for_nsuri ( String uri, String prefix )
{
assert uri != null;
assert prefix == null || prefix.length() > 0;
boolean emptyUri = uri.length() == 0;
return ensureMapping( uri, prefix, emptyUri, !emptyUri );
}
public final String getNamespaceForPrefix ( String prefix )
{
if (prefix != null && prefix.equals( "xml" ))
return Splay._xml1998Uri;
return (String) _prefixMap.get( prefix );
}
protected final String ensureFragmentNamespace ( )
{
if (_fragment.getNamespaceURI().length() == 0)
return "";
return ensureMapping( _fragment.getNamespaceURI(), "frag", false, false );
}
/**
* A Saver which records synthetic namespaces
*/
static final class SynthNamespaceSaver extends Saver
{
LinkedHashMap _synthNamespaces = new LinkedHashMap();
SynthNamespaceSaver ( Root r, Splay s, int p, XmlOptions options )
{
super( r, s, p, options );
}
protected void syntheticNamespace (
String prefix, String uri, boolean considerCreatingDefault )
{
_synthNamespaces.put( uri, considerCreatingDefault ? "useDefault" : null );
}
protected void emitXmlnsFragment ( Splay s ) { }
protected void emitAttrFragment ( Splay s ) { }
protected void emitTextFragment ( Splay s, int p, int cch ) { }
protected void emitCommentFragment ( Splay s ) { }
protected void emitProcinstFragment ( Splay s ) { }
protected void emitComment ( Splay s ) { }
protected void emitTextAfter ( Splay s, int p, int cch ) { }
protected void emitEnd ( Splay s, QName name ) { }
protected void emitProcinst ( Splay s ) { }
protected void emitContainer ( Container c, QName name ) { }
protected void emitDocType(
String doctypeName, String publicID, String systemID ) { }
}
/**
* A Saver which generates characters.
*/
static final class TextSaver extends Saver
{
TextSaver (
Root r, Splay s, int p, XmlOptions options, String encoding )
{
super( r, s, p, options );
_wantFragTest = true;
if (encoding != null)
{
String version = r._props.getVersion();
if (version == null)
version = "1.0";
emit( "<?xml version=\"" );
emit( version );
emit( "\" encoding=\"" + encoding + "\"?>" + _newLine );
}
}
protected void emitContainer ( Container c, QName name )
{
if (c.isBegin())
{
emitContainerHelper( c, name, null, null, false );
if (c.isLeaf())
{
int cch = _text == null ? c.getCchValue() : _text.length();
if (cch > 0)
{
emit( '>' );
if (_text == null)
emit( getRoot().getCp( c ), cch );
else
emit( _text );
entitizeContent();
emit( "</" );
emitName( name );
}
else
emit( '/' );
}
else
{
assert !c.isLeaf();
if (c.getCchAfter() == 0 && c.nextNonAttrSplay().isEnd())
{
emit( '/' );
_skipContainerFinish = true;
}
}
emit( '>' );
}
else
{
assert c.isDoc();
if (name != null)
emitContainerHelper( c, name, null, null, true );
if (_text == null)
emit( getRoot().getCp( c ), c.getCch() );
else
emit( _text );
entitizeContent();
}
}
private void emitAttrHelper ( Splay s, String invalidValue )
{
assert s.isNormalAttr();
emit( ' ' );
emitName( s.getName() );
emit( "=\"" );
if (invalidValue != null)
emit( invalidValue );
else
emit( getRoot().getCp( s ), s.getCch() );
entitizeAttrValue();
emit( '"' );
}
private void emitNamespacesHelper ( )
{
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
emit( ' ' );
emitXmlns( mappingPrefix(), mappingUri() );
}
}
private void emitContainerHelper (
Container c, QName name,
Splay extraAttr, StringBuffer extraAttrText,
boolean close )
{
assert name != null;
emit( '<' );
emitName( name );
if (_saveNamespacesFirst)
emitNamespacesHelper();
if (c != null)
{
for ( Iterator i = _attrs.keySet().iterator() ; i.hasNext() ; )
{
Splay s = (Splay) i.next();
emitAttrHelper( s, (String) _attrs.get( s ) );
}
}
if (extraAttr != null)
{
emitAttrHelper(
extraAttr,
extraAttrText == null ? null : extraAttrText.toString() );
}
if (!_saveNamespacesFirst)
emitNamespacesHelper();
if (close)
emit( '>' );
}
protected void emitText ( Splay s, int p, int cch )
{
emit( s.getCpForPos( getRoot(), p ), cch );
entitizeContent();
}
protected void emitTextAfter ( Splay s, int p, int cch )
{
if (_text == null)
emitText( s, p, cch );
else
{
emit( _text );
entitizeContent();
}
}
protected void emitTextFragment ( Splay s, int p, int cch )
{
emitContainerHelper( null, _fragment, null, null, false );
if (_text != null)
{
if (_text.length() > 0)
{
emit( ">" );
emit( _text );
emitEndHelper( _fragment );
return;
}
}
else if (s != null)
{
if (cch > 0)
{
emit( ">" );
emitText( s, p, cch );
emitEndHelper( _fragment );
return;
}
}
emit( "/>" );
}
protected void emitAttrFragment ( Splay s )
{
emitContainerHelper( null, _fragment, s, _text, false );
emit( "/>" );
}
protected void emitXmlnsFragment ( Splay s )
{
emitContainerHelper( null, _fragment, null, null, false );
emit( "/>" );
}
protected void emitCommentFragment ( Splay s )
{
emitContainerHelper( null, _fragment, null, null, true );
emitComment( s );
emitEndHelper( _fragment );
}
protected void emitProcinstFragment ( Splay s )
{
emitContainerHelper( null, _fragment, null, null, true );
emitProcinst( s );
emitEndHelper( _fragment );
}
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( '"' );
}
protected void emitEndHelper ( QName name )
{
emit( "</" );
emitName( name );
emit( '>' );
}
protected void emitEnd ( Splay s, QName name )
{
if (name != null)
emitEndHelper( name );
}
private void emitLiteral ( String literal )
{
if (literal.indexOf( "\"" ) < 0)
{
emit( "\"" );
emit( literal );
emit( "\"" );
}
else
{
emit( "'" );
emit( literal );
emit( "'" );
}
}
protected void emitDocType(
String doctypeName, String publicID, String systemID )
{
assert doctypeName != null;
emit( "<!DOCTYPE " );
emit( doctypeName );
if (publicID == null && systemID != null)
{
emit( " SYSTEM " );
emitLiteral( systemID );
}
else if (publicID != null)
{
emit( " PUBLIC " );
emitLiteral( publicID );
emit( " " );
emitLiteral( systemID );
}
emit( ">" + _newLine );
}
protected void emitComment ( Splay s )
{
assert s.isComment();
emit( "<!--" );
emit( getRoot().getCp( s ), s.getCchValue() );
entitizeComment();
emit( "-->" );
}
protected void emitProcinst ( Splay s )
{
assert s.isProcinst();
emit( "<?" );
// TODO - encoding issues here?
emit( s.getLocal() );
if (s.getCchValue() > 0)
{
emit( " " );
emit( getRoot().getCp( s ), s.getCchValue() );
entitizeProcinst();
}
emit( "?>" );
}
/**
* Ensure that there are at least cch chars available in the buffer.
* Return the actual number of characters available. If less than cch,
* then that's all which will ever become available.
*/
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;
}
private void emitName ( QName name )
{
assert name != null;
String uri = name.getNamespaceURI();
assert uri != null;
if (uri.length() != 0)
{
String prefix = getUriMapping( uri );
if (prefix.length() > 0)
{
emit( prefix );
emit( ":" );
}
}
assert name.getLocalPart().length() > 0;
emit( name.getLocalPart() );
}
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 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 ( StringBuffer sb )
{
int cch = sb == null ? 0 : sb.length();
if (preEmit( cch ))
return;
int chunk;
if (_in <= _out || cch < (chunk = _buf.length - _in))
{
sb.getChars( 0, cch, _buf, _in );
_in += cch;
}
else
{
sb.getChars( 0, chunk, _buf, _in );
sb.getChars( chunk, cch, _buf, 0 );
_in = (_in + cch) % _buf.length;
}
}
private void emit ( int cp, int cch )
{
emit(
getRoot()._text._buf,
getRoot()._text.unObscure( cp, cch ),
cch );
}
private void emit ( char ch )
{
preEmit( 1 );
_buf[ _in ] = ch;
_in = (_in + 1) % _buf.length;
}
private void emit ( char[] buf, int off, int cch )
{
assert cch >= 0;
if (preEmit( cch ))
return;
int chunk;
if (_in <= _out || cch < (chunk = _buf.length - _in))
{
System.arraycopy( buf, off, _buf, _in, cch );
_in += cch;
}
else
{
System.arraycopy( buf, off, _buf, _in, chunk );
System.arraycopy( buf, off + chunk, _buf, 0, cch - chunk );
_in = (_in + cch) % _buf.length;
}
}
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;
}
int getAvailable ( )
{
return _buf == null ? 0 : _buf.length - _free;
}
/**
* Make sure there is enough room for cch chars in the buffer
*/
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;
}
/**
* Ensure all text and return it as a string.
*/
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;
}
/**
* A Reader which exposes the text of a part of the tree.
*/
static final class TextReader extends Reader
{
TextReader ( Root r, Splay s, int p, XmlOptions options )
{
_textSaver = new TextSaver( r, s, p, 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 (
Root r, Splay s, int p, XmlOptions options )
{
options = XmlOptions.maskNull(options);
_byteBuffer = new OutputStreamImpl();
String encoding = null;
if (r._props.getEncoding() != null)
{
encoding =
EncodingMap.getIANA2JavaMapping( r._props.getEncoding() );
}
if (options.hasOption(XmlOptions.CHARACTER_ENCODING))
{
encoding =
(String) options.get(XmlOptions.CHARACTER_ENCODING);
}
if (encoding != null)
{
String ianaEncoding =
EncodingMap.getJava2IANAMapping( encoding );
if (ianaEncoding != null)
encoding = ianaEncoding;
}
if (encoding == null)
encoding = EncodingMap.getJava2IANAMapping( "UTF8" );
String javaEncoding = EncodingMap.getIANA2JavaMapping( encoding );
if (javaEncoding == null)
{
throw
new IllegalStateException(
"Unknown encoding: " + encoding );
}
try
{
_converter = new OutputStreamWriter( _byteBuffer, javaEncoding);
}
catch ( UnsupportedEncodingException e )
{
throw new RuntimeException( e );
}
_textSaver = new TextSaver( r, s, p, options, encoding );
}
public int read ( )
{
return _byteBuffer.read();
}
public int read ( byte[] bbuf, int off, int len )
{
return _byteBuffer.read ( bbuf, off, len );
}
private int ensure ( int cbyte )
{
// Even if we're asked to ensure nothing, still try to ensure
// atleast one byte so we can determine if we're at the
// end of the stream.
if (cbyte <= 0)
cbyte = 1;
int bytesAvailable = _byteBuffer.getAvailable();
for ( ; bytesAvailable < cbyte ;
bytesAvailable = _byteBuffer.getAvailable() )
{
if (_textSaver.write( _converter, 2048 ) < 2048)
break;
}
bytesAvailable = _byteBuffer.getAvailable();
if (bytesAvailable == 0)
return 0;
return bytesAvailable;
}
private final class OutputStreamImpl extends OutputStream
{
int read ( )
{
if (InputStreamSaver.this.ensure( 1 ) == 0)
return -1;
assert getAvailable() > 0;
int bite = _buf[ _out ];
_out = (_out + 1) % _buf.length;
_free++;
return bite;
}
int read ( byte[] bbuf, int off, int len )
{
// Check for end of stream even if there is no way to return
// characters because the Reader doc says to return -1 at end of
// stream.
int n;
if ((n = ensure( len )) == 0)
return -1;
if (bbuf == null || len <= 0)
return 0;
if (n < len)
len = n;
if (_out < _in)
{
System.arraycopy( _buf, _out, bbuf, off, len );
}
else
{
int chunk = _buf.length - _out;
if (chunk >= len)
System.arraycopy( _buf, _out, bbuf, off, len );
else
{
System.arraycopy( _buf, _out, bbuf, off, chunk );
System.arraycopy(
_buf, 0, bbuf, off + chunk, len - chunk );
}
}
_out = (_out + len) % _buf.length;
_free += len;
return len;
}
int getAvailable ( )
{
return _buf == null ? 0 : _buf.length - _free;
}
public void write ( int bite )
{
if (_free == 0)
resize( 1 );
assert _free > 0;
_buf[ _in ] = (byte) bite;
_in = (_in + 1) % _buf.length;
_free--;
}
public void write ( byte[] buf, int off, int cbyte )
{
assert cbyte >= 0;
if (cbyte == 0)
return;
if (_free < cbyte)
resize( cbyte );
if (_in == _out)
{
assert getAvailable() == 0;
assert _free == _buf.length - getAvailable();
_in = _out = 0;
}
int chunk;
if (_in <= _out || cbyte < (chunk = _buf.length - _in))
{
System.arraycopy( buf, off, _buf, _in, cbyte );
_in += cbyte;
}
else
{
System.arraycopy( buf, off, _buf, _in, chunk );
System.arraycopy(
buf, off + chunk, _buf, 0, cbyte - chunk );
_in = (_in + cbyte) % _buf.length;
}
_free -= cbyte;
}
void resize ( int cbyte )
{
assert cbyte > _free;
int newLen = _buf == null ? _initialBufSize : _buf.length * 2;
int used = getAvailable();
while ( newLen - used < cbyte )
newLen *= 2;
byte[] newBuf = new byte [ newLen ];
if (used > 0)
{
if (_out == _in)
System.arraycopy( _buf, 0, newBuf, 0, used );
else if (_in > _out)
System.arraycopy( _buf, _out, newBuf, 0, used );
else
{
System.arraycopy(
_buf, _out, newBuf, 0, used - _in );
System.arraycopy(
_buf, 0, newBuf, used - _in, _in );
}
_out = 0;
_in = used;
_free += newBuf.length - _buf.length;
}
else
{
_free += newBuf.length;
assert _in == 0 && _out == 0;
}
_buf = newBuf;
}
private static final int _initialBufSize = 4096;
int _free;
int _in;
int _out;
byte[] _buf;
}
private OutputStreamImpl _byteBuffer;
private TextSaver _textSaver;
private OutputStreamWriter _converter;
}
/**
*
*/
static final class XmlInputStreamSaver extends Saver
{
XmlInputStreamSaver ( Root r, Splay s, int p, XmlOptions options )
{
super( r, s, p, options );
}
XMLEvent dequeue ( ) throws XMLStreamException
{
if (_out == null && !process())
return null;
if (_out == null)
return null;
XmlEventImpl e = _out;
if ((_out = _out._next) == null)
_in = null;
return e;
}
private void enqueue ( XmlEventImpl e )
{
assert e._next == null;
if (_in == null)
{
assert _out == null;
_out = _in = e;
}
else
{
_in._next = e;
_in = e;
}
}
//
//
//
protected void emitXmlnsFragment ( Splay s )
{
throw new IllegalStateException( "Can't stream an attribute" );
}
protected void emitText ( Splay s, int p, int cch )
{
assert cch > 0;
enqueue( new CharacterDataImpl( getRoot(), s, p, null ) );
}
protected void emitTextFragment ( Splay s, int p, int cch )
{
// BUGBUG - there could be namespaces pushed for this text, not
// sure how to communicate those, however
if (_text != null)
{
if (_text.length() > 0)
emitText( s, p, cch );
return;
}
if (s != null && cch > 0)
emitText( s, p, cch );
}
protected void emitTextAfter ( Splay s, int p, int cch )
{
if (_text == null)
emitText( s, p, cch );
else
enqueue( new CharacterDataImpl( getRoot(), s, -1, text() ) );
}
protected void emitEndPrefixMappings ( )
{
Root r = getRoot();
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
String prevPrefixUri = mappingPrevPrefixUri();
if (prevPrefixUri == null)
enqueue( new EndPrefixMappingImpl( r, mappingPrefix() ) );
else
{
enqueue(
new ChangePrefixMappingImpl(
r, mappingPrefix(), mappingUri(), prevPrefixUri ) );
}
}
}
protected void emitEnd ( Splay s, QName name )
{
Root r = getRoot();
if (s.isRoot())
enqueue( new EndDocumentImpl( r, s ) );
else if (s.isEnd())
enqueue( new EndElementImpl( r, s, name, getUriMap() ) );
else
{
assert s.isLeaf();
enqueue( new EndElementImpl( r, s, name, getUriMap() ) );
}
emitEndPrefixMappings();
}
protected void emitCommentFragment ( Splay s )
{
emitComment( s );
}
protected void emitProcinstFragment ( Splay s )
{
emitProcinst( s );
}
protected void emitDocType(
String doctypeName, String publicID, String systemID )
{
}
protected void emitComment ( Splay s )
{
enqueue( new CommentImpl( getRoot(), s ) );
}
protected void emitProcinst ( Splay s )
{
enqueue( new ProcessingInstructionImpl( getRoot(), s ) );
}
protected void emitAttrFragment ( Splay s )
{
throw new IllegalStateException( "Can't stream an attribute" );
}
protected void emitContainer ( Container c, QName name )
{
Root r = getRoot();
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
enqueue(
new StartPrefixMappingImpl(
r, mappingPrefix(), mappingUri() ) );
}
if (c.isDoc())
enqueue( new StartDocumentImpl( r, c ) );
if (name != null)
{
enqueue(
new StartElementImpl( r, c, name, this ) );
}
if (c.isDoc())
{
assert c.isDoc();
if (_text != null)
{
if (_text.length() > 0)
enqueue( new CharacterDataImpl( r, c, -1, text() ) );
}
else if (c.getCch() > 0)
enqueue( new CharacterDataImpl( r, c, 1, null ) );
}
else if (c.isLeaf())
{
if (_text != null)
{
if (_text.length() > 0)
enqueue( new CharacterDataImpl( r, c, -1, text() ));
}
else if (c.getCchValue() > 0)
enqueue( new CharacterDataImpl( r, c, 1, null ) );
enqueue( new EndElementImpl( r, c, name, getUriMap() ) );
emitEndPrefixMappings();
}
}
//
//
//
private static XMLName computeName ( QName n, Map uriMap )
{
String uri = n.getNamespaceURI();
if (uri.length() == 0)
uri = null;
assert n.getLocalPart().length() > 0;
// The following assert may fire if someone computes a name
// of an element/attr too late (after other events have been
// enqueued and the uri map has been updated. I check later
// to make sure we don't crash, however.
assert uri == null || uriMap.containsKey( uri ) : "Problem uri " + uri;
String prefix = null;
if (uri != null)
{
prefix = (String) uriMap.get( uri );
if (prefix != null && prefix.length() == 0)
prefix = null;
}
return new XmlNameImpl( uri, n.getLocalPart(), prefix );
}
private static abstract class XmlEventImpl extends XmlEventBase
{
public Object monitor()
{
return _root;
}
XmlEventImpl ( int type, Root r, Splay s )
{
super( type );
_root = r;
_splay = s;
_version = _root.getVersion();
}
public XMLName getName ( )
{
synchronized (monitor())
{
checkVersion();
return null;
}
}
public XMLName getSchemaType ( )
{
synchronized (monitor())
{
checkVersion();
throw new RuntimeException( "NYI" );
}
}
public boolean hasName ( )
{
synchronized (monitor())
{
checkVersion();
return false;
}
}
public final Location getLocation ( )
{
synchronized (monitor())
{
checkVersion();
// TODO - perhaps I can save a location goober sometimes?
return null;
}
}
protected final void checkVersion ( )
{
if (_version != _root.getVersion())
throw new ConcurrentModificationException( "Document changed" );
}
protected final Root getRoot ( )
{
return _root;
}
protected final Splay getSplay ( )
{
return _splay;
}
private Root _root;
private Splay _splay;
private long _version;
XmlEventImpl _next;
}
private static class StartDocumentImpl
extends XmlEventImpl implements StartDocument
{
StartDocumentImpl ( Root r, Splay s )
{
super( XMLEvent.START_DOCUMENT, r, s );
}
public String getSystemId ( )
{
synchronized (monitor())
{
checkVersion();
return getRoot()._props.getDoctypeSystemId();
}
}
public String getCharacterEncodingScheme ( )
{
synchronized (monitor())
{
checkVersion();
return getRoot()._props.getEncoding();
}
}
public boolean isStandalone ( )
{
synchronized (monitor())
{
checkVersion();
return getRoot()._standAlone;
}
}
public String getVersion ( )
{
synchronized (monitor())
{
checkVersion();
return getRoot()._props.getVersion();
}
}
}
private static class StartElementImpl
extends XmlEventImpl implements StartElement
{
StartElementImpl ( Root r, Splay s, QName name, Saver saver )
{
super( XMLEvent.START_ELEMENT, r, s );
_name = computeName( name, saver.getUriMap() );
_prefixMap = saver.getPrefixMap();
AttributeImpl lastAttr = null;
for ( Iterator i = saver._attrs.keySet().iterator() ;
i.hasNext() ; )
{
Splay a = (Splay) i.next();
AttributeImpl attr =
new NormalAttributeImpl(
r, a, (String) saver._attrs.get( a ),
saver.getUriMap() );
if (_attributes == null)
_attributes = attr;
else
lastAttr._next = attr;
lastAttr = attr;
}
lastAttr = null;
for ( saver.iterateMappings() ;
saver.hasMapping() ; saver.nextMapping() )
{
AttributeImpl attr =
new XmlnsAttributeImpl(
r, saver.mappingPrefix(), saver.mappingUri(),
saver.getUriMap() );
if (_namespaces == null)
_namespaces = attr;
else
lastAttr._next = attr;
lastAttr = attr;
}
}
public boolean hasName()
{
synchronized (monitor())
{
checkVersion();
return true;
}
}
public XMLName getName ( )
{
synchronized (monitor())
{
checkVersion();
return _name;
}
}
public AttributeIterator getAttributes ( )
{
synchronized (monitor())
{
checkVersion();
return
new AttributeIteratorImpl( getRoot(), _attributes, null );
}
}
public AttributeIterator getNamespaces ( )
{
synchronized (monitor())
{
checkVersion();
return
new AttributeIteratorImpl( getRoot(), null, _namespaces );
}
}
public AttributeIterator getAttributesAndNamespaces ( )
{
synchronized (monitor())
{
checkVersion();
return
new AttributeIteratorImpl(
getRoot(), _attributes, _namespaces );
}
}
public Attribute getAttributeByName ( XMLName xmlName )
{
synchronized (monitor())
{
checkVersion();
for ( AttributeImpl a = _attributes ; a != null ; a = a._next )
{
if (xmlName.equals( a.getName() ))
return a;
}
return null;
}
}
public String getNamespaceUri ( String prefix )
{
synchronized (monitor())
{
checkVersion();
return
(String) _prefixMap.get( prefix == null ? "" : prefix );
}
}
public Map getNamespaceMap ( )
{
synchronized (monitor())
{
checkVersion();
return _prefixMap;
}
}
private static class AttributeIteratorImpl
implements AttributeIterator
{
public Object monitor()
{
return _root;
}
AttributeIteratorImpl (
Root r, AttributeImpl attributes, AttributeImpl namespaces )
{
_root = r;
_version = r.getVersion();
_attributes = attributes;
_namespaces = namespaces;
}
public Attribute next ( )
{
synchronized (monitor())
{
checkVersion();
AttributeImpl attr = null;
if (_attributes != null)
{
attr = _attributes;
_attributes = attr._next;
}
else if (_namespaces != null)
{
attr = _namespaces;
_namespaces = attr._next;
}
return attr;
}
}
public boolean hasNext ( )
{
synchronized (monitor())
{
checkVersion();
return _attributes != null || _namespaces != null;
}
}
public Attribute peek ( )
{
synchronized (monitor())
{
checkVersion();
if (_attributes != null)
return _attributes;
else if (_namespaces != null)
return _namespaces;
return null;
}
}
public void skip ( )
{
synchronized (monitor())
{
checkVersion();
if (_attributes != null)
_attributes = _attributes._next;
else if (_namespaces != null)
_namespaces = _namespaces._next;
}
}
private final void checkVersion ( )
{
if (_version != _root.getVersion())
throw new IllegalStateException( "Document changed" );
}
private Root _root;
private long _version;
private AttributeImpl _attributes;
private AttributeImpl _namespaces;
}
private static abstract class AttributeImpl implements Attribute
{
public Object monitor()
{
return _root;
}
AttributeImpl ( Root r )
{
_root = r;
_version = r.getVersion();
}
public XMLName getName ( )
{
synchronized (monitor())
{
checkVersion();
return _name;
}
}
public String getType ( )
{
synchronized (monitor())
{
checkVersion();
// TODO - Make sure throwing away this DTD info is ok.
// Is there schema info which can return more useful info?
return "CDATA";
}
}
public XMLName getSchemaType ( )
{
synchronized (monitor())
{
checkVersion();
// TODO - Can I return something reasonable here?
return null;
}
}
protected final void checkVersion ( )
{
if (_version != _root.getVersion())
throw new IllegalStateException( "Document changed" );
}
AttributeImpl _next;
protected XMLName _name;
protected Root _root;
private long _version;
}
private static class XmlnsAttributeImpl extends AttributeImpl
{
XmlnsAttributeImpl (
Root r, String prefix, String uri, Map uriMap )
{
super( r );
_uri = uri;
String local;
if (prefix.length() == 0)
{
prefix = null;
local = "xmlns";
}
else
{
local = prefix;
prefix = "xmlns";
}
_name = new XmlNameImpl( null, local, prefix );
}
public String getValue ( )
{
synchronized (monitor())
{
checkVersion();
return _uri;
}
}
private String _uri;
}
private static class NormalAttributeImpl extends AttributeImpl
{
NormalAttributeImpl (
Root r, Splay s, String value, Map uriMap )
{
super( r );
assert s.isNormalAttr();
_splay = s;
_value = value;
_name = computeName( s.getName(), uriMap );
}
public String getValue ( )
{
synchronized (monitor())
{
checkVersion();
return _value != null ? _value : _splay.getText( _root );
}
}
private String _value; // If invalid in the store
private Splay _splay;
}
private XMLName _name;
private Map _prefixMap;
private AttributeImpl _attributes;
private AttributeImpl _namespaces;
}
private static class StartPrefixMappingImpl
extends XmlEventImpl implements StartPrefixMapping
{
StartPrefixMappingImpl ( Root r, String prefix, String uri )
{
super( XMLEvent.START_PREFIX_MAPPING, r, null );
_prefix = prefix;
_uri = uri;
}
public String getNamespaceUri ( )
{
synchronized (monitor())
{
checkVersion();
return _uri;
}
}
public String getPrefix ( )
{
synchronized (monitor())
{
checkVersion();
return _prefix;
}
}
private String _prefix, _uri;
}
private static class ChangePrefixMappingImpl
extends XmlEventImpl implements ChangePrefixMapping
{
ChangePrefixMappingImpl (
Root r, String prefix, String oldUri, String newUri )
{
super( XMLEvent.CHANGE_PREFIX_MAPPING, r, null );
_oldUri = oldUri;
_newUri = newUri;
_prefix = prefix;
}
public String getOldNamespaceUri ( )
{
synchronized (monitor())
{
checkVersion();
return _oldUri;
}
}
public String getNewNamespaceUri ( )
{
synchronized (monitor())
{
checkVersion();
return _newUri;
}
}
public String getPrefix ( )
{
synchronized (monitor())
{
checkVersion();
return _prefix;
}
}
private String _oldUri, _newUri, _prefix;
}
private static class EndPrefixMappingImpl
extends XmlEventImpl implements EndPrefixMapping
{
EndPrefixMappingImpl ( Root r, String prefix )
{
super( XMLEvent.END_PREFIX_MAPPING, r, null );
_prefix = prefix;
}
public String getPrefix ( )
{
synchronized (monitor())
{
checkVersion();
return _prefix;
}
}
private String _prefix;
}
private static class CharacterDataImpl
extends XmlEventImpl implements CharacterData
{
CharacterDataImpl ( Root r, Splay s, int p, String charData )
{
super( XMLEvent.CHARACTER_DATA, r, s );
assert p > 0 || (charData != null && charData.length() > 0);
_pos = p;
_charData = charData;
}
public String getContent ( )
{
synchronized (monitor())
{
checkVersion();
Splay s = getSplay();
if (_pos == -1)
return _charData;
Root r = getRoot();
return
r._text.fetch(
s.getCpForPos( r, _pos ), s.getPostCch( _pos ) );
}
}
public boolean hasContent ( )
{
synchronized (monitor())
{
checkVersion();
return true;
}
}
private int _pos;
private String _charData;
}
private static class EndElementImpl
extends XmlEventImpl implements EndElement
{
EndElementImpl ( Root r, Splay s, QName name, Map uriMap )
{
super( XMLEvent.END_ELEMENT, r, s );
assert s.isLeaf() || s.isEnd();
if (name == null)
{
if (s.isEnd())
s = s.getContainer();
name = s.getName();
}
_name = computeName( name, uriMap );
}
public boolean hasName ( )
{
synchronized (monitor())
{
checkVersion();
return true;
}
}
public XMLName getName ( )
{
synchronized (monitor())
{
checkVersion();
return _name;
}
}
private XMLName _name;
}
private static class EndDocumentImpl
extends XmlEventImpl implements EndDocument
{
EndDocumentImpl ( Root r, Splay s )
{
super( XMLEvent.END_DOCUMENT, r, s );
}
}
private static class CommentImpl
extends XmlEventImpl implements Comment
{
CommentImpl ( Root r, Splay s )
{
super( XMLEvent.COMMENT, r, s );
assert s.isComment();
}
public String getContent ( )
{
synchronized (monitor())
{
checkVersion();
Splay s = getSplay();
return s.getCch() == 0 ? null : s.getText( getRoot() );
}
}
public boolean hasContent ( )
{
synchronized (monitor())
{
checkVersion();
return getSplay().getCch() > 0;
}
}
}
private static class ProcessingInstructionImpl
extends XmlEventImpl implements ProcessingInstruction
{
ProcessingInstructionImpl ( Root r, Splay s )
{
super( XMLEvent.PROCESSING_INSTRUCTION, r, s );
assert s.isProcinst();
}
public String getTarget ( )
{
synchronized (monitor())
{
checkVersion();
return getSplay().getLocal();
}
}
public String getData ( )
{
synchronized (monitor())
{
checkVersion();
Splay s = getSplay();
return s.getCch() == 0 ? null : s.getText( getRoot() );
}
}
}
private XmlEventImpl _in, _out;
}
static final class XmlInputStreamImpl extends GenericXmlInputStream
{
XmlInputStreamImpl ( Root r, Splay s, int p, XmlOptions options )
{
_xmlInputStreamSaver =
new XmlInputStreamSaver( r, s, p, options );
// Make the saver grind away just a bit to throw any exceptions
// related to the inability to create a stream on this xml
_xmlInputStreamSaver.process();
}
protected XMLEvent nextEvent ( ) throws XMLStreamException
{
return _xmlInputStreamSaver.dequeue();
}
private XmlInputStreamSaver _xmlInputStreamSaver;
}
//
//
//
static final class ValidatorSaver
extends Saver implements ValidatorListener.Event
{
ValidatorSaver (
Root r, Splay s, int p,
XmlOptions options, ValidatorListener vEventSink )
{
super( r, s, p, options );
_wantDupAttrs = true;
assert p == 0;
_startSplay = s;
_vEventSink = vEventSink;
while ( process() )
; // Empty
}
protected void emitXmlnsFragment ( Splay s )
{
throw new IllegalStateException();
}
protected void emitTextFragment ( Splay s, int p, int cch )
{
throw new IllegalStateException();
}
protected void emitCommentFragment ( Splay s )
{
throw new IllegalStateException();
}
protected void emitProcinstFragment ( Splay s )
{
throw new IllegalStateException();
}
protected void emitTextAfter ( Splay s, int p, int cch )
{
if (_text == null)
{
assert cch > 0;
emitEvent( ValidatorListener.TEXT, s, p, null, s, p );
}
else
emitEvent( ValidatorListener.TEXT, s, p, null, text() );
}
protected void emitEnd ( Splay s, QName name )
{
emitEvent( ValidatorListener.END, s, 0 );
}
protected void emitDocType(
String doctypeName, String publicID, String systemID )
{
}
protected void emitComment ( Splay s )
{
if (s.getCchAfter() > 0)
emitEvent( ValidatorListener.TEXT, s, 0, null, s, 1 );
}
protected void emitProcinst ( Splay s )
{
if (s.getCchAfter() > 0)
emitEvent( ValidatorListener.TEXT, s, 0, null, s, 1 );
}
protected void emitAttrFragment ( Splay s )
{
emitEvent( ValidatorListener.BEGIN, s, 0, null );
if (_text != null)
{
if (_text.length() > 0)
emitEvent( ValidatorListener.TEXT, s, 0, null, text() );
}
else if (s.getCch() > 0)
emitEvent( ValidatorListener.TEXT, s, 0, null, s, 0 );
emitEvent( ValidatorListener.END, s, 0 );
}
protected void emitContainer ( Container c, QName name )
{
assert _xsiNoLoc == null;
assert _xsiLoc == null;
assert _xsiType == null;
assert _xsiNil == null;
for ( Splay s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() )
{
if (s.isXsiAttr())
{
String local = s.getLocal();
if (local.equals( "type" ))
_xsiType = s;
else if (local.equals( "nil" ))
_xsiNil = s;
else if (local.equals( "schemaLocation" ))
_xsiLoc = s;
else if (local.equals( "noNamespaceSchemaLocation" ))
_xsiNoLoc = s;
}
}
emitEvent(
ValidatorListener.BEGIN, c, 0,
c == _startSplay ? null : name );
_xsiNoLoc = _xsiLoc = _xsiType = _xsiNil = null;
for ( Iterator i = _attrs.keySet().iterator() ; i.hasNext() ; )
{
Splay s = (Splay) i.next();
if (s.isXsiAttr())
{
String local = s.getLocal();
if (local.equals( "type" ) ||
local.equals( "nil" ) ||
local.equals( "schemaLocation" ) ||
local.equals( "noNamespaceSchemaLocation" ))
{
continue;
}
}
String invalidAttrValue = (String) _attrs.get( s );
if (invalidAttrValue == null)
{
emitEvent(
ValidatorListener.ATTR, s, 0, s.getName(), s, 0 );
}
else
{
emitEvent(
ValidatorListener.ATTR, s, 0, s.getName(),
invalidAttrValue );
}
}
emitEvent( ValidatorListener.ENDATTRS, c, 0 );
if (c.isDoc())
{
assert c.isDoc();
if (_text != null)
{
if (_text.length() > 0)
emitEvent( ValidatorListener.TEXT, c, 1, null, text() );
}
else if (c.getCch() > 0)
emitEvent( ValidatorListener.TEXT, c, 1, null, c, 1 );
}
else if (c.isLeaf())
{
int cch = _text != null ? _text.length() : c.getCchValue();
if (cch > 0)
{
if (_text != null)
{
emitEvent(
ValidatorListener.TEXT, c, 1, null, text() );
}
else
{
emitEvent(
ValidatorListener.TEXT, c, 1, null, c, 1 );
}
}
emitEvent( ValidatorListener.END, c, c.getPosLeafEnd() );
}
}
protected void emitEvent ( int kind, Splay sLoc, int pLoc )
{ emitEvent( kind, sLoc, pLoc, null, null, 0, null ); }
protected void emitEvent ( int kind, Splay sLoc, int pLoc, QName name )
{ emitEvent( kind, sLoc, pLoc, name, null, 0, null ); }
protected void emitEvent (
int kind, Splay sLoc, int pLoc, QName name, String text )
{ emitEvent ( kind, sLoc, pLoc, name, null, 0, text ); }
protected void emitEvent (
int kind, Splay sLoc, int pLoc, QName name, Splay sText, int pText )
{ emitEvent ( kind, sLoc, pLoc, name, sText, pText, null ); }
protected void emitEvent (
int kind, Splay sLoc, int pLoc, QName name,
Splay sText, int pText, String text )
{
if (kind == ValidatorListener.TEXT && _emittedText)
return;
boolean hasText = text != null || sText != null;
assert
!hasText ||
(kind == ValidatorListener.ATTR ||
kind == ValidatorListener.TEXT);
assert kind != ValidatorListener.ATTR || hasText;
assert kind != ValidatorListener.TEXT || hasText;
assert kind != ValidatorListener.ATTR || name != null;
_name = name;
_sText = sText;
_pText = pText;
_eventText = text;
_hasText = hasText;
_sLoc = sLoc;
_pLoc = pLoc;
_vEventSink.nextEvent( kind, this );
_emittedText = kind == ValidatorListener.TEXT;
}
//
//
//
public XmlCursor getLocationAsCursor ( )
{
checkVersion();
return new Cursor( getRoot(), _sLoc, _pLoc );
}
public boolean getXsiType ( Chars chars )
{
if (_xsiType == null)
return false;
setChars( chars, PRESERVE, null, _xsiType, 0 );
return true;
}
public boolean getXsiNil ( Chars chars )
{
if (_xsiNil == null)
return false;
setChars( chars, PRESERVE, null, _xsiNil, 0 );
return true;
}
public boolean getXsiLoc ( Chars chars )
{
if (_xsiLoc == null)
return false;
setChars( chars, PRESERVE, null, _xsiLoc, 0 );
return true;
}
public boolean getXsiNoLoc ( Chars chars )
{
if (_xsiNoLoc == null)
return false;
setChars( chars, PRESERVE, null, _xsiNoLoc, 0 );
return true;
}
public QName getName ( )
{
return _name;
}
private void setChars (
Chars chars, int wsr, String string, Splay sText, int pText )
{
assert string != null || sText != null;
checkVersion();
Root r = getRoot();
chars.buffer = null;
chars.string = null;
if (string != null)
{
chars.string = string;
}
else if (pText == 0)
{
chars.buffer = r._text._buf;
chars.length = sText.getCch();
chars.offset =
r._text.unObscure(
r.getCp( sText ), chars.length );
}
else if (pText == 1 && sText.isLeaf())
{
chars.buffer = r._text._buf;
chars.length = sText.getCchValue();
chars.offset =
r._text.unObscure(
r.getCp( sText ), chars.length );
}
else
{
assert pText == sText.getPosAfter();
boolean moreText = false;
for ( Splay t = sText.nextNonAttrSplay() ; ;
t = t.nextSplay() )
{
if (!t.isComment() && !t.isProcinst())
break;
if (t.getCchAfter() > 0)
{
moreText = true;
break;
}
}
if (!moreText)
{
chars.buffer = r._text._buf;
chars.length = sText.getCchAfter();
chars.offset =
r._text.unObscure(
sText.getCpForPos( r, pText ),
chars.length );
}
else
{
StringBuffer sb = new StringBuffer();
int cch = sText.getCchAfter();
int off =
r._text.unObscure(
sText.getCpForPos( r, pText ), cch );
sb.append( r._text._buf, off, cch );
for ( Splay t = sText.nextNonAttrSplay() ; ;
t = t.nextSplay() )
{
if (!t.isComment() && !t.isProcinst())
break;
if (t.getCchAfter() > 0)
{
cch = t.getCchAfter();
off =
r._text.unObscure(
t.getCpForPos( r, 1 ), cch );
sb.append( r._text._buf, off, cch );
}
}
chars.length = sb.length();
chars.buffer = new char [ chars.length ];
chars.offset = 0;
sb.getChars( 0, chars.length, chars.buffer, 0 );
}
}
if (wsr != PRESERVE)
{
// TODO - this is quick, dirty and very inefficient
// make it faster!
String str = chars.asString();
StringBuffer sb = new StringBuffer();
int state = -1, nSpaces = 0, cch = str.length();
for ( int i = 0 ; i < cch ; i++ )
{
char ch = str.charAt( i );
if (ch == '\n' || ch == '\r' || ch == '\t')
ch = ' ';
if (wsr == COLLAPSE)
{
if (ch == ' ')
{
if (state == -1)
continue;
nSpaces++;
continue;
}
if (nSpaces > 1)
nSpaces = 1;
for ( ; nSpaces > 0 ; nSpaces-- )
sb.append( ' ' );
state = 0;
}
sb.append( ch );
}
chars.string = sb.toString();
chars.buffer = null;
}
}
public void getText ( Chars chars )
{
getText( chars, PRESERVE );
}
public void getText ( Chars chars, int wsr )
{
if (!_hasText)
throw new RuntimeException( "No text for this event");
setChars( chars, wsr, _eventText, _sText, _pText );
}
// TODO - rather expensive to make Chars and getText and get
// String
public boolean textIsWhitespace ( )
{
Chars chars = new Chars();
getText( chars );
String s = chars.asString();
for ( int i = 0 ; i < s.length() ; i++ )
{
switch ( s.charAt( i ) )
{
case ' ':
case '\n':
case '\r':
case '\t':
break;
default :
return false;
}
}
return true;
}
private ValidatorListener _vEventSink;
private Splay _startSplay;
private boolean _emittedText;
private QName _name;
private Splay _xsiType;
private Splay _xsiNil;
private Splay _xsiLoc;
private Splay _xsiNoLoc;
private String _eventText;
private Splay _sText;
private int _pText;
private Splay _sLoc;
private int _pLoc;
private boolean _hasText;
}
//
//
//
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 );
_attributes = new AttributesImpl();
_wantFragTest = true;
_contentHandler = contentHandler;
_lexicalhandler = lexicalhandler;
// _contentHandler.setDocumentLocator( hhmmmm );
_contentHandler.startDocument();
try
{
while ( process() )
;
}
catch ( SaverSAXException e )
{
throw e._saxException;
}
_contentHandler.endDocument();
}
private class SaverSAXException extends RuntimeException
{
SaverSAXException ( SAXException e )
{
_saxException = e;
}
SAXException _saxException;
}
private void throwSaxException ( SAXException e )
{
throw new SaverSAXException( e );
}
protected void emitContainer ( Container c, QName name )
{
if (c.isBegin())
{
emitContainerHelper( c, name, null, null );
if (c.isLeaf())
{
int cch = _text == null ? c.getCchValue() : _text.length();
if (cch > 0)
{
if (_text == null)
emitCharacters( c, 0, cch );
else
emitCharacters( _text );
}
emitEndElement( c.getName() );
}
}
else
{
assert c.isDoc();
if (name != null)
emitContainerHelper( c, name, null, null );
if (_text == null)
emitCharacters( c, 0, c.getCch() );
else
emitCharacters( _text );
}
}
private void emitAttrHelper ( Splay s, String value )
{
assert s.isNormalAttr();
String local = s.getLocal();
String uri = s.getUri();
_attributes.addAttribute(
s.getUri(), s.getLocal(),
getPrefixedName( s.getName() ),
"CDATA",
value == null ? s.getText( getRoot() ) : value );
}
private void addNamespaceAttr ( String prefix, String uri )
{
try
{
_contentHandler.startPrefixMapping( prefix, uri );
}
catch ( SAXException e )
{
throwSaxException( e );
}
if (prefix.length() == 0)
_attributes.addAttribute( "", "", "xmlns", "CDATA", uri );
else
{
_attributes.addAttribute(
"", "", "xmlns:" + prefix, "CDATA", uri );
}
}
private void emitNamespacesHelper ( )
{
for ( iterateMappings() ; hasMapping() ; nextMapping() )
addNamespaceAttr( mappingPrefix(), mappingUri() );
}
private void emitContainerHelper (
Container c, QName name,
Splay extraAttr, StringBuffer extraAttrText )
{
assert name != null;
_attributes.clear();
if (_saveNamespacesFirst)
emitNamespacesHelper();
for ( Iterator i = _attrs.keySet().iterator() ; i.hasNext() ; )
{
Splay s = (Splay) i.next();
emitAttrHelper( s, (String) _attrs.get( s ) );
}
if (extraAttr != null)
{
emitAttrHelper(
extraAttr,
extraAttrText == null ? null : extraAttrText.toString() );
}
if (!_saveNamespacesFirst)
emitNamespacesHelper();
emitElement( name, getPrefixedName( name ) );
}
private void emitCharacters ( char[] buf, int off, int cch )
{
try
{
_contentHandler.characters( buf, off, cch );
}
catch ( SAXException e )
{
throwSaxException( e );
}
}
private void emitCharacters ( Splay s, int p, int cch )
{
emitCharacters( s.getCpForPos( getRoot(), p ), cch );
}
private void emitCharacters ( int cp, int cch )
{
if (cch == 0)
return;
emitCharacters(
getRoot()._text._buf, getRoot()._text.unObscure( cp, cch ),
cch );
}
private void emitCharacters ( StringBuffer sb )
{
if (sb.length() == 0)
return;
String text = sb.toString(); // Inefficient, use a shared char[]
emitCharacters( text.toCharArray(), 0, text.length() );
}
private void emitEndElement ( QName name )
{
try
{
_contentHandler.endElement(
name.getNamespaceURI(), name.getLocalPart(),
getPrefixedName( name ) );
}
catch ( SAXException e )
{
throwSaxException( e );
}
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
try
{
_contentHandler.endPrefixMapping( mappingPrefix() );
}
catch ( SAXException e )
{
throwSaxException( e );
}
}
}
private void endNamespaces ( )
{
try
{
for ( int i = 0 ; i < _attributes.getLength() ; i ++ )
{
String qn = _attributes.getQName( i );
if (!qn.startsWith( "xmlns" ))
continue;
int j = qn.indexOf( ':' );
if (j >= 0)
{
_contentHandler.endPrefixMapping(
qn.substring( j + 1 ) );
}
else
_contentHandler.endPrefixMapping( "" );
}
}
catch ( SAXException e )
{
throwSaxException( e );
}
}
private String getPrefixedName ( QName name )
{
String ns = name.getNamespaceURI();
String lp = name.getLocalPart();
if (ns.length() == 0)
return lp;
String prefix = getUriMapping( ns );
if (prefix.length() == 0)
return lp;
return prefix + ":" + lp;
}
private void emitElement ( QName name, String prefixedName )
{
try
{
_contentHandler.startElement(
name.getNamespaceURI(), name.getLocalPart(),
getPrefixedName( name ), _attributes );
}
catch ( SAXException e )
{
throwSaxException( e );
}
}
protected void emitEnd ( Splay s, QName name )
{
if (name != null)
emitEndElement( name );
}
protected void emitTextAfter ( Splay s, int p, int cch )
{
if (_text == null)
emitCharacters( s, p, cch );
else
emitCharacters( _text );
}
protected void emitDocType(
String doctypeName, String publicID, String systemID )
{
if (_lexicalhandler != null)
{
try
{
_lexicalhandler.startDTD( doctypeName, publicID, systemID );
_lexicalhandler.endDTD();
}
catch ( SAXException e )
{
throwSaxException( e );
}
}
}
protected void emitComment ( Splay s )
{
if (_lexicalhandler != null)
{
int cp = getRoot().getCp( s );
int cch = s.getCchValue();
try
{
_lexicalhandler.comment(
getRoot()._text._buf,
getRoot()._text.unObscure( cp, cch ),
cch );
}
catch ( SAXException e )
{
throwSaxException( e );
}
}
}
protected void emitProcinst ( Splay s )
{
try
{
_contentHandler.processingInstruction(
s.getLocal(), s.getText( getRoot() ) );
}
catch ( SAXException e )
{
throwSaxException( e );
}
}
protected void emitTextFragment ( Splay s, int p, int cch )
{
emitContainerHelper( null, _fragment, null, null );
if (_text != null)
{
if (_text.length() > 0)
emitCharacters( _text );
}
else if (s != null && cch > 0)
emitCharacters( s, p, cch );
emitEndElement( _fragment );
}
protected void emitXmlnsFragment ( Splay s )
{
emitContainerHelper( null, _fragment, null, null );
emitEndElement( _fragment );
}
protected void emitAttrFragment ( Splay s )
{
emitContainerHelper( null, _fragment, s, _text );
emitEndElement( _fragment );
}
protected void emitCommentFragment ( Splay s )
{
emitContainerHelper( null, _fragment, null, null );
emitComment( s );
emitEndElement( _fragment );
}
protected void emitProcinstFragment ( Splay s )
{
emitContainerHelper( null, _fragment, null, null );
emitProcinst( s );
emitEndElement( _fragment );
}
private AttributesImpl _attributes;
private SAXException _saxException;
private ContentHandler _contentHandler;
private LexicalHandler _lexicalhandler;
}
//
//
//
private static ThreadLocal _threadDocumentBuilderFactory =
new ThreadLocal()
{
protected Object initialValue()
{
try
{
return DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
catch ( ParserConfigurationException e )
{
throw new RuntimeException( e.getMessage(), e );
}
}
};
static final class DomSaver extends Saver
{
DomSaver ( Root r, Splay s, int p, boolean createDoc, XmlOptions options )
{
super( r, s, p, options );
_createDoc = createDoc;
}
Node exportDom ( )
throws Exception
{
// TODO - add an options which specifies a Document with which
// to create the fragment
_doc = ((DocumentBuilder) _threadDocumentBuilderFactory.get()).newDocument();
Node result;
if (_createDoc)
{
result = _currentNode = _doc;
}
else
{
DocumentFragment frag = _doc.createDocumentFragment();
result = _currentNode = frag;
}
while ( process() )
;
return result;
}
protected void emitContainer ( Container c, QName name )
{
Root r = getRoot();
if (c.isDoc())
{
if (hasMappings())
{
throw new IllegalStateException(
"Namespace attribute not associated with an element" );
}
for ( Splay s = c.nextSplay() ; s.isAttr() ; s = s.nextSplay() )
{
if (s.isNormalAttr())
{
throw new IllegalStateException(
"Attribute not associated with an element" );
}
}
String text = null;
if (_text != null)
{
if (_text.length() > 0)
text = text();
}
else if (c.getCch() > 0)
text = r._text.fetch( 0, c.getCch() );
if (text != null && _currentNode != _doc)
{
_currentNode.insertBefore(
_doc.createTextNode( text ), null );
}
}
else
{
assert c.isBegin();
String qname = c.getLocal();
if (c.getUri().length() > 0)
{
String prefix = getUriMapping( c.getUri() );
if (prefix.length() > 0)
qname = prefix + ":" + qname;
}
Element e = _doc.createElementNS( c.getUri(), qname );
_currentNode.insertBefore( e, null );
for ( iterateMappings() ; hasMapping() ; nextMapping() )
{
String prefix = mappingPrefix();
if (prefix.length() == 0)
qname = "xmlns";
else
qname = "xmlns:" + prefix;
e.setAttributeNS( Splay._xmlnsUri, qname, mappingUri() );
}
for ( Iterator i = _attrs.keySet().iterator() ; i.hasNext() ; )
{
Splay s = (Splay) i.next();
qname = s.getLocal();
if (s.getUri().length() > 0)
{
String prefix = getUriMapping( s.getUri() );
if (prefix.length() > 0)
qname = prefix + ":" + qname;
}
String invalidAttrValue = (String) _attrs.get( s );
e.setAttributeNS(
s.getUri(), qname,
invalidAttrValue == null
? s.getText( r )
: invalidAttrValue );
}
if (c.isLeaf())
{
String text = null;
if (_text != null)
{
if (_text.length() > 0)
text = text();
}
else if (c.getCchValue() > 0)
{
text =
r._text.fetch(
c.getCpForPos( r, 1 ), c.getCchValue() );
}
if (text != null)
e.insertBefore( _doc.createTextNode( text ), null );
}
else
{
_currentNode = e;
}
}
}
protected void emitEnd ( Splay s, QName name )
{
_currentNode = _currentNode.getParentNode();
}
protected void emitTextAfter ( Splay s, int p, int cch )
{
assert cch > 0;
Root r = getRoot();
String text = null;
if (_text != null)
{
if (_text.length() > 0)
text = text();
}
else if (cch > 0)
text = r._text.fetch( s.getCpForPos( r, p ), cch );
if (_currentNode != _doc)
_currentNode.insertBefore( _doc.createTextNode( text ), null );
}
protected void emitDocType(
String doctypeName, String publicID, String systemID )
{
}
protected void emitComment ( Splay s )
{
Root r = getRoot();
_currentNode.insertBefore(
_doc.createComment(
r._text.fetch(
s.getCpForPos( r, 0 ), s.getCchValue() ) ), null );
}
protected void emitProcinst ( Splay s )
{
Root r = getRoot();
_currentNode.insertBefore(
_doc.createProcessingInstruction(
s.getLocal(),
r._text.fetch(
s.getCpForPos( r, 0 ), s.getCchValue() ) ), null );
}
protected void emitXmlnsFragment ( Splay s )
{
throw new IllegalStateException(
"Cannot create a node for a namespace attribute" );
}
protected void emitAttrFragment ( Splay s )
{
throw new IllegalStateException(
"Cannot create a node for a attribute" );
}
protected void emitTextFragment ( Splay s, int p, int cch )
{
// BUGBUG - there could be namespaces pushed for this text, but not
// sure how to represent them here.... I should really put the
// fragment logic in the base saver and have the base saver
// synthesize well formed output...
if (s != null)
{
Root r = getRoot();
_currentNode.insertBefore(
_doc.createTextNode(
_text != null
? _text.toString()
: s != null
? r._text.fetch( s.getCpForPos( r, p ), cch )
: "" ),
null );
}
}
protected void emitCommentFragment ( Splay s )
{
Root r = getRoot();
_currentNode.insertBefore(
_doc.createComment(
r._text.fetch(
s.getCpForPos( r, 0 ), s.getCchValue() ) ), null );
}
protected void emitProcinstFragment ( Splay s )
{
Root r = getRoot();
_currentNode.insertBefore(
_doc.createProcessingInstruction(
s.getLocal(),
r._text.fetch(
s.getCpForPos( r, 0 ), s.getCchValue() ) ), null );
}
Document _doc;
Node _currentNode;
boolean _createDoc;
}
//
//
//
protected StringBuffer _text;
protected StringBuffer _sb;
protected boolean _skipContainerFinish;
protected LinkedHashMap _attrs;
private HashSet _attrNames;
private final boolean _inner;
private final Root _root;
private final Splay _top;
private final long _version;
protected boolean _wantDupAttrs;
protected boolean _wantFragTest;
protected boolean _needsFrag;
protected QName _fragment;
protected QName _docElem;
protected QName _synthElem;
protected boolean _saveNamespacesFirst;
protected boolean _useDefaultNamespace;
private boolean _prettyPrint;
private int _prettyIndent;
private int _prettyOffset;
private Splay _splay;
private int _pos;
private boolean _preProcess;
private boolean _postProcess;
private boolean _postPop;
private Splay _endSplay;
private ArrayList _namespaceStack;
private int _currentMapping;
private HashMap _uriMap;
private HashMap _prefixMap;
private boolean _firstPush;
private String _initialDefaultUri;
private HashMap _preComputedNamespaces;
private String _filterProcinst;
private Map _suggestedPrefixes;
protected String _newLine;
}