【问题标题】:How to group nodes between nodes?如何在节点之间对节点进行分组?
【发布时间】:2015-05-17 08:13:05
【问题描述】:

我知道这个问题之前已经回答过了,但是我很好奇如何在没有密钥的情况下实现这一点。

我有这个 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%}')]]

这样做的问题是“否”节点也被选中,因为它们也在这些节点之间。所以,我必须确保跟在某个节点(第一次出现)之后的 {%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]
]

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

【问题讨论】:

    标签: xml xslt xpath xslt-1.0


    【解决方案1】:

    忽略后面的兄弟,计算前面的tabs 和前面的endtabs。如果它们不相等,那么您要么是endtab,要么是yes。排除您是endtab 的情况。

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        两个普遍感兴趣的笔记:

        1.

        我知道这个问题之前已经回答过了,但我很好奇如何 无需钥匙即可实现这一目标。

        使用 key 的解决方案链接将按顺序排列:
        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>
        

        【讨论】:

          【解决方案4】:

          编辑:起初我没有注意到 标签;这个答案可能对原始海报没用

          使用 XSLT 2.0@bjimba's answer 的巧妙技巧,您可以将“选项卡”内的元素组合在一起,这将允许对整个组和每个单个 @ 执行某些操作987654323@元素:

          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>
          

          【讨论】:

          • 这个问题被标记为xslt-1.0
          • @michael.hor257k:你说得对,对不起,直到你指出我才注意到那个标签;添加了注释,您认为完全删除答案会更好吗?
          • 我真的不在乎。我只是觉得说清楚很重要。
          猜你喜欢
          • 2023-01-24
          • 2015-05-15
          • 2017-03-29
          • 2021-03-14
          • 1970-01-01
          • 1970-01-01
          • 2012-04-22
          • 2018-04-20
          • 1970-01-01
          相关资源
          最近更新 更多