/* * Copyright (c) 2012, 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 jdk.internal.util.xml.impl; import java.io.OutputStream; import java.io.UnsupportedEncodingException; /** * Implementation of a reduced version of XMLStreamWriter * * @author Joe Wang */ public class XMLStreamWriterImpl implements XMLStreamWriter { //Document state static final int STATE_XML_DECL = 1; static final int STATE_PROLOG = 2; static final int STATE_DTD_DECL = 3; static final int STATE_ELEMENT = 4; //Element state static final int ELEMENT_STARTTAG_OPEN = 10; static final int ELEMENT_STARTTAG_CLOSE = 11; static final int ELEMENT_ENDTAG_OPEN = 12; static final int ELEMENT_ENDTAG_CLOSE = 13; 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 ENCODING_PREFIX = "&#x"; public static final char SPACE = ' '; public static final char AMPERSAND = '&'; public static final char DOUBLEQUOT = '"'; public static final char SEMICOLON = ';'; //current state private int _state = 0; private Element _currentEle; private XMLWriter _writer; private String _encoding; /** * This flag can be used to turn escaping off for content. It does * not apply to attribute content. */ boolean _escapeCharacters = true; //pretty print by default private boolean _doIndent = true; //The system line separator for writing out line breaks. private char[] _lineSep = System.getProperty("line.separator").toCharArray(); public XMLStreamWriterImpl(OutputStream os) throws XMLStreamException { this(os, XMLStreamWriter.DEFAULT_ENCODING); } public XMLStreamWriterImpl(OutputStream os, String encoding) throws XMLStreamException { if (encoding == null) { _encoding = XMLStreamWriter.DEFAULT_ENCODING; } else { if (!isEncodingSupported(encoding)) { throw new XMLStreamException( new UnsupportedEncodingException("The basic XMLWriter does " + "not support " + encoding)); } this._encoding = encoding; } _writer = new XMLWriter(os, encoding); } /** * Write the XML Declaration. Defaults the XML version to 1.0, and the * encoding to utf-8. * * @throws XMLStreamException */ public void writeStartDocument() throws XMLStreamException { writeStartDocument(_encoding, XMLStreamWriter.DEFAULT_XML_VERSION); } /** * Write the XML Declaration. Defaults the encoding to utf-8 * * @param version version of the xml document * @throws XMLStreamException */ public void writeStartDocument(String version) throws XMLStreamException { writeStartDocument(_encoding, version, null); } /** * Write the XML Declaration. Note that the encoding parameter does not set * the actual encoding of the underlying output. That must be set when the * instance of the XMLStreamWriter is created * * @param encoding encoding of the xml declaration * @param version version of the xml document * @throws XMLStreamException If given encoding does not match encoding of the * underlying stream */ public void writeStartDocument(String encoding, String version) throws XMLStreamException { writeStartDocument(encoding, version, null); } /** * Write the XML Declaration. Note that the encoding parameter does not set * the actual encoding of the underlying output. That must be set when the * instance of the XMLStreamWriter is created * * @param encoding encoding of the xml declaration * @param version version of the xml document * @param standalone indicate if the xml document is standalone * @throws XMLStreamException If given encoding does not match encoding of the * underlying stream */ public void writeStartDocument(String encoding, String version, String standalone) throws XMLStreamException { if (_state > 0) { throw new XMLStreamException("XML declaration must be as the first line in the XML document."); } _state = STATE_XML_DECL; String enc = encoding; if (enc == null) { enc = _encoding; } else { if (!isEncodingSupported(encoding)) { throw new XMLStreamException( new UnsupportedEncodingException("The basic XMLWriter does " + "not support " + encoding)); } } if (version == null) { version = XMLStreamWriter.DEFAULT_XML_VERSION; } _writer.write(""); writeLineSeparator(); } /** * Write a DTD section. This string represents the entire doctypedecl production * from the XML 1.0 specification. * * @param dtd the DTD to be written * @throws XMLStreamException */ public void writeDTD(String dtd) throws XMLStreamException { if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { closeStartTag(); } _writer.write(dtd); writeLineSeparator(); } /** * Writes a start tag to the output. * @param localName local name of the tag, may not be null * @throws XMLStreamException */ public void writeStartElement(String localName) throws XMLStreamException { if (localName == null || localName.length() == 0) { throw new XMLStreamException("Local Name cannot be null or empty"); } _state = STATE_ELEMENT; if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { closeStartTag(); } _currentEle = new Element(_currentEle, localName, false); openStartTag(); _writer.write(localName); } /** * Writes an empty element tag to the output * @param localName local name of the tag, may not be null * @throws XMLStreamException */ public void writeEmptyElement(String localName) throws XMLStreamException { if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { closeStartTag(); } _currentEle = new Element(_currentEle, localName, true); openStartTag(); _writer.write(localName); } /** * Writes an attribute to the output stream without a prefix. * @param localName the local name of the attribute * @param value the value of the attribute * @throws IllegalStateException if the current state does not allow Attribute writing * @throws XMLStreamException */ public void writeAttribute(String localName, String value) throws XMLStreamException { if (_currentEle.getState() != ELEMENT_STARTTAG_OPEN) { throw new XMLStreamException( "Attribute not associated with any element"); } _writer.write(SPACE); _writer.write(localName); _writer.write("=\""); writeXMLContent( value, true, // true = escapeChars true); // true = escapeDoubleQuotes _writer.write(DOUBLEQUOT); } public void writeEndDocument() throws XMLStreamException { if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { closeStartTag(); } /** * close unclosed elements if any */ while (_currentEle != null) { if (!_currentEle.isEmpty()) { _writer.write(OPEN_END_TAG); _writer.write(_currentEle.getLocalName()); _writer.write(CLOSE_END_TAG); } _currentEle = _currentEle.getParent(); } } public void writeEndElement() throws XMLStreamException { if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { closeStartTag(); } if (_currentEle == null) { throw new XMLStreamException("No element was found to write"); } if (_currentEle.isEmpty()) { return; } _writer.write(OPEN_END_TAG); _writer.write(_currentEle.getLocalName()); _writer.write(CLOSE_END_TAG); writeLineSeparator(); _currentEle = _currentEle.getParent(); } public void writeCData(String cdata) throws XMLStreamException { if (cdata == null) { throw new XMLStreamException("cdata cannot be null"); } if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { closeStartTag(); } _writer.write(START_CDATA); _writer.write(cdata); _writer.write(END_CDATA); } public void writeCharacters(String data) throws XMLStreamException { if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { closeStartTag(); } writeXMLContent(data); } public void writeCharacters(char[] data, int start, int len) throws XMLStreamException { if (_currentEle != null && _currentEle.getState() == ELEMENT_STARTTAG_OPEN) { closeStartTag(); } writeXMLContent(data, start, len, _escapeCharacters); } /** * Close this XMLStreamWriter by closing underlying writer. */ public void close() throws XMLStreamException { if (_writer != null) { _writer.close(); } _writer = null; _currentEle = null; _state = 0; } /** * Flush this XMLStreamWriter by flushing underlying writer. */ public void flush() throws XMLStreamException { if (_writer != null) { _writer.flush(); } } /** * Set the flag to indicate if the writer should add line separator * @param doIndent */ public void setDoIndent(boolean doIndent) { _doIndent = doIndent; } /** * 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 XMLStreamException { if (!escapeChars) { _writer.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 (!_writer.canEncode(ch)){ _writer.write(content, startWritePos, index - startWritePos ); // Escape this char as underlying encoder cannot handle it _writer.write( ENCODING_PREFIX ); _writer.write(Integer.toHexString(ch)); _writer.write( SEMICOLON ); startWritePos = index + 1; continue; } switch (ch) { case OPEN_START_TAG: _writer.write(content, startWritePos, index - startWritePos); _writer.write("<"); startWritePos = index + 1; break; case AMPERSAND: _writer.write(content, startWritePos, index - startWritePos); _writer.write("&"); startWritePos = index + 1; break; case CLOSE_START_TAG: _writer.write(content, startWritePos, index - startWritePos); _writer.write(">"); startWritePos = index + 1; break; } } // Write any pending data _writer.write(content, startWritePos, end - startWritePos); } private void writeXMLContent(String content) throws XMLStreamException { if ((content != null) && (content.length() > 0)) { writeXMLContent(content, _escapeCharacters, // 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 XMLStreamException { if (!escapeChars) { _writer.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 (!_writer.canEncode(ch)){ _writer.write(content, startWritePos, index - startWritePos ); // Escape this char as underlying encoder cannot handle it _writer.write( ENCODING_PREFIX ); _writer.write(Integer.toHexString(ch)); _writer.write( SEMICOLON ); startWritePos = index + 1; continue; } switch (ch) { case OPEN_START_TAG: _writer.write(content, startWritePos, index - startWritePos); _writer.write("<"); startWritePos = index + 1; break; case AMPERSAND: _writer.write(content, startWritePos, index - startWritePos); _writer.write("&"); startWritePos = index + 1; break; case CLOSE_START_TAG: _writer.write(content, startWritePos, index - startWritePos); _writer.write(">"); startWritePos = index + 1; break; case DOUBLEQUOT: _writer.write(content, startWritePos, index - startWritePos); if (escapeDoubleQuotes) { _writer.write("""); } else { _writer.write(DOUBLEQUOT); } startWritePos = index + 1; break; } } // Write any pending data _writer.write(content, startWritePos, end - startWritePos); } /** * marks open of start tag and writes the same into the writer. */ private void openStartTag() throws XMLStreamException { _currentEle.setState(ELEMENT_STARTTAG_OPEN); _writer.write(OPEN_START_TAG); } /** * marks close of start tag and writes the same into the writer. */ private void closeStartTag() throws XMLStreamException { if (_currentEle.isEmpty()) { _writer.write(CLOSE_EMPTY_ELEMENT); } else { _writer.write(CLOSE_START_TAG); } if (_currentEle.getParent() == null) { writeLineSeparator(); } _currentEle.setState(ELEMENT_STARTTAG_CLOSE); } /** * Write a line separator * @throws XMLStreamException */ private void writeLineSeparator() throws XMLStreamException { if (_doIndent) { _writer.write(_lineSep, 0, _lineSep.length); } } private boolean isEncodingSupported(String encoding) { if (encoding.equalsIgnoreCase("UTF-32")) { return false; } return true; } /* * Start of Internal classes. * */ protected class Element { /** * the parent element */ protected Element _parent; /** * The size of the stack. */ protected short _Depth; /** * indicate if an element is an empty one */ boolean _isEmptyElement = false; String _localpart; int _state; /** * Default constructor. */ public Element() { } /** * @param parent the parent of the element * @param localpart name of the element * @param isEmpty indicate if the element is an empty one */ public Element(Element parent, String localpart, boolean isEmpty) { _parent = parent; _localpart = localpart; _isEmptyElement = isEmpty; } public Element getParent() { return _parent; } public String getLocalName() { return _localpart; } /** * get the state of the element */ public int getState() { return _state; } /** * Set the state of the element * * @param state the state of the element */ public void setState(int state) { _state = state; } public boolean isEmpty() { return _isEmptyElement; } } }