How to fold by a tag a group of selected (neighbor) tags with XSLT1?

后端 未结 1 679
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-01-15 10:33

I have a set of sequential nodes that must be enclosed into a new element. Example:

  
    cccc
    aaaa<         


        
相关标签:
1条回答
  • 2021-01-15 11:35

    In XSLT 1.0 the standard technique to handle this sort of thing is called Muenchian grouping, and involves the use of a key that defines how the nodes should be grouped and a trick using generate-id to extract just the first node in each group as a proxy for the group as a whole.

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
      <xsl:strip-space elements="*" />
      <xsl:output indent="yes" />
      <xsl:key name="elementsByGr" match="*[@gr]" use="@gr" />
    
      <xsl:template match="@*|node()" name="identity">
        <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
      </xsl:template>
    
      <!-- match the first element with each @gr value -->
      <xsl:template match="*[@gr][generate-id() =
             generate-id(key('elementsByGr', @gr)[1])]" priority="2">
        <fold>
          <xsl:for-each select="key('elementsByGr', @gr)">
            <xsl:call-template name="identity" />
          </xsl:for-each>
        </fold>
      </xsl:template>
    
      <!-- ignore subsequent ones in template matching, they're handled within
           the first element template -->
      <xsl:template match="*[@gr]" priority="1" />
    </xsl:stylesheet>
    

    This achieves the grouping you're after, but just like your non-general solution it doesn't preserve the indentation and the whitespace text nodes between the a and b elements, i.e. it will give you

    <root>
      <c>cccc</c>
      <fold>
        <a gr="g1">aaaa</a>
        <b gr="g1">1111</b>
      </fold>
      <fold>
        <a gr="g2">bbbb</a>
        <b gr="g2">2222</b>
      </fold>
    </root>
    

    Note that if you were able to use XSLT 2.0 then the whole thing becomes one for-each-group:

    <xsl:template match="root">
      <xsl:for-each-group select="*" group-adjacent="@gr">
        <xsl:choose>
          <!-- wrap each group in a fold -->
          <xsl:when test="@gr">
            <fold><xsl:copy-of select="current-group()" /></fold>
          </xsl:when>
          <!-- or just copy as-is for elements that don't have a @gr -->
          <xsl:otherwise>
            <xsl:copy-of select="current-group()" />
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each-group>
    </xsl:template>
    
    0 讨论(0)
提交回复
热议问题