XSL - counting nodes depending on parents' position - xslt

I am trying to count nodes depending on the position of their parents.
This is an example :
<tbody>
<row>
<entry>L1C1</entry>
<entry>L1C2</entry>
<entry>L1C3</entry>
</row>
<row>
<entry>L2C1</entry>
<entry morerows="1">L2C2</entry>
<entry>L2C3</entry>
</row>
<row>
<entry>L3C1</entry>
<entry>L3C3</entry>
</row>
</tbody>
For each entry, I want to count the number of entry elements of preceding row elements whose attribute morerows is greater than a number which depends of the position of the row.
I have something like this:
<xsl:variable name="nbRows">
<xsl:value-of select="count(ancestor::tbody/row)">
</xsl:value-of>
</xsl:variable>
<xsl:value-of select="count(parent::row/preceding-sibling::row/entry[#morerows > ($nbRows - count(current()/../preceding-sibling::row))])">
</xsl:variable>"/>
But as you can imagine, this does not work.
Can someone help me with this?

If I understood correctly the question this should do the job:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="row">
<xsl:variable name="nRows" select="count(../row)"/>
<xsl:variable name="precedingEntries" select="preceding-sibling::row/entry"/>
<xsl:variable name="minMoreRows" select="$nRows - position() + 1"/>
<n>
<xsl:value-of select="count($precedingEntries[#morerows>=$minMoreRows])"/>
</n>
</xsl:template>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
</xsl:stylesheet>
The output - when applied to the example in the question - is:
<root>
<n>0</n>
<n>0</n>
<n>1</n>
</root>

Related

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>

Multiple records/elements grouped to create new structure

I searched and came close to finding a solution but that requires Stylesheet 2.0 and I'm stuck on 1.0.
This is the sample XML I have:
<root>
<row>A1: Apples</row>
<row>B1: Red</row>
<row>C1: Reference text</row>
<row>badly formatted text which belongs to row above</row>
<row>and here.</row>
<row>D1: ABC</row>
<row>E1: 123</row>
<row>A1: Oranges</row>
<row>B1: Purple</row>
<row>C1: More References</row>
<row>with no identifier</row>
<row>again and here.</row>
<row>D1: DEF</row>
<row>E1: 456</row>
.
.
I want it to look like:
<root>
<row>
<A1>Apples</A1>
<B1>Red</B1>
<C1>Reference text badly formatted text which belongs to row above and here.</C1>
<D1>ABC</D1>
<E1>123</E1>
</row>
<row>
<A1>Oranges</A1>
<B1>Purple</B1>
<C1>More Reference with no identifier again and here.</C1>
<D1>DEF</D1>
<E1>456</E1>
</row>
.
.
There is a pattern to this which I can convert using other utilities but quite hard with XSL 1.0.
There are headings within the elements that I can use and the reference text field is multi-line when it gets converted to XML, it creates its own row for each line but it's always in the same position between C1 and D1. The actual name of the elements, ie is not important.
The row should break up after E1. I think my example is straightforward but this transformation is not. I consider myself not even a beginner at XML/XSL. I am learning from scratch and then I get shifted to other projects and then have to come back to it again. TIA.
Update: Another case I ran into with slightly different structure but I want the result to be the same:
<root>
<row>
<Field>A1: Apples</Field>
</row>
<row>
<Field>B1: Red</Field>
</row>
<row>
<Field>C1: Reference text</Field>
</row>
<row>
<Field>badly formatted text which belongs to row above</Field>
</row>
<row>
<Field>and here.</Field>
</row>
<row>
<Field>D1: ABC</Field>
</row>
<row>
<Field>E1: 123</Field>
</row>
<row>
<Field>A1: Oranges</Field>
</row>
<row>
<Field>B1: Purple</Field>
</row>
<row>
<Field>C1: More References</Field>
</row>
<row>
<Field>with no identifier</Field>
</row>
<row>
<Field>again and here.</Field>
</row>
<row>
<Field>D1: DEF</Field>
</row>
<row>
<Field>E1: 456</Field>
</row>
I tried applying an identity transform but didn't seem to work:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match ="row/Field">
<xsl:apply-templates/>
</xsl:template>
This looks kind of tricky, but I have a solution which seems to work. It allows for a variable number of rows after the C1 row (it wasn't clear whether this was always 2 rows or not).
The solution makes heavy use of the following-sibling axis, which is probably very inefficient, especially for a large input file.
You can test it out here.
<xsl:template match="/root">
<!-- Loop through every "A1" row -->
<xsl:for-each select="row[substring-before(text(), ':') = 'A1']">
<!-- Add a <row> tag -->
<xsl:element name="row">
<!-- Add each of the A1-E1 tags by finding the first following-sibling that matches before the colon -->
<xsl:apply-templates select="." />
<xsl:apply-templates select="following-sibling::*[substring-before(text(), ':') = 'B1'][1]" />
<xsl:apply-templates select="following-sibling::*[substring-before(text(), ':') = 'C1'][1]" />
<xsl:apply-templates select="following-sibling::*[substring-before(text(), ':') = 'D1'][1]" />
<xsl:apply-templates select="following-sibling::*[substring-before(text(), ':') = 'E1'][1]" />
</xsl:element>
</xsl:for-each>
</xsl:template>
<!-- Process each row -->
<xsl:template match="/root/row">
<!-- Create an element whose name is whatever is before the colon in the text -->
<xsl:element name="{substring-before(text(), ':')}">
<!-- Output everything after the colon -->
<xsl:value-of select="normalize-space(substring-after(text(), ':'))" />
<!-- Special treatment for the C1 node -->
<xsl:if test="substring-before(text(), ':') = 'C1'">
<!-- Count how many A1 nodes exist after this node -->
<xsl:variable name="remainingA1nodes" select="count(following-sibling::*[substring-before(text(), ':') = 'A1'])" />
<!-- Loop through all following-siblings that don't have a colon at position 3, and still have the same number of following A1 rows as this one does -->
<xsl:for-each select="following-sibling::*[substring(text(), 3, 1) != ':'][count(following-sibling::*[substring-before(text(), ':') = 'A1']) = $remainingA1nodes]">
<xsl:text> </xsl:text>
<xsl:value-of select="." />
</xsl:for-each>
</xsl:if>
</xsl:element>
</xsl:template>
Every record or group is 7 lines.
Then why not do it simply by the numbers:
XSLT 1.0
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<root>
<xsl:for-each select="row[position() mod 7 = 1]">
<row>
<xsl:apply-templates select=". | following-sibling::row[position() < 3] | following-sibling::row[4 < position() and position() < 7]"/>
</row>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="row">
<xsl:element name="{substring-before(., ': ')}">
<xsl:value-of select="substring-after(., ': ')"/>
</xsl:element>
</xsl:template>
<xsl:template match="row[starts-with(., 'C1: ')]">
<C1>
<xsl:value-of select="substring-after(., 'C1: ')"/>
<xsl:for-each select="following-sibling::row[position() < 3]">
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
</xsl:for-each>
</C1>
</xsl:template>
</xsl:stylesheet>

XSLT: Get row element inside a for-loop over one table from another table

I have three tables in my XML file: tableX, tableA and tableB.
This is my algorithm:
Go through each row element of tableX and check, if Xelement1 is NULL (empty).
If match:
Go through each row in tableA and compare the value of the row element Aelement2 with another row element Xelement2 of tableX.
If match:
Go through each row element of tableB and compare the value of row element Belement1 oftableB with the value of the row element Aelement1 of tableA
If match:
Print a value of another row element Belement2 of tableB
Currently I am doing this and it is working:
<xsl:for-each select="/root/table[#name='tableX']/row">
<xsl:variable name="rec" select="."/>
<xsl:choose>
<xsl:when test="Xelement1=''">
<xsl:for-each select="/root/table[#name='tableA']/row">
<xsl:variable name="member" select="."/>
<xsl:if test="Aelement2=$rec/Xelement2">
<xsl:for-each select="/root/table[#name='tableB']/row">
<xsl:if test="Belement1=$member/Aelement1">
<xsl:value-of select="Belement2"/>&#160
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<!-- Xelement1 is not null -->
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
However, I wish I could access e.g. Aelement1 within the third for-each loop, without having to save it to a variable member.
Also, why doesn't this work?
[...]
<xsl:for-each select="/root/table[#name='tableA']/row">
<xsl:variable name="member" select="Aelement1"/>
<xsl:if test="Aelement2=$rec/Xelement2">
<xsl:for-each select="/root/table[#name='tableB']/row">
<xsl:if test="Belement1=$member">
<xsl:value-of select="Belement2"/>&#160
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
[...]
Minimal, but complete XML example:
<root>
<table name="tableX">
<row>
<Xelement1>11</Xelement1>
<Xelement2>3</Xelement2>
<Xother>failure</Xother>
</row>
<row>
<Xelement1>NULL</Xelement1>
<Xelement2>9</Xelement2>
<Xother>success</Xother>
</row>
</table>
<table name="tableA">
<row>
<Aelement1>10</Aelement1>
<Aelement2>16</Aelement2>
<Aother>failure</Aother>
</row>
<row>
<Aelement1>12</Aelement1>
<Aelement2>9</Aelement2>
<Aother>success</Aother>
</row>
<row>
<Aelement1>12</Aelement1>
<Aelement2>16</Aelement2>
<Aother>failure</Aother>
</row>
<row>
<Aelement1>14</Aelement1>
<Aelement2>9</Aelement2>
<Aother>success</Aother>
</row>
</table>
<table name="tableB">
<row>
<Belement1>10</Belement1>
<Belement2>failure</Belement2>
<Bother>random</Bother>
</row>
<row>
<Belement1>12</Belement1>
<Belement2>success</Belement2>
<Bother>random</Bother>
</row>
<row>
<Belement1>14</Belement1>
<Belement2>success</Belement2>
<Bother>random</Bother>
</row>
</table>
</root>
You should be able to select the corresponding rows from table B with a single XPath expression:
<xsl:for-each select="/root/table[#name = 'tableB']/row[
Belement = /root/table[#name = 'tableA']/row/Aelement
]">
<!-- Do something -->
</xsl:for-each>
This works because XPath's =, when operating on node-sets, compares all nodes on the left-hand side with all nodes on the right-hand side (just like an INNER JOIN in SQL).
It will select one node in your example (namely the <row> that has <Belement>5</Belement>), but it would select more if there were more matches.
After a substantial edit to the question, the XPath expression got more complex. The same principle applies.
//table[#name = 'tableB']/row[
Belement1 = //table[#name = 'tableA']/row[
Aelement2 = //table[#name = 'tableX']/row[
Xelement1 = 'NULL'
]/Xelement2
]/Aelement1
]/Belement2
will select the elements containing "success" from your sample.
Read it from the inside out:
from the tableX rows with Xelement1 = 'NULL' you want the Xelement2
from the tableA rows where Aelement2 corresponds to those you want Aelement1
from the tableB rows where Belement1 corresponds to those you want Belement2
I would define a key
<xsl:key name="row" match="table[#name = 'tableB']/row" use="Belement"/>
then you can shorten
<xsl:for-each select="/root/table[#name='tableA']/row">
<xsl:variable name="member" select="."/>
<xsl:for-each select="/root/table[#name='tableB']/row">
<xsl:if test="Belement=$member/Aelement">
<!--Do something-->
</xsl:if>
</xsl:for-each>
</xsl:for-each>
to
<xsl:for-each select="/root/table[#name='tableA']/row/key('row', Aelement)">
<!--Do something-->
</xsl:for-each>
As for the terminology, your code processes row elements or row element nodes.
As for the sample that is not working, you would need to show us minimal but complete samples of XML input, XSLT code, result you want, result you get so that we can easily reproduce the problem.
Also, why doesn't this work?
> <xsl:for-each select="/root/table[#name='tableA']/row">
> <xsl:variable name="member" select="Aelement"/>
> <xsl:for-each select="/root/table[#name='tableB']/row">
> <xsl:if test="Belement=$member">
> <!--Do something-->
> </xsl:if>
> </xsl:for-each>
> </xsl:for-each>
Actually, it does work. If you try actually doing something with the matching row in tableB, for example:
<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="/">
<output>
<xsl:for-each select="/root/table[#name='tableA']/row">
<xsl:variable name="member" select="Aelement"/>
<xsl:for-each select="/root/table[#name='tableB']/row">
<xsl:if test="Belement=$member">
<!--Do something-->
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
you will receive:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<row>
<Belement>5</Belement>
</row>
</output>

Change template so XSLT Outputs a sum instead of a list of values

I have an XSLT template that is working fine.
<xsl:template match="Row[contains(BenefitType, 'MyBenefit')]">
<value>
<xsl:value-of select="BenefitList/Row/Premium* 12" />
</value>
</xsl:template>
The output is
<value>100</value>
<value>110</value>
What I would prefer is if it would just output 220. So, basically in the template I would need to use some sort of variable or looping to do this and then output the final summed value?
XSLT 1 compliance is required.
The template is being used as follows:
<xsl:apply-templates select="Root/Row[contains(BenefitType, 'MyBenefit')]" />
For some reason, when I use the contains here it only sums the first structure that matches and not all of them. If The XML values parent wasn't dependent on having a sibling element that matched a specific value then a'sum' approach would work.
The direct solution to the problem was already mentioned in the comments, but assuming you really want to do the same with some variables, this might be interesting for you:
XML:
<Root>
<Row>
<BenefitType>MyBenefit</BenefitType>
<BenefitList>
<Premium>100</Premium>
</BenefitList>
</Row>
<Row>
<BenefitType>MyBenefit, OtherBenefit</BenefitType>
<BenefitList>
<Premium>100</Premium>
</BenefitList>
</Row>
<Row>
<BenefitType>OtherBenefit</BenefitType>
<BenefitList>
<Premium>1000</Premium>
</BenefitList>
</Row>
<Row>
<BenefitType>OtherBenefit</BenefitType>
<BenefitList>
<Premium>1000</Premium>
</BenefitList>
</Row>
</Root>
XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:template match="/">
<total>
<xsl:variable name="valuesXml">
<values>
<xsl:apply-templates select="Root/Row[contains(BenefitType, 'MyBenefit')]" />
</values>
</xsl:variable>
<xsl:variable name="values" select="exsl:node-set($valuesXml)/values/value" />
<xsl:value-of select="sum($values)" />
</total>
</xsl:template>
<xsl:template match="Row[contains(BenefitType, 'MyBenefit')]">
<value>
<xsl:value-of select="BenefitList/Premium * 12" />
</value>
</xsl:template>
</xsl:stylesheet>
Here the same result set generated in your question is saved in another variable, which can then again be processed.

Need to show the same value once using XSLT

<root>
<dql1>
<row>
<abcd> Prod 1 </abcd>
<td_formulation_name> 123 </td_formulation_name>
</row>
<row>
<abcd> Prod 2 </abcd>
<td_formulation_name> 123 </td_formulation_name>
</row>
</dql1>
</root>
I want to show the output as :-
Prod 1
Prod 2
123
How is it possible through XSLT ? Can any one please help ?
You can just access them using below xslt. Though not very sure if you need this.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="root/dql1/row/abcd"/>
<xsl:value-of select="root/dql1/row[2]/abcd"/>
<xsl:value-of select="root/dql1/row/td_formulation_name"/>
</xsl:template>
</xsl:stylesheet>
If you do not know the index or if it is dynamic you can do
<xsl:for-each select="root/dql1/row">
<xsl:value-of select="abcd" />
<xsl:value-of select="td_formulation_name" />
</xsl:for-each>