xslt dynamic xpath Saxon EE - xslt

I have following xml:
...
<peci:Address peci:isDeleted="1">
<peci:Usage_Type>WORK</peci:Usage_Type>
<peci:Address_Line_1>AMEC</peci:Address_Line_1>
<peci:Address_Line_2>2020 Winston Park Drive, Suite
700</peci:Address_Line_2>
<peci:City>Oakville</peci:City>
<peci:Postal_Code>L6H 6X7</peci:Postal_Code>
<peci:Country>CA</peci:Country>
<peci:Country_Region>ON</peci:Country_Region>
<peci:State_Province>Ontario</peci:State_Province>
</peci:Address>
<peci:Address peci:isAdded="1">
<peci:Usage_Type>WORK</peci:Usage_Type>
<peci:Address_Line_1>639 5th Avenue SW</peci:Address_Line_1>
<peci:Address_Line_2>Suite 1410</peci:Address_Line_2>
<peci:City>Calgary</peci:City>
<peci:Postal_Code>T2P 0M9</peci:Postal_Code>
<peci:Country>CA</peci:Country>
<peci:Country_Region>AB</peci:Country_Region>
<peci:State_Province>Alberta</peci:State_Province>
<peci:Address>
<peci:Usage_Type>HOME</peci:Usage_Type>
<peci:Address_Line_1>88 Bridge Cres.</peci:Address_Line_1>
<peci:City>Burlington</peci:City>
<peci:Postal_Code>L7L 0A3</peci:Postal_Code>
<peci:Country>CA</peci:Country>
<peci:Country_Region>ON</peci:Country_Region>
<peci:State_Province>Ontario</peci:State_Province>
</peci:Address>
<peci:Email/>
...
I iterate through the document and when find added node
peci:isAdded="1"
I check whether presiding node has the same element and has been deleted
peci:isDeleted="1"
if that is the case I would like to iterate through child elements and list all elements that are different (here everything but country)
I would like to use that for different nodes so I cannot use hard coded list of elements and when iterate through added element I dont know how to access the corresponding element in the proceeding sibling. It try sth like:
<xsl:when
test="../#peci:isAdded and ../preceding-sibling::*[../local-name()]/#peci:isDeleted">
<xsl:variable name="oldValueLocations" select="local-name()"/>
<xsl:value-of select="../preceding-sibling::*[../local-name()][#peci:isDeleted]/$oldValueLocations"/>
</xsl:when>
I would expect
<out:Row xtt:separator="," xtt:quotes="always">
<out:PayGroupCode>93JH</out:PayGroupCode>
<out:LegacyID>93JH000660265</out:LegacyID>
<out:WorkdayID>660265</out:WorkdayID>
<out:Name>SixWFNLastname SixWFNFirstname</out:Name>
<out:Status>Active</out:Status>
<out:Transaction>CHANGE</out:Transaction>
<out:Process_Type>DTA</out:Process_Type>
<out:Type>Address</out:Type>
<out:SubType>Country_Region</out:SubType>
<out:Effective_Date>2017-03-06</out:Effective_Date>
<out:Old_Value>ON</out:Old_Value>
<out:New_Value>AB</out:New_Value>
</out:Row>
but instead I get
<out:Row xtt:separator="," xtt:quotes="always">
<out:PayGroupCode>93JH</out:PayGroupCode>
<out:LegacyID>93JH000660265</out:LegacyID>
<out:WorkdayID>660265</out:WorkdayID>
<out:Name>SixWFNLastname SixWFNFirstname</out:Name>
<out:Status>Active</out:Status>
<out:Transaction>CHANGE</out:Transaction>
<out:Process_Type>DTA</out:Process_Type>
<out:Type>Address</out:Type>
<out:SubType>Country_Region</out:SubType>
<out:Effective_Date>2017-03-06</out:Effective_Date>
<out:Old_Value>WORK AMEC 2020 Winston Park Drive, Suite 700 Oakville L6H 6X7 CA ON
Ontario</out:Old_Value>
<out:New_Value>AB</out:New_Value>
</out:Row>

If you know about local-name() and predicate it seems selecting *[local-name() = $oldValueLocations] seems obvious to select elements of that name, probably with your other path prefixed (although I don't understand what the [../local-name()] is supposed to do).

Related

XSLT 2.0 using key with except returns unexpected result

NB: title changed to reflect the problem better.
My xml documents contain an element <tei:seg #type #xml:id #corresp> which wrap little 'stories'. The attribute #corresp allows me to connect these stories to a master story. For example, these seg are all connected by their #corresp:
doc1.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc1-05']
doc2.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc2-06']
doc6.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc6-03']
My objective is: when the XSLT template finds a #corresp, find other seg in other documents with the same #corresp and output their respective `#xml:id``
So, in the above example, if the current seg was #xml:id='doc1-05', the template outputs a list: Corresponds to doc2-06, doc6-03
Until I can solve the current problems with XSLT collection() in eXist-DB, I'm falling back on my previous solution: a 'TEI corpus' xml document which maintains a master list of all related tei-xml documents via xi:include. This way I provide a single document node whereby the processor can access and search all the xml documents.
So, I declare the corpus document:
<xsl:variable name="corpus" select="doc('ms609_corpus.xml')"/>
Then create a key for the #corresp:
<xsl:key name="correspkey" match="//tei:seg[#type='dep_event' and #corresp]" use="#corresp"/>
Then I use the key with the doc() to search:
<xsl:when test="tei:seg[#type='dep_event' and #corresp]">
<xsl:variable name="correspvar"
select="data(self::seg[#type='dep_event' and #corresp]/#corresp)"/>
<xsl:text>Corresponds to </xsl:text>
<xsl:value-of select="data($corpus/(key('correspkey',$correspvar) except $correspvar)/#xml:id)" separator=", "/>
</xsl:when>
It returns the results, but the except should exclude the current #corresp. Yet it is included in the results.
The except operator works on sequences of nodes based on node identity, see https://www.w3.org/TR/xpath20/#combining_seq defining
The except operator takes two node sequences as operands and returns a
sequence containing all the nodes that occur in the first operand but
not in the second operand ... All these operators eliminate duplicate
nodes from their result sequences based on node identity
Based on that I think you simply want
<xsl:value-of select="$corpus/(key('correspkey', current()/#corresp) except current())/#xml:id)" separator=", "/>
Using data on nodes which atomizes nodes to values and then trying to use except which works on nodes doesn't seem to make sense to me.

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

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>

Using <xsl:for-each> for incremental element names

I have an XML that is converted from a java map. So all the map keys are converted into node names. The XML structure is as below
<map>
<firstName>AAA</firstName>
<firstName1>BBB</firstName1>
<firstName2>CCC</firstName2>
<firstName3>DDD</firstName3>
</map>
I am trying to write a for-each loop to extract data from this XML to create an output XML. I have tried most of the options available such as name(), local-name(), contains(), etc but couldn't come up with something that worked. What are the options available since the incremental node name can go upto count 100 or more. Any inputs in coding the loop would be of great help. I am using XSLT 1.0.
There are many ways to select the children of the top element (map):
/*/*
This selects all elements that are children of the top element of the XML document.
/*/*[starts-with(name(), 'firstName')]
This selects all top element's children-elements, whose name starts with the string 'firstName'.
/*/*[starts-with(name(), 'firstName')
and floor(substring-after(name(), 'firstName')) = substring-after(name(), 'firstName')) ]
This selects all top element's children-elements, whose name starts with the string 'firstName' and the remaining substring after this is an integer.
/*/*[starts-with(name(), 'firstName')
and translate(name(), '0123456789', '') = 'firstName')) ]
This selects all top element's children-elements, whose name starts with the string 'firstName' and the remaining substring after this contains only digits.
Finally, in XPath 2.0 (XSLT 2.0) one can use regular expressions:
/*/*[matches(name(), '^firstName\d+$')]
This will select all the first level elements and their information, which you can then use as you wish:
<xsl:for-each select="/*/*">
<xsl:value-of select="local-name()"/>
<xsl:value-of select="."/>
</xsl:for-each>

XSL 1.0 - sort a list from a different list

i'm new with XSL and have tried to look through all the examples on here but none match my problem.
i have a sort order list of movies (order from left to right)
<movies>movieF,movieC,movieG</movies>
now i want to take that sort order list and sort on top of this huge movies list of mine
<moviesList>
<movie>movieA</movie>
<movie>movieB</movie>
<movie>movieC</movie>
<movie>movieD</movie>
<movie>movieE</movie>
<movie>movieF</movie>
<movie>movieG</movie>
<movie>movieH</movie>
</moviesList>
result i want:
<moviesList>
<movie>movieF</movie>
<movie>movieC</movie>
<movie>movieG</movie>
<movie>movieA</movie>
<movie>movieB</movie>
<movie>movieD</movie>
<movie>movieE</movie>
<movie>movieH</movie>
</moviesList>
would someone please give me some guidance of how to achieve such thing. i've tried to create a variable $sortlist, and then add delimited character around and then use substring-before trick on the sort. result is my sorted list did show up on top before the rest of the movies but it's not on the right order. please help.
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="sortlist" select="';movieF;movieC;movieG;'"/>
<xsl:template match="moviesList">
<xsl:copy>
<xsl:for-each select="*[contains($sortlist, concat(';',.,';'))]">
<xsl:sort select="substring-before($sortlist,concat(';',.,';'))" />
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:copy-of select="*[not(contains($sortlist, concat(';',.,';')))]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Notice, I didn't use string-length to sort, I just used the string itself. XSLT1.0 can sort alphabetically instead of numerically, which would place 14 above 7, resulting in the order movieF, movieG, movieC.
It actually uses the portion of the list of movie names that comes before each one as the sort key; alphabetically speaking, ;movieF; comes before ;movieF;movieC;, therefore it'll place movieC above movieG when sorting.
Personally I tend to avoid using commas as a separator as they can be used in some names, such as 'Crouching Tiger, Hidden Dragon'. A semicolon's far less likely, but you could use any character you like as long as it's not one that appears in a movie name.

xsl return the dynamic node value

hello:
do you guys know how to display the nodes' value which the nodes name are dynamic, for example, the nodes name is like x1, x2, x3... the number 1, 2 ,3 depends on the returns of the table.
i can get the node name using the loop, but only can get the name, even xsl:value-of select="$nodename", returns the nodename, not the value
As #Dimitre said, you haven't given us much specific information to work with, but in general you can use this to select elements whose names are determined at run time:
<xsl:value-of select="*[local-name() = $someDynamicValue]" />
You can also use name(), but local-name() ignores the namespace prefix, which usually makes things easier.
If you'd like more detailed help, please provide your sample input XML (especially the "returns of the table"), and the XSLT you've tried so far; and preferably, a sample of desired output XML.