org.databene.commons.xml.XMLUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of databene-commons Show documentation
Show all versions of databene-commons Show documentation
'databene commons' is an open source Java library by Volker Bergmann.
It provides extensions to the Java core library by utility classes, abstract concepts
and concrete implementations.
The newest version!
/*
* Copyright (C) 2004-2015 Volker Bergmann ([email protected]).
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.databene.commons.xml;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.databene.commons.ArrayBuilder;
import org.databene.commons.BeanUtil;
import org.databene.commons.ConfigurationError;
import org.databene.commons.Converter;
import org.databene.commons.Encodings;
import org.databene.commons.ErrorHandler;
import org.databene.commons.Filter;
import org.databene.commons.IOUtil;
import org.databene.commons.Level;
import org.databene.commons.ParseUtil;
import org.databene.commons.StringUtil;
import org.databene.commons.SyntaxError;
import org.databene.commons.SystemInfo;
import org.databene.commons.Visitor;
import org.databene.commons.converter.NoOpConverter;
import org.databene.commons.converter.String2DateConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.EntityResolver;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Provides XML Utility methods.
* Created: 25.08.2007 22:09:26
* @author Volker Bergmann
*/
public class XMLUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(XMLUtil.class);
private static final String DOCUMENT_BUILDER_FACTORY_IMPL = "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl";
private static final ErrorHandler DEFAULT_ERROR_HANDLER = new ErrorHandler(XMLUtil.class.getSimpleName(), Level.error);
private static String defaultDocumentBuilderClassName = DOCUMENT_BUILDER_FACTORY_IMPL;
private XMLUtil() {}
public static String format(Document document) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
String encoding = Encodings.UTF_8;
SimpleXMLWriter out = new SimpleXMLWriter(buffer, encoding, true);
format(document.getDocumentElement(), out);
out.close();
try {
return new String(buffer.toByteArray(), encoding);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String format(Element element) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
String encoding = Encodings.UTF_8;
SimpleXMLWriter out = new SimpleXMLWriter(buffer, encoding, false);
format(element, out);
out.close();
try {
return new String(buffer.toByteArray(), encoding);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String formatShort(Element element) {
StringBuilder builder = new StringBuilder();
builder.append('<').append(element.getNodeName());
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Attr attribute = (Attr) attributes.item(i);
builder.append(' ').append(attribute.getName()).append("=\"").append(attribute.getValue()).append('"');
}
builder.append("...");
return builder.toString();
}
public static String formatStartTag(Element element) {
StringBuilder builder = new StringBuilder();
builder.append('<').append(element.getNodeName());
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Attr attribute = (Attr) attributes.item(i);
builder.append(' ').append(attribute.getName()).append("=\"").append(attribute.getValue()).append('"');
}
builder.append(">");
return builder.toString();
}
public static String localName(Element element) {
return localName(element.getNodeName());
}
public static String localName(String elementName) {
if (elementName == null)
return null;
int sep = elementName.indexOf(':');
if (sep < 0)
return elementName;
return elementName.substring(sep + 1);
}
public static Element[] getChildElements(Element parent) {
NodeList childNodes = parent.getChildNodes();
return toElementArray(childNodes);
}
public static Element[] toElementArray(NodeList nodeList) {
if (nodeList == null)
return new Element[0];
int n = nodeList.getLength();
ArrayBuilder builder = new ArrayBuilder(Element.class, n);
for (int i = 0; i < n; i++) {
Node item = nodeList.item(i);
if (item instanceof Element)
builder.add((Element) item);
}
return builder.toArray();
}
public static List toElementList(NodeList nodeList) {
List list = new ArrayList(nodeList != null ? nodeList.getLength() : 0);
if (nodeList == null)
return list;
int n = nodeList.getLength();
for (int i = 0; i < n; i++) {
Node item = nodeList.item(i);
if (item instanceof Element)
list.add((Element) item);
}
return list;
}
public static Element[] getChildElements(Element parent, boolean namespaceAware, String name) {
ArrayBuilder builder = new ArrayBuilder(Element.class);
NodeList childNodes = parent.getChildNodes();
if (childNodes == null)
return new Element[0];
int n = childNodes.getLength();
for (int i = 0; i < n; i++) {
Node item = childNodes.item(i);
if (item instanceof Element && hasName(name, namespaceAware, item))
builder.add((Element) item);
}
return builder.toArray();
}
public static Element getChildElementAtPath(Element parent, String path, boolean namespaceAware, boolean required) {
Element[] elements = getChildElementsAtPath(parent, path, namespaceAware);
return assertSingleSearchResult(elements, required, path);
}
public static Element[] getChildElementsAtPath(Element parent, String path, boolean namespaceAware) {
ArrayBuilder builder = new ArrayBuilder(Element.class);
getChildElementsAtPath(parent, namespaceAware, path.split("/"), 0, builder);
return builder.toArray();
}
private static void getChildElementsAtPath(Element parent, boolean namespaceAware, String[] pathComponents, int pathIndex, ArrayBuilder result) {
NodeList childNodes = parent.getChildNodes();
if (childNodes != null) {
String pathComponentName = pathComponents[pathIndex];
int n = childNodes.getLength();
for (int i = 0; i < n; i++) {
Node item = childNodes.item(i);
if (item instanceof Element) {
Element element = (Element) item;
if (pathIndex < pathComponents.length - 1)
getChildElementsAtPath(element, namespaceAware, pathComponents, pathIndex + 1, result);
else if (hasName(pathComponentName, namespaceAware, item))
result.add(element);
}
}
}
}
public static boolean hasName(String name, boolean namespaceAware, Node item) {
String fqName = item.getNodeName();
if (namespaceAware)
return fqName.equals(name);
else
return name.equals(StringUtil.lastToken(fqName, ':'));
}
public static Element getChildElement(Element parent, boolean namespaceAware, boolean required, String name) {
Element[] elements = getChildElements(parent, namespaceAware, name);
return assertSingleSearchResult(elements, required, name);
}
private static Element assertSingleSearchResult(Element[] elements, boolean required, String searchTerm) {
if (required && elements.length == 0)
throw new IllegalArgumentException("No element found in search: " + searchTerm);
if (elements.length > 1)
throw new IllegalArgumentException("More that one element found in search: " + searchTerm);
return (elements.length > 0 ? elements[0] : null);
}
public static String getText(Node node) {
if (node == null)
return null;
if (node instanceof Text)
return node.getNodeValue();
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++)
if (children.item(i) instanceof Text)
return children.item(i).getNodeValue();
return null;
}
public static Integer getIntegerAttribute(Element element, String name, Integer defaultValue) {
if (LOGGER.isDebugEnabled())
LOGGER.debug("getIntegerAttribute(" + element.getNodeName() + ", " + name + ')');
String stringValue = getAttribute(element, name, false);
if (StringUtil.isEmpty(stringValue))
return defaultValue;
return Integer.parseInt(stringValue);
}
public static Long getLongAttribute(Element element, String name, long defaultValue) {
if (LOGGER.isDebugEnabled())
LOGGER.debug("getLongAttribute(" + element.getNodeName() + ", " + name + ')');
String stringValue = getAttribute(element, name, false);
if (StringUtil.isEmpty(stringValue))
return defaultValue;
return Long.parseLong(stringValue);
}
public static String getAttribute(Element element, String attributeName, boolean required) {
String value = StringUtil.emptyToNull(element.getAttribute(attributeName));
if (value == null && required)
throw new IllegalArgumentException("Element '" + element.getNodeName() + "'" +
" is missing the required attribute '" + attributeName + "'");
return value;
}
public static Map getAttributes(Element element) {
NamedNodeMap attributes = element.getAttributes();
Map result = new HashMap();
int n = attributes.getLength();
for (int i = 0; i < n; i++) {
Attr attribute = (Attr) attributes.item(i);
result.put(attribute.getName(), attribute.getValue());
}
return result;
}
public static PrintWriter createXMLFile(String uri, String encoding)
throws FileNotFoundException, UnsupportedEncodingException {
PrintWriter printer = IOUtil.getPrinterForURI(uri, encoding);
printer.println("");
return printer;
}
public static String normalizedAttributeValue(Element element, String attributeName) {
String value = element.getAttribute(attributeName);
if (StringUtil.isEmpty(value))
value = null;
return value;
}
public static Comment[] getChildComments(Node parent) {
NodeList children;
if (parent instanceof Document)
children = ((Document) parent).getChildNodes();
else if (parent instanceof Element)
children = ((Element) parent).getChildNodes();
else
throw new UnsupportedOperationException("Not a supported type: " + parent.getClass());
ArrayBuilder builder = new ArrayBuilder(Comment.class);
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child instanceof Comment)
builder.add((Comment) child);
}
return builder.toArray();
}
// XML operations --------------------------------------------------------------------------------------------------
public static Document parse(String uri) throws IOException {
return parse(uri, true, null, null, null);
}
public static Document parse(String uri, boolean namespaceAware, EntityResolver resolver, String schemaUri, ClassLoader classLoader)
throws IOException {
InputStream stream = null;
try {
stream = IOUtil.getInputStreamForURI(uri);
return parse(stream, namespaceAware, resolver, schemaUri, classLoader, DEFAULT_ERROR_HANDLER);
} catch (ConfigurationError e) {
throw new ConfigurationError("Error parsing " + uri, e);
} finally {
IOUtil.close(stream);
}
}
public static Document parseString(String text) {
return parseString(text, null, null);
}
public static Element parseStringAsElement(String xml) {
return XMLUtil.parseString(xml).getDocumentElement();
}
public static Document parseString(String text, EntityResolver resolver, ClassLoader classLoader) {
if (LOGGER.isDebugEnabled())
LOGGER.debug(text);
try {
String encoding = getEncoding(text, SystemInfo.getFileEncoding());
return parse(new ByteArrayInputStream(text.getBytes(encoding)), true, resolver, null, classLoader, DEFAULT_ERROR_HANDLER);
} catch (IOException e) {
throw new RuntimeException("Unexpected error", e);
}
}
public static String getEncoding(String text, String defaultEncoding) {
if (text.startsWith(" 0 && ei < qm2i) {
int dq = text.indexOf('"', ei);
int sq = text.indexOf('\'', ei);
int q1 = (dq > 0 ? (sq > 0 ? dq : Math.min(sq, dq)) : sq);
dq = text.indexOf('"', q1 + 1);
sq = text.indexOf('\'', q1 + 1);
int q2 = (dq > 0 ? (sq > 0 ? dq : Math.min(sq, dq)) : sq);
if (q1 > 0 && q2 > 0)
return text.substring(q1 + 1, q2);
}
}
return defaultEncoding;
}
public static Document parse(InputStream stream) throws IOException {
return parse(stream, null, null, DEFAULT_ERROR_HANDLER);
}
/**
* Parses a stream's output into an XML document.
* @param in the {@link InputStream} to read
* @param resolver an {@link EntityResolver} implementation or null, in the latter case, no validation is applied
* @param schemaUri the URI of the XML document
* @param errorHandler the error handler
* @return the resulting XML {@link Document}
* @throws IOException if stream access fails
*/
public static Document parse(InputStream in, EntityResolver resolver, String schemaUri, ErrorHandler errorHandler) throws IOException {
return parse(in, true, resolver, schemaUri, null, errorHandler);
}
public static Document parse(InputStream stream, boolean namespaceAware, EntityResolver resolver,
String schemaUri, ClassLoader classLoader, ErrorHandler errorHandler)
throws IOException {
try {
DocumentBuilderFactory factory = createDocumentBuilderFactory(classLoader);
factory.setNamespaceAware(namespaceAware);
if (schemaUri != null)
activateXmlSchemaValidation(factory, schemaUri);
DocumentBuilder builder = factory.newDocumentBuilder();
if (resolver != null)
builder.setEntityResolver(resolver);
if (errorHandler == null)
errorHandler = new ErrorHandler("XMLUtil");
builder.setErrorHandler(createSaxErrorHandler(errorHandler));
return builder.parse(stream);
} catch (ParserConfigurationException e) {
throw new ConfigurationError(e);
} catch (SAXParseException e) {
throw new ConfigurationError("Error in line " + e.getLineNumber() + " column " + e.getColumnNumber(), e);
} catch (SAXException e) {
throw new ConfigurationError(e);
}
}
public static String getDefaultDocumentBuilderClassName() {
return defaultDocumentBuilderClassName;
}
public static void setDefaultDocumentBuilderClassName(String defaultDocumentBuilderClassName) {
XMLUtil.defaultDocumentBuilderClassName = defaultDocumentBuilderClassName;
}
public static DocumentBuilderFactory createDocumentBuilderFactory(ClassLoader classLoader) {
if (defaultDocumentBuilderClassName != null) {
if (classLoader == null)
classLoader = Thread.currentThread().getContextClassLoader();
return DocumentBuilderFactory.newInstance(defaultDocumentBuilderClassName, classLoader);
} else {
return DocumentBuilderFactory.newInstance();
}
}
public static NamespaceAlias namespaceAlias(Document document, String namespaceUri) {
Map attributes = XMLUtil.getAttributes(document.getDocumentElement());
for (Map.Entry entry : attributes.entrySet()) {
String namespaceName = entry.getValue();
if (namespaceUri.equals(namespaceName)) {
String def = entry.getKey();
String alias = (def.contains(":") ? StringUtil.lastToken(def, ':') : "");
return new NamespaceAlias(alias, namespaceName);
}
}
return new NamespaceAlias("", namespaceUri);
}
public static Map getNamespaces(Document document) {
Map namespaces = new HashMap();
Map attributes = XMLUtil.getAttributes(document.getDocumentElement());
for (Map.Entry entry : attributes.entrySet()) {
String attributeName = entry.getKey();
if (attributeName.startsWith("xmlns")) {
String alias = (attributeName.contains(":") ? StringUtil.lastToken(attributeName, ':') : "");
namespaces.put(alias, entry.getValue());
}
}
return namespaces;
}
public static String getTargetNamespace(Document xsdDocument) {
return xsdDocument.getDocumentElement().getAttribute("targetNamespace");
}
public static Boolean getBooleanAttribute(Element element, String attributeName, boolean required) {
String stringValue = element.getAttribute(attributeName);
if (StringUtil.isEmpty(stringValue) && required)
throw new SyntaxError("Missing attribute '" + attributeName + "'", format(element));
return ParseUtil.parseBoolean(stringValue);
}
public static boolean getBooleanAttributeWithDefault(Element element, String attributeName, boolean defaultValue) {
String stringValue = element.getAttribute(attributeName);
return (StringUtil.isEmpty(stringValue) ? defaultValue : Boolean.parseBoolean(stringValue));
}
public static double getDoubleAttribute(Element element, String name) {
return Double.parseDouble(element.getAttribute(name));
}
public static Date getDateAttribute(Element element, String name) {
return new String2DateConverter().convert(element.getAttribute(name));
}
public static void mapAttributesToProperties(Element element, Object bean, boolean unescape) {
mapAttributesToProperties(element, bean, unescape, new NoOpConverter());
}
public static void mapAttributesToProperties(Element element, Object bean, boolean unescape, Converter nameNormalizer) {
for (Map.Entry attribute : getAttributes(element).entrySet()) {
String name = StringUtil.lastToken(attribute.getKey(), ':');
name = nameNormalizer.convert(name);
String value = attribute.getValue();
if (unescape)
value = StringUtil.unescape(value);
Class> type = bean.getClass();
if (BeanUtil.hasProperty(type, name))
BeanUtil.setPropertyValue(bean, name, value, true, true);
}
}
public static void visit(Node element, Visitor visitor) {
visitor.visit(element);
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++)
visit(childNodes.item(i), visitor);
}
public static Element findElementByAttribute(String attributeName, String attributeValue, Element root) {
if (attributeValue.equals(root.getAttribute(attributeName)))
return root;
else
for (Element child : XMLUtil.getChildElements(root)) {
Element candidate = findElementByAttribute(attributeName, attributeValue, child);
if (candidate != null)
return candidate;
}
return null;
}
public static Element findFirstAccepted(Filter filter, Element element) {
if (filter.accept(element))
return element;
else
for (Element child : XMLUtil.getChildElements(element)) {
Element candidate = findFirstAccepted(filter, child);
if (candidate != null)
return candidate;
}
return null;
}
public static List findElementsByName(String name, boolean caseSensitive, Element root) {
return findElementsByName(name, caseSensitive, root, new ArrayList());
}
public static String getWholeText(Element element) {
StringBuilder builder = new StringBuilder();
NodeList nodeList = element.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Text)
builder.append(((Text) node).getWholeText());
else if (node instanceof Element)
builder.append(getWholeText((Element) node));
}
return builder.toString();
}
public static String formatText(String text) {
return text.replace("&", "&").replace("<", "<").replace(">", ">");
}
// private helpers -------------------------------------------------------------------------------------------------
private static void format(Element element, SimpleXMLWriter out) {
String name = element.getNodeName();
Map attributes = XMLUtil.getAttributes(element);
try {
out.startElement(name, attributes);
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child instanceof Element)
format((Element) child, out);
else if (child instanceof Text) {
String text = child.getTextContent();
if (!StringUtil.isEmpty(text))
out.characters(text.toCharArray(), 0, text.length());
}
}
out.endElement(name);
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
private static List findElementsByName(String name, boolean caseSensitive, Element root, List result) {
if (root.getNodeName().equals(name))
result.add(root);
else
for (Element child : getChildElements(root))
findElementsByName(name, caseSensitive, child, result);
return result;
}
private static Schema activateXmlSchemaValidation(DocumentBuilderFactory factory, String schemaUrl) {
try {
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(new URL(schemaUrl));
factory.setSchema(schema);
return schema;
} catch (Exception e) {
// some XML parsers may not support attributes in general or especially XML Schema
LOGGER.error("Error activating schema validation, possibly you are offline or behind a proxy?", e.getMessage());
return null;
}
}
private static org.xml.sax.ErrorHandler createSaxErrorHandler(
final ErrorHandler errorHandler) {
return new org.xml.sax.ErrorHandler() {
@Override
public void error(SAXParseException e) {
errorHandler.handleError(e.getMessage(), e);
}
@Override
public void fatalError(SAXParseException e) {
errorHandler.handleError(e.getMessage(), e);
}
@Override
public void warning(SAXParseException e) {
errorHandler.handleError(e.getMessage(), e);
}
};
}
@SuppressWarnings("null")
public static void saveAsProperties(Properties properties, File file, String encoding) throws FileNotFoundException {
if (properties.size() == 0)
throw new IllegalArgumentException("Cannot save empty Properties");
Document document = null;
for (Map.Entry