XML to HTML table with XSLT with dynamic headings - xslt

My question is a bit similar to XML to HTML table with XSLT .
I have a dictionary defined as follows in XML:
<dictionary>
<languages>
<language>en</language>
<language>ja</language>
</languages>
<entries>
<entry id="1">
<en>Test</en>
<ja>テスト</ja>
</entry>
<entry id="2">
<en>Test2</en>
<ja>テスト2</en>
</entry>
</entries>
</dictionary>
And I would like the following output in XHTML:
<table>
<thead>
<tr>
<th>en</th>
<th>ja</th>
</tr>
</thead>
<tbody>
<tr>
<td>Test</td>
<td>テスト</td>
</tr>
<tr>
<td>Test2</td>
<td>テスト2</td>
</tr>
</tbody>
</table>
I adapted the answer from XML to HTML table with XSLT as follows:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="//dictionary/entries">
<table><xsl:apply-templates select="entry"/></table>
</xsl:template>
<xsl:template match="entry[1]">
<thead><tr><xsl:apply-templates select="*" mode="header"/></tr></thead>
<xsl:call-template name="standardRow"/>
</xsl:template>
<xsl:template match="entry" name="standardRow">
<tbody><tr><xsl:apply-templates select="*"/></tr></tbody>
</xsl:template>
<xsl:template match="entry/*">
<td><xsl:apply-templates select="node()"/></td>
</xsl:template>
<xsl:template match="entry/*" mode="header">
<th><xsl:value-of select="name()"/></th>
</xsl:template>
</xsl:stylesheet>
The thing is that I might have inputs as follows:
<dictionary>
<languages>
<language>en</language>
<language>ja</language>
<language>id</language>
</languages>
<entries>
<entry id="1">
<en>Test</en>
<ja>テスト</ja>
</entry>
<entry id="2">
<ja>テスト2</ja>
<en>Test2</en>
<id>uji2</id>
</entry>
</entries>
</dictionary>
As you might have understood, XSLT takes the first entry node to define the column names and the column id is not generated. Moreover, if the language order is changed in entry the <td> do not appear in order.
With the input above, I would like the following output:
<table>
<thead>
<tr>
<th>en</th>
<th>ja</th>
<th>id</th>
</tr>
</thead>
<tbody>
<tr>
<td>Test</td>
<td>テスト</td>
<td></td>
</tr>
<tr>
<td>Test2</td>
<td>テスト2</td>
<td>Uji2</td>
</tr>
</tbody>
</table>
This is my first time using XSLT and I do not really know how I could do this. I guess I could use the languages node. Please note that the XML input format is flexible and I would welcome any suggestions even if I need to change the format.

Here is a sample stylesheet:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes"/>
<xsl:key name="k1" match="entry/*" use="concat(generate-id(..), '|', local-name())"/>
<xsl:variable name="languages" select="/dictionary/languages/language"/>
<xsl:template match="dictionary">
<xsl:apply-templates select="entries"/>
</xsl:template>
<xsl:template match="entries">
<table>
<thead>
<tr>
<xsl:apply-templates select="$languages" mode="header"/>
</tr>
</thead>
<tbody>
<xsl:apply-templates/>
</tbody>
</table>
</xsl:template>
<xsl:template match="language" mode="header">
<th>
<xsl:value-of select="."/>
</th>
</xsl:template>
<xsl:template match="entry">
<tr>
<xsl:apply-templates select="$languages">
<xsl:with-param name="entry" select="current()"/>
</xsl:apply-templates>
</tr>
</xsl:template>
<xsl:template match="language">
<xsl:param name="entry"/>
<td>
<xsl:value-of select="key('k1', concat(generate-id($entry), '|', .))"/>
</td>
</xsl:template>
</xsl:stylesheet>

Related

XSLT to show a unique object name

My XML input:
<result>
<objects name="wf1">
<object>
<qname>wf</qname>
<object_name>Nr 1</object_name>
<person_name>Anton</person_name>
</object>
<object>
<qname>wf</qname>
<object_name>Nr 2</object_name>
<person_name>Ben</person_name>
</object>
</objects>
<objects name="wf2">
<object>
<qname>wf</qname>
<object_name>Nr 2</object_name>
<person_name>Chris</person_name>
</object>
<object>
<qname>wf</qname>
<object_name>Nr 3</object_name>
<person_name>Dirk</person_name>
</object>
</objects>
</result>
Things to note
There are two blocks with information: wf1 and wf2
Both have an object with object_name = 'Nr 2'
All objects have qname = 'wf'
My XSLT:
<xsl:template match="/">
<xsl:call-template name="output_html" />
</xsl:template>
<xsl:template name="output_html">
<html>
<body >
<table>
<tr>
<th>Object name</th>
<th>Person name</th>
</tr>
<xsl:apply-templates select="result/objects/object[qname='wf']" />
</table>
</body>
</html>
</xsl:template>
<xsl:template match="object[*]">
<tr align="center" valign="top">
<td><xsl:value-of select="object_name"/></td>
<td><xsl:value-of select="person_name"/></td>
</tr>
</xsl:template>
Output:
Object name
Person name
Nr 1
Anton
Nr 2
Ben
Nr 2
Chris
Nr 3
Dirk
My wish:
I would like to see unique object names. If an object with a certain name appears in both wf1 and wf2 then only the one from wf2 should be shown.
So the desired output would be:
Object name
Person name
Nr 1
Anton
Nr 2
Chris
Nr 3
Dirk
The information "Ben" gets lost. That is fine.
Does anybody have ideas about how to achieve that in XSLT 1.0?
In XSLT 1.0, it would be best to adapt the Muenchian method to the current problem:
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:key name="obj-by-name" match="object" use="object_name" />
<xsl:template match="/result">
<html>
<body >
<table>
<!-- header -->
<tr>
<th>Object name</th>
<th>Person name</th>
</tr>
<!-- for each distinct object_name -->
<xsl:for-each select="objects/object[count(. | key('obj-by-name', object_name)[1]) = 1]">
<tr>
<td>
<xsl:value-of select="object_name"/>
</td>
<td>
<!-- sort the group with wf2 on top -->
<xsl:for-each select="key('obj-by-name', object_name)">
<xsl:sort select="number(../#name='wf2')" data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:value-of select="person_name"/>
</xsl:if>
</xsl:for-each>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
If <objects name="wf2"> will always come after <objects name="wf1">, then you can shorten this to:
<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:key name="obj-by-name" match="object" use="object_name" />
<xsl:template match="/result">
<html>
<body >
<table>
<!-- header -->
<tr>
<th>Object name</th>
<th>Person name</th>
</tr>
<!-- for each distinct object_name -->
<xsl:for-each select="objects/object[count(. | key('obj-by-name', object_name)[1]) = 1]">
<tr>
<td>
<xsl:value-of select="object_name"/>
</td>
<td>
<xsl:value-of select="key('obj-by-name', object_name)[last()]/person_name"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

XSLT Transformation incorrectly

Below is my XSLT and i need to consider all the "Table" tags from XML which has "XYZ" as text in it.
<xsl:template match="/">
<xsl:element name="Root">
<xsl:apply-templates select= "Document"/>
</xsl:element>
</xsl:template>
<xsl:template match="//Table[.//TH[contains(text(), 'XYZ')]]">
<xsl:copy-of select="spext:Converter(., 'true', './TR[5]', '2')"/>
</xsl:template>
Below is my Input XML snippet. This will call method in c# for extracting specific tags from "Table" element passed by XSLT.
<Document>
<Table>
<TR>
<TH>
XYZ
</TH>
<TH>
PQR
</TH>
</TR>
<TR>
<TD>
Test data 1
</TD>
<TD>
Test data 1
</TD>
</TR>
</Table>
<Part>
<p>Hello World! This is some random text!</p>
</Part>
<Part>
<Table>
<TR>
<TH>
XYZ
</TH>
<TH>
PQR
</TH>
</TR>
<TR>
<TD>
Test data 3
</TD>
<TD>
Test data 4
</TD>
</TR>
</Table>
<p>Random Test after!</p>
</Part>
</Document>
My actual transformation output is below
<Root>
<Table>
<XYZ>Test data 1</XYZ>
<PQR>Test data 2</PQR>
</Table>Hello World! This is some random text!
<Table>
<XYZ>Test data 3</XYZ>
<PQR>Test data 4</PQR>
</Table>Random Test after!
</Root>
But desired output should be below
<Root>
<XYZ>
Test data 1
</XYZ>
<PQR>
Test data 2
</PQR>
<XYZ>
Test data 3
</XYZ>
<PQR>
Test data 4
</PQR>
</Root>
XSLT should only consider "Table" elements nothing else but here other tags text is also getting transformed and thus XML is not formed correctly.enter code here
Any help would be appreciated!
A pure XSLT approach to transform the presented input sample to the desired output sample would be
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Document">
<Root>
<xsl:apply-templates select="//Table[TR/TH[contains(., 'XYZ')]]/TR[TD]"/>
</Root>
</xsl:template>
<xsl:template match="TR">
<xsl:apply-templates select="TD"/>
</xsl:template>
<xsl:template match="TD">
<xsl:param name="pos" select="position()"/>
<xsl:element name="{normalize-space(../../TR[1]/TH[$pos])}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/ehVZvvL

Parse through multiple nodes using xslt

Below is my input xml
<Record>
<Header>
<field1/>
</Header>
<Body>
<firstname>x1</firstname>
<lastname>y1</lastname>
<company>Test1</company>
<Body>
<Body>
<firstname>x2</firstname>
<lastname>y2</lastname>
<company></company>
<Body>
<Body>
<firstname>x3</firstname>
<lastname>y3</lastname>
<company>Test2</company>
<Body>
</Record>
I am trying to loop through body and check if the company value is blank,to output the corresponding first name and last name.This whole output,I am mapping to DATA on the target using xslt mapper.Can someone help me with the below code which is not working
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="Namespace">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="NAME" select="/RecordSet/Body"/>
<xsl:variable name="break"><br></xsl:variable>
<xsl:variable name="tableB"><table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0" ></xsl:variable>
<xsl:variable name="tableE"></table></xsl:variable>
<xsl:variable name="trB"><tr></xsl:variable>
<xsl:variable name="trE"></tr></xsl:variable>
<xsl:variable name="tdB"><td></xsl:variable>
<xsl:variable name="tdE"></td></xsl:variable>
<xsl:variable name="nbsp">&nbsp;</xsl:variable>
<xsl:variable name="thB"><tr BGCOLOR="#CCCCCC"></xsl:variable>
<xsl:template match="/">
<DATA>
<xsl:value-of select="$tableB"/>
<xsl:value-of select="$thB"/>
<xsl:value-of select="$tdB"/><B>FirstName</B>
<xsl:value-of select="$nbsp"/>
<xsl:value-of select="$tdE"/>
<xsl:value-of select="$tdB"/><B>LASTNAME </B>
<xsl:value-of select="$nbsp"/>
<xsl:value-of select="$tdE"/>
<xsl:value-of select="$trE"/>
<xsl:value-of select="$trB"/>
<xsl:for-each select="$NAME/Body[string-length(company) > 0]">
<xsl:value-of select="$tdB"/>
<xsl:value-of select="$MT_NAME/firstname"/>
<xsl:value-of select="$nbsp"/>
<xsl:value-of select="$tdE"/>
<xsl:value-of select="$tdB"/>
<xsl:value-of select="$MT_NAME/lastname"/>
<xsl:value-of select="$nbsp"/>
<xsl:value-of select="$tdE"/>
<xsl:if test="position() mod 2 = 0">
<xsl:value-of select="$trE"/>
</xsl:if>
</xsl:for-each>
<xsl:value-of select="$tableE"/>
</DATA>
The output should be
|FIRSTNAME|LASTNAME|
| X2 | Y2 |
The provided code doesn't produce HTML at all -- it produces strings -- one-dimensional text.
Also, AFAIK, DATA isn't an HTML element.
Also, the provided "XML" is severely malformed.
Here is an example how to produce an HTML table with XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<table>
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Company</td>
</tr>
</thead>
<xsl:apply-templates/>
</table>
</xsl:template>
<xsl:template match="Body">
<tr>
<xsl:apply-templates select="*"/>
</tr>
</xsl:template>
<xsl:template match="Body/*">
<td> </td>
</xsl:template>
<xsl:template match="Body/*[normalize-space()]">
<td><xsl:value-of select="."/></td>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (the provided severely malformed text -- corrected):
<Record>
<Header>
<field1/>
<Body>
<firstname>x1</firstname>
<lastname>y1</lastname>
<company>Test1</company>
</Body>
<Body>
<firstname>x2</firstname>
<lastname>y2</lastname>
<company></company>
</Body>
<Body>
<firstname>x3</firstname>
<lastname>y3</lastname>
<company>Test2</company>
</Body>
</Header>
</Record>
A meaningful and sensible HTML table is produced:
<table>
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Company</td>
</tr>
</thead>
<tr>
<td>x1</td>
<td>y1</td>
<td>Test1</td>
</tr>
<tr>
<td>x2</td>
<td>y2</td>
<td> </td>
</tr>
<tr>
<td>x3</td>
<td>y3</td>
<td>Test2</td>
</tr>
</table>
I have to take a guess at your requirements as they are not very well explained.
This XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<DATA>
<table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0">
<tr BGCOLOR="#CCCCCC">
<th>First name</th>
<th>Last name</th>
<th>Company</th>
</tr>
<xsl:apply-templates select="*/Body" />
</table>
</DATA>
</xsl:template>
<xsl:template match="Body">
<tr>
<td><xsl:value-of select="firstname" /></td>
<td><xsl:value-of select="lastname" /></td>
<td>
<xsl:choose>
<xsl:when test="company!=''" >
<xsl:value-of select="company" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(firstname,' ',lastname)" />
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
... when applied on this document...
<Record>
<Header>
<field1/>
</Header>
<Body>
<firstname>x1</firstname>
<lastname>y1</lastname>
<company>Test1</company>
</Body>
<Body>
<firstname>x2</firstname>
<lastname>y2</lastname>
<company/>
</Body>
<Body>
<firstname>x3</firstname>
<lastname>y3</lastname>
<company>Test2</company>
</Body>
</Record>
...will yield...
<DATA>
<table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0">
<tr BGCOLOR="#CCCCCC">
<th>First name</th>
<th>Last name</th>
<th>Company</th>
</tr>
<tr>
<td>x1</td>
<td>y1</td>
<td>Test1</td>
</tr>
<tr>
<td>x2</td>
<td>y2</td>
<td>x2 y2</td>
</tr>
<tr>
<td>x3</td>
<td>y3</td>
<td>Test2</td>
</tr>
</table>
</DATA>
Note
Notice that the middle record has a cell value of 'x2 y2' for Company as per stated requirements when the input Company is empty or missing.
Update
The input document is still badly malformed. In consideration of the OP's updated requirements, this XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<DATA>
<table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0">
<tr BGCOLOR="#CCCCCC">
<th>First name</th>
<th>Last name</th>
</tr>
<xsl:apply-templates select="*/Body[company='']" />
</table>
</DATA>
</xsl:template>
<xsl:template match="Body">
<tr>
<td><xsl:value-of select="firstname" /></td>
<td><xsl:value-of select="lastname" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>
...when applied to the same input document as before, yields...
<DATA>
<table Border="1" BorderColor="#000000" cellpadding="4" cellspacing="0">
<tr BGCOLOR="#CCCCCC">
<th>First name</th>
<th>Last name</th>
</tr>
<tr>
<td>x2</td>
<td>y2</td>
</tr>
</table>
</DATA>

How do you link two elements together when they have the same name in xslt?

I have these trees, one with this structure /cars/car and the second /maker/cars/car. The first one has a reference to the id of the second list of cars.
<xsl:template match="t:cars/t:car">
<tr>
<td>
<xsl:if test="position()=1">
<b><xsl:value-of select="../#name"/><xsl:text> </xsl:text></b>
</xsl:if>
</td>
</tr>
I have this, it was filled in with a for loop I learn after a bit that I could't do it.
This is what it was before:
<xsl:template match="t:cars/t:car">
<tr>
<td>
<xsl:if test="position()=1">
<b><xsl:value-of select="../#name"/><xsl:text> </xsl:text></b>
</xsl:if>
<xsl:for-each select="/t:root/t:maker/t:car">
<xsl:if test="t:root/t:maker/#id = #ref">
<xsl:value-of select="#title"/>
</xsl:if>
</xsl:for-each>
</td>
</tr>
sample:
auto>
<maker type="toyota">
<car name="prius" id="1"/>
</maker>
<cars name="My Collection">
<car ref="1" />
</cars>
This simple transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kCarById" match="maker/car" use="#id"/>
<xsl:template match="/*">
<table>
<xsl:apply-templates/>
</table>
</xsl:template>
<xsl:template match="cars/car">
<tr>
<td>
<b>
<xsl:value-of select="key('kCarById', #ref)/#name"/>
</b>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (the provided one, just extended a little):
<auto>
<maker type="toyota">
<car name="prius" id="1"/>
</maker>
<maker type="honda">
<car name="accord" id="2"/>
</maker>
<maker type="benz">
<car name="mercedes" id="3"/>
</maker>
<cars name="My Collection">
<car ref="2" />
<car ref="3" />
</cars>
</auto>
produces the wanted, correct result:
<table>
<tr>
<td>
<b>accord</b>
</td>
</tr>
<tr>
<td>
<b>mercedes</b>
</td>
</tr>
</table>
Explanation: Appropriate use of keys.

XSLT and tables: setting number of cells in a row

The XML:
<data>
<table id="returns">
<each>
<name>1 year</name>
<value>17.01062531999216</value>
</each>
<each>
<name>3 years</name>
<value>18.01062531999216</value>
</each>
<each>
<name>5 years</name>
<value>21.01062531999216</value>
</each>
<each>
<name>Since inception</name>
<value>12.01062531999216</value>
</each>
</table>
</data>
The XSL I have been trying:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<tr>
<xsl:for-each select="data/table[#id='returns']/each">
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="value"/></td>
<xsl:if test="//each[position() mod 2 = 0]">
<xsl:text disable-output-escaping="yes"></tr><tr></xsl:text>
</xsl:if>
</xsl:for-each>
</tr>
</xsl:template>
</xsl:stylesheet>
The result I want:
<table>
<tr>
<td>1 year</td>
<td>17.01062531999216</td>
<td>3 years</td>
<td>18.01062531999216</td>
</tr>
<tr>
<td>5 years</td>
<td>21.01062531999216</td>
<td>Since inception</td>
<td>12.01062531999216</td>
</tr>
</table>
I'm embarrassed to say how long I have been working on this. Suffice to say the brute force, try-everything-multiple-times technique didn't work.
A shorter and correct solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="table">
<table>
<xsl:apply-templates/>
</table>
</xsl:template>
<xsl:template match="each[position() mod 2 = 1]">
<tr>
<xsl:apply-templates select="(.|following-sibling::each[1])/*"/>
</tr>
</xsl:template>
<xsl:template match="name|value">
<td><xsl:apply-templates/></td>
</xsl:template>
<xsl:template match="each[position() mod 2 = 0]"/>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<data>
<table id="returns">
<each>
<name>1 year</name>
<value>17.01062531999216</value>
</each>
<each>
<name>3 years</name>
<value>18.01062531999216</value>
</each>
<each>
<name>5 years</name>
<value>21.01062531999216</value>
</each>
<each>
<name>Since inception</name>
<value>12.01062531999216</value>
</each>
</table>
</data>
the wanted, correct result is produced:
<table>
<tr>
<td>1 year</td>
<td>17.01062531999216</td>
<td>3 years</td>
<td>18.01062531999216</td>
</tr>
<tr>
<td>5 years</td>
<td>21.01062531999216</td>
<td>Since inception</td>
<td>12.01062531999216</td>
</tr>
</table>
To render 2 cells per row.
Select every second node starting with the first. For these, render a row containing self and the following cell. If the last row does not contain 2 each elements, finish the row with empty cells.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="table">
<table>
<xsl:apply-templates select="each[1 = position() mod 2]"/>
</table>
</xsl:template>
<xsl:template match="each">
<tr>
<xsl:for-each select=". | following-sibling::each[1]" >
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="value"/></td>
</xsl:for-each>
<xsl:if test="not(following-sibling::each)">
<td/>
<td/>
</xsl:if>
</tr>
</xsl:template>
</xsl:stylesheet>