org.opensaml.xml.util.XMLHelper Maven / Gradle / Ivy
/*
* Licensed to the University Corporation for Advanced Internet Development,
* Inc. (UCAID) under one or more contributor license agreements. See the
* NOTICE file distributed with this work for additional information regarding
* copyright ownership. The UCAID 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.opensaml.xml.util;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.namespace.QName;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLRuntimeException;
import org.opensaml.xml.parse.XMLParserException;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.w3c.dom.ls.LSSerializerFilter;
/**
* A helper class for working with W3C DOM objects.
*/
public final class XMLHelper {
/**
* A string which contains the valid delimiters for the XML Schema 'list' type. These are: space, newline, carriage
* return, and tab.
*/
public static final String LIST_DELIMITERS = " \n\r\t";
/** DOM configuration parameters used by LSSerializer in pretty print format output. */
private static Map prettyPrintParams;
/** JAXP DatatypeFactory. */
private static DatatypeFactory dataTypeFactory;
/** Constructor. */
private XMLHelper() {
}
/**
* Gets a static instance of a JAXP DatatypeFactory.
*
* @return the factory or null if the factory could not be created
*/
public static DatatypeFactory getDataTypeFactory() {
if (dataTypeFactory == null) {
try {
dataTypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
// do nothing
}
}
return dataTypeFactory;
}
/**
* Checks if the given element has an xsi:type defined for it.
*
* @param e the DOM element
*
* @return true if there is a type, false if not
*/
public static boolean hasXSIType(Element e) {
if (e != null) {
if (e.getAttributeNodeNS(XMLConstants.XSI_NS, "type") != null) {
return true;
}
}
return false;
}
/**
* Gets the XSI type for a given element if it has one.
*
* @param e the element
*
* @return the type or null
*/
public static QName getXSIType(Element e) {
if (hasXSIType(e)) {
Attr attribute = e.getAttributeNodeNS(XMLConstants.XSI_NS, "type");
String attributeValue = attribute.getTextContent().trim();
StringTokenizer tokenizer = new StringTokenizer(attributeValue, ":");
String prefix = null;
String localPart;
if (tokenizer.countTokens() > 1) {
prefix = tokenizer.nextToken();
localPart = tokenizer.nextToken();
} else {
localPart = tokenizer.nextToken();
}
return constructQName(e.lookupNamespaceURI(prefix), localPart, prefix);
}
return null;
}
/**
* Gets the ID attribute of a DOM element.
*
* @param domElement the DOM element
*
* @return the ID attribute or null if there isn't one
*/
public static Attr getIdAttribute(Element domElement) {
if (!domElement.hasAttributes()) {
return null;
}
NamedNodeMap attributes = domElement.getAttributes();
Attr attribute;
for (int i = 0; i < attributes.getLength(); i++) {
attribute = (Attr) attributes.item(i);
if (attribute.isId()) {
return attribute;
}
}
return null;
}
/**
* Gets the QName for the given DOM node.
*
* @param domNode the DOM node
*
* @return the QName for the element or null if the element was null
*/
public static QName getNodeQName(Node domNode) {
if (domNode != null) {
return constructQName(domNode.getNamespaceURI(), domNode.getLocalName(), domNode.getPrefix());
}
return null;
}
/**
* Gets the lcoale currently active for the element. This is done by looking for an xml:lang attribute and parsing
* its content. If no xml:lang attribute is present the default locale is returned. This method only uses the
* language primary tag, as defined by RFC3066.
*
* @param element element to retrieve local information for
*
* @return the active local of the element
*/
public static Locale getLanguage(Element element) {
String lang = DatatypeHelper.safeTrimOrNullString(element.getAttributeNS(XMLConstants.XML_NS, "lang"));
if (lang != null) {
if (lang.contains("-")) {
lang = lang.substring(0, lang.indexOf("-"));
}
return new Locale(lang.toUpperCase());
} else {
return Locale.getDefault();
}
}
/**
* Constructs an attribute owned by the given document with the given name.
*
* @param owningDocument the owning document
* @param attributeName the name of that attribute
*
* @return the constructed attribute
*/
public static Attr constructAttribute(Document owningDocument, QName attributeName) {
return constructAttribute(owningDocument, attributeName.getNamespaceURI(), attributeName.getLocalPart(),
attributeName.getPrefix());
}
/**
* Constructs an attribute owned by the given document with the given name.
*
* @param document the owning document
* @param namespaceURI the URI for the namespace the attribute is in
* @param localName the local name
* @param prefix the prefix of the namespace that attribute is in
*
* @return the constructed attribute
*/
public static Attr constructAttribute(Document document, String namespaceURI, String localName, String prefix) {
String trimmedLocalName = DatatypeHelper.safeTrimOrNullString(localName);
if (trimmedLocalName == null) {
throw new IllegalArgumentException("Local name may not be null or empty");
}
String qualifiedName;
String trimmedPrefix = DatatypeHelper.safeTrimOrNullString(prefix);
if (trimmedPrefix != null) {
qualifiedName = trimmedPrefix + ":" + DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
} else {
qualifiedName = DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
}
if (DatatypeHelper.isEmpty(namespaceURI)) {
return document.createAttributeNS(null, qualifiedName);
} else {
return document.createAttributeNS(namespaceURI, qualifiedName);
}
}
/**
* Constructs a QName from an attributes value.
*
* @param attribute the attribute with a QName value
*
* @return a QName from an attributes value, or null if the given attribute is null
*/
public static QName getAttributeValueAsQName(Attr attribute) {
if (attribute == null || DatatypeHelper.isEmpty(attribute.getValue())) {
return null;
}
String attributeValue = attribute.getTextContent();
String[] valueComponents = attributeValue.split(":");
if (valueComponents.length == 1) {
return constructQName(attribute.lookupNamespaceURI(null), valueComponents[0], null);
} else {
return constructQName(attribute.lookupNamespaceURI(valueComponents[0]), valueComponents[1],
valueComponents[0]);
}
}
/**
* Parses the attribute's value. If the value is 0 or "false" then false is returned, if the value is 1 or "true"
* then true is returned, if the value is anything else then null returned.
*
* @param attribute attribute whose value will be converted to a boolean
*
* @return boolean value of the attribute or null
*/
public static Boolean getAttributeValueAsBoolean(Attr attribute) {
if (attribute == null) {
return null;
}
String valueStr = attribute.getValue();
if (valueStr.equals("0") || valueStr.equals("false")) {
return Boolean.FALSE;
} else if (valueStr.equals("1") || valueStr.equals("true")) {
return Boolean.TRUE;
} else {
return null;
}
}
/**
* Gets the value of a list-type attribute as a list.
*
* @param attribute attribute whose value will be turned into a list
*
* @return list of values, never null
*/
public static List getAttributeValueAsList(Attr attribute) {
if (attribute == null) {
return Collections.emptyList();
}
return DatatypeHelper.stringToList(attribute.getValue(), LIST_DELIMITERS);
}
/**
* Marshall an attribute name and value to a DOM Element. This is particularly useful for attributes whose names
* appear in namespace-qualified form.
*
* @param attributeName the attribute name in QName form
* @param attributeValue the attribute value
* @param domElement the target element to which to marshall
* @param isIDAttribute flag indicating whether the attribute being marshalled should be handled as an ID-typed
* attribute
*/
public static void marshallAttribute(QName attributeName, String attributeValue, Element domElement,
boolean isIDAttribute) {
Document document = domElement.getOwnerDocument();
Attr attribute = XMLHelper.constructAttribute(document, attributeName);
attribute.setValue(attributeValue);
domElement.setAttributeNodeNS(attribute);
if (isIDAttribute) {
domElement.setIdAttributeNode(attribute, true);
}
}
/**
* Marshall an attribute name and value to a DOM Element. This is particularly useful for attributes whose names
* appear in namespace-qualified form.
*
* @param attributeName the attribute name in QName form
* @param attributeValues the attribute values
* @param domElement the target element to which to marshall
* @param isIDAttribute flag indicating whether the attribute being marshalled should be handled as an ID-typed
* attribute
*/
public static void marshallAttribute(QName attributeName, List attributeValues, Element domElement,
boolean isIDAttribute) {
marshallAttribute(attributeName, DatatypeHelper.listToStringValue(attributeValues, " "), domElement,
isIDAttribute);
}
/**
* Marshall the attributes represented by the indicated AttributeMap into the indicated DOM Element.
*
* @param attributeMap the AttributeMap
* @param domElement the target Element
*/
public static void marshallAttributeMap(AttributeMap attributeMap, Element domElement) {
Document document = domElement.getOwnerDocument();
Attr attribute = null;
for (Entry entry : attributeMap.entrySet()) {
attribute = XMLHelper.constructAttribute(document, entry.getKey());
attribute.setValue(entry.getValue());
domElement.setAttributeNodeNS(attribute);
if (Configuration.isIDAttribute(entry.getKey()) || attributeMap.isIDAttribute(entry.getKey())) {
domElement.setIdAttributeNode(attribute, true);
}
}
}
/**
* Unmarshall a DOM Attr to an AttributeMap.
*
* @param attributeMap the target AttributeMap
* @param attribute the target DOM Attr
*/
public static void unmarshallToAttributeMap(AttributeMap attributeMap, Attr attribute) {
QName attribQName = XMLHelper.constructQName(attribute.getNamespaceURI(), attribute.getLocalName(), attribute
.getPrefix());
attributeMap.put(attribQName, attribute.getValue());
if (attribute.isId() || Configuration.isIDAttribute(attribQName)) {
attributeMap.registerID(attribQName);
}
}
/**
* Constructs a QName from an element's adjacent Text child nodes.
*
* @param element the element with a QName value
*
* @return a QName from an element's value, or null if the given element is empty
*/
public static QName getElementContentAsQName(Element element) {
if (element == null) {
return null;
}
String elementContent = null;
Node child = element.getFirstChild();
while (child != null) {
if (child.getNodeType() == Node.TEXT_NODE) {
elementContent = DatatypeHelper.safeTrimOrNullString(((Text) child).getWholeText());
break;
}
child = child.getNextSibling();
}
if (elementContent == null) {
return null;
}
String[] valueComponents = elementContent.split(":");
if (valueComponents.length == 1) {
return constructQName(element.lookupNamespaceURI(null), valueComponents[0], null);
} else {
return constructQName(element.lookupNamespaceURI(valueComponents[0]), valueComponents[1],
valueComponents[0]);
}
}
/**
* Gets the value of a list-type element as a list.
*
* @param element element whose value will be turned into a list
*
* @return list of values, never null
*/
public static List getElementContentAsList(Element element) {
if (element == null) {
return Collections.emptyList();
}
return DatatypeHelper.stringToList(element.getTextContent(), LIST_DELIMITERS);
}
/**
* Constructs a QName.
*
* @param namespaceURI the namespace of the QName
* @param localName the local name of the QName
* @param prefix the prefix of the QName, may be null
*
* @return the QName
*/
public static QName constructQName(String namespaceURI, String localName, String prefix) {
if (DatatypeHelper.isEmpty(prefix)) {
return new QName(namespaceURI, localName);
} else if (DatatypeHelper.isEmpty(namespaceURI)) {
return new QName(localName);
}
return new QName(namespaceURI, localName, prefix);
}
/**
* Constructs a QName from a string (attribute or element content) value.
*
* @param qname the QName string
* @param owningObject XMLObject, with cached DOM, owning the QName
*
* @return the QName respresented by the string
*/
public static QName constructQName(String qname, XMLObject owningObject) {
return constructQName(qname, owningObject.getDOM());
}
/**
* Constructs a QName from a string (attribute element content) value.
*
* @param qname the QName string
* @param owningElement parent DOM element of the Node which contains the QName value
*
* @return the QName respresented by the string
*/
public static QName constructQName(String qname, Element owningElement) {
String nsURI;
String nsPrefix;
String name;
if (qname.indexOf(":") > -1) {
StringTokenizer qnameTokens = new StringTokenizer(qname, ":");
nsPrefix = qnameTokens.nextToken();
name = qnameTokens.nextToken();
} else {
nsPrefix = "";
name = qname;
}
nsURI = lookupNamespaceURI(owningElement, nsPrefix);
return constructQName(nsURI, name, nsPrefix);
}
/**
* Constructs an element, rooted in the given document, with the given name.
*
* @param document the document containing the element
* @param elementName the name of the element, must contain a local name, may contain a namespace URI and prefix
*
* @return the element
*/
public static Element constructElement(Document document, QName elementName) {
return constructElement(document, elementName.getNamespaceURI(), elementName.getLocalPart(), elementName
.getPrefix());
}
/**
* Constructs an element, rooted in the given document, with the given information.
*
* @param document the document containing the element
* @param namespaceURI the URI of the namespace the element is in
* @param localName the element's local name
* @param prefix the prefix of the namespace the element is in
*
* @return the element
*/
public static Element constructElement(Document document, String namespaceURI, String localName, String prefix) {
String trimmedLocalName = DatatypeHelper.safeTrimOrNullString(localName);
if (trimmedLocalName == null) {
throw new IllegalArgumentException("Local name may not be null or empty");
}
String qualifiedName;
String trimmedPrefix = DatatypeHelper.safeTrimOrNullString(prefix);
if (trimmedPrefix != null) {
qualifiedName = trimmedPrefix + ":" + DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
} else {
qualifiedName = DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
}
if (!DatatypeHelper.isEmpty(namespaceURI)) {
return document.createElementNS(namespaceURI, qualifiedName);
} else {
return document.createElementNS(null, qualifiedName);
}
}
/**
* Appends the child Element to the parent Element, adopting the child Element into the parent's Document if needed.
*
* @param parentElement the parent Element
* @param childElement the child Element
*/
public static void appendChildElement(Element parentElement, Element childElement) {
Document parentDocument = parentElement.getOwnerDocument();
adoptElement(childElement, parentDocument);
parentElement.appendChild(childElement);
}
/**
* Adopts an element into a document if the child is not already in the document.
*
* @param adoptee the element to be adopted
* @param adopter the document into which the element is adopted
*/
public static void adoptElement(Element adoptee, Document adopter) {
if (!(adoptee.getOwnerDocument().equals(adopter))) {
if (adopter.adoptNode(adoptee) == null) {
// This can happen if the adopter and adoptee were produced by different DOM implementations
throw new XMLRuntimeException("DOM Element node adoption failed");
}
}
}
/**
* Creates a text node with the given content and appends it as child to the given element.
*
* @param domElement the element to recieve the text node
* @param textContent the content for the text node
*/
public static void appendTextContent(Element domElement, String textContent) {
if (textContent == null) {
return;
}
Document parentDocument = domElement.getOwnerDocument();
Text textNode = parentDocument.createTextNode(textContent);
domElement.appendChild(textNode);
}
/**
* Adds a namespace declaration (xmlns:) attribute to the given element.
*
* @param domElement the element to add the attribute to
* @param namespaceURI the URI of the namespace
* @param prefix the prefix for the namespace
*/
public static void appendNamespaceDeclaration(Element domElement, String namespaceURI, String prefix) {
String nsURI = DatatypeHelper.safeTrimOrNullString(namespaceURI);
String nsPrefix = DatatypeHelper.safeTrimOrNullString(prefix);
String attributeName;
if (nsPrefix == null) {
attributeName = XMLConstants.XMLNS_PREFIX;
} else {
attributeName = XMLConstants.XMLNS_PREFIX + ":" + nsPrefix;
}
String attributeValue;
if (nsURI == null) {
attributeValue = "";
} else {
attributeValue = nsURI;
}
domElement.setAttributeNS(XMLConstants.XMLNS_NS, attributeName, attributeValue);
}
/**
* Looks up the namespace URI associated with the given prefix starting at the given element. This method differs
* from the {@link Node#lookupNamespaceURI(java.lang.String)} in that it only those namespaces declared by an xmlns
* attribute are inspected. The Node method also checks the namespace a particular node was created in by way of a
* call like {@link Document#createElementNS(java.lang.String, java.lang.String)} even if the resulting element
* doesn't have an namespace delcaration attribute.
*
* @param startingElement the starting element
* @param prefix the prefix to look up
*
* @return the namespace URI for the given prefix
*/
public static String lookupNamespaceURI(Element startingElement, String prefix) {
return lookupNamespaceURI(startingElement, null, prefix);
}
/**
* Looks up the namespace URI associated with the given prefix starting at the given element. This method differs
* from the {@link Node#lookupNamespaceURI(java.lang.String)} in that it only those namespaces declared by an xmlns
* attribute are inspected. The Node method also checks the namespace a particular node was created in by way of a
* call like {@link Document#createElementNS(java.lang.String, java.lang.String)} even if the resulting element
* doesn't have an namespace delcaration attribute.
*
* @param startingElement the starting element
* @param stopingElement the ancestor of the starting element that serves as the upper-bound, inclusive, for the
* search
* @param prefix the prefix to look up
*
* @return the namespace URI for the given prefer or null
*/
public static String lookupNamespaceURI(Element startingElement, Element stopingElement, String prefix) {
String namespaceURI;
// This code is a modified version of the lookup code within Xerces
if (startingElement.hasAttributes()) {
NamedNodeMap map = startingElement.getAttributes();
int length = map.getLength();
for (int i = 0; i < length; i++) {
Node attr = map.item(i);
String attrPrefix = attr.getPrefix();
String value = attr.getNodeValue();
namespaceURI = attr.getNamespaceURI();
if (namespaceURI != null && namespaceURI.equals(XMLConstants.XMLNS_NS)) {
// at this point we are dealing with DOM Level 2 nodes only
if (prefix == null && attr.getNodeName().equals(XMLConstants.XMLNS_PREFIX)) {
// default namespace
return value;
} else if (attrPrefix != null && attrPrefix.equals(XMLConstants.XMLNS_PREFIX)
&& attr.getLocalName().equals(prefix)) {
// non default namespace
return value;
}
}
}
}
if (startingElement != stopingElement) {
Element ancestor = getElementAncestor(startingElement);
if (ancestor != null) {
return lookupNamespaceURI(ancestor, stopingElement, prefix);
}
}
return null;
}
/**
* Looks up the namespace prefix associated with the given URI starting at the given element. This method differs
* from the {@link Node#lookupPrefix(java.lang.String)} in that it only those namespaces declared by an xmlns
* attribute are inspected. The Node method also checks the namespace a particular node was created in by way of a
* call like {@link Document#createElementNS(java.lang.String, java.lang.String)} even if the resulting element
* doesn't have an namespace delcaration attribute.
*
* @param startingElement the starting element
* @param namespaceURI the uri to look up
*
* @return the prefix for the given namespace URI
*/
public static String lookupPrefix(Element startingElement, String namespaceURI) {
return lookupPrefix(startingElement, null, namespaceURI);
}
/**
* Looks up the namespace prefix associated with the given URI starting at the given element. This method differs
* from the {@link Node#lookupPrefix(java.lang.String)} in that it only those namespaces declared by an xmlns
* attribute are inspected. The Node method also checks the namespace a particular node was created in by way of a
* call like {@link Document#createElementNS(java.lang.String, java.lang.String)} even if the resulting element
* doesn't have an namespace delcaration attribute.
*
* @param startingElement the starting element
* @param stopingElement the ancestor of the starting element that serves as the upper-bound, inclusive, for the
* search
* @param namespaceURI the uri to look up
*
* @return the prefix for the given namespace URI
*/
public static String lookupPrefix(Element startingElement, Element stopingElement, String namespaceURI) {
String namespace;
// This code is a modified version of the lookup code within Xerces
if (startingElement.hasAttributes()) {
NamedNodeMap map = startingElement.getAttributes();
int length = map.getLength();
for (int i = 0; i < length; i++) {
Node attr = map.item(i);
String attrPrefix = attr.getPrefix();
String value = attr.getNodeValue();
namespace = attr.getNamespaceURI();
if (namespace != null && namespace.equals(XMLConstants.XMLNS_NS)) {
// DOM Level 2 nodes
if (attr.getNodeName().equals(XMLConstants.XMLNS_PREFIX)
|| (attrPrefix != null && attrPrefix.equals(XMLConstants.XMLNS_PREFIX))
&& value.equals(namespaceURI)) {
String localname = attr.getLocalName();
String foundNamespace = startingElement.lookupNamespaceURI(localname);
if (foundNamespace != null && foundNamespace.equals(namespaceURI)) {
return localname;
}
}
}
}
}
if (startingElement != stopingElement) {
Element ancestor = getElementAncestor(startingElement);
if (ancestor != null) {
return lookupPrefix(ancestor, stopingElement, namespaceURI);
}
}
return null;
}
/**
* Gets the child nodes with the given namespace qualified tag name. If you need to retrieve multiple, named,
* children consider using {@link #getChildElements(Element)}.
*
* @param root element to retrieve the children from
* @param namespaceURI namespace URI of the child element
* @param localName local, tag, name of the child element
*
* @return list of child elements, never null
*/
public static List getChildElementsByTagNameNS(Element root, String namespaceURI, String localName) {
ArrayList children = new ArrayList();
Element e = getFirstChildElement(root);
while (e != null) {
if (DatatypeHelper.safeEquals(e.getNamespaceURI(), namespaceURI)
&& DatatypeHelper.safeEquals(e.getLocalName(), localName)) {
children.add(e);
}
e = getNextSiblingElement(e);
}
return children;
}
/**
* Gets the child nodes with the given local tag name. If you need to retrieve multiple, named, children consider
* using {@link #getChildElements(Element)}.
*
* @param root element to retrieve the children from
* @param localName local, tag, name of the child element
*
* @return list of child elements, never null
*/
public static List getChildElementsByTagName(Element root, String localName) {
ArrayList children = new ArrayList();
Element e = getFirstChildElement(root);
while (e != null) {
if (DatatypeHelper.safeEquals(e.getLocalName(), localName)) {
children.add(e);
}
e = getNextSiblingElement(e);
}
return children;
}
/**
* Gets the child elements of the given element in a single iteration.
*
* @param root element to get the child elements of
*
* @return child elements indexed by namespace qualifed tag name, never null
*/
public static Map> getChildElements(Element root) {
Map> children = new HashMap>();
Element e = getFirstChildElement(root);
while (e != null) {
QName qname = getNodeQName(e);
List elements = children.get(qname);
if (elements == null) {
elements = new ArrayList();
children.put(qname, elements);
}
elements.add(e);
e = getNextSiblingElement(e);
}
return children;
}
/**
* Gets the ancestor element node to the given node.
*
* @param currentNode the node to retrive the ancestor for
*
* @return the ancestral element node of the current node, or null
*/
public static Element getElementAncestor(Node currentNode) {
Node parent = currentNode.getParentNode();
if (parent != null) {
short type = parent.getNodeType();
if (type == Node.ELEMENT_NODE) {
return (Element) parent;
}
return getElementAncestor(parent);
}
return null;
}
/**
* Converts a Node into a String using the DOM, level 3, Load/Save serializer.
*
* @param node the node to be written to a string
*
* @return the string representation of the node
*/
public static String nodeToString(Node node) {
StringWriter writer = new StringWriter();
writeNode(node, writer);
return writer.toString();
}
/**
* Pretty prints the XML node.
*
* @param node xml node to print
*
* @return pretty-printed xml
*/
public static String prettyPrintXML(Node node) {
StringWriter writer = new StringWriter();
writeNode(node, writer, getPrettyPrintParams());
return writer.toString();
}
/**
* Create the parameters set used in pretty print formatting of an LSSerializer.
*
* @return the params map
*/
private static Map getPrettyPrintParams() {
if (prettyPrintParams == null) {
prettyPrintParams = new LazyMap();
prettyPrintParams.put("format-pretty-print", Boolean.TRUE);
}
return prettyPrintParams;
}
/**
* Writes a Node out to a Writer using the DOM, level 3, Load/Save serializer. The written content is encoded using
* the encoding specified in the writer configuration.
*
* @param node the node to write out
* @param output the writer to write the XML to
*/
public static void writeNode(Node node, Writer output) {
writeNode(node, output, null);
}
/**
* Writes a Node out to a Writer using the DOM, level 3, Load/Save serializer. The written content is encoded using
* the encoding specified in the writer configuration.
*
* @param node the node to write out
* @param output the writer to write the XML to
* @param serializerParams parameters to pass to the {@link DOMConfiguration} of the serializer
* instance, obtained via {@link LSSerializer#getDomConfig()}. May be null.
*/
public static void writeNode(Node node, Writer output, Map serializerParams) {
DOMImplementationLS domImplLS = getLSDOMImpl(node);
LSSerializer serializer = getLSSerializer(domImplLS, serializerParams);
LSOutput serializerOut = domImplLS.createLSOutput();
serializerOut.setCharacterStream(output);
serializer.write(node, serializerOut);
}
/**
* Writes a Node out to an OutputStream using the DOM, level 3, Load/Save serializer. The written content
* is encoded using the encoding specified in the output stream configuration.
*
* @param node the node to write out
* @param output the output stream to write the XML to
*/
public static void writeNode(Node node, OutputStream output) {
writeNode(node, output, null);
}
/**
* Writes a Node out to an OutputStream using the DOM, level 3, Load/Save serializer. The written content
* is encoded using the encoding specified in the output stream configuration.
*
* @param node the node to write out
* @param output the output stream to write the XML to
* @param serializerParams parameters to pass to the {@link DOMConfiguration} of the serializer
* instance, obtained via {@link LSSerializer#getDomConfig()}. May be null.
*/
public static void writeNode(Node node, OutputStream output, Map serializerParams) {
DOMImplementationLS domImplLS = getLSDOMImpl(node);
LSSerializer serializer = getLSSerializer(domImplLS, serializerParams);
LSOutput serializerOut = domImplLS.createLSOutput();
serializerOut.setByteStream(output);
serializer.write(node, serializerOut);
}
/**
* Obtain a the DOM, level 3, Load/Save serializer {@link LSSerializer} instance from the
* given {@link DOMImplementationLS} instance.
*
*
* The serializer instance will be configured with the parameters passed as the serializerParams
* argument. It will also be configured with an {@link LSSerializerFilter} that shows all nodes to the filter,
* and accepts all nodes shown.
*
*
* @param domImplLS the DOM Level 3 Load/Save implementation to use
* @param serializerParams parameters to pass to the {@link DOMConfiguration} of the serializer
* instance, obtained via {@link LSSerializer#getDomConfig()}. May be null.
*
* @return a new LSSerializer instance
*/
public static LSSerializer getLSSerializer(DOMImplementationLS domImplLS, Map serializerParams) {
LSSerializer serializer = domImplLS.createLSSerializer();
serializer.setFilter(new LSSerializerFilter() {
public short acceptNode(Node arg0) {
return FILTER_ACCEPT;
}
public int getWhatToShow() {
return SHOW_ALL;
}
});
if (serializerParams != null) {
DOMConfiguration serializerDOMConfig = serializer.getDomConfig();
for (String key : serializerParams.keySet()) {
serializerDOMConfig.setParameter(key, serializerParams.get(key));
}
}
return serializer;
}
/**
* Get the DOM Level 3 Load/Save {@link DOMImplementationLS} for the given node.
*
* @param node the node to evaluate
* @return the DOMImplementationLS for the given node
*/
public static DOMImplementationLS getLSDOMImpl(Node node) {
DOMImplementation domImpl;
if (node instanceof Document) {
domImpl = ((Document) node).getImplementation();
} else {
domImpl = node.getOwnerDocument().getImplementation();
}
DOMImplementationLS domImplLS = (DOMImplementationLS) domImpl.getFeature("LS", "3.0");
return domImplLS;
}
/**
* Converts a QName into a string that can be used for attribute values or element content.
*
* @param qname the QName to convert to a string
*
* @return the string value of the QName
*/
public static String qnameToContentString(QName qname) {
StringBuffer buf = new StringBuffer();
String prefix = DatatypeHelper.safeTrimOrNullString(qname.getPrefix());
if (prefix != null) {
buf.append(prefix);
buf.append(":");
}
buf.append(qname.getLocalPart());
return buf.toString();
}
/**
* Ensures that all the visibly used namespaces referenced by the given Element or its descendants are declared by
* the given Element or one of its descendants.
*
* NOTE: This is a very costly operation.
*
* @param domElement the element to act as the root of the namespace declarations
*
* @throws XMLParserException thrown if a namespace prefix is encountered that can't be resolved to a namespace URI
*/
public static void rootNamespaces(Element domElement) throws XMLParserException {
rootNamespaces(domElement, domElement);
}
/**
* Recursively called function that ensures all the visibly used namespaces referenced by the given Element or its
* descendants are declared if they don't appear in the list of already resolved namespaces.
*
* @param domElement the Element
* @param upperNamespaceSearchBound the "root" element of the fragment where namespaces may be rooted
*
* @throws XMLParserException thrown if a namespace prefix is encountered that can't be resolved to a namespace URI
*/
private static void rootNamespaces(Element domElement, Element upperNamespaceSearchBound) throws XMLParserException {
String namespaceURI = null;
String namespacePrefix = domElement.getPrefix();
// Check if the namespace for this element is already declared on this element
boolean nsDeclaredOnElement = false;
if (namespacePrefix == null) {
nsDeclaredOnElement = domElement.hasAttributeNS(null, XMLConstants.XMLNS_PREFIX);
} else {
nsDeclaredOnElement = domElement.hasAttributeNS(XMLConstants.XMLNS_NS, namespacePrefix);
}
if (!nsDeclaredOnElement) {
// Namspace for element was not declared on the element itself, see if the namespace is declared on
// an ancestral element within the subtree where namespaces much be rooted
namespaceURI = lookupNamespaceURI(domElement, upperNamespaceSearchBound, namespacePrefix);
if (namespaceURI == null) {
// Namespace for the element is not declared on any ancestral nodes within the subtree where namespaces
// must be rooted. Resolve the namespace from ancestors outside that subtree.
namespaceURI = lookupNamespaceURI(upperNamespaceSearchBound, null, namespacePrefix);
if (namespaceURI != null) {
// Namespace resolved outside the subtree where namespaces must be declared so declare the namespace
// on this element (within the subtree).
appendNamespaceDeclaration(domElement, namespaceURI, namespacePrefix);
} else {
// Namespace couldn't be resolved from any ancestor. If the namespace prefix is null then the
// element is simply in the undeclared default document namespace, which is fine. If it isn't null
// then a namespace prefix, that hasn't properly been declared, is being used.
if (namespacePrefix != null) {
throw new XMLParserException("Unable to resolve namespace prefix " + namespacePrefix
+ " found on element " + getNodeQName(domElement));
}
}
}
}
// Make sure all the attribute URIs are rooted here or have been rooted in an ancestor
NamedNodeMap attributes = domElement.getAttributes();
Node attributeNode;
for (int i = 0; i < attributes.getLength(); i++) {
namespacePrefix = null;
namespaceURI = null;
attributeNode = attributes.item(i);
// Shouldn't need this check, but just to be safe, we have it
if (attributeNode.getNodeType() != Node.ATTRIBUTE_NODE) {
continue;
}
namespacePrefix = attributeNode.getPrefix();
if (!DatatypeHelper.isEmpty(namespacePrefix)) {
// If it's the "xmlns" prefix then it is the namespace declaration,
// don't try to look it up and redeclare it
if (namespacePrefix.equals(XMLConstants.XMLNS_PREFIX)
|| namespacePrefix.equals(XMLConstants.XML_PREFIX)) {
continue;
}
// check to see if the namespace for the prefix has already been defined within the XML fragment
namespaceURI = lookupNamespaceURI(domElement, upperNamespaceSearchBound, namespacePrefix);
if (namespaceURI == null) {
namespaceURI = lookupNamespaceURI(upperNamespaceSearchBound, null, namespacePrefix);
if (namespaceURI == null) {
throw new XMLParserException("Unable to resolve namespace prefix " + namespacePrefix
+ " found on attribute " + getNodeQName(attributeNode) + " found on element "
+ getNodeQName(domElement));
}
appendNamespaceDeclaration(domElement, namespaceURI, namespacePrefix);
}
}
}
// Now for the child elements, we pass a copy of the resolved namespace list in order to
// maintain proper scoping of namespaces.
Element childNode = getFirstChildElement(domElement);
while (childNode != null) {
rootNamespaces(childNode, upperNamespaceSearchBound);
childNode = getNextSiblingElement(childNode);
}
}
/**
* Shortcut for checking a DOM element node's namespace and local name.
*
* @param e An element to compare against
* @param ns An XML namespace to compare
* @param localName A local name to compare
* @return true iff the element's local name and namespace match the parameters
*/
public static boolean isElementNamed(Element e, String ns, String localName) {
return e != null && DatatypeHelper.safeEquals(ns, e.getNamespaceURI())
&& DatatypeHelper.safeEquals(localName, e.getLocalName());
}
/**
* Gets the first child Element of the node, skipping any Text nodes such as whitespace.
*
* @param n The parent in which to search for children
* @return The first child Element of n, or null if none
*/
public static Element getFirstChildElement(Node n) {
Node child = n.getFirstChild();
while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
child = child.getNextSibling();
}
if (child != null) {
return (Element) child;
} else {
return null;
}
}
/**
* Gets the next sibling Element of the node, skipping any Text nodes such as whitespace.
*
* @param n The sibling to start with
* @return The next sibling Element of n, or null if none
*/
public static Element getNextSiblingElement(Node n) {
Node sib = n.getNextSibling();
while (sib != null && sib.getNodeType() != Node.ELEMENT_NODE) {
sib = sib.getNextSibling();
}
if (sib != null) {
return (Element) sib;
} else {
return null;
}
}
/**
* Converts a lexical duration, as defined by XML Schema 1.0, into milliseconds.
*
* @param duration lexical duration representation
*
* @return duration in milliseconds
*/
public static long durationToLong(String duration) {
Duration xmlDuration = getDataTypeFactory().newDuration(duration);
return xmlDuration.getTimeInMillis(new GregorianCalendar());
}
/**
* Converts a duration in milliseconds to a lexical duration, as defined by XML Schema 1.0.
*
* @param duration the duration
*
* @return the lexical representation
*/
public static String longToDuration(long duration) {
Duration xmlDuration = getDataTypeFactory().newDuration(duration);
return xmlDuration.toString();
}
}