Make a map in full Xslt

Florian CAILLAUD
Published by Florian CAILLAUD
Category : BizTalk
25/11/2018

Between many artifacts BizTalk Server provided maps. Maps allow XML Source messages transformation into XML Destination messages. For that purpose, there are basically three approaches.

 

Microsoft BizTalk Mapper

The BizTalk Mapper is an integrated tool of Visual Studio that allows you to graphically specify both its input and output XSD schemas. Once the operation is fulfilled, simple affectations are straightforward. For more complex operations, BizTalk brings to us a functoïds Toolbox. These elements achieve various treatments (string operations, logic, database access, etc.).

BizTalk mapper

Nonetheless, we quickly realize that some transformations still too laborious or impossible to implement with the provided functoïds.

 

Scripting functoïd

For these cases, we can use the scripting functoïd. It allows inline C#, Jscript, VB or XSLT scripts integration. Moreover, it is possible to run external methods in your transformation.

scripting functoid configuration

This functoïd helps you to perform every kind of transformations, even complex ones. However, for some processing this may quickly become difficult to read and therefore to maintain.

 

Full XSLT (Extensible Stylesheet Language Transformations) Maps

To avoid this drawback, the alternative is to build our transformation directly into XSLT. This option is less graphical. Some will say that it is also harder to maintain. The fact is that this often allows to carry out complex treatments relatively quickly. We will detail this approach here.
We’re going to set up a XSLT solution to meet the following problem : from a raw list of persons in input of the map, we want to get a list of unique persons (with a full name in capital letters)
For this purpose, it is necessary to create an empty map (.btm) and specify the source and destination schema, then click onto the grid to access to the right properties :

custom xslt configuration

 

The « Custom XSLT Path » property gives the path to a XSL file describing the transformation from the source message to the destination message.
By creating the file below (and adding its URI in the « Custom XSLT Path » property), it is possible to figure out our problem.


<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="https://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp" version="1.0" xmlns:ns0="https://XSLTransform.DestinationSchema" xmlns:s0="https://XSLTransform.SourceSchema" xmlns:userCSharp="https://schemas.microsoft.com/BizTalk/2003/userCSharp">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key name="uniqueName" match="Person" use="Name"/>

<xsl:template match="/">
  <xsl:apply-templates select="/s0:Persons" />
</xsl:template>

<xsl:template match="/s0:Persons">
   <ns0:Persons>
      <xsl:for-each select="key('uniqueName', Person/Name)">
         <xsl:if test="not(preceding-sibling::*[1]/Name = Name)">
            <xsl:variable name="fullUpperName" select="userCSharp:GetUpperCaseFullName(Name/text(), FirstName/text())"/>
            <Person>
               <FullName>
                  <xsl:value-of select="$fullUpperName" />
               </FullName>
            </Person>
         </xsl:if>
      </xsl:for-each>
   </ns0:Persons>
</xsl:template>

<msxsl:script language="C#" implements-prefix="userCSharp"><![CDATA[
   public string GetUpperCaseFullName(string pName, string pFirstName)
   {
      string fullName = pName + " " + pFirstName;
      return fullName.ToUpper();
   }
]]>
</msxsl:script>
</xsl:stylesheet>

This code performs a transformation which would be impossible to set up with simple functoïds, in the mapper. In addition, we can see that this is obviously simpler than using an external method, through a scripting functoïd.
We also remind that BizTalk does not handle XSLT version up to 1.0. However, as a workaround, it is possible to use C# scripts inside the « Script » tag and then calling them, in the XSL, prefixed by the value of the « Implements-Prefix » attribute.

 

Tips

If the from scratch creation of the XSL file seems too complex or if the map already exists, we can generate this file. You have to right click on the map and then click « Validate Map ».
In addition, external method (from another assembly) can be call into the XSL file, after referencing the assembly. This requires to first create an extension file (.xml) containing the strong name of each external assembly:

<ExtensionObjects>
   <ExtensionObject Namespace="https://schemas.microsoft.com/BizTalk/2003/ScriptNS0" AssemblyName="XSLTransform.Helper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=dbf4b132923889f0" ClassName="XSLTransform.Helper" />
</ExtensionObjects>

The URI of this file must be specified in the « Custom XML Extension » property of the map. It is then possible to modify the XSL file as follow :


<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="https://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp ScriptNS0" version="1.0" xmlns:ns0="https://XSLTransform.DestinationSchema" xmlns:s0="https://XSLTransform.SourceSchema" xmlns:userCSharp="https://schemas.microsoft.com/BizTalk/2003/userCSharp" xmlns:ScriptNS0="https://schemas.microsoft.com/BizTalk/2003/ScriptNS0">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key name="uniqueName" match="Person" use="Name"/>

<xsl:template match="/">
   <xsl:apply-templates select="/s0:Persons" />
</xsl:template>

<xsl:template match="/s0:Persons">
   <ns0:Persons>
      <xsl:for-each select="key('uniqueName', Person/Name)">
         <xsl:if test="not(preceding-sibling::*[1]/Name = Name)">
            <xsl:variable name="fullUpperName" select="concat(ScriptNS0:ToUpper(Name/text()), ' ', ScriptNS0:ToUpper(FirstName/text()))"/>
            <Person>
               <FullName>
                  <xsl:value-of select="$fullUpperName" />
               </FullName>
            </Person>
         </xsl:if>
      </xsl:for-each>
   </ns0:Persons>
</xsl:template>

</xsl:stylesheet>