XPath表达式根据属性值获取节点
XPath expression to get node based on attribute value
我有以下输入 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
但它们只 return 节点而不考虑 status
的值,或者只有 return 没有周围节点的属性。
有没有办法用 xpath 表达式来做到这一点?如果没有,简单 解决方案结合了其他 bash 工具也很好。
正如@svasa 在评论中所说,您应该使用 XSLT。您可以轻松地处理 bash 和 xsltproc, xmlstarlet (using tr
command), Saxon (java on the command line) 等
中的 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>
I have a small follow-up question, if you don't mind. When the
input.xml file does not contain any status=fail nodes, then the output
is just two lines: <?xml version="1.0"?>
and <rootnode/>
. Is it
possible two suppress the output entirely in this case? It is not
really a problem, I know how to work around it in bash. I am just
interested if there is a clean solution via xslt.
您可以省略 XML 声明(xsl:output
中的 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>
我有以下输入 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
但它们只 return 节点而不考虑 status
的值,或者只有 return 没有周围节点的属性。
有没有办法用 xpath 表达式来做到这一点?如果没有,简单 解决方案结合了其他 bash 工具也很好。
正如@svasa 在评论中所说,您应该使用 XSLT。您可以轻松地处理 bash 和 xsltproc, xmlstarlet (using tr
command), Saxon (java on the command line) 等
这是一个使用 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>
I have a small follow-up question, if you don't mind. When the input.xml file does not contain any status=fail nodes, then the output is just two lines:
<?xml version="1.0"?>
and<rootnode/>
. Is it possible two suppress the output entirely in this case? It is not really a problem, I know how to work around it in bash. I am just interested if there is a clean solution via xslt.
您可以省略 XML 声明(xsl:output
中的 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>