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

com.fasterxml.aalto.dom.DOMWriterImpl Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
package com.fasterxml.aalto.dom;

import java.util.*;

import javax.xml.XMLConstants;
import javax.xml.namespace.*;
import javax.xml.stream.*;
import javax.xml.transform.dom.DOMResult;

import org.w3c.dom.*;

import org.codehaus.stax2.ri.EmptyNamespaceContext;
import org.codehaus.stax2.ri.dom.DOMWrappingWriter;

import com.fasterxml.aalto.out.WriterConfig;

/**
 * This is an adapter class that allows building a DOM tree using
 * {@link XMLStreamWriter} interface.
 *

* Note that the implementation is only to be used for use with * javax.xml.transform.dom.DOMResult. *

* Some notes regarding missing/incomplete functionality: *

    *
  • Namespace-repairing mode not implemented *
  • *
  • Validation functionality not implemented *
  • *
* @author Tatu Saloranta */ public final class DOMWriterImpl extends DOMWrappingWriter { /* //////////////////////////////////////////////////// // Configuration //////////////////////////////////////////////////// */ protected final WriterConfig _config; /* //////////////////////////////////////////////////// // State //////////////////////////////////////////////////// */ /** * This element is the current context element, under which * all other nodes are added, until matching end element * is output. Null outside of the main element tree. *

* Note: explicit empty element (written using * writeEmptyElement) will never become * current element. */ protected DOMOutputElement _currElem; /** * This element is non-null right after a call to * either writeStartElement and * writeEmptyElement, and can be used to * add attributes and namespace declarations. *

* Note: while this is often the same as {@link #_currElem}, * it's not always. Specifically, an empty element (written * explicitly using writeEmptyElement) will * become open element but NOT current element. Conversely, * regular elements will remain current element when * non elements are written (text, comments, PI), but * not the open element. */ protected DOMOutputElement _openElement; /** * for NsRepairing mode */ protected int[] _autoNsSeq; protected String _suggestedDefNs = null; protected String _automaticNsPrefix; /** * 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 _suggestedPrefixes = null; /* //////////////////////////////////////////////////// // Life-cycle //////////////////////////////////////////////////// */ private DOMWriterImpl(WriterConfig cfg, Node treeRoot) throws XMLStreamException { super(treeRoot, true, cfg.willRepairNamespaces()); _config = cfg; _autoNsSeq = null; _automaticNsPrefix = cfg.getAutomaticNsPrefix(); /* Ok; we need a document node; or an element node; or a document * fragment node. */ switch (treeRoot.getNodeType()) { case Node.DOCUMENT_NODE: case Node.DOCUMENT_FRAGMENT_NODE: // both are ok, but no current element _currElem = DOMOutputElement.createRoot(); _openElement = null; break; case Node.ELEMENT_NODE: // can make sub-tree... ok { // still need a virtual root node as parent DOMOutputElement root = DOMOutputElement.createRoot(); Element elem = (Element) treeRoot; _openElement = _currElem = root.createChild(elem); } break; default: // other Nodes not usable throw new XMLStreamException("Can not create an XMLStreamWriter for a DOM node of type "+treeRoot.getClass()); } } public static DOMWriterImpl createFrom(WriterConfig cfg, DOMResult dst) throws XMLStreamException { Node rootNode = dst.getNode(); return new DOMWriterImpl(cfg, rootNode); } /* //////////////////////////////////////////////////// // XMLStreamWriter API (Stax 1.0) //////////////////////////////////////////////////// */ //public void close() { } //public void flush() { } @Override public NamespaceContext getNamespaceContext() { if (!mNsAware) { return EmptyNamespaceContext.getInstance(); } return _currElem; } @Override public String getPrefix(String uri) { if (!mNsAware) { return null; } if (mNsContext != null) { String prefix = mNsContext.getPrefix(uri); if (prefix != null) { return prefix; } } return _currElem.getPrefix(uri); } @Override public Object getProperty(String name) { /* Here we don't want to throw an exception, should the property * not be supported; thus passing false as second arg */ return _config.getProperty(name, false); } @Override public void setDefaultNamespace(String uri) { _suggestedDefNs = (uri == null || uri.length() == 0) ? null : uri; } //public void setNamespaceContext(NamespaceContext context) @Override public void setPrefix(String prefix, String uri) throws XMLStreamException { if (prefix == null) { throw new NullPointerException("Can not pass null 'prefix' value"); } // Are we actually trying to set the default namespace? if (prefix.length() == 0) { setDefaultNamespace(uri); return; } if (uri == null) { throw new NullPointerException("Can not pass null 'uri' value"); } /* Let's verify that xml/xmlns are never (mis)declared; as * mandated by XML NS specification */ { if (prefix.equals("xml")) { if (!uri.equals(XMLConstants.XML_NS_URI)) { throwOutputError("Trying to redeclare prefix 'xml' from its default URI '"+XMLConstants.XML_NS_URI+"' to \""+uri+"\""); } } else if (prefix.equals("xmlns")) { // prefix "xmlns" if (!uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) { throwOutputError("Trying to declare prefix 'xmlns' (illegal as per NS 1.1 #4)"); } // At any rate; we are NOT to output it return; } else { // Neither of prefixes.. but how about URIs? if (uri.equals(XMLConstants.XML_NS_URI)) { throwOutputError("Trying to bind URI '" +XMLConstants.XML_NS_URI+" to prefix \""+prefix+"\" (can only bind to 'xml')"); } else if (uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) { throwOutputError("Trying to bind URI '" +XMLConstants.XMLNS_ATTRIBUTE_NS_URI+" to prefix \""+prefix+"\" (can not be explicitly bound)"); } } } if (_suggestedPrefixes == null) { _suggestedPrefixes = new HashMap(16); } _suggestedPrefixes.put(uri, prefix); } @Override public void writeAttribute(String localName, String value) throws XMLStreamException { outputAttribute(null, null, localName, value); } @Override public void writeAttribute(String nsURI, String localName, String value) throws XMLStreamException { outputAttribute(nsURI, null, localName, value); } @Override public void writeAttribute(String prefix, String nsURI, String localName, String value) throws XMLStreamException { outputAttribute(nsURI, prefix, localName, value); } //public void writeCData(String data) //public void writeCharacters(char[] text, int start, int len) //public void writeCharacters(String text) //public void writeComment(String data) @Override public void writeDefaultNamespace(String nsURI) { if (_openElement == null) { throw new IllegalStateException("No currently open START_ELEMENT, cannot write attribute"); } setDefaultNamespace(nsURI); _openElement.addAttribute(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns", nsURI); } //public void writeDTD(String dtd) @Override public void writeEmptyElement(String localName) throws XMLStreamException { writeEmptyElement(null, localName); } @Override public void writeEmptyElement(String nsURI, String localName) throws XMLStreamException { // First things first: must /* Note: can not just call writeStartElement(), since this * element will only become the open elem, but not a parent elem */ createStartElem(nsURI, null, localName, true); } @Override public void writeEmptyElement(String prefix, String localName, String nsURI) throws XMLStreamException { if (prefix == null) { // passing null would mean "dont care", if repairing prefix = ""; } createStartElem(nsURI, prefix, localName, true); } @Override public void writeEndDocument() { _currElem = _openElement = null; } @Override public void writeEndElement() { // Simple, just need to traverse up... if we can if (_currElem == null || _currElem.isRoot()) { throw new IllegalStateException("No open start element to close"); } _openElement = null; // just in case it was open _currElem = _currElem.getParent(); } @Override public void writeNamespace(String prefix, String nsURI) throws XMLStreamException { if (prefix == null || prefix.length() == 0) { writeDefaultNamespace(nsURI); return; } if (!mNsAware) { throwOutputError("Can not write namespaces with non-namespace writer."); } outputAttribute(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns", prefix, nsURI); _currElem.addPrefix(prefix, nsURI); } //public void writeProcessingInstruction(String target) //public void writeProcessingInstruction(String target, String data) //public void writeStartDocument() //public void writeStartDocument(String version) //public void writeStartDocument(String encoding, String version) @Override public void writeStartElement(String localName) throws XMLStreamException { writeStartElement(null, localName); } @Override public void writeStartElement(String nsURI, String localName) throws XMLStreamException { createStartElem(nsURI, null, localName, false); } @Override public void writeStartElement(String prefix, String localName, String nsURI) throws XMLStreamException { createStartElem(nsURI, prefix, localName, false); } /* //////////////////////////////////////////////////// // XMLStreamWriter2 API (Stax2 v2.0) //////////////////////////////////////////////////// */ @Override public boolean isPropertySupported(String name) { // !!! TBI: not all these properties are really supported return _config.isPropertySupported(name); } @Override public boolean setProperty(String name, Object value) { /* Note: can not call local method, since it'll return false for * recognized but non-mutable properties */ return _config.setProperty(name, value); } //public XMLValidator validateAgainst(XMLValidationSchema schema) //public XMLValidator stopValidatingAgainst(XMLValidationSchema schema) //public XMLValidator stopValidatingAgainst(XMLValidator validator) //public ValidationProblemHandler setValidationProblemHandler(ValidationProblemHandler h) //public XMLStreamLocation2 getLocation() //public String getEncoding() { //public void writeCData(char[] text, int start, int len) @Override public void writeDTD(String rootName, String systemId, String publicId, String internalSubset) throws XMLStreamException { /* Alas: although we can create a DocumentType object, there * doesn't seem to be a way to attach it in DOM-2! */ if (_currElem != null) { throw new IllegalStateException("Operation only allowed to the document before adding root element"); } reportUnsupported("writeDTD()"); } //public void writeFullEndElement() throws XMLStreamException //public void writeSpace(char[] text, int start, int len) //public void writeSpace(String text) //public void writeStartDocument(String version, String encoding, boolean standAlone) /* //////////////////////////////////////////// // Impls of abstract methods from base class //////////////////////////////////////////// */ @Override protected void appendLeaf(Node n) throws IllegalStateException { _currElem.appendNode(n); _openElement = null; } /* /////////////////////////////// // Internal methods /////////////////////////////// */ /* Note: copied from regular RepairingNsStreamWriter#writeStartOrEmpty * (and its non-repairing counterpart). */ /** * Method called by all start element write methods. * * @param nsURI Namespace URI to use: null and empty String denote 'no namespace' */ protected void createStartElem(String nsURI, String prefix, String localName, boolean isEmpty) throws XMLStreamException { DOMOutputElement elem; if (!mNsAware) { if(nsURI != null && nsURI.length() > 0) { throwOutputError("Can not specify non-empty uri/prefix in non-namespace mode"); } elem = _currElem.createAndAttachChild(mDocument.createElement(localName)); } else { if (mNsRepairing) { String actPrefix = validateElemPrefix(prefix, nsURI, _currElem); if (actPrefix != null) { // fine, an existing binding we can use: if (actPrefix.length() != 0) { elem = _currElem.createAndAttachChild(mDocument.createElementNS(nsURI, actPrefix+":"+localName)); } else { elem = _currElem.createAndAttachChild(mDocument.createElementNS(nsURI, localName)); } } else { // nah, need to create a new binding... /* Need to ensure that we'll pass "" as prefix, not null, * so it is understood as "I want to use the default NS", * not as "whatever prefix, I don't care" */ if (prefix == null) { prefix = ""; } actPrefix = generateElemPrefix(prefix, nsURI, _currElem); boolean hasPrefix = (actPrefix.length() != 0); if (hasPrefix) { localName = actPrefix + ":" + localName; } elem = _currElem.createAndAttachChild(mDocument.createElementNS(nsURI, localName)); /* Hmmh. writeNamespace method requires open element * to be defined. So we'll need to set it first * (will be set again at a later point -- would be * good to refactor this method into separate * sub-classes or so) */ _openElement = elem; // Need to add new ns declaration as well if (hasPrefix) { writeNamespace(actPrefix, nsURI); elem.addPrefix(actPrefix, nsURI); } else { writeDefaultNamespace(nsURI); elem.setDefaultNsUri(nsURI); } } } else { /* Non-repairing; if non-null prefix (including "" to * indicate "no prefix") passed, use as is, otherwise * try to locate the prefix if got namespace. */ if (prefix == null && nsURI != null && nsURI.length() > 0) { prefix = (_suggestedPrefixes == null) ? null : (String) _suggestedPrefixes.get(nsURI); if (prefix == null) { throwOutputError("Can not find prefix for namespace \""+nsURI+"\""); } } if (prefix != null && prefix.length() != 0) { localName = prefix + ":" +localName; } elem = _currElem.createAndAttachChild(mDocument.createElementNS(nsURI, localName)); } } /* Got the element; need to make it the open element, and * if it's not an (explicit) empty element, current element as well */ _openElement = elem; if (!isEmpty) { _currElem = elem; } } protected void outputAttribute(String nsURI, String prefix, String localName, String value) throws XMLStreamException { if (_openElement == null) { throw new IllegalStateException("No currently open START_ELEMENT, cannot write attribute"); } if (mNsAware) { if (mNsRepairing) { prefix = findOrCreateAttrPrefix(prefix, nsURI, _openElement); } if (prefix != null && prefix.length() > 0) { localName = prefix + ":" + localName; } _openElement.addAttribute(nsURI, localName, value); } else { // non-ns, simple if (prefix != null && prefix.length() > 0) { localName = prefix + ":" + localName; } _openElement.addAttribute(localName, value); } } private final String validateElemPrefix(String prefix, String nsURI, DOMOutputElement 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 == DOMOutputElement.PREFIX_OK) { return prefix; } return null; } /* //////////////////////////////////////////////////// // 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, DOMOutputElement 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 _currElem.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, DOMOutputElement 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 (_suggestedDefNs != null && _suggestedDefNs.equals(nsURI)) { suggPrefix = ""; } else { suggPrefix = (_suggestedPrefixes == null) ? null: (String) _suggestedPrefixes.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 (_autoNsSeq == null) { _autoNsSeq = new int[1]; _autoNsSeq[0] = 1; } suggPrefix = elem.generateMapping(_automaticNsPrefix, nsURI, _autoNsSeq); } } } // 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, DOMOutputElement 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 == OutputElementBase.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 == OutputElementBase.PREFIX_UNBOUND) { elem.addPrefix(suggPrefix, nsURI); writeNamespace(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 (_suggestedPrefixes != null) { prefix = (String) _suggestedPrefixes.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 (_autoNsSeq == null) { _autoNsSeq = new int[1]; _autoNsSeq[0] = 1; } prefix = _currElem.generateMapping(_automaticNsPrefix, nsURI, _autoNsSeq); } // Ok; so far so good: let's now bind and output the namespace: elem.addPrefix(prefix, nsURI); writeNamespace(prefix, nsURI); return prefix; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy