【问题标题】:Why XPath.evaluate is returning NULL?为什么 XPath.evaluate 返回 NULL?
【发布时间】:2014-01-07 11:18:21
【问题描述】:

当我使用以下代码修改 xml 文件时,我收到此错误:

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

错误发生在以下行:node.setTextContent(newValue);

我没有正确使用 xpath 吗?

这是我正在尝试更新的代码和 xml 文件

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 文件:

<?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>

【问题讨论】:

    标签: java xml xpath


    【解决方案1】:
    xmlns="http://maven.apache.org/POM/4.0.0"
    

    表示 XML 文件中不带前缀的元素名称在此命名空间中。在 XPath 1.0 中,不带前缀的节点名称总是指代 no 命名空间中的节点,因此 /project/parent/version 不会正确匹配任何内容。

    要匹配 XPath 中的命名空间节点,您需要将前缀绑定到命名空间 URI,然后在表达式中使用该前缀。对于javax.xml.xpath,这意味着创建一个NamespaceContext。不幸的是,标准 Java 库中没有此接口的默认实现,但 Spring 框架提供了一个您可以使用的SimpleNamespaceContext

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

    也就是说,您仍然需要做更多的工作来实际修改文件,因为您当前正在加载它并修改 DOM,但没有将修改后的 DOM 保存在任何地方。

    另一种方法可能是使用 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>
    

    然后你可以使用Transformer API 来调用这个带有适当参数的样式表

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

    【讨论】:

      【解决方案2】:

      由于您的 XML 文档是命名空间限定的(请参阅 project 元素中的 xmlns 属性):

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

      您需要在 XPath 对象上设置javax.xml.namespace.NamespaceContext 的实现。这用于返回 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;
              }
          });
      

      您需要更改 XPath 以包含 NamespaceContext 中使用的前缀。现在您不是在寻找名为 project 的元素,而是在寻找具有本地名称 project 的命名空间限定元素,NamespaceContext 将解析前缀以匹配您正在寻找的实际 URI。

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

      【讨论】:

        【解决方案3】:

        @Adrain 请使用以下 xpath,您应该能够获取或更改值 /parent/version/text()

        【讨论】:

        • 您阅读其他答案了吗?由于默认的命名空间声明,这将不起作用。
        猜你喜欢
        • 2022-12-26
        • 2016-12-28
        • 2015-11-25
        • 2015-07-04
        • 2019-09-29
        • 2015-02-25
        • 2015-10-21
        • 2021-08-12
        • 2011-03-18
        相关资源
        最近更新 更多