tags:

views:

23

answers:

2

I can't figure out how create xsl to group some nodes between other nodes. Basically, everytime I see a 'SPLIT' I have to end the div and create a new one.

The xml looks like this:

<data name="a" />
<data name="b" />
<data name="c" />
<data name="SPLIT" />
<data name="d" />
<data name="e" />
<data name="SPLIT" />
<data name="f" />
<data name="g" />
<data name="h" />

The output needs to look like this

<div>
a
b
c
</div>

<div>
d
e
</div>

<div>
f
g
h
</div>

I know how to do this by 'cheating', but would like to know if there is a proper way to do it:

<div>
<xsl:for-each select="data">
    <xsl:choose>
    <xsl:when test="@name='SPLIT'">
        <xsl:text disable-output-escaping="yes"> &lt;/div&gt; &lt;div&gt;</xsl:text>
    </xsl:when>
    <xsl:otherwise>
        <xsl:value-of select="@name"/>
    </xsl:otherwise>
</xsl:for-each>
</div>
+2  A: 

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:key name="kFollowing" match="data[not(@name='SPLIT')]"
 use="generate-id(preceding-sibling::data[@name='SPLIT'][1])"/>

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

 <xsl:template match="data[@name='SPLIT']" name="regularSplit">
  <div>
   <xsl:apply-templates mode="copy"
    select="key('kFollowing', generate-id())"/>
  </div>
 </xsl:template>

  <xsl:template match="data[@name='SPLIT'][
              not(preceding-sibling::data[@name='SPLIT'])]">
  <div>
   <xsl:apply-templates mode="copy"
    select="preceding-sibling::data"/>
  </div>
   <xsl:call-template name="regularSplit"/>
 </xsl:template>

 <xsl:template match="*" mode="copy">
  <xsl:call-template name="identity"/>
 </xsl:template>
<xsl:template match="data[not(@name='SPLIT')]"/>
</xsl:stylesheet>

when applied on the provided XML document (wrapped into a top element to become well-formed):

<t>
    <data name="a" />
    <data name="b" />
    <data name="c" />
    <data name="SPLIT" />
    <data name="d" />
    <data name="e" />
    <data name="SPLIT" />
    <data name="f" />
    <data name="g" />
    <data name="h" />
</t>

produces the wanted, correct result:

<t>
    <div>
        <data name="a"></data>
        <data name="b"></data>
        <data name="c"></data>
    </div>
    <div>
        <data name="d"></data>
        <data name="e"></data>
    </div>
    <div>
        <data name="f"></data>
        <data name="g"></data>
        <data name="h"></data>
    </div>
</t>

Do note: Keys are used to specify conveniently and verry efficiently all "non-SPLIT" elements that follow immediately a "SPLIT" element.

Dimitre Novatchev
@Dimitre Novatchev - The wanted result was to have the `<div>` elements as siblings.
Mads Hansen
@Mads: Thanks for noticing this. Fixed now!
Dimitre Novatchev
@Dimitre: good answer, +1. Might also want to explain (or give a link to explanation) of how XSLT constructs elements, not individual tags, etc. etc.
LarsH
+2  A: 

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:strip-space elements="*"/>
    <xsl:template match="node()">
        <xsl:apply-templates 
                   select="node()[1]|following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="data">
        <div>
            <xsl:call-template name="open"/>
        </div>
        <xsl:apply-templates 
                   select="following-sibling::data[@name='SPLIT'][1]
                                    /following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="data" mode="open" name="open">
        <xsl:value-of select="concat(@name,'&#xA;')"/>
        <xsl:apply-templates select="following-sibling::node()[1]" 
                             mode="open"/>
    </xsl:template>
    <xsl:template match="data[@name='SPLIT']" mode="open"/>
</xsl:stylesheet>

Output:

<div>
a
b
c
</div>
<div>
d
e
</div>
<div>
f
g
h
</div>

Note: Fine grained traversal.

Alejandro
+1 Very nice. Clean and concise.
Mads Hansen