How to build a two-colum HTML table with XSLT? - xslt

I have an xml document that contains a list of titles. I need to loop through the title and build a two-column table dynamically.
XML Document
<documents>
<titles>
<title>One</title>
<title>two</title>
<title>three</title>
<title>four</title>
<title>five</title>
<title>six</title>
<title>eight</title>
</titles>
</documents>
XSLT
I know it's wrong and I am not that good with XSLT loops.
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:variable name="newline"><xsl:text>
</xsl:text></xsl:variable>
<xsl:template match="/">
<xsl:value-of select="$newline"/>
<html>
<xsl:value-of select="$newline"/>
<head>
<xsl:value-of select="$newline"/>
<xsl:comment> Sitemap 1.0 </xsl:comment>
<xsl:value-of select="$newline"/>
</head>
<xsl:value-of select="$newline"/>
<body>
<xsl:apply-templates/>
<xsl:value-of select="$newline"/>
</body>
<xsl:value-of select="$newline"/>
</html>
</xsl:template>
<xsl:template match="titles">
<table>
<thead>
<tr>
<th>hello</th>
<th>hello2</th>
</tr>
</thead>
<tbody>
<xsl:for-each select="title">
<tr>
<xsl:value-of select="."/>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
Actual Result
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- Sitemap 1.0 -->
</head>
<body>
<documents>
<table>
<thead>
<tr>
<th>hello</th>
<th>hello2</th>
</tr>
</thead>
<tbody>
<tr>
<td>One</td>
</tr>
<tr>
<td>two</td>
</tr>
<tr>
<td>three</td>
</tr>
<tr>
<td>four</td>
</tr>
<tr>
<td>five</td>
</tr>
<tr>
<td>six</td>
</tr>
<tr>
<td>eight</td>
</tr>
</tbody>
</table>
</documents>
</body>
</html>
Expected Result
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- Sitemap 1.0 -->
</head>
<body>
<documents>
<table>
<thead>
<tr>
<th>hello</th>
<th>hello2</th>
</tr>
</thead>
<tbody>
<tr>
<td>One</td>
<td>two</td>
</tr>
<tr>
<td>three</td>
<td>four</td>
</tr>
<tr>
<td>five</td>
<td>six</td>
</tr>
<tr>
<td>eight</td>
</td>
</tr>
</tbody>
</table>
</documents>
</body>
</html>
How can I build a two-column table when the selection is always a single/the same node()?

Change your for-loop to this
<xsl:for-each select="title[position() mod 2 = 1]">
<tr>
<td>
<xsl:value-of select="."/>
</td>
<td>
<xsl:value-of select="following-sibling::title[1]"/>
</td>
</tr>
</xsl:for-each>
It selects every second element and then outputs also the following title element.

Related

Using XSLT how can I get my output to repeat instead of only returning the first instance?

I'm working on an XSLT script to output an HTML table containing data from an XML file but my resulting document is only giving me the first set when I need each set.
This is my XML:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE map PUBLIC "-//KPE//DTD DITA KPE Map//EN" "kpe-map.dtd" []>
<map>
<title><ph conref="../../titles/sec_s63_title_l1.dita#sec_s63_title_l1/topic_title"/></title>
<topicref href="../questions/sec_question_00260_1.dita">
<topicsubject keyref="sec_s63_los_1"/>
</topicref>
<topicref href="../questions/sec_question_00260_2.dita">
<topicsubject keyref="sec_s63_los_1"/>
</topicref>
<topicref href="../questions/sec_question_00260_3.dita">
<topicsubject keyref="sec_s63_los_1"/>
</topicref>
</map>
This is my 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="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="/">
<html>
<body>
<h2></h2>
<table border="1">
<tr>
<td><xsl:value-of select="//topicref/#href"/></td>
<td><xsl:value-of select="//topicref/topicsubject/#keyref"/></td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This is the output I'm getting:
<html>
<body>
<h2></h2>
<table border="1">
<tr>
<td>../questions/sec_question_00260_1.dita</td>
<td>sec_s63_los_1</td>
</tr>
</table>
</body>
</html>
This is what I'm trying to get:
<html>
<body>
<h2></h2>
<table border="1">
<tr>
<td>../questions/sec_question_00260_1.dita</td>
<td>sec_s63_los_1</td>
</tr>
<tr>
<td>../questions/sec_question_00260_2.dita</td>
<td>sec_s63_los_1</td>
</tr>
<tr>
<td>../questions/sec_question_00260_3.dita</td>
<td>sec_s63_los_1</td>
</tr>
</table>
</body>
</html>
Where is my script off? Thanks in advance for any help!
I think you want something along the lines of
<xsl:template match="/">
<html>
<body>
<h2></h2>
<table border="1">
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="topicref">
<tr>
<td><xsl:value-of select="#href"/></td>
<td><xsl:value-of select="topicsubject/#keyref"/></td>
</tr>
</xsl:template>
Try it this way:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/map">
<html>
<body>
<h2></h2>
<table border="1">
<xsl:for-each select="topicref">
<tr>
<td><xsl:value-of select="#href"/></td>
<td><xsl:value-of select="topicsubject/#keyref"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
See the Repetition section in XSLT specification.

how locate an element in source xml file dynamically in a loop

if we have the following xml input:
<root xmlns:ns="http://www.blabla">
<ns:element1 attribute="attr1">10</ns:element1>
<ns:element1 attribute="attr2">20</ns:element1>
<ns:element2 attribute="attr1">30</ns:element1>
<ns:element2 attribute="attr2">40</ns:element1>
</root>
how can we locate each element inside a for-each in xslt?
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Column 1</th>
<th style="text-align:left">Column 2</th>
</tr>
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<tr>
<td>
<xsl:value-of select="???what should be here???[lower-case(#attribute)='attr0']"/>
</td>
<td>
<xsl:value-of select="???same question???[lower-case(#attribute)='attr1']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
Please note that I want to follow this procedure (if possible) as the input is very dynamic and we don't always get all elements in every row.
I appreciate your help.
The expression you want is this...
<xsl:value-of select="*[local-name() = $name][lower-case(#attribute)='attr1']"/>
... Except this will fail with an error along the lines of Required item type of context item for the child axis is node(); supplied expression (.) has item type xs:integer, due to the code being executed in the context of the xsl:for-each on atomic values. To get around this, you will need to save a reference to the child elements in a variable before the xsl:for-each.
Try this XSLT
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Title</th>
</tr>
<xsl:variable name="children" select="*" />
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<tr>
<td>
<xsl:value-of select="$children[local-name() = $name][lower-case(#attribute)='attr1']"/>
</td>
<td>
<xsl:value-of select="$children[local-name() = $name][lower-case(#attribute)='attr2']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Or slightly better, to reduce code duplication...
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Title</th>
</tr>
<xsl:variable name="children" select="*" />
<xsl:for-each select="1 to 10">
<xsl:variable name="name" select="concat('element', .)"/>
<xsl:variable name="element" select="$children[local-name() = $name]"/>
<tr>
<td>
<xsl:value-of select="$element[lower-case(#attribute)='attr1']"/>
</td>
<td>
<xsl:value-of select="$element[lower-case(#attribute)='attr2']"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Note, I would really consider changing your input XML if you have control over it. Numbering elements using the element name makes it harder to manipulate. Ideally you would do this instead...
<ns:element num="1" attribute="attr1">10</ns:element>
If you dont have control try to take count of the elements and run the loop as per count like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://www.blabla">
<xsl:template match="root">
<html>
<body>
<h2>My Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Column 1</th>
<th style="text-align:left">Column 2</th>
</tr>
<xsl:variable name="all-element" select="count(//*:element)"/>
<xsl:for-each select="*[concat('ns:element', (1 to $all-element))]">
<xsl:message select="."/>
<tr>
<td>
<xsl:value-of select="#attribute"/>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

xsl is there a way to generate a unique id for containing element and get it to the javascript

Find the following line below in the xsl:
var tdElem = document.getElementById('???') <!-- Would like to get td here, if possible -->
Is it possible to generate a unique id for the containing td and concat that into my javascript function?
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 CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Title</th>
<th style="text-align:left">Artist</th>
</tr>
<xsl:for-each select="catalog/cd">
<tr>
<td style="background-color: 'can_be_any_color_here'">
<xsl:value-of select="title"/>
<script>
var tdElem = document.getElementById('???') <!-- Would like to get td here, if possible -->
var bgColor = tdElem.style.backgroundColor;
var textColor = mycolorcontrastfx(bgcolor);
tdElem.style.color = textColor;
</script>
</td>
<td>
<xsl:value-of select="artist"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
xml
<?xml-stylesheet type="text/xsl" href="blackorwhite.xslt"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
</cd>
<cd>
<title>Hide your heart</title>
</cd>
</catalog>
You can use generate-id to generate a unique ID for an XML node
<xsl:variable name="id" select="concat('CDTableCell', generate-id())" />
Or, in this particular case, you make do with using just the position of the cd selected
<xsl:variable name="id" select="concat('CDTableCell', position())" />
To assign the id to the td node, you can use Attribute Value Templates
<td id="{$id}" style="..." />
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<script>
function mycolorcontrastfx(bgColor)
{
return '#000000';
}
</script>
</head>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th style="text-align:left">Title</th>
<th style="text-align:left">Artist</th>
</tr>
<xsl:for-each select="catalog/cd">
<tr>
<xsl:variable name="id" select="concat('CDTableCell', position())" />
<td id="{$id}" style="background-color:#FFFFFF">
<xsl:value-of select="title"/>
<script>
var tdElem = document.getElementById('<xsl:value-of select="$id" />')
var bgColor = tdElem.style.backgroundColor;
var textColor = mycolorcontrastfx(bgColor);
tdElem.style.color = textColor;
</script>
</td>
<td>
<xsl:value-of select="artist"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Xsl count based on Filtered node

I have the following xml:
<Activity>
<item>
<task>XXX</task>
<assignto>User1</assignto>
</item>
<item>
<task>YYY</task>
<assignto>User2</assignto>
</item>
<item>
<task>ZZZ</task>
<assignto>User1</assignto>
</item>
<team>
<member>User1</member>
<member>User2</member>
<team>
</Activity>
I want to generate using XSL a count of task per member in the team.
User- Count
user1- 2
user2- 1
so far I have the following XSL:
<?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="/">
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<xsl:for-each select="Activity/team/member">
<tr>
<td><xsl:value-of select="node()" /></td>
<td><xsl:value-of select="count(/Activity/item[assignto='user1'])" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
so far I hardcoded 'user1', I would like to filter based on the current member in the for each loop.
Can someone help, please?
Thanks,
here you go, store the member in a variable and test on that variable. You also have an error in your source XML, you need /team.
<xsl:template match="/">
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<xsl:for-each select="Activity/team/member">
<xsl:variable name="assignto">
<xsl:value-of select="."/>
</xsl:variable>
<tr>
<td><xsl:value-of select="node()" /></td>
<td><xsl:value-of select="count(/Activity/item[assignto=$assignto])" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
output is:
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<tr>
<td>User1</td>
<td>2</td>
</tr>
<tr>
<td>User2</td>
<td>1</td>
</tr>
</table>
</body>
</html>
Just replace your reference to 'user1' with a reference to the current node at the start of the XPath address, which is done using the current() function:
t:\ftemp>type activity.xml
<Activity>
<item>
<task>XXX</task>
<assignto>User1</assignto>
</item>
<item>
<task>YYY</task>
<assignto>User2</assignto>
</item>
<item>
<task>ZZZ</task>
<assignto>User1</assignto>
</item>
<team>
<member>User1</member>
<member>User2</member>
</team>
</Activity>
t:\ftemp>call xslt activity.xml activity.xsl
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<tr>
<td>User1</td>
<td>2</td>
</tr>
<tr>
<td>User2</td>
<td>1</td>
</tr>
</table>
</body>
</html>
t:\ftemp>type activity.xsl
<?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="/">
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<xsl:for-each select="Activity/team/member">
<tr>
<td><xsl:value-of select="node()" /></td>
<td><xsl:value-of select="count(/Activity/item[assignto=current()])" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem Done!
If you want to filter based on the current member, you can just use the "current()" function:
<xsl:value-of select="count(/Activity/item[assignto=current()])" />
However, you might benefit from using a key here, to make the counting more efficient. First define your key like so:
<xsl:key name="item" match="item" use="assignto" />
Then you can write your count like so:
<xsl:value-of select="count(key('item', .))" />
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item" match="item" use="assignto" />
<xsl:template match="/">
<html>
<body>
<table>
<tr>
<th>User</th>
<th>Task Count</th>
</tr>
<xsl:for-each select="Activity/team/member">
<tr>
<td><xsl:value-of select="node()" /></td>
<td><xsl:value-of select="count(key('item', .))" /></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

XSLT Retrieving Attribute Info to Display in table

Hi I'm new to XSLT and I'm trying to display the value of a parent node along with the values of my data.
I have this XML..
<?xml-stylesheet type="text/xsl" href="Sample.xsl"?>
<DataView Client="Client1" ID="1000" TimeStamp="12/7/2011 5:35:09 PM">
<Group ID="5000" Name="GroupName1">
<SubGroup ID="7000" Order="0" Name="NameIWant1">
<Data ID="1" Name="DataName1" Order="0">1</Data>
<Data ID="2" Name="DataName2" Order="0">2</Data>
<Data ID="3" Name="DataName3" Order="0">3</Data>
<Data ID="12" Name="DataName4" Order="0">4</Data>
</SubGroup>
<SubGroup ID="8000" Order="0" Name="NameIWant2">
<Data ID="1" Name="DataName1" Order="0">6</Data>
<Data ID="2" Name="DataName2" Order="0">7</Data>
<Data ID="3" Name="DataName3" Order="0">8</Data>
<Data ID="12" Name="DataName4" Order="0">9</Data>
</SubGroup>
</Group>
</DataView>
Ive written the basic XSL to walk the values
<?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="/">
<html>
<body>
<h2>My Data</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>DataName1</th>
<th>DataName2</th>
<th>DataName3</th>
<th>DataName4</th>
</tr>
<xsl:for-each select="DataView/Group/SubGroup">
<tr>
<xsl:for-each select="Data">
<td><xsl:value-of select="."/></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
How do I retrieve and display the attribute value of Subgroup "Name" so my table looks like this...
MyData
NameIWant1 1 2 3 4
NameIWant2 6 7 8 9
Any help is very much appreciated!!
The simple, short answer is to add the following just before the inner for-each loop:
<td><xsl:value-of select="#Name"/></td>
You're already in the context of a DataView/Group/SubGroup node, so you just need to use the attribute axis specifier (#) to select one of its attributes by name.
However, (as usual) I think this is better expressed using individual templates. The for-each loop is almost never necessary in XSLT. The following stylesheet produces the desired result:
<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="/">
<html>
<body>
<h2>My Data</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Subgroup</th>
<th>DataName1</th>
<th>DataName2</th>
<th>DataName3</th>
<th>DataName4</th>
</tr>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="SubGroup">
<tr>
<td><xsl:value-of select="#Name"/></td>
<xsl:apply-templates/>
</tr>
</xsl:template>
<xsl:template match="Data">
<td><xsl:apply-templates/></td>
</xsl:template>
</xsl:stylesheet>
Output:
<html>
<body>
<h2>My Data</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Subgroup</th>
<th>DataName1</th>
<th>DataName2</th>
<th>DataName3</th>
<th>DataName4</th>
</tr>
<tr>
<td>NameIWant1</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
</tr>
<tr>
<td>NameIWant2</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
</tr>
</table>
</body>
</html>
<table border="1">
<thead>
<tr bgcolor="#9acd32">
<th>SubGroupName</th>
<th>DataName1</th>
<th>DataName2</th>
<th>DataName3</th>
<th>DataName4</th>
</tr>
</thead>
<tbody>
<xsl:for-each select="DataView/Group/SubGroup">
<tr>
<td>
<xsl:value-of select="#Name"/>
</td>
<xsl:for-each select="Data">
<td><xsl:value-of select="."/></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>