如何对节点之间的节点进行分组?
How to group nodes between nodes?
我知道之前已经回答过这个问题,但我很好奇如何在没有密钥的情况下实现这一点。
我有这个 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%} 之后的相同,我用这个 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
和前面的endtab
。如果它们不相等,那么你要么是endtab
,要么是yes
。排除您是 endtab
.
的情况
请允许我省略 normalize-space()
以使示例更清楚。
<xsl:template match="hello[
(. != '{%endtab%}')
and
(
count(preceding-sibling::hello[. = '{%tab%}'])
!= count(preceding-sibling::hello[. = '{%endtab%}'])
)
]">
编辑: 一开始我没有注意到 xslt-1.0 标签;这个答案可能对原发布者没有用
使用 XSLT 2.0 和 相同的巧妙技巧,您可以将 "tab" 中的元素组合在一起,这将允许同时做一些事情整个组和每个 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>
这个 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.
I know that this has been answered before, but I am curious about how
to achieve this without keys.
A link 使用 key 的解决方案将按顺序排列:
2.
由于之前已经建议了 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>
我知道之前已经回答过这个问题,但我很好奇如何在没有密钥的情况下实现这一点。
我有这个 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%} 之后的相同,我用这个 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
和前面的endtab
。如果它们不相等,那么你要么是endtab
,要么是yes
。排除您是 endtab
.
请允许我省略 normalize-space()
以使示例更清楚。
<xsl:template match="hello[
(. != '{%endtab%}')
and
(
count(preceding-sibling::hello[. = '{%tab%}'])
!= count(preceding-sibling::hello[. = '{%endtab%}'])
)
]">
编辑: 一开始我没有注意到 xslt-1.0 标签;这个答案可能对原发布者没有用
使用 XSLT 2.0 和 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>
这个 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.
I know that this has been answered before, but I am curious about how to achieve this without keys.
A link 使用 key 的解决方案将按顺序排列:
2.
由于之前已经建议了 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>