Group by ID and Sum Amount in XSLT - sum

I'm fairly new to XSLT and stuck on a current problem. I've done some searches throughout Stackflow (seems like Muenchian method is the common group method) but I can't seem to mimic some of the posted ideas as of yet.
So I'm using a line item read system of which I'm trying to write code in XSLT to read every line to check if the supplier ID is the same, if true, it will aggregate this into one line, then sum the amounts. If not true, it should start a new line with the ID and sum the amount and so forth. I am using xml version='1.0'
Below is my current data file in XML:
<data>
<row>
<column1>06-11111</column1>
<column2>CP</column2>
<column3>744.04</column3>
<column4>CAD</column4>
</row>
<row>
<column1>06-11111</column1>
<column2>CP</column2>
<column3>105.09</column3>
<column4>CAD</column4>
</row>
<row>
<column1>06-11111</column1>
<column2>CP</column2>
<column3>1366.24</column3>
<column4>CAD</column4>
</row>
<row>
<column1>06-11111</column1>
<column2>CP</column2>
<column3>485.71</column3>
<column4>CAD</column4>
</row>
<row>
<column1>06-11112</column1>
<column2>Ever</column2>
<column3>459.60</column3>
<column4>CAD</column4>
</row>
<row>
<column1>06-11112</column1>
<column2>Ever</column2>
<column3>409.14</column3>
<column4>CAD</column4>
</row>
<row>
<column1>06-11112</column1>
<column2>Ever</column2>
<column3>397.12</column3>
<column4>CAD</column4>
</row>
<row>
<column1>06-11113</column1>
<column2>GE</column2>
<column3>1425</column3>
<column4>CAD</column4>
</row>
<row>
<column1>06-11114</column1>
<column2>Husky</column2>
<column3>-215.14</column3>
<column4>USD</column4>
</row>
<row>
<column1>06-11114</column1>
<column2>Husky</column2>
<column3>2015</column3>
<column4>USD</column4>
</row>
<row>
<column1>06-11114</column1>
<column2>Husky</column2>
<column3>11195.34</column3>
<column4>USD</column4>
</row>
</data>
The output I would like to achieve after running the XSLT is
06-11111 | CP |2701.08
06-11112 | Ever |1265.86
06-11113 | GE |1425
06-11114 | Husky |12995.20
Any help to get me started would be fantastic!

Here is the grouping using the Muenchian method. I'll let you play with getting the numbers formatted correctly based on the number of decimal points.
I typically don't use this because it's limited, tricky and doesn't lend itself to push programming. But, it will work for you today.
<xsl:template match="#* | node()">
<xsl:apply-templates select="#* | node()"/>
</xsl:template>
<xsl:key name="rows" match="row" use="concat(column1, '||', column2)" />
<xsl:template match="data">
<xsl:for-each select="row[generate-id(.) = generate-id(key('rows', concat(column1, '||', column2))[1])]">
<xsl:sort select="column1" data-type="text" order="ascending"/>
<xsl:sort select="column2" data-type="text" order="ascending"/>
<xsl:value-of select="concat(column1,'|',column2,'|')"/>
<xsl:variable name="mySum">
<xsl:value-of select="sum(key('rows', concat(column1, '||', column2))/column3)"/>
</xsl:variable>
<xsl:value-of select="format-number($mySum,'#,##0.00')"/>
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>

Related

How do I use XSLT to merge XML elements and then structure elements/attributes of the same name/type?

Main issue
I have an XML file which has multiple similar elements with slightly different information. So, lets say I have two elements with the same X attirbute, but different Y attributes. Here is an example:
<FILE>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<ATTRIBUTEY>A</ATTRIBUTEY>
</ELEMENT>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<ATTRIBUTEY>B</ATTRIBUTEY>
</ELEMENT>
</FILE>
What I want to do is--based on the fact that both elements have the same X attribute--merge them into a single element with one attribute X and multiple child elements (of the same type)--each of which contain each of the different attributes Y. So, for example, I want my file to end up like this:
<FILE>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<NEWELEMENT>
<ATTRIBUTEY>A</ATTRIBUTEY>
</NEWELEMENT>
<NEWELEMENT>
<ATTRIBUTEY>B</ATTRIBUTEY>
</NEWELEMENT>
</ELEMENT>
</FILE>
Possible solution?
One possible solution I can think of, but lack the knowledge to execute, is made up of two steps:
Firstly, I could merge all elements which contain the X attribute so that the information is, at the least, in one place. I don't know quite how to do this.
Secdonly, once this is done, this opens me up--in theory--to use my knowledge of XSLT to re-structure the element.
Problem with the possible solution
However, once all elements that containin the X attribute are merged together (first step), I will have a file in which there will be attributes with the same name (specifically, the Y attribute). Here is an example:
<FILE>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<ATTRIBUTEY>A</ATTRIBUTEY>
<ATTRIBUTEY>B</ATTRIBUTEY>
</ELEMENT>
</FILE>
What this means is that--at least with my knowledge--when I execute an XSLT file on the above XML (second step), it cannot distinguish between the two Y elements when sorting them into the two new child elements (NEWELEMENT).
So, let's say I execute the following XSLT on the above XML:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="FILE">
<Record>
<xsl:for-each select = "ELEMENT">
<ELEMENT>
<xsl:copy-of select="ATTRIBUTEX"/>
<NEWELEMENT>
<xsl:copy-of select="ATTRIBUTEY"/>
</NEWELEMENT>
<NEWELEMENT>
<xsl:copy-of select="ATTRIBUTEY"/>
</NEWELEMENT>
</ELEMENT>
</xsl:for-each>
</Record>
</xsl:template>
</xsl:stylesheet>
The output is this:
<Record>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<NEWELEMENT>
<ATTRIBUTEY>A</ATTRIBUTEY>
<ATTRIBUTEY>B</ATTRIBUTEY>
</NEWELEMENT>
<NEWELEMENT>
<ATTRIBUTEY>A</ATTRIBUTEY>
<ATTRIBUTEY>B</ATTRIBUTEY>
</NEWELEMENT>
</ELEMENT>
</Record>
As you can see, the XSLT has taken all and any ATTRIBUTEY elements and put them into each NEWELEMENT rather than distinguishing between them and placing one ATTRIBUTEY in the first NEWELEMENT, and the second ATTRIBUTEY in the second NEWELEMENT. I am in need of an XSLT file which does just that, and, as stated further up, produces an XML that looks like this:
<FILE>
<ELEMENT>
<ATTRIBUTEX>1</ATTRIBUTEX>
<NEWELEMENT>
<ATTRIBUTEY>A</ATTRIBUTEY>
</NEWELEMENT>
<NEWELEMENT>
<ATTRIBUTEY>B</ATTRIBUTEY>
</NEWELEMENT>
</ELEMENT>
</FILE>
Would anyone be able to help me with this? Any solutions which generate the desired output (above) from the very first example file would be much appreciated! It's a bonus if that solution follows steps one and two. Thanks!
I'm getting your requested output from the XSLT 3.0 below.
Just a couple of notes beforehand:
You mention ATTRIBUTEs but, in fact, everything is an element in your XML document. There are no attributes in the XML sense.
I really don't see the need to create the additional subelements .. you can already have a list of elements of the same name.
Nonetheless, as an exercise in style(sheets), here is a stylesheet that produces your output from your given input:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes" />
<xsl:template match="/FILE" >
<FILE>
<xsl:for-each-group select="*" group-by="name(.)" >
<xsl:variable name="parentName" as="xs:string" select="current-grouping-key()" />
<xsl:variable name="childNamesSameValues" as="xs:string*" >
<xsl:for-each-group select="current-group()/*" group-by="name(.)" >
<xsl:if test="count(distinct-values(current-group()/text())) eq 1">
<xsl:sequence select="current-grouping-key()" />
</xsl:if>
</xsl:for-each-group>
</xsl:variable>
<xsl:element name="{$parentName}" >
<xsl:for-each-group select="current-group()/*" group-by="name(.)" >
<xsl:choose>
<xsl:when test="current-grouping-key() = $childNamesSameValues">
<xsl:copy-of select="current-group()[1]" />
</xsl:when>
<xsl:otherwise >
<xsl:for-each select="current-group()" >
<xsl:element name="NEW{$parentName}" >
<xsl:copy-of select="." />
</xsl:element>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:element>
</xsl:for-each-group>
</FILE>
</xsl:template>
</xsl:stylesheet>

XSL v.1, Muenchian Grouping, Summing Line Items per Invoice, Call-Template

I'm trying to sum the aggregate of line item amounts in a group with multiple groups. That is, there are many Invoices with many Line Items per Invoice. I need to sum the Amounts of Line Items across each Invoice.
I've searched across various posts here on Stack, as well as various forums, and haven't been able to decipher the code to be able to sum values with a Muenchian Grouping approach. If there is already a solution for this, please point me in the right direction.
The grouping occurs on the recordId attribute.
XML:
<?xml version="1.0" encoding="UTF-8"?>
<query>
<results total="6">
<result recordId="15918960" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$20.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918960" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$30.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918960" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$40.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918961" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$20.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918961" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$30.74]]></LI_Amount_display>
</column>
</columns>
</result>
<result recordId="15918962" associatedRecordId="null" boId="10002385">
<columns>
<column>
<field>AmountNU</field>
<LI_Amount_display><![CDATA[$29.74]]></LI_Amount_display>
</column>
</columns>
</result>
</results>
</query>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:datetime="http://exslt.org/dates-and-times"
xmlns:exsl="http://exslt.org/common"
xmlns:fn="http://www.w3.org/2013/xpath-functions"
exclude-result-prefixes="datetime">
<xsl:output method="text" encoding="UTF-8" indent="no"/>
<xsl:key name="recordID" match="result" use="#recordId"/><!-- Define a key to use for grouping the results -->
<!-- Might be needed for nulls
<xsl:template match="/results/result/columns/column/LI_Amount_display[not(text()[normalize-space()])]">
<xsl:element name='LI_Amount_display' value="0.00"></xsl:element>
</xsl:template> -->
<xsl:template match="/">
<xsl:call-template name="fixTheWidth" >
<!-- This parameter is a Id for each group of records based on the result/#recordId attribute. This groups all records to the record ID-->
<xsl:with-param name="resultIndex" select="//results/result[generate-id(.) = generate-id(key('recordID', #recordId)[1])]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="fixTheWidth" match="/results">
<xsl:param name="resultIndex" /> <!-- A unique index based on grouping the records on the recordID -->
<!-- MORE CODE HERE USING $resultIndex has been redacted for simplicity-->
<xsl:for-each select="$resultIndex" >
<xsl:text> BEGIN | </xsl:text>
<xsl:value-of select="number(translate(substring(key('recordID',#recordId)/columns/column/LI_Amount_display,2),',',''))"></xsl:value-of>
<!-- <xsl:value-of select="sum(number(translate(substring(key('recordID',#recordId)/columns/column/LI_Amount_display,2),',','')))"></xsl:value-of>-->
</xsl:for-each>
<xsl:text> | END </xsl:text>
<!-- MORE CODE HERE USING $resultIndex has been redacted for simplicity-->
</xsl:template>
</xsl:stylesheet>
I realize that using the "call-template" strategy is not necessarily the "best" approach, but its what I've come to such that I can create a fixed width flat file and not have to refactor the entire script. If there's a way to accomplish this otherwise, I'm all ears.
You cannot sum nodes that aren't numbers. A value of $20.74 is a string, not a number. You must first convert the values to numbers by removing the currency symbol (and any other non-numeric characters, if they are allowed in the input), then proceed to group the resulting nodes and sum the groups.
Here's an example:
XSLT 1.0
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:key name="amt" match="amount" use="#id"/>
<xsl:template match="/query">
<xsl:variable name="amounts">
<xsl:for-each select="results/result">
<amount id="{#recordId}"><xsl:value-of select="translate(columns/column/LI_Amount_display, '$', '')"/></amount>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="amount-set" select="exsl:node-set($amounts)/amount" />
<xsl:for-each select="$amount-set[generate-id() = generate-id(key('amt', #id)[1])]" >
<xsl:text> BEGIN | </xsl:text>
<xsl:value-of select="sum(key('amt', #id))"/>
</xsl:for-each>
<xsl:text> | END </xsl:text>
</xsl:template>
</xsl:stylesheet>
Applied to your example input, the result is:
BEGIN | 92.22 BEGIN | 51.48 BEGIN | 29.74 | END
I couldn't understand the strategy of calling a template and "create a fixed width flat file". In any case, it seems unrelated to the question at hand. You might post a separate question, if necessary.

Concat distinct values in a for-each

I've run into a problem and i'm looking for a quick fix. I have an xml from which i take a some values:
<root>
<item>
<property1>value</property1>
<property2>value</property2>
<property3>value</property3>
</item>
<item>...</item>
<item>...</item>
<item>...</item>
</root>
I'm making a variable to use after using:
<xsl:for-each select="root/item"><xsl:value-of select="concat(property1,';')"/></xsl:for-each>
But i've ran into a problem when too many items, the variable gets too big (over 255 characters). So i was thinking of taking only the unique values (unique property values).
Any simple way to do it ?
Thanks
Please test the stylesheet below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:key name="group" match="property1" use="."/>
<xsl:variable name="unique_properties">
<xsl:for-each select="//property1[count(. | key('group', .)[1]) = 1]"><!-- this selects unique values -->
<xsl:value-of select="concat(.,';')"/>
</xsl:for-each>
</xsl:variable>
<xsl:template match="root">
<xsl:value-of select="$unique_properties"/>
</xsl:template>
</xsl:stylesheet>

How to use parameter or variable values as node name?

I am trying to use the value of a parameter or variable as a node name inside a value-of select but so far failed..
So my XML is as below.
<Data>
<Name>John Smith</Name>
<Date>28112012</Date>
<Phone>iphone</Phone>
<Car>BMW</Car>
</Data>
And my incomplete xslt looks like below.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0"
exclude-result-prefixes="#all">
<xsl:param name="nodename" select="'Name'"/>
<xsl:template match="/Data">
<Output>
<xsl:value-of select="{$nodename}"/>
</Output>
</xsl:template>
</xsl:stylesheet>
Ideally I want the out put to be
<Output>John Smith</Output>
Is there any way I can do this using XSLT?
I want to be able to select appropriate node based on a users choice.
Thanks
SK
A wild guess, let me know if it works:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" exclude-result-prefixes="#all">
<xsl:param name="nodename" select="'Name'"/>
<xsl:template match="/Data">
<Output>
<xsl:value-of select="//*[name()=$nodename]" />
</Output>
</xsl:template>
</xsl:stylesheet>

Piwik statitics about all websites

Is it possible to use the Piwik-API with all Websites, not just for a single one?
What i want to do is get a mean value of used browsers. I can do this for a single website like this:
?module=API&method=UserSettings.getBrowser&idSite=1&period=day&date=last10&format=rss
If i just remove idSite=1 i get an error.
You can specify all sites using idSite=all, you can also specify multiple sites by separating the ids with commas idSite=1,2,4,5.
The resulting output is given per idSite wrapped in an extra <results> tag, so whereas before you had
<result>
<row>
<label>Chrome 14.0</label>
<nb_uniq_visitors>13</nb_uniq_visitors>
...
</row>
<row>
<label>Chrome 13.0</label>
<nb_uniq_visitors>13</nb_uniq_visitors>
...
</row>
...
</result>
You now get
<results>
<result idSite="2">
<row>
<label>Chrome 14.0</label>
<nb_uniq_visitors>13</nb_uniq_visitors>
...
</row>
<row>
<label>Chrome 13.0</label>
<nb_uniq_visitors>13</nb_uniq_visitors>
...
</row>
...
</result>
<result idSite="3">
<row>
<label>Chrome 14.0</label>
<nb_uniq_visitors>13</nb_uniq_visitors>
...
</row>
<row>
<label>Chrome 13.0</label>
<nb_uniq_visitors>13</nb_uniq_visitors>
...
</row>
...
</result>
...
</results>
This does mean that any aggregating for your mean value will have to be done once you get the data but this should be relatively trivial.