BizTalk 2010 Conditional Mapping Issue (from two different recurring source nodes to same destination node) - xslt

I have an EDI 810 file, from which I have to conditionally map certain values from two different repeating SAC nodes, which occur multiple times in different spots in the document. Please note that the SAC_2 occurs at a lower level when compared to the SAC_3 node. A sample fragment of the source document looks like this:
<ns1:IT1Loop1>
<ns1:SLNLoop1>
<ns1:SAC_2>
<SAC01>C</ID>
<SAC05>3443</Name>
<SAC15>Service A</ID>
</ns1:SAC_2>
</ns1:SLNLoop1>
<ns1:SLNLoop1>
<ns1:SAC_2>
<SAC01>C</ID>
<SAC05>120</Name>
<SAC15>Service B</ID>
</ns1:SAC_2>
</ns1:SLNLoop1>
<ns1:SLNLoop1>
<ns1:SAC_2>
<SAC01>A</ID>
<SAC05>243</Name>
<SAC15>Service D</ID>
</ns1:SAC_2>
</ns1:SLNLoop1>
</ns1:IT1Loop1>
<ns1:IT1Loop1>
<ns1:SLNLoop1>
<ns1:SAC_2>
<SAC01>A</ID>
<SAC05>567</Name>
<SAC15>Service C</ID>
</ns1:SAC_2>
</ns1:SLNLoop1>
<ns1:SLNLoop1>
<ns1:SAC_2>
<SAC01>F</ID>
<SAC05>4558</Name>
<SAC15>Service M</ID>
</ns1:SAC_2>
</ns1:SLNLoop1>
</ns1:IT1Loop1>
<ns1:SACLoop2>
<ns1:SAC_3>
<SAC01>A</ID>
<SAC05>-1234</Name>
<SAC15>Adjustment</ID>
</ns1:SAC_3>
</ns1:SACLoop2>
<ns1:SACLoop2>
<ns1:SAC_3>
<SAC01>D</ID>
<SAC05>24567</Name>
<SAC15>Balance Forward</ID>
</ns1:SAC_3>
</ns1:SACLoop2>
Here are the conditions:
From the SAC_2, I need to map the values of the SAC05 (to Amount) and SAC15 (to Description) elements, IF SAC_2/SAC01 has the values "C" or "A".
From the SAC_3, I need to map the values of the SAC05 (to Amount) and SAC15 (to Description) elements, IF SAC_3/SAC01 has the values "C" or "A" AND the SAC15 != "Balance Forward".
So it is supposed to generate as many "MeasureItems" as there are any of these segments with criteria fulfilled.
Here is what the output should look like for the sample input:
<Root>
<HeaderItems>
...
</HeaderItems>
<MeasureItems>
<Description>Service A</Description>
<Amount>3443</Amount>
</MeasureItems>
<MeasureItems>
<Description>Service B</Description>
<Amount>120</Amount>
</MeasureItems>
<MeasureItems>
<Description>Service D</Description>
<Amount>243</Amount>
</MeasureItems>
<MeasureItems>
<Description>Service C</Description>
<Amount>567</Amount>
</MeasureItems>
<MeasureItems>
<Description>Adjustment</Description>
<Amount>-1234</Amount>
</MeasureItems>
<ReadItems>
...
</ReadItems>
</Root>
There is no way to do this easily through only functoids alone so I have tried a combination of the EqualTo, NotEqualTo, LogicalOR, ValueMapping functoids along with the scripting functoid (inline C#) to choose between inputs (if conditions held true), but nothing gave me the correct output.
Image:
With this arrangement (shown in image) functoid I would always get everything mapped correctly from the SAC_2 repetitions but it would completely ignore the SAC_3 elements.
And with inline XSLT it would always map only from the first occurrence of SAC_2 segment, from each recurring IT1Loop1 parent. And, of course, it would completely ignore the SAC_3 elements again.
Here is one version of the inline XSLT code I used:
<xsl:element name = "Description">
<xsl:choose>
<xsl:when test=".//*[local-name()='SAC_2']/SAC01 = 'C' or .//*[local-name()='SAC_2']/SAC01 = 'A'">
<xsl:value-of select = ".//*[local-name()='SAC_2']/SAC15[preceding-sibling::SAC01='C' or preceding-sibling::SAC01='A']"/>
</xsl:when>
<xsl:when test=".//*[local-name()='SAC_3']/SAC01 = 'C' and not(.//*[local-name()='SAC_3']/SAC15 = 'Balance Forward')">
<xsl:value-of select = ".//*[local-name()='SAC_3']/SAC15[preceding-sibling::SAC01='C' or preceding-sibling::SAC01='A']"/>
</xsl:when>
</xsl:choose>
</xsl:element>
I'm guessing switch statements and loops don't work the same way in XSLT as they do in other languages. Also, I tried the same logic through inline C# alone too. It did not yield correct results.
I'm quite sure there should be a way to do this using inline XSLT or some other custom code solution.
Additionally, I don't understand why the SAC_3 elements keep getting ignored.
Could someone please help me with this?

You should be able to do this with a looping functoid and a couple logical functoids. Connect the ITLoop1 and the SACLoop2 as inputs to the looping functoid, and have it output to your destination repeating node (MeasurementItems). Then like SAC05 and SAC15 to the correct destinations. Finally, add some Equals functoids to the map, with SAC1 as the input, 'A' as the second parameter, and output to the MeasurementItems. Add the same functoid for both SAC1 elements, and another set that uses C as the second input. See the documentation for more information.
If you want to do this in XSLT, it will have to generate the entire MeasurementItems node, which may or may not be more difficult than its worth (or might be worth jus tdoing the whole map in XSLT). Your template would look something like this:
<xsl:template match="//ns1:SAC_2[SAC01='A' or SAC01='C'] | //ns1:SAC_3[(SAC01 = 'A' or SAC01= 'C') and SAC15 != 'Balance Forward']" xmlns:ns1='REPLACE_WITH_REAL_NAMESPACE'>
<Amount>
<xsl:value-of select="SAC05" />
</Amount>
<Description>
<xsl:value-of select="SAC15" />
</Description>
</xsl:template>

Related

XSLT: Can a node be a variable and used elsewhere?

xsl
<xsl:variable name="varName>
<xsl:value-of select="/can/be/a/long/path/down/xml/item[#id=1] />
</xsl:variable>
xml
<xml>
<item id="1" text="Yes">
<item id="2" text="No">
</xml>
use
I was thinking I could use like this:
<xsl:when test="$varName/#text = 'Yes'">
blah
</xsl:when>
but blank space is generated in place of variable. Is this even possible, have a node as a variable and use elsewhere?
<xsl:variable name="varName">
<xsl:value-of select="/can/be/a/long/path/down/xml/item[#id=1]" />
</xsl:variable>
This is one of the most common XSLT errors I see. Usually what people intended is:
<xsl:variable name="varName" select="/can/be/a/long/path/down/xml/item[#id=1]"/>
And most of the time, the code works just fine, except that it's a lot slower than it needs to be. But sometimes the fact that the two constructs are quite different beneath the covers comes back to bite you.
To understand the difference, xsl:variable with a select attribute binds the variable to whatever the select expression evaluates to, which in this case is a set of zero or more item elements. By contrast, xsl:variable with nested instructions creates a document node (XSLT 2.0) or result tree fragment (XSLT 1.0) whose content is a COPY of whatever those instructions produce. In this case, because the content is an xsl:value-of instruction, the variable contains a copy of the string-value of the selected node.
And of course, the string value of the selected node doesn't have any attributes, so test="$varname/#text = 'x'" will always return false.

Manage flags in xslt?

All,
i am searching in a list of fields those who has the type clob and i am writing it separed by a comma like this [field1, field2, ... fieldn]
my problem is how to identify the first matched field to write it without comma ( i can't use position() because the first field matched can be the first of the list or the last of the list)
I want to make this algorithm in xslt,
variable is_first = TRUE;
if(is_first) {
do smthng;
isfirst = False;
}
Actually it is not possible to make something like this in xslt since variable are immutable. There probably could be workarounds but you have to specify your need in more details.
edit:
If your input is string with values separated by commas...
<xsl:variable name="inputString" select="'field1,field2,field3a,field4,field3b'" />
... you could use tokenize() functions...
<xsl:variable name="tokenized" select="tokenize($inputString, ',')" />
... and then select items corresponding to your condition
<!-- Select item corresponding to condition (e.g. it contains 3). Take first one if there are several such items -->
<xsl:value-of select="$tokenized[contains(., '3')][1]" />
Edit2:
You can use separator attribute of xsl:value-of (xslt 2.0) for output of delimited values.
Assuming following variable
<xsl:variable name="list">
<item>first</item>
<item>second</item>
<item>third</item>
</xsl:variable>
this <xsl:value-of select="$list/item" separator="," /> makes desired output first,second,third
You need to write this using functional code rather than procedural code. It's not possible to do the conversion without seeing the context (it's much easier to work from the problem rather than from the solution in a lower-level language).
But the most common equivalent in XSLT would take the form
<xsl:for-each select=".....">
<xsl:if test="position() = 1"><!-- first time code --></xsl:if>
....
</xsl:for-each>

XSLT to call secondary XML based on first XML element/attribute

love the stuff - newbie Æthelred here
I have a XSLT 1.0 file pulling in a secondary XML (to a variable) to build a table
<xsl:variable name="table_values" select="document('./table_variants/external_table.xml')/xml/channel_1"/>
I then get the values i need from the variable, eg:
<xsl:value-of select="$table_values/monkey/tennis/#medals"/>
<xsl:value-of select="$table_values/monkey/tennis/#bananas"/>
What i want to do is have the first XML trigger/steer where to look for the table data.
I hoped i could, within the triggered XML, state the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/>
but apparently i cannot create a xpath on the fly like that
Please - What can i do?
What i want to do is have the first XML trigger/steer where to look
for the table data. I hoped i could, within the triggered XML, state
the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/> but
apparently i cannot create a xpath on the fly like that
Please - What can i do?
This can easily be done just extending the code that you already have.
Change this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')/xml/channel_1"/>
to this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')
/xml/*[name() = $channelName"/>
Needless to say, the variable (or global, external param) $channelName should have a value that is the (string) name of the element you want to use in the last location step of the XPath expression.

Break the XSLT for-each loop when first match is found

I am having trouble to display the first matching value, like
<test>
<p>30</p>
<p>30{1{{23{45<p>
<p>23{34</p>
<p>30{1{98</p>
</test>
<test2>
<p1>text</p1>
</test2>
So i want to loop through the <test></test> and find the value of <p> node whose string length is greater than 2 and that contains 30. I want only the first value.
so i tired the following code
<xsl:variable name="var_test">
<xsl:for-each select="*/*/test()>
<xsl:if string-length(p/text())>2 and contains(p/text(),'30'))
<xsl:value-of select="xpath">
</xsl:variable>
the problem is the var_test is being null always.
if i try directly with out any variable
<xsl:for-each select="*/*/test()>
<xsl:if string-length(p/text())>2 and contains(p/text(),'30'))
<xsl:value-of select="xpath">
I am getting the following output
<p>30{1{23{4530{1{98</p>
but the desired output is
<p>0{1{23{45</p>
so how can i achieve this?
Instead of the for-each, use
<xsl:copy-of select="(*/*/test/p[string-length() > 2 and
contains(.,'30'))] )[1]" />
The [1] selects only the first matching <p>. (Updated: I changed the XPath above in response to #markusk's comment.)
The above will output that <p> element as well as its text content, as shown in your "desired output". If you actually want only the value of the <p>, that is, its text content, use <xsl:value-of> instead of <xsl:copy-of>.
Addendum:
The idea of breaking out of a loop does not apply to XSLT, because it is not a procedural language. In a <xsl:for-each> loop, the "first" instantiation (speaking in terms of document order, or sorted order) of the loop is not necessarily evaluated at a time chronologically before the "last" instantiation. They may be evaluated in any order, or in parallel, because they do not depend on each other. So trying to "break out of the loop", which is intended to cause "subsequent" instantiations of the loop not to be evaluated, cannot work: if it did, the outcome of later instantiations would be dependent on earlier instantiations, and parallel evaluation would be ruled out.

XSL unique value key

Goal
(XSLT 1.0). My goal is to take a set of elements, S, and produce another set, T, where T contains the unique elements in S. And to do so as efficiently as possible. (Note: I don't have to create a variable containing the set, or anything like that. I just need to loop over the elements that are unique).
Example Input and Key
<!-- My actual input consists of a bunch of <Result> elements -->
<AllMyResults>
<Result>
<someElement>value</state>
<otherElement>value 2</state>
<subject>Get unique subjects!</state>
</Result>
</AllMyResults>
<xsl:key name="SubjectKey" match="AllMyResults/Result" use="subject"/>
I think the above works, but when I go to use my key, it is incredibly slow. Below is the code for how I use my key.
<xsl:for-each select="Result[count(. | key('SubjectKey', subject)[1]) = 1]">
<xsl:sort select="subject" />
<!-- Do something with the unique subject value -->
<xsl:value-of select="subject" />
</xsl:for-each>
Additional Info
I believe I am doing this wrong because it slowed down my XSL considerably. As some additional info, the code shown above is in a separate XSL file from my main XSL file. From the main XSL, I am calling a template that contains the xsl:key and the for-each shown above. The input to this template is an xsl:param containing my node-set (similar to the example input shown above).
I can't see any reason from the information given why the code should be slow. It might be worth seeing if the slowness is something that happens on all XSLT processors, or if it's peculiar to one.
Try substituting
count(. | key('SubjectKey', subject)[1]) = 1
with:
generate-id() = generate-id(key('SubjectKey', subject)[1])
In some XSLT processors the latter is much faster.