com.fasterxml.aalto.out.StreamWriterBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aalto-xml Show documentation
Show all versions of aalto-xml Show documentation
Ultra-high performance non-blocking XML processor (Stax/Stax2, SAX/SAX2)
/* Aalto XML processor
*
* Copyright (c) 2006- Tatu Saloranta, [email protected]
*
* Licensed under the License specified in the file LICENSE which is
* included with the source code.
* You may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fasterxml.aalto.out;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.MessageFormat;
import java.util.*;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import org.codehaus.stax2.*;
import org.codehaus.stax2.validation.*;
import org.codehaus.stax2.ri.Stax2WriterImpl;
import org.codehaus.stax2.ri.typed.AsciiValueEncoder;
import org.codehaus.stax2.ri.typed.ValueEncoderFactory;
import org.codehaus.stax2.typed.Base64Variant;
import org.codehaus.stax2.typed.Base64Variants;
import com.fasterxml.aalto.ValidationException;
import com.fasterxml.aalto.impl.ErrorConsts;
import com.fasterxml.aalto.impl.IoStreamException;
import com.fasterxml.aalto.impl.LocationImpl;
import com.fasterxml.aalto.impl.StreamExceptionBase;
import com.fasterxml.aalto.util.TextUtil;
import com.fasterxml.aalto.util.XmlConsts;
/**
* Base class for {@link XMLStreamReader} implementations.
*/
public abstract class StreamWriterBase
extends Stax2WriterImpl
implements NamespaceContext, ValidationContext
{
protected enum State { PROLOG, TREE, EPILOG };
/*
/**********************************************************************
/* Configuration
/**********************************************************************
*/
protected final WriterConfig _config;
/**
* Root namespace context defined for this writer, if any.
*/
protected NamespaceContext _rootNsContext;
// // // Config flags:
// // Basic Stax:
// // Stax2:
// // Custom:
// Note: non-final to support pluggable validators' needs
protected boolean _cfgCheckStructure;
protected boolean _cfgCheckContent;
protected boolean _cfgCheckAttrs;
protected final boolean _cfgCDataAsText;
/*
/**********************************************************************
/* Symbol table for reusing name serializations
/**********************************************************************
*/
protected WNameTable _symbols;
/*
/**********************************************************************
/* Output objects
/**********************************************************************
*/
/**
* Actual physical writer to output serialized XML content to
*/
protected final XmlWriter _xmlWriter;
/**
* When outputting using Typed Access API, we will need
* encoders. If so, they will created by lazily-constructed
* factory
*/
protected ValueEncoderFactory _valueEncoderFactory;
/*
/**********************************************************************
/* Validation support
/**********************************************************************
*/
/**
* Optional validator to use for validating output against
* one or more schemas, and/or for safe pretty-printing (indentation).
*/
protected XMLValidator _validator = null;
/**
* State value used with validation, to track types of content
* that is allowed at this point in output stream. Only used if
* validation is enabled: if so, value is determined via validation
* callbacks.
*/
protected int _vldContent = XMLValidator.CONTENT_ALLOW_ANY_TEXT;
/**
* Custom validation problem handler, if any.
*/
protected ValidationProblemHandler _vldProblemHandler = null;
/*
/**********************************************************************
/* State information
/**********************************************************************
*/
protected State _state = State.PROLOG;
/**
* We'll use a virtual root element (like a document node of sort),
* to simplify other processing, basically such that there is
* always a current output element instance, even when in prolog
* or epilog.
*/
protected OutputElement _currElem = OutputElement.createRoot();
/**
* Flag that is set to true first time something has been output.
* Generally needed to keep track of whether XML declaration
* (START_DOCUMENT) can be output or not.
*/
protected boolean _stateAnyOutput = false;
/**
* Flag that is set during time that a start element is "open", ie.
* START_ELEMENT has been output (and possibly zero or more name
* space declarations and attributes), before other main-level
* constructs have been output.
*/
protected boolean _stateStartElementOpen = false;
/**
* Flag that indicates that current element is an empty element (one
* that is explicitly defined as one, by calling a method -- NOT one
* that just happens to be empty).
* This is needed to know what to do when next non-ns/attr node
* is output; normally a new context is opened, but for empty
* elements not.
*/
protected boolean _stateEmptyElement = false;
/**
* Value passed as the expected root element, when using the multiple
* argument {@link #writeDTD} method. Will be used in structurally
* validating mode (and in dtd-validating mode, since that automatically
* enables structural validation as well, to pre-filter well-formedness
* errors that validators might have trouble dealing with).
*/
protected String _dtdRootElemName = null;
/*
/**********************************************************************
/* Pool for recycling OutputElement instances.
/*
/* Note: although pooling of cheap objects like OutputElement
/* is usually not a good idea, here it does make sense, as
/* instances are still short-lived (same as writer's). Since
/* instances are ONLY reused within this context, they stay in
/* cheap ("Eden") GC area, and can lead to slight net gain
/**********************************************************************
*/
protected OutputElement _outputElemPool = null;
/**
* Although pooled objects are small, let's limit the pool size
* nonetheless, to minimize extra memory usage for deeply nested
* documents. Even just 4 levels might be enough, 8 should cover
* > 95% of cases
*/
final static int MAX_POOL_SIZE = 8;
protected int _poolSize = 0;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
protected StreamWriterBase(WriterConfig cfg, XmlWriter writer,
WNameTable symbols)
{
_config = cfg;
_xmlWriter = writer;
_symbols = symbols;
// // Config settings:
// !!! TBI:
_cfgCheckStructure = cfg.willCheckStructure();
_cfgCheckContent = cfg.willCheckContent();
_cfgCheckAttrs = cfg.willCheckAttributes();
_cfgCDataAsText = false;
}
/*
/**********************************************************************
/* Basic Stax API
/**********************************************************************
*/
@Override
public void close() throws XMLStreamException
{
_finishDocument(false);
}
@Override
public void flush() throws XMLStreamException
{
try {
_xmlWriter.flush();
} catch (IOException ie) {
throw new IoStreamException(ie);
}
}
@Override
public final NamespaceContext getNamespaceContext() {
return this;
}
// note: will be defined later on, in NamespaceContext impl part:
//public String getPrefix(String uri);
@Override
public Object getProperty(String name) {
// true -> mandatory, unrecognized will throw exception, as per Stax javadocs
return _config.getProperty(name, true);
}
@Override
public abstract void setDefaultNamespace(String uri)
throws XMLStreamException;
@Override
public void setNamespaceContext(NamespaceContext ctxt)
throws XMLStreamException
{
// This is only allowed before root element output:
if (_state != State.PROLOG) {
throwOutputError("Called setNamespaceContext() after having already output root element.");
}
_rootNsContext = ctxt;
}
@Override
public final void setPrefix(String prefix, String uri)
throws XMLStreamException
{
if (prefix == null) {
throw new NullPointerException();
}
// Are we actually trying to set the default namespace?
if (prefix.length() == 0) {
setDefaultNamespace(uri);
return;
}
if (uri == null) {
throw new NullPointerException();
}
// Let's check that xml/xmlns are not improperly (re)defined
{
if (prefix.equals("xml")) {
if (!uri.equals(XMLConstants.XML_NS_URI)) {
throwOutputError(ErrorConsts.ERR_NS_REDECL_XML, uri);
}
} else if (prefix.equals("xmlns")) {
if (!uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
throwOutputError(ErrorConsts.ERR_NS_REDECL_XMLNS, uri);
}
} else {
// Neither of prefixes.. but how about URIs?
if (uri.equals(XMLConstants.XML_NS_URI)) {
throwOutputError(ErrorConsts.ERR_NS_REDECL_XML_URI, prefix);
} else if (uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
throwOutputError(ErrorConsts.ERR_NS_REDECL_XMLNS_URI, prefix);
}
}
// Empty URI can only be bound to the default ns on xml 1.0:
if (uri.length() == 0) {
if (!_config.isXml11()) {
throwOutputError(ErrorConsts.ERR_NS_EMPTY);
}
}
}
_setPrefix(prefix, uri);
}
protected abstract void _setPrefix(String prefix, String uri);
@Override
public final void writeAttribute(String localName, String value)
throws XMLStreamException
{
if (!_stateStartElementOpen) {
throwOutputError(ErrorConsts.WERR_ATTR_NO_ELEM);
}
// note: for attributes, no prefix <=> no namespace, so:
_writeAttribute(_symbols.findSymbol(localName), value);
}
@Override
public abstract void writeAttribute(String nsURI, String localName, String value)
throws XMLStreamException;
@Override
public abstract void writeAttribute(String prefix, String nsURI,
String localName, String value) throws XMLStreamException;
@Override
public void writeCData(String data) throws XMLStreamException
{
if (_cfgCDataAsText) {
writeCharacters(data);
return;
}
_verifyWriteCData();
// Also, do we do textual content validation?
if ((_vldContent == XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT)
&& (_validator != null)) {
// Last arg is false, since we do not know if more text
// may be added with additional calls
_validator.validateText(data, false);
}
try {
int ix = _xmlWriter.writeCData(data);
if (ix >= 0) { // unfixable problems?
_reportNwfContent(ErrorConsts.WERR_CDATA_CONTENT, Integer.valueOf(ix));
}
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
@Override
public void writeCharacters(char[] text, int start, int len)
throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
// If outside the main element tree, must be all white space,
// so let's call appropriate method:
if (inPrologOrEpilog()) {
writeSpace(text, start, len);
return;
}
// Validator-based validation?
if (_vldContent <= XMLValidator.CONTENT_ALLOW_WS) {
if (_vldContent == XMLValidator.CONTENT_ALLOW_NONE) { // never ok
_reportInvalidContent(CHARACTERS);
} else { // all-ws is ok...
if (!TextUtil.isAllWhitespace(text, start, len, _config.isXml11())) {
_reportInvalidContent(CHARACTERS);
}
}
} else if (_vldContent == XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT) {
if (_validator != null) {
// Last arg is false, since we do not know if more text
// may be added with additional calls
_validator.validateText(text, start, len, false);
}
}
if (len > 0) {
try {
_xmlWriter.writeCharacters(text, start, len);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
}
@Override
public void writeCharacters(String text) throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
if (inPrologOrEpilog()) {
writeSpace(text);
return;
}
// Validator-based validation?
/* Note: although it'd be good to check validity first, we
* do not know allowed textual content before actually writing
* pending start element (if any)... so can't call this earlier
*/
if (_vldContent <= XMLValidator.CONTENT_ALLOW_WS) {
if (_vldContent == XMLValidator.CONTENT_ALLOW_NONE) { // never ok
_reportInvalidContent(CHARACTERS);
} else { // all-ws is ok...
if (!TextUtil.isAllWhitespace(text, _config.isXml11())) {
_reportInvalidContent(CHARACTERS);
}
}
} else if (_vldContent == XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT) {
if (_validator != null) {
/* Last arg is false, since we do not know if more text
* may be added with additional calls
*/
_validator.validateText(text, false);
}
}
try {
_xmlWriter.writeCharacters(text);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
@Override
public void writeComment(String data) throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
if (_vldContent == XMLValidator.CONTENT_ALLOW_NONE) { // from DTD, EMPTY
_reportInvalidContent(COMMENT);
}
/* No structural validation needed per se, for comments; they are
* allowed anywhere in XML content. However, content may need to
* be checked (by XmlWriter)
*/
try {
int ix = _xmlWriter.writeComment(data);
if (ix >= 0) {
_reportNwfContent(ErrorConsts.WERR_COMMENT_CONTENT, Integer.valueOf(ix));
}
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
@Override
public abstract void writeDefaultNamespace(String nsURI) throws XMLStreamException;
@Override
public final void writeDTD(String dtd) throws XMLStreamException
{
_verifyWriteDTD();
_dtdRootElemName = ""; // marker to verify only one is output
try {
_xmlWriter.writeDTD(dtd);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
/**
* It is assumed here that caller actually wants whatever is the
* default namespace (or it is used in "non-namespace" mode, where
* no namespaces are bound ever). As such we do not have to
* distinguish between repairing and non-repairing modes.
*/
@Override
public void writeEmptyElement(String localName) throws XMLStreamException
{
_verifyStartElement(null, localName);
WName name = _symbols.findSymbol(localName);
if (_validator != null) {
_validator.validateElementStart(localName, "", "");
}
_writeStartTag(name, true);
}
@Override
public abstract void writeEmptyElement(String nsURI, String localName)
throws XMLStreamException;
@Override
public abstract void writeEmptyElement(String prefix, String localName, String nsURI)
throws XMLStreamException;
@Override
public void writeEndDocument() throws XMLStreamException {
_finishDocument(false);
}
@Override
public void writeEndElement() throws XMLStreamException
{
/* Do we need to close up an earlier empty element?
* (open start element that was not created via call to
* writeEmptyElement gets handled later on)
*/
if (_stateStartElementOpen && _stateEmptyElement) {
_stateEmptyElement = false;
_closeStartElement(true);
}
// Better have something to close... (to figure out what to close)
if (_state != State.TREE) {
_reportNwfStructure("No open start element, when trying to write end element");
}
OutputElement thisElem = _currElem;
// Ok, and then let's pop that element from the stack
_currElem = thisElem.getParent();
if (_poolSize < MAX_POOL_SIZE) {
thisElem.addToPool(_outputElemPool);
_outputElemPool = thisElem;
++_poolSize;
}
if (_cfgCheckStructure) {
/*
if (expName != null) { // let's just check local name?
String localName = thisElem.getLocalName();
if (!localName.equals(expName.getLocalPart())) {
_reportNwfStructure("Mismatching close element local name, '"+localName+"'; expected '"+expName.getLocalPart()+"'.");
}
}
*/
}
try {
// Do we have an unfinished start element? If so, will get empty elem
if (_stateStartElementOpen) {
/* Can't/shouldn't call _closeStartElement (since we need to
* write empty element), but need to do same processing.
*/
_stateStartElementOpen = false;
_xmlWriter.writeStartTagEmptyEnd();
} else { // Otherwise, full end element
_xmlWriter.writeEndTag(thisElem.getName());
}
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
if (_currElem.isRoot()) { // (note: we have dummy placeholder elem that contains doc)
_state = State.EPILOG;
}
// Time to validate?
if (_validator != null) {
_vldContent = _validator.validateElementEnd(thisElem.getLocalName(), thisElem.getNonNullPrefix(),
thisElem.getNonNullNamespaceURI());
}
}
@Override
public void writeEntityRef(String name)
throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
// Structurally, need to check we are not in prolog/epilog.
if (_cfgCheckStructure) {
if (inPrologOrEpilog()) {
_reportNwfStructure(ErrorConsts.WERR_PROLOG_ENTITY);
}
}
// Validator-based validation?
if (_vldContent == XMLValidator.CONTENT_ALLOW_NONE) {
// Char entity, general entity; whatever it is it's invalid...
_reportInvalidContent(ENTITY_REFERENCE);
}
try {
_xmlWriter.writeEntityReference(_symbols.findSymbol(name));
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
@Override
public abstract void writeNamespace(String prefix, String nsURI)
throws XMLStreamException;
@Override
public void writeProcessingInstruction(String target) throws XMLStreamException {
writeProcessingInstruction(target, null);
}
@Override
public void writeProcessingInstruction(String target, String data)
throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
/* Structurally, PIs are always ok (content might not be)...
* except in empty content models...
*/
if (_vldContent == XMLValidator.CONTENT_ALLOW_NONE) {
_reportInvalidContent(PROCESSING_INSTRUCTION);
}
try {
int ix = _xmlWriter.writePI(_symbols.findSymbol(target), data);
if (ix >= 0) {
_reportNwfContent(ErrorConsts.WERR_PI_CONTENT, Integer.valueOf(ix));
}
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
@Override
public void writeStartDocument() throws XMLStreamException
{
String enc = _config.getActualEncoding();
if (enc == null) {
enc = XmlConsts.STAX_DEFAULT_OUTPUT_ENCODING;
_config.setActualEncodingIfNotSet(enc);
}
_writeStartDocument(XmlConsts.STAX_DEFAULT_OUTPUT_VERSION, enc, null);
}
@Override
public void writeStartDocument(String version)
throws XMLStreamException
{
_writeStartDocument(version, _config.getActualEncoding(), null);
}
@Override
public void writeStartDocument(String encoding, String version)
throws XMLStreamException
{
_writeStartDocument(version, encoding, null);
}
/**
* It is assumed here that caller actually wants whatever is the
* default namespace (or it is used in "non-namespace" mode, where
* no namespaces are bound ever). As such we do not have to
* distinguish between repairing and non-repairing modes.
*/
@Override
public void writeStartElement(String localName)
throws XMLStreamException
{
_verifyStartElement(null, localName);
WName name = _symbols.findSymbol(localName);
if (_validator != null) {
_validator.validateElementStart(localName, "", "");
}
_writeStartTag(name, false);
}
@Override
public abstract void writeStartElement(String nsURI, String localName)
throws XMLStreamException;
@Override
public abstract void writeStartElement(String prefix, String localName,
String nsURI)
throws XMLStreamException;
/*
/**********************************************************************
/* NamespaceContext implementation
/**********************************************************************
*/
@Override
public String getNamespaceURI(String prefix)
{
String uri = _currElem.getNamespaceURI(prefix);
if (uri == null) {
if (_rootNsContext != null) {
uri = _rootNsContext.getNamespaceURI(prefix);
}
}
return uri;
}
@Override
public String getPrefix(String uri)
{
String prefix = _currElem.getPrefix(uri);
if (prefix == null) {
if (_rootNsContext != null) {
prefix = _rootNsContext.getPrefix(uri);
}
}
return prefix;
}
@Override
public Iterator getPrefixes(String uri)
{
return _currElem.getPrefixes(uri, _rootNsContext);
}
/*
/**********************************************************************
/* TypedXMLStreamWriter2 implementation
/* (Typed Access API, Stax v3.0)
/**********************************************************************
*/
// // // Typed element content write methods
@Override
public void writeBoolean(boolean b)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getScalarEncoder(b ? "true" : "false"));
}
@Override
public void writeInt(int value)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(value));
}
@Override
public void writeLong(long value)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(value));
}
@Override
public void writeFloat(float value)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(value));
}
@Override
public void writeDouble(double value)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(value));
}
@Override
public void writeInteger(BigInteger value)
throws XMLStreamException
{
// Not optimal, but should do ok:
writeTypedElement(valueEncoderFactory().getScalarEncoder(value.toString()));
}
@Override
public void writeDecimal(BigDecimal value)
throws XMLStreamException
{
// Not optimal, but should do ok:
writeTypedElement(valueEncoderFactory().getScalarEncoder(value.toString()));
}
@Override
public void writeQName(QName value)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getScalarEncoder(_serializeQName(value)));
}
@Override
public final void writeIntArray(int[] value, int from, int length)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(value, from, length));
}
@Override
public void writeLongArray(long[] value, int from, int length)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(value, from, length));
}
@Override
public void writeFloatArray(float[] value, int from, int length)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(value, from, length));
}
@Override
public void writeDoubleArray(double[] value, int from, int length)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(value, from, length));
}
@Override
public void writeBinary(byte[] value, int from, int length)
throws XMLStreamException
{
Base64Variant v = Base64Variants.getDefaultVariant();
writeTypedElement(valueEncoderFactory().getEncoder(v, value, from, length));
}
@Override
public void writeBinary(Base64Variant v, byte[] value, int from, int length)
throws XMLStreamException
{
writeTypedElement(valueEncoderFactory().getEncoder(v, value, from, length));
}
private final void writeTypedElement(AsciiValueEncoder enc)
throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
try {
_xmlWriter.writeTypedValue(enc);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
// // // Typed attribute value write methods
@Override
public final void writeBooleanAttribute(String prefix, String nsURI, String localName, boolean value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value));
}
@Override
public final void writeIntAttribute(String prefix, String nsURI, String localName, int value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value));
}
@Override
public final void writeLongAttribute(String prefix, String nsURI, String localName, long value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value));
}
@Override
public final void writeFloatAttribute(String prefix, String nsURI, String localName, float value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value));
}
@Override
public final void writeDoubleAttribute(String prefix, String nsURI, String localName, double value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value));
}
@Override
public final void writeIntegerAttribute(String prefix, String nsURI, String localName, BigInteger value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getScalarEncoder(value.toString()));
}
@Override
public final void writeDecimalAttribute(String prefix, String nsURI, String localName, BigDecimal value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getScalarEncoder(value.toString()));
}
@Override
public final void writeQNameAttribute(String prefix, String nsURI, String localName, QName value)
throws XMLStreamException
{
/* Can't use AsciiValueEncoder, since QNames can contain
* non-ascii characters
*/
writeAttribute(prefix, nsURI, localName, _serializeQName(value));
}
@Override
public void writeIntArrayAttribute(String prefix, String nsURI, String localName, int[] value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value, 0, value.length));
}
@Override
public void writeLongArrayAttribute(String prefix, String nsURI, String localName, long[] value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value, 0, value.length));
}
@Override
public void writeFloatArrayAttribute(String prefix, String nsURI, String localName, float[] value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value, 0, value.length));
}
@Override
public void writeDoubleArrayAttribute(String prefix, String nsURI, String localName, double[] value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(value, 0, value.length));
}
@Override
public void writeBinaryAttribute(String prefix, String nsURI, String localName, byte[] value)
throws XMLStreamException
{
Base64Variant v = Base64Variants.getDefaultVariant();
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(v, value, 0, value.length));
}
@Override
public void writeBinaryAttribute(Base64Variant v, String prefix, String nsURI, String localName, byte[] value)
throws XMLStreamException
{
writeTypedAttribute(prefix, nsURI, localName,
valueEncoderFactory().getEncoder(v, value, 0, value.length));
}
/**
* Need to leave implementation of this method abstract, because
* repairing and non-repairing modes differ in how names are
* handled.
*/
public abstract void writeTypedAttribute(String prefix, String nsURI, String localName,
AsciiValueEncoder enc)
throws XMLStreamException;
protected abstract String _serializeQName(QName name)
throws XMLStreamException;
/*
/**********************************************************************
/* XMLStreamWriter2 methods (StAX2)
/**********************************************************************
*/
@Override
public void writeSpace(String text) throws XMLStreamException
{
try {
_xmlWriter.writeSpace(text);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
@Override
public void writeSpace(char[] cbuf, int offset, int len) throws XMLStreamException
{
try {
_xmlWriter.writeSpace(cbuf, offset, len);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
// From base class:
//public void copyEventFromReader(XMLStreamReader2 sr, boolean preserveEventData) throws XMLStreamException
/*
/**********************************************************************
/* Stax2, output handling
/**********************************************************************
*/
@Override
public void closeCompletely() throws XMLStreamException
{
_finishDocument(true);
}
/*
/**********************************************************************
/* Stax2, config
/**********************************************************************
*/
// NOTE: getProperty() defined in Stax 1.0 interface
@Override
public boolean isPropertySupported(String name) {
// !!! TBI: not all these properties are really supported
return _config.isPropertySupported(name);
}
/**
* @param name Name of the property to set
* @param value Value to set property to.
*
* @return True, if the specified property was succesfully
* set to specified value; false if its value was not changed
*/
@Override
public boolean setProperty(String name, Object value)
{
/* Note: can not call local method, since it'll return false for
* recognized but non-mutable properties
*/
return _config.setProperty(name, value);
}
@Override
public XMLValidator validateAgainst(XMLValidationSchema schema) throws XMLStreamException
{
XMLValidator vld = schema.createValidator(this);
if (_validator == null) {
/* Need to enable other validation modes? Structural validation
* should always be done when we have other validators as well,
* as well as attribute uniqueness checks.
*/
_cfgCheckStructure = true;
_cfgCheckAttrs = true;
_validator = vld;
} else {
_validator = new ValidatorPair(_validator, vld);
}
return vld;
}
@Override
public XMLValidator stopValidatingAgainst(XMLValidationSchema schema) throws XMLStreamException
{
XMLValidator[] results = new XMLValidator[2];
XMLValidator found = null;
if (ValidatorPair.removeValidator(_validator, schema, results)) { // found
found = results[0];
_validator = results[1];
found.validationCompleted(false);
if (_validator == null) {
resetValidationFlags();
}
}
return found;
}
@Override
public XMLValidator stopValidatingAgainst(XMLValidator validator) throws XMLStreamException
{
XMLValidator[] results = new XMLValidator[2];
XMLValidator found = null;
if (ValidatorPair.removeValidator(_validator, validator, results)) { // found
found = results[0];
_validator = results[1];
found.validationCompleted(false);
if (_validator == null) {
resetValidationFlags();
}
}
return found;
}
@Override
public ValidationProblemHandler setValidationProblemHandler(ValidationProblemHandler h)
{
ValidationProblemHandler oldH = _vldProblemHandler;
_vldProblemHandler = h;
return oldH;
}
private void resetValidationFlags()
{
_cfgCheckStructure = _config.willCheckStructure();
_cfgCheckAttrs = _config.willCheckAttributes();
}
/*
/**********************************************************************
/* Stax2, other accessors, mutators
/**********************************************************************
*/
@Override
public XMLStreamLocation2 getLocation()
{
return new LocationImpl(null, null, // pub/sys ids not yet known
_xmlWriter.getAbsOffset(), _xmlWriter.getRow(), _xmlWriter.getColumn());
}
@Override
public String getEncoding() {
return _config.getActualEncoding();
}
/*
/**********************************************************************
/* StAX2, output methods
/**********************************************************************
*/
@Override
public void writeCData(char[] cbuf, int start, int len)
throws XMLStreamException
{
if (_cfgCDataAsText) {
writeCharacters(cbuf, start, len);
return;
}
_verifyWriteCData();
int ix;
try {
ix = _xmlWriter.writeCData(cbuf, start, len);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
if (ix >= 0) { // problems that could not to be fixed?
_reportNwfContent(ErrorConsts.WERR_CDATA_CONTENT, Integer.valueOf(ix));
}
}
public void writeDTD(DTDInfo info)
throws XMLStreamException
{
writeDTD(info.getDTDRootName(),
info.getDTDSystemId(),
info.getDTDPublicId(), info.getDTDInternalSubset());
}
@Override
public void writeDTD(String rootName, String systemId, String publicId,
String internalSubset)
throws XMLStreamException
{
_verifyWriteDTD();
_dtdRootElemName = rootName;
try {
_xmlWriter.writeDTD(_symbols.findSymbol(rootName), systemId, publicId, internalSubset);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
/**
* Similar to {@link #writeEndElement}, but never allows implicit
* creation of empty elements.
*/
@Override
public void writeFullEndElement() throws XMLStreamException
{
if (_stateStartElementOpen && _stateEmptyElement) {
_stateEmptyElement = false;
/* The open element was an empty element (created via
* explicit call using writeEmptyElement); not affected
*/
_closeStartElement(true);
}
writeEndElement();
}
@Override
public void writeStartDocument(String version, String encoding,
boolean standAlone)
throws XMLStreamException
{
_writeStartDocument(version, encoding, standAlone ? "yes" : "no");
}
@Override
public void writeRaw(String text) throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
try {
_xmlWriter.writeRaw(text, 0, text.length());
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
@Override
public void writeRaw(String text, int start, int offset) throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
try {
_xmlWriter.writeRaw(text, start, offset);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
@Override
public void writeRaw(char[] text, int offset, int length) throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
try {
_xmlWriter.writeRaw(text, offset, length);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
/*
/**********************************************************************
/* ValidationContext interface (Stax2, validation)
/**********************************************************************
*/
@Override
public String getXmlVersion() {
return _config.isXml11() ? "1.1" : "1.0";
}
@Override
public QName getCurrentElementName() {
return _currElem.getQName();
}
// already part of NamespaceContext impl above...
//public String getNamespaceURI(String prefix)
/**
* As of now, there is no way to specify the base URI. Could be improved
* in future, if xml:base is supported.
*/
@Override
public String getBaseUri() {
return null;
}
@Override
public Location getValidationLocation() {
return getLocation();
}
@Override
public void reportProblem(XMLValidationProblem prob) throws XMLStreamException
{
// Custom handler set? If so, it'll take care of it:
if (_vldProblemHandler != null) {
_vldProblemHandler.reportProblem(prob);
return;
}
// Let's throw an exception for fatal and non-fatal errors:
if (prob.getSeverity() >= XMLValidationProblem.SEVERITY_ERROR) {
throw ValidationException.create(prob);
}
}
/**
* Adding default attribute values does not usually make sense on
* output side, so the implementation is a NOP for now.
*/
@Override
public int addDefaultAttribute(String localName, String uri, String prefix,
String value)
{
// nothing to do, but to indicate we didn't add it...
return -1;
}
// // // Notation/entity access: not (yet?) implemented
@Override
public boolean isNotationDeclared(String name) { return false; }
@Override
public boolean isUnparsedEntityDeclared(String name) { return false; }
// // // Attribute access: not yet implemented:
/* !!! TODO: Implement attribute access (iff validate-attributes
* enabled?
*/
@Override
public int getAttributeCount() { return 0; }
@Override
public String getAttributeLocalName(int index) { return null; }
@Override
public String getAttributeNamespace(int index) { return null; }
@Override
public String getAttributePrefix(int index) { return null; }
@Override
public String getAttributeValue(int index) { return null; }
@Override
public String getAttributeValue(String nsURI, String localName) {
return null;
}
@Override
public String getAttributeType(int index) {
return "";
}
@Override
public int findAttributeIndex(String nsURI, String localName) {
return -1;
}
/*
/**********************************************************************
/* Package methods (ie not part of public API)
/**********************************************************************
*/
/**
* Method called to close an open start element, when another
* main-level element (not namespace declaration or attribute)
* is being output; except for end element which is handled differently.
*/
/**
* Method called to close an open start element, when another
* main-level element (not namespace declaration or attribute)
* is being output; except for end element which is handled differently.
*/
protected void _closeStartElement(boolean emptyElem)
throws XMLStreamException
{
_stateStartElementOpen = false;
try {
if (emptyElem) {
_xmlWriter.writeStartTagEmptyEnd();
} else {
_xmlWriter.writeStartTagEnd();
}
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
// Need bit more special handling for empty elements...
if (emptyElem) {
OutputElement thisElem = _currElem;
_currElem = thisElem.getParent();
if (_currElem.isRoot()) { // Did we close the root? (isRoot() returns true for the virtual "document node")
_state = State.EPILOG;
}
if (_poolSize < MAX_POOL_SIZE) {
thisElem.addToPool(_outputElemPool);
_outputElemPool = thisElem;
++_poolSize;
}
}
}
protected final boolean inPrologOrEpilog() {
return (_state != State.TREE);
}
protected final ValueEncoderFactory valueEncoderFactory()
{
if (_valueEncoderFactory == null) {
_valueEncoderFactory = new ValueEncoderFactory();
}
return _valueEncoderFactory;
}
/*
/**********************************************************************
/* Package methods, write helpers
/**********************************************************************
*/
protected final void _writeAttribute(WName name, String value)
throws XMLStreamException
{
if (_cfgCheckAttrs) { // still need to ensure no duplicate attrs?
_verifyWriteAttr(name);
}
try {
_xmlWriter.writeAttribute(name, value);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
protected final void _writeAttribute(WName name, AsciiValueEncoder enc)
throws XMLStreamException
{
if (_cfgCheckAttrs) { // still need to ensure no duplicate attrs?
_verifyWriteAttr(name);
}
try {
_xmlWriter.writeAttribute(name, enc);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
protected final void _writeDefaultNamespace(String uri)
throws XMLStreamException
{
WName name = _symbols.findSymbol("xmlns");
try {
_xmlWriter.writeAttribute(name, uri);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
protected final void _writeNamespace(String prefix, String uri)
throws XMLStreamException
{
WName name = _symbols.findSymbol("xmlns", prefix);
try {
_xmlWriter.writeAttribute(name, uri);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
protected void _writeStartDocument(String version, String encoding,
String standAlone)
throws XMLStreamException
{
/* Not legal to output XML declaration if there has been ANY
* output prior... that is, if we validate the structure.
*/
if (_cfgCheckStructure) {
if (_stateAnyOutput) {
_reportNwfStructure(ErrorConsts.WERR_DUP_XML_DECL);
}
}
_stateAnyOutput = true;
if (_cfgCheckContent) {
// !!! If and how to check encoding?
// if (encoding != null) { }
if (version != null && version.length() > 0) {
if (!(version.equals(XmlConsts.XML_V_10_STR)
|| version.equals(XmlConsts.XML_V_11_STR))) {
_reportNwfContent("Illegal version argument ('"+version
+"'); should only use '"+XmlConsts.XML_V_10_STR
+"' or '"+XmlConsts.XML_V_11_STR+"'");
}
}
}
if (version == null || version.length() == 0) {
version = XmlConsts.STAX_DEFAULT_OUTPUT_VERSION;
}
if (XmlConsts.XML_V_11_STR.equals(version)) {
_config.enableXml11();
_xmlWriter.enableXml11();
}
if (encoding != null && encoding.length() > 0) {
/* What about conflicting encoding? Let's only update encoding,
* if it wasn't set.
*/
_config.setActualEncodingIfNotSet(encoding);
}
try {
_xmlWriter.writeXmlDeclaration(version, encoding, standAlone);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
}
protected void _writeStartTag(WName name, boolean isEmpty)
throws XMLStreamException
{
_stateAnyOutput = true;
_stateStartElementOpen = true;
if (_outputElemPool != null) {
OutputElement newCurr = _outputElemPool;
_outputElemPool = newCurr.reuseAsChild(_currElem, name);
--_poolSize;
_currElem = newCurr;
} else {
_currElem = _currElem.createChild(name);
}
try {
_xmlWriter.writeStartTagStart(name);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
_stateEmptyElement = isEmpty;
}
protected void _writeStartTag(WName name, boolean isEmpty, String uri)
throws XMLStreamException
{
_stateAnyOutput = true;
_stateStartElementOpen = true;
if (uri == null) { // let's canonicalize to empty String here
uri = "";
}
if (_outputElemPool != null) {
OutputElement newCurr = _outputElemPool;
_outputElemPool = newCurr.reuseAsChild(_currElem, name, uri);
--_poolSize;
_currElem = newCurr;
} else {
_currElem = _currElem.createChild(name, uri);
}
try {
_xmlWriter.writeStartTagStart(name);
} catch (IOException ioe) {
throw new IoStreamException(ioe);
}
_stateEmptyElement = isEmpty;
}
/*
/**********************************************************************
/* Package methods, validation
/**********************************************************************
*/
protected final void _verifyWriteAttr(WName name)
{
// !!! TBI
}
/**
* Method that is called to ensure that we can start writing an
* element, both from structural point of view, and from syntactic
* (close previously open start element, if any). Note that since
* it needs to be called before writing out anything, no namespace
* bindings have been (or can be) output, and hence given prefix
* may not be one that actually gets used.
*/
protected void _verifyStartElement(String prefix, String localName)
throws XMLStreamException
{
// Need to finish an open start element?
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
} else if (_state == State.PROLOG) {
_verifyRootElement(prefix, localName);
} else if (_state == State.EPILOG) {
if (_cfgCheckStructure) {
String name = (prefix == null) ? localName : (prefix+":"+localName);
_reportNwfStructure(ErrorConsts.WERR_PROLOG_SECOND_ROOT, name);
}
/* When outputting a fragment, need to reset this to the
* tree. No point in trying to verify the root element?
*/
_state = State.TREE;
}
}
protected final void _verifyWriteCData()
throws XMLStreamException
{
_stateAnyOutput = true;
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
// Not legal outside main element tree:
if (_cfgCheckStructure) {
if (inPrologOrEpilog()) {
_reportNwfStructure(ErrorConsts.WERR_PROLOG_CDATA);
}
}
}
protected final void _verifyWriteDTD()
throws XMLStreamException
{
if (_cfgCheckStructure) {
if (_state != State.PROLOG) {
throw new XMLStreamException("Can not write DOCTYPE declaration (DTD) when not in prolog any more (state "+_state+"; start element(s) written)");
}
// And let's also check that we only output one...
if (_dtdRootElemName != null) {
throw new XMLStreamException("Trying to write multiple DOCTYPE declarations");
}
}
}
protected void _verifyRootElement(String prefix, String localName)
throws XMLValidationException
{
// !!! TBI: only relevant if we are actually validating?
_state = State.TREE;
}
/*
/**********************************************************************
/* Package methods, basic output problem reporting
/**********************************************************************
*/
protected static void throwOutputError(String msg)
throws XMLStreamException
{
throw new StreamExceptionBase(msg);
}
protected static void throwOutputError(String format, Object arg)
throws XMLStreamException
{
String msg = MessageFormat.format(format, new Object[] { arg });
throwOutputError(msg);
}
/**
* Method called when an illegal method (namespace-specific method
* on non-ns writer) is called by the application.
*/
protected static void reportIllegalMethod(String msg)
throws XMLStreamException
{
throwOutputError(msg);
}
/**
* This is the method called when an output method call violates
* structural well-formedness checks
* and structural checking
* is enabled.
*/
protected static void _reportNwfStructure(String msg)
throws XMLStreamException
{
throwOutputError(msg);
}
protected static void _reportNwfStructure(String msg, Object arg)
throws XMLStreamException
{
throwOutputError(msg, arg);
}
/**
* This is the method called when an output method call violates
* content well-formedness checks
* and content validation
* is enabled.
*/
protected static void _reportNwfContent(String msg)
throws XMLStreamException
{
throwOutputError(msg);
}
protected static void _reportNwfContent(String msg, Object arg)
throws XMLStreamException
{
throwOutputError(msg, arg);
}
/**
* This is the method called when an output method call violates
* attribute well-formedness checks (trying to output dup attrs)
* and name validaty checking
* is enabled.
*/
protected static void _reportNwfAttr(String msg)
throws XMLStreamException
{
throwOutputError(msg);
}
protected static void _reportNwfAttr(String msg, Object arg)
throws XMLStreamException
{
throwOutputError(msg, arg);
}
protected static void _reportNwfName(String msg)
throws XMLStreamException
{
throwOutputError(msg);
}
protected static void throwFromIOE(IOException ioe)
throws XMLStreamException
{
throw new IoStreamException(ioe);
}
protected static void reportIllegalArg(String msg)
throws IllegalArgumentException
{
throw new IllegalArgumentException(msg);
}
/*
protected static void throwIllegalState(String msg)
throws IllegalArgumentException
{
throw new IllegalStateException(msg);
}
*/
/*
/**********************************************************************
/* Package methods, output validation problem reporting
/**********************************************************************
*/
protected void _reportInvalidContent(int evtType)
throws XMLStreamException
{
switch (_vldContent) {
case XMLValidator.CONTENT_ALLOW_NONE:
_reportValidationProblem(MessageFormat.format
(ErrorConsts.VERR_EMPTY, _currElem.getNameDesc(), ErrorConsts.tokenTypeDesc(evtType)));
break;
case XMLValidator.CONTENT_ALLOW_WS:
_reportValidationProblem(MessageFormat.format
(ErrorConsts.VERR_NON_MIXED, _currElem.getNameDesc()));
break;
case XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT:
case XMLValidator.CONTENT_ALLOW_ANY_TEXT:
/* Not 100% sure if this should ever happen... depends on
* interpretation of 'any' content model?
*/
_reportValidationProblem(MessageFormat.format
(ErrorConsts.VERR_ANY, _currElem.getNameDesc(), ErrorConsts.tokenTypeDesc(evtType)));
break;
default: // should never occur:
_reportValidationProblem("Internal error: trying to report invalid content for "+evtType);
}
}
public void _reportValidationProblem(String msg) throws XMLStreamException
{
reportProblem(new XMLValidationProblem(getValidationLocation(),
msg,
XMLValidationProblem.SEVERITY_ERROR));
}
/*
/**********************************************************************
/* Package methods, output validation problem reporting
/**********************************************************************
*/
private final void _finishDocument(boolean forceRealClose)
throws XMLStreamException
{
// Is tree still open?
if (_state != State.EPILOG) {
if (_cfgCheckStructure && _state == State.PROLOG) {
_reportNwfStructure(ErrorConsts.WERR_PROLOG_NO_ROOT);
}
// Need to close the open sub-tree, if it exists...
// First, do we have an open start element?
if (_stateStartElementOpen) {
_closeStartElement(_stateEmptyElement);
}
// Then, one by one, need to close open scopes:
while (_state != State.EPILOG) {
writeEndElement();
}
}
// Any symbols to merge?
if (_symbols.maybeDirty()) {
_symbols.mergeToParent();
}
/* And finally, inform the underlying writer that it should flush
* and release its buffers, and close components it uses if any.
*/
try {
_xmlWriter.close(forceRealClose);
} catch (IOException ie) {
throw new IoStreamException(ie);
}
}
@Override
public String toString()
{
return "[StreamWriter: "+getClass()+", underlying outputter: "
+((_xmlWriter == null) ? "NULL" : _xmlWriter.toString());
}
}