org.magicwerk.brownies.jdom.JdomTools Maven / Gradle / Ivy
/*
* Copyright 2010 by Thomas Mauch
*
* 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.
*
* $Id$
*/
package org.magicwerk.brownies.jdom;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import org.apache.commons.lang3.mutable.MutableInt;
import org.jdom2.Attribute;
import org.jdom2.Comment;
import org.jdom2.Content;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.Text;
import org.jdom2.Verifier;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.DOMOutputter;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.transform.JDOMResult;
import org.jdom2.transform.JDOMSource;
import org.magicwerk.brownies.collections.GapList;
import org.magicwerk.brownies.collections.ICollectionTools;
import org.magicwerk.brownies.collections.IList;
import org.magicwerk.brownies.core.CharsetTools;
import org.magicwerk.brownies.core.CheckTools;
import org.magicwerk.brownies.core.ObjectHelper;
import org.magicwerk.brownies.core.ObjectTools;
import org.magicwerk.brownies.core.StringTools;
import org.magicwerk.brownies.core.function.Predicates;
import org.magicwerk.brownies.core.net.NetTools;
import org.magicwerk.brownies.core.regex.RegexTools;
import org.magicwerk.brownies.core.strings.StringStreamer;
import org.magicwerk.brownies.jdom.XmlProcessor.Validation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.XMLReader;
/**
* Class {@link JdomTools} contains utilities for working with JDOM 2.
*/
public class JdomTools {
static final Logger LOG = LoggerFactory.getLogger(JdomTools.class);
public static final Namespace NsXsi = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
private static final String AttrNoNamespaceSchemaLocation = "noNamespaceSchemaLocation";
private static final String AttrSchemaLocation = "schemaLocation";
//-- Namespaces
//namespace
// - xmlns (available as Namespace) maps namespace prefix to namespace uri
// - xsi:schemaLocation maps several namespace uri to schema location
//
//[Namespace: prefix "" is mapped to URI ""]
//[Namespace: prefix "xsi" is mapped to URI "http://www.w3.org/2001/XMLSchema-instance"]
//[Namespace: prefix "" is mapped to URI "http://wm6.swisslog.com/schema/wm6process"]]
//users-xsd.xml:
//
//
//
//users-xsd-ns.xml:
//
//
public static class NamespaceInfo {
Namespace namespace;
String schemaLocation;
NamespaceInfo(Namespace namespace, String schemaLocation) {
this.namespace = CheckTools.checkNonNull(namespace);
this.schemaLocation = schemaLocation;
}
/** Getter for {@link #namespace} */
public Namespace getNamespace() {
return namespace;
}
public String getPrefix() {
return namespace.getPrefix();
}
public String getUri() {
return namespace.getURI();
}
/** Setter for {@link #namespace} */
public NamespaceInfo setNamespace(Namespace namespace) {
this.namespace = namespace;
return this;
}
/** Getter for {@link #schemaLocation} */
public String getSchemaLocation() {
return schemaLocation;
}
/** Setter for {@link #schemaLocation} */
public NamespaceInfo setSchemaLocation(String schemaLocation) {
this.schemaLocation = schemaLocation;
return this;
}
@Override
public int hashCode() {
return ObjectHelper.implHashCode(this, NamespaceInfo::getNamespace);
}
@Override
public boolean equals(Object obj) {
return ObjectHelper.implEquals(this, obj, NamespaceInfo::getNamespace);
}
@Override
public String toString() {
return "NamespaceInfo [prefix=" + getPrefix() + ", uri=" + getUri() + ", schemaLocation=" + schemaLocation + "]";
}
}
//
/** Returns value of xsi:noNamespaceSchemaLocation, null if not defined */
public static String getNoNamespaceSchemaLocation(Element elem) {
return elem.getAttributeValue(AttrNoNamespaceSchemaLocation, NsXsi);
}
/**
* Add a schema definition without namespace to specified element.
*
* @param elem base element
* @param location schema location
*/
public static void addNoNamespaceSchemaLocation(Element elem, String location) {
elem.addNamespaceDeclaration(NsXsi);
elem.setAttribute(AttrNoNamespaceSchemaLocation, location, NsXsi);
}
/** Returns value of xsi:schemaLocation, null if not defined */
public static String getSchemaLocation(Element elem) {
return elem.getAttributeValue(AttrSchemaLocation, NsXsi);
}
/**
* Add a schema definition with namespace to specified element.
* The namespace is set for the specified element and all its descendants.
*
* @param elem base element
* @param location schema location
*/
public static void addSchemaLocation(Element elem, String location) {
elem.addNamespaceDeclaration(NsXsi);
elem.setAttribute(AttrSchemaLocation, location, NsXsi);
setNamespaceAll(elem, Namespace.getNamespace(location));
}
//
/** Returns {@link Namespace} for element (never null) */
public static Namespace getNamespace(Element elem) {
return elem.getNamespace();
}
/** Returns {@link NamespaceInfo} for element (never null) */
public static NamespaceInfo getNamespaceInfo(Element elem) {
Namespace ns = getNamespace(elem);
Map nsMap = getNamespaceSchemaLocations(elem);
String loc = (nsMap != null) ? nsMap.get(ns.getURI()) : null;
return new NamespaceInfo(ns, loc);
}
public static NamespaceInfo getNamespaceInfo(Element elem, String nsUri) {
List nss = elem.getNamespacesInScope();
Namespace ns = ICollectionTools.getIf(nss, n -> n.getURI().equals(nsUri));
if (ns == null) {
return null;
}
Map nsMap = getNamespaceSchemaLocations(elem);
String loc = (nsMap != null) ? nsMap.get(ns.getURI()) : null;
return new NamespaceInfo(ns, loc);
}
public static IList getNamespacesInfo(Element elem) {
List nss = elem.getNamespacesInScope();
Map nsMap = getNamespaceSchemaLocations(elem);
IList nis = GapList.create();
for (Namespace ns : nss) {
String loc = (nsMap != null) ? nsMap.get(ns.getURI()) : null;
nis.add(new NamespaceInfo(ns, loc));
}
return nis;
}
public static boolean hasSchemaNamespace(Element elem) {
List nss = elem.getNamespacesInScope();
return ICollectionTools.containsIf(nss, Predicates.is(NsXsi));
}
/** Returns map with information in xsi:schemaLocation, mapping the URI to the location */
public static Map getNamespaceSchemaLocations(Element elem) {
String schemaLocations = getSchemaLocation(elem);
if (schemaLocations == null) {
return null;
}
Map nsMap = new HashMap();
IList locations = RegexTools.split(schemaLocations, Pattern.compile("\\s+"));
int numNs = locations.size() / 2;
for (int i = 0; i < numNs; i++) {
String uri = locations.get(2 * i);
String loc = (2 * i + 1 < locations.size()) ? locations.get(2 * i + 1) : null;
nsMap.put(uri, loc);
}
return nsMap;
}
/**
* Set namespace for specified element and all its descendants.
*
* @param elem starting element
* @param ns namespace to set
*/
public static void setNamespaceAll(Element elem, Namespace ns) {
elem.setNamespace(ns);
for (Object obj : elem.getChildren()) {
Element el = (Element) obj;
el.setNamespace(ns);
setNamespaceAll(el, ns);
}
}
// Validity of XML text
public static boolean isValidChar(char c) {
return Verifier.isXMLCharacter(c);
}
public static boolean isValidString(String str) {
for (int i = 0; i < str.length(); i++) {
if (!isValidChar(str.charAt(i))) {
return false;
}
}
return true;
}
/**
* Make string a valid XML string by replacing invalid characters with spaces.
*
* @param str input string
* @return valid XML string
*/
public static String makeStringValid(String str) {
StringStreamer ss = new StringStreamer(str).setAutoFlush(false);
while (true) {
int c = ss.readChar();
if (c == -1) {
break;
} else if (!isValidChar((char) c)) {
ss.useWriteBuffer();
ss.setCharAt(-1, ' ');
}
}
return ss.stringAt(0, ss.length());
}
//--
public static Document fromString(String xml) {
try {
SAXBuilder builder = new SAXBuilder(false);
StringReader sr = new StringReader(xml);
Document doc = builder.build(sr);
return doc;
} catch (Exception e) {
// IOException, JDOMException
throw new JdomException(e);
}
}
public static String toString(Document doc) {
try {
XMLOutputter serializer = getPrettySerializer();
StringWriter sw = new StringWriter();
serializer.output(doc, sw);
return sw.toString();
} catch (IOException e) {
throw new JdomException(e);
}
}
/**
* Read XML document.
* The document not validated (mode Validation.NONE).
*
* @param url URL or local file path of XML document
* @return read XML document
* @throws JdomException if loading fails
*/
public static Document readDocument(String url) {
return readDocument(url, Validation.NONE);
}
/**
* Read XML document.
*
* @param url URL or local file path of XML document
* @param validation how to validate XML document
* @return read XML document
* @throws JdomException if loading fails
*/
public static Document readDocument(String url, Validation validation) {
XmlSource source = new XmlSource().setInput(url).setValidation(validation);
return readDocument(source);
}
/**
* Read XML document.
*
* @param source source of XML document
* @return read XML document
* @throws JdomException if loading fails
*/
public static Document readDocument(XmlSource source) {
return readDocument(source, new XmlProcessor());
}
public static Document parseDocument(String str) {
return readDocument(new XmlSource().setString(str));
}
public static Element parse(String str) {
return parseDocument(str).detachRootElement();
}
/**
* Read XML document.
*
* @param source source of XML document
* @param proc XML processor to read document from
* @return read XML document
* @throws JdomException if loading fails
*/
public static Document readDocument(final XmlSource source, final XmlProcessor proc) {
try {
SAXBuilder builder = new SAXBuilder() {
@Override
protected XMLReader createParser() throws JDOMException {
return proc.getXmlReader(source);
}
};
Document doc = builder.build(source.getInputSource());
return doc;
} catch (Exception e) {
String msg = "Error on reading document from " + source.toString();
throw new JdomException(msg, e);
}
}
/**
* Write XML document to file. The XML document is formatted in a human readable way.
*
* @param doc document to write
* @param file file to write
* @throws JdomException if writing fails
*/
public static void writeDocument(Document doc, String file) {
writeDocument(doc, file, true);
}
/**
* Write XML document to file. The XML document is formatted in a human readable way.
*
* @param doc document to write
* @param file file to write
* @param pretty true to beautify the XML output
* @throws JdomException if writing fails
*/
public static void writeDocument(Document doc, String file, boolean pretty) {
LOG.debug("writeDocument: {}", file);
try {
XMLOutputter serializer = getSerializer(pretty);
serializer.output(doc, new FileOutputStream(file));
} catch (Exception e) {
// IOException
throw new JdomException(e);
}
}
public static void writeDocument(Document doc, String file, XMLOutputter serializer) {
LOG.debug("writeDocument: {}", file);
try {
serializer.output(doc, new FileOutputStream(file));
} catch (Exception e) {
// IOException
throw new JdomException(e);
}
}
/**
* Create a SAXSource for the given JDOM document.
*
* @param doc JDOM document
* @return SAXSource
*/
public static SAXSource getSAXSource(Document doc) {
// Create an instance of org.jdom.transform.JDOMSource which extends javax.xml.transform.sax.SAXSource.
JDOMSource jds = new JDOMSource(doc);
return jds;
}
/**
* Create a DOMSource for the given JDOM document.
*
* @param doc JDOM document
* @return DOMSource
*/
public static DOMSource getDOMSource(Document doc) {
// Create an instance of javax.xml.transform.DOMSource using the org.jdom.output.DOMOutputter
try {
DOMOutputter outputter = new DOMOutputter();
org.w3c.dom.Document documentW3C = outputter.output(doc);
DOMSource ds = new DOMSource(documentW3C);
return ds;
} catch (JDOMException e) {
throw new JdomException(e);
}
}
/**
* Create transformer using the given file.
*
* @param file file to load
* @return created transformer
*/
public static Transformer getTransformer(String file) {
Transformer transformer;
try {
TransformerFactory factory = TransformerFactory.newInstance();
transformer = factory.newTransformer(new javax.xml.transform.stream.StreamSource(NetTools.getFileUriString(file)));
} catch (TransformerConfigurationException e) {
throw new RuntimeException(e);
}
return transformer;
}
/**
* Create transformer using the given file and transform the given document.
*
* @param doc JDOM input document
* @param file XSLT file
* @return transformed XML document
*/
public static Document transform(Document doc, String file) {
Transformer transformer = getTransformer(file);
return transform(doc, transformer);
}
/**
* Transform a JDOM document using a JAXP transformer.
* The transformation must return a valid XML document.
*
* @param doc JDOM input document
* @param transformer JAXP transformer
* @return transformed XML document
*/
public static Document transform(Document doc, Transformer transformer) {
try {
JDOMSource in = new JDOMSource(doc);
JDOMResult out = new JDOMResult();
transformer.transform(in, out);
return out.getDocument();
} catch (TransformerException e) {
throw new RuntimeException("XSLT Transformation failed", e);
}
}
/**
* Transform a JDOM document using a JAXP transformer.
* The transformation can return an arbitrary list of XML nodes.
*
* @param doc JDOM document
* @param transformer JAXP transformer
* @return List of JDOM nodes
*/
public static List> transformList(Document doc, Transformer transformer) {
try {
JDOMSource in = new JDOMSource(doc);
JDOMResult out = new JDOMResult();
transformer.transform(in, out);
return out.getResult();
} catch (TransformerException e) {
throw new JdomException("XSLT Transformation failed", e);
}
}
/**
* Set XML schema to use for the root element of the document.
*
* @param doc XML document
* @param xsdFile XSD file
*/
public static void setXMLSchema(Document doc, String xsdFile) {
setXMLSchema(doc.getRootElement(), xsdFile);
}
/**
* Set XML schema to use for this element.
*
* @param element element
* @param xsdFile XSD file
*/
public static void setXMLSchema(Element element, String xsdFile) {
Namespace nsXsi = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
element.addNamespaceDeclaration(nsXsi);
element.setAttribute("noNamespaceSchemaLocation", xsdFile, nsXsi);
}
//--- Printing ---
/**
* Returns textual representation of attribute as string.
*
* @param attr XML attribute
* @return textual representation of attribute
*/
public static String print(Attribute attr) {
// TODO
// Currently no escaping is done on the attribute value as
// outputting attributes with escaped entities is not feasible easily
// right now:
// Have a look at the class XMLOutputter, functions printAttributes()
// and
// escapeAttributeEntities()
return new StringBuffer().append(attr.getQualifiedName()).append("=\"").append(attr.getValue()).append("\"").toString();
}
/**
* Returns textual representation of elements as string.
*
* @param elems list of XML elements
* @return textual representation of elements
*/
public static String print(List> elems) {
StringBuilder buf = new StringBuilder();
XMLOutputter serializer = getPrettySerializer();
for (Iterator> i = elems.iterator(); i.hasNext();) {
Object elem = i.next();
if (elem instanceof Element) {
buf.append(serializer.outputString((Element) elem));
} else if (elem instanceof Document) {
buf.append(serializer.outputString((Document) elem));
} else {
buf.append(elem);
}
}
return buf.toString();
}
/**
* Returns textual representation of document as string.
* The string is formatted to be human readable.
* It will include an XML definition like "<?xml version="1.0">".
*
* @param doc XML document
* @return textual representation of document
*/
public static String print(Document doc) {
return print(doc, true);
}
/**
* Returns textual representation of document as string.
* It will include an XML definition like "<?xml version="1.0">".
*
* @param doc XML document
* @param pretty true to pretty format the document
* @return textual representation of document
*/
public static String print(Document doc, boolean pretty) {
XMLOutputter serializer = getSerializer(pretty);
return serializer.outputString(doc);
}
/**
* Returns textual representation of element as string.
*
* @param elem XML element
* @return textual representation of element
*/
public static String printElement(Object elem) {
return print((Element) elem);
}
/**
* Returns textual representation of element list as string.
*
* @param elems XML elements
* @return textual representation of elements
*/
public static String printElements(List elems) {
return print(elems);
}
/**
* Returns textual representation of element as string.
*
* @param elem XML element
* @return textual representation of element
*/
public static String print(Element elem, boolean pretty) {
XMLOutputter serializer = getSerializer(pretty);
return serializer.outputString(elem);
}
public static String print(Element elem) {
return print(elem, true);
}
// --- XMLOutputter
static XMLOutputter getSerializer(boolean pretty) {
if (pretty) {
return getPrettySerializer();
} else {
return getRawSerializer();
}
}
/**
* The encoding used is ISO-8859-1.
*
* @return serializer for pretty XML output
*/
public static XMLOutputter getPrettySerializer() {
return getPrettySerializer("UTF-8");
}
/**
* @param encoding encoding to use
* @return serializer for pretty XML output
*/
public static XMLOutputter getPrettySerializer(String encoding) {
XMLOutputter serializer = new XMLOutputter();
Format format = Format.getPrettyFormat();
format.setEncoding(encoding);
// Needed if HTML documents are written out as XML
format.setExpandEmptyElements(true);
serializer.setFormat(format);
return serializer;
}
public static XMLOutputter getOutputter(Format format) {
XMLOutputter outputter = new XMLOutputter();
outputter.setFormat(format);
return outputter;
}
/**
* The encoding used is UTF-8.
*
* @return serializer for raw XML output
*/
public static XMLOutputter getRawSerializer() {
return getRawSerializer("UTF-8");
}
/**
* @param encoding encoding to use
* @return serializer for raw XML output
*/
public static XMLOutputter getRawSerializer(String encoding) {
XMLOutputter serializer = new XMLOutputter();
Format format = Format.getRawFormat();
format.setEncoding(encoding);
// Needed if HTML documents are written out as XML
format.setExpandEmptyElements(true);
serializer.setFormat(format);
return serializer;
}
// ---
/**
* Add element before the given reference element with the same parent.
*
* @param add element to add
* @param ref reference element
*/
public static void addBefore(Element add, Element ref) {
Element parent = ref.getParentElement();
int index = parent.indexOf(ref);
parent.addContent(index, add);
}
/**
* Add element after the given reference element with the same parent.
*
* @param add element to add
* @param ref reference element
*/
public static void addAfter(Element add, Element ref) {
Element parent = ref.getParentElement();
int index = parent.indexOf(ref);
parent.addContent(index + 1, add);
}
// --- Handling namespaces
/**
* Removes all namespaces from specified XML document.
*
* @param doc XML document
*/
public static void stripNamespaces(Document doc) {
Element root = doc.getRootElement();
stripNamespaces(root);
}
/**
* Removes all namespaces from specifed XML element, its attributes and all its children.
*
* @param elem XML element
*/
public static void stripNamespaces(Element elem) {
// This element
List nss = GapList.create(elem.getAdditionalNamespaces());
for (Namespace ns : nss) {
elem.removeNamespaceDeclaration(ns);
}
elem.setNamespace(null);
// Attributes
List> attributes = elem.getAttributes();
Iterator> attrIter = attributes.iterator();
while (attrIter.hasNext()) {
Attribute attr = (Attribute) attrIter.next();
attr.setNamespace(null);
}
// Child elememts
List> elements = elem.getChildren();
Iterator> elemIter = elements.iterator();
while (elemIter.hasNext()) {
Element child = (Element) elemIter.next();
stripNamespaces(child);
}
}
/**
* Recursively remove all text elements which contain only whitespaces.
*
* @param doc document where whitespace elements should be removed
*/
public static void stripWhitespaceTexts(Document doc) {
stripWhitespaceTexts(doc.getRootElement());
}
/**
* Recursively remove all text elements which contain only whitespaces.
*
* @param elem element where whitespace elements should be removed
*/
public static void stripWhitespaceTexts(Element elem) {
List> content = elem.getContent();
Iterator> contIter = content.iterator();
while (contIter.hasNext()) {
Object node = contIter.next();
if (node instanceof Element) {
stripWhitespaceTexts((Element) node);
} else if (node instanceof Text) {
Text text = (Text) node;
if (text.getTextTrim().isEmpty()) {
contIter.remove();
}
}
}
}
/**
* Removes recursively all comments from XML element.
*
* @param elem XML element where comments should be removed recursively
*/
public static void stripComments(Element elem) {
List> elements = elem.getContent();
Iterator> elemIter = elements.iterator();
while (elemIter.hasNext()) {
Content content = (Content) elemIter.next();
if (content instanceof Comment) {
elemIter.remove();
} else if (content instanceof Element) {
stripComments((Element) content);
}
}
}
// --- Handling attributes
public static List getAttributes(Element elem) {
return elem.getAttributes();
}
// Map Format 1
/**
* Return attribute with specified local name, null if no such attribute.
*/
public static Attribute getAttribute(Element elem, String name) {
for (Attribute attr : elem.getAttributes()) {
if (attr.getName().equals(name)) {
return attr;
}
}
return null;
}
/**
* @param elem element
* @param name attribute name
* @return attribute with name, null if not found
*/
public static String getAttributeValue(Element elem, String name) {
Attribute attr = getAttribute(elem, name);
if (attr != null) {
return attr.getValue();
}
return null;
}
/**
* Set or remove attribute from given element.
* If value is null, the attribute is removed if it exists, or nothing is done.
*
* @param elem element
* @param name attribute name
* @param value value to set, null for remove
*/
public static void setAttributeValue(Element elem, String name, String value) {
if (value == null) {
removeAttribute(elem, name);
} else {
Attribute attr = getAttribute(elem, name);
if (attr != null) {
elem.setAttribute(name, value);
} else {
elem.setAttribute(name, value);
}
}
}
/**
* Remove given attribute from given element.
*
* @param elem element
* @param name name of attribute to remove
*/
public static void removeAttribute(Element elem, String name) {
List attributes = elem.getAttributes();
Iterator i = attributes.iterator();
while (i.hasNext()) {
Attribute a = i.next();
if (a.getName().equals(name)) {
i.remove();
return;
}
}
}
/**
* Remove given attributes from given element. This function does recurse into its child elements.
*
* @param elem XML element
* @param attr name of attribute to remove
*/
public static void stripAttribute(Element elem, String attr) {
// This element
removeAttribute(elem, attr);
// Child elememts
List> elements = elem.getChildren();
Iterator> elemIter = elements.iterator();
while (elemIter.hasNext()) {
Element child = (Element) elemIter.next();
stripAttribute(child, attr);
}
}
/**
* Remove all namespaces from the specified element and its attributes.
*/
public static void removeNamespaces(Element elem) {
removeNamespaces(elem, false);
}
/**
* Remove namespaces from all elements and attributes.
* The parameter recursive controls whether also all namespaces will be removed from the child elements.
*/
public static void removeNamespaces(Element elem, boolean recursive) {
setNamespace(elem, null, recursive, true);
}
/**
* Checks for the default namespace.
* A default namespace has no prefix defined.
*
* @param ns namespace
* @return true if this is the default namespace
*/
public static boolean isDefaultNamespace(Namespace ns) {
return (ns != null && ns.getPrefix().length() == 0);
}
/**
* Sets namespace for elements.
* Note that the namespace of attributes are not changed:
* attributes rarely needs namespaces and there is also no default namespace
*
* @param elem element
* @param ns namespace to set
* @param recursive true to recurse into child elements
* @param attributes true to set namespace for attributes too
*/
public static void setNamespace(Element elem, Namespace ns, boolean recursive, boolean attributes) {
if (attributes) {
if (isDefaultNamespace(ns)) {
// The XML standard forbids attributes to be in the default namespace
attributes = false;
}
}
elem.setNamespace(ns);
if (attributes) {
List attrs = getAttributes(elem);
for (Attribute attr : attrs) {
attr.setNamespace(ns);
}
}
if (recursive) {
List children = getChildren(elem);
for (Element child : children) {
setNamespace(child, ns, recursive, attributes);
}
}
}
/**
* Remove all attributes from the specified element.
*
* @param elem element
*/
public static void removeAttributes(Element elem) {
List> attributes = elem.getAttributes();
attributes.clear();
}
/**
* Remove all attributes from the specified element.
*
* @param elem element
* @param recursive true to recurse into child elements
*/
public static void removeAttributes(Element elem, boolean recursive) {
// This element
elem.getAttributes().clear();
// Child elememts
if (recursive) {
List children = getChildren(elem);
for (Element child : children) {
removeAttributes(child, recursive);
}
}
}
// --- Handling text ---
/**
* Recursively modifies all text nodes.
*
* @param elem element element to handle
* @param textMode mode how text should be modified
* @param removeEmpty true to remove text nodes which are empty after change
*/
public static void changeText(Element elem, TextMode textMode, boolean removeEmpty) {
changeText(elem, true, textMode, removeEmpty);
}
/**
* Modifies all text nodes.
*
* @param elem element element to handle
* @param recurse true to recurse into child elements
* @param textMode mode how text should be modified
* @param removeEmpty true to remove text nodes which are empty after change
*/
public static void changeText(Element elem, boolean recurse, TextMode textMode, boolean removeEmpty) {
doChangeText(elem, true, textMode, removeEmpty);
}
/**
* Recursively removes all empty text nodes.
*
* @param elem element which is recursively traversed
*/
public static void removeEmptyText(Element elem) {
removeEmptyText(elem, false);
}
/**
* Removes all empty text nodes.
*
* @param elem element which is recursively traversed
* @param recurse true to recurse into child elements
*/
public static void removeEmptyText(Element elem, boolean recurse) {
doChangeText(elem, recurse, TextMode.PRESERVE, true);
}
/**
* Recursively removes all text nodes.
*
* @param elem element which is recursively traversed
*/
public static void removeText(Element elem) {
removeText(elem, false);
}
public static void removeText(Element elem, boolean recurse) {
doChangeText(elem, recurse, TextMode.REMOVE, true);
}
/**
* Modes how text will be modified.
*/
public enum TextMode {
/** Text is not modified */
PRESERVE,
/** Text is not normalized */
NORMALIZE,
/** Text is trimmed */
TRIM,
/** Text is not modified */
REMOVE
}
/**
* Modifies all text nodes.
*
* @param elem element element to handle
* @param recurse true to recurse into child elements
* @param textMode mode how text should be modified
* @param removeEmpty true to remove text nodes which are empty after change
*/
static void doChangeText(Element elem, boolean recurse, TextMode textMode, boolean removeEmpty) {
// Child elememts
List> content = elem.getContent();
int numContent = content.size();
int i = 0;
while (i < numContent) {
// Check for non-element content
if (!(content.get(i) instanceof Text)) {
if (recurse && content.get(i) instanceof Element) {
doChangeText((Element) content.get(i), recurse, textMode, removeEmpty);
}
i++;
continue;
}
// Handle text
Text text = (Text) content.get(i);
String str;
switch (textMode) {
case PRESERVE:
str = text.getText();
break;
case NORMALIZE:
str = text.getTextNormalize();
text.setText(str);
break;
case TRIM:
str = text.getTextTrim();
text.setText(str);
break;
case REMOVE:
str = "";
break;
default:
throw new IllegalArgumentException("Illegal case");
}
if (!removeEmpty || str.length() > 0) {
i++;
continue;
}
// Empty text must be removed
content.remove(i);
numContent--;
}
}
public static void stripEmptyElement(Element elem, String stripName) {
// Strip children
doStripEmptyElement(elem, stripName);
// Check whether this element must be stripped itself
if (elem.getName().equals(stripName) && elem.getContentSize() == 0) {
if (elem.isRootElement())
throw new IllegalArgumentException("Cannot strip root element");
Element parent = elem.getParentElement();
int elemIndex = parent.indexOf(elem);
parent.removeContent(elemIndex);
moveContent(parent, elemIndex, elem.getContent());
}
}
// Empty elements (i.e. elements without child and text nodes) are stripped
private static void doStripEmptyElement(Element elem, String stripName) {
// This function is called by stripElement and does not check the
// element itself, but only its children.
// Child elememts
List> content = elem.getContent();
int numContent = content.size();
int i = 0;
while (i < numContent) {
// Check for non-element content
if (!(content.get(i) instanceof Element)) {
i++;
continue;
}
// Handle element
Element child = (Element) content.get(i);
if (!(child.getName().equals(stripName) && child.getContentSize() == 0)) {
doStripEmptyElement(child, stripName);
i++;
continue;
}
// Empty element must be stripped
content.remove(i);
numContent--;
}
}
public static void stripElement(Element elem, String stripName) {
// Strip children
doStripElement(elem, stripName);
// Check whether this element must be stripped itself
if (elem.getName().equals(stripName)) {
if (elem.isRootElement())
throw new IllegalArgumentException("Cannot strip root element");
Element parent = elem.getParentElement();
int elemIndex = parent.indexOf(elem);
parent.removeContent(elemIndex);
moveContent(parent, elemIndex, elem.getContent());
}
}
private static void doStripElement(Element elem, String stripName) {
// This function is called by stripElement and does not check the
// element itself, but only its children.
// Child elememts
List> content = elem.getContent();
int numContent = content.size();
int i = 0;
while (i < numContent) {
// Check for non-element content
if (!(content.get(i) instanceof Element)) {
i++;
continue;
}
// Handle element
Element child = (Element) content.get(i);
if (!child.getName().equals(stripName)) {
doStripElement(child, stripName);
i++;
continue;
}
// Element must be stripped
content.remove(i);
List> childContent = child.getContent();
moveContent(elem, i, childContent);
// Note that childContent.size() is 0 after calling moveContent()
numContent = content.size();
}
}
/**
* Moves all content to the target element. The content is detached from its parent before adding it to the target.
*/
public static void moveContent(Element target, Collection> content) {
moveContent(target, target.getContentSize(), content);
}
public static void moveContent(Element target, Element source) {
moveContent(target, source.getContent());
}
/**
* Moves element to the target element. The element is detached from its parent before adding it to the target.
*/
public static void moveElement(Element target, Element elem) {
target.addContent(elem.detach());
}
/**
* Moves attribute to the target element. The attribute is detached from its parent before adding it to the target.
*/
public static void moveAttribute(Element target, Attribute attr) {
target.setAttribute(attr.detach());
}
/**
* Move element to top position in its current parent element.
*
* @param elem element to move
*/
public static void moveElementTop(Element elem) {
List content = elem.getParentElement().getContent();
int index = content.indexOf(elem);
if (index > 0) {
content.remove(index);
content.add(0, elem);
}
}
/**
* Move element one position up in its current parent element.
*
* @param elem element to move
*/
public static void moveElementUp(Element elem) {
List content = elem.getParentElement().getContent();
int index = content.indexOf(elem);
if (index > 0) {
content.remove(index);
content.add(index - 1, elem);
}
}
/**
* Move element to bottom position in its current parent element.
*
* @param elem element to move
*/
public static void moveElementBottom(Element elem) {
List content = elem.getParentElement().getContent();
int index = content.indexOf(elem);
int size = content.size();
if (index < size - 1) {
content.remove(index);
content.add(size - 1, elem);
}
}
/**
* Move element one position down in its current parent element.
*
* @param elem element to move
*/
public static void moveElementDown(Element elem) {
List content = elem.getParentElement().getContent();
int index = content.indexOf(elem);
int size = content.size();
if (index < size - 1) {
content.remove(index);
content.add(index + 1, elem);
}
}
public static void moveElementBefore(Element elem, Element target) {
elem.detach();
List content = target.getParentElement().getContent();
int index = content.indexOf(target);
content.add(index, elem);
}
public static void moveElementAfter(Element elem, Element target) {
elem.detach();
List content = target.getParentElement().getContent();
int index = content.indexOf(target);
content.add(index + 1, elem);
}
/**
* Moves all content of the source to the target element. The content is detached from its parent before adding it to the end of the target.
*
* @param target target where content is added
* @param source source where content is taken from and detached
*/
public static void moveElementContent(Element target, Element source) {
moveContent(target, -1, source.getContent());
}
/**
* Moves all content to the target element. The content is detached from its parent before adding it to the target.
*
* @param target element where content should be moved to
* @param index index where element should be added, -1 for adding at the end
* @param content content to move
*/
public static void moveContent(Element target, int index, Collection> content) {
// Note the simple function below will fail because a element can only
// have one parent at a time:
// target.addContent(index, content);
for (Iterator> i = content.iterator(); i.hasNext();) {
Content c = (Content) i.next();
// Note that i.remove() automatically resets the elements parent
// to null, so elem.detach() is not needed when adding afterwards
i.remove();
if (c.getParent() != null) {
c.detach();
}
if (index == -1) {
target.addContent(c);
} else {
target.addContent(index, c);
index++;
}
}
}
public static Element copy(Element elem) {
return elem.clone();
}
public static Document copy(Document doc) {
return doc.clone();
}
/**
* Copy content of source into target. The new content is added at then end.
*/
public static void copyElement(Element target, Element source) {
target.addContent(source.clone());
}
/**
* Copy content of source (not source itself) into target. The new content is added at then end.
*/
public static void copyElementContent(Element target, Element source) {
copyContent(target, target.getContentSize(), source.getContent());
}
public static void copyElementContent(Element target, int index, Element source) {
copyContent(target, index, source.getContent());
}
/**
* Copy content into target. The new content is added at the specified index.
*/
public static void copyContent(Element target, int index, Collection> content) {
for (Iterator> i = content.iterator(); i.hasNext();) {
Content c = (Content) i.next();
target.addContent(index, c.clone());
index++;
}
}
/**
* Replaces given element with new content.
*/
public static void replace(Element oldElem, Element newElem) {
Element parent = oldElem.getParentElement();
int index = parent.indexOf(oldElem);
parent.removeContent(index);
parent.addContent(index, newElem);
}
public static int getChildCount(Element elem) {
Element parent = elem.getParentElement();
String name = elem.getName();
List children = parent.getChildren(name);
return children.size();
}
public static int getChildIndex(Element elem) {
Element parent = elem.getParentElement();
String name = elem.getName();
List children = parent.getChildren(name);
int index = children.indexOf(elem);
return index;
}
public static Element getParent(Element elem, String name) {
if (elem == null) {
return null;
}
while (true) {
elem = elem.getParentElement();
if (elem == null) {
return null;
}
if (elem.getName().equals(name)) {
return elem;
}
}
}
/**
* Returns whether the given element has nested children elements.
*
* @param elem element to test
* @return true if the given element has nested children elements
*/
public static boolean hasChildren(Element elem) {
return elem.getChildren().iterator().hasNext();
}
/**
* Returns all children elements.
*
* @return all children elements
*/
public static List getChildren(Element elem) {
return elem.getChildren();
}
/**
* Returns first child with specified name.
*
* @return first child or null
*/
public static Element getChild(Element elem, String name) {
for (Element child : elem.getChildren()) {
if (child.getName().equals(name)) {
return child;
}
}
return null;
}
/**
* Returns single child element with specified local name or null if not found.
*/
public static Element getSingleChild(Element elem, String name) {
Element found = null;
for (Element child : elem.getChildren()) {
if (child.getName().equals(name)) {
if (found == null) {
found = child;
} else {
// multiple children with same name
return null;
}
}
}
return found;
}
public static Element getSingleChild(Element elem, String... names) {
for (String name : names) {
elem = getSingleChild(elem, name);
if (elem == null) {
break;
}
}
return elem;
}
public static String getChildText(Element elem, String name) {
Element e = getSingleChild(elem, name);
if (e == null) {
return null;
}
return e.getText();
}
public static void addChildText(Element elem, String name, String text) {
Element e = new Element(name);
e.setText(text);
elem.addContent(e);
}
public static IList getChildren(Element elem, String name) {
IList list = GapList.create();
for (Element child : getChildren(elem)) {
if (child.getName().equals(name)) {
list.add(child);
}
}
return list;
}
public static String getTextValue(Element elem, String... names) {
Element child = elem;
for (String name : names) {
child = getSingleChild(child, name);
if (child == null) {
return null;
}
}
return child.getText();
}
/**
* Return text of specified child in element.
* If the element has no child with the specified name, null is returned.
*/
public static String getTextValue(Element elem, String name) {
Element child = getChild(elem, name);
if (child != null) {
return child.getText();
}
return null;
}
/**
* Set text of specified child.
* If the element has no child with the specified name, it is created.
*/
public static void setTextValue(Element elem, String key, String value) {
if (value == null) {
removeChild(elem, key);
} else {
Element child = getChild(elem, key);
if (child == null) {
child = new Element(key);
elem.addContent(child);
}
child.setText(value);
}
}
/**
* Sanitize text by replacing all characters invalid in XML with the Unicode replacement character ((U+FFFD).
*
* Use this method to work around errors like
* - org.jdom2.IllegalDataException: The data is not legal for a JDOM character content: 0x001b is not a legal XML character
*/
public static String sanitizeText(String text) {
return sanitizeText(text, CharsetTools.REPLACEMENT_CHAR);
}
/**
* Sanitize text by replacing all characters invalid in XML by the specified replacement.
* If replaceChar is {@link StringTools#NOT_A_CHAR}, the invalid characters are removed.
*
* Use this method to work around errors like
* - org.jdom2.IllegalDataException: The data is not legal for a JDOM character content: 0x001b is not a legal XML character
*/
public static String sanitizeText(String text, char replaceChar) {
StringBuilder buf = null;
int start = 0;
int len = text.length();
for (int i = 0; i < len; i++) {
char c = text.charAt(i);
if (!isValidChar(c)) {
if (buf == null) {
buf = new StringBuilder();
}
buf.append(text, start, i);
if (replaceChar != StringTools.NOT_A_CHAR) {
buf.append(replaceChar);
}
start = i + 1;
}
}
if (buf == null) {
return text;
}
buf.append(text, start, len);
return buf.toString();
}
public static Element removeChild(Element elem, String name) {
Element child = getChild(elem, name);
if (child != null) {
child.detach();
}
return child;
}
public static Element addContent(Element elem, Element container) {
container.getAttributes().forEach(a -> elem.setAttribute(a.clone()));
container.getContent().forEach(c -> elem.addContent(c.clone()));
return elem;
}
public static Element addChild(Element elem, Element child) {
elem.addContent(child);
return child;
}
public static Element addChild(Element elem, int index, Element child) {
elem.addContent(index, child);
return child;
}
/**
* Add child element with specified name to element.
*/
public static Element addChild(Element elem, String name) {
Element child = new Element(name);
elem.addContent(child);
return child;
}
public static Text addText(Element elem, String text) {
Text t = new Text(text);
elem.addContent(t);
return t;
}
public static Element addChild(Document doc, String name) {
Element child = new Element(name);
doc.addContent(child);
return child;
}
public static Element addTextValue(Element elem, String name, String text) {
Element child = new Element(name);
elem.addContent(child);
child.setText(text);
return child;
}
public static void setTextValue(Element elem, String name1, String name2, String... namesOrText) {
CheckTools.check(namesOrText.length > 0, "namesOrText may not be empty");
GapList names = GapList.create(name1, name2);
int len = namesOrText.length;
for (int i = 0; i < len - 1; i++) {
names.add(namesOrText[i]);
}
String text = namesOrText[len - 1];
for (String name : names) {
Element child = getSingleChild(elem, name);
if (child == null) {
child = createElement(elem, name);
}
elem = child;
}
elem.setText(text);
}
public static Element createElement(Element elem, String name) {
Element child = new Element(name);
elem.addContent(child);
return child;
}
/**
* Returns child element with given name and given attribute value.
* If there more than one child elements, the first is returned.
* If no child is found, null is returned.
*/
public static Element getChild(Element elem, String name, String attrKey, String attrValue) {
if (attrValue == null) {
return null;
}
for (Element child : getChildren(elem, name)) {
if (ObjectTools.equals(getAttributeValue(child, attrKey), attrValue)) {
return child;
}
}
return null;
}
/**
* Returns all child element with given name and given attribute value.
*/
public static List getChildren(Element elem, String elemName, String attrName, String attrValue) {
List elements = new ArrayList();
for (Element child : (elem.getChildren(elemName))) {
if (child.getAttributeValue(attrName).equals(attrValue))
elements.add(child);
}
return elements;
}
/**
* Returns whether the given element has textual content.
* It does not recurse into child elements.
*
* @param elem element to test
* @return true if the element has textual content
*/
public static boolean hasText(Element elem) {
return elem.getText().length() > 0;
}
/**
* Checks whether the given element has textual content besides white spaces.
* It does not recurse into child elements.
*/
public static boolean hasTrimmedText(Element elem) {
return elem.getTextTrim().length() > 0;
}
/**
* Checks whether the given element is empty, i.e. it has no children and contains no text.
*/
public static boolean isEmpty(Element elem) {
return elem.getContentSize() == 0;
}
/**
* Checks whether the given element contains only text.
*
* @param elem element to check
* @return true if element contains only text
*/
public static boolean hasTextOnly(Element elem) {
return !hasChildren(elem) && hasText(elem);
}
/**
* Checks whether the given element contains only children, but no text.
*/
public static boolean hasChildrenOnly(Element elem) {
return hasChildren(elem) && !hasText(elem);
}
/**
* Checks whether the given element contains children and text.
*/
public static boolean hasMixedContent(Element elem) {
return hasChildren(elem) && hasText(elem);
}
/**
* Returns the concatenated text of all nodes of type Text and CDATA
* including the text of the children elements.
* A space is inserted between different text elements
*
* @param elem element
* @return concatenated text
*/
public static String getText(Element elem) {
return getText(elem, " ");
}
/**
* Returns the concatenated text of all nodes of type Text and CDATA
* including the text of the children elements.
*
* @param elem element
* @param delim delimiter text to insert between different text elements
* @return concatenated text
*/
public static String getText(Element elem, String delim) {
StringBuilder buf = new StringBuilder();
traverse(elem, c -> {
if (c instanceof Text) {
// CDATA extends Text
if (buf.length() > 0) {
buf.append(delim);
}
buf.append(((Text) c).getText());
}
});
return buf.toString();
}
/**
* Returns the concatenated size of all nodes of type Text and CDATA
* including the text of the children elements.
*/
public static int getTextSize(Element elem) {
MutableInt size = new MutableInt();
traverse(elem, c -> {
if (c instanceof Text) {
// CDATA extends Text
String str = ((Text) c).getText();
size.add(str.length());
}
});
return size.intValue();
}
/**
* Returns an estimated size of including children elements.
*/
public static int getContentSize(Element elem) {
MutableInt size = new MutableInt();
traverse(elem, c -> {
if (c instanceof Text) {
// CDATA extends Text
String str = ((Text) c).getText();
size.add(str.length());
} else if (c instanceof Element) {
Element e = (Element) c;
int len = 2 * e.getName().length() + 4;
size.add(len);
for (Attribute a : e.getAttributes()) {
int key = a.getName().length() + 4;
size.add(key);
int val = a.getValue().length() + 4;
size.add(val);
}
} else {
String str = c.toString();
size.add(str.length());
}
});
return size.intValue();
}
/**
* Traverse tree and call consumer for each content node.
* Traversal is done using depth first traversal.
*/
public static void traverse(Element elem, Consumer consumer) {
consumer.accept(elem);
for (Content e : elem.getContent()) {
if (e instanceof Element) {
traverse((Element) e, consumer);
} else {
consumer.accept(e);
}
}
}
/**
* Returns textual content of given element. It will fail if the element contains no text or non-textual elements.
*/
public static String getTextOnly(Element elem) {
if (!hasText(elem))
throw new JdomException("Element contains no text or has non-textual elements", elem);
return elem.getText();
}
/**
* Returns all child elements with given name. The element must not contain other child elements or an exception is thrown.
*/
public static List> getOnlyChildren(Element elem, String name) {
List> children = elem.getChildren(name);
if (children.size() != elem.getChildren().size()) {
throw new JdomException("Element contains other elements than " + name, elem);
}
return children;
}
// public static Element selectSingleElement(Document doc, String
// xpathSelect) {
// return selectSingleElement(doc.getRootElement(), xpathSelect);
// }
//
// public static Element selectSingleElement(Element elem, String
// xpathSelect) {
// Object node = selectSingleNode(elem, xpathSelect);
// try {
// return (Element) node;
// }
// catch (ClassCastException e) {
// throw new JdomToolsException("Selected node is not an element", node);
// }
// }
//
// public static Attribute selectSingleAttribute(Element elem, String
// xpathSelect) {
// Object node = selectSingleNode(elem, xpathSelect);
// try {
// return (Attribute) node;
// }
// catch (ClassCastException e) {
// throw new JdomToolsException("Selected node is not an attribute", node);
// }
// }
//
// public static int countElements(Document doc, String xpathSelect) {
// return countElements(doc.getRootElement(), xpathSelect);
// }
//
// public static int countElements(Element elem, String xpathSelect) {
// List list = selectElements(elem, xpathSelect);
// return list.size();
// }
//
// public static List selectElements(Document doc, String xpathSelect) {
// return selectElements(doc.getRootElement(), xpathSelect);
// }
//
// public static List selectElements(Element elem, String xpathSelect) {
//
// List l = selectNodes(elem, xpathSelect);
// Iterator i = l.iterator();
// while (i.hasNext()) {
// Object o = i.next();
// if (!(o instanceof Element))
// throw new JdomToolsException("Non-element nodes selected");
// }
// return l;
// }
//
// -- Selection without XPath
public static Element selectFirstElement(Element root, Predicate predicate) {
return doSelectElements(root, predicate, null);
}
public static Element selectSingleElement(Element root, Predicate predicate, boolean allowEmpty) {
IList elems = selectElements(root, predicate);
int minSize = (allowEmpty) ? 0 : 1;
CheckTools.check(elems.size() >= minSize && elems.size() <= 1);
return elems.peekFirst();
}
public static IList selectElements(Element root, Predicate predicate) {
IList elements = GapList.create();
doSelectElements(root, predicate, elements);
return elements;
}
static Element doSelectElements(Element elem, Predicate predicate, List foundElements) {
if (predicate.test(elem)) {
if (foundElements == null) {
return elem;
} else {
foundElements.add(elem);
}
}
List children = elem.getChildren();
for (Element child : children) {
Element found = doSelectElements(child, predicate, foundElements);
if (found != null) {
return found;
}
}
return null;
}
//
public static class XmlRef {
IList elemRefs = GapList.create();
/**
* @return last referenced property
*/
public Element getElement() {
return elemRefs.getLast();
}
/**
* If 0 is passed as index, the result is the same as by calling getElement().
*/
public Element getElement(int index) {
return elemRefs.get(elemRefs.size() - 1 - index);
}
void push(Element child) {
elemRefs.addLast(child);
}
void pop() {
elemRefs.removeLast();
}
}
public static void iterate(Element elem, Consumer consumer) {
XmlRef ref = new XmlRef();
ref.push(elem);
doIterate(elem, consumer, ref);
}
static void doIterate(Element elem, Consumer consumer, XmlRef ref) {
consumer.accept(ref);
for (Element child : elem.getChildren()) {
ref.push(child);
doIterate(child, consumer, ref);
ref.pop();
}
}
// -- Working with XPath
/**
* Return path to given element.
* If the element has a document root and there are no namespaces used, the path can be used ax XPath expression to select the element.
* If the element does not have a document root, the path does not start with a slash '/'.
*
* @param elem XML element
* @return path to element
*/
public static String getElementPath(Element elem) {
// Note: JDOM2 has a method to create an XPath expression to an element:
// http://www.jdom.org/docs/apidocs/org/jdom2/xpath/XPathHelper.html
GapList parents = GapList.create();
while (true) {
Element parent = elem.getParentElement();
String prefix = "";
if (parent != null || elem.getParent() != null) {
prefix = "/";
}
String name = prefix + elem.getName();
if (parent != null) {
List elems = parent.getChildren(elem.getName());
if (elems.size() > 1) {
int index = elems.indexOf(elem);
name += "[" + (index + 1) + "]";
}
}
parents.add(name);
if (parent == null) {
break;
}
elem = parent;
}
StringBuilder buf = new StringBuilder();
for (int i = parents.size() - 1; i >= 0; i--) {
buf.append(parents.get(i));
}
return buf.toString();
}
// /**
// * Set or remove attribute from given element.
// * If value is null, the attribute is removed if it exists, or nothing is
// done.
// */
// public static void setAttribute(Element elem, String key, String value) {
// if (value != null)
// elem.setAttribute(key, value);
// else
// elem.removeAttribute(key);
// }
// public static List transformFile(Document doc, String stylesheet) throws
// JDOMException {
// try {
// Transformer transformer = TransformerFactory.newInstance()
// .newTransformer(new StreamSource(stylesheet));
// JDOMSource in = new JDOMSource(doc);
// JDOMResult out = new JDOMResult();
// transformer.transform(in, out);
// return out.getResult();
// }
// catch (TransformerException e) {
// throw new JDOMException("XSLT Transformation failed", e);
// }
// }
//
// public static List transformInline(Document doc, String stylesheet)
// throws JDOMException {
// try {
// Transformer transformer = TransformerFactory.newInstance()
// .newTransformer(new StreamSource(new StringReader(stylesheet)));
// JDOMSource in = new JDOMSource(doc);
// JDOMResult out = new JDOMResult();
// transformer.transform(in, out);
// return out.getResult();
// }
// catch (TransformerException e) {
// throw new JDOMException("XSLT Transformation failed", e);
// }
// }
public static int getElementSize(Element elem) {
return elem.getChildren().size();
}
public static int getElementSizeAll(Element elem) {
int size = elem.getChildren().size();
List> elements = elem.getChildren();
Iterator> elemIter = elements.iterator();
while (elemIter.hasNext()) {
Element child = (Element) elemIter.next();
size += getElementSizeAll(child);
}
return size;
}
// -- Comparing and equality
/**
* Two documents are equal if all elements are equal.
*
* @see #equals(Element, Element)
*
* @param doc1 first document to compare
* @param doc2 second document to compare
* @return true if the two documents are equal, false otherwise
*/
public static boolean equals(Document doc1, Document doc2) {
return equals(doc1.getRootElement(), doc2.getRootElement());
}
/**
* Two elements are equals if attributes, text and child elements are equal.
*
* @param elem1 first element to compare
* @param elem2 second element to compare
* @return true if the two elements are equal, false otherwise
*/
public static boolean equals(Element elem1, Element elem2) {
// Compare attributes
// Attributes must be sorted first before they can be compared
Iterator attr1Iter = elem1.getAttributes().iterator();
Iterator attr2Iter = elem2.getAttributes().iterator();
TreeSet attrs1 = new TreeSet(attributeComparator);
TreeSet attrs2 = new TreeSet(attributeComparator);
while (attr1Iter.hasNext() && attr2Iter.hasNext()) {
attrs1.add(attr1Iter.next());
attrs2.add(attr2Iter.next());
}
if (attr1Iter.hasNext() || attr2Iter.hasNext()) {
return false; // number of attributes differs
}
if (!attrs1.equals(attrs2)) {
return false;
}
// Compare content
Iterator cont1Iter = elem1.getContent().iterator();
Iterator cont2Iter = elem2.getContent().iterator();
while (cont1Iter.hasNext() && cont2Iter.hasNext()) {
Content cont1 = cont1Iter.next();
Content cont2 = cont2Iter.next();
if (cont1.getClass() != cont2.getClass()) {
return false; // class of content element differs
}
if (cont1 instanceof Text) {
if (compare((Text) cont1, (Text) cont2) != 0) {
return false;
}
} else if (cont1 instanceof Element) {
// Recursive call
if (!equals((Element) cont1, (Element) cont2)) {
return false;
}
} else {
throw new IllegalArgumentException("Unsupported content type " + cont1.getClass());
}
}
if (cont1Iter.hasNext() || cont2Iter.hasNext()) {
return false; // number of content elements differs
}
return true;
}
/**
* Comparator for XML attributes.
*/
private static Comparator attributeComparator = new Comparator() {
@Override
public int compare(Attribute o1, Attribute o2) {
return JdomTools.compare(o1, o2);
}
};
/**
* Compares two XML attributes. The method compares name, namespaceURI, and value.
*
* @param attr1 first attribute to compare
* @param attr2 second attribute to compare
* @return comparison value
*/
public static int compare(Attribute attr1, Attribute attr2) {
int cmp;
cmp = attr1.getName().compareTo(attr2.getName());
if (cmp != 0) {
return cmp;
}
cmp = attr1.getNamespaceURI().compareTo(attr2.getNamespaceURI());
if (cmp != 0) {
return cmp;
}
cmp = attr1.getValue().compareTo(attr2.getValue());
if (cmp != 0) {
return cmp;
}
return 0;
}
/**
* Compares two XML texts.
*
* @param text1 first text to compare
* @param text2 second text to compare
* @return comparison value
*/
public static int compare(Text text1, Text text2) {
return text1.getText().compareTo(text2.getText());
}
/**
* Set element to a new position within its parent.
*
* @param elem element to move
* @param pos new position index of element, negative values are used to indicate positions
* starting from the end, so -1 is the last position
*/
public static void setPosition(Element elem, int pos) {
Element parent = elem.getParentElement();
List children = getChildren(parent);
children.remove(elem);
if (pos < 0) {
pos = children.size() + pos + 1;
}
children.add(pos, elem);
}
/**
* Returns root element.
*
* @param elem element to determine root element for
* @return root element
*/
public static Element getRoot(Element elem) {
while (true) {
Element parent = elem.getParentElement();
if (parent == null) {
return elem;
}
elem = parent;
}
}
}