I've just started using XSLT and am trying to figure this out.
Your help would be greatly appreciated.
Example XML file
<purchases>
<item id = "1" customer_id = "5">
</item>
<item id = "2" customer_id = "5">
</item>
<item id = "7" customer_id = "4">
</item>
</purchases>
Desired HTML Output:
<table>
<tr><th>Customer ID</th><th>Number of Items Purchased</th></tr>
<tr><td>5</td><td>2</td></tr>
<tr><td>4</td><td>1</td></tr>
</table>
Customer with id number 5 has bought 2 items.
Customer with id number 4 has bought 1 item.
XSLT 1.0 solution ....
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="purchases/item"/>
</table>
</xsl:template>
<xsl:template match="item">
<xsl:if test="not(
preceding-sibling::*[#customer_id=current()/#customer_id]
)">
<tr><td><xsl:value-of select="#customer_id"/></td>
<td><xsl:value-of select="count(following-sibling::item
[#customer_id=current()/#customer_id])+1"/></td>
</tr>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
A possible Xslt 1.0 solution would be
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:key name="kCustomer" match="item" use="#customer_id"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="purchases">
<xsl:element name="table">
<xsl:element name="tr">
<xsl:element name="th">Customer ID</xsl:element>
<xsl:element name="th">Number of Items Purchased</xsl:element>
<xsl:for-each select="item[generate-id(.) = generate-id(key('kCustomer', #customer_id))]">
<xsl:variable name="total" select="count(/purchases/item[#customer_id=current()/#customer_id]/#id)" />
<xsl:element name="tr">
<xsl:element name="td">
<xsl:value-of select="./#customer_id" />
</xsl:element>
<xsl:element name="td">
<xsl:value-of select="$total" />
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Adopted from Dimitres answer on How can I Group and sort based on sum.
As pointed out by Dimitre the purchases template can be simplified to
<xsl:template match="purchases">
<table>
<tr>
<th>Customer ID</th>
<th>Number of Items Purchased</th>
</tr>
<xsl:for-each select="item[generate-id(.) = generate-id(key('kCustomer', #customer_id))]">
<xsl:variable name="total" select="count(/purchases/item[#customer_id=current()/#customer_id]/#id)" />
<tr>
<td>
<xsl:value-of select="./#customer_id" />
</td>
<td>
<xsl:value-of select="$total" />
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
If you want XSLT 2.0 then the following XSLT should do the trick:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes"/>
<xsl:template match="/purchases">
<table>
<tr>
<th>Customer ID</th>
<th>Number of Items Purchased</th>
</tr>
<xsl:for-each-group select="item" group-by="#customer_id">
<tr>
<td>
<xsl:value-of select="current-grouping-key()"/>
</td>
<td>
<xsl:value-of select="count(current-group())"/>
</td>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
Related
Here is an excerpt from an xsl document (inside a template technically):
<table>
<tr>
<th>INSTANCE</th>
<th>SWVER</th>
<th>SYSTEMID</th>
<th>SYSTIME</th>
<th>SYSMEM</th>
<th>CUTMEM</th>
<th>FILEMEM</th>
<th>CALCONFIG</th>
</tr>
<tr>
<td><xsl:value-of select='#INSTANCE'/></td>
<td><xsl:value-of select='#SWVER'/></td>
<td><xsl:value-of select='#SYSTEMID'/></td>
<td><xsl:value-of select='#SYSTIME'/></td>
<td><xsl:value-of select='#SYSMEM'/></td>
<td><xsl:value-of select='#CUTMEM'/></td>
<td><xsl:value-of select='#FILEMEM'/></td>
<td><xsl:value-of select="#CALCONFIG"/></td>
</tr>
</table>
Is there some way that I can avoid the redundancy of writing out the attributes both as table headers and as the attribute selection? I cannot use external sources.
I was thinking I could define some xsl variable that contains a basic structure, as follows and generate the table from there.
<list>
<item>INSTANCE</item>
...
<item>CALCONFIG</item>
</list>
The XML data is just a bunch of tags, of the same value that contain at least the above listed attributes. Each tag looks something like this:
<THING INSTANCE="boop" SWVER="foo" SYSTEMID="123"
...
CALCONFIG="cal.cfg" SOMETHINGELSE="bar"
/>
To illustrate the point I made in a comment to your question, consider the following example:
XML
<root>
<item color="red" name="alpha" size="small" garbage="123" id="1"/>
<item color="green" name="bravo" size="medium" garbage="456" id="2"/>
<item color="blue" name="charlie" size="large" garbage="789" id="3"/>
</root>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://www.example.com/my"
exclude-result-prefixes="my">
<xsl:output method="html" encoding="utf-8"/>
<xsl:strip-space elements="*"/>
<my:columns>
<col>id</col>
<col>name</col>
<col>size</col>
<col>color</col>
</my:columns>
<xsl:variable name="columns" select="document('')/xsl:stylesheet/my:columns" />
<xsl:template match="/root">
<table border="1">
<tr>
<xsl:for-each select="$columns/col">
<th>
<xsl:value-of select="." />
</th>
</xsl:for-each>
</tr>
<xsl:for-each select="item">
<xsl:variable name="attributes" select="#*" />
<tr>
<xsl:for-each select="$columns/col">
<td>
<xsl:value-of select="$attributes[name()=current()]" />
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Result (rendered)
Bryant, you need to work with two files (XML and XSL):
First, you need to specify a template (example.xsl):
<?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 Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>instance</th>
<th>swver</th>
<th>systemid</th>
<th>systime</th>
<th>sysmem</th>
<th>cutmem</th>
<th>filemem</th>
<th>calconfig</th>
</tr>
<xsl:for-each select="catalog/cd">
<tr>
<td><xsl:value-of select="instance"/></td>
<td><xsl:value-of select="swver"/></td>
<td><xsl:value-of select="systemid"/></td>
<td><xsl:value-of select="systime"/></td>
<td><xsl:value-of select="sysmem"/></td>
<td><xsl:value-of select="cutmem"/></td>
<td><xsl:value-of select="filemem"/></td>
<td><xsl:value-of select="calconfig"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
And then you need to define your XML file (example.xml), based on the template (example.xsl):
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="example.xsl"?>
<catalog>
<cd>
<instance>instance1</instance>
<swver>swver1</swver>
<systemid>systemid1</systemid>
<sysmem>sysmem1</sysmem>
<systime>systime1</systime>
<cutmem>cutmem1</cutmem>
<filemem>filemem1</filemem>
<calconfig>calconfig1</calconfig>
</cd>
<cd>
<instance>instance2</instance>
<swver>swver2</swver>
<systemid>systemid2</systemid>
<sysmem>sysmem2</sysmem>
<systime>systime2</systime>
<cutmem>cutmem2</cutmem>
<filemem>filemem2</filemem>
<calconfig>calconfig2</calconfig>
</cd>
.
.
</catalog>
Result:
Reference:
http://www.w3schools.com/xsl/xsl_transformation.asp
Hope this helps!
The XML matrix with csv rows
<matrix ROWS="3" COLS="4">
<row>"1,2,3,4"</row>
<row>"5,6,7,8"</row>
<row>"9,10,11,12"</row>
</matrix>
is to be transformed with XSLT 1.0 without extensions into a HTML table
<table>
<tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
<tr><td>5</td><td>6</td><td>7</td><td>8</td></tr>
<tr><td>9</td><td>10</td><td>11</td><td>12</td></tr>
</table>
The number of rows and columns (ROWS, COLS) are variable.
I appreciate any help.
How about:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/matrix">
<table>
<xsl:for-each select="row">
<tr>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring(., 2, string-length(.) - 2)"/>
</xsl:call-template>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="','"/>
<xsl:variable name="token" select="substring-before(concat($text, $delimiter), $delimiter)" />
<xsl:if test="$token">
<td>
<xsl:value-of select="$token"/>
</td>
</xsl:if>
<xsl:if test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I have XML file:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<Data>
<Records>
<Record>
<AddInfo>
<Info>
</Info>
</AddInfo>
</Record>
</Records>
</Data>
and XSL file:
<?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="Dane">
<html>
<link rel="stylesheet" type="text/css" href="report.css"></link>
<body>
<h2>Table1</h2>
<table border="1" cellspacing="0">
<tr>
<th>XXX</th>
</tr>
<xsl:for-each select="Records/Record">
<tr>
<td>
<xsl:value-of select="XXX"/>
</td>
</tr>
</xsl:for-each>
</table>
<h2>SecondTable</h2>
<table border="1" cellspacing="0">
<tr>
<th>YYY</th>
<th>ZZZ</th>
</tr>
<xsl:for-each select="Records/Record/AddInfo/Info">
<tr>
<td>
<xsl:value-of select="YYY"/>
</td>
<td>
<xsl:value-of select="ZZZ"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
And I want to make it like this: if node exists, display table with "Info" nodes, and if not, display SOME TEXT.
I've been trying
<xsl:if test="following-sibling::AddInfo">
</xsl:if>
and
<xsl:if test="AddInfo">
</xsl:if>
But it is not working.
I want it like this:
Table1
---------------------
| | | |
(condition: if inside XML will be node, I want to display second table, under Table1)
SecondTable
-------------
| | |
How I can do this?
This outputs Yep if <AddInfo> exists as immediate child of <Record>, and Nope otherwise:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Data">
<xsl:for-each select="Records/Record">
<xsl:choose>
<xsl:when test="AddInfo">Yep</xsl:when>
<xsl:otherwise>Nope</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note that you don't need for-each, you should let a second template match each <Record>:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Data">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="Data/Records/Record">
<xsl:choose>
<xsl:when test="AddInfo">Yep</xsl:when>
<xsl:otherwise>Nope</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You could also avoid choose and use two independent if conditions:
<xsl:template match="Data/Records/Record">
<xsl:if test="AddInfo">Yep</xsl:if>
<xsl:if test="not(AddInfo)">Nope</xsl:if>
</xsl:template>
If you don't want to limit it to immediate children, use .//AddInfo instead.
Consider the following stylesheet:
<?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" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Data">
<xsl:apply-templates select="Records/Record"/>
</xsl:template>
<xsl:template match="Data/Records/Record">
<table class="one"></table>
<xsl:if test="AddInfo">
<table class="two"></table>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It outputs
<table class="one"></table>
if there's no <AddInfo> node in <Record>, and
<table class="one"></table>
<table class="two"></table>
otherwise.
You can solve this with neither using if nor choose. XML:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<Data>
<AddInfo>
<Info>This is ignored</Info>
</AddInfo>
<Records>
<Record>
<AddInfo>
<Info>One,</Info>
<Info>Two,</Info>
<Info>Three</Info>
</AddInfo>
</Record>
<Record>
<Info>Ignored as well</Info>
</Record>
<Record>
<Nested>
<AddInfo>
<Info>So is this</Info>
</AddInfo>
</Nested>
</Record>
</Records>
</Data>
XSLT:
<?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" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Data">
<root>
<xsl:apply-templates select="Records/Record"/>
</root>
</xsl:template>
<xsl:template match="Data/Records/Record">
<xsl:copy>
<table id="one"></table>
<xsl:apply-templates select="AddInfo"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Data/Records/Record/AddInfo">
<table id="two">
<xsl:apply-templates select="Info"/>
</table>
</xsl:template>
<xsl:template match="Data/Records/Record/AddInfo/Info">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<Record>
<table id="one" />
<table id="two">One,Two,Three</table>
</Record>
<Record>
<table id="one" />
</Record>
<Record>
<table id="one" />
</Record>
</root>
To check if node exist in xml this XSLT code works
<xsl:choose>
<xsl:when test="XMLNodeName">
<Cell ss:Index="2" ss:StyleID="s110">
<Data ss:Type="String">
<xsl:value-of select="NodeOne"/>
</Data>
</Cell>
</xsl:when>
<xsl:otherwise>
<Cell`enter code here` ss:Index="2" ss:StyleID="s110">
<Data ss:Type="String">
<xsl:value-of select="NodeTwo"/>
</Data>
</Cell>
</xsl:otherwise>
</xsl:choose>
I have an xml coming as below
<Envelope>
<content>
<feild1>1</feild1>
....
<feild10>10<feild10>
</content>
</Envelope>
but want the xml to be (add the namespace prefix as xs1 and content lower case c to Upper case "C"
<Envelope>
<Content> <!-- Note the lower case c chnaged to Upper Case --->
<xs1:feild1>1</xs1:feild1> <!-- and for the feild, xs1 is added as prefix -->
....
<xs1:feild10>10</xs1:feild10>
</Content>
</Envelope>
Please help looking for XSLT 1.0
Here is my XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs1="https://temp">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:element name="{name()}" namespace="https://temp">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Hope this helps
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs1="https://temp">
<xsl:template match="/">
<xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<Envelope>
<xsl:for-each select="Envelope/*">
<xsl:variable name="root" select="local-name(.)"/>
<xsl:variable name="temp" select="translate($root,$smallcase,$uppercase)"/>
<xsl:variable name="temp2" select="concat(substring($temp,1,1),translate(substring($temp,2,string-length($temp)),$uppercase,$smallcase))"/>
<xsl:element name="{$temp2}">
<xsl:for-each select="child::*">
<xsl:element name="xs1:{local-name(.)}" namespace="https://temp">
<xsl:value-of select="node()"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</Envelope>
</xsl:template>
</xsl:stylesheet>
I am working on improving on some stylesheets that I have inherited and converting them from using <xsl:for-each> to <xsl:apply-templates>. A very simplified version of one of the XML files I will be working with is:
<Root>
<Row ID="123" Region="AMS">
<First>Graham</First>
<Last>Smith</Last>
<Sales>12345.85</Sales>
<Team>Team A</Team>
</Row>
<Row id="321">
<First>John</First>
<Last>Brown</Last>
<Sales>18765.85</Sales>
<Team>Team C</Team>
</Row>
<Row id="456" Region="EMEA">
<First>Anne</First>
<Last>Jones</Last>
<Sales>34567.85</Sales>
<Team>Team B</Team>
</Row>
</Root>
The new stylesheet I have is:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes"/>
<xsl:variable name="RowCount" select="count(/*/*)"/>
<xsl:template match="/#* | node()">
<style>
body * {font-family:Arial;font-size:11pt}
table {border-collapse:collapse}
td {border-bottom:1px solid #D8D8D8;padding:7px}
tr.row1 {background:#F9F9F9;}
td.tdHeader {border-bottom:2px solid #DDD;font-weight:700}
</style>
<table>
<thead>
<tr>
<xsl:apply-templates select="*[1]/#*" mode="headerAttributes" />
<xsl:apply-templates select="*[1]/*" mode="headerFields"/>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="*"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/#*" mode="headerAttributes">
<td class="tdHeader">
<xsl:value-of select="name()" />
</td>
</xsl:template>
<xsl:template match="/*/*/*" mode="headerFields">
<td class="tdHeader">
<xsl:value-of select="name()" />
</td>
</xsl:template>
<xsl:template match="/*/*">
<tr class="row{position() mod 2}">
<xsl:apply-templates select="#*" mode="attributes"/>
<xsl:apply-templates select="*" mode="fields"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/#*" mode="attributes">
<td>
<xsl:value-of select="." />
</td>
</xsl:template>
<xsl:template match="/*/*/*" mode="fields">
<td>
<xsl:value-of select="." />
</td>
</xsl:template>
</xsl:stylesheet>
but, due to the second node in the XML is missing the <Region> attribute, the cells on the result are mis-aligned, with first name now in the Region column, Last name in First name column and so on. This also happens if there is a missing child node on the Row node. for example, no team element
I have tried to test for a missing node, before calling the apply-template, and within the last two templates, but to no avail.
Any ideas? What am I missing here? I am only just starting to get my head around using apply-templates, but other methods of writing stylesheets I am fine with.
Provisionally, this works for your input:
<xsl:template match="Row">
<tr class="row{position() mod 2}">
<td><xsl:apply-templates select="#ID|#id" mode="attr2"/></td>
<td><xsl:apply-templates select="#Region" mode="attr2"/></td>
<xsl:apply-templates select="*" mode="fields"/>
</tr>
</xsl:template>
<xsl:template match="#ID|#id|#Region" mode="attr2">
<b><xsl:value-of select="." /></b>
</xsl:template>
-- and you may remove the catch-all for mode="attributes".
This forces the inclusion of a <td>..</td> pair even if there is no id|ID or Region attribute.
Saxon 8.8 reports an error:
The attribute axis starting at a document-node() node will never select anything
because of your
<xsl:template match="/#* | node()">
Changing it to <xsl:template match="node()">, or, preferably, to <xsl:template match="Root"> will fix this. As I said in my comment, try to use * as little as possible. References to Root and Row can be changed to exact matches.