/* Woodstox XML processor
 *
 * Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi
 *
 * Licensed under the License specified in the file LICENSE,
 * 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.ctc.wstx.sw;

import java.io.IOException;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;

import org.codehaus.stax2.ri.typed.AsciiValueEncoder;

import com.ctc.wstx.api.EmptyElementHandler;
import com.ctc.wstx.api.WriterConfig;
import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.cfg.XmlConsts;
import com.ctc.wstx.exc.WstxIOException;
import com.ctc.wstx.util.DefaultXmlSymbolTable;

Mid-level base class of namespace-aware stream writers. Contains shared functionality between repairing and non-repairing implementations.
/** * Mid-level base class of namespace-aware stream writers. Contains * shared functionality between repairing and non-repairing implementations. */
public abstract class BaseNsStreamWriter extends TypedStreamWriter { final protected static String sPrefixXml = DefaultXmlSymbolTable.getXmlSymbol(); final protected static String sPrefixXmlns = DefaultXmlSymbolTable.getXmlnsSymbol(); final protected static String ERR_NSDECL_WRONG_STATE = "Trying to write a namespace declaration when there is no open start element."; /* /////////////////////////////////////////////////////////////////////// // Configuration (options, features) /////////////////////////////////////////////////////////////////////// */ // // // Additional specific config flags base class doesn't have
True, if writer needs to automatically output namespace declarations (we are in repairing mode)
/** * True, if writer needs to automatically output namespace declarations * (we are in repairing mode) */
final protected boolean mAutomaticNS; final protected EmptyElementHandler mEmptyElementHandler; /* /////////////////////////////////////////////////////////////////////// // State information /////////////////////////////////////////////////////////////////////// */ protected SimpleOutputElement mCurrElem = SimpleOutputElement.createRoot();
Optional "root" namespace context that application can set. If so, it can be used to lookup namespace/prefix mappings
/** * Optional "root" namespace context that application can set. If so, * it can be used to lookup namespace/prefix mappings */
protected NamespaceContext mRootNsContext = null; /* /////////////////////////////////////////////////////////////////////// // Pool for recycling SimpleOutputElement instances /////////////////////////////////////////////////////////////////////// */ /* Note: although pooling of cheap objects like SimpleOutputElement * is usually not a good idea, here profiling showed it to be a * significant improvement. As long as instances are ONLY reused * within context of a single writer, they stay in cheap ("Eden") * GC area, and thus it should be a win. */ protected SimpleOutputElement mOutputElemPool = null;
Although pooled objects are small, let's limit the pool size nonetheless, to optimize memory usage for deeply nested documents. In general, even just low number like 4 levels gets decent return, but 8 should get 99% hit rate.
/** * Although pooled objects are small, let's limit the pool size * nonetheless, to optimize memory usage for deeply nested * documents. In general, even just low number like 4 levels gets * decent return, but 8 should get 99% hit rate. */
final static int MAX_POOL_SIZE = 8; protected int mPoolSize = 0; /* /////////////////////////////////////////////////////////////////////// // Life-cycle (ctors) /////////////////////////////////////////////////////////////////////// */ public BaseNsStreamWriter(XmlWriter xw, String enc, WriterConfig cfg, boolean repairing) { super(xw, enc, cfg); mAutomaticNS = repairing; mEmptyElementHandler = cfg.getEmptyElementHandler(); } /* /////////////////////////////////////////////////////////////////////// // XMLStreamWriter API /////////////////////////////////////////////////////////////////////// */ @Override public NamespaceContext getNamespaceContext() { return mCurrElem; } @Override public String getPrefix(String uri) { return mCurrElem.getPrefix(uri); } @Override public abstract void setDefaultNamespace(String uri) throws XMLStreamException;

Note: Root namespace context works best if automatic prefix creation ("namespace/prefix repairing" in StAX lingo) is enabled.

/** *<p> * Note: Root namespace context works best if automatic prefix * creation ("namespace/prefix repairing" in StAX lingo) is enabled. */
@Override public void setNamespaceContext(NamespaceContext ctxt) throws XMLStreamException { // This is only allowed before root element output: if (mState != STATE_PROLOG) { throwOutputError("Called setNamespaceContext() after having already output root element."); } mRootNsContext = ctxt; mCurrElem.setRootNsContext(ctxt); } @Override public void setPrefix(String prefix, String uri) throws XMLStreamException { if (prefix == null) { throw new NullPointerException("Can not pass null 'prefix' value"); } // Are we actually trying to set the default namespace? if (prefix.length() == 0) { setDefaultNamespace(uri); return; } if (uri == null) { throw new NullPointerException("Can not pass null 'uri' value"); } /* 25-Sep-2004, TSa: Let's check that "xml" and "xmlns" are not * (re-)defined to any other value, nor that value they * are bound to are bound to other prefixes. */ /* 01-Apr-2005, TSa: And let's not leave it optional: such * bindings should never succeed. */ // ... perhaps it really should be optional though? { if (prefix.equals(sPrefixXml)) { // prefix "xml" if (!uri.equals(XMLConstants.XML_NS_URI)) { throwOutputError(ErrorConsts.ERR_NS_REDECL_XML, uri); } } else if (prefix.equals(sPrefixXmlns)) { // prefix "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); } } /* 05-Feb-2005, TSa: Also, as per namespace specs; the 'empty' * namespace URI can not be bound as a non-default namespace * (ie. for any actual prefix) */ /* 04-Feb-2005, TSa: Namespaces 1.1 does allow this, though, * so for xml 1.1 documents we need to allow it */ if (!mXml11) { if (uri.length() == 0) { throwOutputError(ErrorConsts.ERR_NS_EMPTY); } } } doSetPrefix(prefix, uri); }
It's assumed calling this method implies caller just wants to add an attribute that does not belong to any namespace; as such no namespace checking or prefix generation is needed.
/** * It's assumed calling this method implies caller just wants to add * an attribute that does not belong to any namespace; as such no * namespace checking or prefix generation is needed. */
@Override public void writeAttribute(String localName, String value) throws XMLStreamException { // No need to set mAnyOutput, nor close the element if (!mStartElementOpen && mCheckStructure) { reportNwfStructure(ErrorConsts.WERR_ATTR_NO_ELEM); } doWriteAttr(localName, null, null, 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;

Note: It is assumed caller just wants the element to belong to whatever is the current default namespace.

/** *<p> * Note: It is assumed caller just wants the element to belong to whatever * is the current default namespace. */
@Override public void writeEmptyElement(String localName) throws XMLStreamException { checkStartElement(localName, null); if (mValidator != null) { mValidator.validateElementStart(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); } mEmptyElement = true; if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, localName); --mPoolSize; mCurrElem = newCurr; } else { mCurrElem = mCurrElem.createChild(localName); } doWriteStartTag(localName); } @Override public void writeEmptyElement(String nsURI, String localName) throws XMLStreamException { writeStartOrEmpty(localName, nsURI); mEmptyElement = true; } @Override public void writeEmptyElement(String prefix, String localName, String nsURI) throws XMLStreamException { writeStartOrEmpty(prefix, localName, nsURI); mEmptyElement = true; } @Override public void writeEndElement() throws XMLStreamException { doWriteEndTag(null, mCfgAutomaticEmptyElems); }
This method is assumed to just use default namespace (if any), and no further checks should be done.
/** * This method is assumed to just use default namespace (if any), * and no further checks should be done. */
@Override public void writeStartElement(String localName) throws XMLStreamException { checkStartElement(localName, null); if (mValidator != null) { mValidator.validateElementStart(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); } mEmptyElement = false; if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, localName); --mPoolSize; mCurrElem = newCurr; } else { mCurrElem = mCurrElem.createChild(localName); } doWriteStartTag(localName); } @Override public void writeStartElement(String nsURI, String localName) throws XMLStreamException { writeStartOrEmpty(localName, nsURI); mEmptyElement = false; } @Override public void writeStartElement(String prefix, String localName, String nsURI) throws XMLStreamException { writeStartOrEmpty(prefix, localName, nsURI); mEmptyElement = false; } @Override protected void writeTypedAttribute(String prefix, String nsURI, String localName, AsciiValueEncoder enc) throws XMLStreamException { if (!mStartElementOpen) { throwOutputError(ErrorConsts.WERR_ATTR_NO_ELEM); } if (mCheckAttrs) { // still need to ensure no duplicate attrs? mCurrElem.checkAttrWrite(nsURI, localName); } try { if (mValidator == null) { if (prefix == null || prefix.length() == 0) { mWriter.writeTypedAttribute(localName, enc); } else { mWriter.writeTypedAttribute(prefix, localName, enc); } } else { mWriter.writeTypedAttribute (prefix, localName, nsURI, enc, mValidator, getCopyBuffer()); } } catch (IOException ioe) { throw new WstxIOException(ioe); } } /* /////////////////////////////////////////////////////////////////////// // Remaining XMLStreamWriter2 methods (StAX2) /////////////////////////////////////////////////////////////////////// */
Similar to writeEndElement, but never allows implicit creation of empty elements.
/** * Similar to {@link #writeEndElement}, but never allows implicit * creation of empty elements. */
@Override public void writeFullEndElement() throws XMLStreamException { doWriteEndTag(null, false); } /* /////////////////////////////////////////////////////////////////////// // Remaining ValidationContext methods (StAX2) /////////////////////////////////////////////////////////////////////// */ @Override public QName getCurrentElementName() { return mCurrElem.getName(); } @Override public String getNamespaceURI(String prefix) { return mCurrElem.getNamespaceURI(prefix); } /* /////////////////////////////////////////////////////////////////////// // Implementations for base-class defined abstract methods /////////////////////////////////////////////////////////////////////// */
Method called by XMLEventWriter implementation (instead of the version that takes no argument), so that we can verify it does match the start element, if necessary
/** * Method called by {@link javax.xml.stream.XMLEventWriter} implementation * (instead of the version * that takes no argument), so that we can verify it does match the * start element, if necessary */
@Override public void writeEndElement(QName name) throws XMLStreamException { doWriteEndTag(mCheckStructure ? name : null, mCfgAutomaticEmptyElems); }
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.
Params:
  • emptyElem – If true, the element being closed is an empty element; if false, a separate stand-alone start element.
/** * 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. * * @param emptyElem If true, the element being closed is an empty * element; if false, a separate stand-alone start element. */
@Override protected void closeStartElement(boolean emptyElem) throws XMLStreamException { mStartElementOpen = false; try { if (emptyElem) { mWriter.writeStartTagEmptyEnd(); } else { mWriter.writeStartTagEnd(); } } catch (IOException ioe) { throw new WstxIOException(ioe); } if (mValidator != null) { mVldContent = mValidator.validateElementAndAttributes(); } // Need bit more special handling for empty elements... if (emptyElem) { SimpleOutputElement curr = mCurrElem; mCurrElem = curr.getParent(); if (mCurrElem.isRoot()) { // Did we close the root? (isRoot() returns true for the virtual "document node") mState = STATE_EPILOG; } if (mValidator != null) { mVldContent = mValidator.validateElementEnd (curr.getLocalName(), curr.getNamespaceURI(), curr.getPrefix()); } if (mPoolSize < MAX_POOL_SIZE) { curr.addToPool(mOutputElemPool); mOutputElemPool = curr; ++mPoolSize; } } } @Override protected String getTopElementDesc() { return mCurrElem.getNameDesc(); } /* /////////////////////////////////////////////////////////////////////// // Package methods sub-classes may also need /////////////////////////////////////////////////////////////////////// */
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).
/** * 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). */
protected void checkStartElement(String localName, String prefix) throws XMLStreamException { // Need to finish an open start element? if (mStartElementOpen) { closeStartElement(mEmptyElement); } else if (mState == STATE_PROLOG) { verifyRootElement(localName, prefix); } else if (mState == STATE_EPILOG) { if (mCheckStructure) { String name = (prefix == null || prefix.length() == 0) ? 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? */ mState = STATE_TREE; } } protected final void doWriteAttr(String localName, String nsURI, String prefix, String value) throws XMLStreamException { if (mCheckAttrs) { // still need to ensure no duplicate attrs? mCurrElem.checkAttrWrite(nsURI, localName); } if (mValidator != null) { // No need to get it normalized... even if validator does normalize // it, we don't use that for anything mValidator.validateAttribute(localName, nsURI, prefix, value); } try { int vlen = value.length(); // Worthwhile to make a local copy? if (vlen >= ATTR_MIN_ARRAYCOPY) { char[] buf = mCopyBuffer; if (buf == null) { mCopyBuffer = buf = mConfig.allocMediumCBuffer(DEFAULT_COPYBUFFER_LEN); } /* Ok, and in unlikely case of attribute values longer than * buffer... for now, let's just skip those case */ if (vlen <= buf.length) { value.getChars(0, vlen, buf, 0); if (prefix != null && prefix.length() > 0) { mWriter.writeAttribute(prefix, localName, buf, 0, vlen); } else { mWriter.writeAttribute(localName, buf, 0, vlen); } return; } } if (prefix != null && prefix.length() > 0) { mWriter.writeAttribute(prefix, localName, value); } else { mWriter.writeAttribute(localName, value); } } catch (IOException ioe) { throw new WstxIOException(ioe); } } protected final void doWriteAttr(String localName, String nsURI, String prefix, char[] buf, int start, int len) throws XMLStreamException { if (mCheckAttrs) { // still need to ensure no duplicate attrs? mCurrElem.checkAttrWrite(nsURI, localName); } if (mValidator != null) { // No need to get it normalized... even if validator does normalize // it, we don't use that for anything mValidator.validateAttribute(localName, nsURI, prefix, buf, start, len); } try { if (prefix != null && prefix.length() > 0) { mWriter.writeAttribute(prefix, localName, buf, start, len); } else { mWriter.writeAttribute(localName, buf, start, len); } } catch (IOException ioe) { throw new WstxIOException(ioe); } } protected void doWriteNamespace(String prefix, String nsURI) throws XMLStreamException { try { int vlen = nsURI.length(); // Worthwhile to make a local copy? if (vlen >= ATTR_MIN_ARRAYCOPY) { char[] buf = mCopyBuffer; if (buf == null) { mCopyBuffer = buf = mConfig.allocMediumCBuffer(DEFAULT_COPYBUFFER_LEN); } // Let's not bother with too long, though if (vlen <= buf.length) { nsURI.getChars(0, vlen, buf, 0); mWriter.writeAttribute(XMLConstants.XMLNS_ATTRIBUTE, prefix, buf, 0, vlen); return; } } mWriter.writeAttribute(XMLConstants.XMLNS_ATTRIBUTE, prefix, nsURI); } catch (IOException ioe) { throw new WstxIOException(ioe); } } protected void doWriteDefaultNs(String nsURI) throws XMLStreamException { try { int vlen = (nsURI == null) ? 0 : nsURI.length(); // Worthwhile to make a local copy? if (vlen >= ATTR_MIN_ARRAYCOPY) { char[] buf = mCopyBuffer; if (buf == null) { mCopyBuffer = buf = mConfig.allocMediumCBuffer(DEFAULT_COPYBUFFER_LEN); } // Let's not bother with too long, though if (vlen <= buf.length) { nsURI.getChars(0, vlen, buf, 0); mWriter.writeAttribute(XMLConstants.XMLNS_ATTRIBUTE, buf, 0, vlen); return; } } mWriter.writeAttribute(XMLConstants.XMLNS_ATTRIBUTE, nsURI); } catch (IOException ioe) { throw new WstxIOException(ioe); } } protected final void doWriteStartTag(String localName) throws XMLStreamException { mAnyOutput = true; mStartElementOpen = true; try { mWriter.writeStartTagStart(localName); } catch (IOException ioe) { throw new WstxIOException(ioe); } } protected final void doWriteStartTag(String prefix, String localName) throws XMLStreamException { mAnyOutput = true; mStartElementOpen = true; try { boolean hasPrefix = (prefix != null && prefix.length() > 0); if (hasPrefix) { mWriter.writeStartTagStart(prefix, localName); } else { mWriter.writeStartTagStart(localName); } } catch (IOException ioe) { throw new WstxIOException(ioe); } }
Params:
  • expName – Name that the closing element should have; null if whatever is in stack should be used
  • allowEmpty – If true, is allowed to create the empty element if the closing element was truly empty; if false, has to write the full empty element no matter what
/** * * @param expName Name that the closing element should have; null * if whatever is in stack should be used * @param allowEmpty If true, is allowed to create the empty element * if the closing element was truly empty; if false, has to write * the full empty element no matter what */
protected void doWriteEndTag(QName expName, boolean allowEmpty) throws XMLStreamException { /* First of all, 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 (mStartElementOpen && mEmptyElement) { mEmptyElement = false; closeStartElement(true); } // Better have something to close... (to figure out what to close) if (mState != STATE_TREE) { // Have to always throw exception... don't necessarily know the name reportNwfStructure("No open start element, when trying to write end element"); } SimpleOutputElement thisElem = mCurrElem; String prefix = thisElem.getPrefix(); String localName = thisElem.getLocalName(); String nsURI = thisElem.getNamespaceURI(); // Ok, and then let's pop that element from the stack mCurrElem = thisElem.getParent(); // Need to return the instance to pool? if (mPoolSize < MAX_POOL_SIZE) { thisElem.addToPool(mOutputElemPool); mOutputElemPool = thisElem; ++mPoolSize; } if (mCheckStructure) { if (expName != null) { // Let's only check the local name, for now... if (!localName.equals(expName.getLocalPart())) { /* Only gets called when trying to output an XMLEvent... in * which case names can actually be compared */ reportNwfStructure("Mismatching close element local name, '"+localName+"'; expected '"+expName.getLocalPart()+"'."); } } } /* Now, do we have an unfinished start element (created via * writeStartElement() earlier)? */ if (mStartElementOpen) { /* Can't/shouldn't call closeStartElement, but need to do same * processing. Thus, this is almost identical to closeStartElement: */ if (mValidator != null) { /* Note: return value is not of much use, since the * element will be closed right away... */ mVldContent = mValidator.validateElementAndAttributes(); } mStartElementOpen = false; try { //If an EmptyElementHandler is provided use it to determine if allowEmpty is set if (mEmptyElementHandler != null) { allowEmpty = mEmptyElementHandler.allowEmptyElement(prefix, localName, nsURI, allowEmpty); } // We could write an empty element, implicitly? if (allowEmpty) { mWriter.writeStartTagEmptyEnd(); if (mCurrElem.isRoot()) { mState = STATE_EPILOG; } if (mValidator != null) { mVldContent = mValidator.validateElementEnd(localName, nsURI, prefix); } return; } // Nah, need to close open elem, and then output close elem mWriter.writeStartTagEnd(); } catch (IOException ioe) { throw new WstxIOException(ioe); } } try { mWriter.writeEndTag(prefix, localName); } catch (IOException ioe) { throw new WstxIOException(ioe); } if (mCurrElem.isRoot()) { mState = STATE_EPILOG; } // Ok, time to validate... if (mValidator != null) { mVldContent = mValidator.validateElementEnd(localName, nsURI, prefix); } } /* /////////////////////////////////////////////////////////////////////// // More abstract methods for sub-classes to implement /////////////////////////////////////////////////////////////////////// */ public abstract void doSetPrefix(String prefix, String uri) throws XMLStreamException; @Override public abstract void writeDefaultNamespace(String nsURI) throws XMLStreamException; @Override public abstract void writeNamespace(String prefix, String nsURI) throws XMLStreamException; @Override public abstract void writeStartElement(StartElement elem) throws XMLStreamException; protected abstract void writeStartOrEmpty(String localName, String nsURI) throws XMLStreamException; protected abstract void writeStartOrEmpty(String prefix, String localName, String nsURI) throws XMLStreamException; }