I need to create XSLT to show results in repeated columns to avoid scrolling in the page.
While checking for a solution i found an example from W3schools -
http://www.w3schools.com/xsl/tryxslt.asp?xmlfile=cdcatalog&xsltfile=cdcatalog
The above example shows how to add multiple columns using XSLT. But my requirement is different. If we take the same example, i want data to be displayed as below
Title Author Title Author
A-Title Gregory D-Title Ford
B-Title Dr.John E-Title Sean
C-Title Bellucci F-Title Steven
To avoid scrolling for the Users I need to split the data in two columns. Also the results need to be sorted vertically in alphabetical order.
Any suggestion would be greatly appreciated.
Thanks
John
You may want to try the following:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
<xsl:template match="/">
<html>
<body>
<h1>Collection</h1>
<table border="1" style="display:inline-block">
<tr>
<th>Title</th>
<th>Artist</th>
<th>Title</th>
<th>Artist</th>
</tr>
<xsl:variable name="sorted-cds">
<xsl:for-each select="catalog/cd">
<xsl:sort select="title" order="ascending" data-type="text" />
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="n" select="ceiling(count(catalog/cd) div 2)"/>
<xsl:for-each select="exsl:node-set($sorted-cds)/cd[position() <= $n]">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="artist"/></td>
<td><xsl:value-of select="following-sibling::cd[$n]/title"/></td>
<td><xsl:value-of select="following-sibling::cd[$n]/artist"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The basic idea is to sort the CDs as node set (sorted-cds)
calculate the half of number of rows (n) and and then iterate over
the first half of the collection (position() <= $n).
In order to get the corresponding second CD in the same row, just
skip (following-sibling::) $n CDs in the collection.
(Note that you need to include the common EXSL extensions xmlns:exsl...
because the non-standard exsl:node-set function is used.)
Another note: If you want to try out the above in the page
you linked in your question, you may need to replace the <=
by <=; they seem to have a quoting bug there(?).
Related
I am trying to implement a logic which would do choosing a value if it matches the condition. The problem is, if my list contains more elements than one, it wouldn't display the value which matches the condition, but only the first one. Could you provide some advice? thanks. I need this to implement multiple filtering by conditions. Meaning that in this for-each I would display only books with year 2008, but in another table I could also use 2010 from the list of years. The below example is a part of the template for one table which uses 2008, but I will have multiple tables where I would filter books by 2010 as well.
XSL:
<xsl:for-each select="years">
<xsl:choose>
<xsl:when test="year=2008">
<td class="year"><xsl:value-of select="year"/></td>
</xsl:when>
</xsl:choose>
</xsl:for-each>
XML:
<book>
<title>Professional ASP.NET 4 in C# and VB</title>
<author>Bill Evjen, Scott Hanselman, Devin Rader</author>
<country>USA</country>
<price>580</price>
<years>
<year>2010</year>
<year>2008</year>
</years>
</book>
This one would populate 2010 into my HTML, not 2008 as I would expect. Is this doable at all?
Update:
I've tried this approach for example to filter against multiple conditions separately:
<xsl:for-each select="library/book">
<tr>
<td class="filterTd title"><xsl:value-of select="title"/></td>
<td class="filterTd author"><xsl:value-of select="author"/></td>
<td class="filterTd price"><xsl:value-of select="price"/></td>
<xsl:for-each select="years/year[.2008]">
<td class="year">
<xsl:value-of select="."/>
</td>
</xsl:for-each>
<xsl:for-each select="years/year[.2010]">
<td class="year">
<xsl:value-of select="."/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
This would not populate any td at all in my case..
If you only want to display books for a specific year, you would put a condition in the xsl:for-each that selects the book
<xsl:for-each select="library/book[years/year=$year]">
Where $year is a variable (or parameter) containing the year you want
Do note, if you know you are dealing with a specific year, then you don't actually need to do <xsl:value-of select="year" />, you can just output the year value you are working with.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<xsl:call-template name="table">
<xsl:with-param name="year" select="2008" />
</xsl:call-template>
</xsl:template>
<xsl:template name="table">
<xsl:param name="year" />
<table>
<xsl:for-each select="library/book[years/year=$year]">
<tr>
<td class="filterTd title"><xsl:value-of select="title"/></td>
<td class="filterTd author"><xsl:value-of select="author"/></td>
<td class="filterTd price"><xsl:value-of select="price"/></td>
<td class="year">
<xsl:value-of select="$year"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Note, you could also use a key here to look up books by year
<xsl:key name="booksByYear" match="book" use="years/year" />
Then, the xsl:for-each to select books, looks like this:
<xsl:for-each select="key('booksByYear', $year)">
In below xml file contain many author elements. I have mentioned in xslt and display the first element only. I want to show all author elements. Kindly provide the xslt coding for element not in the text will be shown in browser.
XML Code:
<?xml version="1.0" encoding="US-ASCII" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="view.xsl"?>
<!DOCTYPE WileyML3G [
<!ENTITY % wileyml3g.ent SYSTEM "http://v.wiley.com:3535/dtds/wileyml3g/wiley.ent">
%wileyml3g.ent;
]>
<bibliography xml:id="aic16349-bibl-0001" style="numbered" cited="no">
<title type="main">REFERENCE<!--<QUERY xml:id="Q2"><p>References "3–35" were not cited anywhere in the text. Please provide a citation. Alternatively, delete the items from the list.</p></QUERY>--></title>
<bib xml:id="aic16349-bib-0001">
<citation type="journal" xml:id="aic16349-cit-0001"><!--<QUERY xml:id="Q3"><p>Reference "1" is not cited in the text. Please indicate where it should be cited; or delete from the reference list.</p></QUERY>-->
<author><familyName>Deer</familyName> <givenNames>TR</givenNames></author>, <author><familyName>Provenzano</familyName> <givenNames>DA</givenNames></author>, <author><familyName>Hanes</familyName> <givenNames>M</givenNames></author>, et al. <articleTitle>The Neurostimulation Appropriateness Consensus Committee (NACC) Recommendations for Infection Prevention and Management</articleTitle>. <journalTitle>Neuromodulation.</journalTitle> <pubYear year="2017">2017</pubYear>;<vol>20</vol>(<issue>1</issue>):<pageFirst>31</pageFirst>‐<pageLast>50</pageLast>.</citation>
</bib>
XSLT Code:
<?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>
<h1>HTML VIEW</h1>
<table border="1" cellpadding="10px">
<tr>
<th>Authors</th>
<th>Year</th>
<th>Article_Title</th>
<th>Journal_Title</th>
<th>Volume</th>
<th>Issue</th>
<th>First_Page</th>
<th>Last_Page</th>
</tr>
<xsl:for-each select="bibliography/bib/citation">
<tr>
<td><span style="background-color:skyblue;"><xsl:value-of select="author"/></span>, </td>
<td><xsl:value-of select="pubYear"/></td>
<td width="75%"><xsl:value-of select="articleTitle"/></td>
<td width="25%"><xsl:value-of select="journalTitle"/></td>
<td><xsl:value-of select="vol"/></td>
<td><xsl:value-of select="issue"/></td>
<td><xsl:value-of select="pageFirst"/></td>
<td><xsl:value-of select="pageLast"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
You have tagged the question as XSLT 2.0 and if you really use an XSLT 2.0 processor and use version="2.0" in your stylesheet then <xsl:value-of select="author"/> would output all selected author child elements and you could even use <xsl:value-of select="author" separator=", "/> to have the different authors separated by ,. If you use XSLT 1.0 then use <xsl:appy-templates select="author"/> and <xsl:template match="author"><xsl:if test="position() > 1">, </xsl:if></xsl:value-of select="."/></xsl:template> or use xsl:for-each if you prefer that.
As #Martin suggestion if you really want to use 1.0 then you have to go with for-each author like:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h1>HTML VIEW</h1>
<table border="1" cellpadding="10px">
<tr>
<th>Authors</th>
<th>Year</th>
<th>Article_Title</th>
<th>Journal_Title</th>
<th>Volume</th>
<th>Issue</th>
<th>First_Page</th>
<th>Last_Page</th>
</tr>
<xsl:for-each select="bibliography/bib/citation">
<tr>
<td>
<span style="background-color:skyblue;">
<xsl:for-each select="author">
<xsl:value-of select="."/>
<xsl:if test="following-sibling::author"><xsl:text>, </xsl:text></xsl:if>
</xsl:for-each>
</span>
</td>
<td><xsl:value-of select="pubYear"/></td>
<td width="75%"><xsl:value-of select="articleTitle"/></td>
<td width="25%"><xsl:value-of select="journalTitle"/></td>
<td><xsl:value-of select="vol"/></td>
<td><xsl:value-of select="issue"/></td>
<td><xsl:value-of select="pageFirst"/></td>
<td><xsl:value-of select="pageLast"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Or it will be done very easy while working with 2.0 like:
Need to change the XSLT version from 1.0 to 2.0
Use #seperator with , in value-of author.
HTML VIEW
Authors
Year
Article_Title
Journal_Title
Volume
Issue
First_Page
Last_Page
I have following source XML
Source XML
<?xml version="1.0" encoding="UTF-16"?>
<PropertySet><SiebelMessage><ListOfXRX_spcUSCO_spcCOL_spcInvoice_spcAR_spcSummary>
<FS_spcInvoice
Type_spcCode="20"
XCS_spcINQ_spcPO_spcNumber="7500020052"
XCS_spcINQ_spcSerial_spcNumber="VDR551061"
XCS_spcINQ_spcCustomer_spcNumber="712246305"
XCS_spcINQ_spcInvoice_spcNumber="060853967"
Gross_spcAmount="747.06"
Invoice_spcDate="04/01/2012"></FS_spcInvoice>
<FS_spcInvoice
Type_spcCode="20"
XCS_spcINQ_spcPO_spcNumber="7500020052"
XCS_spcINQ_spcSerial_spcNumber="VDR551061"
XCS_spcINQ_spcCustomer_spcNumber="712346305"
XCS_spcINQ_spcInvoice_spcNumber="063853967"
Gross_spcAmount="947.06"
Invoice_spcDate="04/01/2013"></FS_spcInvoice>
</ListOfXRX_spcUSCO_spcCOL_spcInvoice_spcAR_spcSummary></SiebelMessage></PropertySet>
I need to generate sorted list of invoices in HTML format. I am able to do that with help following XSLT
XSLT
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/" >
<html><head></head><body>
<H3>Summary</H3>
<table>
<thead>
<tr><th>Invoice Number</th><th>Customer Number</th><th>Serial Number</th><th>PO Number</th><th>Invoice Date</th><th>Invoice Amount</th></tr>
</thead><tbody>
<xsl:apply-templates select="PropertySet/SiebelMessage/ListOfXRX_spcUSCO_spcCOL_spcInvoice_spcAR_spcSummary/FS_spcInvoice">
<xsl:sort select="#Gross_spcAmount" order="descending" />
</xsl:apply-templates>
<tr><td colspan="5">Total Amount</td><td></td></tr>
</tbody></table></body></html>
</xsl:template>
<xsl:template match='FS_spcInvoice'>
<tr>
<td><xsl:value-of select="#XCS_spcINQ_spcInvoice_spcNumber" /></td>
<td><xsl:value-of select="#XCS_spcINQ_spcCustomer_spcNumber" /></td>
<td><xsl:value-of select="#XCS_spcINQ_spcSerial_spcNumber" /></td>
<td><xsl:value-of select="#XCS_spcINQ_spcPO_spcNumber" /></td>
<td><xsl:value-of select="#Invoice_spcDate" /></td>
<td><xsl:value-of select="#Gross_spcAmount" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>
I have two questions
1. How to SUM?
I need to display sum of all invoices in the last row of the table. I have an idea that I need to use nodeset but I am not able to figure out how?
2. Dynamic Sort
Is it possible to provide element name or attribute name dynamically to xsl:sort.
For example the attribute name on which to sort is provided as different element value.
<sortby>Gross_spcAmount</sortby>
(1) How to sum: use the built-in XPath library.
sum(/PropertySet/*/FS_spcInvoice)
There is a nuance if there is a risk that any of the numbers being addressed are not actually numbers, in which case the sum would be spoiled by including the bad value. This is prevented by:
sum(/PropertySet/*/FS_spcInvoice[number(.)=number(.)])
... which relies on the property of NaN that NaN != NaN.
(2) Dynamic sort: it is possible, though inelegant ... you have to express the calculation of the sort string using an address that composes the node you are looking for from whatever variable value or address you need. No real way around this other than exposing the name of the attribute node and checking it.
<xsl:sort select="#*[name(.)=/*/sortby]"/>
I have a stylesheet I'm using with a perl module that only works with XSLT 1.0. I want to create a JSON array inside a JSON dictionary so I need proper comma seperation for the elements. I'm parsing an XHTML table where there are 1 or more spans in the second cell. So for-each select="./tr" and then for-each select="./td[1]/span" or something like that.
After changing it a little it behaves as expected as Ian said it would.
<?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" version="1.0" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" doctype-public="-//W3C//DTD HTML 4.01//EN" encoding="UTF-8" />
<xsl:template match="text()">
</xsl:template>
<xsl:template match="table/tbody">
<xsl:text>[</xsl:text>
<xsl:for-each select="./tr[not(#class='no-results')]">
<xsl:text>{"</xsl:text>
<xsl:value-of select="normalize-space(.//strong)" />
<xsl:text>":{"ingredients":{</xsl:text>
<xsl:for-each select=".//div[#class='reagent-list']//a[#class='item-link reagent']">
<xsl:value-of select="substring(./#href, 14)" />:<xsl:value-of select="normalize-space(./span[1])" />
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>}</xsl:text>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:template>
</xsl:stylesheet>
I realize that the stylesheet does not match the xml below. The actual document is huge. I hope you understand what I mean, though. I just made this up:
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td><span>an element</span><span>an element</span><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
</tbody>
</table>
=>
{
"Row one":["an element"],
"Row two":["an element", "an element"],
"Row three":["an element", "an element", "an element", "an element"]
}
Instead I get this:
{
"Row one":["an element",],
"Row two":["an element", "an element",],
"Row three":["an element" "an element" "an element" "an element"]
}
I've been using position() and last() in a test tag to print a comma and it seems to work correctly for the outer loop, but how do I tell my test tag to use the inner for-each scope when printing the commas that seperate the array?
As mentioned by #IanRoberts, it is difficult to give targeted assistance without seeing what your existing XSLT looks like.
That said, here is a solution that is push-oriented (i.e., no <xsl:for-each>) and does not require last().
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my"
exclude-result-prefixes="my"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" method="text" />
<xsl:strip-space elements="*" />
<my:ones>
<num>one</num>
<num>two</num>
<num>three</num>
<num>four</num>
<num>five</num>
<num>six</num>
<num>seven</num>
<num>eight</num>
<num>nine</num>
</my:ones>
<xsl:template match="/*">
<xsl:text>{
</xsl:text>
<xsl:apply-templates select="tbody/tr" />
<xsl:text>
}</xsl:text>
</xsl:template>
<xsl:template match="tr">
<xsl:variable name="vPos" select="position()" />
<xsl:if test="$vPos > 1">,
</xsl:if>
<xsl:text> "Row </xsl:text>
<xsl:value-of select="document('')/*/my:ones/*[$vPos]" />
<xsl:text>":[</xsl:text>
<xsl:apply-templates select="td[2]/span" />
<xsl:text>]</xsl:text>
</xsl:template>
<xsl:template match="span">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="concat('"', ., '"')" />
</xsl:template>
</xsl:stylesheet>
...is run against the provided XML:
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td>
<span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td>
<span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td>
<span>an element</span><span>an element</span><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
</tbody>
</table>
...the wanted result is produced:
{
"Row one":["an element"],
"Row two":["an element", "an element"],
"Row three":["an element", "an element", "an element", "an element"]
}
Explanation:
The first template matches the root element. It's purpose is to apply templates to that element's <tr> grandchildren and sandwich those results between { and } (adding newlines as appropriate).
The second template matches <tr> elements. It outputs row information and is instructed to apply templates to all <span> children of the second <td> element (again, sandwiching the results between braces and other text as necessary).
NOTE: instead of using last(), you'll see that the first element outputs a comma, followed by a newline, if the position of this <tr> in the current context is greater than 1. This has the same effect of applying commas correctly; it's merely a different way of looking at the same problem (and is what I use because it seems more efficient to me ;) ).
NOTE: to make this solution more extensible, you'll see that I'm not statically outputting the words "one", "two", etc. in each row. Instead, at the top of the XSLT, I've defined a <my:ones> element to hold onto the text values of each "ones" number. When processing each <tr>, I use the position of that <tr> in the current context to retrieve the correct <num> element's value. I've left it as an exercise to the reader, but it would indeed be possible to define <my:tens>, <my:hundreds>, etc. to scale this solution up to potentially large numbers of rows.
The final template matches <span> elements. Again, it uses an <xsl:if> element to test whether the position of this <span> in the current context is greater than 1; if so, a comma (followed by a space) is output. After that, we merely concatenate two " symbols with the value of the span sandwiched in-between.
I have a problem when parsing dynamic xml data into a html table. The XML is as follow.
<results>
<result id="1" desc="Voltage and current">
<measure desc="VOLT" value="1.0" />
<measure desc="AMPERE" value="2.0" />
</result>
<result id="2" desc="Current-1">
<measure desc="AMPERE" value="5.0" />
</result>
</results>
from which I would like a html table like:
ID DESC VOLT AMPERE
1 Voltage and current 1.0 2.0
2 Current-1 5.0
Notice the empty cell at second voltage column. ID and DESC is taken from result/#id and result/#desc and the rest of the column names should come from measure/#desc
No column name should be duplicate, I managed to code that far, but when I start adding my measures I need to match each measure/#desc to correct column in the table. I tried double nested loops to first match all unique column names, and then loop all measures again to match the column header. But the xslt parser threw a NPE on me!
Sorry that I can't show any code as it is on a non-connected computer.
I've browsed so many Q/A here on SO but to no help for my specific problem.
Thanks in advance
Note: I am able to change the XML format in any way to make parsing easier if anyone come up with a neater format.
If you are using XSLT1.0, you can use a technique called 'Muenchian' grouping to get the distinct measure descriptions, which will form the basis of your head row, and also be used to output the values of each row.
Firstly, you define a key to look up measure elements by their #desc attribute
<xsl:key name="measures" match="measure" use="#desc" />
Then, to get the distinct measure descriptions you can iterate over the measure elements that appear first in the group for their given #desc attribute
<xsl:apply-templates
select="result/measure[generate-id() = generate-id(key('measures', #desc)[1])]"
mode="header" />
Then, for your header, you would simply have a template to output the description.
<xsl:template match="measure" mode="header">
<th>
<xsl:value-of select="#desc" />
</th>
</xsl:template>
For each result row, would do a similar thing, and iterate over all distinct measure values, but the only difference is you would have to pass in the current result element as a parameter, for later use.
<xsl:apply-templates
select="/results/result/measure[generate-id() = generate-id(key('measures', #desc)[1])]"
mode="data">
<xsl:with-param name="result" select="." />
</xsl:apply-templates>
Then, in the template that matched the measure this time, you could access the measure within the result element with a matching #desc attribute (and id there is no such attribute, nothing is output for the cell)
<xsl:template match="measure" mode="data">
<xsl:param name="result" />
<td>
<xsl:value-of select="$result/measure[#desc = current()/#desc]/#value" />
</td>
</xsl:template>
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="measures" match="measure" use="#desc" />
<xsl:template match="/results">
<table>
<tr>
<th>ID</th>
<th>DESC</th>
<xsl:apply-templates select="result/measure[generate-id() = generate-id(key('measures', #desc)[1])]" mode="header" />
</tr>
<xsl:apply-templates select="result" />
</table>
</xsl:template>
<xsl:template match="result">
<tr>
<td><xsl:value-of select="#id" /></td>
<td><xsl:value-of select="#desc" /></td>
<xsl:apply-templates select="/results/result/measure[generate-id() = generate-id(key('measures', #desc)[1])]" mode="data">
<xsl:with-param name="result" select="." />
</xsl:apply-templates>
</tr>
</xsl:template>
<xsl:template match="measure" mode="header">
<th>
<xsl:value-of select="#desc" />
</th>
</xsl:template>
<xsl:template match="measure" mode="data">
<xsl:param name="result" />
<td>
<xsl:value-of select="$result/measure[#desc = current()/#desc]/#value" />
</td>
</xsl:template>
</xsl:stylesheet>
Note the use of the mode attributes because you have two templates matching the measure element, which function in different ways.
When applied to your input XML, the following is output
<table>
<tr>
<th>ID</th>
<th>DESC</th>
<th>VOLT</th>
<th>AMPERE</th>
</tr>
<tr>
<td>1</td>
<td>Voltage and current</td>
<td>1.0</td>
<td>2.0</td>
</tr>
<tr>
<td>2</td>
<td>Current-1</td>
<td/>
<td>5.0</td>
</tr>
</table>