/* 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);
}
}
}