/* Woodstox XML processor
 *
 * Copyright (c) 2004- 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.ctc.wstx.sw;

import java.io.IOException;
import java.util.*;

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

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

import com.ctc.wstx.api.WriterConfig;
import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.cfg.XmlConsts;
import com.ctc.wstx.sr.AttributeCollector;
import com.ctc.wstx.sr.InputElementStack;
import com.ctc.wstx.util.EmptyNamespaceContext;
import com.ctc.wstx.util.StringVector;

Implementation of XMLStreamWriter used when namespace support is not enabled. This means that only local names are used for elements and attributes; and if rudimentary namespace declarations need to be output, they are output using attribute writing methods.
/** * Implementation of {@link XMLStreamWriter} used when namespace support * is not enabled. This means that only local names are used for elements * and attributes; and if rudimentary namespace declarations need to be * output, they are output using attribute writing methods. */
public class NonNsStreamWriter extends TypedStreamWriter { /* //////////////////////////////////////////////////// // State information //////////////////////////////////////////////////// */
Stack of currently open start elements; only local names are included.
/** * Stack of currently open start elements; only local names * are included. */
final StringVector mElements;
Container for attribute names for current element; used only if uniqueness of attribute names is to be enforced.

TreeSet is used mostly because clearing it up is faster than clearing up HashSet, and the only access is done by adding entries and see if an value was already set.

/** * Container for attribute names for current element; used only * if uniqueness of attribute names is to be enforced. *<p> * TreeSet is used mostly because clearing it up is faster than * clearing up HashSet, and the only access is done by * adding entries and see if an value was already set. */
TreeSet<String> mAttrNames; /* //////////////////////////////////////////////////// // Life-cycle (ctors) //////////////////////////////////////////////////// */ public NonNsStreamWriter(XmlWriter xw, String enc, WriterConfig cfg) { super(xw, enc, cfg); mElements = new StringVector(32); } /* //////////////////////////////////////////////////// // XMLStreamWriter API //////////////////////////////////////////////////// */ @Override public NamespaceContext getNamespaceContext() { return EmptyNamespaceContext.getInstance(); } @Override public String getPrefix(String uri) { return null; } @Override public void setDefaultNamespace(String uri) throws XMLStreamException { reportIllegalArg("Can not set default namespace for non-namespace writer."); } @Override public void setNamespaceContext(NamespaceContext context) { reportIllegalArg("Can not set NamespaceContext for non-namespace writer."); } @Override public void setPrefix(String prefix, String uri) throws XMLStreamException { reportIllegalArg("Can not set namespace prefix for non-namespace writer."); } @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); } if (mCheckAttrs) { /* 11-Dec-2005, TSa: Should use a more efficient Set/Map value * for this in future. */ if (mAttrNames == null) { mAttrNames = new TreeSet<String>(); } if (!mAttrNames.add(localName)) { reportNwfAttr("Trying to write attribute '"+localName+"' twice"); } } 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, XmlConsts.ATTR_NO_NS_URI, XmlConsts.ATTR_NO_PREFIX, value); } try { mWriter.writeAttribute(localName, value); } catch (IOException ioe) { throwFromIOE(ioe); } } @Override public void writeAttribute(String nsURI, String localName, String value) throws XMLStreamException { writeAttribute(localName, value); } @Override public void writeAttribute(String prefix, String nsURI, String localName, String value) throws XMLStreamException { writeAttribute(localName, value); } @Override public void writeDefaultNamespace(String nsURI) throws XMLStreamException { reportIllegalMethod("Can not call writeDefaultNamespace namespaces with non-namespace writer."); } @Override public void writeEmptyElement(String localName) throws XMLStreamException { doWriteStartElement(localName); mEmptyElement = true; } @Override public void writeEmptyElement(String nsURI, String localName) throws XMLStreamException { writeEmptyElement(localName); } @Override public void writeEmptyElement(String prefix, String localName, String nsURI) throws XMLStreamException { writeEmptyElement(localName); } @Override public void writeEndElement() throws XMLStreamException { doWriteEndTag(null, mCfgAutomaticEmptyElems); } @Override public void writeNamespace(String prefix, String nsURI) throws XMLStreamException { reportIllegalMethod("Can not set write namespaces with non-namespace writer."); } @Override public void writeStartElement(String localName) throws XMLStreamException { doWriteStartElement(localName); mEmptyElement = false; } @Override public void writeStartElement(String nsURI, String localName) throws XMLStreamException { writeStartElement(localName); } @Override public void writeStartElement(String prefix, String localName, String nsURI) throws XMLStreamException { writeStartElement(localName); } /* //////////////////////////////////////////////////// // 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() { if (mElements.isEmpty()) { return null; } return new QName(mElements.getLastString()); } @Override public String getNamespaceURI(String prefix) { return null; } /* //////////////////////////////////////////////////// // Package methods: //////////////////////////////////////////////////// */ @Override public void writeStartElement(StartElement elem) throws XMLStreamException { QName name = elem.getName(); writeStartElement(name.getLocalPart()); @SuppressWarnings("unchecked") Iterator<Attribute> it = elem.getAttributes(); while (it.hasNext()) { Attribute attr = it.next(); name = attr.getName(); writeAttribute(name.getLocalPart(), attr.getValue()); } }
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.getLocalPart() : null, mCfgAutomaticEmptyElems); } @Override protected void writeTypedAttribute(String prefix, String nsURI, String localName, AsciiValueEncoder enc) throws XMLStreamException { // note: mostly copied from the other writeAttribute() method.. if (!mStartElementOpen && mCheckStructure) { reportNwfStructure(ErrorConsts.WERR_ATTR_NO_ELEM); } if (mCheckAttrs) { // doh. Not good, need to construct non-transient value... if (mAttrNames == null) { mAttrNames = new TreeSet<String>(); } if (!mAttrNames.add(localName)) { reportNwfAttr("Trying to write attribute '"+localName+"' twice"); } } try { if (mValidator == null) { mWriter.writeTypedAttribute(localName, enc); } else { mWriter.writeTypedAttribute(null, localName, null, enc, mValidator, getCopyBuffer()); } } catch (IOException ioe) { throwFromIOE(ioe); } }
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. */
@Override protected void closeStartElement(boolean emptyElem) throws XMLStreamException { mStartElementOpen = false; if (mAttrNames != null) { mAttrNames.clear(); } try { if (emptyElem) { mWriter.writeStartTagEmptyEnd(); } else { mWriter.writeStartTagEnd(); } } catch (IOException ioe) { throwFromIOE(ioe); } if (mValidator != null) { mVldContent = mValidator.validateElementAndAttributes(); } // Need bit more special handling for empty elements... if (emptyElem) { String localName = mElements.removeLast(); if (mElements.isEmpty()) { mState = STATE_EPILOG; } if (mValidator != null) { mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); } } }
Element copier method implementation suitable to be used with non-namespace-aware writers. The only special thing here is that the copier can convert namespace declarations to equivalent attribute writes.
/** * Element copier method implementation suitable to be used with * non-namespace-aware writers. The only special thing here is that * the copier can convert namespace declarations to equivalent * attribute writes. */
@Override public void copyStartElement(InputElementStack elemStack, AttributeCollector attrCollector) throws IOException, XMLStreamException { String ln = elemStack.getLocalName(); boolean nsAware = elemStack.isNamespaceAware(); /* First, since we are not to output namespace stuff as is, * we just need to copy the element: */ if (nsAware) { // but reader is ns-aware? Need to add prefix? String prefix = elemStack.getPrefix(); if (prefix != null && prefix.length() > 0) { // yup ln = prefix + ":" + ln; } } writeStartElement(ln); /* However, if there are any namespace declarations, we probably * better output them just as 'normal' attributes: */ if (nsAware) { int nsCount = elemStack.getCurrentNsCount(); if (nsCount > 0) { for (int i = 0; i < nsCount; ++i) { String prefix = elemStack.getLocalNsPrefix(i); if (prefix == null || prefix.length() == 0) { // default NS decl prefix = XMLConstants.XML_NS_PREFIX; } else { prefix = "xmlns:"+prefix; } writeAttribute(prefix, elemStack.getLocalNsURI(i)); } } } /* And then let's just output attributes, if any (whether to copy * implicit, aka "default" attributes, is configurable) */ int attrCount = mCfgCopyDefaultAttrs ? attrCollector.getCount() : attrCollector.getSpecifiedCount(); if (attrCount > 0) { for (int i = 0; i < attrCount; ++i) { attrCollector.writeAttribute(i, mWriter, mValidator); } } } @Override protected String getTopElementDesc() { return mElements.isEmpty() ? "#root" : mElements.getLastString(); } @Override public String validateQNamePrefix(QName name) { // Can either strip prefix out, or return as is return name.getPrefix(); } /* //////////////////////////////////////////////////// // Internal methods //////////////////////////////////////////////////// */ private void doWriteStartElement(String localName) throws XMLStreamException { mAnyOutput = true; // Need to finish an open start element? if (mStartElementOpen) { closeStartElement(mEmptyElement); } else if (mState == STATE_PROLOG) { // 20-Dec-2005, TSa: Does this match DOCTYPE declaration? verifyRootElement(localName, null); } else if (mState == STATE_EPILOG) { if (mCheckStructure) { reportNwfStructure(ErrorConsts.WERR_PROLOG_SECOND_ROOT, localName); } // Outputting fragment? Better reset to tree, then... mState = STATE_TREE; } /* Note: need not check for CONTENT_ALLOW_NONE here, since the * validator should handle this particular case... */ /*if (mVldContent == XMLValidator.CONTENT_ALLOW_NONE) { // EMPTY content reportInvalidContent(START_ELEMENT); }*/ if (mValidator != null) { mValidator.validateElementStart(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); } mStartElementOpen = true; mElements.addString(localName); try { mWriter.writeStartTagStart(localName); } catch (IOException ioe) { throwFromIOE(ioe); } }

Note: Caller has to do actual removal of the element from element stack, before calling this method.

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
/** *<p> * Note: Caller has to do actual removal of the element from element * stack, before calling this method. * * @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 */
private void doWriteEndTag(String 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; // note: this method guarantees proper updates to validation closeStartElement(true); } // Better have something to close... (to figure out what to close) if (mState != STATE_TREE) { // Have to throw an exception always, don't know elem name reportNwfStructure("No open start element, when trying to write end element"); } /* Now, do we have an unfinished start element (created via * writeStartElement() earlier)? */ String localName = mElements.removeLast(); if (mCheckStructure) { if (expName != null && !localName.equals(expName)) { /* Only gets called when trying to output an XMLEvent... in * which case names can actually be compared */ reportNwfStructure("Mismatching close element name, '"+localName+"'; expected '"+expName+"'."); } } /* Can't yet validate, since we have two paths; one for empty * elements, another for non-empty... */ // Got a half output start element to close? 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; if (mAttrNames != null) { mAttrNames.clear(); } try { // We could write an empty element, implicitly? if (allowEmpty) { mWriter.writeStartTagEmptyEnd(); if (mElements.isEmpty()) { mState = STATE_EPILOG; } if (mValidator != null) { mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); } return; } // Nah, need to close open elem, and then output close elem mWriter.writeStartTagEnd(); } catch (IOException ioe) { throwFromIOE(ioe); } } try { mWriter.writeEndTag(localName); } catch (IOException ioe) { throwFromIOE(ioe); } if (mElements.isEmpty()) { mState = STATE_EPILOG; } // Ok, time to validate... if (mValidator != null) { mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); } } }