/* * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.xml.internal.stream.writers; import com.sun.org.apache.xerces.internal.impl.Constants; import com.sun.org.apache.xerces.internal.impl.PropertyManager; import com.sun.org.apache.xerces.internal.util.NamespaceSupport; import com.sun.org.apache.xerces.internal.util.SymbolTable; import com.sun.org.apache.xerces.internal.xni.QName; import com.sun.xml.internal.stream.util.ReadOnlyIterator; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.transform.stream.StreamResult; import jdk.xml.internal.SecuritySupport; /** * This class implements a StAX XMLStreamWriter. It extends * AbstractMap in order to support a getter for * implementation-specific properties. For example, you can get * the underlying OutputStream by casting an instance * of this class to Map and calling * getProperty(OUTPUTSTREAM_PROPERTY). * * @author Neeraj Bajaj * @author K.Venugopal * @author Santiago Pericas-Geertsen * @author Sunitha Reddy */ public final class XMLStreamWriterImpl extends AbstractMap implements XMLStreamWriterBase { public static final String START_COMMENT = ""; public static final String DEFAULT_ENCODING = " encoding=\"utf-8\""; public static final String DEFAULT_XMLDECL = ""; public static final String DEFAULT_XML_VERSION = "1.0"; public static final char CLOSE_START_TAG = '>'; public static final char OPEN_START_TAG = '<'; public static final String OPEN_END_TAG = "'; public static final String START_CDATA = ""; public static final String CLOSE_EMPTY_ELEMENT = "/>"; public static final String SPACE = " "; public static final String UTF_8 = "UTF-8"; public static final String OUTPUTSTREAM_PROPERTY = "sjsxp-outputstream"; /** * This flag can be used to turn escaping off for content. It does * not apply to attribute content. */ boolean fEscapeCharacters = true; /** * Flag for the value of repairNamespace property */ private boolean fIsRepairingNamespace = false; /** * Underlying Writer to which characters are written. */ private Writer fWriter; /** * Underlying OutputStream to which fWriter * writes to. May be null if unknown. */ private OutputStream fOutputStream = null; /** * Collects attributes when the writer is in reparing mode. */ private List fAttributeCache; /** * Collects namespace declarations when the writer is in reparing mode. */ private List fNamespaceDecls; /** * Namespace context encapsulating user specified context * and context built by the writer */ private NamespaceContextImpl fNamespaceContext = null; private NamespaceSupport fInternalNamespaceContext = null; private Random fPrefixGen = null; /** * Reference to PropertyManager */ private PropertyManager fPropertyManager = null; /** * Flag to track if start tag is opened */ private boolean fStartTagOpened = false; /** * Boolean flag to indicate, if instance can be reused */ private boolean fReuse; private SymbolTable fSymbolTable = new SymbolTable(); private ElementStack fElementStack = new ElementStack(); //Change this .-Venu final private String DEFAULT_PREFIX = fSymbolTable.addSymbol(""); private final ReadOnlyIterator fReadOnlyIterator = new ReadOnlyIterator<>(); /** * In some cases, this charset encoder is used to determine if a char is * encodable by underlying writer. For example, an 8-bit char from the * extended ASCII set is not encodable by 7-bit ASCII encoder. Unencodable * chars are escaped using XML numeric entities. */ private CharsetEncoder fEncoder = null; /** * This is used to hold the namespace for attributes which happen to have * the same uri as the default namespace; It's added to avoid changing the * current impl. which has many redundant code for the repair mode */ Map fAttrNamespace = null; /** * Creates a new instance of XMLStreamWriterImpl. Uses platform's default * encoding. * * @param outputStream Underlying stream to write the bytes to * @param props Properties used by this writer */ public XMLStreamWriterImpl(OutputStream outputStream, PropertyManager props) throws IOException { // cannot call this(outputStream, null, props); for constructor, // OutputStreamWriter charsetName cannot be null // use default encoding this(new OutputStreamWriter(outputStream), props); } /** * Creates a new instance of XMLStreamWriterImpl. * * @param outputStream Underlying stream to write the bytes * @param encoding Encoding used to convert chars into bytes * @param props Properties used by this writer */ public XMLStreamWriterImpl(OutputStream outputStream, String encoding, PropertyManager props) throws java.io.IOException { this(new StreamResult(outputStream), encoding, props); } /** * Creates a new instance of XMLStreamWriterImpl using a Writer. * * @param writer Underlying writer to which chars are written * @param props Properties used by this writer */ public XMLStreamWriterImpl(Writer writer, PropertyManager props) throws java.io.IOException { this(new StreamResult(writer), null, props); } /** * Creates a new instance of XMLStreamWriterImpl using a StreamResult. * A StreamResult encasupates an OutputStream, a Writer or a SystemId. * * @param writer Underlying writer to which chars are written * @param props Properties used by this writer */ public XMLStreamWriterImpl(StreamResult sr, String encoding, PropertyManager props) throws java.io.IOException { setOutput(sr, encoding); fPropertyManager = props; init(); } /** * Initialize an instance of this XMLStreamWriter. Allocate new instances * for all the data structures. Set internal flags based on property values. */ private void init() { fReuse = false; fNamespaceDecls = new ArrayList<>(); fPrefixGen = new Random(); fAttributeCache = new ArrayList<>(); fInternalNamespaceContext = new NamespaceSupport(); fInternalNamespaceContext.reset(); fNamespaceContext = new NamespaceContextImpl(); fNamespaceContext.internalContext = fInternalNamespaceContext; // Set internal state based on property values Boolean ob = (Boolean) fPropertyManager.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES); fIsRepairingNamespace = ob; ob = (Boolean) fPropertyManager.getProperty(Constants.ESCAPE_CHARACTERS); setEscapeCharacters(ob); } /** * Reset this instance so that it can be re-used. Do not read properties * again. The method setOutput(StreamResult, encoding) must * be called after this one. */ public void reset() { reset(false); } /** * Reset this instance so that it can be re-used. Clears but does not * re-allocate internal data structures. * * @param resetProperties Indicates if properties should be read again */ void reset(boolean resetProperties) { if (!fReuse) { throw new java.lang.IllegalStateException( "close() Must be called before calling reset()"); } fReuse = false; fNamespaceDecls.clear(); fAttributeCache.clear(); // reset Element/NamespaceContext stacks fElementStack.clear(); fInternalNamespaceContext.reset(); fStartTagOpened = false; fNamespaceContext.userContext = null; if (resetProperties) { Boolean ob = (Boolean) fPropertyManager.getProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES); fIsRepairingNamespace = ob; ob = (Boolean) fPropertyManager.getProperty(Constants.ESCAPE_CHARACTERS); setEscapeCharacters(ob); } } /** * Use a StreamResult to initialize the output for this XMLStreamWriter. Check * for OutputStream, Writer and then systemId, in that order. * * @param sr StreamResult encapsulating output information * @param encoding Encoding to be used except when a Writer is available */ public void setOutput(StreamResult sr, String encoding) throws IOException { if (sr.getOutputStream() != null) { setOutputUsingStream(sr.getOutputStream(), encoding); } else if (sr.getWriter() != null) { setOutputUsingWriter(sr.getWriter()); } else if (sr.getSystemId() != null) { setOutputUsingStream(new FileOutputStream(sr.getSystemId()), encoding); } } private void setOutputUsingWriter(Writer writer) throws IOException { fWriter = writer; if (writer instanceof OutputStreamWriter) { String charset = ((OutputStreamWriter) writer).getEncoding(); if (charset != null && !charset.equalsIgnoreCase("utf-8")) { fEncoder = Charset.forName(charset).newEncoder(); } } } /** * Utility method to create a writer when passed an OutputStream. Make * sure to wrap an OutputStreamWriter using an * XMLWriter for performance reasons. * * @param os Underlying OutputStream * @param encoding Encoding used to convert chars into bytes */ private void setOutputUsingStream(OutputStream os, String encoding) throws IOException { fOutputStream = os; if (encoding != null) { if (encoding.equalsIgnoreCase("utf-8")) { fWriter = new UTF8OutputStreamWriter(os); } else { fWriter = new XMLWriter(new OutputStreamWriter(os, encoding)); fEncoder = Charset.forName(encoding).newEncoder(); } } else { encoding = SecuritySupport.getSystemProperty("file.encoding"); if (encoding != null && encoding.equalsIgnoreCase("utf-8")) { fWriter = new UTF8OutputStreamWriter(os); } else { fWriter = new XMLWriter(new OutputStreamWriter(os)); } } } /** Can this instance be reused * * @return boolean boolean value to indicate if this instance can be reused or not */ public boolean canReuse() { return fReuse; } public void setEscapeCharacters(boolean escape) { fEscapeCharacters = escape; } public boolean getEscapeCharacters() { return fEscapeCharacters; } /** * Close this XMLStreamWriter by closing underlying writer. */ @Override public void close() throws XMLStreamException { if (fWriter != null) { try { //fWriter.close(); fWriter.flush(); } catch (IOException e) { throw new XMLStreamException(e); } } fWriter = null; fOutputStream = null; fNamespaceDecls.clear(); fAttributeCache.clear(); fElementStack.clear(); fInternalNamespaceContext.reset(); fReuse = true; fStartTagOpened = false; fNamespaceContext.userContext = null; } /** * Flush this XMLStreamWriter by flushin underlying writer. */ @Override public void flush() throws XMLStreamException { try { fWriter.flush(); } catch (IOException e) { throw new XMLStreamException(e); } } /** * Return NamespaceContext being used by the writer. * * @return NamespaceContext */ @Override public NamespaceContext getNamespaceContext() { return fNamespaceContext; } /** * Return a prefix associated with specified uri, or null if the * uri is unknown. * * @param uri The namespace uri * @throws XMLStreamException if uri specified is "" or null */ @Override public String getPrefix(String uri) throws XMLStreamException { return fNamespaceContext.getPrefix(uri); } /** * Returns value associated with the specified property name. * * @param str Property name * @throws IllegalArgumentException if the specified property is not supported * @return value associated with the specified property. */ @Override public Object getProperty(String str) throws IllegalArgumentException { if (str == null) { throw new NullPointerException(); } if (!fPropertyManager.containsProperty(str)) { throw new IllegalArgumentException("Property '" + str + "' is not supported"); } return fPropertyManager.getProperty(str); } /** * Set the specified URI as default namespace in the current namespace context. * * @param uri Namespace URI */ @Override public void setDefaultNamespace(String uri) throws XMLStreamException { if (uri != null) { uri = fSymbolTable.addSymbol(uri); } if (fIsRepairingNamespace) { if (isDefaultNamespace(uri)) { return; } QName qname = new QName(); qname.setValues(DEFAULT_PREFIX, "xmlns", null, uri); fNamespaceDecls.add(qname); } else { fInternalNamespaceContext.declarePrefix(DEFAULT_PREFIX, uri); } } /** * Sets the current NamespaceContext for prefix and uri bindings. * This context becomes the root namespace context for writing and * will replace the current root namespace context. Subsequent calls * to setPrefix and setDefaultNamespace will bind namespaces using * the context passed to the method as the root context for resolving * namespaces. This method may only be called once at the start of the * document. It does not cause the namespaces to be declared. If a * namespace URI to prefix mapping is found in the namespace context * it is treated as declared and the prefix may be used by the * XMLStreamWriter. * * @param namespaceContext the namespace context to use for this writer, may not be null * @throws XMLStreamException */ @Override public void setNamespaceContext(NamespaceContext namespaceContext) throws XMLStreamException { fNamespaceContext.userContext = namespaceContext; } /** * Sets the prefix the uri is bound to. This prefix is bound in the scope of * the current START_ELEMENT / END_ELEMENT pair. If this method is called before * a START_ELEMENT has been written the prefix is bound in the root scope. * * @param prefix * @param uri * @throws XMLStreamException */ @Override public void setPrefix(String prefix, String uri) throws XMLStreamException { if (prefix == null) { throw new XMLStreamException("Prefix cannot be null"); } if (uri == null) { throw new XMLStreamException("URI cannot be null"); } prefix = fSymbolTable.addSymbol(prefix); uri = fSymbolTable.addSymbol(uri); if (fIsRepairingNamespace) { String tmpURI = fInternalNamespaceContext.getURI(prefix); if ((tmpURI != null) && (tmpURI == uri)) { return; } if(checkUserNamespaceContext(prefix,uri)) return; QName qname = new QName(); qname.setValues(prefix,XMLConstants.XMLNS_ATTRIBUTE, null,uri); fNamespaceDecls.add(qname); return; } fInternalNamespaceContext.declarePrefix(prefix, uri); } @Override public void writeAttribute(String localName, String value) throws XMLStreamException { try { if (!fStartTagOpened) { throw new XMLStreamException( "Attribute not associated with any element"); } if (fIsRepairingNamespace) { Attribute attr = new Attribute(value); // Revisit:Dont create new one's. Reuse.-Venu attr.setValues(null, localName, null, null); fAttributeCache.add(attr); return; } fWriter.write(" "); fWriter.write(localName); fWriter.write("=\""); writeXMLContent( value, true, // true = escapeChars true); // true = escapeDoubleQuotes fWriter.write("\""); } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { try { if (!fStartTagOpened) { throw new XMLStreamException( "Attribute not associated with any element"); } if (namespaceURI == null) { throw new XMLStreamException("NamespaceURI cannot be null"); } namespaceURI = fSymbolTable.addSymbol(namespaceURI); String prefix = fInternalNamespaceContext.getPrefix(namespaceURI); if (!fIsRepairingNamespace) { if (prefix == null) { throw new XMLStreamException("Prefix cannot be null"); } writeAttributeWithPrefix(prefix, localName, value); } else { Attribute attr = new Attribute(value); attr.setValues(null, localName, null, namespaceURI); fAttributeCache.add(attr); } } catch (IOException e) { throw new XMLStreamException(e); } } private void writeAttributeWithPrefix(String prefix, String localName, String value) throws IOException { fWriter.write(SPACE); if ((prefix != null) && (!prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))) { fWriter.write(prefix); fWriter.write(":"); } fWriter.write(localName); fWriter.write("=\""); writeXMLContent(value, true, // true = escapeChars true); // true = escapeDoubleQuotes fWriter.write("\""); } @Override public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException { try { if (!fStartTagOpened) { throw new XMLStreamException( "Attribute not associated with any element"); } if (namespaceURI == null) { throw new XMLStreamException("NamespaceURI cannot be null"); } if (localName == null) { throw new XMLStreamException("Local name cannot be null"); } if (!fIsRepairingNamespace) { if (prefix == null || prefix.isEmpty()){ if (!namespaceURI.isEmpty()) { throw new XMLStreamException("prefix cannot be null or empty"); } else { writeAttributeWithPrefix(null, localName, value); return; } } if (!prefix.equals(XMLConstants.XML_NS_PREFIX) || !namespaceURI.equals(XMLConstants.XML_NS_URI)) { prefix = fSymbolTable.addSymbol(prefix); namespaceURI = fSymbolTable.addSymbol(namespaceURI); if (fInternalNamespaceContext.containsPrefixInCurrentContext(prefix)){ String tmpURI = fInternalNamespaceContext.getURI(prefix); if (tmpURI != null && tmpURI != namespaceURI){ throw new XMLStreamException("Prefix "+prefix+" is " + "already bound to "+tmpURI+ ". Trying to rebind it to "+namespaceURI+" is an error."); } } fInternalNamespaceContext.declarePrefix(prefix, namespaceURI); } writeAttributeWithPrefix(prefix, localName, value); } else { if (prefix != null) { prefix = fSymbolTable.addSymbol(prefix); } namespaceURI = fSymbolTable.addSymbol(namespaceURI); Attribute attr = new Attribute(value); attr.setValues(prefix, localName, null, namespaceURI); fAttributeCache.add(attr); } } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeCData(String cdata) throws XMLStreamException { try { if (cdata == null) { throw new XMLStreamException("cdata cannot be null"); } if (fStartTagOpened) { closeStartTag(); } fWriter.write(START_CDATA); fWriter.write(cdata); fWriter.write(END_CDATA); } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeCharacters(String data) throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } writeXMLContent(data); } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeCharacters(char[] data, int start, int len) throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } writeXMLContent(data, start, len, fEscapeCharacters); } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeComment(String comment) throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } fWriter.write(START_COMMENT); if (comment != null) { fWriter.write(comment); } fWriter.write(END_COMMENT); } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeDTD(String dtd) throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } fWriter.write(dtd); } catch (IOException e) { throw new XMLStreamException(e); } } /* * Write default Namespace. * * If namespaceURI == null, * then it is assumed to be equivilent to {@link XMLConstants.NULL_NS_URI}, * i.e. there is no Namespace. * * @param namespaceURI NamespaceURI to declare. * * @throws XMLStreamException * * @see * Namespaces in XML, 5.2 Namespace Defaulting */ @Override public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { // normalize namespaceURI String namespaceURINormalized; if (namespaceURI == null) { namespaceURINormalized = ""; // XMLConstants.NULL_NS_URI } else { namespaceURINormalized = namespaceURI; } try { if (!fStartTagOpened) { throw new IllegalStateException( "Namespace Attribute not associated with any element"); } if (fIsRepairingNamespace) { QName qname = new QName(); qname.setValues(XMLConstants.DEFAULT_NS_PREFIX, XMLConstants.XMLNS_ATTRIBUTE, null, namespaceURINormalized); fNamespaceDecls.add(qname); return; } namespaceURINormalized = fSymbolTable.addSymbol(namespaceURINormalized); if (fInternalNamespaceContext.containsPrefixInCurrentContext("")){ String tmp = fInternalNamespaceContext.getURI(""); if (tmp != null && !tmp.equals(namespaceURINormalized)) { throw new XMLStreamException( "xmlns has been already bound to " +tmp + ". Rebinding it to "+ namespaceURINormalized + " is an error"); } } fInternalNamespaceContext.declarePrefix("", namespaceURINormalized); // use common namespace code with a prefix == null for xmlns="..." writenamespace(null, namespaceURINormalized); } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeEmptyElement(String localName) throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } openStartTag(); fElementStack.push(null, localName, null, null, true); fInternalNamespaceContext.pushContext(); if (!fIsRepairingNamespace) { fWriter.write(localName); } } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { if (namespaceURI == null) { throw new XMLStreamException("NamespaceURI cannot be null"); } namespaceURI = fSymbolTable.addSymbol(namespaceURI); String prefix = fNamespaceContext.getPrefix(namespaceURI); writeEmptyElement(prefix, localName, namespaceURI); } @Override public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { try { if (localName == null) { throw new XMLStreamException("Local Name cannot be null"); } if (namespaceURI == null) { throw new XMLStreamException("NamespaceURI cannot be null"); } if (prefix != null) { prefix = fSymbolTable.addSymbol(prefix); } namespaceURI = fSymbolTable.addSymbol(namespaceURI); if (fStartTagOpened) { closeStartTag(); } openStartTag(); fElementStack.push(prefix, localName, null, namespaceURI, true); fInternalNamespaceContext.pushContext(); if (!fIsRepairingNamespace) { if (prefix == null) { throw new XMLStreamException("NamespaceURI " + namespaceURI + " has not been bound to any prefix"); } } else { return; } if ((prefix != null) && (!prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))) { fWriter.write(prefix); fWriter.write(":"); } fWriter.write(localName); } catch (IOException e) { throw new XMLStreamException(e); } } @Override public void writeEndDocument() throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } while (!fElementStack.empty()) { ElementState elem = fElementStack.pop(); fInternalNamespaceContext.popContext(); if (elem.isEmpty) { //fWriter.write(CLOSE_EMPTY_ELEMENT); } else { fWriter.write(OPEN_END_TAG); if ((elem.prefix != null) && !(elem.prefix).isEmpty()) { fWriter.write(elem.prefix); fWriter.write(":"); } fWriter.write(elem.localpart); fWriter.write(CLOSE_END_TAG); } } } catch (IOException e) { throw new XMLStreamException(e); } catch (ArrayIndexOutOfBoundsException e) { throw new XMLStreamException("No more elements to write"); } } @Override public void writeEndElement() throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } ElementState currentElement = fElementStack.pop(); if (currentElement == null) { throw new XMLStreamException("No element was found to write"); } if (currentElement.isEmpty) { //fWriter.write(CLOSE_EMPTY_ELEMENT); return; } fWriter.write(OPEN_END_TAG); if ((currentElement.prefix != null) && !(currentElement.prefix).isEmpty()) { fWriter.write(currentElement.prefix); fWriter.write(":"); } fWriter.write(currentElement.localpart); fWriter.write(CLOSE_END_TAG); fInternalNamespaceContext.popContext(); } catch (IOException e) { throw new XMLStreamException(e); } catch (ArrayIndexOutOfBoundsException e) { throw new XMLStreamException( "No element was found to write: " + e.toString(), e); } } @Override public void writeEntityRef(String refName) throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } fWriter.write('&'); fWriter.write(refName); fWriter.write(';'); } catch (IOException e) { throw new XMLStreamException(e); } } /** * Write a Namespace declaration. * * If namespaceURI == null, * then it is assumed to be equivilent to {@link XMLConstants.NULL_NS_URI}, * i.e. there is no Namespace. * * @param prefix Prefix to bind. * @param namespaceURI NamespaceURI to declare. * * @throws XMLStreamException * * @see * Namespaces in XML, 5.2 Namespace Defaulting */ @Override public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { // normalize namespaceURI String namespaceURINormalized; if (namespaceURI == null) { namespaceURINormalized = ""; // XMLConstants.NULL_NS_URI } else { namespaceURINormalized = namespaceURI; } try { QName qname; if (!fStartTagOpened) { throw new IllegalStateException( "Invalid state: start tag is not opened at writeNamespace(" + prefix + ", " + namespaceURINormalized + ")"); } // is this the default Namespace? if (prefix == null || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX) || prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) { writeDefaultNamespace(namespaceURINormalized); return; } if (prefix.equals(XMLConstants.XML_NS_PREFIX) && namespaceURINormalized.equals(XMLConstants.XML_NS_URI)) return; prefix = fSymbolTable.addSymbol(prefix); namespaceURINormalized = fSymbolTable.addSymbol(namespaceURINormalized); if (fIsRepairingNamespace) { String tmpURI = fInternalNamespaceContext.getURI(prefix); if ((tmpURI != null) && (tmpURI.equals(namespaceURINormalized))) { return; } qname = new QName(); qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, namespaceURINormalized); fNamespaceDecls.add(qname); return; } if (fInternalNamespaceContext.containsPrefixInCurrentContext(prefix)){ String tmp = fInternalNamespaceContext.getURI(prefix); if (tmp != null && !tmp.equals(namespaceURINormalized)) { throw new XMLStreamException("prefix "+prefix+ " has been already bound to " +tmp + ". Rebinding it to "+ namespaceURINormalized+ " is an error"); } } fInternalNamespaceContext.declarePrefix(prefix, namespaceURINormalized); writenamespace(prefix, namespaceURINormalized); } catch (IOException e) { throw new XMLStreamException(e); } } private void writenamespace(String prefix, String namespaceURI) throws IOException { fWriter.write(" xmlns"); if ((prefix != null) && (!prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))) { fWriter.write(":"); fWriter.write(prefix); } fWriter.write("=\""); writeXMLContent( namespaceURI, true, // true = escapeChars true); // true = escapeDoubleQuotes fWriter.write("\""); } @Override public void writeProcessingInstruction(String target) throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } if (target != null) { fWriter.write(""); return; } } catch (IOException e) { throw new XMLStreamException(e); } throw new XMLStreamException("PI target cannot be null"); } /** * @param target * @param data * @throws XMLStreamException */ @Override public void writeProcessingInstruction(String target, String data) throws XMLStreamException { try { if (fStartTagOpened) { closeStartTag(); } if ((target == null) || (data == null)) { throw new XMLStreamException("PI target cannot be null"); } fWriter.write(""); } catch (IOException e) { throw new XMLStreamException(e); } } /** * Writes the XML declaration. * * @throws XMLStreamException in case of an IOException */ @Override public void writeStartDocument() throws XMLStreamException { writeStartDocument(null, null, false, false); } /** * Writes the XML declaration. * * @param version the specified version * @throws XMLStreamException in case of an IOException */ @Override public void writeStartDocument(String version) throws XMLStreamException { writeStartDocument(null, version, false, false); } /** * Writes the XML declaration. * * @param encoding the specified encoding * @param version the specified version * @throws XMLStreamException in case of an IOException */ @Override public void writeStartDocument(String encoding, String version) throws XMLStreamException { writeStartDocument(encoding, version, false, false); } public void writeStartDocument(String encoding, String version, boolean standalone, boolean standaloneSet) throws XMLStreamException { try { if ((encoding == null || encoding.length() == 0) && (version == null || version.length() == 0) && (!standaloneSet)) { fWriter.write(DEFAULT_XMLDECL); return; } // Verify the encoding before writing anything if (encoding != null && !encoding.isEmpty()) { verifyEncoding(encoding); } fWriter.write(""); } catch (IOException ex) { throw new XMLStreamException(ex); } } /** * Verifies that the encoding is consistent between the underlying encoding * and that specified. * * @param encoding the specified encoding * @throws XMLStreamException if they do not match */ private void verifyEncoding(String encoding) throws XMLStreamException { String streamEncoding = null; if (fWriter instanceof OutputStreamWriter) { streamEncoding = ((OutputStreamWriter) fWriter).getEncoding(); } else if (fWriter instanceof UTF8OutputStreamWriter) { streamEncoding = ((UTF8OutputStreamWriter) fWriter).getEncoding(); } else if (fWriter instanceof XMLWriter) { streamEncoding = ((OutputStreamWriter) ((XMLWriter)fWriter).getWriter()).getEncoding(); } if (streamEncoding != null && !streamEncoding.equalsIgnoreCase(encoding)) { // If the equality check failed, check for charset encoding aliases boolean foundAlias = false; Set aliases = Charset.forName(encoding).aliases(); for (Iterator it = aliases.iterator(); !foundAlias && it.hasNext(); ) { if (streamEncoding.equalsIgnoreCase(it.next())) { foundAlias = true; } } // If no alias matches the encoding name, then report error if (!foundAlias) { throw new XMLStreamException("Underlying stream encoding '" + streamEncoding + "' and input paramter for writeStartDocument() method '" + encoding + "' do not match."); } } } /** * @param localName * @throws XMLStreamException */ @Override public void writeStartElement(String localName) throws XMLStreamException { try { if (localName == null) { throw new XMLStreamException("Local Name cannot be null"); } if (fStartTagOpened) { closeStartTag(); } openStartTag(); fElementStack.push(null, localName, null, null, false); fInternalNamespaceContext.pushContext(); if (fIsRepairingNamespace) { return; } fWriter.write(localName); } catch (IOException ex) { throw new XMLStreamException(ex); } } /** * @param namespaceURI * @param localName * @throws XMLStreamException */ @Override public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { if (localName == null) { throw new XMLStreamException("Local Name cannot be null"); } if (namespaceURI == null) { throw new XMLStreamException("NamespaceURI cannot be null"); } namespaceURI = fSymbolTable.addSymbol(namespaceURI); String prefix = null; if (!fIsRepairingNamespace) { prefix = fNamespaceContext.getPrefix(namespaceURI); if (prefix != null) { prefix = fSymbolTable.addSymbol(prefix); } } writeStartElement(prefix, localName, namespaceURI); } /** * @param prefix * @param localName * @param namespaceURI * @throws XMLStreamException */ @Override public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { try { if (localName == null) { throw new XMLStreamException("Local Name cannot be null"); } if (namespaceURI == null) { throw new XMLStreamException("NamespaceURI cannot be null"); } if (!fIsRepairingNamespace) { if (prefix == null) { throw new XMLStreamException("Prefix cannot be null"); } } if (fStartTagOpened) { closeStartTag(); } openStartTag(); namespaceURI = fSymbolTable.addSymbol(namespaceURI); if (prefix != null) { prefix = fSymbolTable.addSymbol(prefix); } fElementStack.push(prefix, localName, null, namespaceURI, false); fInternalNamespaceContext.pushContext(); String tmpPrefix = fNamespaceContext.getPrefix(namespaceURI); if ((prefix != null) && ((tmpPrefix == null) || !prefix.equals(tmpPrefix))) { fInternalNamespaceContext.declarePrefix(prefix, namespaceURI); } if (fIsRepairingNamespace) { if ((prefix == null) || ((tmpPrefix != null) && prefix.equals(tmpPrefix))) { return; } QName qname = new QName(); qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, namespaceURI); fNamespaceDecls.add(qname); return; } if ((prefix != null) && (prefix != XMLConstants.DEFAULT_NS_PREFIX)) { fWriter.write(prefix); fWriter.write(":"); } fWriter.write(localName); } catch (IOException ex) { throw new XMLStreamException(ex); } } /** * Writes character reference in hex format. */ private void writeCharRef(int codePoint) throws IOException { fWriter.write( "&#x" ); fWriter.write( Integer.toHexString(codePoint) ); fWriter.write( ';' ); } /** * Writes XML content to underlying writer. Escapes characters unless * escaping character feature is turned off. */ private void writeXMLContent(char[] content, int start, int length, boolean escapeChars) throws IOException { if (!escapeChars) { fWriter.write(content, start, length); return; } // Index of the next char to be written int startWritePos = start; final int end = start + length; for (int index = start; index < end; index++) { char ch = content[index]; if (fEncoder != null && !fEncoder.canEncode(ch)){ fWriter.write(content, startWritePos, index - startWritePos ); // Check if current and next characters forms a surrogate pair // and escape it to avoid generation of invalid xml content if ( index != end - 1 && Character.isSurrogatePair(ch, content[index+1])) { writeCharRef(Character.toCodePoint(ch, content[index+1])); index++; } else { writeCharRef(ch); } startWritePos = index + 1; continue; } switch (ch) { case '<': fWriter.write(content, startWritePos, index - startWritePos); fWriter.write("<"); startWritePos = index + 1; break; case '&': fWriter.write(content, startWritePos, index - startWritePos); fWriter.write("&"); startWritePos = index + 1; break; case '>': fWriter.write(content, startWritePos, index - startWritePos); fWriter.write(">"); startWritePos = index + 1; break; } } // Write any pending data fWriter.write(content, startWritePos, end - startWritePos); } private void writeXMLContent(String content) throws IOException { if ((content != null) && (content.length() > 0)) { writeXMLContent(content, fEscapeCharacters, // boolean = escapeChars false); // false = escapeDoubleQuotes } } /** * Writes XML content to underlying writer. Escapes characters unless * escaping character feature is turned off. */ private void writeXMLContent( String content, boolean escapeChars, boolean escapeDoubleQuotes) throws IOException { if (!escapeChars) { fWriter.write(content); return; } // Index of the next char to be written int startWritePos = 0; final int end = content.length(); for (int index = 0; index < end; index++) { char ch = content.charAt(index); if (fEncoder != null && !fEncoder.canEncode(ch)){ fWriter.write(content, startWritePos, index - startWritePos ); // Check if current and next characters forms a surrogate pair // and escape it to avoid generation of invalid xml content if ( index != end - 1 && Character.isSurrogatePair(ch, content.charAt(index+1))) { writeCharRef(Character.toCodePoint(ch, content.charAt(index+1))); index++; } else { writeCharRef(ch); } startWritePos = index + 1; continue; } switch (ch) { case '<': fWriter.write(content, startWritePos, index - startWritePos); fWriter.write("<"); startWritePos = index + 1; break; case '&': fWriter.write(content, startWritePos, index - startWritePos); fWriter.write("&"); startWritePos = index + 1; break; case '>': fWriter.write(content, startWritePos, index - startWritePos); fWriter.write(">"); startWritePos = index + 1; break; case '"': fWriter.write(content, startWritePos, index - startWritePos); if (escapeDoubleQuotes) { fWriter.write("""); } else { fWriter.write('"'); } startWritePos = index + 1; break; } } // Write any pending data fWriter.write(content, startWritePos, end - startWritePos); } /** * marks close of start tag and writes the same into the writer. */ private void closeStartTag() throws XMLStreamException { try { ElementState currentElement = fElementStack.peek(); if (fIsRepairingNamespace) { repair(); correctPrefix(currentElement, XMLStreamConstants.START_ELEMENT); if ((currentElement.prefix != null) && (currentElement.prefix != XMLConstants.DEFAULT_NS_PREFIX)) { fWriter.write(currentElement.prefix); fWriter.write(":"); } fWriter.write(currentElement.localpart); int len = fNamespaceDecls.size(); QName qname; for (int i = 0; i < len; i++) { qname = fNamespaceDecls.get(i); if (qname != null) { if (fInternalNamespaceContext.declarePrefix(qname.prefix, qname.uri)) { writenamespace(qname.prefix, qname.uri); } } } fNamespaceDecls.clear(); Attribute attr; for (int j = 0; j < fAttributeCache.size(); j++) { attr = fAttributeCache.get(j); if ((attr.prefix != null) && (attr.uri != null)) { if (!attr.prefix.isEmpty() && !attr.uri.isEmpty() ) { String tmp = fInternalNamespaceContext.getPrefix(attr.uri); if ((tmp == null) || (!tmp.equals(attr.prefix))) { tmp = getAttrPrefix(attr.uri); if (tmp == null) { if (fInternalNamespaceContext.declarePrefix(attr.prefix, attr.uri)) { writenamespace(attr.prefix, attr.uri); } } else { writenamespace(attr.prefix, attr.uri); } } } } writeAttributeWithPrefix(attr.prefix, attr.localpart, attr.value); } fAttrNamespace = null; fAttributeCache.clear(); } if (currentElement.isEmpty) { fElementStack.pop(); fInternalNamespaceContext.popContext(); fWriter.write(CLOSE_EMPTY_ELEMENT); } else { fWriter.write(CLOSE_START_TAG); } fStartTagOpened = false; } catch (IOException ex) { fStartTagOpened = false; throw new XMLStreamException(ex); } } /** * marks open of start tag and writes the same into the writer. */ private void openStartTag() throws IOException { fStartTagOpened = true; fWriter.write(OPEN_START_TAG); } /** * * @param uri * @return */ private void correctPrefix(QName attr, int type) { String tmpPrefix; String prefix; String uri; prefix = attr.prefix; uri = attr.uri; boolean isSpecialCaseURI = false; if (prefix == null || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) { if (uri == null) { return; } if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix) && uri.equals(XMLConstants.DEFAULT_NS_PREFIX)) return; uri = fSymbolTable.addSymbol(uri); QName decl; for (int i = 0; i < fNamespaceDecls.size(); i++) { decl = fNamespaceDecls.get(i); if ((decl != null) && (decl.uri.equals(attr.uri))) { attr.prefix = decl.prefix; return; } } tmpPrefix = fNamespaceContext.getPrefix(uri); if (XMLConstants.DEFAULT_NS_PREFIX.equals(tmpPrefix)) { if (type == XMLStreamConstants.START_ELEMENT) { return; } else if (type == XMLStreamConstants.ATTRIBUTE) { //the uri happens to be the same as that of the default namespace tmpPrefix = getAttrPrefix(uri); isSpecialCaseURI = true; } } if (tmpPrefix == null) { StringBuilder genPrefix = new StringBuilder("zdef"); for (int i = 0; i < 1; i++) { genPrefix.append(fPrefixGen.nextInt()); } prefix = genPrefix.toString(); prefix = fSymbolTable.addSymbol(prefix); } else { prefix = fSymbolTable.addSymbol(tmpPrefix); } if (tmpPrefix == null) { if (isSpecialCaseURI) { addAttrNamespace(prefix, uri); } else { QName qname = new QName(); qname.setValues(prefix, XMLConstants.XMLNS_ATTRIBUTE, null, uri); fNamespaceDecls.add(qname); fInternalNamespaceContext.declarePrefix(fSymbolTable.addSymbol( prefix), uri); } } } attr.prefix = prefix; } /** * return the prefix if the attribute has an uri the same as that of the default namespace */ private String getAttrPrefix(String uri) { if (fAttrNamespace != null) { return fAttrNamespace.get(uri); } return null; } private void addAttrNamespace(String prefix, String uri) { if (fAttrNamespace == null) { fAttrNamespace = new HashMap<>(); } fAttrNamespace.put(prefix, uri); } /** * @param uri * @return */ private boolean isDefaultNamespace(String uri) { String defaultNamespace = fInternalNamespaceContext.getURI(DEFAULT_PREFIX); return uri != null && uri.equals(defaultNamespace); } /** * @param prefix * @param uri * @return */ private boolean checkUserNamespaceContext(String prefix, String uri) { if (fNamespaceContext.userContext != null) { String tmpURI = fNamespaceContext.userContext.getNamespaceURI(prefix); if ((tmpURI != null) && tmpURI.equals(uri)) { return true; } } return false; } /** * Correct's namespaces as per requirements of isReparisingNamespace property. */ protected void repair() { Attribute attr; Attribute attr2; ElementState currentElement = fElementStack.peek(); removeDuplicateDecls(); for(int i=0 ; i< fAttributeCache.size();i++){ attr = fAttributeCache.get(i); if((attr.prefix != null && !attr.prefix.isEmpty()) || (attr.uri != null && !attr.uri.isEmpty())) { correctPrefix(currentElement,attr); } } if (!isDeclared(currentElement)) { if ((currentElement.prefix != null) && (currentElement.uri != null)) { if ((!currentElement.prefix.isEmpty()) && (!currentElement.uri.isEmpty())) { fNamespaceDecls.add(currentElement); } } } for(int i=0 ; i< fAttributeCache.size();i++){ attr = fAttributeCache.get(i); for(int j=i+1;j * Note: The QName values are copied into the * stack. In other words, the caller does not orphan * the element to the stack. Also, the QName object returned * is not orphaned to the caller. It should be * considered read-only. * * @param element The element to push onto the stack. * * @return Returns the actual QName object that stores the */ public ElementState push(ElementState element) { if (fDepth == fElements.length) { ElementState[] array = new ElementState[fElements.length * 2]; System.arraycopy(fElements, 0, array, 0, fDepth); fElements = array; for (int i = fDepth; i < fElements.length; i++) { fElements[i] = new ElementState(); } } fElements[fDepth].setValues(element); return fElements[fDepth++]; } /** * * @param prefix * @param localpart * @param rawname * @param uri * @param isEmpty * @return */ public ElementState push(String prefix, String localpart, String rawname, String uri, boolean isEmpty) { if (fDepth == fElements.length) { ElementState[] array = new ElementState[fElements.length * 2]; System.arraycopy(fElements, 0, array, 0, fDepth); fElements = array; for (int i = fDepth; i < fElements.length; i++) { fElements[i] = new ElementState(); } } fElements[fDepth].setValues(prefix, localpart, rawname, uri, isEmpty); return fElements[fDepth++]; } /** * Pops an element off of the stack by setting the values of * the specified QName. *

* Note: The object returned is not * orphaned to the caller. Therefore, the caller should consider * the object to be read-only. */ public ElementState pop() { return fElements[--fDepth]; } /** Clears the stack without throwing away existing QName objects. */ public void clear() { fDepth = 0; } /** * This function is as a result of optimization done for endElement -- * we dont need to set the value for every end element we encouter. * For Well formedness checks we can have the same QName object that was pushed. * the values will be set only if application need to know about the endElement */ public ElementState peek() { return fElements[fDepth - 1]; } /** * * @return */ public boolean empty() { return (fDepth > 0) ? false : true; } } /** * Maintains element state . localName for now. */ class ElementState extends QName { public boolean isEmpty = false; public ElementState() {} public ElementState(String prefix, String localpart, String rawname, String uri) { super(prefix, localpart, rawname, uri); } public void setValues(String prefix, String localpart, String rawname, String uri, boolean isEmpty) { super.setValues(prefix, localpart, rawname, uri); this.isEmpty = isEmpty; } } /** * Attributes */ class Attribute extends QName { String value; Attribute(String value) { super(); this.value = value; } } /** * Implementation of NamespaceContext . * */ class NamespaceContextImpl implements NamespaceContext { //root namespace context set by user. NamespaceContext userContext = null; //context built by the writer. NamespaceSupport internalContext = null; public String getNamespaceURI(String prefix) { String uri = null; if (prefix != null) { prefix = fSymbolTable.addSymbol(prefix); } if (internalContext != null) { uri = internalContext.getURI(prefix); if (uri != null) { return uri; } } if (userContext != null) { uri = userContext.getNamespaceURI(prefix); return uri; } return null; } public String getPrefix(String uri) { String prefix = null; if (uri != null) { uri = fSymbolTable.addSymbol(uri); } if (internalContext != null) { prefix = internalContext.getPrefix(uri); if (prefix != null) { return prefix; } } if (userContext != null) { return userContext.getPrefix(uri); } return null; } //Cleanup note: leaving these warnings to a xerces.internal.util cleanup public Iterator getPrefixes(String uri) { List prefixes = null; Iterator itr = null; if (uri != null) { uri = fSymbolTable.addSymbol(uri); } if (userContext != null) { itr = userContext.getPrefixes(uri); } if (internalContext != null) { prefixes = internalContext.getPrefixes(uri); } if ((prefixes == null) && (itr != null)) { return itr; } else if ((prefixes != null) && (itr == null)) { return new ReadOnlyIterator<>(prefixes.iterator()); } else if ((prefixes != null) && (itr != null)) { String ob = null; while (itr.hasNext()) { ob = itr.next(); if (ob != null) { ob = fSymbolTable.addSymbol(ob); } if (!prefixes.contains(ob)) { prefixes.add(ob); } } return new ReadOnlyIterator<>(prefixes.iterator()); } return fReadOnlyIterator; } } // -- Map Interface -------------------------------------------------- @Override public int size() { return 1; } @Override public boolean isEmpty() { return false; } @Override public boolean containsKey(Object key) { return key.equals(OUTPUTSTREAM_PROPERTY); } /** * Returns the value associated to an implementation-specific * property. */ @Override public Object get(Object key) { if (key.equals(OUTPUTSTREAM_PROPERTY)) { return fOutputStream; } return null; } @Override public Set> entrySet() { throw new UnsupportedOperationException(); } /** * Overrides the method defined in AbstractMap which is * not completely implemented. Calling toString() in * AbstractMap would cause an unsupported exection to * be thrown. */ @Override public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } /** * Overrides the method defined in AbstractMap * This is required by the toString() method */ @Override public int hashCode() { return fElementStack.hashCode(); } /** * Overrides the method defined in AbstractMap * This is required to satisfy the contract for hashCode. */ @Override public boolean equals(Object obj) { return (this == obj); } }