首页 > 解决方案 > 尝试使用 Java 和 Xpath 更新 XML 文档中任何选定节点的值

问题描述

我开发了 GUI 工具,将 XML 文档显示为可编辑的 JTree,用户可以在 JTree 中选择一个节点并尝试更改 XML 文档中的实际节点值。

我遇到的问题是构建尝试实际更新的正确 Xpath 查询。

这是 JTree 的 GUI,显示选择了哪个元素并且应该编辑:

在此处输入图像描述

它是一个非常大的 XML,所以这里是 XML 的折叠片段:

更新(忽略尝试 1 和 2,第一个问题已解决,转到尝试 3 和 4)

尝试 1 #(尝试创建 XPath 查询以更新节点值的相关 Java 方法):

public void updateXmlData(JTree jTree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
        System.out.println("Selected path=" + jTree.getSelectionPath().toString());

        String[] pathTockens = jTree.getSelectionPath().toString().split(",");
        StringBuilder sb = new StringBuilder();
        //for loop to construct xpath query 
        for (int i = 0; i < pathTockens.length - 1; i++) {
            if (i == 0) {
                sb.append("//");
            } else {
                sb.append(pathTockens[i].trim());
                sb.append("/");
            }

        }//end for loop 
        sb.append("text()");
        System.out.println("Constructed XPath Query:" + sb.toString());
        //new xpath 
        XPath xpath = XPathFactory.newInstance().newXPath();
        //compile query 
        NodeList nodes = (NodeList) xpath.compile(sb.toString()).evaluate(doc, XPathConstants.NODESET);
        //Make the change on the selected nodes
        for (int idx = 0; idx < nodes.getLength(); idx++) {
            Node value = nodes.item(idx).getAttributes().getNamedItem("value");
            String val = value.getNodeValue();
            value.setNodeValue(val.replaceAll(oldValue, newValue));
        }
        //set the new updated xml doc 
        SingleTask.currentTask.setDoc(doc);
    }

控制台日志:

Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest.xml, Ingest, Property_Maps, identifier, identifieXYZ]
Constructed XPath Query://Ingest/Property_Maps/identifier/text()
Jan 26, 2021 2:04:16 PM com.xyz.XmlToXsdValidator.Views.EditXmlTreeNodeDialogJFrame jButtonOkEditActionPerformed
SEVERE: null

javax.xml.transform.TransformerException: Unable to evaluate expression using this context
    at com.sun.org.apache.xpath.internal.XPath.execute(XPath.java:368)

正如您在日志中看到的:

选择路径=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest.xml, Ingest, Property_Maps, identifier, identifieXYZ]

构造的 XPath 查询://Ingest/Property_Maps/identifier/text()

路径是正确的,基本上是 Ingest->Property_Maps->identifier->text()

但我得到:

 javax.xml.transform.TransformerException: Unable to evaluate expression using this context

尝试 2 #(尝试创建 XPath 查询以更新节点值的相关 Java 方法):

public void updateXmlData(JTree jTree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
        // Locate the node(s) with xpath
        System.out.println("Selected path=" + jTree.getSelectionPath().toString());

        String[] pathTockens = jTree.getSelectionPath().toString().split(",");
        StringBuilder sb = new StringBuilder();
        //loop to construct xpath query 
        for (int i = 0; i < pathTockens.length - 1; i++) {
            if (i == 0) {
                sb.append("//");
            } else {
                sb.append(pathTockens[i].trim());
                sb.append("/");
            }
        }//end loop 

        sb.append("[text()=");
        sb.append("'");
        sb.append(oldValue);
        sb.append("']");
        int lastIndexOfPathChar = sb.lastIndexOf("/");
        sb.replace(lastIndexOfPathChar, lastIndexOfPathChar + 1, "");
        System.out.println("Constructed XPath Query:" + sb.toString());
        //new xpath instance 
        XPath xpath = XPathFactory.newInstance().newXPath();
        NodeList nodes = (NodeList) xpath.evaluate(sb.toString(), doc, XPathConstants.NODESET);

        //Make the change on the selected nodes
        for (int idx = 0; idx < nodes.getLength(); idx++) {
            Node value = nodes.item(idx).getAttributes().getNamedItem("value");
            String val = value.getNodeValue();
            value.setNodeValue(val.replaceAll(oldValue, newValue));
        }

        SingleTask.currentTask.setDoc(doc);
    }

我能够解决基于异常的 Andreas 注释,并且没有更多异常/错误,但是 XPath 查询没有找到选定的节点。返回空

新更新的代码:

尝试#3 使用自定义命名空间解析器。参考资料:https ://www.kdgregory.com/index.php?page=xml.xpath

   public boolean updateXmlData(JTree jTree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
            System.out.println("Selected path=" + jTree.getSelectionPath().toString());
            boolean changed = false;
            // Locate the node(s) with xpath
            String[] pathTockens = jTree.getSelectionPath().toString().split(",");
            StringBuilder sb = new StringBuilder();
            //loop to construct xpath query 
            for (int i = 0; i < pathTockens.length - 1; i++) {
    
                if (i == 0) {
                    //do nothing
                } else if (i == 1) {
                    sb.append("/ns:" + pathTockens[i].trim());
                } else if (i > 1 && i != pathTockens.length - 1) {
                    sb.append("/ns:" + pathTockens[i].trim());
                } else {
                    //sb.append("/" + pathTockens[i].trim());
                }
            }//end loop 
    
            sb.append("[text()=");
            sb.append("'");
            sb.append(oldValue);
            sb.append("']");
    
            System.out.println("Constructed XPath Query:" + sb.toString());
            //new xpath instance 
            XPathFactory xpathFactory = XPathFactory.newInstance(); 
            XPath xpath = xpathFactory.newXPath();
          
            xpath.setNamespaceContext(new UniversalNamespaceResolver(SingleTask.currentTask.getXsdFile().getXsdNameSpace()));
            NodeList nodes = (NodeList) xpath.evaluate(sb.toString(), doc, XPathConstants.NODESET);
    
            //start for 
            Node node;
            String val = null;
            for (int idx = 0; idx < nodes.getLength(); idx++) {
                if (nodes.item(idx).getAttributes() != null) {
    
                    node = nodes.item(idx).getAttributes().getNamedItem("value");
                    if (node != null) {
    
                        val = node.getNodeValue();
                        node.setNodeValue(val.replaceAll(oldValue, newValue));
                        changed = true;
                        break;
                    }//end if node is found
                }
            }//end for 
            //set the new updated xml doc 
            SingleTask.currentTask.setDoc(doc);
    
            return changed;
        }

实现自定义命名空间解析器的类:

 import java.util.Arrays;
    import java.util.Collections;
    import java.util.Iterator;
    import java.util.List;
    import javax.xml.XMLConstants;
    import javax.xml.namespace.NamespaceContext;
    import org.w3c.dom.Document;
    
        /**
         *
         * References:https://www.kdgregory.com/index.php?page=xml.xpath
         */
        //custom  NamespaceContext clss implementation 
        public class UniversalNamespaceResolver implements NamespaceContext
        {
            private String _prefix = "ns";
            private String _namespaceUri=null; 
            private List<String> _prefixes = Arrays.asList(_prefix);
            
            public UniversalNamespaceResolver(String namespaceResolver)
            {
                _namespaceUri = namespaceResolver; 
            }
         
            @Override
            @SuppressWarnings("rawtypes")
            public Iterator getPrefixes(String uri)
            {
                if (uri == null)
                    throw new IllegalArgumentException("UniversalNamespaceResolver getPrefixes() URI may not be null");
                else if (_namespaceUri.equals(uri))
                    return _prefixes.iterator();
                else if (XMLConstants.XML_NS_URI.equals(uri))
                    return Arrays.asList(XMLConstants.XML_NS_PREFIX).iterator();
                else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri))
                    return Arrays.asList(XMLConstants.XMLNS_ATTRIBUTE).iterator();
                else
                    return Collections.emptyList().iterator();
            }
        
        
            @Override
            public String getPrefix(String uri)
            {
                if (uri == null)
                    throw new IllegalArgumentException("nsURI may not be null");
                else if (_namespaceUri.equals(uri))
                    return _prefix;
                else if (XMLConstants.XML_NS_URI.equals(uri))
                    return XMLConstants.XML_NS_PREFIX;
                else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri))
                    return XMLConstants.XMLNS_ATTRIBUTE;
                else
                    return null;
            }
        
        
            @Override
            public String getNamespaceURI(String prefix)
            {
                if (prefix == null)
                    throw new IllegalArgumentException("prefix may not be null");
                else if (_prefix.equals(prefix))
                    return _namespaceUri;
                else if (XMLConstants.XML_NS_PREFIX.equals(prefix))
                    return XMLConstants.XML_NS_URI;
                else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
                    return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
                else
                    return null;
            }
        }

控制台输出:

选择路径=[C:\Users\xyz\DocumentsIngest_LDD.xml, Ingest_LDD, Property_Maps, identifier, identifier1]

构造的 XPath: Query:/ns:Ingest_LDD/ns:Property_Maps/ns:identifier[text()='identifier1']

尝试#4(没有自定义命名空间解析器):

public boolean updateXmlData(JTree jTree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
        System.out.println("Selected path=" + jTree.getSelectionPath().toString());
        boolean changed = false;
        // Locate the node(s) with xpath
        String[] pathTockens = jTree.getSelectionPath().toString().split(",");
        StringBuilder sb = new StringBuilder();
        //loop to construct xpath query 
        for (int i = 0; i < pathTockens.length - 1; i++) {

            if (i == 0) {
                //do nothing
            } else if (i == 1) {
                sb.append("/" + pathTockens[i].trim());
            } else if (i > 1 && i != pathTockens.length - 1) {
                sb.append("/" + pathTockens[i].trim());
            } else {
                //sb.append("/" + pathTockens[i].trim());
            }
        }//end loop 

        sb.append("[text()=");
        sb.append("'");
        sb.append(oldValue);
        sb.append("']");

        System.out.println("Constructed XPath Query:" + sb.toString());
        //new xpath instance 
        XPathFactory xpathFactory = XPathFactory.newInstance(); 
        XPath xpath = xpathFactory.newXPath();
      
        //WITHOUT CUSTOM NAMESPACE CONTEXT xpath.setNamespaceContext(new UniversalNamespaceResolver(SingleTask.currentTask.getXsdFile().getXsdNameSpace()));
        NodeList nodes = (NodeList) xpath.evaluate(sb.toString(), doc, XPathConstants.NODESET);

        //start for 
        Node node;
        String val = null;
        for (int idx = 0; idx < nodes.getLength(); idx++) {
            if (nodes.item(idx).getAttributes() != null) {

                node = nodes.item(idx).getAttributes().getNamedItem("value");
                if (node != null) {

                    val = node.getNodeValue();
                    node.setNodeValue(val.replaceAll(oldValue, newValue));
                    changed = true;
                    break;
                }//end if node is found
            }
        }//end for 
        //set the new updated xml doc 
        SingleTask.currentTask.setDoc(doc);

        return changed;
    }

控制台输出:

所选路径 = [C:\Users\anaim\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, Property_Maps, identifier, identifier1]

构造的 XPath查询:/Ingest_LDD/Property_Maps/identifier[text()='identifier1']

我实际上使用( https://www.freeformatter.com/xpath-tester.html#ad-output)在线手动编写了 XPath 查询

抱歉,我无法提供示例 XMl,它太大了。

手动 XPath 查询是:

/Ingest_LDD/Property_Maps/identifier[text()='identifier1']

在线工具成功找到文本并输出:

Element='<identifier xmlns="http://pds.nasa.gov/pds4/pds/v1">identifier1</identifier>' 

因此,我在尝试 #4 下的代码和查询应该可以工作吗?

用户输入后的更新尝试:

尝试 #5(基于用户的响应,命名空间感知 = TRUE),相关代码如下

factory.setNamespaceAware(true);
doc = dBuilder.parse(xmlFile);

if (doc!=null)
{
    //***NOTE program comes meaning doc is NOT null,  however inspecting it shows [#document: null]

    doc.getDocumentElement().normalize();
}


xpath.setNamespaceContext(new UniversalNamespaceResolver(SingleTask.currentTask.getXsdFile().getXsdNameSpace()));
Node node = (Node) xpath.evaluate(sb.toString(), doc, XPathConstants.NODE);
if (node!=null)
{
    // See https://docs.oracle.com/javase/9/docs/api/org/w3c/dom/Node.html#setTextContent-java.lang.String-
    node.setTextContent(newValue);
    SingleTask.currentTask.setDoc(doc);
}

输出(再次找不到节点):

Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, name, name1]
Constructed XPath Query:/Ingest_LDD/name[text()='name1']
Error changing value!

尝试 #6(基于用户的响应,命名空间感知 = FALSE)

factory.setNamespaceAware(false);
doc = dBuilder.parse(xmlFile);

if (doc!=null)
{
    //***NOTE program comes meaning doc is NOT null,  however inspecting it shows [#document: null]
    doc.getDocumentElement().normalize();
}


//COMMENTED OUT , SINCE NAMESPACE AWARE FALSE xpath.setNamespaceContext(new UniversalNamespaceResolver(SingleTask.currentTask.getXsdFile().getXsdNameSpace()));
Node node = (Node) xpath.evaluate(sb.toString(), doc, XPathConstants.NODE);
if (node!=null)
{
    // See https://docs.oracle.com/javase/9/docs/api/org/w3c/dom/Node.html#setTextContent-java.lang.String-
    node.setTextContent(newValue);
    SingleTask.currentTask.setDoc(doc);
}

输出(再次找不到节点):

Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, name, name1]
Constructed XPath Query:/Ingest_LDD/name[text()='name1']
Error changing value!

根据( DocumentBuilder.parse(InputStream) 返回 null ),返回的文档[#document: null]实际上可能不是问题?

尝试 #7(命名空间感知 FALSE)

NamedNodeMap namedNodeMap = doc.getAttributes();返回 NULL。

但是, Node firstChild = doc.getFirstChild()实际上返回的是有效元素!

我将 firstChild 传递给xpath.evaluate(sb.toString(), firstChild , XPathConstants.NODE);但再次找不到所需的节点。

输出(再次找不到节点):

Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, name, name1]
Constructed XPath Query:/Ingest_LDD/name[text()='name1']
Error changing value!

尝试#8(命名空间感知错误)

我还尝试传递doc.getChildNodes()xpath.evaluate()而不是doc对象作为最终的绝望尝试,请参见下面的片段。

if (doc != null) {

            NodeList nodes = (NodeList) xpath.evaluate(sb.toString(), doc.getChildNodes(), XPathConstants.NODESET);

            String val = null;
            Node node; 
            for (int idx = 0; idx < nodes.getLength(); idx++) {
                if (nodes.item(idx).getAttributes() != null) {

                    node = nodes.item(idx).getAttributes().getNamedItem("value");
                    if (node != null) {

                        val = node.getNodeValue();
                        node.setNodeValue(val.replaceAll(oldValue, newValue));
                        changed = true;
                        break;
                    }//end if node is found
                }
            }//end for 
        }

输出(再次找不到节点):

Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, name, name1]
Constructed XPath Query:/Ingest_LDD/name[text()='name1']
Error changing value!

标签: javaxmlxpath

解决方案


对于您在线执行的测试,您的 XML 文件似乎包含名称空间信息。

考虑到这些信息,您的两个 XPath 评估示例可能会起作用,也可能不起作用,这取决于几件事。

例如,您可能可以使用尝试 #4,如果您使用的是非命名空间感知(默认)DocumentBuilderFactory并且您没有在 XPath 表达式中提供任何命名空间信息,那么 XPath 评估就足够了。

但是,如果应用相反的条件,尝试 #3 中的 XPath 评估也可能是足够的,即,您正在使用命名空间感知DocumentBuilderFactory

DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);

并且您在 XPath 表达式中提供命名空间信息和方便的NamespaceContext实现。请参阅这个相关的 SO 问题和这篇很棒的 IBM 文章。

请注意,您不需要在 XML 文件和 XPath 表达式中提供相同的名称空间前缀,唯一的要求是 XML 中的名称空间感知(XPath 始终是名称空间感知的)。

鉴于这种情况,我认为您可以同时应用这两种方法。

在任何情况下,我认为问题可能与您处理实际文本替换的方式有关:您正在寻找具有value属性的节点,并查看相关的XML 模式,该属性不存在。

请考虑以下方法:

// You can get here following both attempts #3 an #4
Node node = (Node) xpath.evaluate(sb.toString(), doc, XPathConstants.NODE);
boolean changed = node != null;
if (changed) {
  // See https://docs.oracle.com/javase/9/docs/api/org/w3c/dom/Node.html#setTextContent-java.lang.String-
  node.setTextContent(newValue);
  SingleTask.currentTask.setDoc(doc);
}

return changed;

此代码假定所选节点是唯一的才能正常工作。

尽管可能不同,但请注意,JTree如果您为 XML 中的重复元素定义相同的值,则从模型构造 XPath 选择器的方式可能会提供重复项。例如,考虑屏幕截图中的元素external_id_property_maps

为了避免这种情况,您可以在构造 XPath 选择器时采用不同的方法。

您的代码片段尚不清楚,但可能您正在DefaultMutableTreeNode用作基本JTree节点类型。如果是这种情况,您可以将所需的任意信息与每个节点相关联。

例如,考虑创建一个简单的 POJO,其中包含两个字段、Element节点表示的名称以及某种唯一的、生成的、id,让我们命名它uiduuid避免与id属性混淆,很可能包含在原始 XML 中文档。

uid应该与每个节点相关联。也许您可以利用JTree创建过程,在处理 XML 文件的每个节点时,也包括使用UUID类生成的这个属性,例如。

或者,您可以在表示之前对原始 XML 文档应用 XSLT 转换:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:attribute name="uid">
        <xsl:value-of select="generate-id(.)"/>
      </xsl:attribute>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

进行此更改后,您的 XPath 查询应如下所示:

/ns:Ingest_LDD[@uid='w1ab1']/ns:Property_Maps[@uid='w1ab1a']/ns:identifier[@uid='w1ab1aq']

当然,有必要修改用于从所选路径构建此表达式的代码,JTree以将自定义对象考虑在内。

您可以将这种方法发挥到极致,并使用仅基于此属性的单个选择器uid,尽管我认为出于性能原因它不合适:

//*[@uid='w1ab1']

综上所述,您可以尝试以下方法。

请考虑这个 XML 文件:

<?xml version="1.0" encoding="utf-8" ?>
<Ingest_LDD xmlns="http://pds.nasa.gov/pds4/pds/v1"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://pds.nasa.gov/pds4/pds/v1 https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1700.xsd">
    <!-- Please, forgive me, I am aware that the document is not XML Schema conformant,
    only for exemplification of the default namespace -->
    <Property_Maps>
        <identifier>identifier1</identifier>
    </Property_Maps>
</Ingest_LDD>

首先,让我们解析文档:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
// As the XML contains namespace, let's configure the parser namespace aware
// This will be of relevance when evaluating XPath
builderFactory.setNamespaceAware(true);
DocumentBuilder builder = builderFactory.newDocumentBuilder();
// Parse the document from some source
Document document = builder.parse(...);
// See http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
document.getDocumentElement().normalize();

现在,创建一个JTree与输入 XML 文件对应的结构。首先,让我们创建一个方便的 POJO 来存储所需的树节点信息:

public class NodeInformation {
  // Node name
  private String name;
  // Node uid
  private String did;
  // Node value
  private String value;

  // Setters and getters

  // Will be reused by DefaultMutableTreeNode
  @Override
  public String toString() {
    return this.name;
  }
}

将 XML 文件转换为其JTree对应文件:

// Get a reference to root element
Element rootElement = document.getDocumentElement();
// Create root tree node
DefaultMutableTreeNode rootTreeNode = getNodeInformation(rootElement);
// Traverse DOM
traverse(rootTreeNode, rootElement);
// Create tree and tree model based on the computed root tree node
DefaultTreeModel treeModel = new DefaultTreeModel(rootTreeNode);
JTree tree = new JTree(treeModel);

在哪里:

private NodeInformation getNodeInformation(Node childElement) {
  NodeInformation nodeInformation = new NodeInformation();
  String name = childElement.getNodeName();
  nodeInformation.setName(name);
  // Provide a new unique identifier for every node
  String uid = UUID.randomUUID().toString();
  nodeInformation.setUid(uid);
  // Uhnn.... We need to associate the new uid with the DOM node as well. 
  // There is nothing wrong with it but mutating the DOM in this way in
  // a method that should be "read-only" is not the best solution.
  // It would be interesting to study the above-mentioned XSLT approach
  chilElement.setAttribute("uid", uid);

  // Compute node value
  StringBuffer buffer = new StringBuffer();
  NodeList childNodes = childElement.getChildNodes();
  boolean found = false;
  for (int i = 0; i < childNodes.getLength(); i++) {
    Node node = childNodes.item(i);
    if (node.getNodeType() == Node.TEXT_NODE) {
      String value = node.getNodeValue();
      buffer.append(value);
      found = true;
    }
  }

  if (found) {
    nodeInformation.setValue(buffer.toString());
  }
}

和:

// Finds all the child elements and adds them to the parent node recursively
private void traverse(DefaultMutableTreeNode parentTreeNode, Node parentXMLElement) {
  NodeList childElements = parentXMLElement.getChildNodes();
  for(int i=0; i<childElements.getLength(); i++) {
    Node childElement = childElements.item(i);
    if (childElement.getNodeType() == Node.ELEMENT_NODE) {
      DefaultMutableTreeNode childTreeNode =
        new DefaultMutableTreeNode
          (getNodeInformation(childElement));
      parentTreeNode.add(childTreeNode);
      traverse(childTreeNode, childElement);
    }
  }
}

尽管NamespaceContext您提供的实现看起来不错,但请在第一步尝试一些更简单的方法,以尽量减少出错的可能性。请参阅下面提供的实现。

然后,您的updateXMLData方法应如下所示:

public boolean updateXmlData(JTree tree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
  boolean changed = false;

  TreePath selectedPath = tree.getSelectionPath();
  int count = getPathCount();
  StringBuilder sb = new StringBuilder();
  NodeInformation lastNodeInformation;
  if (count > 0) {
    for (int i = 1; i < trp.getPathCount(); i++) {
      DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) trp.getPathComponent(i);
      NodeInformation nodeInformation = (NodeInformation) treeNode.getUserObject();
      sb.append(String.format("/ns:%s[@uid='%s']", nodeInformation.getName(), nodeInformation.getUid());
      lastNodeInformation = nodeInformation;
    }
  }

  System.out.println("Constructed XPath Query:" + sb.toString());

  // Although the `NamespaceContext` implementation you provided looks
  // fine, please, at a first step, try something simpler, to minimize the
  // possibility of error. For example:
  NamespaceContext nsContext = new NamespaceContext() {

    public String getNamespaceURI(String prefix) {
      if (prefix == null) {
        throw new IllegalArgumentException("No prefix provided!");
      } else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
        return "http://pds.nasa.gov/pds4/pds/v1";
      } else if (prefix.equals("ns")) {
        return "http://pds.nasa.gov/pds4/pds/v1";
      } else {
        return XMLConstants.NULL_NS_URI;
      }
    }

    public String getPrefix(String namespaceURI) {
      // Not needed in this context.
      return null;
    }

    public Iterator getPrefixes(String namespaceURI) {
      // Not needed in this context.
      return null;
    }

  };

  //new xpath instance 
  XPathFactory xpathFactory = XPathFactory.newInstance(); 
  XPath xpath = xpathFactory.newXPath();
  // As the parser is namespace aware, we can safely use XPath namespaces
  xpath.setNamespaceContext(nsContext);
  
  Node node = (Node) xpath.evaluate(sb.toString(), doc, XPathConstants.NODE);
  boolean changed = node != null;
  if (changed) {
    // See https://docs.oracle.com/javase/9/docs/api/org/w3c/dom/Node.html#setTextContent-java.lang.String-
    node.setTextContent(newValue);
    SingleTask.currentTask.setDoc(doc);
    // Probably the information has been updated in the node, but just in case:
    lastNodeInformation.setValue(newValue);
  }

  return changed;
}

生成的 XPath 表达式将如下所示:

/ns:Ingest_LDD[@uid='w1ab1']/ns:Property_Maps[@uid='w1ab1a']/ns:identifier[@uid='w1ab1aq']

如果要使用默认命名空间,也可以尝试:

/:Ingest_LDD[@uid='w1ab1']/:Property_Maps[@uid='w1ab1a']/:identifier[@uid='w1ab1aq']

请注意,我没有测试过代码,但我希望你能明白。

只是为了澄清,为了给你一个正确的答案,如前所述,如果你现在删除或注释这行代码:

builderFactory.setNamespaceAware(true);

然后,XPath 表达式:

/ns:Ingest_LDD[@uid='w1ab1']/ns:Property_Maps[@uid='w1ab1a']/ns:identifier[@uid='w1ab1aq']

将不再找到所需的节点。现在,如果您从 XPath 表达式中删除命名空间信息:

/Ingest_LDD[@uid='w1ab1']/Property_Maps[@uid='w1ab1a']/identifier[@uid='w1ab1aq']

它将再次找到正确的节点。


推荐阅读