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

com.crabshue.commons.xpath.XPathEvaluator Maven / Gradle / Ivy

package com.crabshue.commons.xpath;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.xml.namespace.QName;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;

import com.crabshue.commons.exceptions.ApplicationException;
import com.crabshue.commons.xml.XmlDocumentBuilder;
import com.crabshue.commons.xml.inputsource.InputSourceBuilder;
import com.crabshue.commons.xpath.exceptions.XPathErrorContext;
import com.crabshue.commons.xpath.exceptions.XPathErrorType;
import com.crabshue.commons.xpath.saxon.NodeInfoUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import net.sf.saxon.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

/**
 * XPath expression evaluator.
 */
@Slf4j
public class XPathEvaluator {

    private InputSource inputSource;

    private Map variables = new HashMap<>();

    private String xpathExpression;

    private QName returnType = XPathConstants.STRING;

    private boolean ignoreValidation = false;

    private Configuration configuration;

    /**
     * Prepare an XPath evaluator with no context ({@code }.
     *
     * @return instance.
     */
    public static XPathEvaluator noContext() {

        return XPathEvaluator.of("");
    }

    /**
     * Prepare an XPath evaluator with an {@link InputSource}.
     *
     * @param inputSource the input source.
     * @return instance.
     */
    public static XPathEvaluator of(@NonNull final InputSource inputSource) {

        final XPathEvaluator ret = new XPathEvaluator();
        ret.inputSource = inputSource;
        return ret;
    }

    /**
     * Prepare an XPath evaluator with an XML document as {@link String}.
     *
     * @param xmlContent the XML document as {@link String}.
     * @return instance.
     * @see #of(InputSource)
     */
    public static XPathEvaluator of(@NonNull final String xmlContent) {

        return XPathEvaluator.of(InputSourceBuilder.newInputSource(xmlContent));
    }

    /**
     * Prepare an XPath evaluator with an XML {@link File}.
     *
     * @param xmlFile the XML file.
     * @return instance.
     * @see #of(InputSource)
     */
    public static XPathEvaluator of(@NonNull final File xmlFile) {

        return XPathEvaluator.of(InputSourceBuilder.newInputSource(xmlFile));
    }

    /**
     * Prepare an XPath evaluator with a byte array.
     *
     * @param xmlByteArray the byte array
     * @return instance.
     * @see #of(InputSource)
     */
    public static XPathEvaluator of(@NonNull final byte[] xmlByteArray) {

        return XPathEvaluator.of(InputSourceBuilder.newInputSource(xmlByteArray));
    }

    /**
     * Prepare an XPath evaluator with an XML {@link Node}.
     *
     * @param xmlNode the XML node.
     * @return instance.
     * @see #of(InputSource)
     */
    public static XPathEvaluator of(@NonNull final Node xmlNode) {

        return XPathEvaluator.of(InputSourceBuilder.newInputSource(xmlNode));
    }

    /**
     * Set the XPath expression for XPath evaluation.
     *
     * @param xpathExpression the XPath expression.
     * @return instance.
     */
    public XPathEvaluator withXpathExpression(@NonNull final String xpathExpression) {

        this.xpathExpression = xpathExpression;
        return this;
    }

    /**
     * Add variables for XPath evaluation.
     *
     * @param variables the variables map.
     * @return instance.
     */
    public XPathEvaluator withVariables(@NonNull Map variables) {

        this.variables.putAll(variables);
        return this;
    }

    /**
     * Set the return type for the XPath evaluation.
     * 

The default return type is {@link XPathConstants#STRING}

* * @param returnType the return type. * @return instance. */ public XPathEvaluator withReturnType(@NonNull final QName returnType) { this.returnType = returnType; return this; } /** * Set the flag to ignore validation on the source XML. *

Default value is : {@code false}

* * @param validationIgnored the flag to ignore validation. * @return instance. */ public XPathEvaluator withValidationIgnored(final boolean validationIgnored) { this.ignoreValidation = validationIgnored; return this; } /** * Set the configuration that must be used by saxon * If not specified, saxon will create a new one by default * * @param configuration the configuration * @return instance */ public XPathEvaluator withConfiguration(final Configuration configuration) { this.configuration = configuration; return this; } /** * Evaluate the XPath. * * @param the return type. * @return the value. */ public T evaluate() { try { if (Objects.nonNull(this.inputSource.getByteStream())) { this.inputSource.getByteStream().reset(); } } catch (IOException e) { logger.warn("Could not reset input source stream. Still will try to evaluate XPath", e); } final XPath xPath = XPathFactoryUtils.newXPath(this.inputSource, variables, configuration); try { xPath.compile(this.xpathExpression); } catch (XPathExpressionException e) { throw new ApplicationException(XPathErrorType.ERROR_XPATH_EVALUATION, e) .addContextValue(XPathErrorContext.XPATH, this.xpathExpression); } if (ignoreValidation) { // convert the input source to disable all validations (especially DTD) final Document document = XmlDocumentBuilder.of(this.inputSource) .withValidationEnabled(false) .build(); this.inputSource = InputSourceBuilder.newInputSource(document); } try { return (T) xPath.evaluate(this.xpathExpression, this.inputSource, this.returnType); } catch (XPathExpressionException e) { throw new ApplicationException(XPathErrorType.ERROR_XPATH_EVALUATION, e); } } /** * Evaluate the XPath and return a collection of values. * * @return the collection of values. */ public Collection evaluateCollection() { List ret = new ArrayList<>(); this.withReturnType(XPathConstants.NODESET); List result = this.evaluate(); for (Object elem : result) { final String resValue = NodeInfoUtils.extractValue(elem); if (StringUtils.isNotBlank(resValue)) { ret.add(resValue); } } return ret; } /** * Evaluate the XPath as a boolean expression. * * @return the result. */ public boolean evaluateBoolean() { this.withReturnType(XPathConstants.BOOLEAN); this.xpathExpression = "fn:deep-equal(fn:true(), " + this.xpathExpression + ")"; return this.evaluate(); } /** * Hidden constructor. */ private XPathEvaluator() { } }