/* 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, softwar
 * 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.HashMap;
import java.util.Iterator;
import java.util.Map;

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.sr.AttributeCollector;
import com.ctc.wstx.sr.InputElementStack;

Namespace-aware implementation of XMLStreamWriter, that does namespace repairing, ie resolves possible conflicts between prefixes (add new bindings as necessary), as well as automatically creates namespace declarations as necessary.
/** * Namespace-aware implementation of {@link XMLStreamWriter}, that does * namespace repairing, ie resolves possible conflicts between prefixes * (add new bindings as necessary), as well as automatically creates * namespace declarations as necessary. */
public final class RepairingNsStreamWriter extends BaseNsStreamWriter { /* /////////////////////////////////////////////////////////// // Configuration (options, features) /////////////////////////////////////////////////////////// */ // // // Additional specific config flags base class doesn't have protected final String mAutomaticNsPrefix; /* /////////////////////////////////////////////////////////// // Additional state /////////////////////////////////////////////////////////// */
Sequence number used for generating dynamic namespace prefixes. Array used as a wrapper to allow for easy sharing of the sequence number.
/** * Sequence number used for generating dynamic namespace prefixes. * Array used as a wrapper to allow for easy sharing of the sequence * number. */
protected int[] mAutoNsSeq = null; protected String mSuggestedDefNs = null;
Map that contains URI-to-prefix entries that point out suggested prefixes for URIs. These are populated by calls to BaseNsStreamWriter.setPrefix, and they are only used as hints for binding; if there are conflicts, repairing writer can just use some other prefix.
/** * Map that contains URI-to-prefix entries that point out suggested * prefixes for URIs. These are populated by calls to * {@link #setPrefix}, and they are only used as hints for binding; * if there are conflicts, repairing writer can just use some other * prefix. */
protected HashMap<String,String> mSuggestedPrefixes = null; /* /////////////////////////////////////////////////////////// // Life-cycle (ctors) /////////////////////////////////////////////////////////// */ public RepairingNsStreamWriter(XmlWriter xw, String enc, WriterConfig cfg) { super(xw, enc, cfg, true); mAutomaticNsPrefix = cfg.getAutomaticNsPrefix(); } /* /////////////////////////////////////////////////////////// // XMLStreamWriter API /////////////////////////////////////////////////////////// */ //public NamespaceContext getNamespaceContext() //public void setNamespaceContext(NamespaceContext context) //public String getPrefix(String uri) //public void setPrefix(String prefix, String uri) //public void setDefaultNamespace(String uri) //public void writeAttribute(String localName, String value) @Override public void writeAttribute(String nsURI, String localName, String value) throws XMLStreamException { // No need to set mAnyOutput, nor close the element if (!mStartElementOpen) { throwOutputError(ErrorConsts.WERR_ATTR_NO_ELEM); } doWriteAttr(localName, nsURI, findOrCreateAttrPrefix(null, nsURI, mCurrElem), value); } @Override public void writeAttribute(String prefix, String nsURI, String localName, String value) throws XMLStreamException { if (!mStartElementOpen) { throwOutputError(ErrorConsts.WERR_ATTR_NO_ELEM); } doWriteAttr(localName, nsURI, findOrCreateAttrPrefix(prefix, nsURI, mCurrElem), value); } @Override public void writeDefaultNamespace(String nsURI) throws XMLStreamException { /* 01-Sep-2006, TSa: The use case for calling this method is that * of caller may wanting to 'suggest' that * such a namespace should indeed be bound at this level. This * may be necessary for canonicalization, or for minimizing number * of binding declarations (all children need the ns, but root * itself not). */ if (!mStartElementOpen) { throwOutputError(ERR_NSDECL_WRONG_STATE); } /* ... We have one complication though: if the current element * uses default namespace, can not change it (attributes don't * matter -- they never use the default namespace, but either don't * belong to a namespace, or belong to one using explicit prefix) */ String prefix = mCurrElem.getPrefix(); if (prefix != null && prefix.length() > 0) { // ok, can change it mCurrElem.setDefaultNsUri(nsURI); doWriteDefaultNs(nsURI); } } //public void writeEmptyElement(String localName) throws XMLStreamException @Override public void writeNamespace(String prefix, String nsURI) throws XMLStreamException { /* (see discussion in 'writeDefaultNamespace()' for details on * if and how this method may get called in repairing mode) */ if (prefix == null || prefix.length() == 0) { writeDefaultNamespace(nsURI); return; } if (!mStartElementOpen) { throwOutputError(ERR_NSDECL_WRONG_STATE); } /* 01-Sep-2006, TSa: Let's only add the declaration if the prefix * is as of yet unbound. If we have to re-bind things in future, * so be it -- for now, this should suffice (and if we have to * add re-binding, must verify that no attribute, nor element * itself, is using overridden prefix) */ int value = mCurrElem.isPrefixValid(prefix, nsURI, true); if (value == SimpleOutputElement.PREFIX_UNBOUND) { mCurrElem.addPrefix(prefix, nsURI); doWriteNamespace(prefix, nsURI); } } /* /////////////////////////////////////////////////////////// // Package methods: /////////////////////////////////////////////////////////// */
With repairing writer, this is only taken as a suggestion as to how the caller would prefer prefixes to be mapped.
/** * With repairing writer, this is only taken as a suggestion as to how * the caller would prefer prefixes to be mapped. */
@Override public void setDefaultNamespace(String uri) throws XMLStreamException { mSuggestedDefNs = (uri == null || uri.length() == 0) ? null : uri; } @Override public void doSetPrefix(String prefix, String uri) throws XMLStreamException { /* Ok; let's assume that passing in a null or empty String as * the URI means that we don't want passed prefix to be preferred * for any URI. */ if (uri == null || uri.length() == 0) { if (mSuggestedPrefixes != null) { for (Iterator<Map.Entry<String,String>> it = mSuggestedPrefixes.entrySet().iterator(); it.hasNext(); ) { Map.Entry<String,String> en = it.next(); String thisP = en.getValue(); if (thisP.equals(prefix)) { it.remove(); } } } } else { if (mSuggestedPrefixes == null) { mSuggestedPrefixes = new HashMap<String,String>(16); } mSuggestedPrefixes.put(uri, prefix); } } @Override public void writeStartElement(StartElement elem) throws XMLStreamException { // In repairing mode this is simple: let's just pass info // we have, and things should work... a-may-zing! QName name = elem.getName(); writeStartElement(name.getPrefix(), name.getLocalPart(), name.getNamespaceURI()); Iterator<Attribute> it = elem.getAttributes(); while (it.hasNext()) { Attribute attr = it.next(); name = attr.getName(); writeAttribute(name.getPrefix(), name.getNamespaceURI(), name.getLocalPart(), attr.getValue()); } } //public void writeEndElement(QName name) throws XMLStreamException @Override protected void writeTypedAttribute(String prefix, String nsURI, String localName, AsciiValueEncoder enc) throws XMLStreamException { super.writeTypedAttribute(findOrCreateAttrPrefix(prefix, nsURI, mCurrElem), nsURI, localName, enc); } @Override protected void writeStartOrEmpty(String localName, String nsURI) throws XMLStreamException { checkStartElement(localName, ""); // First, need to find prefix matching URI, if any: String prefix = findElemPrefix(nsURI, mCurrElem); /* Then need to create the element, since it'll have to * contain the new namespace binding, if one needed * (changed to resolve [WSTX-135] as reported by Y-J Choi, * who also proposed the solution) */ if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, prefix, localName, nsURI); --mPoolSize; mCurrElem = newCurr; } else { mCurrElem = mCurrElem.createChild(prefix, localName, nsURI); } if (prefix != null) { // prefix ok, easy, no need to overwrite if (mValidator != null) { mValidator.validateElementStart(localName, nsURI, prefix); } doWriteStartTag(prefix, localName); } else { // no prefix, more work prefix = generateElemPrefix(null, nsURI, mCurrElem); if (mValidator != null) { mValidator.validateElementStart(localName, nsURI, prefix); } mCurrElem.setPrefix(prefix); doWriteStartTag(prefix, localName); if (prefix == null || prefix.length() == 0) { // def NS mCurrElem.setDefaultNsUri(nsURI); doWriteDefaultNs(nsURI); } else { // explicit NS mCurrElem.addPrefix(prefix, nsURI); doWriteNamespace(prefix, nsURI); } } } @Override protected void writeStartOrEmpty(String suggPrefix, String localName, String nsURI) throws XMLStreamException { checkStartElement(localName, suggPrefix); // In repairing mode, better ensure validity: String actPrefix = validateElemPrefix(suggPrefix, nsURI, mCurrElem); if (actPrefix != null) { // fine, an existing binding we can use: if (mValidator != null) { mValidator.validateElementStart(localName, nsURI, actPrefix); } if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, actPrefix, localName, nsURI); --mPoolSize; mCurrElem = newCurr; } else { mCurrElem = mCurrElem.createChild(actPrefix, localName, nsURI); } doWriteStartTag(actPrefix, localName); } else { // nah, need to create a new binding... /* Need to ensure that we'll pass "" as prefix, not null, so * that it is understood as "I want to use the default NS", not * as "whatever prefix, I don't care" */ if (suggPrefix == null) { suggPrefix = ""; } actPrefix = generateElemPrefix(suggPrefix, nsURI, mCurrElem); if (mValidator != null) { mValidator.validateElementStart(localName, nsURI, actPrefix); } if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, actPrefix, localName, nsURI); --mPoolSize; mCurrElem = newCurr; } else { mCurrElem = mCurrElem.createChild(actPrefix, localName, nsURI); } mCurrElem.setPrefix(actPrefix); doWriteStartTag(actPrefix, localName); if (actPrefix == null || actPrefix.length() == 0) { // def NS mCurrElem.setDefaultNsUri(nsURI); doWriteDefaultNs(nsURI); } else { // explicit NS mCurrElem.addPrefix(actPrefix, nsURI); doWriteNamespace(actPrefix, nsURI); } } }
Element copier method implementation suitable for use with namespace-aware writers in repairing mode. The trickiest thing is having to properly order calls to setPrefix, writeNamespace and writeStartElement; the order writers expect is bit different from the order in which element information is passed in.
/** * Element copier method implementation suitable for use with * namespace-aware writers in repairing mode. * The trickiest thing is having to properly * order calls to <code>setPrefix</code>, <code>writeNamespace</code> * and <code>writeStartElement</code>; the order writers expect is * bit different from the order in which element information is * passed in. */
@Override public final void copyStartElement(InputElementStack elemStack, AttributeCollector ac) throws IOException, XMLStreamException { /* In case of repairing stream writer, we can actually just * go ahead and first output the element: stream writer should * be able to resolve namespace mapping for the element * automatically, as necessary. */ String prefix = elemStack.getPrefix(); String uri = elemStack.getNsURI(); writeStartElement(prefix, elemStack.getLocalName(), uri); /* 04-Sep-2006, TSa: Although we could really just ignore all * namespace declarations, some apps prefer (or even expect...) * that ns bindings are preserved as much as possible. So, let's * just try to output them as they are (could optimize and skip * ones related to the start element [same prefix or URI], but * for now let's not bother) */ int nsCount = elemStack.getCurrentNsCount(); if (nsCount > 0) { // yup, got some... for (int i = 0; i < nsCount; ++i) { writeNamespace(elemStack.getLocalNsPrefix(i), elemStack.getLocalNsURI(i)); } } /* And then let's just output attributes, if any (whether to copy * implicit, aka "default" attributes, is configurable) */ int attrCount = mCfgCopyDefaultAttrs ? ac.getCount() : ac.getSpecifiedCount(); /* Unlike in non-ns and simple-ns modes, we can not simply literally * copy the attributes here. It is possible that some namespace * prefixes have been remapped... so need to be bit more careful. */ if (attrCount > 0) { for (int i = 0; i < attrCount; ++i) { // First; need to make sure that the prefix-to-ns mapping // attribute has is valid... and can not output anything // before that's done (since remapping will output a namespace // declaration!) uri = ac.getURI(i); prefix = ac.getPrefix(i); // With attributes, missing/empty prefix always means 'no // namespace', can take a shortcut: if (prefix == null || prefix.length() == 0) { ; } else { // and otherwise we'll always have a prefix as attributes // can not make use of the def. namespace... prefix = findOrCreateAttrPrefix(prefix, uri, mCurrElem); } /* Hmmh. Since the prefix we use may be different from what * collector has, we can not use pass-through method of * the collector, but need to call XmlWriter directly: */ if (prefix == null || prefix.length() == 0) { mWriter.writeAttribute(ac.getLocalName(i), ac.getValue(i)); } else { mWriter.writeAttribute(prefix, ac.getLocalName(i), ac.getValue(i)); } } } } @Override public String validateQNamePrefix(QName name) throws XMLStreamException { /* Gets bit more complicated: we need to ensure that given URI * is properly bound... */ String uri = name.getNamespaceURI(); String suggPrefix = name.getPrefix(); String actPrefix = validateElemPrefix(suggPrefix, uri, mCurrElem); if (actPrefix == null) { // no suitable prefix, must bind /* Need to ensure that we'll pass "" as prefix, not null, so * that it is understood as "I want to use the default NS", not * as "whatever prefix, I don't care" */ if (suggPrefix == null) { suggPrefix = ""; } actPrefix = generateElemPrefix(suggPrefix, uri, mCurrElem); if (actPrefix == null || actPrefix.length() == 0) { // def NS writeDefaultNamespace(uri); } else { writeNamespace(actPrefix, uri); } } return actPrefix; } /* /////////////////////////////////////////////////////////// // Internal methods /////////////////////////////////////////////////////////// */
Method called to find an existing prefix for the given namespace, if any exists in the scope. If one is found, it's returned (including "" for the current default namespace); if not, null is returned.
Params:
  • nsURI – URI of namespace for which we need a prefix
/** * Method called to find an existing prefix for the given namespace, * if any exists in the scope. If one is found, it's returned (including * "" for the current default namespace); if not, null is returned. * * @param nsURI URI of namespace for which we need a prefix */
protected final String findElemPrefix(String nsURI, SimpleOutputElement elem) throws XMLStreamException { /* Special case: empty NS URI can only be bound to the empty * prefix... */ if (nsURI == null || nsURI.length() == 0) { String currDefNsURI = elem.getDefaultNsUri(); if (currDefNsURI != null && currDefNsURI.length() > 0) { // Nope; won't do... has to be re-bound, but not here: return null; } return ""; } return mCurrElem.getPrefix(nsURI); }
Method called after findElemPrefix has returned null, to create and bind a namespace mapping for specified namespace.
/** * Method called after {@link #findElemPrefix} has returned null, * to create and bind a namespace mapping for specified namespace. */
protected final String generateElemPrefix(String suggPrefix, String nsURI, SimpleOutputElement elem) throws XMLStreamException { /* Ok... now, since we do not have an existing mapping, let's * see if we have a preferred prefix to use. */ /* Except if we need the empty namespace... that can only be * bound to the empty prefix: */ if (nsURI == null || nsURI.length() == 0) { return ""; } /* Ok; with elements this is easy: the preferred prefix can * ALWAYS be used, since it can mask preceding bindings: */ if (suggPrefix == null) { // caller wants this URI to map as the default namespace? if (mSuggestedDefNs != null && mSuggestedDefNs.equals(nsURI)) { suggPrefix = ""; } else { suggPrefix = (mSuggestedPrefixes == null) ? null: mSuggestedPrefixes.get(nsURI); if (suggPrefix == null) { /* 16-Oct-2005, TSa: We have 2 choices here, essentially; * could make elements always try to override the def * ns... or can just generate new one. Let's do latter * for now. */ if (mAutoNsSeq == null) { mAutoNsSeq = new int[1]; mAutoNsSeq[0] = 1; } suggPrefix = elem.generateMapping(mAutomaticNsPrefix, nsURI, mAutoNsSeq); } } } // Ok; let's let the caller deal with bindings return suggPrefix; }
Method called to somehow find a prefix for given namespace, to be used for a new start element; either use an existing one, or generate a new one. If a new mapping needs to be generated, it will also be automatically bound, and necessary namespace declaration output.
Params:
  • suggPrefix – Suggested prefix to bind, if any; may be null to indicate "no preference"
  • nsURI – URI of namespace for which we need a prefix
  • elem – Currently open start element, on which the attribute will be added.
/** * Method called to somehow find a prefix for given namespace, to be * used for a new start element; either use an existing one, or * generate a new one. If a new mapping needs to be generated, * it will also be automatically bound, and necessary namespace * declaration output. * * @param suggPrefix Suggested prefix to bind, if any; may be null * to indicate "no preference" * @param nsURI URI of namespace for which we need a prefix * @param elem Currently open start element, on which the attribute * will be added. */
protected final String findOrCreateAttrPrefix(String suggPrefix, String nsURI, SimpleOutputElement elem) throws XMLStreamException { if (nsURI == null || nsURI.length() == 0) { /* Attributes never use the default namespace; missing * prefix always leads to the empty ns... so nothing * special is needed here. */ return null; } // Maybe the suggested prefix is properly bound? if (suggPrefix != null) { int status = elem.isPrefixValid(suggPrefix, nsURI, false); if (status == SimpleOutputElement.PREFIX_OK) { return suggPrefix; } /* Otherwise, if the prefix is unbound, let's just bind * it -- if caller specified a prefix, it probably prefers * binding that prefix even if another prefix already existed? * The remaining case (already bound to another URI) we don't * want to touch, at least not yet: it may or not be safe * to change binding, so let's just not try it. */ if (status == SimpleOutputElement.PREFIX_UNBOUND) { elem.addPrefix(suggPrefix, nsURI); doWriteNamespace(suggPrefix, nsURI); return suggPrefix; } } // If not, perhaps there's another existing binding available? String prefix = elem.getExplicitPrefix(nsURI); if (prefix != null) { // already had a mapping for the URI... cool. return prefix; } // Nope, need to create one. First, let's see if there's a preference... if (suggPrefix != null) { prefix = suggPrefix; } else if (mSuggestedPrefixes != null) { prefix = mSuggestedPrefixes.get(nsURI); // note: def ns is never added to suggested prefix map } if (prefix != null) { /* Can not use default namespace for attributes. * Also, re-binding is tricky for attributes; can't * re-bind anything that's bound on this scope... or * used in this scope. So, to simplify life, let's not * re-bind anything for attributes. */ if (prefix.length() == 0 || (elem.getNamespaceURI(prefix) != null)) { prefix = null; } } if (prefix == null) { if (mAutoNsSeq == null) { mAutoNsSeq = new int[1]; mAutoNsSeq[0] = 1; } prefix = mCurrElem.generateMapping(mAutomaticNsPrefix, nsURI, mAutoNsSeq); } // Ok; so far so good: let's now bind and output the namespace: elem.addPrefix(prefix, nsURI); doWriteNamespace(prefix, nsURI); return prefix; } private final String validateElemPrefix(String prefix, String nsURI, SimpleOutputElement elem) throws XMLStreamException { /* 06-Feb-2005, TSa: Special care needs to be taken for the * "empty" (or missing) namespace: * (see comments from findOrCreatePrefix()) */ if (nsURI == null || nsURI.length() == 0) { String currURL = elem.getDefaultNsUri(); if (currURL == null || currURL.length() == 0) { // Ok, good: return ""; } // Nope, needs to be re-bound: return null; } int status = elem.isPrefixValid(prefix, nsURI, true); if (status == SimpleOutputElement.PREFIX_OK) { return prefix; } /* Hmmh... now here's bit of dilemma: that particular prefix is * either not bound, or is masked... but it is possible some other * prefix would be bound. Should we search for another one, or * try to re-define suggested one? Let's do latter, for now; * caller can then (try to) bind the preferred prefix: */ return null; } }