/* Aalto XML processor * * Copyright (c) 2006- Tatu Saloranta, tatu.saloranta@iki.fi * * 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<String> 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 <b>succesfully</b> * 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()); } }