XPath表达式根据属性值获取节点

时间:2016-10-10 17:29:06

标签: xml bash shell xpath xmllint

我有以下输入xml文件:

<rootnode>
 <section id="1" status="fail">
  <outer status="fail">
   <inner status="fail"/>
   <inner status="pass"/>
  </outer>
  <outer status="pass">
   <inner status="pass"/>
  </outer>
  <outer status="pass"/>
  <outer status="fail"/>
 </section>
 <section id="2" status="fail">
  <outer status="fail">
   <inner status="pass"/>
   <inner status="fail"/>
   <inner status="inc"/>
  </outer>
 </section>
</rootnode>

我想过滤掉所有非失败状态节点,以便结果如下所示:

<rootnode>
 <section id="1" status="fail">
  <outer status="fail">
   <inner status="fail"/>
  </outer>
  <outer status="fail"/>
 </section>
 <section id="2" status="fail">
  <outer status="fail">
   <inner status="fail"/>
  </outer>
 </section>
</rootnode>

<rootnode>不一定必须包含在结果中。我试图将xmllint与xpath表达式一起使用。我可以用

提取特定节点
xmllint --xpath "//inner" input.xml
xmllint --xpath "//@status" input.xml

但它们只返回节点而不考虑status的值,或者只返回没有周围节点的属性。

有没有办法用xpath表达式做到这一点?如果没有,那么包含其他bash工具的简单解决方案也很好。

1 个答案:

答案 0 :(得分:2)

就像@svasa在评论中所说,你应该使用XSLT。您可以使用xsltprocxmlstarlet(使用tr命令),Saxoncommand line上的java)等轻松处理bash中的XSLT。

以下是使用xsltproc的示例:

$ xsltproc so.xsl so.xml
<?xml version="1.0"?>
<rootnode>
  <section id="1" status="fail">
    <outer status="fail">
      <inner status="fail"/>
    </outer>
    <outer status="fail"/>
  </section>
  <section id="2" status="fail">
    <outer status="fail">
      <inner status="fail"/>
    </outer>
  </section>
</rootnode>

XML输入(so.xml)

<rootnode>
    <section id="1" status="fail">
        <outer status="fail">
            <inner status="fail"/>
            <inner status="pass"/>
        </outer>
        <outer status="pass">
            <inner status="pass"/>
        </outer>
        <outer status="pass"/>
        <outer status="fail"/>
    </section>
    <section id="2" status="fail">
        <outer status="fail">
            <inner status="pass"/>
            <inner status="fail"/>
            <inner status="inc"/>
        </outer>
    </section>
</rootnode>

XSLT 1.0 (so.xsl)

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

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

  <xsl:template match="*[@status[not(normalize-space()='fail')]]"/>

</xsl:stylesheet>
  

如果你不介意,我会有一个小的后续问题。当。。。的时候   input.xml文件不包含任何status = fail节点,然后是输出   只有两行:<?xml version="1.0"?><rootnode/>。是吗   在这种情况下,可能两个完全抑制输出?它不是   真的有问题,我知道如何在bash中解决它。我只是   感兴趣,如果通过xslt有一个干净的解决方案。

您可以做的是省略XML声明(omit-xml-declaration="yes"中的xsl:output)并检查是否存在status="fail"的任何元素。我会使用密钥(xsl:key)...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" omit-xml-declaration="yes">
    <!--If you need to output the declaration when there
    are elements with status="fail", it might be best to post process files that
    only contain the xml declaration.-->
  </xsl:output>
  <xsl:strip-space elements="*"/>

  <!--Key of all elements with status="fail".-->  
  <xsl:key name="fails" match="*[@status='fail']" use="@status"/>

  <xsl:template match="/*[not(key('fails','fail'))]">
    <!--If there aren't any elements with status="fail", don't process
    anything else.-->
  </xsl:template>

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

  <xsl:template match="*[@status[not(normalize-space()='fail')]]"/>

</xsl:stylesheet>