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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.io.IOException;
import java.io.InputStream;
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.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 Comparator super Node> ATTRIBUTE_NODE_COMPARATOR = new Comparator() {
private int compareMaybeNull(Comparable a, 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(Node o1, 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(Node element, 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(DocumentBuilder documentBuilder, final String url, Map requestProperties, final Class> resourceAwareClass) throws IOException {
try {
if (url.startsWith("resource://")) {
InputStream is = resourceAwareClass.getResourceAsStream(url.substring("resource://".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(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(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(Element element, String name, 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(String selector, 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(Document document, 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(Node a, 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(NodeList a, 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(NamedNodeMap a, 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(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(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(Element element, String attributeName, 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(Element element, 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 element
*/
public static void ensureOwnership(Document ownerDocument, Element element) {
if (ownerDocument != element.getOwnerDocument()) {
ownerDocument.adoptNode(element);
}
}
/**
* @param documentOrElement
* @return document that owns the given node
*/
public static Document getOwnerDocumentFor(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(String elementName) {
if (elementName.contains(":")) {
return elementName.replaceAll(":.*", "");
}
return "";
}
}