我知道这之前已经回答过了,但我很好奇如何在没有键的情况下实现这一目标。
我有这个XML,我必须获取{%tab%}和{%endtab%}之间的节点。
<element>
<hello>{%tab%}</hello>
<hello>yes</hello>
<hello>{%endtab%}</hello>
<hello>no</hello>
<hello>no</hello>
<hello>{%tab%}</hello>
<hello>yes</hello>
<hello>{%endtab%}</hello>
</element>
这就是我得到的:
<xsl:template match="hello[preceding-sibling::hello[not(normalize-space(text())!='{%tab%}')]]
[following-sibling::hello[not(normalize-space(text())!='{%endtab%}')]]
[
(preceding-sibling::hello[not(normalize-space(text())!='{%tab%}')])[1]/(following-sibling::hello[not(normalize-space(text())!='{%endtab%}')])[1]
= (following-sibling::hello[not(normalize-space(text())!='{%endtab%}')])[1]
]">
<strong><xsl:value-of select="." /></strong>
</xsl:template>
这将选择后跟 {%endtab%} 且前面有 {%tab%} 节点的所有节点。
hello[preceding-sibling::hello[not(normalize-space(text())!='{%tab%}')]]
[following-sibling::hello[not(normalize-space(text())!='{%endtab%}')]]
这样做的问题是,"no"节点也被选中,因为它们也位于这些节点之间。因此,我必须确保某个节点后面的 {%endtab%}(其第一次出现)与前面的 {%tab%} 后面的 {%tab%} 相同,我对此 XPath 执行此操作:
[
(preceding-sibling::hello[not(normalize-space(text())!='{%tab%}')])[1]
/(following-sibling::hello[not(normalize-space(text())!='{%endtab%}')])[1]
= (following-sibling::hello[not(normalize-space(text())!='{%endtab%}')])[1]
]
但是,这并没有像预期的那样过滤"no"节点。
忽略以下同级并计算前面的 tab
s 和前面的 endtab
s。 如果他们不平等,你要么是endtab
,要么是yes
。 排除您是endtab
的情况。
请允许我省略normalize-space()
使例子更清楚。
<xsl:template match="hello[
(. != '{%endtab%}')
and
(
count(preceding-sibling::hello[. = '{%tab%}'])
!= count(preceding-sibling::hello[. = '{%endtab%}'])
)
]">
这个 XPath 表达式:
/*/*[not(. = '{%tab%}' or . = '{%endtab%}')
and preceding-sibling::*[. = '{%tab%}' or . = '{%endtab%}'][1] = '{%tab%}'
and following-sibling::*[. = '{%tab%}' or . = '{%endtab%}'][1] = '{%endtab%}'
]
选择作为顶部元素的子元素且介于具有字符串值{%tab%}
的同级元素和具有字符串值{%endtab%}
的同级元素之间的任何元素
下面是一个包含简单 XSLT 转换的运行证明:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select=
"/*/*[not(. = '{%tab%}' or . = '{%endtab%}')
and preceding-sibling::*[. = '{%tab%}' or . = '{%endtab%}'][1] = '{%tab%}'
and following-sibling::*[. = '{%tab%}' or . = '{%endtab%}'][1] = '{%endtab%}'
]"/>
</xsl:template>
</xsl:stylesheet>
在提供的源 XML 文档上应用此转换时:
<element>
<hello>{%tab%}</hello>
<hello>yes</hello>
<hello>{%endtab%}</hello>
<hello>no</hello>
<hello>no</hello>
<hello>{%tab%}</hello>
<hello>yes</hello>
<hello>{%endtab%}</hello>
</element>
生成所需的正确结果:
<hello>yes</hello>
<hello>yes</hello>
两个普遍感兴趣的注意事项:
1.
我知道这之前已经回答过,但我很好奇如何 无需密钥即可实现此目的。
使用密钥指向解决方案的链接将按顺序排列:
https://stackoverflow.com/a/28179346/3016153
阿拉伯数字。
由于之前已经提出了XSLT 2.0解决方案,因此我建议使用另一个我认为更简单的解决方案:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/element">
<xsl:copy>
<xsl:for-each-group select="hello" group-starting-with="hello[.='{%tab%}']">
<xsl:for-each-group select="current-group()" group-ending-with="hello[.='{%endtab%}']">
<xsl:if test="current-group()[self::hello[.='{%tab%}']]">
<xsl:for-each select="current-group()[not(position()=1 or position()=last())]">
<strong><xsl:value-of select="." /></strong>
</xsl:for-each>
</xsl:if>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
编辑:起初我没有注意到xslt-1.0标签;这个答案对于原始海报可能毫无用处
使用 XSLT 2.0 和 @bjimba 答案的相同聪明技巧,您可以将元素组合在一个"选项卡"中,这将允许对整个组和每个hello
元素执行一些操作:
XSLT 2.0
...
<xsl:for-each-group select="hello" group-by="my:tabId(.)">
<!-- do something with a whole run -->
<strong>
<!-- do something with the single elements -->
<xsl:apply-templates select="current-group()"/>
</strong>
</xsl:for-each-group>
...
<xsl:function name="my:tabId" as="xs:integer?">
<xsl:param name="e" as="element()"/>
<xsl:variable name="tabStartCount"
select="count($e/preceding-sibling::hello[. = '{%tab%}'])"/>
<xsl:variable name="tabEndCount"
select="count($e/preceding-sibling::hello[. = '{%endtab%}'])"/>
<xsl:sequence select="
if ($e != '{%endtab%}' and ($tabStartCount > $tabEndCount)) then
$tabStartCount
else
()
"/>
</xsl:function>