blue-sky
blue-sky

Reputation: 53786

Why XPath.evaluate is returning NULL?

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>

Upvotes: 5

Views: 9163

Answers (3)

Vinay
Vinay

Reputation: 734

@Adrain Please use the following xpath and you should be able to fetch or change the value /parent/version/text()

Upvotes: -3

bdoughan
bdoughan

Reputation: 148977

Since your XML document is namespace qualified (see xmlns attribute in the project element):

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

You will need to set an implementation of javax.xml.namespace.NamespaceContext on your XPath object. This is used to return the namespace information for an individual step in the XPath.

    // SET A NAMESPACECONTEXT
    xpath.setNamespaceContext(new NamespaceContext() {

        @Override
        public Iterator getPrefixes(String namespaceURI) {
            return null;
        }

        @Override
        public String getPrefix(String namespaceURI) {
            return null;
        }

        @Override
        public String getNamespaceURI(String prefix) {
            if("pom".equals(prefix)) {
                return "http://maven.apache.org/POM/4.0.0";
            }
            return null;
        }
    });

You need to change your XPath to include the prefixes used in the NamespaceContext. Now you are just not looking for an element called project, you are looking for a namespace qualified element with local name project, the NamespaceContext will resolve the prefix to match the actual URI you are looking for.

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

Upvotes: 5

Ian Roberts
Ian Roberts

Reputation: 122364

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

Upvotes: 6

Related Questions