如何在节点之间分组节点?

时间:2015-03-15 00:57:48

标签: xml xslt xpath xslt-1.0

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

我有这个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%}之后的{%endtab%}相同,我使用此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]
]

但是,这并没有按预期过滤“否”节点。

4 个答案:

答案 0 :(得分:3)

忽略以下兄弟并计算前面的tab和前面的endtab。如果它们不相等,则您可以是endtabyes。排除您endtab的情况。

请允许我免除normalize-space()以使示例更清晰。

<xsl:template match="hello[
    (. != '{%endtab%}')
    and
    (
        count(preceding-sibling::hello[. = '{%tab%}'])
        != count(preceding-sibling::hello[. = '{%endtab%}'])
    )
    ]">

答案 1 :(得分:2)

此XPath表达式

 /*/*[not(. = '{%tab%}' or . = '{%endtab%}') 
    and preceding-sibling::*[. = '{%tab%}' or . = '{%endtab%}'][1] = '{%tab%}'
    and following-sibling::*[. = '{%tab%}' or . = '{%endtab%}'][1] = '{%endtab%}'
     ]

选择任何元素作为top元素的子元素,并且位于具有字符串值{%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>

答案 2 :(得分:2)

一般感兴趣的两个注释:

1

  

我知道之前已经回答了这个问题,但我很好奇   没有钥匙就能做到这一点。

使用的解决方案链接将按顺序排列:
https://stackoverflow.com/a/28179346/3016153

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>

答案 3 :(得分:0)

修改:一开始我没有注意到标记;这个答案对原始海报来说可能毫无用处

使用 XSLT 2.0 @bjimba's answer相同的聪明技巧,您可以将&#34;标签&#34;中的元素组合在一起,这样可以同时使用整个组和每个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>