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
Go to download
The coolest XML library for Java around. Define typesafe views (projections) to xml. Use XPath to read and write XML. Bind XML to Java collections. Requires at least Java6, supports Java8 features and has no further runtime dependencies.
/**
* 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.IOException;
import java.io.InputStream;
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.parsers.DocumentBuilder;
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.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xmlbeam.util.IOHelper;
/**
* 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 {
/**
*
*/
private static final String[] RESOURCE_PROTO_NAMES = new String[] { "resource://", "res://" };
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;
}
};
/**
* Remove all child elements with given node name. If nodeSelector is "*", then remove all
* children.
*
* @param element
* @param nodeSelector
*/
public static void removeAllChildrenBySelector(final Node element, final String nodeSelector) {
assert nodeSelector != null;
NodeList nodeList = element.getChildNodes();
List toBeRemoved = new LinkedList();
if ("*".equals(nodeSelector)) {
for (int i = 0; i < nodeList.getLength(); ++i) {
toBeRemoved.add(nodeList.item(i));
}
} else {
for (int i = 0; i < nodeList.getLength(); ++i) {
Node node = nodeList.item(i);
if (Node.ELEMENT_NODE != node.getNodeType()) {
continue;
}
if (selectorMatches(nodeSelector, (Element) node)) {
toBeRemoved.add(nodeList.item(i));
}
}
}
for (Node e : toBeRemoved) {
element.removeChild(e);
}
}
/**
* @param documentBuilder
* @param url
* @param requestProperties
* @param resourceAwareClass
* @return new document instance
* @throws IOException
*/
@SuppressWarnings("unchecked")
public static Document getDocumentFromURL(final DocumentBuilder documentBuilder, final String url, final Map requestProperties, final Class> resourceAwareClass) throws IOException {
try {
for (String resProto : RESOURCE_PROTO_NAMES) {
if (url.startsWith(resProto)) {
InputStream is = resourceAwareClass.getResourceAsStream(url.substring(resProto.length()));
InputSource source = new InputSource(is);
// source.setEncoding("MacRoman");
return documentBuilder.parse(source);
}
}
if (url.startsWith("http:") || url.startsWith("https:")) {
return documentBuilder.parse(IOHelper.httpGet(url, requestProperties), url);
}
Document document = documentBuilder.parse(url);
if (document == null) {
throw new IOException("Document could not be created form uri " + url);
}
return document;
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
/**
* Parse namespace prefixes defined in the documents root element.
*
* @param document
* source document.
* @return map with prefix->uri relationships.
*/
public static Map getNamespaceMapping(final Document document) {
Map map = new HashMap();
Element root = document.getDocumentElement();
if (root == null) {
// No document, no namespaces.
return Collections.emptyMap();
}
NamedNodeMap attributes = root.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())) {
map.put("xbdefaultns", attribute.getNodeValue());
continue;
}
map.put(attribute.getLocalName(), attribute.getNodeValue());
}
return map;
}
/**
* Treat the given path as relative path from a base element to an element and return this
* element. If any element on this path does not exist, create it.
*
* @param document
* @param base
* the relative path will start here
* @param pathToElement
* relative path to element.
* @return element with absolute path.
*/
public static Element ensureElementExists(final Document document, final Element base, final String pathToElement) {
assert base != null;
assert pathToElement != null;
if ((pathToElement.isEmpty()) || (".".equals(pathToElement))) {
return base;
}
Element element = base;
String splitme = pathToElement.replaceAll("(^/)|(/$)", "");
if (splitme.isEmpty()) {
throw new IllegalArgumentException("Path must not be empty. I don't know which element to return.");
}
for (String expectedElementName : splitme.split("/")) {
if (".".equals(expectedElementName)) {
continue;
}
if ("..".equals(expectedElementName)) {
Node parent = element.getParentNode();
if ((parent == null) || (Node.ELEMENT_NODE != parent.getNodeType())) {
throw new IllegalArgumentException("Reference to parent node via '..' bounced out of scope.");
}
element = (Element) parent;
continue;
}
if (expectedElementName.equals(element.getNodeName())) {
continue;
}
String[] nameAndSelector = splitToNameAndSelector(expectedElementName);
// String name = expectedElementName.replaceAll("\\[.*", "");
// String selector = expectedElementName.contains("[") ?
// expectedElementName.replaceAll(".*\\[", "").replaceAll("\\]$", "") : "";
Element child = findElementByTagNameAndSelector(element, nameAndSelector[0], nameAndSelector[1]);
if (child == null) {
// element = (Element)
// element.appendChild(document.createElement(expectedElementName));
element = (Element) element.appendChild(createElementByTagNameAndSelector(document, nameAndSelector[0], nameAndSelector[1]));
continue;
}
// forceSelectorOnElement(document, nameAndSelector[1], child);
element = child;
}
return element;
}
private static String[] splitToNameAndSelector(final String nameAndSelector) {
return new String[] {//
nameAndSelector.replaceAll("\\[.*", ""),//
nameAndSelector.contains("[") ? nameAndSelector.replaceAll(".*\\[", "").replaceAll("\\]$", "") : ""//
};
}
/**
* @param document
* @param expectedElementName
* @param selector
* @return
*/
private static Element createElementByTagNameAndSelector(final Document document, final String name, final String selector) {
// Element element = document.createElement(name);
final Element element = createElement(document, name);
forceSelectorOnElement(document, selector, element);
return element;
}
private static Element forceSelectorOnElement(final Document document, final String selector, final Element element) {
if (selector.isEmpty()) {
return element;
}
final String[] selectorValues = splitSelector(selector);
if (selectorValues[0].startsWith("@")) {
final String prefix = getPrefixOfQName(selectorValues[0].substring(1));
if (prefix.isEmpty()) {
element.setAttribute(selectorValues[0].substring(1), selectorValues[1]);
} else {
final String namespaceURI = ("xmlns".equals(prefix)) ? "http://www.w3.org/2000/xmlns/" : element.getNamespaceURI();
element.setAttributeNS(namespaceURI, selectorValues[0].substring(1), selectorValues[1]);
}
return element;
}
// Element child = document.createElement(selectorValues[0]);
final Element child = createElement(document, selectorValues[0]);
child.setTextContent(selectorValues[1]);
element.appendChild(child);
return element;
}
/**
* @param element
* @param expectedElementName
* @return
*/
private static Element findElementByTagNameAndSelector(final Element element, final String name, final String selector) {
NodeList nodeList = element.getElementsByTagName(name);
for (int i = 0; i < nodeList.getLength(); ++i) {
if (selectorMatches(selector, (Element) nodeList.item(i))) {
return (Element) nodeList.item(i);
}
}
return null;
}
/**
* @param selector
* @param item
* @return
*/
private static boolean selectorMatches(final String selector, final Element item) {
if (item == null) {
return false;
}
if ((selector == null) || (selector.isEmpty())) {
return true;
}
if (!selector.contains("=")) {
if (selector.matches("^@.+")) {
return item.hasAttribute(selector.substring(1));
}
return selector.equals(item.getNodeName());
}
String[] selectorValues = splitSelector(selector);
if (selectorValues[0].startsWith("@")) {
return selectorValues[1].equals(item.getAttribute(selectorValues[0].substring(1)));
}
NodeList nodeList = item.getElementsByTagName(selectorValues[0]);
for (int i = 0; i < nodeList.getLength(); ++i) {
if (selectorValues[1].equals(nodeList.item(i).getTextContent())) {
return true;
}
}
return false;
}
/**
* @param selector
* @return
*/
private static String[] splitSelector(String selector) {
if (!selector.matches("@?[^=]+=[^=]+")) {
return new String[] { selector, "" };
// throw new
// IllegalArgumentException("When using a predicate to create elements, predicate expressions must assign values via '=' to an attribute or direct child element.");
}
selector = selector.replaceAll("(^\\[)|(\\]$)", "");
String[] split = selector.split("=");
split[1] = split[1].replaceAll("(^')|('$)", "");
return split;
}
/**
* Treat the given path as absolute path to an element and return this element. If any element
* on this path does not exist, create it.
*
* @param document
* document
* @param pathToElement
* absolute path to element.
* @return element with absolute path.
*/
public static Element ensureElementExists(final Document document, final String pathToElement) {
assert document != null;
String splitme = pathToElement.replaceAll("(^/)|(/$)", "");
if (splitme.isEmpty()) {
throw new IllegalArgumentException("Path must not be empty. I don't know which element to return.");
}
Element element = document.getDocumentElement();
final String[] nameAndSelector = splitToNameAndSelector(splitme.replaceAll("/.*", ""));
if (element == null) { // No root element yet
element = createElementByTagNameAndSelector(document, nameAndSelector[0], nameAndSelector[1]);
// element = document.createElement(splitme.replaceAll("/.*", ""));
document.appendChild(element);
} else {
forceSelectorOnElement(document, nameAndSelector[1], element);
}
return ensureElementExists(document, element, splitme.replaceFirst("[^/]*/", ""));
}
/**
* 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;
}
// public static void trimTextNodes(Node e) {
// NodeList children = e.getChildNodes();
// for (int i = 0; i < children.getLength(); ++i) {
// Node child = children.item(i);
// if (Node.TEXT_NODE != child.getNodeType()) {
// trimTextNodes(child);
// continue;
// }
// String content = child.getNodeValue();
// if (content != null) {
// content = content.trim();
// if (!content.isEmpty()) {
// child.setNodeValue(content);
// continue;
// }
// }
// e.removeChild(child);
// }
// }
/**
* @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 element
* @param newName
* @return a new Element instance with desired name and content.
*/
public static Element renameElement(final Element element, final String newName) {
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);
}
return newElement;
}
/**
* @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());
// if (Node.TEXT_NODE == domNode.getNodeType()) {
// String content = domNode.getNodeValue();
// if ((content == null) || (content.trim().isEmpty())) {
// return;
// }
// domNode.setNodeValue(content.trim());
// assert domNode.getChildNodes().getLength()==0;
// return;
// }
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 o
* @return Convert a Nodelist into List
*/
public static List asList(final NodeList o) {
if (o == null) {
return null;
}
List list = new LinkedList();
for (Node n : nodeListToIterator(o)) {
list.add(n);
}
return list;
}
/**
* @param previous
* @param newNode
*/
public static void replaceElement(final Element previous, final Element newNode) {
assert previous.getParentNode() != null;
Element parent = (Element) previous.getParentNode();
parent.replaceChild(newNode, previous);
}
}