XSLT XML to HTML Crosstab table - html

I am trying to make a crosstab tables from the XML file from ToDoList (http://www.abstractspoon.com/tdl_resources.html) Here is an example XML file:
<?xml version="1.0" encoding="utf-16" ?>
<TODOLIST PROJECTNAME="Projects">
<TASK TITLE="proj1" ID="1" NUMPERSON="1" PERSON="Chris" NUMTAGS="1" TAG="Caro" CALCTIMEESTIMATE="36"/>
<TASK TITLE="proj2" ID="2" NUMPERSON="1" PERSON="Chris" NUMTAGS="1" TAG="Nat" CALCTIMEESTIMATE="8" />
<TASK TITLE="proj4" ID="4" NUMPERSON="1" PERSON="Chris" NUMTAGS="1" TAG="Caro" CALCTIMEESTIMATE="36" />
<TASK TITLE="proj5" ID="5" NUMPERSON="1" PERSON="Sahb" NUMTAGS="1" TAG="Nat" CALCTIMEESTIMATE="128" />
<TASK TITLE="proj32" ID="32" NUMPERSON="2" PERSON="Seb" PERSON1="Chris" NUMTAGS="1" TAG="Nat" CALCTIMEESTIMATE="0.90" />
</TODOLIST>
Each task is an element and all the info are attributes. I want to make a table like this with the tags as the top row and the persons as the first column.
<table>
<tr>
<td></td>
<td>Caro</td>
<td>Nat</td>
</tr>
<tr>
<td>Chris</td>
<td>72</td>
<td>8</td>
</tr>
<tr>
<td>Sahb</td>
<td>128</td>
<td>0</td>
</tr>
<tr>
<td>Seb, Chris</td>
<td>0</td>
<td>9</td>
</tr>
</table>
As you can see the same person can have many projects and I want to add up the CALCTIMEESTIMATE for each person based on the tag(s). I can get the first row with
<xsl:key name="dtag" match="/TODOLIST/TASK/#TAG" use="." />
<xsl:for-each select="TASK/#TAG[generate-id() = generate-id(key('dtag', .)[1])]">
<td>
<xsl:value-of select="."/>
</td>
</xsl:for-each>
But the sum function does not give the desired results. What I'm I missing? Is there an easy way to do the sum based on two attributes? ToDoList uses XSLT 1.0, that's why I couldn't use distinct-values.
Here is the full XSLT, I've tried alot of different thing, Here I was trying to get it working just for Chris but it would not add up his 2 projects.
<xsl:template match="TODOLIST">
<xsl:text disable-output-escaping="yes"><!DOCTYPE html></xsl:text>
<html>
<head>
<title>Projects..</title>
</head>
<body>
<table>
<caption>Projects..</caption>
<thead>
<tr>
<td></td>
<xsl:for-each select="TASK/#TAG[generate-id() = generate-id(key('dtag', .)[1])]">
<th scope="col">
<xsl:value-of select="."/>
</th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="TASK/#PERSON[generate-id() = generate-id(key('dperson', .)[1])]" />
</tbody>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="TASK/#PERSON">
<tr>
<th scope="row">
<xsl:value-of select="."/>
</th>
<xsl:apply-templates select="/TODOLIST/TASK/#TAG[generate-id() = generate-id(key('dtag', .)[1])]" />
</tr>
</xsl:template>
<xsl:template match="/TODOLIST/TASK/#TAG">
<td>
<xsl:value-of select="sum(../#CALCTIMEESTIMATE[../#PERSON = 'Chris'])" />
</td>
</xsl:template>

To get your sum to work, you need to know both the current person and the current tag.
In your current template that matches TASK/#PERSON, you should pass the current value of the attribute to the next template
<xsl:apply-templates
select="/TODOLIST/TASK/#TAG[generate-id() = generate-id(key('dtag', .)[1])]">
<xsl:with-param name="PERSON" select="." />
</xsl:apply-templates>
Then, in your template that matches #TAG, you could access the 'dperson' key in the SUM, so you sum all tasks for that person, and for the current tag, like so:
<xsl:template match="/TODOLIST/TASK/#TAG">
<xsl:param name="PERSON" />
<td>
<xsl:value-of
select="sum(key('dperson', $PERSON)[../#TAG = current()]/../#CALCTIMEESTIMATE)"/>
</td>
</xsl:template>

Related

Turn XML with XSLT to aHTML order Table

i need yout help. I'm struggle with XSLT: I have for example an XML file like this:
<Adresses>
<Person id="1">
<Name>Bott</Name>
<Vorname>Nils</Vorname>
</Person>
<Person id="2">
<Name>Hubert</Name>
<Vorname>Hubertus</Vorname>
</Person>
<Person id="3">
<Name>Large</Name>
<Vorname>Lars</Vorname>
</Person>
<Numbers>
<number>
<id>1</id>
<tel>12354</tel>
</number>
<number>
<id>3</id>
<tel>32354</tel>
</number>
<number>
<id>2</id>
<tel>22354</tel>
</number>
</Numbers>
</Adresses>
And i have to transform to a order HTML Table.
The HTML Table should look like folowwing (Ordering after ID):
<table>
<tr>
<th>Name</th>
<th>Vorname</th>
<th>Tel</th>
</tr>
<tr>
<th>Bott</th>
<th>Nils</th>
<th>12354</th>
</tr>
</table>
My Problem is i don't know how to ckeck: Choose the number where Peron.id = number.id .... It would be realy great if someone could give me a full example for this problem. At web search it is always not so complicated in the tutorials ;-( .... I'm learning XSLT and just know some rules like apply-templates match="Person" .... but how is it possible in such a statement to call a other template wich select the coorect number?
I try to start with that:
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="Adresses/Person">
<xsl:call-template name="curPos">
</xsl:call-template>
<xsl:value-of select="Name"/>
<xsl:value-of select="Vorname"/>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template name="curPos" match="Person">
<xsl:value-of select="position()"/><br> </br>
</xsl:template>
My Problem is i don't know how to ckeck: Choose the number where
Peron.id = number.id ....
That's exactly what keys are for:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="num" match="number" use="id" />
<xsl:template match="/Adresses">
<html>
<body>
<table>
<tr>
<th>Name</th>
<th>Vorname</th>
<th>Tel</th>
</tr>
<xsl:for-each select="Person">
<tr>
<td>
<xsl:value-of select="Name"/>
</td>
<td>
<xsl:value-of select="Vorname"/>
</td>
<td>
<xsl:value-of select="key('num', #id)/tel"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Note that your table structure assumes each person has only one phone number (as in your example). However, the XML structure suggests there is a one-to-many relationship between persons and phone numbers, so you might want to consider changing:
<td>
<xsl:value-of select="key('num', #id)/tel"/>
</td>
to:
<td>
<xsl:for-each select="key('num', #id)">
<xsl:value-of select="tel"/>
<br/>
</xsl:for-each>
</td>
I have got the following XSLT 2.0 solution for you:
<xsl:template name="makeTable">
<table>
<tr>
<th>Name</th>
<th>Vorname</th>
<th>Tel</th>
</tr>
<xsl:for-each select="/Adresses/Person">
<tr>
<td><xsl:value-of select="Name"/></td>
<td><xsl:value-of select="Vorname"/></td>
<td><xsl:value-of select="../Numbers/number[id = current()/#id]/tel"/></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
Now, for the explanation as far as I understand the problem you have is finding the correct XPath expression for selecting the correct telephone number. The most important code snipped in this case is this line:
<td>
<xsl:value-of select="../Numbers/number[id = current()/#id]/tel"/>
</td>
In XSLT 2.0 - I'm not sure about XSLT 1.0 - you can refer to the current loop element with the current() function in an XPath.
To select the telephone number I filter for the number on the condition of its id being equal to the current loop elements attribute id and then getting this node's child tel to get the actual number.

XSL to transform XML to HTML, filter elements from multiple structures for the same element value

I am a newcomer to XML/XSLT and, so far, in searching SO and the W3SChools sites for clever stuff on Meunchian grouping I haven't been able to grasp an effective solution to my challenge.
Essentially I have one large XML file exported from a database (which means I can't directly edit the XML) which contains invoicing information.
I want to utilise XSLT (1.0) to apply a transformation of the XML into HTML (I am using Saxon) so that each invoice is displayed as a table. However, in the XML, there are many product lines which relate to the same invoice (denoted by the <invoiceNum> element as the identifier).
I don't want to have to create & display a new table for each product line which is part of the same invoice. In my current XSL file, I am trying to create a table for the first instance of a duplicated <invoiceNum> element, and then add only the unique elements from successive product lines (<ProductID>, <ProductName>, <ProductDescription> etc.) and leave out the duplicated shipping information.
In the XML code snippet, you can see the layout of the XML. In the XSL snippet, I hope you can see how I am trying to construct the table. For each successive <invoices_snet> instance in the XML file, I want to add just the product information to the table through the transformation. Using for-each on the <invoices_snet> element simply creates a new table each time.
Should I use conditional logic here, compare the equality of the value of the <invoices_snet> element, use templates?
Your help is much appreciated!
<database>
<invoices>
<invoices_snet>
<invoiceNum NAME="invoiceNum" TYPE="SMALLINT">368</invoiceNum>
<ProductID NAME="ProductID" TYPE="VARCHAR">SS106</ProductID>
<ProductName NAME="ProductName" TYPE="VARCHAR">Senna Sunglasses</ProductName>
<ProductDescription NAME="ProductDescription" TYPE="VARCHAR">Lively sunglasses</ProductDescription>
<Quantity NAME="Quantity" TYPE="SMALLINT">34</Quantity>
<UnitPrice NAME="UnitPrice" TYPE="CURRENCY">40.0000</UnitPrice>
<ExtendedPrice NAME="ExtendedPrice" TYPE="CURRENCY">1360.0000</ExtendedPrice>
<contactName NAME="contactName" TYPE="VARCHAR">Jeff</contactName>
<shippingStreet NAME="shippingStreet" TYPE="VARCHAR">11 Acacia Avenue</shippingStreet>
<shippingCity NAME="shippingCity" TYPE="VARCHAR">Huddersfield</shippingCity>
<shippingCounty NAME="shippingCounty" TYPE="VARCHAR">Yorkshire</shippingCounty>
<shippingPostcode NAME="shippingPostcode" TYPE="VARCHAR">YO12 8LA</shippingPostcode>
<saleDate NAME="salesDate" TYPE="DATETIME">30. Mar. 16</saleDate>
</invoices_snet>
<invoices_snet>
<invoiceNum NAME="invoiceNum" TYPE="SMALLINT">368</invoiceNum>
<ProductID NAME="ProductID" TYPE="VARCHAR">SS368</ProductID>
<ProductName NAME="ProductName" TYPE="VARCHAR">Senna T-shirts</ProductName>
<ProductDescription NAME="ProductDescription" TYPE="VARCHAR">T-shirts of beige colour with cream piping</ProductDescription>
<Quantity NAME="Quantity" TYPE="SMALLINT">20</Quantity>
<UnitPrice NAME="UnitPrice" TYPE="CURRENCY">15.00</UnitPrice>
<ExtendedPrice NAME="ExtendedPrice" TYPE="CURRENCY">300.00</ExtendedPrice>
<contactName NAME="contactName" TYPE="VARCHAR">Jeff</contactName>
<shippingStreet NAME="shippingStreet" TYPE="VARCHAR">11 Acacia Avenue</shippingStreet>
<shippingCity NAME="shippingCity" TYPE="VARCHAR">Huddersfield</shippingCity>
<shippingCounty NAME="shippingCounty" TYPE="VARCHAR">Yorkshire</shippingCounty>
<shippingPostcode NAME="shippingPostcode" TYPE="VARCHAR">YO12 8LA</shippingPostcode>
<saleDate NAME="salesDate" TYPE="DATETIME">30. Mar. 16</saleDate>
</invoices_snet>
</invoices>
</database>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/database">
<html>
<head>
<title>Invoice</title>
<link rel="stylesheet" href="invoicingCSS.css"/>
</head>
<body>
<h1>Invoice</h1>
<br></br>
<xsl:for-each select="invoices/invoices_snet">
<h2>Order for Invoice Number: <xsl:value-of select="invoiceNum"/> </h2>
<table>
<tr>
<th>Product ID</th>
<th>Product Name</th>
<th>Product Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Extended Price</th>
<th>Contact Name</th>
<th>Shipping Address</th>
<th>Sales Date</th>
</tr>
<tr>
<td>
<xsl:value-of select="ProductID"/>
</td>
<td>
<xsl:value-of select="ProductName"/>
</td>
<td>
<xsl:value-of select="ProductDescription"/>
</td>
<td>
<xsl:value-of select="Quantity"/>
</td>
<td>
<xsl:value-of select="UnitPrice"/>
</td>
<td>
<xsl:value-of select="ExtendedPrice"/>
</td>
<td>
<xsl:value-of select="contactName"/>
</td>
<td>
<xsl:value-of select="concat(
shippingStreet,' ',
shippingCity,', ',
shippingCounty,', ',
shippingPostcode)"
/>
</td>
<td>
<xsl:value-of select="saleDate"/>
</td>
</tr>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Grouping is much easier to do in XSLT 2.0, with its built-in xsl:for-each-group instruction.
Try this as your starting point:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="theader">
<tr>
<th>ProductID</th>
<th>Quantity</th>
<!-- add more as required -->
</tr>
</xsl:variable>
<xsl:template match="/database">
<html>
<xsl:for-each-group select="invoices/invoices_snet" group-by="invoiceNum">
<table border="1">
<xsl:copy-of select="$theader"/>
<xsl:apply-templates select="current-group()"/>
</table>
</xsl:for-each-group>
</html>
</xsl:template>
<xsl:template match="invoices_snet">
<tr>
<td>
<xsl:value-of select="ProductID"/>
</td>
<td>
<xsl:value-of select="Quantity"/>
</td>
<!-- add more as required -->
</tr>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="invoices_snet">
<tr>
<td>
<xsl:value-of select="ProductID"/>
</td>
<td>
<xsl:value-of select="Quantity"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
First do you self a favor and have a look for xslt grouping for XSLT 1.0 this will be muenchian grouping e.g. this
And here the basic structure for a adaption to your xml:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:key name="kinvoices_snet" match="invoices_snet" use="invoiceNum"/>
<xsl:template match="invoices">
<xsl:for-each select="invoices_snet [
count ( key('kinvoices_snet',./invoiceNum)[1] | . ) = 1 ]">
<xsl:variable name="this" select="." />
<g>
<gh>
<xsl:value-of select="invoiceNum" />
</gh>
<!-- group stuff -->
<xsl:for-each select=" key('kinvoices_snet',$this/invoiceNum)">
<!-- group member stuff -->
<gm>
<xsl:value-of select="ProductID" />
</gm>
</xsl:for-each>
</g>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Convert XML List to HTML Table Using XSL

Hello I was struggling with this for the past days and I could not find a good answer nor solution. I have an XML file with a list of objects like this:
<?xml version="1.0" encoding="UTF-8"?>
<LineItems>
<TableName>Lines</TableName>
<TableTerm>Lines</TableTerm>
<LineItems>
<Class>A Class</Class>
</LineItems>
<LineItems>
<Number>1234</Number>
</LineItems>
<LineItems>
<Description>G</Description>
</LineItems>
<LineItems>
<Class>B Class</Class>
</LineItems>
<LineItems>
<Number>5678</Number>
</LineItems>
<LineItems>
<Description>F</Description>
</LineItems>
<ColumnMetadata>
<Name>Class</Name>
<Term>Class</Term>
</ColumnMetadata>
<ColumnMetadata>
<Name>Number</Name>
<Term>No</Term>
</ColumnMetadata>
<ColumnMetadata>
<Name>Description</Name>
<Term>Description</Term>
</ColumnMetadata>
</LineItems>
I am applying the following transformaiton:
<?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>
<xsl:variable name="columns" select="count(LineItems/ColumnMetadata)" />
<xsl:variable name="items" select="count(LineItems/LineItems)" />
<xsl:variable name="rows" select="$items div $columns" />
<table border="1">
<thead >
<tr bgcolor="#9acd32">
<xsl:for-each select="LineItems/ColumnMetadata">
<th style="padding: .3em 0;">
<xsl:value-of select="Term" />
</th>
</xsl:for-each>
</tr>
</thead>
<tbody style="text-align: center;">
<xsl:for-each select="(//LineItems)[position()<=$rows]">
<xsl:variable name="i" select="position() - 1"/>
<tr>
<xsl:for-each select="(//*)[position()<=$columns]">
<xsl:variable name="j" select="position()+($columns*$i)"/>
<td style="padding: .3em 0;">
<xsl:value-of select="LineItems/LineItems[$j]" />
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Finally the desire output for this case would be:
<table>
<thead>
<tr>
<th>Class</th>
<th>No</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>A Class</td>
<td>1234</td>
<td>G</td>
</tr>
<tr>
<td>B Class</td>
<td>5678</td>
<td>F</td>
</tr>
</tbody>
</table>
This case is a case of MxN table, I cna know how many columns from nodes. So, to sumarise:
The actual list that have to be transformed as a table is all inside root <LineItems>.
I don't know how many items (rows) I am going to get, but I can calculate them dividing amount of <LineItems> nodes ($items) by amount of <ColumnMetadata> nodes ($columns)
Nodes like <Class>, <Number> and <Description>, are columns in table, but they can have other names, they are dynamic and can be 5, 6... Many columns.
If I transform above XML with XSL in online tools I only get the header row of the table (and inspecting HTML, I can see 2 rows for 2 items, but empty). If I use Visual Studio transformation tool, I not only get header row, I also get first 2 columns of table (in this example Class values) but not the values in the rest of them. I really don't understand what is going on and why do I get different results using different tools.
Thanks in advance
lineitmes folllow a regular pattern.
Then I believe it could be simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/LineItems">
<xsl:variable name="columns" select="count(ColumnMetadata)"/>
<table border="1">
<thead >
<tr>
<xsl:for-each select="ColumnMetadata">
<th>
<xsl:value-of select="Term"/>
</th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:for-each select="LineItems[position() mod $columns = 1]">
<tr>
<xsl:for-each select=". | following-sibling::LineItems[position() < $columns]">
<td>
<xsl:value-of select="*"/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>

Creating semantic tables with rowspan via xslt

I have following xml document:
...
<x>
<symptom><descr></descr></symptom>
<cause></cause>
<solution></solution>
<cause></cause>
<solution></solution>
</x>
...
In my document I have several <x>
In each <x> I have only one <symptom> and n <cause> and <solution> whereby the amount of <cause> and <solution> is always the same.
I want to get following autmatically generated structure:
<table>
<tr>
<td rowspan=count(cause)><xsl:value-of select="symptom/descr"></td>
<td><xsl:value-of select="cause"></td>
<td><xsl:value-of select="symptom"></td>
<tr>
<tr>
<td><xsl:value-of select="cause"></td>
<td><xsl:value-of select="symptom"></td>
<tr>
...
</table>
I tried following code, which I know is totally wrong. But I'm stuck since several hours and couldn't find any good solution on the internet.
<xsl:for-each select="cause">
<tr>
<td rowspan="count(.)">
<xsl:value-of select="../descr[1]"/>
</td>
<td>
<xsl:value-of select="."/>
</td>
<xsl:for-each select="../solution">
<td>
<xsl:value-of select="."/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
You're on the right lines with one tr per cause, how about this:
<xsl:template match="x">
<table>
<xsl:for-each select="cause">
<!-- the index of this cause within the list of causes in the current x -->
<xsl:variable name="pos" select="position()" />
<tr>
<!-- first cause - create the spanning symptom cell -->
<xsl:if test="$pos = 1">
<td rowspan="{last()}"><xsl:value-of select="../symptom/descr"/></td>
</xsl:if>
<!-- this cause -->
<td><xsl:value-of select="." /></td>
<!-- the matching solution -->
<td><xsl:value-of select="../solution[$pos]" /></td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
A trick here is the last() function, which returns the total number of nodes that the current for-each (or apply-templates) is processing, which in this case is precisely the number of rows you want to span.
This is based on the assumption that you want as result a table with following structure: Given an example input XML
<x>
<symptom>
<descr>
Description
</descr>
</symptom>
<cause>
Cause 1
</cause>
<solution>
Solution 1
</solution>
<cause>
Cause 2
</cause>
<solution>
Solution 2
</solution>
</x>
I assume you want following table:
<table>
<tr>
<td rowspan="2">Description</td>
<td>Cause 1</td>
<td>Solution 1</td>
</tr>
<tr>
<td>Cause 2</td>
<td>Solution 2</td>
</tr>
</table>
This could be done with following XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="x">
<table>
<xsl:for-each select="cause">
<xsl:apply-templates select="." mode="row">
<xsl:with-param name="amount" select="count(../cause)"/>
<xsl:with-param name="position" select="position()"/>
</xsl:apply-templates>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="cause" mode="row">
<xsl:param name="amount"/>
<xsl:param name="position"/>
<tr>
<xsl:if test="$position = 1">
<td rowspan="{$amount}">
<xsl:value-of select="//symptom/descr"/>
</td>
</xsl:if>
<td>
<xsl:value-of select="."/>
</td>
<td>
<xsl:value-of select="following-sibling::solution"/>
</td>
</tr>
</xsl:template>
</xsl:transform>
For each cause a row is created by applying the template
<xsl:template match="cause" mode="row">
with the amount of rows and the the position of the current cause as parameters. If the position is 1, the description is written as value in a td with the amount of cause as value of rowspan.
Each row contains the value of the current cause:
<td>
<xsl:value-of select="."/>
</td>
and the value of the solution at the same position (the solution which is the following-sibling of the current cause):
<td>
<xsl:value-of select="following-sibling::solution"/>
</td>

How to get attribute name and its value properly?

Input:
<?xml version="1.0" encoding="utf-8" ?>
<Software>
<MS version="5.2.3.1"/>
<Java version="5.1.0.29" />
<Oracle id="A" version="1.0.1.11" />
<SQL id="P" version="1.0.1.11" />
</Software>
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<table>
<tr>
<xsl:for-each select="//*[1]">
<xsl:for-each select="#*">
<td>
<xsl:value-of select="name()"/>
</td>
</xsl:for-each>
</xsl:for-each>
</tr>
<xsl:for-each select="//*">
<tr>
<xsl:value-of select="local-name()"/> ::
<xsl:for-each select="#*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Output:
Current output [wrong]:
This script gives output without column name
"id". Some "version" column value added in "id(not visible)" column as
there is no attribute name id in node. eg. MS node has version
attribute only hence output result added version info in "id" column.
Please go through output once, possibly save and check in html for proper understanding.
<table>
<tr>
<td/>
<td>
<td>version</td>
</td>
</tr>
<tr>
<td>Software ::
</td>
</tr>
<tr>
<td>MS ::
<td>5.2.3.1</td></td>
</tr>
<tr>
<td>Java ::
<td>5.1.0.29</td></td>
</tr>
<tr>
<td>Oracle ::
<td>A</td><td>1.0.1.11</td></td>
</tr>
<tr>
<td>SQL ::
<td>P</td><td>1.0.1.11</td></td>
</tr>
</table>
Expected output:
Every attribute as columnname/header and all column
have their own value. NOTE: Attributes MS, JAVA, etc as column Name
should not be hard coded because the number of attributes may change on runtime.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title></title>
</head>
<body>
<table>
<tr>
<td/>
<td>
id<td>version</td>
</td>
</tr>
<tr>
<td>MS ::</td>
<td>Not exist</td><td>5.2.3.1</td>
</tr>
<tr>
<td>Java ::</td>
<td>Not exist</td><td>5.1.0.29</td>
</tr>
<tr>
<td>Oracle ::</td>
<td>A</td><td>1.0.1.11</td>
</tr>
<tr>
<td>SQL ::</td>
<td>P</td><td>1.0.1.11</td>
</tr>
</table>
</body>
</html>
If you are looking to output one column for each possible attribute name, and not restrict it to just id and version, then (in XSLT 1.0) you could make use of a technique called Muenchian Grouping to get the distinct attribute names.
First define a key to look up the attributes by their name
<xsl:key name="columns" match="#*" use="local-name()" />
Then, to get the distinct ones (or rather, get the first occurrence of each distinct name), you can define a variable like so
<xsl:variable name="columns" select="//#*[generate-id() = generate-id(key('columns', local-name())[1])]" />
Then, to output the column headers, you can just iterate over this variable
<xsl:for-each select="$columns">
<xsl:sort select="local-name()" />
<td><xsl:value-of select="local-name()" /></td>
</xsl:for-each>
For each row, you would take a similar approach. Assuming you were positioned on a child element, you could first define a variable that contains the attributes of the current element. Then you would iterate over the columns variable again, and output the value of the corresponding attribute for the current element
<xsl:variable name="attributes" select="#*" />
<xsl:for-each select="$columns">
<xsl:sort select="local-name()" />
<td><xsl:value-of select="$attributes[local-name() = local-name(current())]" /></td>
</xsl:for-each>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:key name="columns" match="#*" use="local-name()" />
<xsl:variable name="columns" select="//#*[generate-id() = generate-id(key('columns', local-name())[1])]" />
<xsl:template match="Software">
<table>
<tr>
<td>Software</td>
<xsl:for-each select="$columns">
<xsl:sort select="local-name()" />
<td><xsl:value-of select="local-name()" /></td>
</xsl:for-each>
</tr>
<xsl:apply-templates select="*" />
</table>
</xsl:template>
<xsl:template match="Software/*">
<tr>
<td><xsl:value-of select="local-name()" /></td>
<xsl:variable name="attributes" select="#*" />
<xsl:for-each select="$columns">
<xsl:sort select="local-name()" />
<td><xsl:value-of select="$attributes[local-name() = local-name(current())]" /></td>
</xsl:for-each>
</tr>
</xsl:template>
</xsl:stylesheet>
This should output the following
<table>
<tr>
<td>Software</td>
<td>id</td>
<td>version</td>
</tr>
<tr>
<td>MS</td>
<td/>
<td>5.2.3.1</td>
</tr>
<tr>
<td>Java</td>
<td/>
<td>5.1.0.29</td>
</tr>
<tr>
<td>Oracle</td>
<td>A</td>
<td>1.0.1.11</td>
</tr>
<tr>
<td>SQL</td>
<td>P</td>
<td>1.0.1.11</td>
</tr>
</table>
Note, for brevity, I have not included code to output "Not exists" in the case where the attribute does not exist. But it should be simple enough to add a check for this. (Just store the value in a variable, and use an xsl:choose)
EDIT: If you want to restrict the attributes to specifically be for Software elements too, try changing the key to this:
<xsl:key name="columns" match="Software/*/#*" use="local-name()" />
Hummm, I have tried to fix the HTML issue and the problem you are facing.
This is a bit mannual, but can achieve your requirement. Here is the XSL:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<table>
<tr>
<td></td>
<td>id</td>
<xsl:for-each select="//*[1]">
<xsl:for-each select="#*">
<td><xsl:value-of select="name()"/></td>
</xsl:for-each>
</xsl:for-each>
</tr>
<xsl:for-each select="//*">
<xsl:if test="position() > 1">
<tr>
<td><xsl:value-of select="local-name()"/> ::</td>
<td>
<xsl:choose>
<xsl:when test="#id"><xsl:value-of select="#id"/></xsl:when>
<xsl:otherwise>Not Exist</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:if test="#version"><xsl:value-of select="#version"/></xsl:if>
</td>
</tr>
</xsl:if>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Here is the output of it:
<table>
<tr>
<td/>
<td>id</td>
<td>version</td>
</tr>
<tr>
<td>MS ::</td>
<td>Not Exist</td>
<td>5.2.3.1</td>
</tr>
<tr>
<td>Java ::</td>
<td>Not Exist</td>
<td>5.1.0.29</td>
</tr>
<tr>
<td>Oracle ::</td>
<td>A</td>
<td>1.0.1.11</td>
</tr>
<tr>
<td>SQL ::</td>
<td>P</td>
<td>1.0.1.11</td>
</tr>
</table>
I have tested it on http://www.xslfiddle.net/