BizTalk Mapper/XSLT - Creating sequence number based on destination tree

by Matt Milner 25. October 2007 10:48

When creating maps in BizTalk server, or generally creating XSLT transforms, you might run into a situation, as I did, that calls for you to generate a sequence number in your destination document.  If the source document and destination have a similar repeating structure, you can always use the Iteration Functoid or the position() function to create a sequence based on the source tree.  I recently ran into a situation where I needed to create a sequence number for the output records, but I was conditionally mapping the input records.  For example, I had a structure like the following for my source:

<Invoices>

  <Invoice>

    <InvoiceDetail>

       <SomeFlag>true</SomeFlag>

    </InvoiceDetail>

    <InvoiceDetail>

       <SomeFlag>false</SomeFlag>

    </InvoiceDetail>

</Invoice>

  <Invoice>

    <InvoiceDetail>

       <SomeFlag>false</SomeFlag>

    </InvoiceDetail>

    <InvoiceDetail>

       <SomeFlag>true</SomeFlag>

    </InvoiceDetail>

</Invoice>

</Invoices>

In my output, I actually had a similar structure, but I was only creating output records for the items where the "someFlag" element was true.  So in each case in the example, I'd only have one detail record for each invoice rather than two.  In the first case, creating a sequence number based on the position() function or Iteration functoid would work just fine.  However, in the second Invoice, my sequence would start with 2 since that is what the position() function would return.  This really gets fun when you have multiple details and your sequence starts skipping positions.  I was getting sequences like 1,4,10,13. 

What I needed was a way to generate the number based on the destination tree, not the source; enter xsl:number.  Using xsl:number you can provide a value and do some formatting, etc., but I was more interested in using it without submitting a value which causes a new number to be generated. If you look at the MSDN documentation for xsl:number you'll see that you can specify more attributes to control how the number gets generated.  Specifically the "count" and "from" attributes allow you to scope the generation of the numbers and where to start, respectively.  These attributes allow you to generate the numbers as you want them in the output.  A lot of the examples I found on the web were for formatting numbering in lists or table of contents formats to number things like 1.1  or 1.A.  The "format" attribute really helps here, but I just wanted a raw number. 

I added a scripting functoid to my map and set the type to inline XSLT.  I then used the xsl:number like so (based on the sample document above). 

<xsl:number count="*[./s0:SomeFlag='true']" />

That got me all I needed. My sequence numbers in my output were only generated when an output detail was created and the sequences were unique to each invoice (i.e. they restarted for each invoice which is what I wanted).  In my case I didn't need to use any other controls on xsl:number but you might find that you need to further restrict it using the "from" attribute.  I'd start simple and then work from there. 

A couple of things to note.  First, the XSLT expression that I have in the "count" attribute points to all elements where the SomeFlag is true.  You may need to play around with this to get the right expression, just make sure you are referencing the right context (based on your XSLT) and filter the records appropriately.  Also notice that I used a namespace prefix on the SomeFlag element.  In the BizTalk mapper I needed to add this since BizTalk uses this prefix for the primary/first namespace of my source document.  Without this prefix, I always got empty output, so watch for this in your own code and use the validation map functionality in BizTalk to view the XSLT and see what prefixes you might need.  If you want to make sure things are working at a base level, then just use * for the "count" attribute and you should get a similar output as if you used the position() function.  Then apply filters from there to get the results you want. 

I couldn't find anything to really help me figure this out without some work, so hopefully this will save someone some time (or maybe you are just better at searching the web than I am). 

Tags:

BizTalk Server