BizTalk Advanced Mapping Tips – Inline XSLT – Selecting distinct nodes (grouping)

  • Sandro Pereira
  • Sep 14, 2009
  • 5 min read

One of the more challenging things to do in XSL 1.0 was getting a distinct list of values from a set of nodes. There is no simple syntax for writing this type of XPath query. However, this is very easy to do in XSL 2.0:

<xsl:for-each select="distinct-values(…)">

But unfortunately, BizTalk does not support XSL 2.0.

📝 One-Minute Brief

Standard BizTalk functoids often fail when faced with complex grouping or the need to select distinct nodes from a repeating source. This post demonstrates how to use Inline XSLT within a Scripting Functoid to implement the Muenchian Method. By using XSLT keys, you can efficiently group data (e.g., grouping line items by Invoice ID) and ensure high-performance transformations for large XML messages.

In Sample 1, the transformation needed to take place was to extract the list of External Partners from the list of external employees.

<ns0:ExternalEmployees xmlns:ns0="http://SelectDistinctValues.Input">
   <Employee>
      <Name>Sandro Pereira</Name>
      <Company>DevScope</Company>
   </Employee>
   <Employee>
      <Name>Employee 1</Name>
      <Company>DevScope</Company>
   </Employee>
   <Employee>
      <Name>DemoEmployee2</Name>
      <Company>DemoCompany2</Company>
   </Employee>
   <Employee>
      <Name>Employee 2</Name>
      <Company>DevScope</Company>
   </Employee>
   <Employee>
      <Name>DemoEmployee</Name>
      <Company>DemoCompany</Company>
   </Employee>
</ns0:ExternalEmployees>

The first thing we need to do is to drag a Script functoid onto the Grid. Then, drag a line to the element we want to create output for.

Mapping distinct values

We will start with the following XSLT to create the output in the format we want.

<xsl:variable name="unique-companies" select="//Employee[not(Company=preceding-sibling::Employee/Company)]/Company" />

This creates a variable named unique-companies and populates it with a list of unique companies nodes. Now we need to start outputting our nodes and then loop through the companies. The following code will do that.

<xsl:for-each select="$unique-companies">
   <PartnerName><xsl:value-of select="."/></PartnerName>
</xsl:for-each>

The output looks like this:

<ns0:ListPartners xmlns:ns0="http://SelectDistinctValues.Output1">
   <PartnerName>DevScope</PartnerName>
   <PartnerName>DemoCompany2</PartnerName>
   <PartnerName>DemoCompany</PartnerName>
</ns0:ListPartners>

Sample 1 is composed of:

  • Input.xsd and Output1.xsd
  • Output1.xsd
  • Input.xml

Sample 2 is a response to a question in the MSDN thread:

Giving data like this:

<Data Header="AAA" date="2008-10-28" Name="a1" Value="1.0" />
<Data Header="AAA" date="2008-10-28" Name="a2" Value="2.0" />
<Data Header="AAA" date="2008-10-28" Name="a3" Value="3.0" />
<Data Header="BBB" date="2008-10-28" Name="a1" Value="1.0" />
<Data Header="BBB" date="2008-10-28" Name="a2" Value="2.0" />
<Data Header="BBB" date="2008-10-28" Name="a3" Value="3.0" />

It was a requirement to map for the following schema format:

<data>
   <header>AAA</header>
   <date>2008-10-28</date>
   <record>
      <name>a1</name>
      <value>1.0</value>
      <name>a2</name>
      <value>2.0</value>
      <name>a3</name>
      <value>3.0</value>
   </record>
   <header>BBB</header>
   <date>2008-10-28</date>
   <record>
      <name>a1</name>
      <value>1.0</value>
      <name>a2</name>
      <value>2.0</value>
      <name>a3</name>
      <value>3.0</value>
   </record>
</data>

The logic is that you have to get the distinct values from the Header, and then for all these distinct values, select all elements from this particular value. In this sample, all element that has AAA in the Header, and then all elements that have BBB.

The map looks like this:

Mapping distinct values

Basically, I use two functoid scripts with inline XSLT:

  • In functoid 1, I created a template that gets all records for a specific Header sent by the parameter
<xsl:template name="NameValueTemplate">
   <xsl:param name="param1" />
   <xsl:for-each select="//Data[@Header=$param1]">
      <xsl:element name="Name"><xsl:value-of select="@Name" /></xsl:element>
      <xsl:element name="Value"><xsl:value-of select="@Value" /></xsl:element>
   </xsl:for-each>
</xsl:template>
  • In the functoid 2, I select all distinct headers and call a template for each header
<xsl:element name="Data">
   <xsl:for-each select="Data[not(@Header=preceding-sibling::Data/@Header)]">
      <xsl:element name="Header"><xsl:value-of select="@Header" /></xsl:element>
      <xsl:element name="date"><xsl:value-of select="@date" /></xsl:element>
      <xsl:element name="Record">
         <xsl:call-template name="NameValueTemplate">
            <xsl:with-param name="param1" select="string(@Header)" />
         </xsl:call-template>
      </xsl:element>
   </xsl:for-each>
</xsl:element>

Sample 2 is composed of:

  • DataInput.xsd and DataOutput.xsd
  • DataMap.btm
  • DataInput.xml

Source Code/Download

THIS COMPONENT IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND.

You can download the source code from GitHub here:

Hope you find this helpful! So, if you liked the content or found it useful and want to help me write more, you can help us buy a Star Wars Lego for my son! 

Thanks for buying me a coffee
#1 all-in-one platform for Microsoft BizTalk Server management and monitoring

Author: Sandro Pereira

Sandro Pereira lives in Portugal and works as a consultant at DevScope. In the past years, he has been working on implementing Integration scenarios both on-premises and cloud for various clients, each with different scenarios from a technical point of view, size, and criticality, using Microsoft Azure, Microsoft BizTalk Server and different technologies like AS2, EDI, RosettaNet, SAP, TIBCO etc. He is a regular blogger, international speaker, and technical reviewer of several BizTalk books all focused on Integration. He is also the author of the book “BizTalk Mapping Patterns & Best Practices”. He has been awarded MVP since 2011 for his contributions to the integration community.

Leave a Reply

Your email address will not be published. Required fields are marked *

The Ultimate Cloud
Management Platform for Azure

Supercharge your Azure Cost Saving

Learn More
Turbo360 Widget

Back to Top