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

com.databasesandlife.util.DomParser Maven / Gradle / Ivy

The newest version!
package com.databasesandlife.util;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import org.w3c.dom.*;

import com.databasesandlife.util.gwtsafe.ConfigurationException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;

/**
 * @author This source is copyright Adrian Smith and licensed under the LGPL 3.
 * @see Project on GitHub
 */
public class DomParser {

    /** @param elementNames can be "*" */
    public static @Nonnull List getSubElements(@Nonnull Node container, String... elementNames) {
        var allElementsDesired = "*".equals(elementNames[0]);
        var elementNameSet = new HashSet<>(Arrays.asList(elementNames));
        
        var result = new ArrayList();
        var children = container.getChildNodes();
        for (var i = 0; i < children.getLength(); i++) {
            var child = children.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                if (allElementsDesired || elementNameSet.contains(child.getNodeName()))
                    result.add((Element) child);
            }
        }
        return result;
    }

    public static void assertNoOtherElements(@Nonnull Node container, String... elements)
    throws ConfigurationException {
        var elementsSet = new HashSet<>(Arrays.asList(elements));
        var children = container.getChildNodes();
        for (var i = 0; i < children.getLength(); i++) {
            var child = children.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE)
                if ( ! elementsSet.contains(child.getNodeName()))
                    throw new ConfigurationException("Unexpected element <" + child.getNodeName() + ">");
        }
    }

    public static @Nonnull String getMandatoryAttribute(
        @Nonnull Element node, @Nonnull String attributeName
    ) throws ConfigurationException {
        var attributeNode = node.getAttributeNode(attributeName); 
        if (attributeNode == null)
            throw new ConfigurationException("<" + node.getNodeName() + "> expects mandatory attribute '" + attributeName + "'");
        return attributeNode.getValue();
    }

    public static String getOptionalAttribute(
        @Nonnull Element node, @Nonnull String attributeName, String defaultValue
    ) {
        var attributeNode = node.getAttributeNode(attributeName); 
        if (attributeNode == null) return defaultValue;
        return attributeNode.getValue();
    }
    
    /** @return null if the attribute is not defined */
    public static @CheckForNull String getOptionalAttribute(
        @Nonnull Element node, @Nonnull String attributeName
    ) {
        return getOptionalAttribute(node, attributeName, null);
    }
    
    public static double parseMandatoryDoubleAttribute(@Nonnull Element node, @Nonnull String attributeName)
    throws ConfigurationException {
        var str = getMandatoryAttribute(node, attributeName);
        try { return Double.parseDouble(str); }
        catch (NumberFormatException e) { throw new ConfigurationException("<" + node.getNodeName() + " " + attributeName +
                "='" + str + "'>: couldn't parse decimal attribute"); }
    }

    /** @return null if attribute not found */
    public static @CheckForNull Integer parseOptionalIntegerAttribute(@Nonnull Element node, @Nonnull String attributeName)
    throws ConfigurationException {
        var str = node.getAttribute(attributeName);
        if (str.equals("")) return null;
        try { return Integer.parseInt(str); }
        catch (NumberFormatException e) { throw new ConfigurationException("<" + node.getNodeName() + " " + attributeName +
                "='" + str + "'>: couldn't parse integer attribute"); }
    }

    public static int parseOptionalIntAttribute(@Nonnull Element node, @Nonnull String attributeName, int defaultValue)
    throws ConfigurationException {
        var result = parseOptionalIntegerAttribute(node, attributeName);
        if (result == null) return defaultValue;
        return result;
    }

    public static int parseMandatoryIntAttribute(@Nonnull Element node, @Nonnull String attributeName)
        throws ConfigurationException {
        var result = parseOptionalIntegerAttribute(node, attributeName);
        if (result == null)
            throw new ConfigurationException("<" + node.getNodeName() + "'>: mandatory attribute '" + attributeName + "' missing");
        return result;
    }

    /** 
     * @param subNodeName can be "*" 
     * @throws ConfigurationException if more than one element found
     */
    public static @Nonnull Element getMandatorySingleSubElement(
        @Nonnull Node node, @Nonnull String subNodeName
    ) throws ConfigurationException {
        var resultList = getSubElements(node, subNodeName);
        if (resultList.size() != 1) throw new ConfigurationException("<" + node.getNodeName() + ">: found " +
            resultList.size() + ("*".equals(subNodeName) ? " sub-elements" : (" <" + subNodeName + "> sub-elements")));
        return resultList.get(0);
    }

    /** 
     * @param subNodeName can be "*"
     * @return null if element not found 
     * @throws ConfigurationException if more than one element found
     */
    public static @CheckForNull Element getOptionalSingleSubElement(@Nonnull Element node, @Nonnull String subNodeName) 
    throws ConfigurationException {
        var resultList = getSubElements(node, subNodeName);
        if (resultList.size() == 0) return null;
        else if (resultList.size() == 1) return resultList.get(0);
        else throw new ConfigurationException("<" + node.getNodeName() + ">: found " +
                resultList.size() + ("*".equals(subNodeName) ? " sub-elements" : (" <" + subNodeName + "> sub-elements")));
    }

    /**
     * @param subNodeName can be "*"
     * @return null if element not found 
     * @throws ConfigurationException if more than one element found
     */
    public static @CheckForNull String getOptionalSingleSubElementTextContent(@Nonnull Element node, @Nonnull String subNodeName) 
    throws ConfigurationException {
        var el = getOptionalSingleSubElement(node, subNodeName);
        if (el == null) return null;
        else return el.getTextContent();
    }

    /**
     * @param subNodeName can be "*"
     * @return null if element not found 
     * @throws ConfigurationException if more than one element found
     */
    public static @Nonnull String getMandatorySingleSubElementTextContent(@Nonnull Element node, @Nonnull String subNodeName) 
    throws ConfigurationException {
        var el = getMandatorySingleSubElement(node, subNodeName);
        var result = el.getTextContent();
        if (result == null || result.trim().isEmpty()) throw new ConfigurationException("Element <" + subNodeName + "> is empty");
        return result;
    }

    public static @Nonnull List parseList(@Nonnull Element container, @Nonnull String elementName, @Nonnull String attribute)
    throws ConfigurationException {
        var result = new ArrayList();
        for (var e : getSubElements(container, elementName)) result.add(getMandatoryAttribute(e, attribute));
        return result;
    }

    public static @Nonnull Set parseSet(@Nonnull Element container, @Nonnull String elementName, @Nonnull String attribute)
    throws ConfigurationException {
        return new HashSet<>(parseList(container, elementName, attribute));
    }

    public static @Nonnull List parseList(@Nonnull Element container, @Nonnull String elementName) {
        var result = new ArrayList();
        for (var e : getSubElements(container, elementName)) result.add(e.getTextContent().trim());
        return result;
    }

    public static @Nonnull Set parseSet(@Nonnull Element container, @Nonnull String elementName) {
        return new HashSet<>(parseList(container, elementName));
    }
    
    public interface ElementConverter {
        public @Nonnull T convert(@Nonnull Element element) throws ConfigurationException;
    }
    
    /** 
     * Calls converter for each element, 
     * but in the case of a ConfigurationException throws an exception with a message like "foo element 0" 
     */
    public static  @Nonnull List parseList(
        @Nonnull Element container, @Nonnull String elementName, @Nonnull ElementConverter converter
    ) throws ConfigurationException {
        var result = new ArrayList();
        var elements = DomParser.getSubElements(container, elementName);
        for (int i = 0; i < elements.size(); i++) {
            try {
                var el = elements.get(i);
                result.add(converter.convert(el));
            }
            catch (ConfigurationException e) { 
                throw new ConfigurationException("<"+elementName+"> index " + i + " (zero based)", e); 
            }
        }
        return result;
    }

    public static @Nonnull Map parseMap(
        @Nonnull Element container, @Nonnull String elementName, @Nonnull String keyAttribute, @Nonnull String valueAttribute
    ) throws ConfigurationException {
        Map result = new HashMap<>();
        for (var e : getSubElements(container, elementName))
            result.put(getMandatoryAttribute(e, keyAttribute), getMandatoryAttribute(e, valueAttribute));
        return result;
    }

    public static @Nonnull Map parseMap(
        @Nonnull Element container, @Nonnull String elementName, @Nonnull String keyAttribute
    ) throws ConfigurationException {
        Map result = new HashMap<>();
        for (var e : getSubElements(container, elementName))
            result.put(getMandatoryAttribute(e, keyAttribute), e.getTextContent());
        return result;
    }
    
    public static @Nonnull Map parseDoubleMap(
        @Nonnull Element container, @Nonnull String elementName, @Nonnull String keyAttribute, @Nonnull String valueAttribute
    ) throws ConfigurationException {
        Map result = new HashMap<>();
        for (var e : getSubElements(container, elementName))
            result.put(getMandatoryAttribute(e, keyAttribute), parseMandatoryDoubleAttribute(e, valueAttribute));
        return result;
    }

    public static XPathExpression getExpression(String expression)
    throws XPathExpressionException {
        var xpath = XPathFactory.newInstance().newXPath();
        return xpath.compile(expression);
    }

    public static NodeList getNodesFromXPath(Document document, String expression)
    throws XPathExpressionException {
        return (NodeList) getExpression(expression).evaluate(document, XPathConstants.NODESET);
    }

    public static NodeList getNodesFromXPath(Element root, String expression)
    throws XPathExpressionException {
        return getNodesFromXPath(root.getOwnerDocument(), expression);
    }

    public static List getElementsFromXPath(Document document, String expression)
    throws XPathExpressionException {
        return toElementList(getNodesFromXPath(document, expression));
    }

    public static List getElementsFromXPath(Element root, String expression)
    throws XPathExpressionException {
        return getElementsFromXPath(root.getOwnerDocument(), expression);
    }

    public static List toElementList(NodeList nl) {
        var elements = new ArrayList(nl.getLength());
        for (var i = 0; i < nl.getLength(); i++) {
            var n = nl.item(i);
            if (n instanceof Element) {
                elements.add((Element) n);
            }
        }
        return elements;
    }

    public static @Nonnull Element getElementFromXPath(@Nonnull Document document, @Nonnull String expression)
    throws XPathExpressionException {
        return (Element) getExpression(expression).evaluate(document, XPathConstants.NODE);
    }

    public static @Nonnull Element getElementFromXPath(@Nonnull Element root, @Nonnull String expression)
    throws XPathExpressionException {
        return (Element) getExpression(expression).evaluate(root, XPathConstants.NODE);
    }

    public static @Nonnull Element from(@Nonnull File f) throws ConfigurationException {
        try {
            return newDocumentBuilder().parse(f).getDocumentElement();
        }
        catch (IOException e) { throw new RuntimeException(e); }
        catch (SAXException e) { throw new ConfigurationException("File '"+f+"'", e); }
    }

    public static @Nonnull Element from(@Nonnull InputStream f) throws ConfigurationException {
        try {
            return newDocumentBuilder().parse(f).getDocumentElement();
        }
        catch (IOException e) { throw new RuntimeException(e); }
        catch (SAXException e) { throw new ConfigurationException(e); }
    }

    public static @Nonnull Element from(@Nonnull String xmlValue) throws ConfigurationException {
        try {
            var docBuilder = newDocumentBuilder();
            var inputSource = new InputSource(new StringReader(xmlValue));
            var doc = docBuilder.parse(inputSource);
            return doc.getDocumentElement();
        }
        catch (IOException e) { throw new RuntimeException(e); }
        catch (SAXException e) { throw new ConfigurationException(e); }
    }

    public static @Nonnull DocumentBuilder newDocumentBuilder() {
        try {
            var dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);     // See https://stackoverflow.com/a/49800040
            return dbf.newDocumentBuilder();
        }
        catch (ParserConfigurationException e) { throw new RuntimeException(e); }
    }

    public static @Nonnull String formatXml(@Nonnull Element element) {
        try {
            var str = new StringWriter();

            var transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
            transformer.transform(new DOMSource(element), new StreamResult(str));

            return str.toString();
        }
        catch (TransformerException e) { throw new RuntimeException(e); }
    }
    
    public static @Nonnull String formatXmlPretty(@Nonnull Element element) {
        try {
            var str = new StringWriter();
            
            var transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            transformer.transform(new DOMSource(element), new StreamResult(str));

            return str.toString();
        }
        catch (TransformerException e) { throw new RuntimeException(e); }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy