Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why XPath.evaluate is returning NULL?

Tags:

java

xml

xpath

When I use below code to modify an xml file I receive this error :

Exception in thread "main" java.lang.NullPointerException
    at ModifyXMLFile.updateFile(ModifyXMLFile.java:44)
    at ModifyXMLFile.main(ModifyXMLFile.java:56)

The error occurs at line : node.setTextContent(newValue);

Am I not using xpath correctly ?

Here is the code and the xml file I'm attempting to update

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.io.FileUtils;
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.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class ModifyXMLFile {

    public void updateFile(String newValue){

        XPathFactory xPathfactory = XPathFactory.newInstance();
        XPath xpath = xPathfactory.newXPath();

        try {

            File f = new File("C:\\pom.xml");
            InputStream in = new FileInputStream(f);  
            InputSource source = new InputSource(in);

            Node node = (Node)xpath.evaluate("/project/parent/version/text()", source, XPathConstants.NODE);
            node.setTextContent(newValue);

        } catch (XPathExpressionException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }       

    }

    public static void main(String argv[]) {

        new ModifyXMLFile().updateFile("TEST");

    }

}

xml file :

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>testgroup</groupId>
        <artifactId>testartifact</artifactId>
        <version>update</version>
    </parent>

</project>
like image 483
blue-sky Avatar asked Feb 15 '23 05:02

blue-sky


1 Answers

xmlns="http://maven.apache.org/POM/4.0.0"

means that the un-prefixed element names in the XML file are in this namespace. In XPath 1.0 unprefixed node names always refer to nodes in no namespace, so /project/parent/version correctly matches nothing.

To match namespaced nodes in XPath you need to bind a prefix to the namespace URI and then use that prefix in the expression. For javax.xml.xpath this means creating a NamespaceContext. Unfortunately there are no default implementations of this interface in the standard Java library, but the Spring framework provides a SimpleNamespaceContext that you can use

XPath xpath = xPathfactory.newXPath();
SimpleNamespaceContext nsCtx = new SimpleNamespaceContext();
xpath.setNamespaceContext(nsCtx);
nsCtx.bindNamespaceUri("pom", "http://maven.apache.org/POM/4.0.0");

// ...
Node node = (Node)xpath.evaluate("/pom:project/pom:parent/pom:version/text()", source, XPathConstants.NODE);

That said, you'll still need to do a bit more work to actually modify the file, as you're currently loading it and modifying the DOM but then not saving the modified DOM anywhere.

An alternative approach might be to use XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
                xmlns:pom="http://maven.apache.org/POM/4.0.0">
  <xsl:param name="newVersion" />

  <!-- identity template - copy input to output unchanged except when
       overridden -->
  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
  </xsl:template>

  <!-- override for the version value -->
  <xsl:template match="pom:version/text()">
    <xsl:value-of select="$newVersion" />
  </xsl:template>
</xsl:stylesheet>

Then you can use the Transformer API to call this stylesheet with an appropriate parameter

StreamSource xslt = new StreamSource(new File("transform.xsl"));
Transformer transformer = TransformerFactory.newInstance().newTransformer(xslt);
transformer.setParameter("newVersion", newValue);
StreamSource input = new StreamSource(new File("C:\\pom.xml"));
StreamResult output = new StreamResult(new File("C:\\updated-pom.xml"));
transformer.transform(input, output);
like image 185
Ian Roberts Avatar answered Feb 17 '23 16:02

Ian Roberts