3

Using XSLT 1.0 I need to transform this:

<form>
<question NumOfColumns="3">
 <title>Colors</title> 
 <answer>red</answer> 
 <answer>orange</answer> 
 <answer>yellow</answer> 
 <answer>green</answer> 
 <answer>blue</answer> 
 <answer>indigo</answer> 
 <answer>violet</answer> 
</question>

</form>

into this:

<h2 class="question">Colors</h2>
<div class="answersrow">
<input type="checkbox" name="colors" value="red" id="red" /> <label for="red">red</label>
<input type="checkbox" name="colors" value="orange" id="orange" /> <label for="orange">orange</label>
<input type="checkbox" name="colors" value="yellow" id="yellow" /> <label for="yellow">yellow</label>
</div>
<div class="answersrow">
<input type="checkbox" name="colors" value="green" id="green" /> <label for="green">green</label>
<input type="checkbox" name="colors" value="blue" id="blue" /> <label for="blue">blue</label>
<input type="checkbox" name="colors" value="indigo" id="indigo" /> <label for="indigo">indigo</label>
</div>
<div class="answersrow">
<input type="checkbox" name="colors" value="green" id="green" /> <label for="green">green</label>
</div>

NumOfColumns in the question node tells how many columns to use when outputting answer divs. For each node, I can get its row using:

ceiling(position() div parent::*/@NumOfColumns)

This is working fine; i can output the correct integer. But I can't get the keys/grouping working, and I'm not sure where the problem is.

I thought the key would be:

<xsl:key name="answersrow" match="form/question/answer[ceiling( position() div parent::*/@NumOfColumns) = parent::*/@NumOfColumns]" use="." />

and then I could retrieve nodes with:

<xsl:for-each select="key('answersrow', answer)">

No luck. Anyone have a solution? Or is this not doable in XSLT 1.0?

devjeff
  • 153
  • 1
  • 4

2 Answers2

0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
   <xsl:output indent="yes"/>

   <xsl:template match="question">
       <h2 class="{local-name()}">
          <xsl:apply-templates select="title"/> 
       </h2>
       <!--Select the answers that will begin the groups. 
           Apply templates in mode group and pass in the number of columns -->
       <xsl:variable name="cols" select="@NumOfColumns"/>
       <xsl:apply-templates select="answer[position() mod $cols = 1]"
           mode="group" >
           <xsl:with-param name="cols" select="$cols"/>
       </xsl:apply-templates>
   </xsl:template>

    <!--Group the answers as children of the div -->
    <xsl:template match="answer" mode="group">
        <xsl:param name="cols"/>
        <div class="answersrow">
            <xsl:apply-templates 
                  select=".|following-sibling::answer[position() &lt; $cols]"/>
        </div>
    </xsl:template>

   <!--normal rendering for each answer -->
   <xsl:template match="answer">
       <input type="checkbox" name="colors" value="{.}" id="{.}" /> 
       <label for="{.}">
         <xsl:value-of select="."/>
       </label>
   </xsl:template>

</xsl:stylesheet>
Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
  • Many thanks for the answers. I should have added in my original that this piece was already inside a template so I had to make this part inside a named template. @Dimitre, thanks also for including the XSLT 2.0 approach. – devjeff Mar 13 '12 at 13:37
0

I. XSLT 1.0: A simpler than other answers (no params) and slightly shorter solution:

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

 <xsl:variable name="vNumCols" select="/*/*/@NumOfColumns"/>

 <xsl:template match="/*">
   <h2 class="question"><xsl:value-of select="*/title"/></h2>
   <xsl:apply-templates select=
        "*/answer[position() mod $vNumCols = 1]"/>
 </xsl:template>

 <xsl:template match="answer">
  <div class="answersrow">
   <xsl:apply-templates mode="inGroup" select=
    ". | following-sibling::*[not(position() >= $vNumCols)]"/>
  </div>
 </xsl:template>

 <xsl:template match="answer" mode="inGroup">
    <input type="checkbox" name="colors"
           value="{.}" id="{.}" />
    <label for="{.}"><xsl:value-of select="."/></label>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<form>
    <question NumOfColumns="3">
        <title>Colors</title>
        <answer>red</answer>
        <answer>orange</answer>
        <answer>yellow</answer>
        <answer>green</answer>
        <answer>blue</answer>
        <answer>indigo</answer>
        <answer>violet</answer>
    </question>
</form>

the wanted, correct result is produced:

<h2 class="question">Colors</h2>
<div class="answersrow">
   <input type="checkbox" name="colors" value="red" id="red"/>
   <label for="red">red</label>
   <input type="checkbox" name="colors" value="orange" id="orange"/>
   <label for="orange">orange</label>
   <input type="checkbox" name="colors" value="yellow" id="yellow"/>
   <label for="yellow">yellow</label>
</div>
<div class="answersrow">
   <input type="checkbox" name="colors" value="green" id="green"/>
   <label for="green">green</label>
   <input type="checkbox" name="colors" value="blue" id="blue"/>
   <label for="blue">blue</label>
   <input type="checkbox" name="colors" value="indigo" id="indigo"/>
   <label for="indigo">indigo</label>
</div>
<div class="answersrow">
   <input type="checkbox" name="colors" value="violet" id="violet"/>
   <label for="violet">violet</label>
</div>

II. XSLT 2.0 Solution:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:variable name="vNumCols" select="/*/*/@NumOfColumns" as="xs:integer"/>

 <xsl:template match="/*">
   <h2 class="question"><xsl:value-of select="*/title"/></h2>

   <xsl:for-each-group select="*/answer"
        group-adjacent="(position() -1) idiv $vNumCols">
     <div class="answersrow">
      <xsl:apply-templates select="current-group()"/>
     </div>
   </xsl:for-each-group>
 </xsl:template>

 <xsl:template match="answer">
    <input type="checkbox" name="colors"
           value="{.}" id="{.}" />
    <label for="{.}"><xsl:value-of select="."/></label>
 </xsl:template>
</xsl:stylesheet>
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431