This post is about using a recursive XSLT Call Template to create many elements from one using an incrementing counter. I found this example interesting and thought it was worth sharing. This post is for James just to show that I will use XSLT if I really have to.
Recently I was presented with the following mapping requirement. The input XML is dissembled from a CSV flat file. A column called EAN will be used to create a folder path and another column called numOfImages will be used to create a unique file names for each image. The EAN is converted to a MD5Hash, the first two digits are used to for the first folder in the path and the second two characters are used for the second two characters in the path. For example if EAN =9415599100009 and numofImages = 4, I want an XML fragment like;
Note that many image paths must be created from one element that just vary by the zero padded counter.
Conventional programming would solve this by using a for loop that creates a path on each iteration with a counter that increments until the counter exceeds the numOfImages. For example
“In a functional program you can’t do this, because you can’t update variables. So instead of writing a loop, you need to write a recursive function.” (Michael Kay). The BizTalk Mapper using the looping functoid to process all members of a node-set but it is hard to do this when your input file is flat and the counter is only present in one element. For this reason I decided to use a Scripting function with XSLT Call template to create the required XML fragment. Under the hood the looping functoid creates XSLT that uses the <XSL:for-each> function which handles simple looping over node sets. We could not use this in our case because we need explicit control of the looping.
The final BizTalk Map looks like;
The first scripting functoid shape calls a class library that hashes EAN and returns a random path name like 12\3E\. This is an input parameter for the next scripting functoid which uses an XSLT Call Template. A value of 1 is hard coded as input parameter that will be used later as the first counter value. Finally the third input parameter is the EAN value.
The XSLT template uses recursion to process each counter in loop. Note is was important to use recursion because we must use XSLT 1.0. There are other techniques that can be used with XSLT 2.0 but we will not go into this here. After the first pass through the template is called again xsl:call-template name=”createImagePaths”>. This only processes half the remaining NumofIMages and repeats the recursion. Eventually the recursive processing gets to point where $pStart > $pEnd and it unwinds. At this point the a second call is made to the call template that process the remaining NumofImages counter in the same way. Finally $pStart > $pEnd and this time the function is finished and exits. This is called Divide and Conquer style recursion and is particularly useful for a large number of images when you might meet constraints due to stack overflow . In our case this is unlikely to occur because we have limited ourselves to 100 images but it might be useful if the number was a 1000 or more. The XSLT is shown below;
<xsl:if test=”not($pStart > $pEnd)”>
<xsl:when test=”$pStart = $pEnd”>
<xsl:variable name=”paddedNum” select=”format-number($pStart,’00’)”/>
<xsl:variable name=”vMid” select=
“floor(($pStart + $pEnd) div 2)”/>
<xsl:with-param name=”pStart” select=”$pStart”/>
<xsl:with-param name=”pEnd” select=”$vMid”/>
<xsl:with-param name=”path” select=”$path”/>
<xsl:with-param name=”pStart” select=”$vMid+1″/>
<xsl:with-param name=”pEnd” select=”$pEnd”/>
<xsl:with-param name=”path” select=”$path”/>
The idea for this recursive XSLT came from Dimitre Novatchev. This can be adapted for any XSLT task that needs to “do something N times”.
Finally it is important to mention the importance of the filters to validate the content of the inputs to the scripting functoid. Remember the data originates from a CSV file and if the column is empty a null value will be sent to the scripting functoid. Indeed in many of my samples the value of NumofImages was empty and without the filter an ugly unhandled error occurs. Devenv.exe crashes in System.Data.SqlXl.ni.dll
The simplest way to overcome this is to check that the NumofImages is numeric and that the EAN is a string. If these conditions are not met then suppress the images record creation to stop the scripting functoid from running. The unhandled error will no longer occur.
In summary I have given an example of a recursive XSLT call template that does something N times. This can be adapted to other situations easily.