XSL: Issue with tokenizing in the for loop - xslt

I am using the following stylesheet for displaying a table in the excel workbook with the data. I am not able to get the desired result instead it is displaying as differently as given below.
Suggestions Pls?
The stylesheet used:
<xsl:stylesheet>
<xsl:template match="/">
<xsl:variable name="test1" select="str:tokenize('1$,$2$,$3$,$4$,$5','$,$')" />
<xsl:variable name="test2" select="str:tokenize('a$,$b$,$c$,$d$,$e','$,$')" />
<xsl:for-each select="str:split('1a$,$2b$,$3c$,$4d$,$5e','$,$')>
<row>
<cell Index="1">
<xsl:value-of select="$test1[position()]" />
</cell>
<cell Index="2">
<xsl:value-of select="$test2[position()]" />
</cell>
</row>
</xsl:for-each>
</xsl:template>
Expected Result:
1 a
2 b
3 c
4 d
5 e
Where as the result displayed as
a b
c d
e
It seems like the it is displaying the latest tokenize values.
How to get respected values.

Good question, +1.
It seems to me that instead of:
<xsl:value-of select="$test1[position()]" />
this must be:
<xsl:value-of select="$test1[position() = current()]" />
Exactly the same observation holds for the second <xsl:value-of>
Explanation:
Any expression
$var[position()]
is equivalent to:
$var
because position() can only have values >= 1 and [position()] means the boolean value of position() , and the boolean value of any non-negative number by definition is true().
If we want to select the $k-th node in the node-set $var, in XPath 1.0, which is weakly-typed and it isn't known that $k holds an integer, we have to write:
$var[position() = $k]
Here is a complete, corresponding XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="test1" select=
"tokenize('1$,$2$,$3$,$4$,$5','\$,\$')" />
<xsl:variable name="test2" select=
"tokenize('a$,$b$,$c$,$d$,$e','\$,\$')" />
<xsl:for-each select="tokenize('1a$,$2b$,$3c$,$4d$,$5e','\$,\$')">
<row>
<cell Index="1">
<xsl:value-of select="$test1[position()]" />
</cell>
<cell Index="2">
<xsl:value-of select="$test2[position()]" />
</cell>
</row>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (ignored), the wanted, correct result is produced:
<row xmlns:xs="http://www.w3.org/2001/XMLSchema">
<cell Index="1">1 2 3 4 5</cell>
<cell Index="2">a b c d e</cell>
</row>
<row xmlns:xs="http://www.w3.org/2001/XMLSchema">
<cell Index="1">1 2 3 4 5</cell>
<cell Index="2">a b c d e</cell>
</row>
<row xmlns:xs="http://www.w3.org/2001/XMLSchema">
<cell Index="1">1 2 3 4 5</cell>
<cell Index="2">a b c d e</cell>
</row>
<row xmlns:xs="http://www.w3.org/2001/XMLSchema">
<cell Index="1">1 2 3 4 5</cell>
<cell Index="2">a b c d e</cell>
</row>
<row xmlns:xs="http://www.w3.org/2001/XMLSchema">
<cell Index="1">1 2 3 4 5</cell>
<cell Index="2">a b c d e</cell>
</row>

Related

Removing comma from number format with unique XSLT coding

With the example below, the XSLT is doing a few things, it is grouping by column 1 and column2, if it is the same, then it will group the column 3 amount. As well, within the PayAmount tags, it is reversing negative (-) number into a positive and vice versa. What I am having troubles with is writing logic to remove the comma (,) from column 3 in the output.
Below is my XSLT Code
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="data">
<xsl:for-each select="row[generate-id(.) = generate-id(key(''rows'', concat(column1, ''||'', column2)))]">
</Record>
<Detail>
<Amount>
<xsl:variable name="mySum">
<xsl:value-of select="sum(key(''rows'', concat(column1, ''||'', column2))/column3)" />
</xsl:variable>
<xsl:value-of select="translate(($mySum * ($mySum >= 0) - $mySum * not($mySum >= 0)),'','','''')" />
</Amount>
</Detail>
</Record>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I have tried the following logic, but the comma remains in the output:
- <xsl:value-of select="translate(($mySum * ($mySum >= 0) - $mySum * not($mySum >= 0)),'','', '''')" />
- <xsl:value-of select="format-number($mySum * ($mySum >= 0) - $mySum * not($mySum >= 0),'#,##0.00')" />
- <xsl:value-of select="translate(sum(key(''rows'', concat(column1, ''||'', column2))/column3),'','','''')" />
Below is a sample data in XML:
<data>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>-500.00</column3>
</row>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>-5,000.00</column3>
</row>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>-1,000.00</column3>
</row>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>300.00</column3>
</row>
<row>
<column1>200040</column1>
<column2>Auto</column2>
<column3>-4,000.00</column3>
</row>
<row>
<column1>200041</column1>
<column2>Bike</column2>
<column3>-1,700.00</column3>
</row>
<row>
<column1>200041</column1>
<column2>Bike</column2>
<column3>-1,000.00</column3>
</row>
<row>
<column1>200041</column1>
<column2>Bike</column2>
<column3>800.00</column3>
</row>
<row>
<column1>200045</column1>
<column2>Bus</column2>
<column3>200.00</column3>
</row>
<row>
<column1>200045</column1>
<column2>Bus</column2>
<column3>-10,000.00</column3>
</row>
<row>
<column1>200045</column1>
<column2>Bus</column2>
<column3>5,000.00</column3>
</row>
</data>
The output I would like to achieve after running the XSLT is
200040 | Auto | 10200.00
200041 | Bike | 1900.00
200045 | Bus | 4800.00
Any help would be greatly appreciated!
A value that contains a comma is not a number and cannot be summed. You need to remove the commas before you attempt to sum the values.
Here's a simplified example:
XSLT 1.0 + EXSLT node-set()
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:key name="a" match="amount" use="#key"/>
<xsl:template match="data">
<!-- first pass: convert amounts to numbers -->
<xsl:variable name="amounts">
<xsl:for-each select="row">
<amount key="{concat(column1, '|', column2)}">
<xsl:value-of select="translate(column3, ',', '')"/>
</amount>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<Output>
<xsl:for-each select="exsl:node-set($amounts)/amount[generate-id() = generate-id(key('a', #key))]">
<Record>
<Detail1>
<xsl:value-of select="substring-before(#key, '|')"/>
</Detail1>
<Detail2>
<xsl:value-of select="substring-after(#key, '|')"/>
</Detail2>
<xsl:variable name="sum" select="sum(key('a', #key))"/>
<Amount>
<xsl:value-of select="format-number($sum, '0.00')"/>
</Amount>
</Record>
</xsl:for-each>
</Output>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, this will produce:
Result
<?xml version="1.0" encoding="UTF-8"?>
<Output>
<Record>
<Detail1>200040</Detail1>
<Detail2>Auto</Detail2>
<Amount>-10200.00</Amount>
</Record>
<Record>
<Detail1>200041</Detail1>
<Detail2>Bike</Detail2>
<Amount>-1900.00</Amount>
</Record>
<Record>
<Detail1>200045</Detail1>
<Detail2>Bus</Detail2>
<Amount>-4800.00</Amount>
</Record>
</Output>
To reverse negative amounts to positive (and vice versa), you could simply change:
<xsl:value-of select="format-number($sum, '0.00')"/>
to:
<xsl:value-of select="format-number(-$sum, '0.00')"/>

use xlst to count intermediary elements

How can I count intermediary elements? I think the solution to this is related to this question. Supposing I had something like this:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<cell>Title</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
<cell>example additional cell</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell>Title</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell>Title</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
<row>
<cell>Title</cell>
</row>
<row>
<cell padding='true'>Chapter</cell>
</row>
</root>
And, applying a transformation like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="/root">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="row">
<xsl:choose>
<!--these are the indented ones-->
<xsl:when test="cell/#padding">
<xsl:number
value="count(preceding-sibling::row[child::cell[1][#padding]]) + 1"
format="a. "/>
</xsl:when>
<xsl:otherwise>
<xsl:number
value="count(preceding-sibling::row[child::cell[1][not(#padding)]]) + 1"
format="1. "/>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="cell/text()"/>
</xsl:template>
</xsl:stylesheet>
I'm getting this result:
1. Title
a. Chapter
b. Chapter
c. Chapter
2. Title
d. Chapter
3. Title
e. Chapter
f. Chapter
4. Title
g. Chapter
What I would like is to have the "sub" items restart their numbering. I just can't figure how change the axis to stop looking back at earlier elements.
I was looking to have the "chapters" counted. So:
1. Title
a. Chapter
b. Chapter
c. Chapter
2. Title
a. Chapter
3. Title
a. Chapter
b. Chapter
4. Title
a. Chapter
I would suggest you try it this way:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8" />
<xsl:strip-space elements="*"/>
<xsl:template match="/root">
<xsl:apply-templates select="row[not(cell/#padding)] | row/cell[#padding]"/>
</xsl:template>
<xsl:template match="row">
<xsl:number count="row[not(cell/#padding)]" format="1. "/>
<xsl:value-of select="cell"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="cell">
<xsl:number count="cell[#padding]" level="any" from="row[not(cell/#padding)]" format=" a. "/>
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>

XSL Required: Merging content of next cell based on attribute of the current cell

I have a Table it has two column.
Based on First column's rowmerge and rowspan attribute it should merge the values of next column.
RowMerged attribute is to find out whether cells are merged.
RowSpan attribute is to find-out how many cells are merged.
If Rowspan is 0 then that cells is merged with above one.
In the below example we give input as 5 Rows and it will return 3 row as output.
ie) First two rows are merged into single and the content of second column which is not merged should be copied to the above one.
Concerned main on content of cell not the attribute.
Sample Input:
<Table Name="abc">
<TBODY>
<Row>
<Cell RowMerged="T" RowSpan="2"><Element>ABC</Element></Cell>
<Cell><Element>21</Element></Cell>
</Row>
<Row>
<Cell RowMerged="T" RowSpan="0"></Cell>
<Cell><Element>ABC</Element></Cell>
</Row>
<Row>
<Cell RowMerged="F" RowSpan="1"><Element>PQR</Element></Cell>
<Cell><Element>19</Element></Cell>
</Row>
<Row>
<Cell RowMerged="T" RowSpan="2"><Element>XYZ</Element></Cell>
<Cell><Element>99</Element></Cell>
</Row>
<Row>
<Cell RowMerged="T" RowSpan="0"></Cell>
<Cell><Element>Sample</Element></Cell>
</Row>
</TBODY>
</Table>
Sample Output:
<Table Name="abc">
<TBODY>
<Row>
<Cell RowMerged="F" RowSpan="1"><Element>ABC</Element></Cell>
<Cell><Element>21ABC</Element></Cell>
</Row>
<Row>
<Cell RowMerged="F" RowSpan="1"><Element>PQR</Element></Cell>
<Cell><Element>19</Element></Cell>
</Row>
<Row>
<Cell RowMerged="F" RowSpan="1"><Element>XYZ</Element></Cell>
<Cell><Element>99Sample</Element></Cell>
</Row>
</TBODY>
</Table>
You may try something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row">
<xsl:variable name="rows" select="Cell[1]/#RowSpan"/>
<xsl:copy>
<Cell RowMerged="F" RowSpan="1">
<xsl:apply-templates select="Cell[1]/*" />
</Cell>
<Cell>
<Element>
<xsl:apply-templates select="Cell[2]/Element/node()" />
<xsl:apply-templates select="following-sibling::Row[position() < $rows]/Cell[2]/Element/node()" />
</Element>
</Cell>
</xsl:copy>
<xsl:apply-templates select="Row[Cell[1][#RowSpan > 0]]" />
</xsl:template>
<xsl:template match="TBODY">
<xsl:apply-templates select="Row[Cell[1][#RowSpan > 0]]" />
</xsl:template>
</xsl:stylesheet>
With following output:
<Table Name="abc">
<Row>
<Cell RowMerged="F" RowSpan="1">
<Element>ABC</Element>
</Cell>
<Cell>
<Element>21ABC</Element>
</Cell>
</Row>
<Row>
<Cell RowMerged="F" RowSpan="1">
<Element>PQR</Element>
</Cell>
<Cell>
<Element>19</Element>
</Cell>
</Row>
<Row>
<Cell RowMerged="F" RowSpan="1">
<Element>XYZ</Element>
</Cell>
<Cell>
<Element>99Sample</Element>
</Cell>
</Row>
</Table>

XSL: Convert xml to output with xsl

Please help to write xsl:
I've got this xml:
<?xml version="1.0" encoding="utf-8"?>
<root>
<Table name = "My table1">
<Row isHeader="True">
<Cell val ="Header1"> </Cell>
<Cell val ="Header2"> </Cell>
<Cell val ="Header3"> </Cell>
</Row>
<Row isHeader="False">
<Cell val ="Data2.1"> </Cell>
<Cell val ="Data2.2"> </Cell>
<Cell val ="Data2.3"> </Cell>
</Row>
<Row>
<Cell val ="Data3.1"> </Cell>
<Cell val ="Data3.2"> </Cell>
<Cell val ="Data3.3"> </Cell>
</Row>
</Table>
</root>
to this output: First row contains headers.
<?xml version="1.0" encoding="utf-8"?>
<items>
<item>Header1=Data2.1 Header2=Data2.2 Header3=Data2.3 </item>
<item>Header1=Data3.1 Header2=Data3.2 Header3=Data3.3 </item>
</items>
Many thanks for your help!
Here is my suggestion:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="Table">
<items>
<xsl:variable name="headers" select="Row[1]/Cell/#val"/>
<xsl:for-each select="Row[position() gt 1]">
<item>
<xsl:for-each select="Cell">
<xsl:if test="position() gt 1"><xsl:text> </xsl:text></xsl:if>
<xsl:variable name="pos" select="position()"/>
<xsl:value-of select="concat($headers[$pos], '=', #val)"/>
</xsl:for-each>
</item>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="Table">
<xsl:variable name='headers' select="Row[1]"/>
<xsl:for-each select="remove(Row, 1)">
<item><xsl:value-of
select="for $i in 1 to count($headers/Cell)
return concat($headers/Cell[$i], '=', Cell[$i])"/>
</item>
</xsl:for-each>
</xsl:template>
Not tested.

Output values in a certain way using XSLT/XPath 2.0

I have an XML like this:
<?xml version="1.0" encoding="UTF-8"?>
<Section>
<Chapter>
<Cell colname="1">
<Value>A</Value>
</Cell>
<Cell colname="2">
<MyValue>AAA</MyValue>
<MyValue>BBB</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Honda</MyCar>
</Cell>
</Chapter>
<Chapter>
<Cell colname="1">
<Value>C</Value>
</Cell>
<Cell colname="2">
<MyValue>CCC</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Toyota</MyCar>
</Cell>
</Chapter>
</Section>
I like the have a message (later on convert them tags) output like this:
A
AAA
Honda
A
BBB
Honda
C
CCC
Toyota
This is my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="Section//Chapter"/>
</xsl:template>
<xsl:template match="Chapter">
<xsl:for-each select="Cell[#colname='2']//MyValue">
<xsl:message>
<xsl:value-of select="Cell[#colname='1']/Value"/>
<xsl:value-of select="."/>
<xsl:value-of select="Cell[#colname='3']/MyCar"/>
</xsl:message>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Unfortunately it doesn't output what I'd like it to do :(.
I realize that for-each will change the context so the remaining value-ofs won't do anything.
What would be a solution for this ?.
TIA,
John
This XSLT 2.0 transformation (the equivalent XSLT 1.0 transformation can be mechanically written from this one).
Do note: This is a generic solution that works with any number of children and doesn't rely on hardcoded names.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Chapter">
<xsl:apply-templates select="Cell[1]"/>
</xsl:template>
<xsl:template match="Cell">
<xsl:param name="pText" as="xs:string*"/>
<xsl:apply-templates select="*[1]">
<xsl:with-param name="pText" select="$pText"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Cell/*">
<xsl:param name="pText" as="xs:string*"/>
<xsl:variable name="vText" as="xs:string*"
select="$pText, string(.)"/>
<xsl:sequence select=
"$vText
[not(current()/../following-sibling::Cell)]"/>
<xsl:apply-templates select="../following-sibling::Cell[1]">
<xsl:with-param name="pText" select="$vText"/>
</xsl:apply-templates>
<xsl:apply-templates select="following-sibling::*">
<xsl:with-param name="pText" select="$pText"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Section>
<Chapter>
<Cell colname="1">
<Value>A</Value>
</Cell>
<Cell colname="2">
<MyValue>AAA</MyValue>
<MyValue>BBB</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Honda</MyCar>
</Cell>
</Chapter>
<Chapter>
<Cell colname="1">
<Value>C</Value>
</Cell>
<Cell colname="2">
<MyValue>CCC</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Toyota</MyCar>
</Cell>
</Chapter>
</Section>
produces exactly the wanted, correct result:
A AAA Honda A BBB Honda C CCC Toyota
Explanation:
This is essentially a task for producing all combinations of values that belong to N groups (the children of a Cell make a group), taking one item from each group.
At any item of group K, we add this item to the current combination of items of the groups 1, 2, ..., K-1. Then we pass this newly formed combination to the group K+1. If we are an item in the last group (N), we print out (xsl:sequence) the whole accumulated combination.
When the control returns, all combinations of elements of the remaining groups (K+1, K+2, ..., N) have been appended to our current combination of the group items of groups 1, 2, ..., K. All these combinations have already been printed.
We pass the same group elements combination that was passed to us -- now we pass it to the following item in the current group (the following-sibling).
I have modified your approach a little, because I'm guessing that you really don't want to use <xsl:message> diagnostic operation. Besides that, I'm not generating XML on the output and I'm not using for-each:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" version="1.0" encoding="iso-8859-1" indent="yes"/>
<xsl:template match="/Section">
<xsl:apply-templates select="Chapter"/>
</xsl:template>
<xsl:template match="Chapter">
<xsl:apply-templates select="Cell/MyValue" />
</xsl:template>
<xsl:template match="MyValue">
<xsl:value-of select="../../Cell[#colname='1']/Value/text()"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
<xsl:value-of select="../../Cell[#colname='3']/MyCar"/>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>