views:

61

answers:

3

I am trying to transform a bit of xml which represents an image gallery into an html table. (it must be done with html and not with css). How do I add the row break </tr><tr> every six or so columns with xsl?

I have this:

    <xsl:for-each select="//email/gallery" >
    <td><img>
        <xsl:attribute name="src">
          <xsl:value-of select="gallery-image-location"/>
        </xsl:attribute>
        <xsl:attribute name="alt">
          <xsl:value-of select="gallery-image-alt"/>
        </xsl:attribute>
    </img></td>
    <xsl:if test="????">
      </tr><tr> 
    </xsl:if>
    <xsl:for-each>

In Javascript I would do something like:

for (i=0; i<gallery.length; i++) {
    htm += '<td><img src="' +
    gallery[i].gallery-image-location +
    '" alt="'+ gallery[i].gallery-image-alt +'"></td>';

    if (i%6 == 5 && i != gallery.length-1) {
        htm += '</tr><tr>';
    }
}
+5  A: 

How do I add the row break every six or so columns with xsl?

In XSLT you don't!

XSLT processes nodes, not tags.

Here is the XSLT way of positional grouping:

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

 <xsl:template match="gallery[position() mod 6 = 1]">
  <tr>
   <xsl:apply-templates mode="proc"
        select=".|following-sibling::gallery[not(position() > 5)]"
   />
  </tr>
 </xsl:template>

 <xsl:template match="gallery" mode="proc">
  <td>
    <img src="{gallery-image-location}" alt="{gallery-image-alt}"/>
  </td>
 </xsl:template>

 <xsl:template match="gallery[not(position() mod 6 = 1)]"/>
</xsl:stylesheet>

when this transformation is applied on the following XML document:

<email>
    <gallery>
        <gallery-image-location>http://server/picts/1&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 1</gallery-image-alt>
    </gallery>
    <gallery>
        <gallery-image-location>http://server/picts/2&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 2</gallery-image-alt>
    </gallery>
    <gallery>
        <gallery-image-location>http://server/picts/3&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 3</gallery-image-alt>
    </gallery>
    <gallery>
        <gallery-image-location>http://server/picts/41&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 4</gallery-image-alt>
    </gallery>
    <gallery>
        <gallery-image-location>http://server/picts/5&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 5</gallery-image-alt>
    </gallery>
    <gallery>
        <gallery-image-location>http://server/picts/6&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 6</gallery-image-alt>
    </gallery>
    <gallery>
        <gallery-image-location>http://server/picts/7&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 7</gallery-image-alt>
    </gallery>
    <gallery>
        <gallery-image-location>http://server/picts/8&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 8</gallery-image-alt>
    </gallery>
    <gallery>
        <gallery-image-location>http://server/picts/9&lt;/gallery-image-location&gt;
        <gallery-image-alt>Description 9</gallery-image-alt>
    </gallery>
</email>

the wanted, correct result is produced:

<tr>
    <td>
        <img src="http://server/picts/1" alt="Description 1"/>
    </td>
    <td>
        <img src="http://server/picts/2" alt="Description 2"/>
    </td>
    <td>
        <img src="http://server/picts/3" alt="Description 3"/>
    </td>
    <td>
        <img src="http://server/picts/41" alt="Description 4"/>
    </td>
    <td>
        <img src="http://server/picts/5" alt="Description 5"/>
    </td>
    <td>
        <img src="http://server/picts/6" alt="Description 6"/>
    </td>
</tr>
<tr>
    <td>
        <img src="http://server/picts/7" alt="Description 7"/>
    </td>
    <td>
        <img src="http://server/picts/8" alt="Description 8"/>
    </td>
    <td>
        <img src="http://server/picts/9" alt="Description 9"/>
    </td>
</tr>
Dimitre Novatchev
@Dimitre: +1 For faster and correct answer. Ja!
Alejandro
+1 for `XSLT processes nodes, not tags`
Bert F
+1  A: 

First, assuming that you are using an output format of XML or HTML, I don't think you can place unmatched tags as your are with the </tr><tr> segment. XSL (in these modes) doesn't just produce string output the way you would with your Javascript. (I could be wrong about this though.)

What you're doing there is closely related to paging; you might look at paging scripts.

Here's an (untested) suggestion from me:

<!-- For every sixth item, starting with the first... -->
<xsl:for-each select="//email/gallery[position() mod 6 = 1]">
  <tr>
     <!-- Get that item's position... -->
     <xsl:variable name="thisPos" select="position()" />

     <!-- and select the six (or less) items starting with that position. -->
     <xsl:for-each select="//email/gallery[position() &gt;= $thisPos and position() &lt; $thisPos + 6]">
       <td><img>
        <xsl:attribute name="src">
          <xsl:value-of select="gallery-image-location"/>
        </xsl:attribute>
        <xsl:attribute name="alt">
          <xsl:value-of select="gallery-image-alt"/>
        </xsl:attribute>
       </img></td>
     </xsl:for-each>
  </tr>
</xsl:for-each>

Oh, and IIRC, the interior of the loop can be shortened a little bit too:

<td><img src="{gallery-image-location}" alt="{gallery-image-alt}" /></td>

Those curly braces will help save your sanity on long scripts.

Jesse Millikan
@Jesse Millikan: Why you don't iterate over self and 5 next following siblings?
Alejandro
Eh, didn't think of it. I'm still a bit of a C programmer at heart, I guess.
Jesse Millikan
This example does not really work and is definitely not the right answer but was close to what we ended up using (C programmer at heart here too ;). The [position() >= $thisPos and position() < $thisPos + 6] does not give the desired result. I ended up using [position() > $thisPos*6-6 and position() < $thisPos*6+1]
Nick
Oh, duh... I was trying to use the... The, uh... Like I said, C programmer.
Jesse Millikan
+3  A: 

If you are using XSLT 2

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

 <xsl:template match="email">
   <xsl:for-each-group select="gallery" group-by="(position() - 1) idiv 6">
     <tr>
       <xsl:apply-templates select="current-group()"/>
     </tr>
   </xsl:for-each-group>
 </xsl:template>

 <xsl:template match="gallery">
  <td>
    <img src="{gallery-image-location}" alt="{gallery-image-alt}"/>
  </td>
 </xsl:template>

</xsl:stylesheet>
Nick Jones
A minor issue: `group-by="position() idiv 6"` must actually be: `group-by="(position() -1) idiv 6`
Dimitre Novatchev
@Nick-Jones: Don't you see that unless you apply the recommended fix your proposed solution is incorrect?
Dimitre Novatchev
@Dimitre Novatchev: sorry, have been away, fixed now.
Nick Jones
+1 for an XSLT 2.0 group-by solution.
LarsH