XPath(1.0 和 2.0)是一种用于 XML 文档的查询语言。因此它不能改变任何 XML 文档的节点和结构。
可以通过 XSLT 转换获得想要的结果(I. XSLT 1.0 下面使用):
<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:param name="pStart" select="/*/b"/>
<xsl:param name="pEnd" select="/*/d/d2/d2b"/>
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vPrecedingEnd" select=
"$pEnd/preceding::* | $pEnd/ancestor::*"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:choose>
<xsl:when test=
"count(.|$vFollowingStart) = count($vFollowingStart)
and
count(.|$vPrecedingEnd) = count($vPrecedingEnd)
">
<xsl:call-template name="identity"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
当此转换应用于提供的 XML 文档时:
<r>
<a/><b/><c/>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<e/>
</r>
产生了想要的正确结果:
<c/>
<d>
<d1/>
<d2>
<d2a/>
</d2>
</d>
说明:
身份规则“按原样”复制每个匹配的节点。
有一个覆盖任何元素的模板。
在这个模板中做了两个测试:当前节点是否属于所有元素的集合“跟随开始”和当前节点是否属于所有元素的集合“在结束之前”。如果是,则将当前节点传递给标识模板(复制),否则将其忽略(删除)。
二。 XSLT 2.0 解决方案
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pStart" select="/*/b"/>
<xsl:param name="pEnd" select="/*/d/d2/d2b"/>
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vPrecedingEnd" select=
"$pEnd/preceding::* | $pEnd/ancestor::*"/>
<xsl:variable name="vWanted" select=
"$vFollowingStart intersect $vPrecedingEnd"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(. intersect $vWanted)]">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
当对上述 XML 文件应用此转换时,再次产生相同的正确结果。
说明:使用 XPath 2.0 运算符intersect。
三。 XPath 1.0 解决方案,只选择节点而不更改文档:
为了便于阅读,我提供了一个 XSLT 转换,它输出选择所需节点的结果。出于同样的目的,子表达式被定义为变量:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pStart" select="/*/b"/>
<xsl:param name="pEnd" select="/*/d/d2/d2b"/>
<xsl:template match="node()|@*">
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vPrecedingEnd" select=
"$pEnd/preceding::* | $pEnd/ancestor::*"/>
<xsl:copy-of select=
"$vFollowingStart
[count(.|$vPrecedingEnd)
=
count($vPrecedingEnd)
]
"/>
</xsl:template>
</xsl:stylesheet>
当此转换应用于提供的 XML 文档(上图)时,输出所需的选定节点:
<c/>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
<d2a/>
说明:这里我使用 Kayessian(@Michael Kay)公式计算两个节点集 $ns1 和 $ns2 的交集:
$ns1[count(.|$ns2) = count($ns2)]
四。最后是Xpath 2.0的方案(对应XPath 1.0的方案):
我再次使用 XSLT (2.0) 转换将结果复制到输出:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pStart" select="/*/b"/>
<xsl:param name="pEnd" select="/*/d/d2/d2b"/>
<xsl:template match="node()|@*">
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vPrecedingEnd" select=
"$pEnd/preceding::* | $pEnd/ancestor::*"/>
<xsl:sequence select=
"$vFollowingStart intersect $vPrecedingEnd"/>
</xsl:template>
</xsl:stylesheet>
产生与 XPath 1.0 解决方案相同的结果(正是需要的节点):
<c/>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
<d2a/>
更新:这是针对“合理”问题的 XPath 1.0 解决方案。它再次表示为 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:param name="pStart" select="/*/*/b"/>
<xsl:param name="pEnd" select="/*/*/d/d2/d2b"/>
<xsl:variable name="vFollowingStart" select=
"$pStart/following::* | $pStart/descendant::*"/>
<xsl:variable name="vcommonAncestor" select=
"$pStart/ancestor::*
[count(.|$pEnd/ancestor::*)
=
count($pEnd/ancestor::*)
][1]
"/>
<xsl:variable name="vEndHighestAncestor" select=
"$vcommonAncestor/*
[count($pEnd | descendant::*)
=
count(descendant::*)
]"/>
<xsl:variable name="vPrecedingEnd" select=
"$vEndHighestAncestor/preceding::*
|
$vEndHighestAncestor/ancestor::*"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:copy-of select=
"//*[count(.|$vFollowingStart) = count($vFollowingStart)
and
count(.|$vPrecedingEnd) = count($vPrecedingEnd)
]
"/>
</xsl:template>
</xsl:stylesheet>
当此转换应用于以下 XML 文档时(与提供的相同,但包装在一个顶部元素中,并将两个子元素(g 和 h)添加到 @ 987654336@——让它更有趣:
<t>
<r>
<a/><b/><c><g/><h/></c>
<d>
<d1/>
<d2>
<d2a/>
<d2b/>
<d2c/>
</d2>
</d>
<e/>
</r>
</t>
选择所需的正确节点集并将其复制到输出:
<c>
<g/>
<h/>
</c>
<g/>
<h/>
解释:这和以前几乎一样,但是我们把$pEnd当作它的最高祖先——它是$pStart和$pEnd共同祖先的直系子代。