首页 文章

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

提问于
浏览
1

我有以下输入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> 不一定必须包含在结果中 . 我试图使用带有xpath表达式的 xmllint . 我可以用 . 提取特定节点

xmllint --xpath "//inner" input.xml
xmllint --xpath "//@status" input.xml

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

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

1 回答

  • 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 Input (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" 中的 omit-xml-declaration="yes" ),并检查是否有任何带有 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>
    

相关问题