All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.java.com.ctc.wstx.sw.SimpleOutputElement Maven / Gradle / Ivy

/* Woodstox XML processor
 *
 * Copyright (c) 2005 Tatu Saloranta, [email protected]
 *
 * Licensed under the License specified in 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.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

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

import com.ctc.wstx.util.BijectiveNsMap;
import com.ctc.wstx.util.EmptyIterator;

/**
 * Class that encapsulates information about a specific element in virtual
 * output stack, for writers that do support namespaces, but do NOT
 * do "repairing", that is, expect caller to provide full namespace
 * mapping and writing guidance. It does, however, provide rudimentary
 * URI-to-prefix mappings, for those StAX methods that only take local
 * name and URI arguments.
 *

* One noteworthy feature of the class is that it is designed to allow * "short-term recycling", ie. instances can be reused within context * of a simple document output. While reuse/recycling of such lightweight * object is often useless or even counter productive, here it may * be worth using, due to simplicity of the scheme (basically using * a very simple free-elements linked list). */ public final class SimpleOutputElement implements NamespaceContext { public final static int PREFIX_UNBOUND = 0; public final static int PREFIX_OK = 1; public final static int PREFIX_MISBOUND = 2; final static String sXmlNsPrefix = XMLConstants.XML_NS_PREFIX; final static String sXmlNsURI = XMLConstants.XML_NS_URI; /* //////////////////////////////////////////// // Information about element itself: //////////////////////////////////////////// */ /** * Reference to the parent element, element enclosing this element. * Null for root element. * Non-final only to allow temporary pooling * (on per-writer basis, to keep these short-lived). */ SimpleOutputElement mParent; /** * Prefix that is used for the element. Can not be final, since sometimes * it needs to be dynamically generated and bound after creating the * element instance. */ String mPrefix; /** * Local name of the element. * Non-final only to allow reuse. */ String mLocalName; /** * Namespace of the element, whatever {@link #mPrefix} maps to. * Non-final only to allow reuse. */ String mURI; /* //////////////////////////////////////////// // Namespace binding/mapping information //////////////////////////////////////////// */ /** * Namespace context end application may have supplied, and that * (if given) should be used to augment explicitly defined bindings. */ NamespaceContext mRootNsContext; String mDefaultNsURI; /** * True, if the default namespace URI has been explicitly specified * for this element; false if it was inherited from the parent * element */ boolean mDefaultNsSet; /** * Mapping of namespace prefixes to URIs and back. */ BijectiveNsMap mNsMapping; /** * True, if {@link #mNsMapping} is a shared copy from the parent; * false if a local copy was created (which happens when namespaces * get bound etc). */ boolean mNsMapShared; /* //////////////////////////////////////////// // Attribute information //////////////////////////////////////////// */ /** * Map used to check for duplicate attribute declarations, if * feature is enabled. */ HashMap mAttrMap = null; /* //////////////////////////////////////////// // Life-cycle //////////////////////////////////////////// */ /** * Constructor for the virtual root element */ private SimpleOutputElement() { mParent = null; mPrefix = null; mLocalName = ""; mURI = null; mNsMapping = null; mNsMapShared = false; mDefaultNsURI = ""; mRootNsContext = null; mDefaultNsSet = false; } private SimpleOutputElement(SimpleOutputElement parent, String prefix, String localName, String uri, BijectiveNsMap ns) { mParent = parent; mPrefix = prefix; mLocalName = localName; mURI = uri; mNsMapping = ns; mNsMapShared = (ns != null); mDefaultNsURI = parent.mDefaultNsURI; mRootNsContext = parent.mRootNsContext; mDefaultNsSet = false; } /** * Method called to reuse a pooled instance. * * @return Chained pooled instance that should now be head of the * reuse chain */ private void relink(SimpleOutputElement parent, String prefix, String localName, String uri) { mParent = parent; mPrefix = prefix; mLocalName = localName; mURI = uri; mNsMapping = parent.mNsMapping; mNsMapShared = (mNsMapping != null); mDefaultNsURI = parent.mDefaultNsURI; mRootNsContext = parent.mRootNsContext; mDefaultNsSet = false; } public static SimpleOutputElement createRoot() { return new SimpleOutputElement(); } /** * Simplest factory method, which gets called when a 1-argument * element output method is called. It is, then, assumed to * use the default namespce. */ protected SimpleOutputElement createChild(String localName) { /* At this point we can also discard attribute Map; it is assumed * that when a child element has been opened, no more attributes * can be output. */ mAttrMap = null; return new SimpleOutputElement(this, null, localName, mDefaultNsURI, mNsMapping); } /** * @return New head of the recycle pool */ protected SimpleOutputElement reuseAsChild(SimpleOutputElement parent, String localName) { mAttrMap = null; SimpleOutputElement poolHead = mParent; relink(parent, null, localName, mDefaultNsURI); return poolHead; } protected SimpleOutputElement reuseAsChild(SimpleOutputElement parent, String prefix, String localName, String uri) { mAttrMap = null; SimpleOutputElement poolHead = mParent; relink(parent, prefix, localName, uri); return poolHead; } /** * Full factory method, used for 'normal' namespace qualified output * methods. */ protected SimpleOutputElement createChild(String prefix, String localName, String uri) { /* At this point we can also discard attribute Map; it is assumed * that when a child element has been opened, no more attributes * can be output. */ mAttrMap = null; return new SimpleOutputElement(this, prefix, localName, uri, mNsMapping); } protected void setRootNsContext(NamespaceContext ctxt) { mRootNsContext = ctxt; /* Let's also see if we have an active default ns mapping: * (provided it hasn't yet explicitly been set for this element) */ if (!mDefaultNsSet) { String defURI = ctxt.getNamespaceURI(""); if (defURI != null && defURI.length() > 0) { mDefaultNsURI = defURI; } } } /** * Method called to temporarily link this instance to a pool, to * allow reusing of instances with the same reader. */ protected void addToPool(SimpleOutputElement poolHead) { mParent = poolHead; } /* //////////////////////////////////////////// // Public API, accessors //////////////////////////////////////////// */ public SimpleOutputElement getParent() { return mParent; } public boolean isRoot() { // (Virtual) Root element has no parent... return (mParent == null); } /** * @return String presentation of the fully-qualified name, in * "prefix:localName" format (no URI). Useful for error and * debugging messages. */ public String getNameDesc() { if (mPrefix != null && mPrefix.length() > 0) { return mPrefix + ":" +mLocalName; } if (mLocalName != null && mLocalName.length() > 0) { return mLocalName; } return "#error"; // unexpected case } public String getPrefix() { return mPrefix; } public String getLocalName() { return mLocalName; } public String getNamespaceURI() { return mURI; } public String getDefaultNsUri() { return mDefaultNsURI; } public QName getName() { return new QName(mURI, mLocalName, mPrefix); } /* //////////////////////////////////////////// // Public API, ns binding, checking //////////////////////////////////////////// */ /** * Method similar to {@link #getPrefix}, but one that will not accept * the default namespace, only an explicit one. Usually used when * trying to find a prefix for attributes. */ public String getExplicitPrefix(String uri) { if (mNsMapping != null) { String prefix = mNsMapping.findPrefixByUri(uri); if (prefix != null) { return prefix; } } if (mRootNsContext != null) { String prefix = mRootNsContext.getPrefix(uri); if (prefix != null) { // Hmmh... still can't use the default NS: if (prefix.length() > 0) { return prefix; } // ... should we try to find an explicit one? } } return null; } /** * Method that verifies that passed-in prefix indeed maps to the specified * namespace URI; and depending on how it goes returns a status for * caller. * * @param isElement If true, rules for the default NS are those of elements * (ie. empty prefix can map to non-default namespace); if false, * rules are those of attributes (only non-default prefix can map to * a non-default namespace). * * @return PREFIX_OK, if passed-in prefix matches matched-in namespace URI * in current scope; PREFIX_UNBOUND if it's not bound to anything, * and PREFIX_MISBOUND if it's bound to another URI. * * @throws XMLStreamException True if default (no) prefix is allowed to * match a non-default URI (elements); false if not (attributes) */ public int isPrefixValid(String prefix, String nsURI, boolean isElement) throws XMLStreamException { // Hmmm.... caller shouldn't really pass null. if (nsURI == null) { nsURI = ""; } /* First thing is to see if specified prefix is bound to a namespace; * and if so, verify it matches with data passed in: */ // Checking default namespace? if (prefix == null || prefix.length() == 0) { if (isElement) { // It's fine for elements only if the URI actually matches: if (nsURI == mDefaultNsURI || nsURI.equals(mDefaultNsURI)) { return PREFIX_OK; } } else { /* Attributes never use the default namespace: "no prefix" * can only mean "no namespace" */ if (nsURI.length() == 0) { return PREFIX_OK; } } return PREFIX_MISBOUND; } /* Need to handle 'xml' prefix and its associated * URI; they are always declared by default */ if (prefix.equals(sXmlNsPrefix)) { // Should we thoroughly verify its namespace matches...? // 01-Apr-2005, TSa: Yes, let's always check this if (!nsURI.equals(sXmlNsURI)) { throwOutputError("Namespace prefix '"+sXmlNsPrefix +"' can not be bound to non-default namespace ('"+nsURI+"'); has to be the default '" +sXmlNsURI+"'"); } return PREFIX_OK; } // Nope checking some other namespace String act; if (mNsMapping != null) { act = mNsMapping.findUriByPrefix(prefix); } else { act = null; } if (act == null && mRootNsContext != null) { act = mRootNsContext.getNamespaceURI(prefix); } // Not (yet) bound... if (act == null) { return PREFIX_UNBOUND; } return (act == nsURI || act.equals(nsURI)) ? PREFIX_OK : PREFIX_MISBOUND; } public void checkAttrWrite(String nsURI, String localName, String value) throws XMLStreamException { AttrName an = new AttrName(nsURI, localName); if (mAttrMap == null) { /* 13-Dec-2005, TSa: Should use a more efficient Set/Map value * for this in future -- specifically one that could use * ns/local-name pairs without intermediate objects */ mAttrMap = new HashMap(); mAttrMap.put(an, value); } else { Object old = mAttrMap.put(an, value); if (old != null) { throw new XMLStreamException("Duplicate attribute write for attribute '"+an+"' (previous value '"+old+"', new value '"+value+"')."); } } } /* //////////////////////////////////////////// // Public API, mutators //////////////////////////////////////////// */ public void setDefaultNsUri(String uri) { mDefaultNsURI = uri; mDefaultNsSet = true; } public void setPrefix(String prefix) { mPrefix = prefix; } public String generateMapping(String prefixBase, String uri, int[] seqArr) { // This is mostly cut'n pasted from addPrefix()... if (mNsMapping == null) { // Didn't have a mapping yet? Need to create one... mNsMapping = BijectiveNsMap.createEmpty(); } else if (mNsMapShared) { /* Was shared with parent(s)? Need to create a derivative, to * allow for nesting/scoping of new prefix */ mNsMapping = mNsMapping.createChild(); mNsMapShared = false; } return mNsMapping.addGeneratedMapping(prefixBase, mRootNsContext, uri, seqArr); } public void addPrefix(String prefix, String uri) { if (mNsMapping == null) { // Didn't have a mapping yet? Need to create one... mNsMapping = BijectiveNsMap.createEmpty(); } else if (mNsMapShared) { /* Was shared with parent(s)? Need to create a derivative, to * allow for nesting/scoping of new prefix */ mNsMapping = mNsMapping.createChild(); mNsMapShared = false; } mNsMapping.addMapping(prefix, uri); } /* ////////////////////////////////////////////////// // NamespaceContext implementation ////////////////////////////////////////////////// */ public String getNamespaceURI(String prefix) { if (prefix.length() == 0) { //default NS return mDefaultNsURI; } if (mNsMapping != null) { String uri = mNsMapping.findUriByPrefix(prefix); if (uri != null) { return uri; } } return (mRootNsContext != null) ? mRootNsContext.getNamespaceURI(prefix) : null; } public String getPrefix(String uri) { if (mDefaultNsURI.equals(uri)) { return ""; } if (mNsMapping != null) { String prefix = mNsMapping.findPrefixByUri(uri); if (prefix != null) { return prefix; } } return (mRootNsContext != null) ? mRootNsContext.getPrefix(uri) : null; } public Iterator getPrefixes(String uri) { List l = null; if (mDefaultNsURI.equals(uri)) { l = new ArrayList(); l.add(""); } if (mNsMapping != null) { l = mNsMapping.getPrefixesBoundToUri(uri, l); } // How about the root namespace context? (if any) /* Note: it's quite difficult to properly resolve masking, when * combining these things (not impossible, just tricky); for now * let's do best effort without worrying about masking: */ if (mRootNsContext != null) { Iterator it = mRootNsContext.getPrefixes(uri); while (it.hasNext()) { String prefix = (String) it.next(); if (prefix.length() == 0) { // default NS already checked continue; } // slow check... but what the heck if (l == null) { l = new ArrayList(); } else if (l.contains(prefix)) { // double-defined... continue; } l.add(prefix); } } return (l == null) ? EmptyIterator.getInstance() : l.iterator(); } /* //////////////////////////////////////////// // Internal methods //////////////////////////////////////////// */ private void throwOutputError(String msg) throws XMLStreamException { throw new XMLStreamException(msg); } /* ////////////////////////////////////////////////// // Helper classes: ////////////////////////////////////////////////// */ /** * Simple key class used to represent two-piece (attribute) names; * first part being optional (URI), and second non-optional (local name). */ final static class AttrName implements Comparable { final String mNsURI; final String mLocalName; /** * Let's cache the hash code, since although hash calculation is * fast, hash code is needed a lot as this is always used as a * HashMap/TreeMap key. */ final int mHashCode; public AttrName(String nsURI, String localName) { mNsURI = (nsURI == null) ? "" : nsURI; mLocalName = localName; mHashCode = mNsURI.hashCode() * 31 ^ mLocalName.hashCode(); } public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof AttrName)) { return false; } AttrName other = (AttrName) o; String otherLN = other.mLocalName; // Local names are shorter, more varying: if (otherLN != mLocalName && !otherLN.equals(mLocalName)) { return false; } String otherURI = other.mNsURI; return (otherURI == mNsURI || otherURI.equals(mNsURI)); } public String toString() { if (mNsURI.length() > 0) { return "{"+mNsURI + "} " +mLocalName; } return mLocalName; } public int hashCode() { return mHashCode; } public int compareTo(Object o) { AttrName other = (AttrName) o; // Let's first order by namespace: int result = mNsURI.compareTo(other.mNsURI); if (result == 0) { result = mLocalName.compareTo(other.mLocalName); } return result; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy