All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy