Make your own free website on Tripod.com

Recursion

Recursion is a powerful technique in XSLT. It is a primary means of processing in XSLT. In this page, I am compiling some XSLT problems solved using recursion.

1. Determining number of times a sub-string occurs in a string

The following question was asked on microsoft.public.xsl Newsgroup.

I need to determine the number of times a sub-string occurs in a string.

Dimitre Novatchev provided the following answer:

Using XPath 2.0 this is expressed by the following XPath expression:
     "count(for $i in (1 to (string-length($vStr) - $vlenPat + 1) )
                  return
                    if(substring($vStr, $i, $vlenPat) eq $vPat)
                     then 1
                     else ()
                )
where $vStr is the string to be searched, $vPat is the pattern we are searching for (the substring) and $vlenPat is its length.
For example, if:
       $vStr is 'codecodecode'
and
       $vPat is 'codeco'
then the value of the above expression is:
   2

I provided the following XSLT 1.0 solution:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  version="1.0">
 
 <xsl:output method="text" />

 <xsl:variable name="string" select="'codecodecode'" />
 <xsl:variable name="substring" select="'codeco'" />
 
 <xsl:template match="/">
   <xsl:call-template name="countInstances">
     <xsl:with-param name="string" select="$string" />
     <xsl:with-param name="substring" select="$substring" />
     <xsl:with-param name="i" select="0" /> 
   </xsl:call-template>
 </xsl:template>
 
 <xsl:template name="countInstances">
   <xsl:param name="string" />
   <xsl:param name="substring" />
   <xsl:param name="i" />  
     
   <xsl:choose>
     <xsl:when test="starts-with($string,$substring)">
       <xsl:call-template name="countInstances">
         <xsl:with-param name="string" select="substring($string, 2)" />
         <xsl:with-param name="substring" select="$substring" />
         <xsl:with-param name="i" select="$i + 1" />
       </xsl:call-template>
     </xsl:when>
     <xsl:when test="string-length($string) >= string-length($substring)">
       <xsl:call-template name="countInstances">
         <xsl:with-param name="string" select="substring($string, 2)" />
         <xsl:with-param name="substring" select="$substring" />
         <xsl:with-param name="i" select="$i" />
       </xsl:call-template>
     </xsl:when>
     <xsl:otherwise>
       <xsl:value-of select="$i" />  
     </xsl:otherwise>
   </xsl:choose>
  
 </xsl:template>
 
</xsl:stylesheet>

Dimitre later provided an efficient implementation, written in XSLT 2.0:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:t="http://fxsl.sf.net/test" exclude-result-prefixes="t xs" >

 <xsl:output method="text"/>

 <xsl:template match="/">
   <xsl:value-of select="t:countPatterns('codecodecode', 'codeco')"/>
 </xsl:template>

 <xsl:function name="t:countPatterns" as="xs:double">
   <xsl:param name="pStr" as="xs:string"/>
   <xsl:param name="pPat" as="xs:string"/>

   <xsl:variable name="vlenStr" select="string-length($pStr)"/>
   <xsl:variable name="vlenPat" select="string-length($pPat)"/>

   <xsl:variable name="vleftStr" select="substring($pStr,1,$vlenStr -$vlenPat+1)"/>
   <xsl:variable name="vFstChar" select="substring($pPat,1,1)"/>

   <xsl:sequence select="t:countPatterns($pStr,$pPat,string-length($vleftStr) - string-length(translate($vleftStr, $vFstChar,'')))"/>

    </xsl:function>

 <xsl:function name="t:countPatterns" as="xs:double">
   <xsl:param name="pStr" as="xs:string"/>
   <xsl:param name="pPat" as="xs:string"/>
   <xsl:param name="pTimesChar1" as="xs:integer"/>

   <xsl:variable name="vlenStr" select="string-length($pStr)"/>
   <xsl:variable name="vlenPat" select="string-length($pPat)"/>

   <xsl:variable name="vleftStr" select="substring($pStr,1,$vlenStr -$vlenPat+1)"/>
   <xsl:variable name="vFstChar" select="substring($pPat,1,1)"/>

   <xsl:sequence select= "if( $pTimesChar1 > 0)
       then
          for $tail2 in substring-after($pStr, $vFstChar),
               $tail in concat($vFstChar,$tail2)
                  return number(starts-with($tail, $pPat))
                          +
                            t:countPatterns($tail2, $pPat, $pTimesChar1 -1)
       else 0
    "
    />
 </xsl:function>
</xsl:stylesheet>
 

2. Tree from directory listing

The following question was asked on XSL-List.

I need to create a nested xml Tree structure from file listing with paths?

eg.
  ...
<file name ="f1.xyz" path="/test/"/>
<file name ="f2.xyz" path="/test/folderInFolder/"/>
<file name ="f3.xyz" path="/test/folderInFolder/"/>
<file name ="f4.xyz" path="/test/folderInFolder2/"/>
<file name ="f5.xyz" path="/test2/folderInFolder3/"/>

 to

<folder name="test">
    <folder name="folderInFolder">
        <file name="f2.xyz"/>
        <file name="f3.xyz"/>
    </folder>
    <folder name="folderInFolder2">
        <file name="f4.xyz"/>
    </folder>
</folder>
<folder name="test2">
    <folder name="folderInFolder3">
        <file name="f5.xyz">
    </folder>
</folder>

Michael Kay provided the following solution written in XSLT 2.0:

It's essentially a recursive grouping problem. In XSLT 2.0 you can solve it like this:

<xsl:template name="g">
  <xsl:param name="files" as="element(file)*"/>
  <xsl:param name="level" as="xs:integer"/>
  <xsl:for-each-group select="$files" group-adjacent="tokenize(@path, '/')[$level]">
   <folder name="current-grouping-key()">
     <xsl:call-template name="g">
       <xsl:with-param name="files" select="current-group() except ."/>
       <xsl:with-param name="level" select="$level + 1"/>
     </xsl:call-template>
   </folder>
  </xsl:for-each-group>
</xsl:template>

Michael mentioned: I haven't tried to distinguish folders from files here.

I provided the following XSLT 1.0 solution:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:exslt="http://exslt.org/common" exclude-result-prefixes="exslt">
 
<xsl:output method="xml" indent="yes" />

<xsl:key name="x" match="file" use="@parentfolder" />
<xsl:key name="y" match="file" use="concat(@folder,'', @parentfolder)" />

<xsl:template match="/">
 <xsl:variable name="RTF">
   <temp>
     <xsl:for-each select="root/file">
       <xsl:variable name="folder-name">
         <xsl:call-template name="get-folder-name">
           <xsl:with-param name="path" select="@path" />
         </xsl:call-template>
       </xsl:variable>
       <xsl:variable name="parent-folder-name">
         <xsl:call-template name="get-parent-folder-name">
           <xsl:with-param name="path" select="@path"/>  
         </xsl:call-template>
       </xsl:variable>
       <file name="{@name}" folder="{$folder-name}" parentfolder="{$parent-folder-name}" />
     </xsl:for-each>
   </temp>
 </xsl:variable>
 <root>
   <xsl:apply-templates select="exslt:node-set($RTF)/temp" /> 
 </root>
</xsl:template>
 
<xsl:template name="get-folder-name">
  <xsl:param name="path" />
  <xsl:choose>
    <xsl:when test="(string-length($path) - string-length(translate($path, '/', ''))) = 2">
      <xsl:value-of select="substring-before(substring-after($path, '/'), '/')" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="get-folder-name">
        <xsl:with-param name="path" select="substring-after($path, '/')" />
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
 
<xsl:template name="get-parent-folder-name">
  <xsl:param name="path" />
  <xsl:choose>
     <xsl:when test="(string-length($path) - string-length(translate($path, '/', ''))) = 2">
       <xsl:value-of select="substring-before($path, '/')" />
     </xsl:when>
     <xsl:otherwise>
       <xsl:call-template name="get-parent-folder-name">
         <xsl:with-param name="path" select="substring-after($path, '/')" />
       </xsl:call-template>
     </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="temp">
  <xsl:apply-templates select="file[generate-id(.) = generate-id(key('x', @parentfolder)[1])]" />
</xsl:template>

<xsl:template match="file">
  <xsl:if test="@parentfolder != ''">
  <folder name="{@parentfolder}">   
    <xsl:for-each select="key('x', @parentfolder)[generate-id(.) = generate-id(key('y', concat(@folder, '', @parentfolder))[1])]">
      <folder name="{@folder}">
        <xsl:for-each select="key('y', concat(@folder, '', @parentfolder))">
          <file name="{@name}" />
        </xsl:for-each>
      </folder>
    </xsl:for-each>
  </folder>
 </xsl:if>
</xsl:template>
   
</xsl:stylesheet>

When the above XSL is applied to XML:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <file name ="f1.xyz" path="/test/"/>
  <file name ="f2.xyz" path="/test/folderInFolder/"/>
  <file name ="f3.xyz" path="/test/folderInFolder/"/>
  <file name ="f4.xyz" path="/test/folderInFolder2/"/>
  <file name ="f5.xyz" path="/test2/folderInFolder3/"/>
</root>

it produces output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <folder name="test">
   <folder name="folderInFolder">
     <file name="f2.xyz"/>
     <file name="f3.xyz"/>
   </folder>
   <folder name="folderInFolder2">
     <file name="f4.xyz"/>
   </folder>
  </folder>
  <folder name="test2">
    <folder name="folderInFolder3">
      <file name="f5.xyz"/>
    </folder>
  </folder>
</root>

Geert Josten pointed some minor bugs in my stylesheet, and provided an improved XSLT 1.0 solution:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common"
                                            exclude-result-prefixes="exslt">

 <xsl:output method="xml" indent="yes" encoding="utf-8" />

 <xsl:key name="folders" match="folder" use="@path" />


  <xsl:template match="root">
    <xsl:variable name="folders">
      <!-- create a temporary tree with a folder structure -->
      <xsl:copy>
        <xsl:copy-of select="@*" />
        <xsl:for-each select="file">
          <!-- sort the files for nicer output -->
          <xsl:sort select="@path" />
          <xsl:sort select="@name" />
          <!-- build a folder structure for each file (separately) -->
          <xsl:apply-templates select="." mode="build-folders">
            <xsl:with-param name="path" select="concat(@path, @name)" />
          </xsl:apply-templates>
        </xsl:for-each>
      </xsl:copy>
    </xsl:variable>
    <!-- we have files in separate folder elements, now merge them -->
    <xsl:apply-templates select="exslt:node-set($folders)/node()" mode="merge" />
</xsl:template>

<xsl:template match="file" mode="build-folders">
    <xsl:param name="path-base" />
    <xsl:param name="path" /> <!-- assumption: path always starts with '/' -->
    <!-- folder-count equals the number of slashes in the path, minus one for the file name -->
    <xsl:param name="folder-count" select="string-length($path) - string-length(translate($path, '/', '')) - 1"/>

    <xsl:choose>
        <!-- should not occur, actually -->
        <xsl:when test="string-length($path) = 0" />
        <!-- path contains slashes, output a folder -->
        <xsl:when test="$folder-count > 0">
            <!-- Note: folder-count = 1 means at least two slashes -->
            <xsl:variable name="folder-name" select="substring-before(substring-after($path, '/'), '/')" />
            <xsl:variable name="folder-path" select="concat($path-base, '/', $folder-name)" />
            <xsl:variable name="remainder" select="substring-after(substring-after($path, '/'), '/')" />
            <folder name="{$folder-name}" path="{$folder-path}">
                <!-- and recurse to add sub folders -->
                <xsl:apply-templates select="." mode="build-folders">
                    <xsl:with-param name="path" select="concat('/', $remainder)" />
                    <xsl:with-param name="path-base" select="$folder-path" />
                </xsl:apply-templates>
            </folder>
        </xsl:when>
        <!-- no more slashes, output the file -->
        <xsl:otherwise>
            <!-- note: could do a xsl:copy-of select="." as well -->
            <file name="{@name}" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="root" mode="merge">
    <xsl:copy>
        <xsl:copy-of select="@*" />
        <!-- insert top-level files -->
        <xsl:copy-of select="file" />
        <!-- apply those folders that are the first ones with a specific path -->
        <xsl:apply-templates select="folder[generate-id(.) = generate-id(key('folders', @path)[1])]" mode="merge"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="folder" mode="merge">
    <xsl:copy>
        <!-- note: one might want to suppress the path attribute -->
        <xsl:copy-of select="@*" />
        <xsl:copy-of select="key('folders', @path)/file" />
        <xsl:apply-templates select="key('folders', @path)/folder[generate-id(.) = generate-id(key('folders', @path)[1])]" mode="merge"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

There is a similar problem discussed here.

3. Tokenizing a delimited list

The following question was asked on microsoft.public.xsl Newsgroup.

Is there a way in XSLT to match on numbers that are delimited by a character or space in a text node?

The stylesheet for this problem is:

For e.g. if XML is -
<root>
  <numbers>1 5.2 3 64.5 100 -2 11</numbers>
</root>
(please note: different numbers in 'numbers' tag are separated by space; but  we can use any delimiter we wish, for e.g.  "," )

This XSLT stylesheet tokenizes the number list (using recursion):

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method="xml" indent="yes" />

<xsl:template match="/root">
  <tokens>
    <xsl:call-template name="tokenize">
      <xsl:with-param name="list" select="numbers" /> 
      <xsl:with-param name="delim" select="' '" /> 
    </xsl:call-template>
  </tokens> 
</xsl:template>

<xsl:template name="tokenize">
  <xsl:param name="list" />
  <xsl:param name="delim" />
 
  <xsl:choose>
    <xsl:when test="substring-after($list,$delim) != ''">
      <token>
        <xsl:value-of select="substring-before($list,$delim)" />
      </token>
      <xsl:call-template name="tokenize">
        <xsl:with-param name="list" select="substring-after($list,$delim)" /> 
        <xsl:with-param name="delim" select="$delim" /> 
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <token>
        <xsl:value-of select="$list" />
      </token>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

The output received is:

<?xml version="1.0" encoding="UTF-8"?>
<tokens>
   <token>1</token>
   <token>5.2</token>
   <token>3</token>
   <token>64.5</token>
   <token>100</token>
   <token>-2</token>
   <token>11</token>
</tokens>


4.
Formatting XML data

The following question was asked on XSL-List.

I need to format data from the XML using XSL.

Example :   Input data from XML is HELLO, I want the output as |H|E|L|L|O|

The stylesheet for this problem is:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
 
<xsl:output method="text"/>

<xsl:template match="/root">
   <xsl:call-template name="FormatString">
      <xsl:with-param name="str" select="s" /> 
   </xsl:call-template>
   <xsl:text>|</xsl:text>
</xsl:template>
 
<xsl:template name="FormatString">
   <xsl:param name="str" />
  
   <xsl:if test="string-length($str) &gt; 0">
      <xsl:text>|</xsl:text><xsl:value-of select="substring($str,1,1)" />
      <xsl:call-template name="FormatString">
        <xsl:with-param name="str" select="substring($str,2,string-length($str))" />
     </xsl:call-template>
   </xsl:if>
  
</xsl:template>
 
</xsl:stylesheet>

For example, when it is applied to XML:

<?xml version="1.0"?>
<root>
  <s>HELLO</s>
</root>

it produces output:

|H|E|L|L|O|


5.
Generate N elements

The following question was asked on XSL-List.

I need to generate N elements on the fly. N is a number in my source xml.
 
Source xml:

<Root>
   <data>4</data>
</Root>
 
Result xml:

<Root>
    <info/>
    <info/>
    <info/>
    <info/>
</Root>

The stylesheet for this problem is:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  
<xsl:output method="xml" indent="yes" />
  
<xsl:template match="/Root">
  <Root>
     <xsl:call-template name="generateTag">
       <xsl:with-param name="x" select="data" />
     </xsl:call-template>
  </Root> 
</xsl:template>
  
<xsl:template name="generateTag">
   <xsl:param name="x" />
   <info/>
   <xsl:if test="$x > 1">
     <xsl:call-template name="generateTag">
        <xsl:with-param name="x" select="$x - 1" />
     </xsl:call-template>
   </xsl:if>
</xsl:template>
 
</xsl:stylesheet>


6.
Replacing HTML tag

The following question was asked on XSL-List.

I am having some characters like {i},{/i} in the XML file. Whenever I find the word like {i} in XML I should replace the "{" with "<" and while viewing the contents in the HTML browser I should get the text to be italised once it finds the tag like that.. For example {i}Hi{/i} --> should appear like Hi in italised format in browser.

Below is an example illustrating the problem.

The XML file is:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
 <document>
   <para>{i}this{/i} is {i}an{/i} example 1</para>
   <para>this is an {i}example 2{/i}</para>
   <para>this is an example 3</para>
   <para>th{i}is{/i} is an {i}ex{/i}ample{i}4{/i}</para>
</document>

The XSLT stylesheet is:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
 <xsl:output method="html" />
 
  <xsl:template match="/document">
    <html>
      <head>
        <title></title>
        <body>
          <xsl:for-each select="para">
            <p>
              <xsl:call-template name="italics-template">
                 <xsl:with-param name="text" select="." />
                 <xsl:with-param name="startdelim" select="'{i}'" />
                 <xsl:with-param name="enddelim" select="'{/i}'" />               
              </xsl:call-template>
            </p> 
          </xsl:for-each>
        </body>
      </head>
    </html>
  </xsl:template>
 
  <!-- template to create italics tags -->
  <xsl:template name="italics-template">
    <xsl:param name="text" />
    <xsl:param name="startdelim" />
    <xsl:param name="enddelim" />
   
    <xsl:choose>
      <xsl:when test="contains($text, $startdelim) and contains($text, $enddelim)">
        <xsl:value-of select="substring-before($text, $startdelim)" />
        <i><xsl:value-of select="substring-before(substring-after($text,$startdelim),$enddelim)" /></i>       
        <xsl:call-template name="italics-template">
           <xsl:with-param name="text" select="substring-after($text,$enddelim)" />
           <xsl:with-param name="startdelim" select="$startdelim" />
           <xsl:with-param name="enddelim" select="$enddelim" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
 
</xsl:stylesheet>


The result of transformation is:

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title></title>
      <body>
         <p><i>this</i> is <i>an</i> example 1</p>
         <p>this is an <i>example 2</i></p>
         <p>this is an example 3</p>
         <p>th<i>is</i> is an <i>ex</i>ample<i>4</i></p>
      </body>
   </head>
</html>

(All {i}...{/i} pairs are replaced by <i>...</i>)

7. String Extraction

The following question was asked on XSL-List.

How can I extract a string from a string:
person(p1,p2,p3)

I need a table:
person p1
           p2
           p3

I don't know anything about length of p1,p2 or p3. There can be any number of p.

Solution

Given this XML file:

<?xml version="1.0" encoding="UTF-8"?>
<top>person(p1,p2,p3)</top>

The following XSLT stylesheet produces the wanted result:

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

<xsl:output method="html" indent="yes"/>

<xsl:template match="/top">
    <xsl:variable name="str1" select="substring-before(., '(')"/>
    <xsl:variable name="str2">
    <xsl:choose>
        <xsl:when test="contains(substring-after(., '('), ',')">
           <xsl:value-of select="substring-before(substring-after(., '('), ',')"/>
        </xsl:when>
        <xsl:otherwise>
           <xsl:value-of select="substring-before(substring-after(., '('), ')')"/>
        </xsl:otherwise>
    </xsl:choose>
   </xsl:variable>
   <xsl:variable name="str3" select="substring-before(substring-after(., ','), ')')"/>
   <html>
     <head>
       <title/>
     </head>
     <body>
       <table>
            <tr>
               <td>
                   <xsl:value-of select="$str1"/>
               </td>
               <td>
                   <xsl:value-of select="$str2"/>
               </td>
            </tr>
            <xsl:call-template name="generateTRs">
                 <xsl:with-param name="str" select="$str3"/>
                 <xsl:with-param name="delim" select="','"/>
            </xsl:call-template>
       </table>
     </body>
   </html>
</xsl:template>

<!-- a recursive template to generate <tr>s -->
<xsl:template name="generateTRs">
  <xsl:param name="str"/>
  <xsl:param name="delim"/>
 
  <xsl:choose>
    <xsl:when test="contains($str, ',')">
      <tr>
       <td/>
       <td>
           <xsl:value-of select="substring-before($str, ',')"/>
       </td>
      </tr>
      <xsl:call-template name="generateTRs">
          <xsl:with-param name="str" select="substring-after($str, ',')"/>
          <xsl:with-param name="delim" select="$delim"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <tr>
           <td/>
           <td>
              <xsl:value-of select="$str"/>
           </td>
      </tr>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

8. Adding fixed whitespace to XSLT text output

The following question was asked on microsoft.public.xsl Newsgroup.

I want to take an element from a XML file and turn the text of the element into a fixed-length string of, say, 20 characters.

The stylesheet for this problem is:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
 
 <xsl:output method="text" />

 <xsl:template match="/root">
   <xsl:variable name="new-str">
     <xsl:call-template name="str-pad">
        <xsl:with-param name="string" select="A" />
        <xsl:with-param name="pad-char" select="' '" />
        <xsl:with-param name="str-length" select="20" />
     </xsl:call-template>
   </xsl:variable>
  
   New string = <xsl:value-of select="$new-str" /> , Length = <xsl:value-of select="string-length($new-str)" />
  
 </xsl:template>
 
 <xsl:template name="str-pad">
   <xsl:param name="string" />
   <xsl:param name="pad-char" />
   <xsl:param name="str-length" />
  
   <xsl:choose>
     <xsl:when test="string-length($string) > $str-length">
        <xsl:value-of select="substring($string,1,20)" />  
     </xsl:when>
     <xsl:when test="string-length($string) = $str-length">
        <xsl:value-of select="$string" />  
     </xsl:when>
     <xsl:otherwise>
       <xsl:call-template name="str-pad">
          <xsl:with-param name="string" select="concat($pad-char,$string)" />
          <xsl:with-param name="pad-char" select="$pad-char" />
          <xsl:with-param name="str-length" select="$str-length" />
       </xsl:call-template>
     </xsl:otherwise>    
   </xsl:choose>  
 </xsl:template>
 
</xsl:stylesheet>

For e.g. when it is applied to this XML:

<?xml version="1.0"?>
<root>
  <A>hello</A> 
</root>

it produces output:

New string =                hello , Length = 20

Here I am doing left padding. If you wish, you may do the right padding similarly.. For this you need to replace <xsl:with-param name="string"
select="concat($pad-char,$string)" /> with <xsl:with-param name="string" select="concat($string, $pad-char)" /> ...

9. XML to HTML transformation, with blank cells in output

The following question was asked on XSL-List.

I need to create an HTML table from XML using XSLT.

The XML looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<worksheet>
    <row number="0">
        <col number="0">Name</col>
        <col number="1">Grade</col>
        <col number="2">Phone</col>
        <col number="3">City</col>
        <col number="4">State</col>
    </row>
    <row number="1">
        <col number="0">Anna</col>
        <col number="1">3</col>
        <col number="2">555-5555</col>
        <col number="3">Livermore</col>
        <col number="4">CA</col>
    </row>
    <row number="2">
        <col number="0">David</col>
        <col number="1">4</col>
        <col number="3">Livermore</col>
        <col number="4">CA</col>
    </row>
    <row number="3">
        <col number="0">Jane</col>
        <col number="1">5</col>
    </row>
</worksheet>

I want a resulting HTML table that accounts for the fact that every piece of data is in the correct column but not every row has data in all columns. For example, in my example, Anna has all data, but David does not have a phone number, and Jane only has a grade.

The stylesheet for this problem is:

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

<xsl:output method="html" indent="yes" />

<xsl:template match="/worksheet">
    <html>
        <head>
            <title/>
        </head>
        <body>
            <table>
                <tr>
                    <xsl:for-each select="row[@number = '0']/col">
                        <th><xsl:value-of select="." /></th>
                    </xsl:for-each>
                </tr>
                <xsl:variable name="n" select="count(row[@number = '0']/col) - 1" />
                <xsl:for-each select="row[not(@number = '0')]">
                    <tr>
                        <xsl:call-template name="GenerateRows">
                            <xsl:with-param name="m" select="$n" />
                            <xsl:with-param name="n" select="$n" />
                            <xsl:with-param name="row" select="." />
                        </xsl:call-template>
                    </tr>
                 </xsl:for-each>
            </table>
        </body>
    </html>
</xsl:template>

<xsl:template name="GenerateRows">
<xsl:param name="m" />
<xsl:param name="n" />
<xsl:param name="row" />

<xsl:if test="$m &gt;= 0">
    <td><xsl:value-of select="$row/col[@number = ($n - $m)]" /></td>
    <xsl:call-template name="GenerateRows">
        <xsl:with-param name="m" select="$m - 1" />
        <xsl:with-param name="n" select="$n" />
        <xsl:with-param name="row" select="$row" />
    </xsl:call-template>
</xsl:if>
</xsl:template>

</xsl:stylesheet>

10. Detecting upper case letters in a string

The following question was asked on microsoft.public.xsl Newsgroup.

I have the situation where I have a document element that looks like the following:
<field key="DocumentLogicalType">Submission</field>

I'd like to render "DocumentLogicalType" as "Document Logical Type" - basically inserting a space whenever an upper case character is encountered. How do I detect upper case (as opposed to a translation)?

The stylesheet for this problem is:

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

<xsl:output method="text" />

<xsl:variable name="caps" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />

<xsl:template match="/field">
    <xsl:call-template name="ModifyString">
        <xsl:with-param name="original-string" select="@key" />
        <xsl:with-param name="new-string" select="substring(@key,1,1)" />
        <xsl:with-param name="i" select="2" />
        <xsl:with-param name="len" select="string-length(@key)" />
    </xsl:call-template>
</xsl:template>

<xsl:template name="ModifyString">
    <xsl:param name="original-string" />
    <xsl:param name="new-string" />
    <xsl:param name="i" />
    <xsl:param name="len" />

    <xsl:choose>
        <xsl:when test="$i &lt;= $len">
            <xsl:choose>
                <xsl:when test="contains($caps, substring($original-string, $i, 1))">
                    <xsl:call-template name="ModifyString">
                        <xsl:with-param name="original-string" select="$original-string" />
                        <xsl:with-param name="new-string" select="concat($new-string, ' ', substring($original-string, $i, 1))" />
                        <xsl:with-param name="i" select="$i + 1" />
                        <xsl:with-param name="len" select="$len" />
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:call-template name="ModifyString">
                        <xsl:with-param name="original-string" select="$original-string" />
                        <xsl:with-param name="new-string" select="concat($new-string, substring($original-string, $i, 1))" />
                        <xsl:with-param name="i" select="$i + 1" />
                        <xsl:with-param name="len" select="$len" />
                    </xsl:call-template>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$new-string" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Dimitre Novatchev suggested the following solution written using XSLT 2.0, and his FXSL library:

Here's what I believe is a "clean" pure XSLT solution (the XSLT 1.0 solution is quite similar):

When the following transformation is applied against any xml document (not used):

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://fxsl.sf.net/" xmlns:testmap="testmap" exclude-result-prefixes="f testmap">

<xsl:import href="../f/func-str-dvc-map.xsl"/>
<xsl:variable name="vCaps" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>

<!-- to be applied on any xml source -->
<xsl:variable name="vTestMap" as="element()">
    <testmap:testmap/>
</xsl:variable>

<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="/">
    <xsl:value-of select="f:str-map($vTestMap, 'DocumentLogicalType')"/>
</xsl:template>

<xsl:template name="double" match="testmap:*" mode="f:FXSL">
    <xsl:param name="arg1"/>

    <xsl:value-of select="concat(if(contains($vCaps, $arg1)) then ' ' else '', $arg1)"/>
</xsl:template>

</xsl:stylesheet>

the wanted result is produced:

" Document Logical Type"

11. Flat file listing to hierarchical

The following question was asked on XSL-List.

Can anyone propose a pure XSLT (1 or 2) solution to transforming the following flat xml structure of directory paths into a hierarchical
(nested) xml.

<?xml version='1.0'?>
<listing>
    <item>cn/test.xml</item>
    <item>en</item>
    <item>en/test.html</item>
    <item>en/test1.html</item>
    <item>en/resource</item>
    <item>en/resource/style</item>
    <item>en/resource/style/test.css</item>
    <item>favicon.ico</item>
    <item>cn</item>
</listing>

to

<dir>
    <file name="favicon.ico"/>
    <dir name="cn">
        <file name="test.xml"/>
    </dir>
    <dir name="en">
        <file name="test.html"/>
        <file name="test1.html"/>
        <dir name="resource">
            <dir name="style">
                <file name="test.css"/>
            </dir>
        </dir>
    </dir>
</dir>

There were interesting replies.

Michael Kay wrote:

An outline design for XSLT 2.0: write a recursive template that does

<xsl:for-each-group select="$in" group-by="tokenize(.,'/')[$n]">

Initially call this with $in := the sequence of item elements, $n := 1.

On subsequent calls, $in := current-group(), $n := $n+1.

for-each-group does nothing if the population is empty, so the recursion will terminate naturally.

Andrew Welch:

This should do the job:

<xsl:stylesheet version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
                                            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                                            exclude-result-prefixes="xs">

<xsl:template match="listing">
    <dir>
        <xsl:call-template name="process">
            <xsl:with-param name="depth" select="1" as="xs:integer"/>
            <xsl:with-param name="seq" select="item"/>
        </xsl:call-template>
    </dir>
</xsl:template>

<xsl:template name="process">
    <xsl:param name="depth" as="xs:integer"/>
    <xsl:param name="seq"/>
    <xsl:for-each-group select="$seq" group-by="tokenize(., '/')[$depth]">
        <xsl:variable name="part" select="tokenize(., '/')[$depth]"/>
        <xsl:choose>
            <xsl:when test="contains($part, '.')">
                <file name="{$part}"/>
            </xsl:when>
            <xsl:otherwise>
                <dir name="{$part}">
                    <xsl:call-template name="process">
                        <xsl:with-param name="depth" select="$depth + 1"/>
                        <xsl:with-param name="seq" select="$seq[tokenize(., '/')[$depth] = $part]"/>
                    </xsl:call-template>
                </dir>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>

The output is:

<dir>
    <dir name="cn">
        <file name="test.xml"/>
    </dir>
    <dir name="en">
        <file name="test.html"/>
        <file name="test1.html"/>
        <dir name="resource">
            <dir name="style">
                <file name="test.css"/>
            </dir>
        </dir>
    </dir>
    <file name="favicon.ico"/>
</dir>

David Carlisle:

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

<xsl:output indent="yes"/>

<xsl:template match="listing">
    <xsl:apply-templates select="item"/>
</xsl:template>

<xsl:template match="item">
    <xsl:param name="path" select="''"/>
    <xsl:variable name="rel" select="substring-after(.,$path)"/>
    <xsl:choose>
        <xsl:when test="not(starts-with(.,$path))"/>
        <xsl:when test="contains($rel,'/')"/>
        <xsl:when test="../item[starts-with(.,concat(current(),'/'))]">
            <dir name="{$rel}">
                <xsl:apply-templates select="../item">
                    <xsl:with-param name="path" select="concat(current(),'/')"/>
                </xsl:apply-templates>
            </dir>
        </xsl:when>
        <xsl:otherwise>
            <file name="{$rel}"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

$ saxon listing.xml listing.xsl
<?xml version="1.0" encoding="utf-8"?>
<dir name="en">
    <file name="test.html"/>
    <file name="test1.html"/>
    <dir name="resource">
        <dir name="style">
            <file name="test.css"/>
        </dir>
    </dir>
</dir>
<file name="favicon.ico"/>
<dir name="cn">
    <file name="test.xml"/>
</dir>

George Cristian Bina provides two solutions:

[1]

I assume the files are those items that do not have further content. My take will be something like below. I believe there is still some room
for optimizing the identification of a file.

<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

<xsl:output indent="yes"/>

<xsl:template match="listing">
    <dir>
        <xsl:call-template name="group">
            <xsl:with-param name="items" select="./item"/>
        </xsl:call-template>
     </dir>
</xsl:template>

<xsl:template name="group">
    <xsl:param name="items" select="/.."/>
    <xsl:param name="base" select="''"/>
    <xsl:for-each select="$items[not(contains(substring-after(.,$base), '/'))]">
        <xsl:if test="not($items[current()!=. and contains(., current())])">
            <file name="{substring-after(., $base)}"/>
        </xsl:if>
    </xsl:for-each>
    <xsl:for-each-group select="$items[contains(substring-after(.,$base), '/')]" group-adjacent="substring-before(substring-after(.,$base), '/')" >
        <xsl:sort select="."/>
        <xsl:variable name="folder" select="substring-before(substring-after(current-group()[1], $base), '/')"/>
        <dir name="{$folder}">
            <xsl:call-template name="group">
                <xsl:with-param name="items" select="current-group()"/>
                <xsl:with-param name="base" select="concat($base, $folder,'/')"/>
            </xsl:call-template>
        </dir>
    </xsl:for-each-group>
</xsl:template>

</xsl:stylesheet>

[2]

Here it is another solution, XSLT 2.0, that uses a key that maps from the folder to the items contained in that folder, then it is enough to start from empty folder and just iterate the items in each folder.

<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:f="http://www.oxygenxml.com/functions" exclude-result-prefixes="xs f">

<xsl:output indent="yes"/>

<xsl:function name="f:baseFolder">
    <xsl:param name="value" as="xs:string"/>
    <xsl:variable name="tokens" select="tokenize($value, '/')"/>
    <xsl:value-of select="$tokens[position()!=last()]" separator="/"/>
</xsl:function>

<xsl:key name="folder" match="item" use="f:baseFolder(.)"/>

<xsl:template match="listing">
    <dir>
        <xsl:call-template name="list"/>
    </dir>
</xsl:template>

<xsl:template name="list">
    <xsl:param name="folder" select="''"/>
    <xsl:for-each select="key('folder', $folder)">
        <xsl:choose>
            <xsl:when test="key('folder', .)">
                <dir name="{translate(substring-after(., $folder), '/', '')}">
                    <xsl:call-template name="list">
                        <xsl:with-param name="folder" select="."/>
                    </xsl:call-template>
                </dir>
            </xsl:when>
            <xsl:otherwise>
                <file name="{translate(substring-after(., $folder), '/', '')}"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Dimitre Novatchev:

Dimitre uses his FXSL library.

I'm using a two-step approach naturally expressed as functional composition.

Notice that not only is this a litlle easier to understand, but that the main "powerhorse" (the function f:makeTree() ) is just 17 lines of code:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:f="http://fxsl.sf.net/" exclude-result-prefixes="f xs">

<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="/*">
    <xsl:sequence select="f:makeTree(f:tokenizePaths(/*/*), 1)"/>
</xsl:template>

<xsl:function name="f:tokenizePaths">
    <xsl:param name="pfilePaths" as="element()+"/>
    <xsl:for-each select="$pfilePaths">
        <item>
            <xsl:for-each select="tokenize(.,'/')">
                <q><xsl:value-of select="."/></q>
            </xsl:for-each>
        </item>
    </xsl:for-each>
</xsl:function>

<xsl:function name="f:makeTree" as="element()*">
    <xsl:param name="pItems" as="element()*"/>
    <xsl:param name="pqLevel" as="xs:integer"/>

    <xsl:for-each-group select="$pItems" group-by="q[$pqLevel]">
        <xsl:choose>
            <xsl:when test="count(current-group()) = 1">
                <file name="{q[$pqLevel]}"/>
            </xsl:when>
            <xsl:otherwise>
                <dir name="{q[$pqLevel]}">
                    <xsl:sequence select="f:makeTree(current-group(), $pqLevel+1)"/>
                </dir>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each-group>

</xsl:function>

</xsl:stylesheet>