package org.xmlpull.mxp1_serializer; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import org.xmlpull.v1.XmlSerializer; /** * Implementation of XmlSerializer interface from XmlPull V1 API. * This implementation is optimzied 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; private static final boolean TRACE_ESCAPING = 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 String elPrefix[] = 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 neede 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; elPrefix[ 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 consistensy 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(elPrefix, 0, arr, 0, elStackSize); elPrefix = 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 writeLineSepartor; //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 chnages. */ protected void rebuildIndentationBuf() { if(doIndent == false) return; final int maxIndent = 65; //hardcoded maximum indentation size in characters int bufSize = 0; offsetNewLine = 0; if(writeLineSepartor) { 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(writeLineSepartor) { 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 = writeLineSepartor ? 0 : offsetNewLine; final int level = (depth > maxIndentLevel) ? maxIndentLevel : depth; out.write( indentationBuf, start, ( (level - 1) * 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); } writeLineSepartor = lineSeparator != null && lineSeparator.length() > 0; writeIndentation = indentationString != null && indentationString.length() > 0; // optimize - do not write when nothing to write ... doIndent = indentationString != null && (writeLineSepartor || 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.booleanValue()) { out.write("yes"); } else { out.write("no"); } out.write(attributeUseApostrophe ? '\'' : '"'); // if(standalone.booleanValue()) { // out.write(" standalone='yes'"); // } else { // out.write(" standalone='no'"); // } } out.write("?>"); } public void endDocument() throws IOException { // close all unclosed tag; while(depth > 0) { endTag(elNamespace[ depth ], elName[ depth ]); } //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) { return getPrefix(namespace, generatePrefix, false); } protected String getPrefix(String namespace, boolean generatePrefix, boolean nonEmpty) { //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 ]; if(nonEmpty && prefix.length() == 0) continue; // 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; ++depth; if(doIndent && depth > 0 && seenTag) { writeIndent(); } seenTag = true; setPrefixCalled = false; startTagIncomplete = true; 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 this algo a feature on serializer String prefix = null; if(depth > 0 && (namespaceEnd - elNamespaceCount[depth-1]) == 1) { // if only one prefix was declared un-declare it if the 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 namesapces (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: this is kludge! prefix = elPfx; } break; } } } } if(prefix == null) { prefix = lookupOrDeclarePrefix( namespace ); } //assert prefix != null; // make sure that default ("") namespace to not print ":" if(prefix.length() > 0) { elPrefix[ depth ] = prefix; out.write(prefix); out.write(':'); } else { elPrefix[ depth ] = ""; } } 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; } } elPrefix[ depth ] = ""; } } else { elPrefix[ depth ] = ""; } 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 = getPrefix( namespace, false, true ); //assert( prefix != null); //if(prefix.length() == 0) { if(prefix == null) { // 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 delcarations! 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); } String startTagName = elName[ depth ]; if((!namesInterned && !name.equals(startTagName)) || (namesInterned && name != startTagName )) { 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 { //assert startTagIncomplete == false; if(doIndent && seenTag) { writeIndent(); } out.write("</"); String startTagPrefix = elPrefix[ depth ]; if(startTagPrefix.length() > 0) { out.write(startTagPrefix); out.write(':'); } // if(namespace != null && namespace.length() > 0) { // //TODO prefix should be alredy 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('>'); --depth; } 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 ? "'" : """; 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("&"); pos = i + 1; } if(ch == '<') { if(i > pos) out.write(value.substring(pos, i)); out.write("<"); 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 { if(TRACE_ESCAPING) System.err.println(getClass().getName()+" DEBUG ATTR value.len="+value.length()+" "+printable(value)); throw new IllegalStateException( //"character "+Integer.toString(ch)+" is not allowed in output"+getLocation()); "character "+printable(ch)+" ("+Integer.toString(ch)+") is not allowed in output"+getLocation() +" (attr value="+printable(value)+")"); // 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 { // esccape '<', '&', ']]>', <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("&"); pos = i + 1; } else if(ch == '<') { if(i > pos) out.write(text.substring(pos, i)); out.write("<"); pos = i + 1; } else if(seenBracketBracket && ch == '>') { if(i > pos) out.write(text.substring(pos, i)); out.write(">"); 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 { if(TRACE_ESCAPING) System.err.println(getClass().getName()+" DEBUG TEXT value.len="+text.length()+" "+printable(text)); throw new IllegalStateException( "character "+Integer.toString(ch)+" is not allowed in output"+getLocation() +" (text value="+printable(text)+")"); // 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 { // esccape '<', '&', ']]>' 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("&"); pos = i + 1; } else if(ch == '<') { if(i > pos) { out.write(buf, pos, i - pos); } out.write("<"); pos = i + 1; } else if(seenBracketBracket && ch == '>') { if(i > pos) { out.write(buf, pos, i - pos); } out.write(">"); 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 { if(TRACE_ESCAPING) System.err.println( getClass().getName()+" DEBUG TEXT value.len=" +len+" "+printable(new String(buf,off,len))); throw new IllegalStateException( "character "+printable(ch)+" ("+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 */ protected static final String printable(String s) { if(s == null) return "null"; StringBuffer retval = new StringBuffer(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) { StringBuffer retval = new StringBuffer(); addPrintable(retval, ch); return retval.toString(); } private static void addPrintable(StringBuffer 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" + ss.substring(ss.length() - 4, ss.length())); } else { retval.append(ch); } } } }