5

How to use xsl:for-each in xslt to get value iteratively from an xml file and to dispaly it in table fromat

For example: the xml file is like

<order>
  <item name ="a"/>
  <item name ="b"/>  
  <item name ="c"/>
  <item name ="d"/>
  <item name ="e"/>
  <item name ="f"/>
  <item name ="g"/>
</order>

and the output should be

  a    b    c   d

  e    f    g

the loop should count the item and if it is divisble by 4 it

should close the current row and add a new row and so on..

i'm using the following xslt for this

but i can not display it in table format

   <xsl:template match="/">
    <html>
    <body>
     <xsl:call-template name ="incr">
        <xsl:with-param name ="value">1</xsl:with-param>
        <xsl:with-param name ="limit">
          <xsl:value-of select ="count(//item)"/>
        </xsl:with-param>
      </xsl:call-template>
  </body>
</html>
</xsl:template >
<xsl:template name="incr">
  <xsl:param name="value"/>
  <xsl:param name ="limit"/>
  <xsl:if test ="$value!=$limit+1">
    <xsl:value-of select ="//item[$value]/@name"/>
    <xsl:if test ="$value mod 4 =0">
      <br/>
      <br/>
    </xsl:if>
    <xsl:call-template name ="incr">
      <xsl:with-param name ="value" select ="$value+1"/>
      <xsl:with-param name ="limit" select ="$limit"/>
    </xsl:call-template>
  </xsl:if>

</xsl:template>

please help me to do this

Thanks in advance

2
  • 1
    What have you done to solve this problem so far? This looks like homework. Commented May 10, 2010 at 11:21
  • +1 for the question. See my answer for a complete and correct solution. :) Commented May 10, 2010 at 12:41

4 Answers 4

8

This transformation:

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

 <xsl:variable name="vNumCols" select="4"/>

 <xsl:template match="/*">
  <table>
   <xsl:for-each select=
     "item[position() mod $vNumCols = 1]">

     <tr>
       <xsl:for-each select=
       ". | following-sibling::*
                 [not(position() >= $vNumCols)]">
        <td><xsl:value-of select="@name"/></td>
       </xsl:for-each>
     </tr>
   </xsl:for-each>
  </table>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document, produces the desired correct results:

<table>
   <tr>
      <td>a</td>
      <td>b</td>
      <td>c</td>
      <td>d</td>
   </tr>
   <tr>
      <td>e</td>
      <td>f</td>
      <td>g</td>
   </tr>
</table>
Sign up to request clarification or add additional context in comments.

2 Comments

Nice solution, but I found a problem, if "vNumCols" is set to "1", it will display nothing.
@XiaodanMao, Yes, and the same problem exists for $vNumCols set to 0. This is because such values are out of scope by definition for this problem. You may find that any solution how to pore water into N cups doesn't work if the temperature is -1 degree Celsius.
8

The way to think about problems of any complexity in XSLT is not "how would I write a program to produce Y, given X as input?" but rather, "given output Y, what X am I going to transform to produce it?" It's not an easy principle to grasp (or articulate), but once you get it, what seem like hard problems in XSLT become trivial.

If the output is a series of tr elements, like this:

<tr>
   <td>a</td><td>b</td><td>c</td>
</tr>
<tr>
   <td>d</td><td>e</td><td>f</td>
</tr>
<tr>
   <td>g</td><td>h</td><td>i</td>
</tr>
<tr>
   <td>j</td><td/><td/>
</tr>

there are, in essence, four output elements. So there must be four input elements.

The first question is, which four? Pretty clearly, it's going to be the 1st, 4th, 7th, and 10th - that is, every 3 elements, starting with the first. So your starting point is to transform those four elements:

<xsl:apply-templates select="/order/item[position() mod 3 = 1]"/>

Okay, and now that we've selected every third element, how are we going to create a tr out of it and the elements right after it? Using the following-sibling axis:

<xsl:template match="item">
   <tr>
      <td><xsl:value-of select="@name"/></td>
      <td><xsl:value-of select="following-sibling::item[1]/@name"/></td>
      <td><xsl:value-of select="following-sibling::item[2]/@name"/></td>
   </tr>
</xsl:template>

That's good, as far as it goes. But there's a fair amount of duplicated code, and a lot you have to modify if you want (say) to change the number of columns from 3 to 6. You can eliminate the duplicated code by making another template:

<xsl:template match="item">
   <tr>
      <xsl:apply-templates select="@name | following-sibling::item[position() &lt;= 3]/@name"/>
   </tr>
</xsl:template>

<xsl:template match="@name">
   <td><xsl:value-of select="."/></td>
</xsl:template>

And you can parameterize the number of columns by putting it in a variable, as Dimitre has done in his example.

Comments

-1

I am not 100% sure but the code below should do it:

<table>
    <tr>
    <xsl:for-each select="//order/item">
        <td>
        <xsl:value-of select ="current()/@name"/>
        </td>
    <xsl:if test="position() mod 4 = 0">
    <xsl:text disable-output-escaping="yes"><![CDATA[</tr><tr>]]></xsl:text>
    </xsl:if>
    </xsl:for-each>
    <xsl:variable name="item_count_mod4" select="count(//order/item) mod 4"/>
    <xsl:choose>
      <xsl:when test="$item_count_mod4 = 1">
        <td></td><td></td><td></td>
      </xsl:when>
      <xsl:when test="$item_count_mod4 = 2">
        <td></td><td></td>
      </xsl:when>
      <xsl:when test="$item_count_mod4 = 3">
        <td></td>
      </xsl:when>
      <xsl:otherwise>
      </xsl:otherwise>
    </xsl:choose>
    </tr>
</table>

2 Comments

we can not do like this because the <tr> can not close properly and its showing error
You're absolutely right about the "<tr>" problem. CDATA trick easily solves it. You can check my edited code above. I tested with XML Notepad and it transforms successfully.
-4
<table> 
 <tr> 
  <xsl:for-each select="//order/item"> 
   <td> 
    <xsl:value-of select ="current()/@name"/> 
   </td> 
   <xsl:if test="position() mod 4 = 0"> 
    <xsl:text disable-output-escaping="yes"><![CDATA[</tr><tr>]]></xsl:text> 
   </xsl:if> 
  </xsl:for-each> 
 </tr> 
</table> 

1 Comment

No. That way madness lies. A opening and closing tag declares a single thing. The above solution is a bodge.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.