Java 使用xPath修改XML文件

Java 使用xPath修改XML文件,java,xml,file,xslt,xpath,Java,Xml,File,Xslt,Xpath,我想使用xPath修改现有的XML文件。如果该节点不存在,则应创建该节点(必要时还应创建其父节点)。例如: <?xml version="1.0" encoding="UTF-8"?> <configuration> <param0>true</param0> <param1>1.0</param1> </configuration> XML文件应如下所示: <?xml version="1.0"

我想使用xPath修改现有的XML文件。如果该节点不存在,则应创建该节点(必要时还应创建其父节点)。例如:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <param0>true</param0>
  <param1>1.0</param1>
</configuration>
XML文件应如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <param0>true</param0>
  <param1>4.0</param1>
  <param2>asdf</param2>
  <test>
    <param3>true</param3>
  </test>
</configuration>
运行此代码后,文件中的节点将发生更改。正是我想要的。但是,如果我使用以下路径之一,
节点
为null(因此会引发
NullPointerException
):

如何更改此代码以创建节点(以及不存在的父节点)

编辑:好的,澄清一下:我有一组参数要保存为XML。在开发过程中,此集合可以更改(一些参数被添加,一些参数被移动,一些参数被删除)。因此,我基本上希望有一个函数将当前参数集写入一个已经存在的文件。它应该覆盖文件中已经存在的参数,添加新参数并保留旧参数

与阅读一样,我可以使用xPath或其他坐标,然后从XML中获取值。如果不存在,则返回空字符串

我对如何实现它没有任何限制,xPath、DOM、SAX、XSLT。。。一旦编写了功能,它应该很容易使用(比如BeniBela的解决方案)

因此,如果我要设置以下参数:

/configuration/param1/text()         -> 4.0
/configuration/param2/text()         -> "asdf"
/configuration/test/param3/text()    -> true

结果应该是起始XML+这些参数。如果它们已经存在于该xPath中,则会被替换,否则会在该位置插入。

这里有一个简单的XSLT解决方案:

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

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="param1/text()">4.0</xsl:template>

 <xsl:template match="/*">
  <xsl:copy>
   <xsl:apply-templates select="@*|node()"/>
     <param2>asdf</param2>
     <test><param3>true</param3></test>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

4
asdf
真的
在提供的XML文档上应用此转换时:

<configuration>
    <param0>true</param0>
    <param1>1.0</param1>
</configuration>
<configuration>
   <param0>true</param0>
   <param1>4.0</param1>
   <param2>asdf</param2>
   <test><param3>true</param3></test>
</configuration>

真的
1
生成所需的正确结果:

<configuration>
    <param0>true</param0>
    <param1>1.0</param1>
</configuration>
<configuration>
   <param0>true</param0>
   <param1>4.0</param1>
   <param2>asdf</param2>
   <test><param3>true</param3></test>
</configuration>

真的
4
asdf
真的
注意事项

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

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="param1/text()">4.0</xsl:template>

 <xsl:template match="/*">
  <xsl:copy>
   <xsl:apply-templates select="@*|node()"/>
     <param2>asdf</param2>
     <test><param3>true</param3></test>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

XSLT转换从不“就地更新”。它总是创建一个新的结果树。因此,如果要修改同一文件,通常转换的结果以另一个名称保存,然后删除原始文件,并重命名结果,使其具有原始名称

如果您想要一个没有依赖项的解决方案,可以只使用DOM而不使用XPath/XSLT

Node.getChildNodes | getNodeName/NodeList.*可用于查找节点,Document.createElement | createTextNode,Node.appendChild可用于创建新节点

然后,您可以编写自己的简单“XPath”解释器,在路径中创建丢失的节点,如下所示:

public static void update(Document doc, String path, String def){
  String p[] = path.split("/");
  //search nodes or create them if they do not exist
  Node n = doc;
  for (int i=0;i < p.length;i++){
    NodeList kids = n.getChildNodes();
    Node nfound = null;
    for (int j=0;j<kids.getLength();j++) 
      if (kids.item(j).getNodeName().equals(p[i])) {
    nfound = kids.item(j);
    break;
      }
    if (nfound == null) { 
      nfound = doc.createElement(p[i]);
      n.appendChild(nfound);
      n.appendChild(doc.createTextNode("\n")); //add whitespace, so the result looks nicer. Not really needed
    }
    n = nfound;
  }
  NodeList kids = n.getChildNodes();
  for (int i=0;i<kids.getLength();i++)
    if (kids.item(i).getNodeType() == Node.TEXT_NODE) {
      //text node exists
      kids.item(i).setNodeValue(def); //override
      return;
    }

  n.appendChild(doc.createTextNode(def));    
}

我创建了一个使用XPATH创建/更新XML的小项目: 更改xml的代码如下所示:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(xmlfile);

XModifier modifier = new XModifier(document);
modifier.addModify("/configuration/param1", "asdf");
modifier.addModify("/configuration/param2", "asdf");
modifier.addModify("/configuration/test/param3", "true");
modifier.modify();

我想为你指出一种新的/新颖的方法,通过使用。。。为什么VTD-XML比针对这个问题提供的所有其他解决方案都要好得多,原因有很多。。。这里有一些链接

dfs

导入com.ximpleware.*;
导入java.io.*;
公共类modifyXML{
公共静态void main(字符串[]s)引发VTDException、IOException{
VTDGen vg=新VTDGen();
if(!vg.parseFile(“input.xml”,false))
返回;
VTDNav vn=vg.getNav();
自动驾驶仪ap=新自动驾驶仪(vn);
ap.selectXPath(“/configuration/param1/text()”);
XMLModifier xm=新的XMLModifier(vn);
//使用XPath
int i=ap.evalXPath();
如果(i!=-1){
xm.updateToken(i,“4.0”);
}
字符串s1=“asdf/n/ntrue/n”;
xm.insertAfterElement(s1);
output(“output.xml”);
}
}

谢谢您的示例。据我所知,复制所有现有节点,然后将
param1
的值更改为
4.0
,然后插入
param2/3
。但我仍然需要检查节点是否已经存在,然后更新或插入它,对吗?嗯,这只是转移了我的问题。我仍然需要创建一个文件(现在只是一个XSLT文件),并检查每个节点是否在给定的XML文件中都可用。也许我没有发现比使用DOM更简单的解决方案?@brimborium,xslt代码只有几行(可能是当前代码的两倍)。对于更复杂的问题,这个比率将缩短数十倍甚至数百倍。XSLT代码也更简单(在大多数情况下不需要显式的条件指令)、可扩展(由于模板和模板匹配)和可维护(由于前面的所有事实)。因此,总而言之,当问题稍微复杂一点时,可以清楚地看到使用XSLT的优势。查看“xslt”标记中提出的问题,并尝试在没有xslt的情况下解决它们:)@brimborium,您没有定义良好的问题。请编辑问题,并给我们一个或多个XML文档,以及每个文档应转换为的确切结果。如果你没有错过一个重要案例,人们会给你一个涵盖所有重要案例的解决方案。如果您未能提供一些重要的案例,那么不要抱怨人们无法猜到您当时脑子里想的是什么(不是)。@brimborium,XSLT 1.0或XSLT 2.0都无法动态计算碰巧包含语法有效XPath表达式的字符串。在XSLT 3.0中,将有一个新的指令
xsl:evaluate
()来准确地执行此操作,因此您的一般问题可以在纯XSLT 3.0中得到解决,并且可能是早期的实现,如Saxon 9.4已经支持此功能。JDK/JRE包含XPath(
javax.xml.XPath
)和XSLT(
javax.xml.transform
)API,所以这些方法不会引入任何依赖关系。@BlaiseDoughan:但是从他的评论来看
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(xmlfile);

XModifier modifier = new XModifier(document);
modifier.addModify("/configuration/param1", "asdf");
modifier.addModify("/configuration/param2", "asdf");
modifier.addModify("/configuration/test/param3", "true");
modifier.modify();
   import com.ximpleware.*;
    import java.io.*;
    public class modifyXML {
            public static void main(String[] s) throws VTDException, IOException{
                VTDGen vg = new VTDGen();
                if (!vg.parseFile("input.xml", false))
                    return;
                VTDNav vn = vg.getNav();
                AutoPilot ap = new AutoPilot(vn);
                ap.selectXPath("/configuration/param1/text()");
                XMLModifier xm = new XMLModifier(vn);
                // using XPath
                int i=ap.evalXPath();
                if(i!=-1){
                    xm.updateToken(i, "4.0");
                }
                String s1 ="<param2>asdf</param2>/n<test>/n<param3>true</param3>/n</test>";
                xm.insertAfterElement(s1);
                xm.output("output.xml");
            }
        }