XSLT 1.0 advanced calculations - xslt

I have been banging my head against a wall on this for a while. Our application processes a complex structure XML invoice from another system. The invoice contains rows of information which contain various counts. These counts may or may not contain a value. There is an overall document charge. We need to work out the unit charge. The formula would be the total cost divided by the total of the counts.
I have been working through the examples kindly provided by others regarding summing in XSLT1.0. I can use xsl:call-template to get the sum of the counts, but I don't know how to apply the result to the calculate the unit price.
Sample XML
<Document>
<Row>
<Count1>
<Value>10</Value>
</Count1>
<Count2>
<Value/>
</Count2>
</Row>
<Row>
<Count1>
<Value>5</Value>
</Count1>
<Count2>
<Value>6</Value>
</Count2>
</Row>
<Row>
<Count1>
<Value>2</Value>
</Count1>
<Count2>
<Value>3</Value>
</Count2>
</Row>
<Charge>
<Value>260</Value>
</Charge>
</Document>
If I could see how to get the following XML output that would probably show me what I need.
<Document>
<Row>
<Total>10</Total>
<UnitPrice>10</UnitPrice>
</Row>
<Row>
<Total>11</Total>
<UnitPrice>10</UnitPrice>
</Row>
<Row>
<Total>15</Total>
<UnitPrice>10</UnitPrice>
</Row>
</Document>
Many thanks in advance

You just need to call sum() on the required Values, like so:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/Document">
<Document>
<xsl:apply-templates select="Row"/>
</Document>
</xsl:template>
<xsl:template match="Row">
<Row>
<Total>
<xsl:value-of select="sum(./*[Value>0]/Value)"/>
</Total>
<UnitPrice>10</UnitPrice>
</Row>
</xsl:template>
</xsl:stylesheet>
This gives the output:
<Document>
<Row>
<Total>10</Total>
<UnitPrice>10</UnitPrice>
</Row>
<Row>
<Total>11</Total>
<UnitPrice>10</UnitPrice>
</Row>
<Row>
<Total>5</Total>
<UnitPrice>10</UnitPrice>
</Row>
</Document>

Related

How to perform pagination of table for following case using xslt?

enter image description here
I want to paginate this table using xslt such that the last two p elements, and first two p elements should be together.
My input xml is somewhat like this:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<table>
<tgroup>
<row>
<entry>Parameter</entry>
<entry>Description</entry>
</row>
<row>
<entry>A</entry>
<entry><p>A1</p>
<p>A2</p>
<p>A3</p>
<p>A4</p>
<p>A5</p>
</entry>
</row>
<row>
<entry>B</entry>
<entry><p>B1</p>
<p>B2</p>
<p>B3</p>
<p>B4</p>
<p>B5</p>
</entry>
</row>
</tgroup>
</table>
And I want to automate adding outputclass attributes to p elements for pagination as shown here:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<table>
<tgroup>
<row>
<entry>Parameter</entry>
<entry>Description</entry>
</row>
<row>
<entry>A</entry>
<entry><p>A1</p>
<p>A2 outputclass="keep-with-previous"</p>
<p>A3</p>
<p>A4 outputclass="keep-with-next"</p>
<p>A5</p>
</entry>
</row>
<row>
<entry>B</entry>
<entry><p>B1</p>
<p>B2 outputclass="keep-with-previous"</p>
<p>B3</p>
<p>B4 outputclass="keep-with-next"</p>
<p>B5</p>
</entry>
</row>
</tgroup>
</table>
My css is as follows:
#media print
{ *[outputclass~="keep-with-next"] { page-break-after: avoid; }
*[outputclass~="keep-with-previous"] { page-break-before: avoid; }
*[outputclass~="top-of-page"] { page-break-before: always; } }
You might need these two templates:
<xsl:template match="row/entry[2]/p[2]">
<p outputclass="keep-with-previous" >
<xsl:apply-templates/>
</p>
</xsl:template>
<xsl:template match="row/entry[2]/p[4]">
<p outputclass="keep-with-next">
<xsl:apply-templates/>
</p>
</xsl:template>

Using sum() to sum up values with nested foreach()

I need help in properly specifying the input parameter for sum(). I encountered sum()'s output to concatenate the values instead of summing up.
My goal is: To sum values of Column6 when Column2 is "invoice"
In the example below, I have 2000 and 1000 for Column6 whose Column is "invoice".
I'm expecting it to display 3000 for <TotalAmount>
<Root>
<DataArea>
<Row>... </Row>
<Row>... </Row>
<!-- and so on -->
<Row>
<Column1>29/03/19</Column1>
<Column2>cr note</Column2>
<Column3>092-213280101</Column3>
<Column4>1,474.98 </Column4>
<Column5>103.25 </Column5>
<Column6>2000 </Column6>
</Row>
<Row>
<Column1>29/03/19</Column1>
<Column2>invoice</Column2>
<Column3>092-213280101</Column3>
<Column4>1,474.98 </Column4>
<Column5>103.25 </Column5>
<Column6>2000 </Column6>
</Row>
<Row>
<Column1>11/06/19</Column1>
<Column2>invoice</Column2>
<Column3>123-123456789</Column3>
<Column4>100.50 </Column4>
<Column5>100.50 </Column5>
<Column6>1000</Column6>
</Row>
</DataArea>
</Root>
<xsl:for-each
select="Root/DataArea/Row">
<xsl:if ... >
<TransactionSummary>
<DebitSummary>
<TotalAmount>
<xsl:attribute name="currencyID"></xsl:attribute>
<xsl:for-each select="../Row">
<xsl:if test="(Column2 = 'invoice') and (position() > 9) ">
<xsl:value-of select="sum(../Column6)" />
</xsl:if>
</xsl:for-each>
</TotalAmount>
</DebitSummary>
</TransactionSummary>
</xsl:if>
</xsl:for-each>
I tried sum(Column6), didn't work. Output: 20001000
I tried sum(../Column6), didn't work. Output: 00
I tried sum(../Row/Column6), didn't work. Output: Cannot convert string "" to a double
I tried sum(Root/DataArea/Row/Column6), didn't work. Output: 00
I'd appreciate any help.
Thank you.
You don't need the inner xsl:for-each here, you can do it with a single xsl:value-of with the conditions in the select
<xsl:value-of select="sum(../Row[Column2 = 'invoice' and position() > 9]/Column6)" />
Or, in the context of your snippet....
<xsl:for-each select="Root/DataArea/Row">
<TransactionSummary>
<DebitSummary>
<TotalAmount currencyID="">
<xsl:value-of select="sum(../Row[Column2 = 'invoice' and position() > 9]/Column6)" />
</TotalAmount>
</DebitSummary>
</TransactionSummary>
</xsl:for-each>
It seems you want something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<TransactionSummary>
<DebitSummary>
<TotalAmount currencyID="someId">
<xsl:value-of select="sum(/*/*/Row[Column2 = 'invoice']/Column6)"/>
</TotalAmount>
</DebitSummary>
</TransactionSummary>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Root>
<DataArea>
<Row>... </Row>
<Row>... </Row>
<!-- and so on -->
<Row>
<Column1>29/03/19</Column1>
<Column2>cr note</Column2>
<Column3>092-213280101</Column3>
<Column4>1,474.98 </Column4>
<Column5>103.25 </Column5>
<Column6>2000 </Column6>
</Row>
<Row>
<Column1>29/03/19</Column1>
<Column2>invoice</Column2>
<Column3>092-213280101</Column3>
<Column4>1,474.98 </Column4>
<Column5>103.25 </Column5>
<Column6>2000 </Column6>
</Row>
<Row>
<Column1>11/06/19</Column1>
<Column2>invoice</Column2>
<Column3>123-123456789</Column3>
<Column4>100.50 </Column4>
<Column5>100.50 </Column5>
<Column6>1000</Column6>
</Row>
</DataArea>
</Root>
the wanted result is produced:
<TransactionSummary>
<DebitSummary>
<TotalAmount currencyID="someId">3000</TotalAmount>
</DebitSummary>
</TransactionSummary>

Compare 2 sets of child nodes to find a match where a least one of the child node values matches one of the other child node values

I have this XML with two sets of table data (element names generalized for simplicity).
<root>
<table>
<Row type="1">
<Id>AAAA</Id>
<Properties>
<Property>A</Property>
<Property>D</Property>
</Properties>
</Row>
<Row type="1">
<Id>BBBB</Id>
<Properties>
<Property>B</Property>
</Properties>
</Row>
<Row type="1">
<Id>CCCC</Id>
<Properties>
<Property>G</Property>
<Property>H</Property>
</Properties>
</Row>
</table>
<table>
<Row type="2">
<Id>123abc</Id>
<Properties>
<Property>A</Property>
<Property>D</Property>
<Property>E</Property>
</Properties>
</Row>
<Row type="2">
<Id>456def</Id>
<Properties>
<Property>B</Property>
<Property>C</Property>
<Property>I</Property>
</Properties>
</Row>
<Row type="2">
<Id>798ghi</Id>
<Properties>
<Property>F</Property>
<Property>G</Property>
<Property>H</Property>
</Properties>
</Row>
</table>
</root>
I am trying to write a transform to output a new table that associates a row from table 1 to a row in table 2 based on their properties. Rows in table 1 are not required to have all of the properties present in a row in table 2; any one property is all that's required to be considered a match. There will always only be a one to one relationship between a row in table 1 and a row in table 2.
My desired output is:
<root>
<Row>
<Name>NewTable:AAAA</Name>
<Table2Id>123abc</Table2Id>
</Row>
<Row>
<Name>NewTable:BBBB</Name>
<Table2Id>456def</Table2Id>>
</Row>
<Row>
<Name>NewTable:CCCC</Name>
<Table2Id>789ghi</Table2Id>
</Row>
</root>
I've started with this and have been trying to follow this logic: Find me the Id tag of the row in table 2 who has at least one property that matches one of the properties of the current row being processed in table 1.
Here's what I have so far. It's not working, but I feel like I'm close.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="//Row[#type != '2']">
<xsl:variable name="Name" select="concat('NewTable:', ./Id)"/>
<Row>
<Name>
<xsl:value-of select="$Name"/>
</Name>
<Table2Id>
<xsl:value-of select="//Row[#type = '2'][Property = ./Property]/Id"/>
</Table2Id>
</Row>
</xsl:template>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
There are two things that can be very useful here: one is the key feature of XSLT that allows you to create a relationship based on matching values. The other is set comparison, where if at least one member of a set matches at least one member of another set, the sets will be considered matching.
Try the following stylesheet that takes advantage of both:
<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:key name="table2" match="Row[#type=2]" use="Properties/Property" />
<xsl:template match="/">
<root>
<xsl:for-each select="root/table/Row[#type=1]">
<row>
<Name>
<xsl:value-of select="Id" />
</Name>
<Table2Id>
<xsl:value-of select="key('table2', Properties/Property)/Id" />
</Table2Id>
</row>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
BTW, your method would have worked too (albeit less efficiently) if only you had used:
[Properties/Property = ./Properties/Property]
instead of just:
[Property = ./Property]
since you are in the context of a <Row> (a grandparent of <Property>).

xslt 2.0 multiple grouping

My Question: How can I apply double (or multiple) grouping?
Here is the source XML:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<root>
<row>
<Type>1</Type>
<WeaNr>100519</WeaNr>
</row>
<row>
<Type>2</Type>
<WeaNr>100519</WeaNr>
<ETADC_SKU>2007925</ETADC_SKU>
<CrossDock>N</CrossDock>
</row>
<row>
<Type>2</Type>
<WeaNr>100519</WeaNr>
<ETADC_SKU>12007925</ETADC_SKU>
<CrossDock>N</CrossDock>
</row>
<row>
<Type>2</Type>
<WeaNr>100519</WeaNr>
<ETADC_SKU>200792ww5</ETADC_SKU>
<CrossDock>Y</CrossDock>
</row>
<row>
<Type>1</Type>
<WeaNr>100520</WeaNr>
</row>
<row>
<Type>2</Type>
<WeaNr>100520</WeaNr>
<ETADC_SKU>2007925444</ETADC_SKU>
<CrossDock>N</CrossDock>
</row>
<row>
<Type>2</Type>
<WeaNr>100520</WeaNr>
<ETADC_SKU>2007925333</ETADC_SKU>
<CrossDock>Y</CrossDock>
</row>
<row>
<Type>2</Type>
<WeaNr>100520</WeaNr>
<ETADC_SKU>204445333</ETADC_SKU>
<CrossDock>Y</CrossDock>
</row>
</root>
I want use grouping by WeaNr and CrossDock
Expected results in this case are 4 groups:
1. WeaNr=100519 and CrossDock=N
2. WeaNr=100519 and CrossDock=Y
3. WeaNr=100520 and CrossDock=N
4. WeaNr=100520 and CrossDock=Y
Grouping just by one field, like WeaNr is easy:
<xsl:for-each-group select="row" group-by="WeaNr">
So how can I apply double (or multiple) grouping?
You would group-by some string that is a combination of the two, for example
<xsl:for-each-group select="row" group-by="concat(WeaNr, '|', CrossDock)">
or alternatively use two nested levels of for-each-group
<xsl:for-each-group select="row" group-by="WeaNr">
<xsl:for-each-group select="current-group()" group-by="CrossDock">
The difference between these two approaches is apparent if you use the position() function in the body of the for-each-group - in the concat case you'll get position values from 1 to 4, in the nested case you'll get 1, 2, 1, 2 (because the position() is determined by the nearest enclosing for-each-group). Similarly, last() will be 4 in the concat case and 2 in the nested case.

XML value to XML node conversion using XSLT?

I am trying to write XSLT file for following input XML to output XML, is it possible XSLT to convert the value of input xml as node in output XML? how can I implement this?
Input XML
<?xml version="1.0" encoding="UTF-8"?>
<Rows>
<Row><xml_data_name/> <xml_data_value/> </Row>
<Row><xml_data_name>persons</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>person</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>username</xml_data_name> <xml_data_value>JS1</xml_data_value> </Row>
<Row><xml_data_name>name</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>name</xml_data_name> <xml_data_value>John</xml_data_value> </Row>
<Row><xml_data_name>name</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>family-name</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>family-name</xml_data_name> <xml_data_value>Smith</xml_data_value> </Row>
<Row><xml_data_name>family-name</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>person</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>person</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>username</xml_data_name> <xml_data_value>MI1</xml_data_value> </Row>
<Row><xml_data_name>name</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>name</xml_data_name> <xml_data_value>Morka</xml_data_value> </Row>
<Row><xml_data_name>name</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>family-name</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>family-name</xml_data_name> <xml_data_value>Ismincius</xml_data_value> </Row>
<Row><xml_data_name>family-name</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>person</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name>persons</xml_data_name> <xml_data_value/> </Row>
<Row><xml_data_name/> <xml_data_value/> </Row>
</Rows>
Output XML
<?xml version="1.0" ?>
<persons>
<person username="JS1">
<name>John</name>
<family-name>Smith</family-name>
</person>
<person username="MI1">
<name>Morka</name>
<family-name>Ismincius</family-name>
</person>
</persons>
You could certainly use xsl:element like
<xsl:template match="Row">
<!-- Note {} brackets in name attribute -->
<xsl:element name="{xml_data_name}">
<xsl:value-of select="xml_data_value" />
</xsl:element>
</xsl:template>
What would be greater problem is a structure of output because it is not easy to decide which rows should be nested, which rows should transform into an attribute rather than element etc.
Well, that's one of the weirdest data formats I've ever seen! Are you sure you can't get whatever produced this to produce something more reasonable?
I think the solution has to be recursion: you want a function that takes a sequence of rows as input; it outputs an element whose name is the name of the first element in the sequence with no data value and whose content is obtained by a recursive call that passes all rows after that first row up to the next row with no data value and the same name, then calls itself to process all rows after that row. Not easy, and certainly takes more time than I allow myself for answering SO questions!