XSLT sum grouped table cell - xslt

Maybe you can help me.
I don't know how to sum some table's cell "Year" which are grouped by "Title". I need that sum cell also would be merge as first cell "Title".
I used sum(), but it returs 0.
XMl Code:
<?xml version="1.0" encoding="UTF-8"?>
<LIST>
<Row>
<TITLE>Empire Burlesque</TITLE>
<YEAR>2001</YEAR>
<artist>Bob Dylan</artist>
<artist1>Bob Dylan1</artist1>
</Row>
<Row>
<TITLE>Empire Burlesque</TITLE>
<YEAR>2002</YEAR>
<artist>Bob Dylan</artist>
<artist1>Bob Dylanas</artist1>
</Row>
<Row>
<TITLE>Empire Burlesque</TITLE>
<YEAR>2003</YEAR>
<artist>Bonnie Tyler</artist>
<artist1>Bob Dylan</artist1>
</Row>
<Row>
<TITLE>Empire Burlesque</TITLE>
<YEAR>2004</YEAR>
<artist>Bonnie Tyler</artist>
<artist1>Bob Dylanas</artist1>
</Row>
<Row>
<TITLE>Empire Burlesque1</TITLE>
<YEAR>2005</YEAR>
<artist>Bonnie Tyler</artist>
<artist1>Bob Dylan</artist1>
</Row>
<Row>
<TITLE>Empire Burlesque1</TITLE>
<YEAR>2006</YEAR>
<artist>Bonnie Tyler</artist>
<artist1>Bob Dylanas</artist1>
</Row>
</LIST>
XSLT code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:key name="cds" match="Row" use="TITLE" />
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Year</th>
<th>Artist</th>
<th>Artist1</th>
</tr>
<xsl:for-each select="LIST/Row[generate-id() = generate-id(key('cds', TITLE)[1])]" >
<tr>
<td>
<xsl:if test="key('cds', TITLE)[1]">
<xsl:attribute name="rowspan">
<xsl:value-of select="count(key('cds', TITLE))" />
</xsl:attribute>
</xsl:if>
<xsl:value-of select="TITLE"/>
</td>
<td>
<xsl:value-of select="YEAR"/>
</td>
<td>
<xsl:value-of select="artist"/>
</td>
<td>
<xsl:value-of select="artist1"/>
</td>
</tr>
<xsl:for-each select="key('cds', TITLE)[position() > 1]">
<tr>
<td>
<xsl:value-of select="YEAR"/>
</td>
<td>
<xsl:value-of select="artist"/>
</td>
<td>
<xsl:value-of select="artist1"/>
</td>
</tr>
</xsl:for-each>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Now result is that:

Try sum(key('cds', TITLE)/YEAR) inside of the for-each.

Related

Greater than and lesser than condition in xsl

I would like to have greater than and lesser than condition in xsl.
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
</catalog>
xslt:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<xsl:for-each select="catalog/cd">
<tr>
<td>
<xsl:value-of select="title"/>
</td>
<xsl:choose>
<xsl:when test="price > '9' and price < '10'">
<td bgcolor="#B22222">
<xsl:value-of select="artist"/>
</td>
</xsl:when>
</xsl:choose>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I tried
<xsl:when test="price > 9 and price < 10">.
But is not working.
Expected result: Display records which price is between 9 and 10.
Actual result : Nothing display
I added the xsl namespace, and its working fine.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<xsl:for-each select="catalog/cd[price > '9' and price < '10']">
<tr>
<td>
<xsl:value-of select="title"/>
</td>
<td bgcolor="#B22222">
<xsl:value-of select="artist"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Output.
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr>
<td>Hide your heart</td>
<td bgcolor="#B22222">Bonnie Tyler</td>
</tr>
</table>
</body>
</html>
I think are you want following:-
<xsl:template match="catalog/cd">
<xsl:choose>
<xsl:when test="price > 9 and price < 10">
<xsl:copy-of select="*"/>
</xsl:when>
</xsl:choose>
</xsl:template>
OutPut:-
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>

extract the value of an element node via XPath?

Source-XML:
<data>
<item>
<values>
<element1>
<language>EN</language>
<text>text</text>
</element1>
<element2>
<language>DE</language>
<text>Text</text>
</element2>
</values>
</item>
<item>
<values>
<element5>
<language>EN</language>
<text>description</text>
</element5>
<element6>
<language>DE</language>
<text>Beschreibung</text>
</element6>
</values>
</item> </data>
I want to get all the elements in language 'EN'. First I have a loop, where I saved the elment names in a variable. In the next step I want to get only the elements in language "EN". I need in the result of this step only the element-name and text which have the language 'EN' to build a table.
I tried this:
<xsl:param name="element" select="'element1'"/>
<xsl:template match="/">
<xsl:if test="data/item/values[local-name()=$element]/language[text()='EN']">
</xsl:if>
</xsl:template>
And the output XSLT should be something like:
<table id="123">
<tgroup cols="2">
<colspec colname="c1" colnum="1" colwidth="1.0*"/>
<colspec colname="c2" colnum="2" colwidth="1.0*"/>
<thead>
<row>
<entry>Name</entry>
<entry>Values</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<ph>element1</ph>
</entry>
<entry>text</entry>
</row>
<row>
<entry>
<ph>element5</ph>
</entry>
<entry>description</entry>
</row>
</tbody>
</tgroup>
</table>
I know that there are other ways to solve this problem. But for other steps in the transformation it is important to test every element separately.
Thanks in advance!
<xsl:output method="xml" indent="yes"/>
<xsl:template match="data">
<xsl:for-each-group select="item" group-by="values/*[language = 'EN']">
<xsl:for-each select="current-group()">
<xsl:element name="{current-group()/values/*[language = 'EN']/local-name()}">
<language>
<xsl:value-of select="descendant::language[text() = 'EN']"/>
</language>
<txt>
<xsl:value-of select="current-group()/values/*[language = 'EN']/text"/>
</txt>
</xsl:element>
</xsl:for-each>
</xsl:for-each-group>
</xsl:template>
You may do like this
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common" version="1.0">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:variable name="ENelements">
<xsl:for-each select="/data/item/values/*[language='EN']">
<element name="{local-name()}"><xsl:value-of select="text" /></element>
</xsl:for-each>
</xsl:variable>
<table id="123">
<tgroup cols="2">
<colspec colname="c1" colnum="1" colwidth="1.0*"/>
<colspec colname="c2" colnum="2" colwidth="1.0*"/>
<thead>
<row>
<entry>Name</entry>
<entry>Values</entry>
</row>
</thead>
<tbody>
<xsl:for-each select="exsl:node-set($ENelements)/*">
<row>
<entry>
<ph><xsl:value-of select="#name" /></ph>
</entry>
<entry><xsl:value-of select="." /></entry>
</row>
</xsl:for-each>
</tbody>
</tgroup>
</table>
</xsl:template>
</xsl:stylesheet>
http://xsltfiddle.liberty-development.net/jyRYYig
AFAICT, it could be simply:
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="/data">
<table id="123">
<tgroup cols="2">
<colspec colname="c1" colnum="1" colwidth="1.0*"/>
<colspec colname="c2" colnum="2" colwidth="1.0*"/>
<thead>
<row>
<entry>Name</entry>
<entry>Values</entry>
</row>
</thead>
<tbody>
<xsl:for-each select="item">
<xsl:variable name="elem" select="values/*[language='EN']" />
<row>
<entry>
<ph>
<xsl:value-of select="name($elem)"/>
</ph>
</entry>
<entry>
<xsl:value-of select="$elem/text"/>
</entry>
</row>
</xsl:for-each>
</tbody>
</tgroup>
</table>
</xsl:template>
</xsl:stylesheet>

xsl:key how to select unmatched key values

I am trying to compare two xml using xsl:key, but not sure how to print unmatched keys. In this scenario I am keying b.xml and comparing with a.xml, but it does not print unmatched keys from b.xml.
a.xml
<root>
<metas>
<meta>
<name>x</name>
<value>0</value>
</meta>
<meta>
<name>y</name>
<value>1</value>
</meta>
<meta>
<name>z</name>
<value>1</value>
</meta>
</metas>
b.xml
<root>
<metas>
<info>
<name>a</name>
<value>0</value>
</info>
<info>
<name>y</name>
<value>1</value>
</info>
<info>
<name>z</name>
<value>1</value>
</info>
</metas>
Desired output:
Table
a.xml b.xml
name missing in a.xml a
value missing in a.xml 0
name x missing in b.xml
value 0 missing in b.xml
name y y
value 1 1
name z z
value 1 1
My xsl;
<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:param name="uri" as="xs:string" select="'b.xml'"/>
<xsl:param name="b" as="document-node()" select="doc($uri)"/>
<xsl:key name="bCompare" match="root/metas/info" use="name"/>
<xsl:template match="/">
<html>
<head>
<title>Test</title>
</head>
<body>
<table>
<tr>
<td></td>
<th>a.xml</th>
<th>b.xml</th>
</tr>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="metas">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="meta">
<xsl:variable name="compare" select="key('bCompare', name, $b)"/>
<tr>
<th>Name</th>
<td>
<xsl:value-of select="name"/>
</td>
<td>
<xsl:value-of select="$compare/name"/>
<xsl:if test="empty($compare/name)">missing in b.xml</xsl:if>
</td>
</tr>
<tr>
<th>Value</th>
<td>
<xsl:value-of select="value"/>
</td>
<td>
<xsl:value-of select="$compare/value"/>
<xsl:if test="empty($compare/value)">missing in b.xml</xsl:if>
</td>
</tr>
</xsl:template>
You need to define a key for the other direction as well, i.e. <xsl:key name="aCompare" match="root/metas/meta" use="name"/>, then you need to make sure you process those elements of the second document with e.g. <xsl:apply-templates/><xsl:variable name="main-doc" select="/"/><xsl:apply-templates select="$b//info/meta[not(name = key('bCompare', $main-doc//meta/name, $main-doc))]"/> and then you need a template
<xsl:template match="info">
<xsl:variable name="compare" select="key('aCompare', name, $main-doc)"/>
<tr>
<th>Name</th>
<td>
<xsl:value-of select="$compare/name"/>
<xsl:if test="empty($compare/name)">missing in a.xml</xsl:if>
</td>
<td>
<xsl:value-of select="name"/>
</td>
</tr>
<tr>
<th>Value</th>
<td>
<xsl:value-of select="$compare/value"/>
<xsl:if test="empty($compare/value)">missing in a.xml</xsl:if>
</td>
<td>
<xsl:value-of select="value"/>
</td>
</tr>
</xsl:template>

XSLT and Complex Xpath

This transformable HTML5:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<table border="1">
<caption>Complex Table</caption>
<tbody>
<tr>
<th>Title</th>
<th>Volume</th>
<th>Chapter</th>
<th>Stds.</th>
<th>Dept.</th>
</tr>
<tr>
<td rowspan="6">STEM</td>
<td rowspan="1">1</td>
<td rowspan="2">1</td>
<td>1 to 10</td>
<td rowspan="2">Biology</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>20 to 30</td>
</tr>
<tr>
<td rowspan="1">3</td>
<td rowspan="1">2</td>
<td>40 to 60</td>
<td rowspan="1">Chemistry</td>
</tr>
<tr>
<td>4</td>
<td>3</td>
<td>70 to 80</td>
<td>Physics</td>
</tr>
<tr>
<td rowspan="4">5</td>
<td rowspan="1">4</td>
<td>80 to 120</td>
<td rowspan="1">Math</td>
</tr>
<tr>
<td rowspan="1">5</td>
<td>120 to 135</td>
<td rowspan="1">Geometry</td>
</tr>
</tbody>
</table>
<table border="1">
<caption>Simpler Table</caption>
<tbody>
<tr>
<th>Title</th>
<th>Volume</th>
<th>Chapter</th>
<th>Stds.</th>
<th>Dept.</th>
</tr>
<tr>
<td colspan="1" rowspan="3">Kinesiology</td>
<td>1</td>
<td>1</td>
<td>A to C</td>
<td>Strength</td>
</tr>
<tr>
<td>2</td>
<td>2 to 3</td>
<td>D to H</td>
<td>Agility</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
<td>I to X</td>
<td>Flexibility</td>
</tr>
</tbody>
</table>
<table border="1">
<caption>Simplest Table</caption>
<tbody>
<tr>
<th>Title</th>
<th>Volume</th>
<th>Chapter</th>
<th>Stds.</th>
<th>Dept.</th>
</tr>
<tr>
<td>Skills</td>
<td>1</td>
<td>1</td>
<td>A to C</td>
<td>Keyboard</td>
</tr>
</tbody>
</table>
</body>
</html>
This desired output (if you view the rendered HTML, you can see the pattern of data wanted):
<?xml version="1.0" encoding="UTF-8"?>
<production>
<book title="STEM" volume="1"/>
<book title="STEM" volume="2"/>
<book title="STEM" volume="3"/>
<book title="STEM" volume="4"/>
<book title="STEM" volume="5"/>
<book title="Kinesiology" volume="1"/>
<book title="Kinesiology" volume="2"/>
<book title="Kinesiology" volume="3"/>
<book title="Skills" volume="1"/>
</production>
The not quite working transform:
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates/>
</catalog>
</xsl:template>
<xsl:template match="text()"/>
<!-- multi-volume edition -->
<xsl:template match="table">
<xsl:variable name="title" select="descendant::td[1]"/>
<xsl:variable name="context-td" select="."/>
<!-- the following needs work -->
<xsl:for-each select="descendant::tr/td[1][matches(.,'\d+$')]">
<book>
<xsl:attribute name="title" select="$title"/>
<xsl:attribute name="volume" select="."/>
</book>
</xsl:for-each>
</xsl:template>
<!-- single-volume edition -->
<xsl:template match="table[count(descendant::tr) < 3]">
<book>
<xsl:attribute name="title" select="descendant::td[1]"/>
<xsl:attribute name="volume" select="descendant::tr[2]/td[2]"/>
</book>
</xsl:template>
</xsl:stylesheet>
The xpath in for-each needs work. I've tried various axis but haven't found one that works across all use cases.
Couldn't this be simply:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates select="html/body/table"/>
</catalog>
</xsl:template>
<xsl:template match="table">
<xsl:variable name="title" select="tbody/tr[2]/td[1]"/>
<xsl:for-each select="tbody/tr[2]/td[2] | tbody/tr[position() > 2]/td[1]">
<book>
<xsl:attribute name="title" select="$title"/>
<xsl:attribute name="volume" select="."/>
</book>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Oops, I see there is a problem with volume 5 of STEM being listed twice - hold on...
No, I don't see a simple solution to this. I suspect you'd have to drill down into the table's structure, taking preceding rowspans into consideration - somewhat similar to:
Please suggest for XSLT code for Table rowspan and colspan issues
Edit:
Ok, I believe this should work:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates select="html/body/table"/>
</catalog>
</xsl:template>
<xsl:template match="table">
<xsl:apply-templates select="tbody/tr[2]/td[2]">
<xsl:with-param name="title" select="tbody/tr[2]/td[1]" tunnel="yes"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="td">
<xsl:param name="title" tunnel="yes"/>
<book>
<xsl:attribute name="title" select="$title"/>
<xsl:attribute name="volume" select="."/>
</book>
<xsl:variable name="rowspan" select="if(#rowspan) then #rowspan else 1" />
<xsl:apply-templates select="parent::tr/following-sibling::tr[number($rowspan)]/td[1]"/>
</xsl:template>
</xsl:stylesheet>
Test, applied to a modified input in the form of:
http://xsltransform.net/94hvTz1/2
I tried it with grouping:
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates select="//table"/>
</catalog>
</xsl:template>
<xsl:template match="table">
<xsl:for-each-group select="tbody/tr[position() gt 1]/td[1]" group-by="../../(tr[2]/td[2] | tr[position() gt 2]/td[1])">
<book title="{.}" volume="{current-grouping-key()}"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Would this help by any chance(little changes made to michael.hor257k's answer) :
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<catalog>
<xsl:apply-templates select="html/body/table"/>
</catalog>
</xsl:template>
<xsl:template match="table">
<xsl:variable name="title" select="tbody/tr[2]/td[1]"/>
<xsl:variable name="table-id" select="generate-id()"/>
<xsl:for-each select="tbody/tr[2]/td[2] | tbody/tr[position() > 2]/td[1]">
<xsl:variable name="curr-td" select="."/>
<xsl:if test="not(exists(following::tr[td[1][generate-id(../../..) = $table-id and . = $curr-td]]))">
<book>
<xsl:attribute name="title" select="$title"/>
<xsl:attribute name="volume" select="."/>
</book>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

No elements after XSL namespace added

Any help would be appreciated on this simple (I hope) problem. The xsl:for-each fails to find any elements after adding a namespace to this XML doc:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="cdsort.xsl"?>
<catalog xmlns="http://www.mycompany.com/test" >
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
</cd>
<cd>
<title>Tupelo Honey</title>
<artist>Van Morrison</artist>
</cd>
</catalog>
Here is the XSL code:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:vv="http://www.mycompany.com/test"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Artist</th>
</tr>
<xsl:for-each select="vv:catalog/cd">
<tr>
<td><xsl:value-of select="title" /></td>
<td><xsl:value-of select="artist" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
You've set the default namespace for the whole document so the child elements in the xpaths also need a prefix:
<xsl:for-each select="vv:catalog/vv:cd">
<tr>
<td><xsl:value-of select="vv:title" /></td>
<td><xsl:value-of select="vv:artist" /></td>
</tr>
</xsl:for-each>