XSL : Fake a loop

2013-05-15
Laurent Bientz

If you are a beginner in XSL, beyond the basic features (select, for-each, choose, if variable), you have been surprised not to find a simple for loop, while the for-each was present, but the for-each does not always do everything. So if you want to make a small loop for several solutions available to you. To illustrate, we will take a simple example: a mark/5 on article (float) on the fact that you have this mark this in your XML feed, and you want to display the number of stars corresponding to this mark.

1st solution you are using PHP5 and using the registerPHPFunctions() of class XSLTProcessor(), you can simply use one of your PHP functions to carry out the treatment.

<?php
$xml = new DOMDocument()
$xml->load('flux.xml');
$xsl = new DOMDocument()
$xsl->load('flux.xsl');
$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStyleSheet($xsl); 
echo $proc->transformToXML($xml);

function displayStars($note)
{
  $html = "";
  for($i=0 ; $i<floor($note) ; $i++)
    $html .= '<img src="Public/Styles/Img/star.gif" title="'.floor($note).'/5" />';
  echo $html;
}
?>

Thus, in XSL, simply call your PHP function with php xmlns without forgetting the attribute disable-output-escaping to interpret HTML returned your function.

<!--
- Test with PHP
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
  <xsl:template match="root">
    <html>
      <head></head>
      <body>
        <xsl:value-of select="php:function('displayStars',//article/note)" disable-output-escaping="yes" />
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

The 2nd solution is based solely on XSL and involves the use of recursive templates to simulate a for loop.

<!--
- Recursive template
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl"" version="1.0">
  <xsl:template name="displayStars">
    <xsl:param name="i" />
    <xsl:param name="length" />

    <!-- For process -->
    <img src="Public/Styles/Img/star.gif" alt="{concat($length,'/5')}" title="{concat($length,'/5')}" />
    <!-- /For process -->

    <!-- While length is not reached, we recalled the template to continue the loop -->
    <xsl:if test="$i < $length">
      <xsl:call-template name="displayStars">
        <xsl:with-param name="i" select="$i + 1" />
        <xsl:with-param name="length" select="$length" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Whether we call it like this:

<!--
- Call recursive template
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
  <xsl:template match="root">
    <html>
      <head></head>
      <body>
        <xsl:call-template name="displayStars">
          <xsl:with-param name="i" select="'1'" />
          <xsl:with-param name="length" select="round(//article/note)" />
        </xsl:call-template>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Note that even if the for loop in XSL is exploitable, it quickly becomes tedious redevelopment of templates specific to each processing loop.

A 3rd solution, for large projects, if we keep the same example of the mark, is to arrange to slightly modify the structure of its formed XML as PHP to achieve its treatment with only the for-each.

So, instead of training:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <article>
    <note><![CDATA[3.4]]></note>
  </article>
</root>

We should format:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <article>
    <note>
      <item><![CDATA[]]></item>
      <item><![CDATA[]]></item>
      <item><![CDATA[]]></item>
    </note>
  </article>
</root>

where the number of nodes corresponding at floor() php in order to "foreach" quietly on our marks and simulate a for loop:

<!--
- Fake for-each
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
  <xsl:template match="root">
    <html>
      <head></head>
      <body>
        <xsl:variable name="note" select="count(//article/note/item)" />

        <xsl:for-each select="//article/note/item">
          <img src="Public/Styles/Img/star.gif" alt="{concat($note,'/5')}" title="{concat($note,'/5')}" />
        </xsl:for-each>

      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

You can see several solutions that allow you to make your loop, you are free to choose what suits you best, with your technical envirronnement and your choice of architecture.

Comment

USER EXPERIENCE

Wandi invites you to discover a new approach to navigating a website...
Are you ready?

Launch the experience