Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.xmlbeam.util.intern.DOMHelper Maven / Gradle / Ivy
/**
* Copyright 2012 Sven Ewald
*
* Licensed 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.xmlbeam.util.intern;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
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.Text;
import org.xmlbeam.XBProjector;
import org.xmlbeam.exceptions.XBException;
/**
* A set of tiny helper methods internally used in the projection framework. This methods are
* not part of the public framework API and might change in minor version updates.
*
* @author Sven Ewald
*/
public final class DOMHelper {
/**
* Null safe comparator for DOM nodes.
*/
private static final Comparator super Node> ATTRIBUTE_NODE_COMPARATOR = new Comparator() {
private int compareMaybeNull(final Comparable a, final Object b) {
if (a == b) {
return 0;
}
if (a == null) {
return -1;
}
if (b == null) {
return 1;
}
return a.compareTo(b);
}
@Override
public int compare(final Node o1, final Node o2) {
Comparable[] c1 = getNodeAttributes(o1);
Comparable[] c2 = getNodeAttributes(o2);
assert c1.length == c2.length;
for (int i = 0; i < c1.length; ++i) {
int result = compareMaybeNull(c1[i], c2[i]);
if (result != 0) {
return result;
}
}
return 0;
}
};
/**
* Parse namespace prefixes defined anywhere in the document.
*
* @param document
* source document.
* @return map with prefix->uri relationships.
*/
public static Map getNamespaceMapping(final Document document) {
Map map = new HashMap();
map.put("xmlns", "http://www.w3.org/2000/xmlns/");
map.put("xml", "http://www.w3.org/XML/1998/namespace");
// if (childName.equals("xmlns") || childName.startsWith("xmlns:")) {
// return "http://www.w3.org/2000/xmlns/";
// }
// if (childName.startsWith("xml:")) {
// return "http://www.w3.org/XML/1998/namespace";
// }
Element root = document.getDocumentElement();
if (root == null) {
// No document, no namespaces.
return map;
}
fillNSMapWithPrefixesDeclaredInElement(map, root);
return map;
}
/**
* Search for prefix definitions in element and all children. There still is an issue for
* documents that use the same prefix on differen namespaces in disjunct subtrees. This might be
* possible but we won't support this. Same is with declaring multiple default namespaces.
* XMLBeams behaviour will be undefined in that case. There is a workaround by defining a custom
* namespace/prefix mapping, so the effort to support this is not justified.
*
* @param nsMap
* @param element
* @throws DOMException
*/
private static void fillNSMapWithPrefixesDeclaredInElement(final Map nsMap, final Element element) throws DOMException {
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attribute = attributes.item(i);
if ((!XMLConstants.XMLNS_ATTRIBUTE.equals(attribute.getPrefix())) && (!XMLConstants.XMLNS_ATTRIBUTE.equals(attribute.getLocalName()))) {
continue;
}
if (XMLConstants.XMLNS_ATTRIBUTE.equals(attribute.getLocalName())) {
nsMap.put("xbdefaultns", attribute.getNodeValue());
continue;
}
nsMap.put(attribute.getLocalName(), attribute.getNodeValue());
}
NodeList childNodes = element.getChildNodes();
for (Node n : nodeListToIterator(childNodes)) {
if (n.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
fillNSMapWithPrefixesDeclaredInElement(nsMap, (Element) n);
}
}
/**
* Replace the current root element. If element is null, the current root element will be
* removed.
*
* @param document
* @param element
*/
public static void setDocumentElement(final Document document, final Element element) {
Element documentElement = document.getDocumentElement();
if (documentElement != null) {
document.removeChild(documentElement);
}
if (element != null) {
if (element.getOwnerDocument().equals(document)) {
document.appendChild(element);
return;
}
Node node = document.adoptNode(element);
document.appendChild(node);
}
}
/**
* Implementation independent version of the Node.isEqualNode() method. Matches the same
* algorithm as the nodeHashCode method.
* Two nodes are equal if and only if the following conditions are satisfied:
*
* The two nodes are of the same type.
* The following string attributes are equal: nodeName
, localName
,
* namespaceURI
, prefix
, nodeValue
. This is: they are
* both null
, or they have the same length and are character for character
* identical.
* The attributes
NamedNodeMaps
are equal. This is: they are both
* null
, or they have the same length and for each node that exists in one map
* there is a node that exists in the other map and is equal, although not necessarily at the
* same index.
* The childNodes
NodeLists
are equal. This is: they are both
* null
, or they have the same length and contain equal nodes at the same index.
* Note that normalization can affect equality; to avoid this, nodes should be normalized before
* being compared.
*
*
* For two DocumentType
nodes to be equal, the following conditions must also be
* satisfied:
*
* The following string attributes are equal: publicId
, systemId
,
* internalSubset
.
* The entities
NamedNodeMaps
are equal.
* The notations
NamedNodeMaps
are equal.
*
*
* @param a
* @param b
* @return true if and only if the nodes are equal in the manner explained above
*/
public static boolean nodesAreEqual(final Node a, final Node b) {
if (a == b) {
return true;
}
if ((a == null) || (b == null)) {
return false;
}
if (!Arrays.equals(getNodeAttributes(a), getNodeAttributes(b))) {
return false;
}
if (!namedNodeMapsAreEqual(a.getAttributes(), b.getAttributes())) {
return false;
}
if (!nodeListsAreEqual(a.getChildNodes(), b.getChildNodes())) {
return false;
}
return true;
}
/**
* NodelLists are equal if and only if their size is equal and the containing nodes at the same
* indexes are equal.
*
* @param a
* @param b
* @return
*/
private static boolean nodeListsAreEqual(final NodeList a, final NodeList b) {
if (a == b) {
return true;
}
if ((a == null) || (b == null)) {
return false;
}
if (a.getLength() != b.getLength()) {
return false;
}
for (int i = 0; i < a.getLength(); ++i) {
if (!nodesAreEqual(a.item(i), b.item(i))) {
return false;
}
}
return true;
}
/**
* NamedNodeMaps (e.g. the attributes of a node) are equal if for each containing node an equal
* node exists in the other map.
*
* @param a
* @param b
* @return
*/
private static boolean namedNodeMapsAreEqual(final NamedNodeMap a, final NamedNodeMap b) {
if (a == b) {
return true;
}
if ((a == null) || (b == null)) {
return false;
}
if (a.getLength() != b.getLength()) {
return false;
}
List listA = new ArrayList(a.getLength());
List listB = new ArrayList(a.getLength());
for (int i = 0; i < a.getLength(); ++i) {
listA.add(a.item(i));
listB.add(b.item(i));
}
Collections.sort(listA, ATTRIBUTE_NODE_COMPARATOR);
Collections.sort(listB, ATTRIBUTE_NODE_COMPARATOR);
for (Node n1 : listA) {
if (!nodesAreEqual(n1, listB.remove(0))) {
return false;
}
}
return true;
}
@SuppressWarnings("unchecked")
private static Comparable[] getNodeAttributes(final Node node) {
return new Comparable[] { Short.valueOf(node.getNodeType()), node.getNodeName(), node.getLocalName(), node.getNamespaceURI(), node.getPrefix(), node.getNodeValue() };
}
/**
* hashCode() implementation that is compatible with equals().
*
* @param node
* @return hash code for node
*/
public static int nodeHashCode(final Node node) {
assert node != null;
int hash = 1 + node.getNodeType();
hash = (hash * 17) + Arrays.hashCode(getNodeAttributes(node));
if (node.hasAttributes()) {
NamedNodeMap nodeMap = node.getAttributes();
for (int i = 0; i < nodeMap.getLength(); ++i) {
hash = (31 * hash) + nodeHashCode(nodeMap.item(i));
}
}
if (node.hasChildNodes()) {
NodeList childNodes = node.getChildNodes();
for (int i = 0; i < childNodes.getLength(); ++i) {
hash = (hash * 47) + nodeHashCode(childNodes.item(i));
}
}
return hash;
}
/**
* @param element
* @param attributeName
* @param value
*/
public static void setOrRemoveAttribute(final Element element, final String attributeName, final String value) {
if (value == null) {
element.removeAttribute(attributeName);
return;
}
element.setAttribute(attributeName, value);
}
/**
* @param node
* @param newName
* @return a new Element instance with desired name and content.
*/
@SuppressWarnings("unchecked")
public static T renameNode(final T node, final String newName) {
if (node instanceof Attr) {
Attr attributeNode = (Attr) node;
final Element owner = attributeNode.getOwnerElement();
if (owner == null) {
throw new IllegalArgumentException("Attribute has no owner " + node);
}
owner.removeAttributeNode(attributeNode);
owner.setAttribute(newName, attributeNode.getValue());
return (T) owner.getAttributeNode(newName);
}
if (node instanceof Element) {
Element element = (Element) node;
Node parent = element.getParentNode();
Document document = element.getOwnerDocument();
// Element newElement = document.createElement(newName);
final Element newElement = createElement(document, newName);
NodeList nodeList = element.getChildNodes();
List toBeMoved = new LinkedList();
for (int i = 0; i < nodeList.getLength(); ++i) {
toBeMoved.add(nodeList.item(i));
}
for (Node e : toBeMoved) {
element.removeChild(e);
newElement.appendChild(e);
}
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); ++i) {
newElement.setAttributeNode((Attr) attributes.item(i).cloneNode(true));
}
if (parent != null) {
parent.replaceChild(newElement, element);
}
return (T) newElement;
}
throw new IllegalArgumentException("Can not rename node " + node);
}
/**
* @param ownerDocument
* @param node
*/
public static void ensureOwnership(final Document ownerDocument, final Node node) {
if (ownerDocument != node.getOwnerDocument()) {
ownerDocument.adoptNode(node);
}
}
/**
* @param documentOrElement
* @return document that owns the given node
*/
public static Document getOwnerDocumentFor(final Node documentOrElement) {
if (Node.DOCUMENT_NODE == documentOrElement.getNodeType()) {
return (Document) documentOrElement;
}
return documentOrElement.getOwnerDocument();
}
private static Element createElement(final Document document, final String elementName) {
final String prefix = getPrefixOfQName(elementName);// .replaceAll("(:.*)|([^:])*", "");
final String namespaceURI = prefix.isEmpty() ? null : document.lookupNamespaceURI(prefix);
final Element element;
if (namespaceURI == null) {
element = document.createElement(elementName);
} else {
element = document.createElementNS(namespaceURI, elementName);
}
return element;
}
/**
* @param elementName
* @return
*/
private static String getPrefixOfQName(final String elementName) {
if (elementName.contains(":")) {
return elementName.replaceAll(":.*", "");
}
return "";
}
/**
* @param domNode
*/
public static void trim(final Node domNode) {
assert domNode != null;
assert (Node.TEXT_NODE != domNode.getNodeType());
List removeMe = new LinkedList();
NodeList childNodes = domNode.getChildNodes();
for (Node child : nodeListToIterator(childNodes)) {
if (Node.TEXT_NODE == child.getNodeType()) {
if ((child.getNodeValue() == null) || child.getNodeValue().trim().isEmpty()) {
removeMe.add((Text) child);
}
continue;
}
trim(child);
}
for (Text node : removeMe) {
Node parent = node.getParentNode();
if (parent != null) {
parent.removeChild(node);
}
}
}
/**
* @param childNodes
* @return
*/
private static Iterable nodeListToIterator(final NodeList nodeList) {
return new Iterable() {
@Override
public Iterator iterator() {
return new Iterator() {
private int pos = 0;
@Override
public boolean hasNext() {
return nodeList.getLength() > pos;
}
@Override
public Node next() {
return nodeList.item(pos++);
}
@Override
public void remove() {
throw new IllegalStateException();
}
};
}
};
}
/**
* @param node
* @return either a List with this node, or an empty list if node is null.
*/
public static List asList(final T node) {
if (node == null) {
return Collections.emptyList();
}
return Collections.singletonList(node);
}
/**
* @param previous
* @param newNode
*/
public static void replaceElement(final Element previous, final Element newNode) {
assert previous.getParentNode() != null;
Element parent = (Element) previous.getParentNode();
Document document = DOMHelper.getOwnerDocumentFor(parent);
DOMHelper.ensureOwnership(document, newNode);
parent.replaceChild(newNode, previous);
}
/**
* Simply removes all child nodes.
*
* @param node
*/
public static void removeAllChildren(final Node node) {
assert node != null;
assert node.getNodeType() != Node.ATTRIBUTE_NODE;
if (node.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
Element documentElement = ((Document) node).getDocumentElement();
if (documentElement != null) {
((Document) node).removeChild(documentElement);
}
return;
}
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
for (Node n = element.getFirstChild(); n != null; n = element.getFirstChild()) {
element.removeChild(n);
}
}
}
/**
* @param attributeNode
*/
public static void removeAttribute(final Attr attributeNode) {
if (attributeNode == null) {
return;
}
final Element owner = attributeNode.getOwnerElement();
if (owner == null) {
return;
}
owner.removeAttributeNode(attributeNode);
}
/**
* @param node
*/
private static void removeNode(final Node node) {
if (node == null) {
return;
}
while (node.hasChildNodes()) {
removeNode(node.getFirstChild());
}
final Node parent = node.getParentNode();
if (parent == null) {
return;
}
parent.removeChild(node);
}
/**
* @param parentElement
* @param o
* @return the new clone
*/
public static Node appendClone(final Element parentElement, final Node o) {
Node clone = o.cloneNode(true);
ensureOwnership(DOMHelper.getOwnerDocumentFor(parentElement), clone);
parentElement.appendChild(clone);
return clone;
}
/**
* @param existingNodes
*/
public static void removeNodes(final Iterable extends Node> existingNodes) {
for (Node e : existingNodes) {
removeNode(e);
}
}
/**
* @param projector
* @param domNode
* @return rendered XML as String
*/
public static String toXMLString(final XBProjector projector, final Node domNode) {
if (domNode == null) {
return "";
}
if (domNode.getNodeType() == Node.ATTRIBUTE_NODE) {
return domNode.toString();
}
try {
final StringWriter writer = new StringWriter();
projector.config().createTransformer().transform(new DOMSource(domNode), new StreamResult(writer));
final String output = writer.getBuffer().toString();
return output;
} catch (TransformerConfigurationException e) {
throw new XBException("Error while creating transformer",e);
} catch (TransformerException e) {
throw new XBException("Error while transforming document",e);
}
}
/**
* @param item
* @return Text content of this node, without child content.
*/
public static String directTextContent(final Node item) {
if ((item.getNodeType() == Node.TEXT_NODE)||(item.getNodeType() == Node.CDATA_SECTION_NODE)) {
return item.getNodeValue();
}
NodeList childNodes = item.getChildNodes();
if (childNodes == null) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < childNodes.getLength(); ++i) {
Node child = childNodes.item(i);
if ((child.getNodeType() != Node.TEXT_NODE)&&(child.getNodeType() != Node.CDATA_SECTION_NODE)) {
continue;
}
sb.append(child.getNodeValue());
}
return sb.toString();
}
/**
* Set text content of given element without removing existing child nodes. Text nodes are added
* after child element nodes always.
*
* @param elementToChange
* @param asString
*/
public static void setDirectTextContent(final Node elementToChange, final String asString) {
assert elementToChange.getNodeType() != Node.DOCUMENT_NODE;
if (Node.ATTRIBUTE_NODE == elementToChange.getNodeType()) {
elementToChange.setTextContent(asString);
return;
}
List nodes = new LinkedList();
List nodes2 = new LinkedList();
for (Node n : nodeListToIterator(elementToChange.getChildNodes())) {
if (Node.TEXT_NODE == n.getNodeType()) {
continue;
}
nodes.add(n);
}
if ((asString != null) && (!asString.isEmpty())) {
elementToChange.setTextContent(asString);
for (Node n : nodeListToIterator(elementToChange.getChildNodes())) {
if (Node.TEXT_NODE != n.getNodeType()) {
continue;
}
nodes.add(n);
}
}
removeAllChildren(elementToChange);
for (Node n : nodes) {
elementToChange.appendChild(n);
}
for (Node n : nodes2) {
elementToChange.appendChild(n);
}
}
}