/* 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.io.Writer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.stream.Location; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLReporter; import javax.xml.stream.XMLStreamWriter; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Attribute; import javax.xml.stream.events.Namespace; import javax.xml.stream.events.StartElement; import org.codehaus.stax2.XMLStreamReader2; import com.ctc.wstx.api.WriterConfig; import com.ctc.wstx.cfg.ErrorConsts; import com.ctc.wstx.sr.AttributeCollector; import com.ctc.wstx.sr.InputElementStack; import com.ctc.wstx.sr.StreamReaderImpl; import com.ctc.wstx.util.DefaultXmlSymbolTable; /** * 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 class RepairingNsStreamWriter extends BaseNsStreamWriter { /* //////////////////////////////////////////////////// // Configuration (options, features) //////////////////////////////////////////////////// */ // // // Additional specific config flags base class doesn't have 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. */ int[] mAutoNsSeq = null; String mSuggestedDefNs = null; /** * 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. */ HashMap 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) 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); } 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); } public void writeDefaultNamespace(String nsURI) throws XMLStreamException { /* 21-Sep-2004, TSa: Should not call this in "namespace repairing" * mode? However, if it is called, what should be done? There * are multiple possibilities; like: * (a) Throw an exception * (b) Ignore the call * (c) Check potential validity; ignore if it matched a declaration, * throw an exception if it didn't. */ /* 01-Sep-2006, TSa: Actually, there is one use case for calling * this method. Specifically, caller may want 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 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. */ public void setDefaultNamespace(String uri) throws XMLStreamException { mSuggestedDefNs = (uri == null || uri.length() == 0) ? null : uri; } 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 it = mSuggestedPrefixes.entrySet().iterator(); it.hasNext(); ) { Map.Entry en = (Map.Entry) it.next(); String thisP = (String) en.getValue(); if (thisP.equals(prefix)) { it.remove(); } } } } else { if (mSuggestedPrefixes == null) { mSuggestedPrefixes = new HashMap(16); } mSuggestedPrefixes.put(uri, prefix); } } 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 it = elem.getAttributes(); while (it.hasNext()) { Attribute attr = (Attribute) it.next(); name = attr.getName(); writeAttribute(name.getPrefix(), name.getNamespaceURI(), name.getLocalPart(), attr.getValue()); } } //public void writeEndElement(QName name) throws XMLStreamException protected void writeStartOrEmpty(String localName, String nsURI) throws XMLStreamException { checkStartElement(localName, ""); // Need a prefix.... String prefix = findElemPrefix(nsURI, mCurrElem); if (prefix != null) { // prefix ok, easy if (mValidator != null) { mValidator.validateElementStart(localName, nsURI, prefix); } if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, prefix, localName, nsURI); --mPoolSize; mCurrElem = newCurr; } else { mCurrElem = mCurrElem.createChild(prefix, localName, nsURI); } doWriteStartTag(prefix, localName); } else { // no prefix, more work // prefix = generateElemPrefix(null, nsURI, mCurrElem); // if (mValidator != null) { // mValidator.validateElementStart(localName, nsURI, prefix); // } // if (mOutputElemPool != null) { // SimpleOutputElement newCurr = mOutputElemPool; // mOutputElemPool = newCurr.reuseAsChild(mCurrElem, prefix, localName, nsURI); // --mPoolSize; // mCurrElem = newCurr; // } else { // mCurrElem = mCurrElem.createChild(prefix, localName, nsURI); // } // 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); // } if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, prefix, localName, nsURI); --mPoolSize; mCurrElem = newCurr; } else { mCurrElem = mCurrElem.createChild(prefix, localName, nsURI); } // It was parent Element where prefix delaraction registered, though prefix would be used at child element. 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); } } } 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. */ public final void copyStartElement(InputElementStack elemStack, AttributeCollector attrCollector) 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) */ AttributeCollector ac = mAttrCollector; 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 = attrCollector.getURI(i); prefix = attrCollector.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(attrCollector.getLocalName(i), attrCollector.getValue(i)); } else { mWriter.writeAttribute(prefix, attrCollector.getLocalName(i), attrCollector.getValue(i)); } } } } /* //////////////////////////////////////////////////// // 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. * * @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 {@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: (String) 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. * * @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 = (String) 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; } }