634 lines
19 KiB
Java
634 lines
19 KiB
Java
/*
|
|
* 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;
|
|
import java.nio.charset.Charset;
|
|
import java.nio.charset.IllegalCharsetNameException;
|
|
import java.nio.charset.UnsupportedCharsetException;
|
|
import jdk.internal.util.xml.XMLStreamException;
|
|
import jdk.internal.util.xml.XMLStreamWriter;
|
|
|
|
/**
|
|
* 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 char CLOSE_END_TAG = '>';
|
|
public static final String START_CDATA = "<![CDATA[";
|
|
public static final String END_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
|
|
{
|
|
Charset cs = null;
|
|
if (encoding == null) {
|
|
_encoding = XMLStreamWriter.DEFAULT_ENCODING;
|
|
} else {
|
|
try {
|
|
cs = getCharset(encoding);
|
|
} catch (UnsupportedEncodingException e) {
|
|
throw new XMLStreamException(e);
|
|
}
|
|
|
|
this._encoding = encoding;
|
|
}
|
|
|
|
_writer = new XMLWriter(os, encoding, cs);
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
//check if the encoding is supported
|
|
try {
|
|
getCharset(encoding);
|
|
} catch (UnsupportedEncodingException e) {
|
|
throw new XMLStreamException(e);
|
|
}
|
|
}
|
|
|
|
if (version == null) {
|
|
version = XMLStreamWriter.DEFAULT_XML_VERSION;
|
|
}
|
|
|
|
_writer.write("<?xml version=\"");
|
|
_writer.write(version);
|
|
_writer.write(DOUBLEQUOT);
|
|
|
|
if (enc != null) {
|
|
_writer.write(" encoding=\"");
|
|
_writer.write(enc);
|
|
_writer.write(DOUBLEQUOT);
|
|
}
|
|
|
|
if (standalone != null) {
|
|
_writer.write(" standalone=\"");
|
|
_writer.write(standalone);
|
|
_writer.write(DOUBLEQUOT);
|
|
}
|
|
_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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a charset object for the specified encoding
|
|
* @param encoding
|
|
* @return a charset object
|
|
* @throws UnsupportedEncodingException if the encoding is not supported
|
|
*/
|
|
private Charset getCharset(String encoding) throws UnsupportedEncodingException {
|
|
if (encoding.equalsIgnoreCase("UTF-32")) {
|
|
throw new UnsupportedEncodingException("The basic XMLWriter does "
|
|
+ "not support " + encoding);
|
|
}
|
|
|
|
Charset cs;
|
|
try {
|
|
cs = Charset.forName(encoding);
|
|
} catch (IllegalCharsetNameException | UnsupportedCharsetException ex) {
|
|
throw new UnsupportedEncodingException(encoding);
|
|
}
|
|
return cs;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|