XSLT group by name and sum values - xslt

INPUT
<root>
<TL>
<msg>
<output_getquerydata>
<queries>
<query name="q1">
<parameters>
<parameter name="id">906OREA</parameter>
</parameters>
<queryErrors/>
<queryResults>
<record>
<column name="actionState">sdss</column>
</record>
</queryResults>
</query>
<query name="q2">
<parameters>
<parameter name="resCode">CTL-3819</parameter>
<parameter name="prodCode">89CMID</parameter>
</parameters>
<queryErrors/>
<queryResults>
<record id="1">
<column name="resource_externalId">CTL-000002</column>
<column name="compartmentCode">CTL-3819-01</column>
<column name="ExternalProductId"/>
</record>
<record id="2">
<column name="resource_externalId">CTL-000002</column>
<column name="compartmentCode">CTL-3819-02</column>
<column name="ExternalProductId"/>
</record>
<record id="3">
<column name="resource_externalId"/>
<column name="compartmentCode"/>
<column name="position"/>
<column name="ExternalProductId">316442</column>
</record>
</queryResults>
</query>
<query name="q2">
<parameters>
<parameter name="resCode">CTL-3819</parameter>
<parameter name="prodCode">91VPRM</parameter>
</parameters>
<queryErrors/>
<queryResults>
<record id="1">
<column name="resource_externalId">CTL-000002</column>
<column name="compartmentCode">CTL-3819-01</column>
<column name="position">1</column>
<column name="ExternalProductId"/>
</record>
<record id="2">
<column name="resource_externalId"/>
<column name="compartmentCode"/>
<column name="position"/>
<column name="ExternalProductId">316495</column>
</record>
</queryResults>
</query>
</queries>
</output_getquerydata>
</msg>
<TL>
<id>65004</id>
<ArticleNr>89CMID</ArticleNr>
<Gross>2700</Gross>
</TL>
<TL>
<id>65005</id>
<ArticleNr>89CMID</ArticleNr>
<Gross>1700</Gross>
<BOLNumber>18117</BOLNumber>
</TL>
<TL>
<id>65006</id>
<ArticleNr>89CMID</ArticleNr>
<Gross>2100</Gross>
<BOLNumber>18117</BOLNumber>
</TL>
<TL>
<id>65007</id>
<ArticleNr>91VPRM</ArticleNr>
<Gross>500</Gross>
<BOLNumber>18117</BOLNumber>
</TL>
</TL>
</root>
XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output encoding="ISO-8859-1" indent="yes" method="xml"/>
<xsl:key name="Article" match="TL" use="./ArticleNr"/>
<xsl:key name="prod" match="query[#name='q2']/queryResults/record" use="../../parameters/parameter[#name='prodCode']"/>
<xsl:template match="root">
<msglist>
<xsl:for-each select="./TL[./msg/output_getquerydata/queries/query/queryResults/record/column[#name='actionState'] !='finished'] ">
<xsl:variable name="distinctArticle" select="//TL[string(ArticleNr)][count(. | key('Article',ArticleNr)[1]) = 1]"/>
<msg>
<CMT>
<m>
<a>
<AP>
<xsl:variable name="position">
<!--variable to concatenate all products -->
<xsl:for-each select="$distinctArticle">
<xsl:variable name="pos" select="concat(key('prod', .//ArticleNr)/column[#name='ExternalProductId'][. != ''],';')"/>
<xsl:value-of select="$pos"/>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="TL[generate-id() =
generate-id(key('Article', .//ArticleNr)[1])]">
<grossVolume>
<xsl:value-of select="sum(concat(.,';'))"/>
</grossVolume>
</xsl:for-each>
<product>
<xsl:choose>
<xsl:when test="substring($position, string-length($position))=';'">
<xsl:value-of select="substring($position, 1, string-length($position)-1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$position"/>
</xsl:otherwise>
</xsl:choose>
</product>
</AP>
</a>
</m>
</CMT>
</msg>
</xsl:for-each>
</msglist>
</xsl:template>
</xsl:stylesheet>
The output is almost good. The thing is I need in the grossVolume node to have the sum of all "Gross" nodes for the same ArticleNr.
SO, my output would be:
<grossVolume>6500;500</grossVolume>
<product>316442;316495</product>
Because for ArticleNr - 89CMID -, we have the sum values in gross 2700+1700+2100=6500.
Basically, for all "ArticleNr" nodes that have the same values, I need to sum all the numbers from their respective "Gross" nodes, and then produce an output where we concatenate these sums.
For the Product node output, I concatenate the required values, and it works, I need to have that similar information in the grossVolume node also.
Thank you!

You cannot do a sum() and concat() in the same instruction as it would throw an error for string and number combination. The sum will have to be stored in a local variable and then used to concatenate with other sums to form the value of <grossVolume>.
Please change the for-each loop for TL
<xsl:for-each select="TL[generate-id() = generate-id(key('Article', .//ArticleNr)[1])]">
<grossVolume>
<xsl:value-of select="sum(concat(.,';'))"/>
</grossVolume>
</xsl:for-each>
to the below snippet
<grossVolume>
<xsl:for-each select="TL[generate-id() = generate-id(key('Article', ./ArticleNr)[1])]">
<xsl:variable name="sumGross" select="sum(key('Article', ./ArticleNr)/Gross)" />
<xsl:value-of select="$sumGross" />
<xsl:if test="position() != last()">
<xsl:value-of select="';'" />
</xsl:if>
</xsl:for-each>
</grossVolume>
Output
<grossVolume>6500;500</grossVolume>

Related

XSLT: Check if string contains any value from another group of nodes

I have this XML:
<table tableName="Groups">
<item table="Groups">
<column columnName="GroupID">GROUP1</column>
<column columnName="GroupID">GROUP2</column>
</item>
</table>
<table tableName="Products">
<item table="Products">
<column columnName="ProductID">1</column>
<column columnName="ProductName">Product A</column>
<column columnName="Groups">"GROUP1","GROUP2"</column>
</item>
<item table="EcomProducts">
<column columnName="ProductID">2</column>
<column columnName="ProductName">Product B</column>
<column columnName="Groups">"GROUP3","GROUP4"</column>
</item>
<item table="EcomProducts">
<column columnName="ProductID">3</column>
<column columnName="ProductName">Product C</column>
<column columnName="Groups">"GROUP1","GROUP3"</column>
</item>
</table>
I'm trying to filter out products that has any groups that are present in the Groups-table.
I would like to end up with this.
<products>
<product>
<name>Product A</name>
<groups>"GROUP1","GROUP2"</groups>
</product>
<product>
<name>Product C</name>
<groups>"GROUP1","GROUP3"</groups>
</product>
</products>
So far I have this XSLT, but as I'm pretty new to XSLT, I have no idea how to make that test-statement more dynamic:
<products>
<xsl:for-each select="table[#tableName='Products']/item">
<xsl:if test="contains(column[#columnName = 'Groups'] = 'GROUP1' and contains(column[#columnName = 'Groups'] = 'GROUP2'">
<product>
<name><xsl:value-of select="column[#columnName = 'ProductName']"/></name>
<groups><xsl:value-of select="column[#columnName = 'Groups']"/></groups>
</product>
</xsl:if>
</xsl:for-each>
</products>
Try it this way:
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"/>
<xsl:template match="/root">
<xsl:variable name="group-ids" select="table[#tableName='Groups']/item/column[#columnName='GroupID']" />
<products>
<xsl:for-each select="table[#tableName='Products']/item">
<xsl:if test="$group-ids[contains(current()/column[#columnName='Groups'], concat('"', ., '"'))]">
<product>
<name>
<xsl:value-of select="column[#columnName='ProductName']"/>
</name>
<groups>
<xsl:value-of select="column[#columnName='Groups']"/>
</groups>
</product>
</xsl:if>
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/jxNakB6
However, if your processor happens to support the EXSLT str:tokenize() extension function, you could do something a bit simpler:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="exsl str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="grp-id" match="table[#tableName='Groups']/item/column[#columnName='GroupID']" use="concat('"', ., '"')" />
<xsl:template match="/root">
<products>
<xsl:for-each select="table[#tableName='Products']/item[key('grp-id', str:tokenize(column[#columnName='Groups'], ','))]">
<product>
<name>
<xsl:value-of select="column[#columnName='ProductName']"/>
</name>
<groups>
<xsl:value-of select="column[#columnName='Groups']"/>
</groups>
</product>
</xsl:for-each>
</products>
</xsl:template>
</xsl:stylesheet>
XSLT 1.0 solution, using keys:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kContainsGroup" match="item[not(#table='Groups')]"
use="boolean(
/*/table[#tableName='Groups']/item/column
[contains(current()/column[#columnName='Groups'],
concat('"', ., '"'))]
)"/>
<xsl:template match="/">
<products>
<xsl:apply-templates select="key('kContainsGroup', 'true')"/>
</products>
</xsl:template>
<xsl:template match="item">
<product>
<name><xsl:value-of select="column[#columnName='ProductName']"/></name>
<groups><xsl:value-of select="column[#columnName='Groups']"/></groups>
</product>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML (fragment, made a well-formed XML document by enclosing it within a single top element <tables>):
<tables>
<table tableName="Groups">
<item table="Groups">
<column columnName="GroupID">GROUP1</column>
<column columnName="GroupID">GROUP2</column>
</item>
</table>
<table tableName="Products">
<item table="Products">
<column columnName="ProductID">1</column>
<column columnName="ProductName">Product A</column>
<column columnName="Groups">"GROUP1","GROUP2"</column>
</item>
<item table="EcomProducts">
<column columnName="ProductID">2</column>
<column columnName="ProductName">Product B</column>
<column columnName="Groups">"GROUP3","GROUP4"</column>
</item>
<item table="EcomProducts">
<column columnName="ProductID">3</column>
<column columnName="ProductName">Product C</column>
<column columnName="Groups">"GROUP1","GROUP3"</column>
</item>
</table>
</tables>
the wanted, correct result is produced:
<products>
<product>
<name>Product A</name>
<groups>"GROUP1","GROUP2"</groups>
</product>
<product>
<name>Product C</name>
<groups>"GROUP1","GROUP3"</groups>
</product>
</products>
Do note:
No XSLT conditional instruction (such as <xsl:if>) has been used.
No <xsl:for-each> instruction has been used
The solution is implemented in almost pure "push-style"

Read xml file to capture the values from <value> tags

We have a requirement to read the xml file and capture the EmployeeName and EmailId values from tags to create the output as xml file.
The first tag always represents EmployeeName and 5th tag always represents EmailId.
Need to capture the values present in the row/value....
The input xml file as follows:
<?xml version="1.0" encoding="utf-8"?>
<dataset xmlns="http://developer.net.com/schemas/xmldata/1/" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance">
<!--
<dataset
xmlns="http://developer.net.com/schemas/xmldata/1/"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://developer.net.com/schemas/xmldata/1/ xmldata.xsd"
>
-->
<metadata>
<item length="20" type="xs:string" name="EmployeeName"/>
<item length="4" type="xs:string" name="Full/Part Time Code"/>
<item type="xs:dateTime" name="Hire Date"/>
<item type="xs:dateTime" name="Termination Date"/>
<item length="30" type="xs:string" name="EmailID"/>
<item length="30" type="xs:string" name="State"/>
</metadata>
<data>
<row>
<value>JOSEPH</value>
<value>F</value>
<value>1979-04-19T00:00:00</value>
<value>2007-08-27T00:00:00</value>
<value>joseph.Tim#gmail.com</value>
<value>TX</value>
</row>
<row>
<value>NANDY</value>
<value>F</value>
<value>1979-04-19T00:00:00</value>
<value>2007-08-27T00:00:00</value>
<value>Nandy123#gmailcom</value>
<value>PA</value>
</row>
</data>
</dataset>
The Expected Ouput as below:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:EMPLOYEEDETAILS xmlns:ns0="http://net.com/EmployeeDetails">
<Records>
<EmployeeName>JOSEPH</EmployeeName>
<EmailId>joseph.Tim#gmail.com</EmailId>
</Records>
<Records>
<EmployeeName>NANDY</EmployeeName>
<EmailId>Nandy123#gmailcom</EmailId>
</Records>
</ns0:EMPLOYEEDETAILS>
Thanks,
Ravi
Please try the XSLT below. You need to make additional changes for matching namespaces according to the input XML and adding the root node <EMPLOYEEDETAILS> in the output.
EDIT: XSLT solution updated to handle the namespace issue. Root node <ns0:EMPLOYEEDETAILS> included in the solution.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://developer.net.com/schemas/xmldata/1/">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:param name="param-name" select="1" />
<xsl:param name="param-email" select="5" />
<xsl:template match="/">
<ns0:EMPLOYEEDETAILS>
<xsl:for-each select="//ns0:data/ns0:row">
<Records>
<xsl:for-each select="ns0:value">
<xsl:choose>
<xsl:when test="position() = $param-name">
<EmployeeName>
<xsl:value-of select="." />
</EmployeeName>
</xsl:when>
<xsl:when test="position() = $param-email">
<EmailId>
<xsl:value-of select="." />
</EmailId>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</Records>
</xsl:for-each>
</ns0:EMPLOYEEDETAILS>
</xsl:template>
</xsl:stylesheet>
Output
<ns0:EMPLOYEEDETAILS xmlns:ns0="http://developer.net.com/schemas/xmldata/1/">
<Records>
<EmployeeName>JOSEPH</EmployeeName>
<EmailId>joseph.Tim#gmail.com</EmailId>
</Records>
<Records>
<EmployeeName>NANDY</EmployeeName>
<EmailId>Nandy123#gmailcom</EmailId>
</Records>
</ns0:EMPLOYEEDETAILS>

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>

Position of node with attribute

How can i select the position of a node with a specific attribute in XSL.
XML:
<document type="async" page="tabTabel">
<tabs>
<table>
<row type="header">
<column type="text">href</column>
<column type="number">Mapnr.</column>
<column type="mapposition">Nr.</column>
<column type="text">Description</column>
<column type="text">Document</column>
<column type="date">Date</column>
</row>
<row type="data">
<column><![CDATA[]]></column>
<column><![CDATA[10]]></column>
<column><![CDATA[17]]></column>
<column><![CDATA[Documentation may 2013 .pdf]]></column>
<column><![CDATA[Documentation may 2013 .pdf]]></column>
<column><![CDATA[03-04-2014]]></column>
</row>
</table>
</tabs>
</document>
Current not-working XSLT:
<xsl:template match="tabs//row[#type='data']>
<xsl:variable name="mapnumber">
<xsl:value-of select="../row[#type='header']/column[#type='mapposition'][position()]" />
</xsl:variable>
</xsl:template>
I want the index number/position of the column with type 'mapposition'. How can I do this?
Try:
<xsl:variable name="mapnumber" select="count(../row[#type='header']/column[#type='mapposition']/preceding-sibling::column) + 1" />
In view of your edit, you'd probably want to do something like:
<xsl:template match="table">
<xsl:variable name="mapnumber" select="count(row[#type='header']/column[#type='mapposition']/preceding-sibling::column) + 1" />
<xsl:value-of select="row[#type='data']/column[$mapnumber]" />
</xsl:template>
This example might help you:
If you've the following XML
<row type='header'>
<column type='text'>a</column>
<column type='mapposition'>b</column>
<column type='number'>c</column>
</row>
To get the position of column with #type = 'mapposition', you can use this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="row[#type='header']">
<xsl:apply-templates select="column"/>
</xsl:template>
<xsl:template match="column">
<xsl:if test="#type='mapposition'"><xsl:value-of select="position()"/></xsl:if>
</xsl:template>
</xsl:stylesheet>

XSLT Sorting with first letter of family name

I am trying to modify the following xsl so that the output matches the desired output shown below. I am trying to sort the records alphabetically by the first name of the family Initial. However at the moment it only sorts by the creators initial I need it to include the editors initial where the record does not have a creators element.
XML:
<records>
<record>
<creators>
<item>
<name>
<family>Smith</family>
<given>Tim</given>
</name>
</item>
</creators>
</record>
<record>
<creators>
<item>
<name>
<family>Lambert</family>
<given>John</given>
</name>
</item>
</creators>
<editors>
<item>
<name>
<family>testEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
<record>
<editors>
<item>
<name>
<family>ZambertEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
XSL:
<xsl:key name="initial" match="record" use="substring(creators/item/name/family,1,1)"/>
<xsl:template match="/">
<xsl:for-each select="//record[generate-id(.)= generate-id(key('initial', substring(creators/item/name/family,1,1))[1])]">
<xsl:sort select="substring(creators/item/name/family,1,1)"/>
<xsl:for-each select="key('initial', substring(creators/item/name/family,1,1))">
<xsl:if test="position() = 1">
<br /><h3 class="border">
<xsl:value-of select="substring(creators/item/name/family,1,1)"/>
</h3>
</xsl:if>
<p>
<xsl:value-of select="creators/item/name/family"/>
</p>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
Desired output:
L
Lambert
S
Smith
Z
ZambertEDITOR
This is a simple grouping problem:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kWith1stLetter" match="family" use="substring(.,1,1)"/>
<xsl:template match="/*">
<xsl:apply-templates select=
"*/*/*/*/family
[generate-id()
=
generate-id(key('kWith1stLetter',substring(.,1,1))[1])
]">
<xsl:sort select="substring(.,1,1)" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match= "family">
<h3 class="border">
<xsl:value-of select="substring(.,1,1)"/>
</h3>
<xsl:apply-templates mode="inGroup"
select="key('kWith1stLetter',substring(.,1,1))"/>
</xsl:template>
<xsl:template match="family" mode="inGroup">
<p><xsl:value-of select="."/></p>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<records>
<record>
<creators>
<item>
<name>
<family>Smith</family>
<given>Tim</given>
</name>
</item>
</creators>
</record>
<record>
<creators>
<item>
<name>
<family>Lambert</family>
<given>John</given>
</name>
</item>
</creators>
<editors>
<item>
<name>
<family>testEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
<record>
<editors>
<item>
<name>
<family>ZambertEDITOR</family>
<given>Bob</given>
</name>
</item>
</editors>
</record>
</records>
the wanted, correct result is produced:
<h3 class="border">L</h3>
<p>Lambert</p>
<h3 class="border">S</h3>
<p>Smith</p>
<h3 class="border">t</h3>
<p>testEDITOR</p>
<h3 class="border">Z</h3>
<p>ZambertEDITOR</p>
and it is displayed by the browser as:
L
Lambert
S
Smith
t
testEDITOR
Z
ZambertEDITOR
Explanation:
Proper use of the Muenchian Grouping Method.