Yossi Dahan [BizTalk]

Google
 

Sunday, September 07, 2008

Where did it go?

You've created your map, selected your schemas and (if you're anything like me) set the custom xsl path to the file containing the xsl script you've worked hard to create.

You then go to the orchestration and add a transform shape, but when you select the messages the designer notices you have selected different types to those in the map and kindly offers to change the types in the map for you; you think - how generous and kind...

However, it goes one step further, and without much notice, it is re-setting the custom xsl path (to nothing, that is), which would result in an empty output from the map when you run the process and one very frustrated BizTalk developer.

To be fair it does suggest that "some links may be lost" before doing all of that and ask for confirmation, and of course you can see their ("they" being Microsoft) side of the story - they assume that if you change types in the map the links/xsl may no longer be relevant...but I would argue that if I chose to use the same map, then they probably are, otherwise I'd start a new one....

Labels: , , ,

Thursday, December 27, 2007

Mapper vs. XSLT round 2


I've received a good question today -

"we had a little debate in the office today - what is faster - running a map with pure xsl or the standard way with functoids, what you think?"

As I've
blogged before - I'm a big supporter of writing custom XSL and not using the Mapper and Functoids in anything other than the simplest of maps; so - although performance is only one of my arguments - the answer should be obvious.

Nevertheless I'll take the chance to answer properly again, although I suspect the question is not accurate enough -

At runtime there's no difference between the two; the Mapper generates XSL (which you can see by "validating" the map in visual studio and following the link to the XSL file generated which would appear in the output window, so the question should be, in my view, whether the Mapper can generate as good XSL as a developer could, but as you can imagine the answer really depends on a particular scenario - how many functoids are you using? how are they working together? what's the size of the map? what's its complexity?

Anyway, in my view there is a bottom line answer to that question and that is that under most real-world scenarios custom written XSL will almost always be better than generated one, but I'll try to explain a little bit more -

When you're using Functoids in your map you're generally doing one of two things - you're either calling external assemblies or you're adding some XSL lines to perform some actions for you.

The former one is easier to tackle - if you need external assemblies you can call them from custom XSL as well (as I've explained
here ); as the Mapper will do exactly the same, the performance impact will generally be identical in both cases (using mapper or custom XSL).

The latter is harder to tackle, as there's no one-rule-fits-all statement one can make - but here's a shot at it -

The Mapper is a visual, generic, designer that generates code.
As is always the case with these tools they come with a price, and that price is often the quality of the code generated; now - don't get me wrong - I don't argue that the Mapper is bad, or that it always generates bad, slow XSL; but if you know XSL well, there's no doubt you will write better code than a generator will.

When you're adding a Functoid that does not call an external assembly you'll be doing one of three things -

  • You will be adding an embedded c# code - most Functoids do this, look at the string manipulations as a simple example.

  • You will be adding a template based on input nodes - the Looping Functoid for example.

  • Or - You will be adding XLS structures or functions - the record count or value mapping Functoids for example


  • All three are perfectly fine, and even more so - if you'll try them out you'll see that the designer does generate quite a nice XLS in all cases.

    The problem starts when, and this is inevitable in the real-world, the maps get more complex.

    Once you move out of the playing ground and into real scenarios, the maps get more complicated and the inefficiency of the generated code becomes both more apparent (as multiple Functoids need to work together to achieve the desired output the XSL gets 'uglier and uglier') and that inefficiency becomes a greater problem as it is repeated many times over a large-ish map.

    Bottom line is from my perspective - if you feel comfortable with XSL (and the rest of the team) - you will always achieve better scripts than any generator would so use it. If you don't feel comfortable with XSL - learn it! It's easy! (and in the mean time use the mapper).

    Labels: , , ,

    Wednesday, August 29, 2007

    <xsl:message>

    How could I not know about this? I guess I simply did not expect it to exist, but now that I know it does I can easily justify it, especially as it is so useful!

    And just in case anyone else missed it -

    <xsl:message>some text here</xsl:message> will output "some text here" to the console, which is nice but not so useful.

    <xsl:message terminate="yes">some text here</xsl:message>, on the other hand, will raise an expcetion with "some text here" as the message, which, unless handled, will cause the orchestration to get suspended and "some text here" written to the event log as part of the error, something like this:

    Uncaught exception (see the 'inner exception' below) has suspended an instance of service '********************'.
    The service instance will remain suspended until administratively resumed or terminated.
    If resumed the instance will continue from its last persisted state and may re-throw the same unexpected exception.
    InstanceId: 4c40dfad-d915-4691-a01e-d2df4393acb1
    Shape name: Construct Response set
    ShapeId: 5ed349ae-4a14-479f-8f84-6eaebd955de0
    Exception thrown from: segment 5, progress 37
    Inner exception: Transform terminated: 'some text here.'.

    Labels: , ,

    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: ,

    Tuesday, May 29, 2007

    Passing xml between xsl and a helper method

    I'm using a lot of custom xsl. in fact - most of my transformations are custom xsl files, skipping the mapper altogether.

    In that, I'm also quite frequently using helper classes in assemblies called directly from the xsl, on which I have posted before.

    Mostly I'm passing simple values between the two such as strings and ints, but every now and then I need to pass a whole xml struture between them - it might be a node-set from the xsl that needs to be processed by the helper method, it might be that the method returns an xml fragment the xsl needs to then iterate on or, most likely, it will be both.

    The way to do it is quite simply to use the XPathNodeIterator class in the System.Xml.XPath namespace.

    I did not know about this class until I had to do this bit, so it has to be worth posting (for myself if not for anyone else).

    To demonstrate it's use I've create a helper method as follwos -

    public XPathNodeIterator processXml(XPathNodeIterator nodeset)

    {

    nodeset.MoveNext();

    XPathNavigator nav = nodeset.Current;

    XPathNodeIterator i = nav.Select("//*");

    return i;

    }

    As you can see this method doesn't really do much, but it already demonstrates how to receive an xml node-set and how to return one.

    the helper method can work with the following xsl -

    <xsl:variable name="MyNodeSet" select="<some xpath>"/>

    <xsl:variable name="HelperResult" select="helpers:processXml($MyNodeSet)"/>

    <xsl:value-of select="$HelperResult/<some other xpath>"/>


    The next challange was how to create a brand new XPathNodeIterator, here's one way (assuming xmlDoc is an XmlDocument that contains the xml you need to return -

    XPathNavigator xPathNav;

    xPathNav = xmlDoc.CreateNavigator();

    xPathNav.MoveToRoot();

    XPathExpression xPathExpr = xPathNav.Compile("");

    XPathNodeIterator xPathNodI = xPathNav.Select(xPathExpr);

    Labels: ,