Old BizTalk XSLT Tricks 3 – Global XSLT Variables

Posted: October 9, 2023  |  Categories: Azure BizTalk
Tags: XSLT

This final blog in this series talks about how I use global XSLT variables. I still use the BizTalk mapper to generate my XSLT for use in our Azure Integration account. I use one of my own examples to show case using global variables. I think this might be useful for Logic App developers that do not have a history of BizTalk XSLT development.

Without global variables, we could not collect data for processing. Global variables provide a
place for us to store the collected data until we are ready to use it. Once a global variable has
been created, the content of that variable is available to the rest of the map.

Pro Mapping in BizTalk Server 2009 | SpringerLink

My example using global XSLT variables

Sometimes we send through crate charges on an invoice that was not on the original order. There can be many line items like this on the invoice. A list of crate charge item codes as comma separated variables is available. The crate charge item line is not sent to the customer. The crate charges are sent as AdditionalCharges on the invoice instead.

The solution

An XSLT map in an AZure integration account implements this logic;

  1. exclude the item lines that contain crate charges.
  2. add those GST exclusive amounts to the SpecialHandling Amount cXML fragment.
  3. subtract this amount from the SubTotal amount.
  4. subtract the GST from the tax amount.

The BizTalk map that generates the XSLT is shown.

First create a C# msxsl:script to check whether the SupplierStockCode exists in the ItemExclusions attribute. I talk about this here.

///*Check if a CSV delimited string contains a specific element
//*/

public string ContainsItem(string itemExclusions, string item)
{
	string[] items = itemExclusions.Split(',');

	System.Collections.Generic.List<string> itemList = new System.Collections.Generic.List<string>(items);
	return itemList.Contains(item).ToString();
}

Secondly, we test whether the value of the first script is False. If the value is False it is an item to be included in the repeating node destination XML. The XSLT fragment looks like

            <xsl:for-each select="InputMessagePart_1/s0:IF_INVOICE/s0:InvoiceBody">
              <xsl:for-each select="s0:InvoiceLine">
                <xsl:for-each select="s0:OrderLineBody">
                  <xsl:for-each select="s0:StockItemDetail">
                    <xsl:variable name="var:v30" select="string(../../../../s0:InvoiceHeader/@ItemExclusions)" />
                    <xsl:variable name="var:v31" select="string(@SupplierStockCode)" />
                    <xsl:variable name="var:v32" select="userCSharp:ContainsItem($var:v30 , $var:v31)" />
                    <xsl:variable name="var:v33" select="userCSharp:LogicalEq(string($var:v32) , &quot;False&quot;)" />
                    <xsl:if test="$var:v33">
                      <InvoiceDetailItem>

Thirdly, we define some global variables for data Collection. Global variables provide a place for us to store the collected data until we are ready to use it. Once a global variable has been created, the content of that variable is available to the rest of the map. The script below must be linked to an output node that occurs before both of the other scripts. Note that this script does nothing but create the global variable. This attached to the root node cXML. Since the script has no output and the root node is always output, we don’t need to block anything.

///*Declare variables
//
//.*/

decimal specialHandingAmount;
decimal crateTaxAmount;
public void DeclareVariables ()
{}

Fourthly, we test whether the value of the first script is True. If it is, we accumulate the total of the GST exclusive amount in the specialHandingAmount global variable and the tax amount in the crateTaxAmount global variable. This script is attached to the data collection record node. Even though this script has no output, the data collection node would be created unless we block it. If this was BizTalk map we would use the Logical String functoid to block the creation of that node.

///*Collect special handling ammount
//
//.*/

public void AddSpecialHandlingAmount(decimal amount)
{
   specialHandingAmount = amount +  specialHandingAmount;
}
///*Collect special handling tax amount
//
//.*/

public void AddSpecialHandlingTaxAmount(decimal amount)
{
   crateTaxAmount = amount +  crateTaxAmount;
}

The XSLT fragment looks like

            <xsl:for-each select="InputMessagePart_1/s0:IF_INVOICE/s0:InvoiceBody">
              <xsl:for-each select="s0:InvoiceLine">
                <xsl:for-each select="s0:OrderLineBody">
                  <xsl:for-each select="s0:StockItemDetail">
                    <xsl:variable name="var:v25" select="userCSharp:ContainsItem(string(../../../../s0:InvoiceHeader/@ItemExclusions) , string(@SupplierStockCode))" />
                    <xsl:variable name="var:v26" select="userCSharp:LogicalEq(string($var:v25) , &quot;True&quot;)" />
                    <xsl:if test="string($var:v26)='true'">
                      <xsl:variable name="var:v27" select="s0:LineTax/s0:Amount111/@value" />
                      <xsl:variable name="var:v28" select="userCSharp:AddSpecialHandlingTaxAmount(string($var:v27))" />
                      <xsl:variable name="var:v29" select="userCSharp:LogicalIsString(string($var:v28))" />
                      <xsl:if test="$var:v29">
                        <InvoiceDetailShipNoticeInfo />
                      </xsl:if>
                    </xsl:if>
                  </xsl:for-each>
                </xsl:for-each>
              </xsl:for-each>
            </xsl:for-each>
            
            .....
                        <xsl:for-each select="InputMessagePart_1/s0:IF_INVOICE/s0:InvoiceBody">
              <xsl:for-each select="s0:InvoiceLine">
                <xsl:for-each select="s0:OrderLineBody">
                  <xsl:for-each select="s0:StockItemDetail">
                    <xsl:variable name="var:v48" select="string(../../../../s0:InvoiceHeader/@ItemExclusions)" />
                    <xsl:variable name="var:v49" select="string(@SupplierStockCode)" />
                    <xsl:variable name="var:v50" select="userCSharp:ContainsItem($var:v48 , $var:v49)" />
                    <xsl:variable name="var:v51" select="userCSharp:LogicalEq(string($var:v50) , &quot;True&quot;)" />
                    <xsl:if test="string($var:v51)='true'">
                      <xsl:variable name="var:v52" select="s0:ExtPrice/s0:Amount112/@value" />
                      <xsl:variable name="var:v53" select="userCSharp:AddSpecialHandlingAmount(string($var:v52))" />
                      <xsl:variable name="var:v54" select="userCSharp:LogicalIsString(string($var:v53))" />
                      <xsl:if test="$var:v54">
                        <InvoiceDetailServiceItem />
                      </xsl:if>
                    </xsl:if>
                  </xsl:for-each>
                </xsl:for-each>
              </xsl:for-each>
            </xsl:for-each>

Fifthly output the final value of the specialHandingAmount global variable, subtract it from the SubTotal Amount and add it to the SpecialHandling Amount cXML fragment.

///Output Special handing amount // //./

public decimal OutputSpecialHandlingAmount ()
{
return specialHandingAmount;
}

Sixthly output the final value of the crateTaxAmount global variable and subtract it from the tax Amount.

///Output Special handing amount // //./

public decimal OutputSpecialHandlingAmount ()
{
return specialHandingAmount;
}

Conclusion

In summary, I have shown an example of how to use global XSLT variables to accumulate totals for later use in an XSLT map.

Last but not least writing this article prompts some question for me about the Azure Standard data mapper and the Standard Logic App runtime. I deploy all of my XSLT to an integration account and use it in an XML transform action in a Consumption Logic Apps. While I would expect this to run in a Standard Logic App too, does the Standard version offer new opportunities or constraints?

  1. What version of .Net does the Azure Standard data mapper use? Does it support msxsl with a more recent version of .Net?
  2. Does the Azure Standard data mapper still generate msxsl scripting for custom code snippets?
turbo360

Back to Top