/* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/
// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)

package org.codehaus.plexus.util.xml.pull;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

Implementation of XmlSerializer interface from XmlPull V1 API. This implementation is optimized for performance and low memory footprint.

Implemented features:

  • FEATURE_NAMES_INTERNED - when enabled all returned names (namespaces, prefixes) will be interned and it is required that all names passed as arguments MUST be interned
  • FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE

Implemented properties:

  • PROPERTY_SERIALIZER_INDENTATION
  • PROPERTY_SERIALIZER_LINE_SEPARATOR
/** * Implementation of XmlSerializer interface from XmlPull V1 API. This implementation is optimized for performance and * low memory footprint. * <p> * Implemented features: * <ul> * <li>FEATURE_NAMES_INTERNED - when enabled all returned names (namespaces, prefixes) will be interned and it is * required that all names passed as arguments MUST be interned * <li>FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE * </ul> * <p> * Implemented properties: * <ul> * <li>PROPERTY_SERIALIZER_INDENTATION * <li>PROPERTY_SERIALIZER_LINE_SEPARATOR * </ul> */
public class MXSerializer implements XmlSerializer { protected final static String XML_URI = "http://www.w3.org/XML/1998/namespace"; protected final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; private static final boolean TRACE_SIZING = false; protected final String FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE = "http://xmlpull.org/v1/doc/features.html#serializer-attvalue-use-apostrophe"; protected final String FEATURE_NAMES_INTERNED = "http://xmlpull.org/v1/doc/features.html#names-interned"; protected final String PROPERTY_SERIALIZER_INDENTATION = "http://xmlpull.org/v1/doc/properties.html#serializer-indentation"; protected final String PROPERTY_SERIALIZER_LINE_SEPARATOR = "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; protected final static String PROPERTY_LOCATION = "http://xmlpull.org/v1/doc/properties.html#location"; // properties/features protected boolean namesInterned; protected boolean attributeUseApostrophe; protected String indentationString = null; // " "; protected String lineSeparator = "\n"; protected String location; protected Writer out; protected int autoDeclaredPrefixes; protected int depth = 0; // element stack protected String elNamespace[] = new String[2]; protected String elName[] = new String[elNamespace.length]; protected int elNamespaceCount[] = new int[elNamespace.length]; // namespace stack protected int namespaceEnd = 0; protected String namespacePrefix[] = new String[8]; protected String namespaceUri[] = new String[namespacePrefix.length]; protected boolean finished; protected boolean pastRoot; protected boolean setPrefixCalled; protected boolean startTagIncomplete; protected boolean doIndent; protected boolean seenTag; protected boolean seenBracket; protected boolean seenBracketBracket; // buffer output if needed to write escaped String see text(String) private static final int BUF_LEN = Runtime.getRuntime().freeMemory() > 1000000L ? 8 * 1024 : 256; protected char buf[] = new char[BUF_LEN]; protected static final String precomputedPrefixes[]; static { precomputedPrefixes = new String[32]; // arbitrary number ... for ( int i = 0; i < precomputedPrefixes.length; i++ ) { precomputedPrefixes[i] = ( "n" + i ).intern(); } } private boolean checkNamesInterned = false; private void checkInterning( String name ) { if ( namesInterned && name != name.intern() ) { throw new IllegalArgumentException( "all names passed as arguments must be interned" + "when NAMES INTERNED feature is enabled" ); } } protected void reset() { location = null; out = null; autoDeclaredPrefixes = 0; depth = 0; // nullify references on all levels to allow it to be GCed for ( int i = 0; i < elNamespaceCount.length; i++ ) { elName[i] = null; elNamespace[i] = null; elNamespaceCount[i] = 2; } namespaceEnd = 0; // NOTE: no need to intern() as all literal strings and string-valued constant expressions // are interned. String literals are defined in 3.10.5 of the Java Language Specification // just checking ... // assert "xmlns" == "xmlns".intern(); // assert XMLNS_URI == XMLNS_URI.intern(); // TODO: how to prevent from reporting this namespace? // this is special namespace declared for consistency with XML infoset namespacePrefix[namespaceEnd] = "xmlns"; namespaceUri[namespaceEnd] = XMLNS_URI; ++namespaceEnd; namespacePrefix[namespaceEnd] = "xml"; namespaceUri[namespaceEnd] = XML_URI; ++namespaceEnd; finished = false; pastRoot = false; setPrefixCalled = false; startTagIncomplete = false; // doIntent is not changed seenTag = false; seenBracket = false; seenBracketBracket = false; } protected void ensureElementsCapacity() { final int elStackSize = elName.length; // assert (depth + 1) >= elName.length; // we add at least one extra slot ... final int newSize = ( depth >= 7 ? 2 * depth : 8 ) + 2; // = lucky 7 + 1 //25 if ( TRACE_SIZING ) { System.err.println( getClass().getName() + " elStackSize " + elStackSize + " ==> " + newSize ); } final boolean needsCopying = elStackSize > 0; String[] arr = null; // reuse arr local variable slot arr = new String[newSize]; if ( needsCopying ) System.arraycopy( elName, 0, arr, 0, elStackSize ); elName = arr; arr = new String[newSize]; if ( needsCopying ) System.arraycopy( elNamespace, 0, arr, 0, elStackSize ); elNamespace = arr; final int[] iarr = new int[newSize]; if ( needsCopying ) { System.arraycopy( elNamespaceCount, 0, iarr, 0, elStackSize ); } else { // special initialization iarr[0] = 0; } elNamespaceCount = iarr; } protected void ensureNamespacesCapacity() { // int size) { // int namespaceSize = namespacePrefix != null ? namespacePrefix.length : 0; // assert (namespaceEnd >= namespacePrefix.length); // if(size >= namespaceSize) { // int newSize = size > 7 ? 2 * size : 8; // = lucky 7 + 1 //25 final int newSize = namespaceEnd > 7 ? 2 * namespaceEnd : 8; if ( TRACE_SIZING ) { System.err.println( getClass().getName() + " namespaceSize " + namespacePrefix.length + " ==> " + newSize ); } final String[] newNamespacePrefix = new String[newSize]; final String[] newNamespaceUri = new String[newSize]; if ( namespacePrefix != null ) { System.arraycopy( namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd ); System.arraycopy( namespaceUri, 0, newNamespaceUri, 0, namespaceEnd ); } namespacePrefix = newNamespacePrefix; namespaceUri = newNamespaceUri; // TODO use hashes for quick namespace->prefix lookups // if( ! allStringsInterned ) { // int[] newNamespacePrefixHash = new int[newSize]; // if(namespacePrefixHash != null) { // System.arraycopy( // namespacePrefixHash, 0, newNamespacePrefixHash, 0, namespaceEnd); // } // namespacePrefixHash = newNamespacePrefixHash; // } // prefixesSize = newSize; // ////assert nsPrefixes.length > size && nsPrefixes.length == newSize // } } public void setFeature( String name, boolean state ) throws IllegalArgumentException, IllegalStateException { if ( name == null ) { throw new IllegalArgumentException( "feature name can not be null" ); } if ( FEATURE_NAMES_INTERNED.equals( name ) ) { namesInterned = state; } else if ( FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals( name ) ) { attributeUseApostrophe = state; } else { throw new IllegalStateException( "unsupported feature " + name ); } } public boolean getFeature( String name ) throws IllegalArgumentException { if ( name == null ) { throw new IllegalArgumentException( "feature name can not be null" ); } if ( FEATURE_NAMES_INTERNED.equals( name ) ) { return namesInterned; } else if ( FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals( name ) ) { return attributeUseApostrophe; } else { return false; } } // precomputed variables to simplify writing indentation protected int offsetNewLine; protected int indentationJump; protected char[] indentationBuf; protected int maxIndentLevel; protected boolean writeLineSeparator; // should end-of-line be written protected boolean writeIndentation; // is indentation used?
For maximum efficiency when writing indents the required output is pre-computed This is internal function that recomputes buffer after user requested changes.
/** * For maximum efficiency when writing indents the required output is pre-computed This is internal function that * recomputes buffer after user requested changes. */
protected void rebuildIndentationBuf() { if ( doIndent == false ) return; final int maxIndent = 65; // hardcoded maximum indentation size in characters int bufSize = 0; offsetNewLine = 0; if ( writeLineSeparator ) { offsetNewLine = lineSeparator.length(); bufSize += offsetNewLine; } maxIndentLevel = 0; if ( writeIndentation ) { indentationJump = indentationString.length(); maxIndentLevel = maxIndent / indentationJump; bufSize += maxIndentLevel * indentationJump; } if ( indentationBuf == null || indentationBuf.length < bufSize ) { indentationBuf = new char[bufSize + 8]; } int bufPos = 0; if ( writeLineSeparator ) { for ( int i = 0; i < lineSeparator.length(); i++ ) { indentationBuf[bufPos++] = lineSeparator.charAt( i ); } } if ( writeIndentation ) { for ( int i = 0; i < maxIndentLevel; i++ ) { for ( int j = 0; j < indentationString.length(); j++ ) { indentationBuf[bufPos++] = indentationString.charAt( j ); } } } } // if(doIndent) writeIndent(); protected void writeIndent() throws IOException { final int start = writeLineSeparator ? 0 : offsetNewLine; final int level = ( depth > maxIndentLevel ) ? maxIndentLevel : depth; out.write( indentationBuf, start, ( level * indentationJump ) + offsetNewLine ); } public void setProperty( String name, Object value ) throws IllegalArgumentException, IllegalStateException { if ( name == null ) { throw new IllegalArgumentException( "property name can not be null" ); } if ( PROPERTY_SERIALIZER_INDENTATION.equals( name ) ) { indentationString = (String) value; } else if ( PROPERTY_SERIALIZER_LINE_SEPARATOR.equals( name ) ) { lineSeparator = (String) value; } else if ( PROPERTY_LOCATION.equals( name ) ) { location = (String) value; } else { throw new IllegalStateException( "unsupported property " + name ); } writeLineSeparator = lineSeparator != null && lineSeparator.length() > 0; writeIndentation = indentationString != null && indentationString.length() > 0; // optimize - do not write when nothing to write ... doIndent = indentationString != null && ( writeLineSeparator || writeIndentation ); // NOTE: when indentationString == null there is no indentation // (even though writeLineSeparator may be true ...) rebuildIndentationBuf(); seenTag = false; // for consistency } public Object getProperty( String name ) throws IllegalArgumentException { if ( name == null ) { throw new IllegalArgumentException( "property name can not be null" ); } if ( PROPERTY_SERIALIZER_INDENTATION.equals( name ) ) { return indentationString; } else if ( PROPERTY_SERIALIZER_LINE_SEPARATOR.equals( name ) ) { return lineSeparator; } else if ( PROPERTY_LOCATION.equals( name ) ) { return location; } else { return null; } } private String getLocation() { return location != null ? " @" + location : ""; } // this is special method that can be accessed directly to retrieve Writer serializer is using public Writer getWriter() { return out; } public void setOutput( Writer writer ) { reset(); out = writer; } public void setOutput( OutputStream os, String encoding ) throws IOException { if ( os == null ) throw new IllegalArgumentException( "output stream can not be null" ); reset(); if ( encoding != null ) { out = new OutputStreamWriter( os, encoding ); } else { out = new OutputStreamWriter( os ); } } public void startDocument( String encoding, Boolean standalone ) throws IOException { char apos = attributeUseApostrophe ? '\'' : '"'; if ( attributeUseApostrophe ) { out.write( "<?xml version='1.0'" ); } else { out.write( "<?xml version=\"1.0\"" ); } if ( encoding != null ) { out.write( " encoding=" ); out.write( attributeUseApostrophe ? '\'' : '"' ); out.write( encoding ); out.write( attributeUseApostrophe ? '\'' : '"' ); // out.write('\''); } if ( standalone != null ) { out.write( " standalone=" ); out.write( attributeUseApostrophe ? '\'' : '"' ); if ( standalone ) { out.write( "yes" ); } else { out.write( "no" ); } out.write( attributeUseApostrophe ? '\'' : '"' ); // if(standalone.booleanValue()) { // out.write(" standalone='yes'"); // } else { // out.write(" standalone='no'"); // } } out.write( "?>" ); if ( writeLineSeparator ) { out.write( lineSeparator ); } } public void endDocument() throws IOException { // close all unclosed tag; while ( depth > 0 ) { endTag( elNamespace[depth], elName[depth] ); } if ( writeLineSeparator ) { out.write( lineSeparator ); } // assert depth == 0; // assert startTagIncomplete == false; finished = pastRoot = startTagIncomplete = true; out.flush(); } public void setPrefix( String prefix, String namespace ) throws IOException { if ( startTagIncomplete ) closeStartTag(); // assert prefix != null; // assert namespace != null; if ( prefix == null ) { prefix = ""; } if ( !namesInterned ) { prefix = prefix.intern(); // will throw NPE if prefix==null } else if ( checkNamesInterned ) { checkInterning( prefix ); } else if ( prefix == null ) { throw new IllegalArgumentException( "prefix must be not null" + getLocation() ); } // check that prefix is not duplicated ... for ( int i = elNamespaceCount[depth]; i < namespaceEnd; i++ ) { if ( prefix == namespacePrefix[i] ) { throw new IllegalStateException( "duplicated prefix " + printable( prefix ) + getLocation() ); } } if ( !namesInterned ) { namespace = namespace.intern(); } else if ( checkNamesInterned ) { checkInterning( namespace ); } else if ( namespace == null ) { throw new IllegalArgumentException( "namespace must be not null" + getLocation() ); } if ( namespaceEnd >= namespacePrefix.length ) { ensureNamespacesCapacity(); } namespacePrefix[namespaceEnd] = prefix; namespaceUri[namespaceEnd] = namespace; ++namespaceEnd; setPrefixCalled = true; } protected String lookupOrDeclarePrefix( String namespace ) { return getPrefix( namespace, true ); } public String getPrefix( String namespace, boolean generatePrefix ) { // assert namespace != null; if ( !namesInterned ) { // when String is interned we can do much faster namespace stack lookups ... namespace = namespace.intern(); } else if ( checkNamesInterned ) { checkInterning( namespace ); // assert namespace != namespace.intern(); } if ( namespace == null ) { throw new IllegalArgumentException( "namespace must be not null" + getLocation() ); } else if ( namespace.length() == 0 ) { throw new IllegalArgumentException( "default namespace cannot have prefix" + getLocation() ); } // first check if namespace is already in scope for ( int i = namespaceEnd - 1; i >= 0; --i ) { if ( namespace == namespaceUri[i] ) { final String prefix = namespacePrefix[i]; // now check that prefix is still in scope for ( int p = namespaceEnd - 1; p > i; --p ) { if ( prefix == namespacePrefix[p] ) continue; // too bad - prefix is redeclared with different namespace } return prefix; } } // so not found it ... if ( !generatePrefix ) { return null; } return generatePrefix( namespace ); } private String generatePrefix( String namespace ) { // assert namespace == namespace.intern(); while ( true ) { ++autoDeclaredPrefixes; // fast lookup uses table that was pre-initialized in static{} .... final String prefix = autoDeclaredPrefixes < precomputedPrefixes.length ? precomputedPrefixes[autoDeclaredPrefixes] : ( "n" + autoDeclaredPrefixes ).intern(); // make sure this prefix is not declared in any scope (avoid hiding in-scope prefixes)! for ( int i = namespaceEnd - 1; i >= 0; --i ) { if ( prefix == namespacePrefix[i] ) { continue; // prefix is already declared - generate new and try again } } // declare prefix if ( namespaceEnd >= namespacePrefix.length ) { ensureNamespacesCapacity(); } namespacePrefix[namespaceEnd] = prefix; namespaceUri[namespaceEnd] = namespace; ++namespaceEnd; return prefix; } } public int getDepth() { return depth; } public String getNamespace() { return elNamespace[depth]; } public String getName() { return elName[depth]; } public XmlSerializer startTag( String namespace, String name ) throws IOException { if ( startTagIncomplete ) { closeStartTag(); } seenBracket = seenBracketBracket = false; if ( doIndent && depth > 0 && seenTag ) { writeIndent(); } seenTag = true; setPrefixCalled = false; startTagIncomplete = true; ++depth; if ( ( depth + 1 ) >= elName.length ) { ensureElementsCapacity(); } //// assert namespace != null; if ( checkNamesInterned && namesInterned ) checkInterning( namespace ); elNamespace[depth] = ( namesInterned || namespace == null ) ? namespace : namespace.intern(); // assert name != null; // elName[ depth ] = name; if ( checkNamesInterned && namesInterned ) checkInterning( name ); elName[depth] = ( namesInterned || name == null ) ? name : name.intern(); if ( out == null ) { throw new IllegalStateException( "setOutput() must called set before serialization can start" ); } out.write( '<' ); if ( namespace != null ) { if ( namespace.length() > 0 ) { // ALEK: in future make it as feature on serializer String prefix = null; if ( depth > 0 && ( namespaceEnd - elNamespaceCount[depth - 1] ) == 1 ) { // if only one prefix was declared un-declare it if prefix is already declared on parent el with the // same URI String uri = namespaceUri[namespaceEnd - 1]; if ( uri == namespace || uri.equals( namespace ) ) { String elPfx = namespacePrefix[namespaceEnd - 1]; // 2 == to skip predefined namespaces (xml and xmlns ...) for ( int pos = elNamespaceCount[depth - 1] - 1; pos >= 2; --pos ) { String pf = namespacePrefix[pos]; if ( pf == elPfx || pf.equals( elPfx ) ) { String n = namespaceUri[pos]; if ( n == uri || n.equals( uri ) ) { --namespaceEnd; // un-declare namespace prefix = elPfx; } break; } } } } if ( prefix == null ) { prefix = lookupOrDeclarePrefix( namespace ); } // assert prefix != null; // make sure that default ("") namespace to not print ":" if ( prefix.length() > 0 ) { out.write( prefix ); out.write( ':' ); } } else { // make sure that default namespace can be declared for ( int i = namespaceEnd - 1; i >= 0; --i ) { if ( namespacePrefix[i] == "" ) { final String uri = namespaceUri[i]; if ( uri == null ) { // declare default namespace setPrefix( "", "" ); } else if ( uri.length() > 0 ) { throw new IllegalStateException( "start tag can not be written in empty default namespace " + "as default namespace is currently bound to '" + uri + "'" + getLocation() ); } break; } } } } out.write( name ); return this; } public XmlSerializer attribute( String namespace, String name, String value ) throws IOException { if ( !startTagIncomplete ) { throw new IllegalArgumentException( "startTag() must be called before attribute()" + getLocation() ); } // assert setPrefixCalled == false; out.write( ' ' ); //// assert namespace != null; if ( namespace != null && namespace.length() > 0 ) { // namespace = namespace.intern(); if ( !namesInterned ) { namespace = namespace.intern(); } else if ( checkNamesInterned ) { checkInterning( namespace ); } String prefix = lookupOrDeclarePrefix( namespace ); // assert( prefix != null); if ( prefix.length() == 0 ) { // needs to declare prefix to hold default namespace // NOTE: attributes such as a='b' are in NO namespace prefix = generatePrefix( namespace ); } out.write( prefix ); out.write( ':' ); // if(prefix.length() > 0) { // out.write(prefix); // out.write(':'); // } } // assert name != null; out.write( name ); out.write( '=' ); // assert value != null; out.write( attributeUseApostrophe ? '\'' : '"' ); writeAttributeValue( value, out ); out.write( attributeUseApostrophe ? '\'' : '"' ); return this; } protected void closeStartTag() throws IOException { if ( finished ) { throw new IllegalArgumentException( "trying to write past already finished output" + getLocation() ); } if ( seenBracket ) { seenBracket = seenBracketBracket = false; } if ( startTagIncomplete || setPrefixCalled ) { if ( setPrefixCalled ) { throw new IllegalArgumentException( "startTag() must be called immediately after setPrefix()" + getLocation() ); } if ( !startTagIncomplete ) { throw new IllegalArgumentException( "trying to close start tag that is not opened" + getLocation() ); } // write all namespace declarations! writeNamespaceDeclarations(); out.write( '>' ); elNamespaceCount[depth] = namespaceEnd; startTagIncomplete = false; } } private void writeNamespaceDeclarations() throws IOException { // int start = elNamespaceCount[ depth - 1 ]; for ( int i = elNamespaceCount[depth - 1]; i < namespaceEnd; i++ ) { if ( doIndent && namespaceUri[i].length() > 40 ) { writeIndent(); out.write( " " ); } if ( namespacePrefix[i] != "" ) { out.write( " xmlns:" ); out.write( namespacePrefix[i] ); out.write( '=' ); } else { out.write( " xmlns=" ); } out.write( attributeUseApostrophe ? '\'' : '"' ); // NOTE: escaping of namespace value the same way as attributes!!!! writeAttributeValue( namespaceUri[i], out ); out.write( attributeUseApostrophe ? '\'' : '"' ); } } public XmlSerializer endTag( String namespace, String name ) throws IOException { // check that level is valid //// assert namespace != null; // if(namespace != null) { // namespace = namespace.intern(); // } seenBracket = seenBracketBracket = false; if ( namespace != null ) { if ( !namesInterned ) { namespace = namespace.intern(); } else if ( checkNamesInterned ) { checkInterning( namespace ); } } if ( namespace != elNamespace[depth] ) { throw new IllegalArgumentException( "expected namespace " + printable( elNamespace[depth] ) + " and not " + printable( namespace ) + getLocation() ); } if ( name == null ) { throw new IllegalArgumentException( "end tag name can not be null" + getLocation() ); } if ( checkNamesInterned && namesInterned ) { checkInterning( name ); } if ( ( !namesInterned && !name.equals( elName[depth] ) ) || ( namesInterned && name != elName[depth] ) ) { throw new IllegalArgumentException( "expected element name " + printable( elName[depth] ) + " and not " + printable( name ) + getLocation() ); } if ( startTagIncomplete ) { writeNamespaceDeclarations(); out.write( " />" ); // space is added to make it easier to work in XHTML!!! --depth; } else { --depth; // assert startTagIncomplete == false; if ( doIndent && seenTag ) { writeIndent(); } out.write( "</" ); if ( namespace != null && namespace.length() > 0 ) { // TODO prefix should be already known from matching start tag ... final String prefix = lookupOrDeclarePrefix( namespace ); // assert( prefix != null); if ( prefix.length() > 0 ) { out.write( prefix ); out.write( ':' ); } } out.write( name ); out.write( '>' ); } namespaceEnd = elNamespaceCount[depth]; startTagIncomplete = false; seenTag = true; return this; } public XmlSerializer text( String text ) throws IOException { // assert text != null; if ( startTagIncomplete || setPrefixCalled ) closeStartTag(); if ( doIndent && seenTag ) seenTag = false; writeElementContent( text, out ); return this; } public XmlSerializer text( char[] buf, int start, int len ) throws IOException { if ( startTagIncomplete || setPrefixCalled ) closeStartTag(); if ( doIndent && seenTag ) seenTag = false; writeElementContent( buf, start, len, out ); return this; } public void cdsect( String text ) throws IOException { if ( startTagIncomplete || setPrefixCalled || seenBracket ) closeStartTag(); if ( doIndent && seenTag ) seenTag = false; out.write( "<![CDATA[" ); out.write( text ); // escape? out.write( "]]>" ); } public void entityRef( String text ) throws IOException { if ( startTagIncomplete || setPrefixCalled || seenBracket ) closeStartTag(); if ( doIndent && seenTag ) seenTag = false; out.write( '&' ); out.write( text ); // escape? out.write( ';' ); } public void processingInstruction( String text ) throws IOException { if ( startTagIncomplete || setPrefixCalled || seenBracket ) closeStartTag(); if ( doIndent && seenTag ) seenTag = false; out.write( "<?" ); out.write( text ); // escape? out.write( "?>" ); } public void comment( String text ) throws IOException { if ( startTagIncomplete || setPrefixCalled || seenBracket ) closeStartTag(); if ( doIndent && seenTag ) seenTag = false; out.write( "<!--" ); out.write( text ); // escape? out.write( "-->" ); } public void docdecl( String text ) throws IOException { if ( startTagIncomplete || setPrefixCalled || seenBracket ) closeStartTag(); if ( doIndent && seenTag ) seenTag = false; out.write( "<!DOCTYPE " ); out.write( text ); // escape? out.write( ">" ); } public void ignorableWhitespace( String text ) throws IOException { if ( startTagIncomplete || setPrefixCalled || seenBracket ) closeStartTag(); if ( doIndent && seenTag ) seenTag = false; if ( text.length() == 0 ) { throw new IllegalArgumentException( "empty string is not allowed for ignorable whitespace" + getLocation() ); } out.write( text ); // no escape? } public void flush() throws IOException { if ( !finished && startTagIncomplete ) closeStartTag(); out.flush(); } // --- utility methods protected void writeAttributeValue( String value, Writer out ) throws IOException { // .[apostrophe and <, & escaped], final char quot = attributeUseApostrophe ? '\'' : '"'; final String quotEntity = attributeUseApostrophe ? "&apos;" : "&quot;"; int pos = 0; for ( int i = 0; i < value.length(); i++ ) { char ch = value.charAt( i ); if ( ch == '&' ) { if ( i > pos ) out.write( value.substring( pos, i ) ); out.write( "&amp;" ); pos = i + 1; } if ( ch == '<' ) { if ( i > pos ) out.write( value.substring( pos, i ) ); out.write( "&lt;" ); pos = i + 1; } else if ( ch == quot ) { if ( i > pos ) out.write( value.substring( pos, i ) ); out.write( quotEntity ); pos = i + 1; } else if ( ch < 32 ) { // in XML 1.0 only legal character are #x9 | #xA | #xD // and they must be escaped otherwise in attribute value they are normalized to spaces if ( ch == 13 || ch == 10 || ch == 9 ) { if ( i > pos ) out.write( value.substring( pos, i ) ); out.write( "&#" ); out.write( Integer.toString( ch ) ); out.write( ';' ); pos = i + 1; } else { throw new IllegalStateException( "character " + Integer.toString( ch ) + " is not allowed in output" + getLocation() ); // in XML 1.1 legal are [#x1-#xD7FF] // if(ch > 0) { // if(i > pos) out.write(text.substring(pos, i)); // out.write("&#"); // out.write(Integer.toString(ch)); // out.write(';'); // pos = i + 1; // } else { // throw new IllegalStateException( // "character zero is not allowed in XML 1.1 output"+getLocation()); // } } } } if ( pos > 0 ) { out.write( value.substring( pos ) ); } else { out.write( value ); // this is shortcut to the most common case } } protected void writeElementContent( String text, Writer out ) throws IOException { // escape '<', '&', ']]>', <32 if necessary int pos = 0; for ( int i = 0; i < text.length(); i++ ) { // TODO: check if doing char[] text.getChars() would be faster than getCharAt(i) ... char ch = text.charAt( i ); if ( ch == ']' ) { if ( seenBracket ) { seenBracketBracket = true; } else { seenBracket = true; } } else { if ( ch == '&' ) { if ( i > pos ) out.write( text.substring( pos, i ) ); out.write( "&amp;" ); pos = i + 1; } else if ( ch == '<' ) { if ( i > pos ) out.write( text.substring( pos, i ) ); out.write( "&lt;" ); pos = i + 1; } else if ( seenBracketBracket && ch == '>' ) { if ( i > pos ) out.write( text.substring( pos, i ) ); out.write( "&gt;" ); pos = i + 1; } else if ( ch < 32 ) { // in XML 1.0 only legal character are #x9 | #xA | #xD if ( ch == 9 || ch == 10 || ch == 13 ) { // pass through // } else if(ch == 13) { //escape // if(i > pos) out.write(text.substring(pos, i)); // out.write("&#"); // out.write(Integer.toString(ch)); // out.write(';'); // pos = i + 1; } else { throw new IllegalStateException( "character " + Integer.toString( ch ) + " is not allowed in output" + getLocation() ); // in XML 1.1 legal are [#x1-#xD7FF] // if(ch > 0) { // if(i > pos) out.write(text.substring(pos, i)); // out.write("&#"); // out.write(Integer.toString(ch)); // out.write(';'); // pos = i + 1; // } else { // throw new IllegalStateException( // "character zero is not allowed in XML 1.1 output"+getLocation()); // } } } if ( seenBracket ) { seenBracketBracket = seenBracket = false; } } } if ( pos > 0 ) { out.write( text.substring( pos ) ); } else { out.write( text ); // this is shortcut to the most common case } } protected void writeElementContent( char[] buf, int off, int len, Writer out ) throws IOException { // escape '<', '&', ']]>' final int end = off + len; int pos = off; for ( int i = off; i < end; i++ ) { final char ch = buf[i]; if ( ch == ']' ) { if ( seenBracket ) { seenBracketBracket = true; } else { seenBracket = true; } } else { if ( ch == '&' ) { if ( i > pos ) { out.write( buf, pos, i - pos ); } out.write( "&amp;" ); pos = i + 1; } else if ( ch == '<' ) { if ( i > pos ) { out.write( buf, pos, i - pos ); } out.write( "&lt;" ); pos = i + 1; } else if ( seenBracketBracket && ch == '>' ) { if ( i > pos ) { out.write( buf, pos, i - pos ); } out.write( "&gt;" ); pos = i + 1; } else if ( ch < 32 ) { // in XML 1.0 only legal character are #x9 | #xA | #xD if ( ch == 9 || ch == 10 || ch == 13 ) { // pass through // } else if(ch == 13 ) { //if(ch == '\r') { // if(i > pos) { // out.write(buf, pos, i - pos); // } // out.write("&#"); // out.write(Integer.toString(ch)); // out.write(';'); // pos = i + 1; } else { throw new IllegalStateException( "character " + Integer.toString( ch ) + " is not allowed in output" + getLocation() ); // in XML 1.1 legal are [#x1-#xD7FF] // if(ch > 0) { // if(i > pos) out.write(text.substring(pos, i)); // out.write("&#"); // out.write(Integer.toString(ch)); // out.write(';'); // pos = i + 1; // } else { // throw new IllegalStateException( // "character zero is not allowed in XML 1.1 output"+getLocation()); // } } } if ( seenBracket ) { seenBracketBracket = seenBracket = false; } // assert seenBracketBracket == seenBracket == false; } } if ( end > pos ) { out.write( buf, pos, end - pos ); } }
simple utility method -- good for debugging
/** simple utility method -- good for debugging */
protected static final String printable( String s ) { if ( s == null ) return "null"; StringBuilder retval = new StringBuilder( s.length() + 16 ); retval.append( "'" ); char ch; for ( int i = 0; i < s.length(); i++ ) { addPrintable( retval, s.charAt( i ) ); } retval.append( "'" ); return retval.toString(); } protected static final String printable( char ch ) { StringBuilder retval = new StringBuilder(); addPrintable( retval, ch ); return retval.toString(); } private static void addPrintable( StringBuilder retval, char ch ) { switch ( ch ) { case '\b': retval.append( "\\b" ); break; case '\t': retval.append( "\\t" ); break; case '\n': retval.append( "\\n" ); break; case '\f': retval.append( "\\f" ); break; case '\r': retval.append( "\\r" ); break; case '\"': retval.append( "\\\"" ); break; case '\'': retval.append( "\\\'" ); break; case '\\': retval.append( "\\\\" ); break; default: if ( ch < 0x20 || ch > 0x7e ) { final String ss = "0000" + Integer.toString( ch, 16 ); retval.append( "\\u" ).append( ss, ss.length() - 4, ss.length() ); } else { retval.append( ch ); } } } }