Grouping of variables in XSLT 2.0 - xslt

For each <LineDetail> node that has the same value of <CtryCd> and populates 1 <LineDetail> with the summation of <Amt>.
But, I need to check also the <SI> and <TI> node. The output tag of the summation will be based from the <SI> and <TI>
node. The <Amt1> will generate if the value of <SI> and <TI> are both false, while <Amt2> will generate if the value of <SI> is true and <TI> is false.
I created an XSLT and already got the summation. But there's still something missing in my output.
This is a sample XML:
<Record>
<Data>
<Process>
<Header>
<ID>22-BBB</ID>
<Date>2017-02-14</Date>
<ContactName>Abegail</ContactName>
<!-- some other elements -->
</Header>
<Detail>
<ID>22-CCC</ID>
<RequestedDate>2017-02-14</RequestedDate>
<!-- some other elements -->
<LineDetail>
<CtryCd>AF</CtryCd>
<SI>false</SI>
<TI>false</TI>
<Amt>11.11</Amt>
</LineDetail>
<LineDetail>
<CtryCd>SE</CtryCd>
<SI>true</SI>
<TI>false</TI>
<Amt>22.22</Amt>
</LineDetail>
<LineDetail>
<CtryCd>AF</CtryCd>
<SI>false</SI>
<TI>false</TI>
<Amt>33.33</Amt>
</LineDetail>
<LineDetail>
<CtryCd>AF</CtryCd>
<SI>true</SI>
<TI>false</TI>
<Amt>55.55</Amt>
</LineDetail>
</Detail>
</Process>
</Data>
</Record>
Generated output:
<Record>
<Data>
<Process>
<Header>
<ID>22-BBB</ID>
<Date>2017-02-14</Date>
<ContactName>Abegail</ContactName>
<!-- some other elements -->
</Header>
<Detail>
<LineDetail>
<CtryCd>AF</CtryCd>
<Amt1>99.99</Amt1>
</LineDetail>
</Detail>
<Detail>
<LineDetail>
<C<Amt2>22.22</Amt2>
</LineDetail>
</Detail>
</Process>
</Data>
</Record>
Expected output:
<Record>
<Data>
<Process>
<Header>
<ID>22-BBB</ID>
<Date>2017-02-14</Date>
<ContactName>Abegail</ContactName>
<!-- some other elements -->
</Header>
<Detail>
<ID>22-CCC</ID>
<RequestedDate>2017-02-14</RequestedDate>
<!-- some other elements -->
<LineDetail>
<CtryCd>AF</CtryCd>
<Amt1>44.44</<Amt1>
<Amt2>55.55</Amt2>
</LineDetail>
<LineDetail>
<CtryCd>SE</CtryCd>
<Amt1>0</<Amt1>
<Amt2>22.22</Amt2>
</LineDetail>
</Detail>
</Process>
</Data>
</Record>
XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="CtryCd" match="LineDetail" use="CtryCd"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Detail">
<xsl:for-each-group select="LineDetail" group-by="CtryCd">
<Detail>
<LineDetail>
<CtryCd>
<xsl:value-of select="CtryCd"/>
</CtryCd>
<xsl:if test="lower-case(SI)='false' and lower-case(TI)='false'">
<Amt1>
<xsl:value-of select="sum(current-group()/Amt)"/>
</Amt1>
<xsl:if test="lower-case(SI)='true' and lower-case(TI)='false'">
<Amt2>
<xsl:value-of select="sum(current-group()/Amt)"/>
</Amt2>
</xsl:if>
</xsl:if>
<xsl:if test="lower-case(SI)='true' and lower-case(TI)='false'">
<Amt2>
<xsl:value-of select="sum(current-group()/Amt)"/>
</Amt2>
</xsl:if>
</LineDetail>
</Detail>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

You current if condition will only check the first item in the group. You really need it to be part of the actual sum statement, as a condition on each node on the group.
<Amt1>
<xsl:value-of select="sum(current-group()[lower-case(SI)='false' and lower-case(TI)='false']/Amt)"/>
</Amt1>
You would then have a similar statement for the Amt2, testing for "true" and "false"
Try this XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="CtryCd" match="LineDetail" use="CtryCd"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Detail">
<xsl:for-each-group select="LineDetail" group-by="CtryCd">
<Detail>
<LineDetail>
<CtryCd>
<xsl:value-of select="CtryCd"/>
</CtryCd>
<Amt1>
<xsl:value-of select="sum(current-group()[lower-case(SI)='false' and lower-case(TI)='false']/Amt)"/>
</Amt1>
<Amt2>
<xsl:value-of select="sum(current-group()[lower-case(SI)='true' and lower-case(TI)='false']/Amt)"/>
</Amt2>
</LineDetail>
</Detail>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

Related

XSLT 1.0 Help for Multi part Message (BizTalk)

Having a requirement where i have aggregated message and populate and loop for each message in output , below is what i have but need help where to start with.
Input Message :
<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema">
<InputMessagePart_0>
<ns0:Root xmlns:ns0="http://TestXSLT1._0.Inpu1">
<Header>
<SeqNo>1</SeqNo>
<FileName>Test</FileName>
</Header>
<Detail>
<ItemName>Item1</ItemName>
<Quantity>1</Quantity>
</Detail>
<Detail>
<ItemName>Item2</ItemName>
<Quantity>2</Quantity>
</Detail>
</ns0:Root>
</InputMessagePart_0>
<InputMessagePart_1>
<ns0:Root xmlns:ns0="http://TestXSLT1._0.Input2">
<FileName>Test</FileName>
<Header>
<DestinationLocation>Miami</DestinationLocation>
<DestinationName>State</DestinationName>
<Detail>
<ItemName>Item1</ItemName>
<Rate>100</Rate>
</Detail>
<Detail>
<ItemName>Item2</ItemName>
<Rate>200</Rate>
</Detail>
</Header>
</ns0:Root>
</InputMessagePart_1>
</ns0:Root>
Desired OutPut :
<ns0:Root xmlns:ns0="http://TestXSLT1._0.Output">
<SeqNo>1</SeqNo>
<FileName>Test</FileName>
<DestinationLocation>Miami</DestinationLocation>
<DestinationName>State</DestinationName>
<Detail>
<ItemName>Item1</ItemName>
<Quantity>1</Quantity>
</Detail>
<Detail>
<ItemName>Item2</ItemName>
<Quantity>2</Quantity>
</Detail>
<Detail>
<ItemName>Item1</ItemName>
<Rate>100</Rate>
</Detail>
<Detail>
<ItemName>Item2</ItemName>
<Rate>200</Rate>
</Detail>
</ns0:Root>
XSLT I Have started :
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s2 s0 s1" version="1.0" xmlns:ns0="http://TestXSLT1._0.Output" xmlns:s2="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:s0="http://TestXSLT1._0.Input2" xmlns:s1="http://TestXSLT1._0.Inpu1">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/s2:Root" />
</xsl:template>
<xsl:template match="/s2:Root">
<ns0:Root>
<SeqNo>
<xsl:value-of select="InputMessagePart_0/s1:Root/Header/SeqNo/text()" />
</SeqNo>
<FileName>
<xsl:value-of select="InputMessagePart_0/s1:Root/Header/FileName/text()" />
</FileName>
<DestinationLocation>
<xsl:value-of select="InputMessagePart_1/s0:Root/Header/DestinationLocation/text()" />
</DestinationLocation>
<DestinationName>
<xsl:value-of select="InputMessagePart_1/s0:Root/Header/DestinationName/text()" />
</DestinationName>
<xsl:for-each select="InputMessagePart_0/s1:Root/Detail">
<Detail>
<ItemName>
<xsl:value-of select="ItemName/text()" />
</ItemName>
<Quantity>
<xsl:value-of select="Quantity/text()" />
</Quantity>
<Rate>
<xsl:value-of select="../../../InputMessagePart_1/s0:Root/Header/Detail/Rate/text()" />
</Rate>
</Detail>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
The XSLT I have made doesn't give me the desired output.
This should be done in XSLT 1.0
Appreciate for the help ~
The output you show can be produced using:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/*">
<ns0:Root xmlns:ns0="http://TestXSLT1._0.Output">
<!-- header -->
<SeqNo>
<xsl:value-of select="InputMessagePart_0/*/Header/SeqNo" />
</SeqNo>
<FileName>
<xsl:value-of select="InputMessagePart_0/*/Header/FileName" />
</FileName>
<DestinationLocation>
<xsl:value-of select="InputMessagePart_1/*/Header/DestinationLocation" />
</DestinationLocation>
<DestinationName>
<xsl:value-of select="InputMessagePart_1/*/Header/DestinationName" />
</DestinationName>
<!-- details part 0 -->
<xsl:for-each select="InputMessagePart_0/*/Detail">
<Detail>
<ItemName>
<xsl:value-of select="ItemName" />
</ItemName>
<Quantity>
<xsl:value-of select="Quantity" />
</Quantity>
</Detail>
</xsl:for-each>
<!-- details part 1 -->
<xsl:for-each select="InputMessagePart_1/*/Header/Detail">
<Detail>
<ItemName>
<xsl:value-of select="ItemName" />
</ItemName>
<Rate>
<xsl:value-of select="Rate" />
</Rate>
</Detail>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>

Best way to use for-each inside if in XSLT

I have the below XML data as input to my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<Application>
<Data>
<Data1>
<name>Michale</name>
<age>65</age>
<Info>
<Alias name="M">
<Contactmail>abc#gmail.com</Contactmail>
<ContactPh>8988900009</ContactPh>
</Alias>
<Alias name="Q">
<Contactmail>abc#gmail.com</Contactmail>
<ContactPh>8988900009</ContactPh>
</Alias>
</Info>
</Data1>
<Data1>
<name>Albert</name>
<age>69</age>
<Info>
<Alias name="A">
<Contactmail>xyz#gmail.com</Contactmail>
<ContactPh>89889908709</ContactPh>
</Alias>
<Alias name="P">
<Contactmail>pqr#gmail.com</Contactmail>
<ContactPh>8988988779</ContactPh>
</Alias>
</Info>
</Data1>
</Data>
</Application>
And I want to pass the Data1 block whose Alias name matches with "M", i.e.:
<Application>
<Data>
<Data1>
<name>Michale</name>
<age>65</age>
<Info>
<Alias name=M>
<Contactmail>abc#gmail.com</Contactmail>
<ContactPh>8988900009</ContactPh>
</Alias>
<Alias name=Q>
<Contactmail>abc#gmail.com</Contactmail>
<ContactPh>8988900009</ContactPh>
</Alias>
</Info>
</Data1>
</Data>
</Application>
I am stuck as to how to access an loop(ie Alias) inside a test condition?
Is there any better way to do this xslt?
<xsl:for-each select="./*[local-name() = 'Application']/*[local-name() = 'Data']">
<xsl:if test="">
....
</xsl:if>
</xsl:for-each>
The following template will do the job. The explanations are in the code.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" /> <!-- Removes unnecessary space between elements -->
<!-- identity template --> <!-- Copies all nodes not matched by other templates -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Data1[Info/Alias/#name != 'M']" /> <!-- Ignores all Data1 elements which don't have an #name='M' attribute child -->
<xsl:template match="Data1[Info/Alias/#name = 'M']"> <!-- Matches all Data1 elements which have the desired child attribute -->
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Its output is:
<?xml version="1.0"?>
<Application>
<Data>
<Data1>
<name>Michale</name>
<age>65</age>
<Info>
<Alias name="M">
<Contactmail>abc#gmail.com</Contactmail>
<ContactPh>8988900009</ContactPh>
</Alias>
<Alias name="Q">
<Contactmail>abc#gmail.com</Contactmail>
<ContactPh>8988900009</ContactPh>
</Alias>
</Info>
</Data1>
</Data>
</Application>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Application">
<xsl:copy>
<xsl:for-each select="Data/Data1">
<xsl:if test="Info/Alias[#name='M']">
<Data>
<Data1>
<xsl:apply-templates/>
</Data1>
</Data>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
You may also do like this

Insert list of elements inside XML tags

I am trying to obtain a list of all the elements with values that aren't in the (Line 1, Line2), and then insert them into the tags similar to the test.
Right now I can retrieve all the elements, but I'm having trouble restricting this to just my desired values. And then I'm unsure how to match and do a for each on elements outside my match criteria. Any advice would be greatly appreciated!
Given the Following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>test</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
Current XSL for getting elements
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:for-each select="node()[text() != '']">
<xsl:value-of select="local-name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
My WIP xml for inserting the result xml tags is below. I'm unsure how to insert the results of the above xsl into this,
<xsl:template match="Element">
<xsl:copy-of select="."/>
<Element>Value1</Element>
</xsl:template>
And ultimate desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>Identifier</Element>
<Element>Gender</Element>
<Element>Title</Element>
<Element>Name</Element>
<Element>AddressType</Element>
<Element>Line1</Element>
<Element>Suburb</Element>
<Element>State</Element>
<Element>PostCode</Element>
<Element>Country</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
I would change the current template to use mode attribute, so it is only used in specific cases, rather than matching all elements. You should also change it to output elements, not text, like so:
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
Then you can call it like this....
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
Try this XSLT. Note the use of the identity template to copy all other existing elements unchanged
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 transformation

I'm trying to transform a XML file with XSLT 1.0 but I'm having troubles with this.
Input:
<task_order>
<Q>
<record id="1">
<column name="task_externalId">SPLIT4_0</column>
</record>
<record id="2">
<column name="task_externalId">SPLIT4_1</column>
</record>
</Q>
<task>
<id>SPLIT4</id>
<name>test</name>
</task>
</task_order>
Wanted result:
For each task_order element: When there is more than 1 record-element (SPLIT4 and SPLIT4_1) I need to duplicate the task element and change the orginal task-id with the id from record elements.
<task_order>
<Q>
<record id="1">
<column name="task_externalId">SPLIT4_0</column>
</record>
<record id="2">
<column name="task_externalId">SPLIT4_1</column>
</record>
</Q>
<task>
<id>SPLIT4_0</id>
<name>test</name>
</task>
<task>
<id>SPLIT4_1</id>
<name>test</name>
</task>
</task_order>
Any suggestions?
Thank you
First start off with the Identity Template which will handle copying across all existing nodes
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Next, to make looking up the columns slightly easier, consider using an xsl:key
<xsl:key name="column" match="column" use="substring-before(., '_')" />
Then, you have a template matching task where you can look up all matching column elements using the key, and create a new task element for each
<xsl:template match="task">
<xsl:variable name="task" select="." />
<xsl:for-each select="key('column', id)">
<!-- Create new task -->
</xsl:for-each>
</xsl:template>
Try this XSTL
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="column" match="column" use="substring-before(., '_')" />
<xsl:template match="task">
<xsl:variable name="task" select="." />
<xsl:for-each select="key('column', id)">
<task>
<id><xsl:value-of select="." /></id>
<xsl:apply-templates select="$task/*[not(self::id)]" />
</task>
</xsl:for-each>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSL Transformation to split a single node into two nodes in the same level

I have a source XML
<Cars>
<Car>
<Make>Fiat</Make>
<Colors>
<Color>RED</Color>
<Color>BLUE</Color>
</Colors>
</Car>
<Car>
<Make>Volvo</Make>
<Colors>
<Color>RED</Color>
<Color>WHITE</Color>
</Colors>
</Car>
<Car>
<Make>Renault</Make>
<Colors>
<Color>BLUE</Color>
<Color>BLACK</Color>
</Colors>
</Car>
</Cars>
which I want to transform into something like
<Cars>
<Detail>
<Name>MakeName</Name>
<Entry>Fiat</Entry>
<Entry>Volvo</Entry>
<Entry>Renault</Entry>
</Detail>
<Detail>
<Name>AvailableColors</Name>
<Entry>RED</Entry>
<Entry>BLUE</Entry>
<Entry>WHITE</Entry>
<Entry>BLACK</Entry>
</Detail>
<Cars>
I am new to XSL, and created one to do half of processing, but I am stuck with the getting of colors as separate elements in target
<xsl:template match="/">
<Cars>
<xsl:apply-templates />
</Cars>
</xsl:template>
<xsl:template match="Cars">
<xsl:apply-templates select="Car" />
</xsl:template>
<xsl:template match="Car">
<Detail>
<Name>MakeName</Name>
<xsl:apply-templates select="Make" />
</Detail>
</xsl:template>
<xsl:template match="Make">
<Entry><xsl:value-of select"text()"/></Entry>
</xsl:template>
I am not able to create XSL for <Name>AvailableColors</Name>, I am quite new to XSL and any help is greatly appreciated
Here is an XSLT 1.0 stylesheet that shows how to eliminate duplicate colors using Muenchian grouping:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="k1" match="Car/Colors/Color" use="."/>
<xsl:template match="Cars">
<xsl:copy>
<Detail>
<Name>MakeName</Name>
<xsl:apply-templates select="Car/Make"/>
</Detail>
<Detail>
<Name>AvailableColors</Name>
<xsl:apply-templates select="Car/Colors/Color[generate-id() = generate-id(key('k1', .)[1])]"/>
</Detail>
</xsl:copy>
</xsl:template>
<xsl:template match="Car/Make | Colors/Color">
<Entry>
<xsl:value-of select="."/>
</Entry>
</xsl:template>
</xsl:stylesheet>
See a generic "shredding" solution given in this answer:
https://stackoverflow.com/a/8597577/36305