如何在节点之间对节点进行分组



我知道这之前已经回答过了,但我很好奇如何在没有键的情况下实现这一目标。

有这个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>

相关内容

  • 没有找到相关文章