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

主宰稳场 提交于 2019-12-19 10:43:13

问题


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

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

that must be enclosed by fold tags, resulting (after XSLT) in:

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

So, I have a "label for grouping" (@gr) but not imagine how to produce correct fold tags.


I am trying to use the clues of this question, or this other one... But I have a "label for grouping", so I understand that my solution not needs the use of key() function.

My non-general solution is:

   <xsl:template match="/">
       <root>
       <xsl:copy-of select="root/c"/>
       <fold><xsl:for-each select="//*[@gr='g1']">
             <xsl:copy-of select="."/>
       </xsl:for-each></fold>

       <fold><xsl:for-each select="//*[@gr='g2']">
             <xsl:copy-of select="."/>
       </xsl:for-each></fold>

       </root>
   </xsl:template>

I need a general solution (!), looping by all @gr and coping (identity) all context that not have @gr... perhaps using identity transform.

Another (future) problem is to do this recursively, with fold of foldings.


回答1:


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>


来源:https://stackoverflow.com/questions/18311162/how-to-fold-by-a-tag-a-group-of-selected-neighbor-tags-with-xslt1

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!