如何展平相邻相同节点的组?

时间:2019-06-23 07:19:18

标签: xslt xslt-1.0

假设我具有以下元素序列:

<outer>
  <e>…</e>  <!-- Adjacent <e> should be grouped if they aren’t yet. -->
  <e>…</e>
  <group>
    <e>…</e>
    <e>…</e>
  </group>
  <e>…</e>
  <e>…</e>
</outer>

我想合并尚未分组的元素<e>,即输出将是

<outer>
  <group-foo>  <!-- Grouped elements. -->
    <e>…</e>
    <e>…</e>
  </group-foo>
  <group-bar>
    <e>…</e>
    <e>…</e>
  </group-bar>
  <group-foo>
    <e>…</e>
    <e>…</e>
  </group-foo>
</outer>

我只是不太清楚如何选择相邻元素的一个(一个节点集);最接近的主意是选择//e[name(parent::*) = 'outer']或类似的东西,但是它假定某个父元素并且它返回一个节点集,而我需要两个。

2 个答案:

答案 0 :(得分:1)

解决此问题的一种方法是使用所谓的同级递归

XSLT 1.0

<xsl:stylesheet version="1.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="/outer">
    <xsl:copy>
        <xsl:apply-templates select="e[not(preceding-sibling::*[1][self::e])] | group"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="e">
    <group-foo>
        <xsl:copy-of select="."/>
        <!-- immediate sibling in the same group -->
        <xsl:apply-templates select="following-sibling::*[1][self::e]" mode="collect" />
    </group-foo>
</xsl:template>

<xsl:template match="e" mode="collect">
    <xsl:copy-of select="."/>
    <!-- immediate sibling in the same group -->
    <xsl:apply-templates select="following-sibling::*[1][self::e]" mode="collect" />
</xsl:template> 

<xsl:template match="group">
    <group-bar>
        <xsl:copy-of select="*"/>
    </group-bar>
</xsl:template> 

</xsl:stylesheet>

答案 1 :(得分:0)

无递归。使用键 Muenchian grouping ):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>
 <xsl:key name="kE-ByPrecedingNonE" match="e[not(name(..) = 'group')]"
   use="generate-id(preceding-sibling::*[not(self::e)][1])"/>

  <xsl:template match="node()|@*" name="identity">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="e[not(name(..) = 'group')]"/>

  <xsl:template match=
   "e[generate-id()
    = generate-id(key('kE-ByPrecedingNonE',
                      generate-id(preceding-sibling::*[not(self::e)][1])
                      )[1]
                  )]">
      <group-generated>
        <xsl:apply-templates select=
        "key('kE-ByPrecedingNonE',
             generate-id(preceding-sibling::*[not(self::e)][1])
             )" mode="inGroup"/>
      </group-generated>
    </xsl:template>

    <xsl:template match="node()" mode="inGroup">
      <xsl:call-template name="identity"/>
    </xsl:template>
</xsl:stylesheet>

将此转换应用于提供的source-xml文档时:

<outer>
  <e>…</e>
  <e>…</e>
  <group>
    <e>…</e>
    <e>…</e>
  </group>
  <e>…</e>
  <e>…</e>
</outer>

产生所需的正确结果

<outer>
   <group-generated>
      <e>…</e>
      <e>…</e>
   </group-generated>
   <group>
      <e>…</e>
      <e>…</e>
   </group>
   <group-generated>
      <e>…</e>
      <e>…</e>
   </group-generated>
</outer>

说明

  1. 身份规则(模板)将每个节点原样复制到输出中。我们还给它起了一个名称,这样我们不仅可以在应用模板时使用它,还可以直接调用它-就像下面的步骤4一样。

  2. <e>内尚未存在的任何<group>元素匹配的模板。它没有内容-我们只希望在下一个模板中处理这些元素-而不是在身份模板选择要执行的元素时。

  3. 与任何<e>元素匹配的模板,该元素是“未覆盖”组中的第一个元素。这使用了Muenchian分组方法背后的想法-如果您不熟悉它,请学习它-您不会后悔的。该模板为整个组生成一个包装器元素(称为“组生成的”),并且在该包装器内部以“ inGroup”模式将模板应用于该组中的所有<e>,只需将它们复制即可。

  4. 处于“ inGroup”模式下的模板仅委托给身份规则来复制所选节点。因此,任何匹配的节点都将按原样复制。在这里,如果我们决定,我们可以执行任何其他必需的“ inGroup”处理。