Friday, November 10, 2017

BizTalk Maps - Using Recursive XSLT template to simulate FOR clause

Recently, I was resolving an MSDN forum question that needs to split an input String to String destination nodes with maximum 80 length.

The problem was, how to generate N destination nodes with only one input node splitting the string in fixed length strings.

As BizTalk Maps uses XSLT 1.0, we can't use:

<xsl:for-each select="1 to 60">...</xsl:for-each>

For example....

Then I use a recursive call to a XSLT template, to implement this. The objective is from a XML like this:

<ns0:Root xmlns:ns0="http://BizTalkMassCopy.Instructions">
 <Instructions>Instructions_0000000000000000000000000000000000000000000000000000000000000000ENDSECONDSEGMENT</Instructions>
</ns0:Root>

Get an XML like this:

<ns0:Root xmlns:ns0="http://BizTalkMassCopy.NTESegment">
 <NTESegment>Instructions_0000000000000000000000000000000000000000000000000000000000000000END</NTESegment>
 <NTESegment>SECONDSEGMENT</NTESegment>
</ns0:Root>

The question is split the original Instructions element into N NTESegments elements, that have a maximum length of 80 chars:

  • If Instructions length is less or equal than 80, then only one NTESegment at the output
  • If Instructions length is greater than 80, generates NTESegments of 80 chars, to print all the content of Intructions.

My solution to solve this, was using a recursive call to an XSLT template. Here is the solution:

<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp" version="1.0" xmlns:ns0="http://BizTalkMassCopy.NTESegment" xmlns:s0="http://BizTalkMassCopy.Instructions" xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">
  <xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
  <xsl:template match="/">
    <ns0:Root>
      <xsl:apply-templates select="/s0:Root" />
    </ns0:Root>
  </xsl:template>
  <xsl:template name="recursive_loop" match="/s0:Root">
    <xsl:param name="curposition">1</xsl:param>
    <xsl:variable name="var:length" select="userCSharp:StringSize(string(Instructions/text()))" />
        
    <xsl:if test="not($curposition > $var:length)">
      <xsl:if test="not($curposition + 80 > $var:length)">
        <NTESegment>
          <xsl:variable name="var:current" select="userCSharp:StringSubstring(string(Instructions/text()), $curposition, $curposition + 80 - 1)" />
          <xsl:value-of select="$var:current" />
        </NTESegment>
        <xsl:call-template name="recursive_loop">
          <xsl:with-param name="curposition">
            <xsl:value-of select="$curposition + 80" />
          </xsl:with-param>
        </xsl:call-template>
      </xsl:if>
      <xsl:if test="$curposition + 80 > $var:length">
        <NTESegment>
          <xsl:variable name="var:current" select="userCSharp:StringSubstring(string(Instructions/text()), $curposition, $var:length)" />
          <xsl:value-of select="$var:current" />
        </NTESegment>
      </xsl:if>      
    </xsl:if>
    
    
  </xsl:template>   
  <msxsl:script language="C#" implements-prefix="userCSharp">
  <![CDATA[
public int StringSize(string str)
{
 if (str == null)
 {
  return 0;
 }
 return str.Length;
}

public string StringSubstring(string str, string left, string right)
{
 string retval = "";
 double dleft = 0;
 double dright = 0;
 if (str != null && IsNumeric(left, ref dleft) && IsNumeric(right, ref dright))
 {
  int lt = (int)dleft;
  int rt = (int)dright;
  lt--; rt--;
  if (lt >= 0 && rt >= lt && lt < str.Length)
  {
   if (rt < str.Length)
   {
    retval = str.Substring(lt, rt-lt+1);
   }
   else
   {
    retval = str.Substring(lt, str.Length-lt);
   }
  }
 }
 return retval;
}

public bool IsNumeric(string val)
{
 if (val == null)
 {
  return false;
 }
 double d = 0;
 return Double.TryParse(val, System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out d);
}

public bool IsNumeric(string val, ref double d)
{
 if (val == null)
 {
  return false;
 }
 return Double.TryParse(val, System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out d);
}


]]></msxsl:script>
</xsl:stylesheet>

No comments: