Yossi Dahan [BizTalk]

Google
 

Tuesday, June 26, 2007

processing xml in two phases in xsl

Over the last few months I've been involved in the development of quite a few xsl scripts; one requirement that kept coming up and, as I'm not an xsl expert I always thought is impossible, was to perform what I would describe as - two phase parsing -where we run one bit of xsl to create an interim xml only to run another bit of xsl on it to get the results we want.

Here's an example of this requirement, I hope I can describe it in a way that makes sense:

Imagine you have two xml messages that are linked - one has a list of items, and the other their prices, something like this:

<?xml version="1.0" encoding="utf-8"?>
<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema">
<InputMessagePart_0>
<items>
<item type="book" name="book1" barcode="12"/>
<item type="book" name="book2" barcode="34"/>
<item type="cd" name="cd1" barcode="56"/>
<item type="cd" name="cd2" barcode="78"/>
</items>
</InputMessagePart_0>
<InputMessagePart_1>
<prices>
<price price="10.5">
<barcodes>
<barcode>12</barcode>
</barcodes>
</price>
<price price="24.70">
<barcodes>
<barcode>34</barcode>
</barcodes>
</price>
<price price="56.20">
<barcodes>
<barcode>56</barcode>
</barcodes>
</price>
<price price="90.14">
<barcodes>
<barcode>78</barcode>
</barcodes>
</price>
</prices>
</InputMessagePart_1>
</ns0:Root>


(This might not make much sense as an example, but trust me - it represents a real world scenario that does, also - note the structure we've used to get both messages is the one BizTalk uses to get multiple message parts into a map)

Now - what we want to get in the end is this:


<itemTotals>
<itemType>
<type>book</type>
<total>35.2</total>
</itemType>
<itemType>
<type>cd</type>
<total>146.34</total>
</itemType>
</itemTotals>



As far as I can tell, getting from #1 to #2 in one go is not possible (but I'd love to hear otherwise), so our only conclusion was that we need to go through two stages in processing - in the first one we would de-normalise the two messages to one flattened xml, and in the second we will get the distinct types and sums.

So, to tackle the first stage we wrote a simple xsl that creates the interm xml we wanted - the output looks like this:


<itemTotals>
<item type="book" total="10.5" />
<item type="book" total="24.70" />
<item type="cd" total="56.20" />
<item type="cd" total="90.14" />
</itemTotals>



than, we put this xsl script inside a variable declaration; our xsl now looks like this:


<xsl:template match="/">
<xsl:variable name="items">
<xsl:for-each select="/*[local-name()='Root' and namespace-uri()='http://schemas.microsoft.com/BizTalk/2003/aggschema']/*[local-name()='InputMessagePart_0' and namespace-uri()='']/*[local-name()='items' and namespace-uri()='']/*[local-name()='item' and namespace-uri()='']">
<xsl:element name="item">
<xsl:attribute name="type">
<xsl:value-of select="@type"/>
</xsl:attribute>
<xsl:variable name="barcode" select="@barcode"/>
<xsl:attribute name="total">
<xsl:value-of select="/*[local-name()='Root' and namespace-uri()='http://schemas.microsoft.com/BizTalk/2003/aggschema']/*[local-name()='InputMessagePart_1' and namespace-uri()='']/*[local-name()='prices' and namespace-uri()='']/*[local-name()='price' and namespace-uri()='' and child::*/child::*=$barcode]/@price"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:variable>
</xsl:template>


(sorry for the long xpaths, we've had to make them "BizTalk friendly"...)

That gives us the interm xml we need to work on and is one of two "magic" bits to get this working - we did not realise up until now that we could put whole chunk of xml into a variable, and that we could use xsl to create that xml in a variable.

Having the xml in a variable, we hoped, would allow us to run another set of xsl on it before outputing it as the script's result. but there was one additional maginc point missing - as the variable was not retrieved in the normal <variable name="myVar" select="someXPath"/> it's contents is not considered a node-set but just a literal that looks like a node-set and as such we could not use this variable in further xpaths in the script.

Thankfully, Microsoft has provided a function to convert one to the other, so we had the next line of xsl to our script:


<xsl:variable name="itemsNodeSet" select="msxsl:node-set($items)"/>


we had to add the namespace declaratino for msxsl in the stylesheeet declaraion -

xmlns:msxsl="urn:schemas-microsoft-com:xslt"


Now that we have the interm xml in a variable, and is considered a node-set we could simply run the last bit of xsl we need to get the totals:

(the for each uses another nice technique we use in xsl to get a distinct list of items in a list)


<xsl:for-each select="$itemsNodeSet/*[not(@type=preceding-sibling::*/@type)]">
<itemType>
<type>
<xsl:value-of select="@type"/>
</type>
<total>
<xsl:variable name="type" select="@type"/>
<xsl:value-of select="sum($itemsNodeSet/item[@type=$type]/@total)"/>
</total>
</itemType>
</xsl:for-each>


and voila! - the output is exactly what we wanted!

Labels: ,

2 Comments:

  • Hello Everyone,

    I understand what is being accomplished here in this blog entry but I am unable to pass and xml node as a node-set variable using msxsl:node-set($PayeePosition)

    THIS work in IE but not in BizTalk Mapper xslt but Yossi has done this in a BizTalk Mapper and would like to know how?

    Can someone help out.

    I have tested a xslt using internet explorer and have tried to deploy
    the xslt in a custom functoid without success.

    Error btm1050: XSL transform error: Unable to write output instance
    to the following file.xml. Function 'msxsl:node-set
    ()' has failed. Cannot convert the operand to 'Result tree fragment'.

    This works in Internet Explorer but not BizTalk Mapper (Why?)

    By Anonymous Angelo Laris, at 05/06/2008, 03:37  

  • Hi Angelo

    Sorry for getting to your comment just now, I hope you will still read this comment -

    While I cannot say I have used this technique hundreds of times, in the few times that I have I did not encounter the problem you are describing (or any other problem for that matter).

    If you have not solved your problem yet drop me an email with your scenario, if you can, and I'll try and have a look.

    If you have solved your problem already, and are reading this - would you be so kind as to post your findings?

    By Blogger Yossi Dahan, at 08/06/2008, 12:38  

Post a Comment

<< Home