blob: 854a8bcb716894080a5c8b1774c04cd5ecd4b82d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.ode.utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.om.Name11Checker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.ode.utils.sax.LoggingErrorHandler;
import org.apache.xerces.dom.DOMOutputImpl;
import org.apache.xerces.impl.Constants;
import org.apache.xml.serialize.DOMSerializerImpl;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Utility class for dealing with the Document Object Model (DOM).
*/
public class DOMUtils {
private static Logger __log = LoggerFactory.getLogger(DOMUtils.class);
/** The namespaceURI represented by the prefix <code>xmlns</code>. */
public static final String NS_URI_XMLNS = "http://www.w3.org/2000/xmlns/";
private static ThreadLocal<Transformer> __txers = new ThreadLocal();
private static ThreadLocal<DocumentBuilder> __builders = new ThreadLocal();
private static TransformerFactory _transformerFactory = TransformerFactory.newInstance();
private static DocumentBuilderFactory __documentBuilderFactory ;
static {
initDocumentBuilderFactory();
}
/**
* Initialize the document-builder factory.
*/
private static void initDocumentBuilderFactory() {
DocumentBuilderFactory f = XMLParserUtils.getDocumentBuilderFactory();
f.setNamespaceAware(true);
__documentBuilderFactory = f;
}
/**
* Returns the value of an attribute of an element. Returns null if the
* attribute is not found (whereas Element.getAttribute returns "" if an
* attrib is not found).
*
* @param el Element whose attrib is looked for
* @param attrName name of attribute to look for
*
* @return the attribute value
*/
static public String getAttribute(Element el, String attrName) {
String sRet = null;
Attr attr = el.getAttributeNode(attrName);
if (attr != null) {
sRet = attr.getValue();
}
return sRet;
}
/**
* @deprecated relies on XMLSerializer which is a deprecated Xerces class, use domToString instead
*/
static public String prettyPrint(Element e) throws IOException {
OutputFormat format = new OutputFormat(e.getOwnerDocument());
format.setLineWidth(65);
format.setIndenting(true);
format.setIndent(2);
StringWriter out = new StringWriter();
XMLSerializer serializer = new XMLSerializer(out, format);
serializer.serialize(e);
return out.toString();
}
public static Element getFirstChildElement(Node node) {
NodeList l = node.getChildNodes();
for (int i = 0; i < l.getLength(); i++) {
if (l.item(i) instanceof Element) return (Element) l.item(i);
}
return null;
}
/**
* Returns the value of an attribute of an element. Returns null if the
* attribute is not found (whereas Element.getAttributeNS returns "" if an
* attrib is not found).
*
* @param el Element whose attrib is looked for
* @param namespaceURI namespace URI of attribute to look for
* @param localPart local part of attribute to look for
*
* @return the attribute value
*/
static public String getAttributeNS(Element el, String namespaceURI,
String localPart) {
String sRet = null;
Attr attr = el.getAttributeNodeNS(namespaceURI, localPart);
if (attr != null) {
sRet = attr.getValue();
}
return sRet;
}
/**
* Concat all the text and cdata node children of this elem and return the
* resulting text.
*
* @param parentEl the element whose cdata/text node values are to be
* combined.
*
* @return the concatanated string.
*/
static public String getChildCharacterData(Element parentEl) {
if (parentEl == null) { return null; }
Node tempNode = parentEl.getFirstChild();
StringBuffer strBuf = new StringBuffer();
CharacterData charData;
while (tempNode != null) {
switch (tempNode.getNodeType()) {
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
charData = (CharacterData) tempNode;
strBuf.append(charData.getData());
break;
}
tempNode = tempNode.getNextSibling();
}
return strBuf.toString();
}
/**
* DOCUMENTME
*
* @param el DOCUMENTME
* @param id DOCUMENTME
*
* @return DOCUMENTME
*/
public static Element getElementByID(Element el, String id) {
if (el == null) { return null; }
String thisId = el.getAttribute("id");
if (id.equals(thisId)) { return el; }
NodeList list = el.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
if (node instanceof Element) {
Element ret = getElementByID((Element) node, id);
if (ret != null) { return ret; }
}
}
return null;
}
/**
* Return the first child element of the given element. Null if no children
* are found.
*
* @param elem Element whose child is to be returned
*
* @return the first child element.
*/
public static Element getFirstChildElement(Element elem) {
return (Element) findChildByType(elem, Node.ELEMENT_NODE);
}
/**
* Given a prefix and a node, return the namespace URI that the prefix has
* been associated with. This method is useful in resolving the namespace
* URI of attribute values which are being interpreted as QNames. If prefix
* is null, this method will return the default namespace.
*
* @param context the starting node (looks up recursively from here)
* @param prefix the prefix to find an xmlns:prefix=uri for
*
* @return the namespace URI or null if not found
*/
public static String getNamespaceURIFromPrefix(Node context, String prefix) {
short nodeType = context.getNodeType();
Node tempNode = null;
switch (nodeType) {
case Node.ATTRIBUTE_NODE: {
tempNode = ((Attr) context).getOwnerElement();
break;
}
case Node.ELEMENT_NODE: {
tempNode = context;
break;
}
default: {
tempNode = context.getParentNode();
break;
}
}
while ((tempNode != null) && (tempNode.getNodeType() == Node.ELEMENT_NODE)) {
Element tempEl = (Element) tempNode;
String namespaceURI = (prefix == null) ? getAttribute(tempEl, "xmlns")
: getAttributeNS(tempEl, NS_URI_XMLNS, prefix);
if (namespaceURI != null) {
return namespaceURI;
}
tempNode = tempEl.getParentNode();
}
return null;
}
/**
* Return the next sibling element of the given element. Null if no more
* sibling elements are found.
*
* @param elem Element whose sibling element is to be returned
*
* @return the next sibling element.
*/
public static Element getNextSiblingElement(Element elem) {
for (Node n = elem.getNextSibling(); n != null; n = n.getNextSibling()) {
if (n.getNodeType() == Node.ELEMENT_NODE) { return (Element) n; }
}
return null;
}
/**
* DOCUMENTME
*
* @param el DOCUMENTME
* @param attrName DOCUMENTME
*
* @return DOCUMENTME
*
* @throws IllegalArgumentException DOCUMENTME
*/
public static QName getQualifiedAttributeValue(Element el, String attrName)
throws IllegalArgumentException {
String attrValue = DOMUtils.getAttribute(el, attrName);
if (attrValue != null) {
int index = attrValue.indexOf(':');
String attrValuePrefix = (index != -1) ? attrValue.substring(0, index)
: null;
String attrValueLocalPart = attrValue.substring(index + 1);
String attrValueNamespaceURI = DOMUtils.getNamespaceURIFromPrefix(el,
attrValuePrefix);
if (attrValueNamespaceURI != null) {
return new QName(attrValueNamespaceURI, attrValueLocalPart);
}
throw new IllegalArgumentException("Unable to determine "
+ "namespace of '"
+ ((attrValuePrefix != null) ? (attrValuePrefix + ":") : "")
+ attrValueLocalPart + "'.");
}
return null;
}
/**
* Count number of children of a certain type of the given element.
*
* @param elem the element whose kids are to be counted
* @param nodeType DOCUMENTME
*
* @return the number of matching kids.
*/
public static int countKids(Element elem, short nodeType) {
int nkids = 0;
for (Node n = elem.getFirstChild(); n != null; n = n.getNextSibling()) {
if (n.getNodeType() == nodeType) {
nkids++;
}
}
return nkids;
}
/**
* This method traverses the DOM and grabs namespace declarations
* on parent elements with the intent of preserving them for children. <em>Note
* that the DOM level 3 document method {@link Element#getAttribute(java.lang.String)}
* is not desirable in this case, as it does not respect namespace prefix
* bindings that may affect attribute values. (Namespaces in DOM are
* uncategorically a mess, especially in the context of XML Schema.)</em>
* @param el the starting element
* @return a {@link Map} containing prefix bindings.
*/
public static Map<String, String> getParentNamespaces(Element el) {
HashMap<String,String> pref = new HashMap<String,String>();
Map<String,String> mine = getMyNamespaces(el);
Node n = el.getParentNode();
while (n != null && n.getNodeType() != Node.DOCUMENT_NODE) {
if (n instanceof Element) {
Element l = (Element) n;
NamedNodeMap nnm = l.getAttributes();
int len = nnm.getLength();
for (int i = 0; i < len; ++i) {
Attr a = (Attr) nnm.item(i);
if (isNSAttribute(a)) {
String key = getNSPrefixFromNSAttr(a);
String uri = a.getValue();
// prefer prefix bindings that are lower down in the tree.
if (pref.containsKey(key) || mine.containsKey(key)) continue;
pref.put(key, uri);
}
}
}
n = n.getParentNode();
}
return pref;
}
/**
* Construct a {@link NSContext} instance for the supplied element.
* @param el the <code>Element</code> to gather the namespace context for
* @return the <code>NSContext</code>
*/
public static NSContext getMyNSContext(Element el) {
NSContext ns = new NSContext();
ns.register(getParentNamespaces(el));
ns.register(getMyNamespaces(el));
return ns;
}
public static Map<String,String> getMyNamespaces(Element el) {
HashMap<String,String> mine = new HashMap<String,String>();
NamedNodeMap nnm = el.getAttributes();
int len = nnm.getLength();
for (int i=0; i < len; ++i) {
Attr a = (Attr) nnm.item(i);
if (isNSAttribute(a)) {
mine.put(getNSPrefixFromNSAttr(a),a.getValue());
}
}
return mine;
}
/**
* Test whether an attribute contains a namespace declaration.
* @param a an {@link Attr} to test.
* @return <code>true</code> if the {@link Attr} is a namespace declaration
*/
public static boolean isNSAttribute(Attr a) {
assert a != null;
String s = a.getNamespaceURI();
return (s != null && s.equals(NS_URI_XMLNS));
}
/**
* Fetch the non-null namespace prefix from a {@link Attr} that declares
* a namespace. (The DOM APIs will return <code>null</code> for a non-prefixed
* declaration.
* @param a the {@link Attr} with the declaration (must be non-<code>null</code).
* @return the namespace prefix or <code>&quot;&quot;</code> if none was
* declared, e.g., <code>xmlns=&quot;foo&quot;</code>.
*/
public static String getNSPrefixFromNSAttr(Attr a) {
assert a != null;
assert isNSAttribute(a);
if (a.getPrefix() == null) {
return "";
}
return a.getName().substring(a.getPrefix().length()+1);
}
/**
* Convert a DOM node to a stringified XML representation.
*/
static public String domToString(Node node) {
if (node == null) {
throw new IllegalArgumentException("Cannot stringify null Node!");
}
String value = null;
short nodeType = node.getNodeType();
if (nodeType == Node.ELEMENT_NODE || nodeType == Node.DOCUMENT_NODE || nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
// serializer doesn't handle Node type well, only Element
DOMSerializerImpl ser = new DOMSerializerImpl();
ser.setParameter(Constants.DOM_NAMESPACES, Boolean.TRUE);
ser.setParameter(Constants.DOM_WELLFORMED, Boolean.FALSE );
ser.setParameter(Constants.DOM_VALIDATE, Boolean.FALSE);
// create a proper XML encoding header based on the input document;
// default to UTF-8 if the parent document's encoding is not accessible
String usedEncoding = "UTF-8";
Document parent = node.getOwnerDocument();
if (parent != null) {
String parentEncoding = parent.getXmlEncoding();
if (parentEncoding != null) {
usedEncoding = parentEncoding;
}
}
// the receiver of the DOM
DOMOutputImpl out = new DOMOutputImpl();
out.setEncoding(usedEncoding);
// we write into a String
StringWriter writer = new StringWriter(4096);
out.setCharacterStream(writer);
// out, ye characters!
ser.write(node, out);
writer.flush();
// finally get the String
value = writer.toString();
} else {
value = node.getNodeValue();
}
return value;
}
public static void serialize(Element elmt, OutputStream ostr) {
String usedEncoding = "UTF-8";
Document parent = elmt.getOwnerDocument();
if (parent != null) {
String parentEncoding = parent.getXmlEncoding();
if (parentEncoding != null) {
usedEncoding = parentEncoding;
}
}
DOMOutputImpl out = new DOMOutputImpl();
out.setEncoding(usedEncoding);
DOMSerializerImpl ser = new DOMSerializerImpl();
out.setByteStream(ostr);
ser.write(elmt, out);
}
/**
* Convert a DOM node to a stringified XML representation.
*/
static public String domToStringLevel2(Node node) {
if (node == null) {
throw new IllegalArgumentException("Cannot stringify null Node!");
}
String value = null;
short nodeType = node.getNodeType();
if (nodeType == Node.ELEMENT_NODE || nodeType == Node.DOCUMENT_NODE) {
// serializer doesn't handle Node type well, only Element
DOMSerializerImpl ser = new DOMSerializerImpl();
ser.setParameter(Constants.DOM_NAMESPACES, Boolean.TRUE);
ser.setParameter(Constants.DOM_WELLFORMED, Boolean.FALSE );
ser.setParameter(Constants.DOM_VALIDATE, Boolean.FALSE);
// the receiver of the DOM
DOMOutputImpl out = new DOMOutputImpl();
out.setEncoding("UTF-8");
// we write into a String
StringWriter writer = new StringWriter(4096);
out.setCharacterStream(writer);
// out, ye characters!
ser.write(node, out);
writer.flush();
// finally get the String
value = writer.toString();
} else {
value = node.getNodeValue();
}
return value;
}
/**
* Return the first child element of the given element which has the given
* attribute with the given value.
*
* @param elem the element whose children are to be searched
* @param attrName the attrib that must be present
* @param attrValue the desired value of the attribute
*
* @return the first matching child element.
*/
public static Element findChildElementWithAttribute(Element elem,
String attrName, String attrValue) {
for (Node n = elem.getFirstChild(); n != null; n = n.getNextSibling()) {
if (n.getNodeType() == Node.ELEMENT_NODE) {
if (attrValue.equals(DOMUtils.getAttribute((Element) n, attrName))) { return (Element) n; }
}
}
return null;
}
/**
* Parse a String into a DOM.
*
* @param s DOCUMENTME
*
* @return DOCUMENTME
*
* @throws SAXException DOCUMENTME
* @throws IOException DOCUMENTME
*/
static public Element stringToDOM(String s) throws SAXException, IOException {
return parse(new InputSource(new StringReader(s))).getDocumentElement();
}
/**
* Perform a naive check to see if a document is a WSDL document
* based on the root element name and namespace URI.
* @param d the {@link Document} to check
* @return <code>true</code> if the root element appears correct
*/
public static boolean isWsdlDocument(Document d) {
Element e = d.getDocumentElement();
String uri = e.getNamespaceURI();
String localName = e.getLocalName();
if (uri == null || localName == null) { return false; }
return uri.equals(WSDL_NS) && localName.equals(WSDL_ROOT_ELEMENT);
}
/**
* Perform a naive check to see if a document is an XML schema document
* based on the root element name and namespace URI.
* @param d the {@link Document} to check
* @return <code>true</code> if the root element appears correct
*/
public static boolean isXmlSchemaDocument(Document d) {
Element e = d.getDocumentElement();
String uri = e.getNamespaceURI();
String localName = e.getLocalName();
if (uri == null || localName == null) { return false; }
return uri.equals(XSD_NS) && localName.equals(XSD_ROOT_ELEMENT);
}
public static final String WSDL_NS = "http://schemas.xmlsoap.org/wsdl/";
public static final String WSDL_ROOT_ELEMENT = "definitions";
public static final String XSD_NS = "http://www.w3.org/2001/XMLSchema";
public static final String XSD_ROOT_ELEMENT = "schema";
/**
* @param el
*/
public static void pancakeNamespaces(Element el) {
Map ns = getParentNamespaces(el);
Document d = el.getOwnerDocument();
assert d != null;
Iterator it = ns.keySet().iterator();
while (it.hasNext()) {
String key = (String) it.next();
String uri = (String) ns.get(key);
Attr a = d.createAttributeNS(NS_URI_XMLNS,
(key.length() != 0)?("xmlns:" + key):("xmlns"));
a.setValue(uri);
el.setAttributeNodeNS(a);
}
}
public static Document newDocument() {
DocumentBuilder db = getBuilder();
return db.newDocument();
}
/**
* Parse an XML stream using the pooled document builder.
* @param inputStream input stream
* @return parsed XML document
*/
public static Document parse(InputStream inputStream) throws SAXException, IOException {
return parse(new InputSource(inputStream));
}
/**
* Parse an XML document located using an {@link InputSource} using the
* pooled document builder.
*/
public static Document parse(InputSource inputSource) throws SAXException,IOException{
DocumentBuilder db = getBuilder();
return db.parse(inputSource);
}
/**
* Parse an XML document located using an {@link InputSource} using the
* pooled document builder.
*/
public static Document sourceToDOM(Source inputSource) throws IOException {
try {
/*
// Requires JDK 1.6+
if (inputSource instanceof StAXSource) {
StAXSource stax = (StAXSource) inputSource;
//if (stax.getXMLEventReader() != null || sax.getXMLStreamReader() != null) {
if (sax.getXMLStreamReader() != null) {
return parse(stax.getXMLStreamReader());
}
}
*/
if (inputSource instanceof SAXSource) {
InputSource sax = ((SAXSource) inputSource).getInputSource();
if (sax.getCharacterStream() != null || sax.getByteStream() != null) {
return parse( ((SAXSource) inputSource).getInputSource() );
}
}
if (inputSource instanceof DOMSource) {
Node node = ((DOMSource) inputSource).getNode();
if (node != null) {
return toDOMDocument(node);
}
}
if (inputSource instanceof StreamSource) {
StreamSource stream = (StreamSource) inputSource;
if (stream.getReader() != null || stream.getInputStream() != null) {
return toDocumentFromStream( (StreamSource) inputSource);
}
}
DOMResult domresult = new DOMResult(newDocument());
Transformer txer = getTransformer();
txer.transform(inputSource, domresult);
return (Document) domresult.getNode();
} catch (SAXException e) {
throwIOException(e);
} catch (TransformerException e) {
throwIOException(e);
}
throw new IllegalArgumentException("Cannot parse XML source: " + inputSource.getClass());
}
/**
* Check that an element is empty, i.e., it contains no non-whitespace text or
* elements as children.
* @param el the element
* @return <code>true</code> if the element is empty, <code>false</code> if not.
*/
public static boolean isEmptyElement(Element el) {
NodeList nl = el.getChildNodes();
int len = nl.getLength();
for (int i=0; i < len; ++i) {
switch (nl.item(i).getNodeType()) {
case Node.CDATA_SECTION_NODE:
case Node.TEXT_NODE:
String s = nl.item(i).getNodeValue();
if (s != null && s.trim().length() > 0) {
return false;
}
break;
case Node.ELEMENT_NODE:
return false;
}
}
return true;
}
public static QName getNodeQName(Node el) {
String localName = el.getLocalName();
String namespaceUri = el.getNamespaceURI();
if (localName == null) {
String nodeName = el.getNodeName();
int colonIndex = nodeName.indexOf(":");
if (colonIndex > 0) {
localName = nodeName.substring(0, colonIndex);
namespaceUri = nodeName.substring(colonIndex + 1);
} else {
localName = nodeName;
namespaceUri = null;
}
}
return new QName(namespaceUri, localName);
}
public static QName getNodeQName(String qualifiedName) {
int index = qualifiedName.indexOf(":");
if (index >= 0) {
return new QName(qualifiedName.substring(0, index), qualifiedName.substring(index + 1));
} else {
return new QName(qualifiedName);
}
}
/**
* Remove the child nodes under another node.
* @param target the <code>Node</code> to remove the children from.
*/
public static void removeChildren(Node target) {
while (target.hasChildNodes()) {
target.removeChild(target.getFirstChild());
}
}
/**
* Drop the attributes from an element, except possibly an <code>xmlns</code>
* attribute that declares its namespace.
* @param target the element whose attributes will be removed.
* @param flag preserve namespace declaration
*/
public static void removeAttributes(Element target, boolean flag) {
if (!target.hasAttributes()) {
return;
}
String prefix = target.getPrefix();
NamedNodeMap nnm = target.getAttributes();
Attr toPutBack = null;
if (flag) {
if (prefix== null) {
toPutBack = target.getAttributeNodeNS(NS_URI_XMLNS,"xmlns");
} else {
toPutBack = target.getAttributeNodeNS(NS_URI_XMLNS,"xmlns:" + prefix);
}
}
while(nnm.getLength() != 0) {
target.removeAttributeNode((Attr) nnm.item(0));
}
if (toPutBack != null) {
target.setAttributeNodeNS(toPutBack);
}
}
public static Element findChildByName(Element parent, QName name) {
return findChildByName(parent, name, false);
}
public static Element findChildByName(Element parent, QName name, boolean recurse) {
if (parent == null)
throw new IllegalArgumentException("null parent");
if (name == null)
throw new IllegalArgumentException("null name");
NodeList nl = parent.getChildNodes();
for (int i = 0; i < nl.getLength(); ++i) {
Node c = nl.item(i);
if(c.getNodeType() != Node.ELEMENT_NODE)
continue;
// For a reason that I can't fathom, when using in-mem DAO we actually get elements with
// no localname.
String nodeName = c.getLocalName() != null ? c.getLocalName() : c.getNodeName();
if (new QName(c.getNamespaceURI(),nodeName).equals(name))
return (Element) c;
}
if(recurse){
NodeList cnl = parent.getChildNodes();
for (int i = 0; i < cnl.getLength(); ++i) {
Node c = cnl.item(i);
if(c.getNodeType() != Node.ELEMENT_NODE)
continue;
Element result = findChildByName((Element)c, name, recurse);
if(result != null)
return result;
}
}
return null;
}
public static Node findChildByType(Element elem, int type) {
if (elem == null)
throw new NullPointerException("elem parameter must not be null!");
for (Node n = elem.getFirstChild(); n != null; n = n.getNextSibling()) {
if (n.getNodeType() == type) {
return n;
}
}
return null;
}
public static String getTextContent(Node node) {
for (int m = 0; m < node.getChildNodes().getLength(); m++) {
Node child = node.getChildNodes().item(m);
if (child.getNodeType() == Node.TEXT_NODE) {
String childText = child.getNodeValue().trim();
if (childText.length() > 0) return childText;
}
}
return null;
}
public static Element getElementContent(Node node) {
for (int m = 0; m < node.getChildNodes().getLength(); m++) {
Node child = node.getChildNodes().item(m);
if (child.getNodeType() == Node.ELEMENT_NODE) return (Element) child;
}
return null;
}
public static void injectNamespaces(Element domElement, NSContext nscontext) {
for (String uri : nscontext.getUriSet()) {
String prefix = nscontext.getPrefix(uri);
if (prefix == null || "".equals(prefix))
domElement.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns", uri);
else
domElement.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns:"+ prefix, uri);
}
}
/**
* Adds namespaces including all prefixes.
* This is needed for correct handling of xsi:type attributes.
* @param domElement An element wi which the namespace attributes should be added.
* @param nscontext A namespace context.
* @author k.petrauskas
*/
public static void injectNamespacesWithAllPrefixes(Element domElement, NSContext nscontext) {
if (__log.isDebugEnabled())
__log.debug("injectNamespacesWithAllPrefixes: element=" + domToString(domElement) + " nscontext=" + nscontext);
for (Map.Entry<String, String> entry : nscontext.toMap().entrySet()) {
String prefix = entry.getKey();
String uri = entry.getValue();
if (prefix == null || "".equals(prefix))
domElement.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns", uri);
else
domElement.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns:"+ prefix, uri);
if (__log.isDebugEnabled())
__log.debug("injectNamespacesWithAllPrefixes: added namespace: prefix=\"" + prefix + "\" uri=\"" + uri + "\"");
}
if (__log.isDebugEnabled())
__log.debug("injectNamespacesWithAllPrefixes: result: element=" + domToString(domElement));
}
public static void copyNSContext(Element source, Element dest) {
Map<String, String> sourceNS = getParentNamespaces(source);
sourceNS.putAll(getMyNamespaces(source));
Map<String, String> destNS = getParentNamespaces(dest);
destNS.putAll(getMyNamespaces(dest));
// (source - dest) to avoid adding twice the same ns on dest
for (String pr : destNS.keySet()) sourceNS.remove(pr);
for (Map.Entry<String, String> entry : sourceNS.entrySet()) {
String prefix = entry.getKey();
String uri = entry.getValue();
if (prefix == null || "".equals(prefix))
dest.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns", uri);
else
dest.setAttributeNS(DOMUtils.NS_URI_XMLNS, "xmlns:"+ prefix, uri);
}
}
public static Document toDOMDocument(Node node) throws TransformerException {
// If the node is the document, just cast it
if (node instanceof Document) {
return (Document) node;
// If the node is an element
} else if (node instanceof Element) {
Element elem = (Element) node;
// If this is the root element, return its owner document
if (elem.getOwnerDocument().getDocumentElement() == elem) {
return elem.getOwnerDocument();
// else, create a new doc and copy the element inside it
} else {
Document doc = newDocument();
doc.appendChild(doc.importNode(node, true));
return doc;
}
// other element types are not handled
} else {
throw new TransformerException("Unable to convert DOM node to a Document");
}
}
public static Document toDocumentFromStream(StreamSource source) throws IOException, SAXException {
DocumentBuilder builder = getBuilder();
Document document = null;
Reader reader = source.getReader();
if (reader != null) {
document = builder.parse(new InputSource(reader));
} else {
InputStream inputStream = source.getInputStream();
if (inputStream != null) {
InputSource inputsource = new InputSource(inputStream);
inputsource.setSystemId( source.getSystemId() );
document = builder.parse(inputsource);
}
else {
throw new IOException("No input stream or reader available");
}
}
return document;
}
// sadly, as of JDK 5.0 IOException still doesn't support new IOException(Throwable)
private static void throwIOException(Throwable t) throws IOException {
IOException e = new IOException(t.getMessage());
e.setStackTrace(t.getStackTrace());
throw e;
}
public static Document parse(XMLStreamReader reader)
throws XMLStreamException
{
Document doc = newDocument();
parse(reader, doc, doc);
return doc;
}
private static void parse(XMLStreamReader reader, Document doc, Node parent)
throws XMLStreamException
{
int event = reader.getEventType();
while (reader.hasNext()) {
switch (event) {
case XMLStreamConstants.START_ELEMENT:
// create element
Element e = doc.createElementNS(reader.getNamespaceURI(), reader.getLocalName());
if (reader.getPrefix() != null && reader.getPrefix() != "") {
e.setPrefix(reader.getPrefix());
}
parent.appendChild(e);
// copy namespaces
for (int ns = 0; ns < reader.getNamespaceCount(); ns++) {
String uri = reader.getNamespaceURI(ns);
String prefix = reader.getNamespacePrefix(ns);
declare(e, uri, prefix);
}
// copy attributes
for (int att = 0; att < reader.getAttributeCount(); att++) {
String name = reader.getAttributeLocalName(att);
String prefix = reader.getAttributePrefix(att);
if (prefix != null && prefix.length() > 0) {
name = prefix + ":" + name;
}
Attr attr = doc.createAttributeNS(reader.getAttributeNamespace(att), name);
attr.setValue(reader.getAttributeValue(att));
e.setAttributeNode(attr);
}
// sub-nodes
if (reader.hasNext()) {
reader.next();
parse(reader, doc, e);
}
if (parent instanceof Document) {
while (reader.hasNext()) reader.next();
return;
}
break;
case XMLStreamConstants.END_ELEMENT:
return;
case XMLStreamConstants.CHARACTERS:
if (parent != null) {
parent.appendChild(doc.createTextNode(reader.getText()));
}
break;
case XMLStreamConstants.COMMENT:
if (parent != null) {
parent.appendChild(doc.createComment(reader.getText()));
}
break;
case XMLStreamConstants.CDATA:
parent.appendChild(doc.createCDATASection(reader.getText()));
break;
case XMLStreamConstants.PROCESSING_INSTRUCTION:
parent.appendChild(doc.createProcessingInstruction(reader.getPITarget(), reader.getPIData()));
break;
case XMLStreamConstants.ENTITY_REFERENCE:
parent.appendChild(doc.createProcessingInstruction(reader.getPITarget(), reader.getPIData()));
break;
case XMLStreamConstants.NAMESPACE:
case XMLStreamConstants.ATTRIBUTE:
break;
default:
break;
}
if (reader.hasNext()) {
event = reader.next();
}
}
}
private static void declare(Element node, String uri, String prefix) {
if (prefix != null && prefix.length() > 0) {
node.setAttributeNS(NS_URI_XMLNS, "xmlns:" + prefix, uri);
} else {
if (uri != null) {
node.setAttributeNS(NS_URI_XMLNS, "xmlns", uri);
}
}
}
private static Transformer getTransformer() {
Transformer txer = __txers.get();
if (txer == null) {
synchronized(_transformerFactory) {
try {
txer = _transformerFactory.newTransformer();
} catch (TransformerConfigurationException e) {
String errmsg = "Transformer configuration error!";
__log.error(errmsg, e);
throw new Error(errmsg, e);
}
}
__txers.set(txer);
}
return txer;
}
private static DocumentBuilder getBuilder() {
DocumentBuilder builder = __builders.get();
if (builder == null) {
synchronized (__documentBuilderFactory) {
try {
builder = __documentBuilderFactory.newDocumentBuilder();
builder.setErrorHandler(new LoggingErrorHandler());
} catch (ParserConfigurationException e) {
__log.error("",e);
throw new RuntimeException(e);
}
}
__builders.set(builder);
}
return builder;
}
public static List<Element> findChildrenByName(Element parent, QName name) {
if (parent == null)
throw new IllegalArgumentException("null parent");
if (name == null)
throw new IllegalArgumentException("null name");
LinkedList<Element> ret = new LinkedList<Element>();
NodeList nl = parent.getChildNodes();
for (int i = 0; i < nl.getLength(); ++i) {
Node c = nl.item(i);
if(c.getNodeType() != Node.ELEMENT_NODE)
continue;
// For a reason that I can't fathom, when using in-mem DAO we actually get elements with
// no localname.
String nodeName = c.getLocalName() != null ? c.getLocalName() : c.getNodeName();
if (new QName(c.getNamespaceURI(),nodeName).equals(name))
ret.add((Element)c);
}
return ret;
}
/**
* Somewhat eases the pain of dealing with both Lists and Nodelists by converting either
* passed as parameter to a List.
* @param nl a NodeList or a List
* @return a List
*/
public static List<Node> toList(Object nl) {
if (nl == null) return null;
if (nl instanceof List) return (List<Node>) nl;
NodeList cnl = (NodeList) nl;
List<Node> ll = new ArrayList<Node>();
for (int m = 0; m < cnl.getLength(); m++) ll.add(cnl.item(m));
return ll;
}
public static Document getDocument(Node contextNode) {
return (contextNode == null) ? DOMUtils.newDocument() : contextNode.getOwnerDocument();
}
public static String getQualifiedName(QName qName) {
String prefix = qName.getPrefix(), localPart = qName.getLocalPart();
return (prefix == null || "".equals(prefix)) ? localPart : (prefix + ":" + localPart);
}
/**
* Deep clone, but don't fry, the given node in the context of the given document.
* For all intents and purposes, the clone is the exact same copy of the node,
* except that it might have a different owner document.
*
* This method is fool-proof, unlike the <code>adoptNode</code> or <code>adoptNode</code> methods,
* in that it doesn't assume that the given node has a parent or a owner document.
*
* @param document
* @param sourceNode
* @return a clone of node
*/
public static Node cloneNode(Document document, Node sourceNode) {
Node clonedNode = null;
// what is my name?
QName sourceQName = getNodeQName(sourceNode);
String nodeName = sourceQName.getLocalPart();
String namespaceURI = sourceQName.getNamespaceURI();
// if the node is unqualified, don't assume that it inherits the WS-BPEL target namespace
if (Namespaces.WSBPEL2_0_FINAL_EXEC.equals(namespaceURI)) {
namespaceURI = null;
}
switch (sourceNode.getNodeType()) {
case Node.ATTRIBUTE_NODE:
if (namespaceURI == null) {
clonedNode = document.createAttribute(nodeName);
} else {
String prefix = ((Attr) sourceNode).lookupPrefix(namespaceURI);
// the prefix for the XML namespace can't be looked up, hence this...
if (prefix == null && namespaceURI.equals(NS_URI_XMLNS)) {
prefix = "xmlns";
}
// if a prefix exists, qualify the name with it
if (prefix != null && !"".equals(prefix)) {
nodeName = prefix + ":" + nodeName;
}
// create the appropriate type of attribute
if (prefix != null) {
clonedNode = document.createAttributeNS(namespaceURI, nodeName);
} else {
clonedNode = document.createAttribute(nodeName);
}
}
break;
case Node.CDATA_SECTION_NODE:
clonedNode = document.createCDATASection(((CDATASection) sourceNode).getData());
break;
case Node.COMMENT_NODE:
clonedNode = document.createComment(((Comment) sourceNode).getData());
break;
case Node.DOCUMENT_FRAGMENT_NODE:
clonedNode = document.createDocumentFragment();
break;
case Node.DOCUMENT_NODE:
clonedNode = document;
break;
case Node.ELEMENT_NODE:
// create the appropriate type of element
if (namespaceURI == null) {
clonedNode = document.createElement(nodeName);
} else {
String prefix = namespaceURI.equals(Namespaces.XMLNS_URI) ?
"xmlns" : ((Element) sourceNode).lookupPrefix(namespaceURI);
if (prefix != null && !"".equals(prefix)) {
nodeName = prefix + ":" + nodeName;
clonedNode = document.createElementNS(namespaceURI, nodeName);
} else {
clonedNode = document.createElement(nodeName);
}
}
// attributes are not treated as child nodes, so copy them explicitly
NamedNodeMap attributes = ((Element) sourceNode).getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Attr attributeClone = (Attr) cloneNode(document, attributes.item(i));
if (attributeClone.getNamespaceURI() == null) {
((Element) clonedNode).setAttributeNode(attributeClone);
} else {
((Element) clonedNode).setAttributeNodeNS(attributeClone);
}
}
break;
case Node.ENTITY_NODE:
// TODO
break;
case Node.ENTITY_REFERENCE_NODE:
clonedNode = document.createEntityReference(nodeName);
// TODO
break;
case Node.NOTATION_NODE:
// TODO
break;
case Node.PROCESSING_INSTRUCTION_NODE:
clonedNode = document.createProcessingInstruction(((ProcessingInstruction) sourceNode).getData(), nodeName);
break;
case Node.TEXT_NODE:
clonedNode = document.createTextNode(((Text) sourceNode ).getData());
break;
default:
break;
}
// clone children of element and attribute nodes
NodeList sourceChildren = sourceNode.getChildNodes();
if (sourceChildren != null) {
for (int i = 0; i < sourceChildren.getLength(); i++) {
Node sourceChild = sourceChildren.item(i);
Node clonedChild = cloneNode(document, sourceChild);
clonedNode.appendChild(clonedChild);
// if the child has a textual value, parse it for any embedded prefixes
if (clonedChild.getNodeType() == Node.TEXT_NODE ||
clonedChild.getNodeType() == Node.CDATA_SECTION_NODE) {
parseEmbeddedPrefixes(sourceNode, clonedNode, clonedChild);
}
}
}
return clonedNode;
}
/**
* Parse the text in the cloneChild for any embedded prefixes, and define it in it's parent element
*
* @param sourceNode
* @param clonedNode
* @param clonedChild
*/
private static void parseEmbeddedPrefixes(Node sourceNode, Node clonedNode, Node clonedChild) {
Element clonedElement = null;
if (clonedNode instanceof Attr) {
clonedElement = ((Attr) clonedNode).getOwnerElement();
} else if (clonedNode instanceof Element) {
clonedElement = (Element) clonedNode;
}
if (clonedElement == null) {
// couldn't find an element to set prefixes on, so bail out
return;
}
String text = ((Text) clonedChild).getNodeValue();
if (text != null && text.indexOf(":") > 0) {
Name11Checker nameChecker = Name11Checker.getInstance();
for (int colonIndex = text.indexOf(":"); colonIndex != -1 && colonIndex < text.length(); colonIndex = text.indexOf(":", colonIndex + 1)) {
StringBuffer prefixString = new StringBuffer();
for (int prefixIndex = colonIndex - 1;
prefixIndex >= 0 && nameChecker.isNCNameChar(text.charAt(prefixIndex));
prefixIndex--) {
prefixString.append(text.charAt(prefixIndex));
}
prefixString.reverse();
if (prefixString.length() > 0) {
String uri = sourceNode.lookupNamespaceURI(prefixString.toString());
if (uri != null) {
clonedElement.setAttributeNS(NS_URI_XMLNS, "xmlns:" + prefixString, uri);
}
}
}
}
}
public static Element stringToDOM(byte[] bytes) throws SAXException, IOException {
return stringToDOM(new String(bytes));
}
public static byte[] domToBytes(Element element) {
String stringifiedElement = domToString(element);
return (stringifiedElement != null) ? stringifiedElement.getBytes() : null;
}
}