Line Break in XSLT for a comma separated value - xslt

I am new to XSLT and I am trying the following:
I have the following:
<TR> <TD> Name: </TD> <TD><xsl:value-of select="ZNAME"/> </TD> </TR>
which returns
ADAM,BRIAN,CHARLIE,DAVID
How I can make this return:
ADAM
BRIAN
CHARLIE
DAVID
Please suggest.

This 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:template match="ZNAME" name="tokenize">
<xsl:param name="pText" select="concat(., ',')"/>
<xsl:if test="string-length($pText)>0">
<xsl:value-of select="substring-before($pText, ',')"/>
<br />
<xsl:call-template name="tokenize">
<xsl:with-param name="pText"
select="substring-after($pText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<ZNAME>ADAM,BRIAN,CHARLIE,DAVID</ZNAME>
produces the wanted, correct result:
ADAM<br/>BRIAN<br/>CHARLIE<br/>DAVID<br/>
which when viewed in a browser is displayed like:
ADAMBRIANCHARLIEDAVID

If you need a solution that doesn't output a trailing <br/> element, try this:
<TR> <TD> Name: </TD> <TD><xsl:apply-templates select="ZNAME"/> </TD> </TR>
<xsl:template match="ZNAME" name="convertcommas">
<xsl:param name="text" select="."/>
<xsl:value-of select="substring-before(concat($text,','),',')" />
<xsl:if test="contains($text,',')">
<br />
<xsl:call-template name="convertcommas">
<xsl:with-param name="text" select="substring-after($text,',')" />
</xsl:call-template>
</xsl:if>
</xsl:template>
The concat($text,',') makes sure the substring-before sees at least one comma, thereby outputting the string on it's own if there were no commas originally.

Related

XSLT - format comma seperated list into table format

A web service is returning a list of persons in the below xml format. The values are seperated by a semicolon. I need to display the values in 2 or 3 columns (Needs to be a variable).
Desired Results:
<table border="1">
<tr>
<td>Smith, John</td>
<td>Jackson, Samuel</td>
<td>Wayne, Bruce</td>
</tr>
<tr>
<td>Cosby, Bill</td>
<td>Kent, Clarke</td>
<td>Leno, Jay</td>
</tr>
<tr>
<td>OBrian, Conan</td>
<td> </td>
<td> </td>
</tr>
</table>
Xml sample
<?xml version="1.0" encoding="UTF-8"?> <PI>Smith, John; Jackson,
Samuel; Wayne, Bruce; Cosby, Bill; Kent, Clarke; Leno, Jay; OBrian,
Conan; </PI>
Please accept Holman's solution. His technique is spot-on.
XSLT 2.0 Solution
Your sample input data suggests semicolon terminated list, rather than a semicolon separated list.
This XSLT 2.0 stylesheet will work correctly with either a semicolon terminated list or a semicolon separated list. It takes one input parameter col-count, which defaults to 3.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:param name="col-count" select="3" />
<xsl:template match="/*">
<table border="1">
<xsl:variable name="names" select="tokenize(.,';')[normalize-space(.)]" />
<xsl:for-each-group select="$names , for $x in 1 to
($col-count - (count($names) mod $col-count)) mod $col-count return ''"
group-by="(position() - 1) idiv $col-count">
<tr>
<xsl:for-each select="current-group()">
<td><xsl:value-of select="normalize-space(.)" /></td>
</xsl:for-each>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
Notes:
(1) Notice the brevity of the solution, and the absence of clumsy xsl:if statements. The use of calculated sequences (as opposed to input document node selections), is not just limited to the group-by attribute, but can also to applied to select. The use of computed sequences suggests a more functional view, rather than a procedural view.
(2) If you want the output to be safe for old browsers, then replace ...
<xsl:for-each-group select="$names , for $x in 1 to
($col-count - (count($names) mod $col-count)) mod $col-count return ''"
group-by="(position() - 1) idiv $col-count">
... with ...
<xsl:for-each-group select="$names , for $x in 1 to
($col-count - (count($names) mod $col-count)) mod $col-count return ' '"
group-by="(position() - 1) idiv $col-count">
XSLT 1.0 Solution
If you are stuck on XSLT 1.0, then you could use this equivalent, but less efficient stylesheet ...
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:esl="http://exslt.org/common"
xmlns:so="http://stackoverflow.com/questions/18066463"
version="1.0"
exclude-result-prefixes="xsl so esl">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:variable name="col-count" select="3" /><!-- Must be 2 or 3. -->
<xsl:variable name="empty-rtf">
<so:name> </so:name>
</xsl:variable>
<xsl:variable name="empty" select="esl:node-set($empty-rtf)/*" />
<xsl:template match="/*">
<table border="1">
<xsl:variable name="names-rtf">
<xsl:call-template name="split">
<xsl:with-param name="list" select="." />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="names" select="esl:node-set($names-rtf)/*" />
<xsl:for-each select="$names[(position() mod $col-count) = 1]">
<xsl:variable name="row" select="position() - 1" />
<tr>
<xsl:apply-templates select="$names[floor((position() - 1) div $col-count) = $row]" />
<xsl:if test="position()=last()">
<xsl:for-each select="($names|$empty)[position() <=
(($col-count - (count($names) mod $col-count)) mod $col-count)]">
<xsl:apply-templates select="$empty" />
</xsl:for-each>
</xsl:if>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="split">
<xsl:param name="list" />
<xsl:choose>
<xsl:when test="contains($list,';')">
<xsl:call-template name="split">
<xsl:with-param name="list" select="substring-before($list,';')" />
</xsl:call-template>
<xsl:call-template name="split">
<xsl:with-param name="list" select="substring-after($list,';')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="normalize-space($list)">
<so:name><xsl:value-of select="normalize-space($list)" /></so:name>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
<xsl:template match="so:*" priority="2">
<td><xsl:value-of select="." /></td>
</xsl:template>
</xsl:stylesheet>
Here is an XSLT 2.0 solution with the number of columns as a variable as requested. This employs a technique I teach in the classroom that underscores that grouping need not be by values in the XML (as many XSLT users assume), rather, it can be by any arbitrary calculation (in this case, the result of division).
[edited to show multiple invocations with different number of columns]
t:\ftemp>type names.xml
<?xml version="1.0" encoding="UTF-8"?> <PI>Smith, John; Jackson,
Samuel; Wayne, Bruce; Cosby, Bill; Kent, Clarke; Leno, Jay; OBrian,
Conan; </PI>
t:\ftemp>call xslt2 names.xml names.xsl names.out.xml "cols=3"
t:\ftemp>type names.out.xml
<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
<tr>
<td>Smith, John</td>
<td>Jackson, Samuel</td>
<td>Wayne, Bruce</td>
</tr>
<tr>
<td>Cosby, Bill</td>
<td>Kent, Clarke</td>
<td>Leno, Jay</td>
</tr>
<tr>
<td>OBrian, Conan</td>
<td/>
<td/>
</tr>
</table>
t:\ftemp>call xslt2 names.xml names.xsl names.out.xml "cols=2"
t:\ftemp>type names.out.xml
<?xml version="1.0" encoding="UTF-8"?>
<table border="1">
<tr>
<td>Smith, John</td>
<td>Jackson, Samuel</td>
</tr>
<tr>
<td>Wayne, Bruce</td>
<td>Cosby, Bill</td>
</tr>
<tr>
<td>Kent, Clarke</td>
<td>Leno, Jay</td>
</tr>
<tr>
<td>OBrian, Conan</td>
<td/>
</tr>
</table>
t:\ftemp>type names.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:param name="cols" required="yes"/>
<xsl:template match="PI">
<table border="1">
<!--determine population and group by number of columns-->
<xsl:for-each-group select="tokenize(.,';\s+')"
group-by="(position()-1) idiv $cols">
<tr>
<!--put members into the row-->
<xsl:for-each select="current-group()">
<td>
<xsl:value-of select="normalize-space(.)"/>
</td>
</xsl:for-each>
<!--filler for the last row-->
<xsl:if test="position()=last()">
<xsl:for-each select="count(current-group())+1 to $cols">
<td/>
</xsl:for-each>
</xsl:if>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
t:\ftemp>rem

How can I group homogenous data in xsl?

Let's say we have following data:
<all>
<item id="1"/>
<item id="2"/>
...
<item id="N"/>
</all>
What is the most elegant, xslt-ish way to group those items?
For example, imagine we want a table with two cells in each row.
Off the top of my head I can imagine (not tested though)
in template, matching item, I can call this very item, selecting following-sibling.
But even in this case I should pass additional param, to make recursion finite.
As row-count can be variable .. am passing it as a param to the template .. :)
<?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="/all[node]">
<table>
<xsl:for-each select="node[1]">
<xsl:call-template name="whoaa">
<xsl:with-param name="count" select="'1'"/>
<xsl:with-param name="row_count" select="'10'"/>
<!--maximum row_count is set to 10 -->
</xsl:call-template>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="whoaa">
<xsl:param name="count"/>
<xsl:param name="row_count"/>
<!--check if we have crossed row_count-->
<xsl:if test="not ($row_count < $count)">
<tr>
<td>
<xsl:value-of select="."/>
</td>
<td>
<!--copy next column-->
<xsl:for-each select="following-sibling::node[1]">
<xsl:value-of select="."/>
</xsl:for-each>
</td>
</tr>
<!--Select next row .. call the same template untill we reach (row_count > count)-->
<xsl:for-each select="following-sibling::node[2]">
<xsl:call-template name="whoaa">
<xsl:with-param name="count" select="$count+2"/>
<xsl:with-param name="row_count" select="$row_count"/>
</xsl:call-template>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
use position and mod, e.g.
<xsl:template match="/all">
<table>
<xsl:apply-templates name="item" mode="group"/>
</table>
</xsl:template>
<xsl:template match="item[position() mod 2=1]" mode="group">
<tr>
<td><xsl:apply-templates select="." mode="render"/></td>
<td><xsl:apply-templates select="following-sibling::item[1]" mode="render"/></td>
</tr>
</xsl:template>
<xsl:template match="item[position() mod 2=0]"></xsl:template>
<xsl:template match="item" mode="render">item: <xsl:value-of select="#id"/></xsl:template>

alternate sorted nodes in XSLT 1.0 without extension function

This a very similar question as XSL: Transforming xml into a sorted multicolumn html table
But (unfortunately) there's an extra requirement: it should be XSLT 1.0 without extension functions, i.e. without using the node-set function.
This is my simplified XML:
<demo>
<config n_columns="3" />
<messages>
<msg date="2011-07-06" title="2nd message" />
<title>message list</title>
<msg date="2011-07-05" title="4th message" />
<msg date="2011-07-06" title="3rd message" />
<msg date="2011-07-07" title="1st message" />
</messages>
</demo>
Using this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="/">
<xsl:apply-templates select="demo/messages">
<xsl:with-param name="n_columns" select="number(/demo/config/#n_columns)" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="messages">
<xsl:param name="n_columns" />
<div>
<xsl:value-of select="concat(./title, ' (', $n_columns, ' columns)')" />
</div>
<table>
<xsl:variable name="cells" select="msg" />
<xsl:apply-templates select="$cells[(position() - 1) mod $n_columns = 0]"
mode="row">
<xsl:with-param name="n_columns" select="$n_columns" />
<xsl:with-param name="cells" select="$cells" />
</xsl:apply-templates>
</table>
</xsl:template>
<xsl:template match="msg" mode="row">
<xsl:param name="n_columns" />
<xsl:param name="cells" />
<xsl:variable name="n_row" select="position()" />
<xsl:variable name="row_cells"
select="$cells[position() > ($n_row - 1) * $n_columns][position() <= $n_columns]" />
<tr>
<xsl:apply-templates select="$row_cells" mode="cell" />
<xsl:call-template name="empty-cells">
<xsl:with-param name="n" select="$n_columns - count($row_cells)" />
</xsl:call-template>
</tr>
</xsl:template>
<xsl:template match="msg" mode="cell">
<td>
<xsl:value-of select="#title" />
</td>
</xsl:template>
<xsl:template name="empty-cells">
<xsl:param name="n" />
<xsl:if test="$n > 0">
<td>
<xsl:attribute name="colspan">
<xsl:value-of select="$n" />
</xsl:attribute>
<xsl:text> </xsl:text>
</td>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Produces this HTML fragment as output:
<div>message list (3 columns)</div>
<table>
<tr>
<td>2nd message</td>
<td>4th message</td>
<td>3rd message</td>
</tr>
<tr>
<td>1st message</td>
<td colspan="2"> </td>
</tr>
</table>
What is obviously missing is the sort part...
Redefining the "cells" variable as follows is actually what I need:
<xsl:variable name="cells">
<xsl:for-each select="msg">
<xsl:sort select="#date" order="descending" />
<xsl:sort select="#title" />
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
But now I must define another variable to convert the RTF to a nodelist and pass that one to the template I'm applying.
<xsl:variable name="sCells" select="ext:node-set($cells)/*" />
Doing that would produce the following HTML fragment:
<div>message list (3 columns)</div>
<table>
<tr>
<td>1st message</td>
<td>2nd message</td>
<td>3rd message</td>
</tr>
<tr>
<td>4th message</td>
<td colspan="2"> </td>
</tr>
</table>
Unfortunately, my XSLT engine (SAP XML toolkit for java) doesn't support this (or a similar) extension function. Thus I'm looking for another solution that doesn't require the node-set extension function.
I spent quite some time reading all kinds of forums etc., but I really can't figure it out. Perhaps someone has a good idea for an alternative approach? tnx!
This is the follow up based on Dimitre's (slightly extended) solution.
This XML input
<demo>
<config n_columns="3" />
<messages>
<msg date="2011-07-06" title="2nd message" />
<title>message list</title>
<msg date="2011-07-05" title="4th message" />
<msg date="2011-07-06" title="3rd message" />
<msg date="2011-07-07" title="1st message" />
<msg date="2011-07-05" title="5th message" />
<msg date="2011-07-05" title="7th message" />
<msg date="2011-07-05" title="6th message" />
</messages>
</demo>
combined with this XSLT stylesheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:variable name="vNumCols" select="/*/config/#n_columns" />
<xsl:variable name="vCells" select="/*/messages/msg" />
<xsl:variable name="vNumCells" select="count($vCells)" />
<xsl:variable name="vNumRows" select="ceiling($vNumCells div $vNumCols)" />
<xsl:variable name="vIndexPatternLength"
select="string-length(concat('', $vNumCells))" />
<xsl:variable name="vIndexPattern">
<xsl:call-template name="padding">
<xsl:with-param name="length" select="$vIndexPatternLength" />
<xsl:with-param name="chars" select="'0'" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vSortedIndex">
<xsl:for-each select="$vCells">
<xsl:sort select="#date" order="descending" />
<xsl:sort select="#title" />
<xsl:value-of
select="format-number(count(preceding-sibling::msg) + 1,
$vIndexPattern)" />
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates select="demo/messages" />
</xsl:template>
<xsl:template match="messages">
<table>
<xsl:for-each select="$vCells[not(position() > $vNumRows)]">
<xsl:variable name="vRow" select="position()" />
<tr>
<xsl:for-each select="$vCells[not(position() > $vNumCols)]">
<xsl:variable name="vCol" select="position()" />
<xsl:variable name="vCell"
select="($vRow - 1) * $vNumCols + $vCol" />
<xsl:variable name="vIndex"
select="substring($vSortedIndex,
($vCell - 1) * $vIndexPatternLength + 1,
$vIndexPatternLength)" />
<xsl:variable name="vMessage"
select="$vCells[position() = $vIndex]" />
<xsl:choose>
<xsl:when test="$vMessage">
<xsl:apply-templates select="$vMessage"
mode="cell" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="empty-cell" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="msg" mode="cell">
<td>
<xsl:apply-templates select="." />
</td>
</xsl:template>
<xsl:template match="msg">
<xsl:value-of select="concat(#date, ' : ', #title)" />
</xsl:template>
<xsl:template name="empty-cell">
<td>
<xsl:text> </xsl:text>
</td>
</xsl:template>
<!-- http://www.exslt.org/str/functions/padding/ -->
<xsl:template name="padding">
<xsl:param name="length" select="0" />
<xsl:param name="chars" select="' '" />
<xsl:choose>
<xsl:when test="not($length) or not($chars)" />
<xsl:otherwise>
<xsl:variable name="string"
select="concat($chars, $chars, $chars, $chars, $chars,
$chars, $chars, $chars, $chars, $chars)" />
<xsl:choose>
<xsl:when test="string-length($string) >= $length">
<xsl:value-of select="substring($string, 1, $length)" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="padding">
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="chars" select="$string" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
produces this HTML output
<table>
<tr>
<td>2011-07-07 : 1st message</td>
<td>2011-07-06 : 2nd message</td>
<td>2011-07-06 : 3rd message</td>
</tr>
<tr>
<td>2011-07-05 : 4th message</td>
<td>2011-07-05 : 5th message</td>
<td>2011-07-05 : 6th message</td>
</tr>
<tr>
<td>2011-07-05 : 7th message</td>
<td> </td>
<td> </td>
</tr>
</table>
Thanks Dimitre!
It is difficult, but not impossible to perform the required processing in XSLT 1.0 without using any extension functions:
<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:variable name="vNumCols"
select="/*/config/#n_columns"/>
<xsl:variable name="vItems" select="/*/messages/msg"/>
<xsl:variable name="vNumItems" select="count($vItems)"/>
<xsl:variable name="vNumRows" select=
"ceiling($vNumItems div $vNumCols)"/>
<xsl:variable name="vsortedInds">
<xsl:for-each select="$vItems">
<xsl:sort select="#date" order="descending"/>
<xsl:value-of select=
"format-number(count(preceding-sibling::msg)+1,
'0000'
)
"/>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<table>
<xsl:for-each select=
"$vItems[not(position() > $vNumRows)]">
<tr>
<xsl:variable name="vRow" select="position()"/>
<xsl:for-each select="$vItems[not(position() > $vNumCols)]">
<xsl:variable name="vcurIndIndex" select=
"($vRow -1)*$vNumCols + position()"/>
<xsl:variable name="vcurInd" select=
"substring($vsortedInds, 4*($vcurIndIndex -1) +1, 4)"/>
<xsl:variable name="vcurItem" select="$vItems[position()=$vcurInd]"/>
<xsl:if test="$vcurItem">
<td>
<xsl:value-of select="$vcurItem/#title"/>
</td>
</xsl:if>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<demo>
<config n_columns="3" />
<messages>
<msg date="2011-07-06" title="2nd message" />
<title>message list</title>
<msg date="2011-07-05" title="4th message" />
<msg date="2011-07-06" title="3rd message" />
<msg date="2011-07-07" title="1st message" />
</messages>
</demo>
the significant part of the desired output is produced (I am leaving the rest as an exercise to the reader :) ):
<table>
<tr>
<td>1st message</td>
<td>2nd message</td>
<td>3rd message</td>
</tr>
<tr>
<td>4th message</td>
</tr>
</table>
Explanation:
In order to avoid having to convert an RTF to a node-set, we are using a string of the indexes of the sorted elements. Every index occupies four characters (left padded with zeroes when necessary). Then we are using these indexes in populating the rows of the table.
In order to avoid resursion, we are using the Piez method of iteration through N non-node items.
Do note: This solution assumes that the table will not contain more than 9999 cells. If more cells are expected, you can easily change the code, for example:
Replace:
format-number(count(preceding-sibling::msg)+1,
'0000'
)
with:
format-number(count(preceding-sibling::msg)+1,
'00000'
)
And replace:
<xsl:variable name="vcurInd" select=
"substring($vsortedInds, 4*($vcurIndIndex -1) +1, 4)"/>
with
<xsl:variable name="vcurInd" select=
"substring($vsortedInds, 5*($vcurIndIndex -1) +1, 5)"/>

XSLT Confusion with xsl:apply-templates

I have an XML file with this format:
<?xml version="1.0" encoding="utf-8" ?>
<OpacResult>
<valueObjects class="list">
<Catalog>
<notes>
Daily newsletter available via e-mail.
IP authenticated. Login not needed within firm.
</notes>
<title>Health law360. </title>
<url>http://health.law360.com/</url>
<catalogTitles class="list">
<CatalogTitle>
<uuid>e5e2bc53ac1001f808cddc29f93ecad8</uuid>
<timeChanged class="sql-timestamp">2010-12-14 09:17:10.707</timeChanged>
<timeEntered class="sql-timestamp">2010-12-14 09:17:10.707</timeEntered>
<whoChanged>B23DE2FFE8DD49B0B0A03D1FEB3E7DA2</whoChanged>
<whoEntered>B23DE2FFE8DD49B0B0A03D1FEB3E7DA2</whoEntered>
<updateSearchIndex>true</updateSearchIndex>
<corpId>RopesGray</corpId>
<catalogUuid>a20b6b4bac1001f86d28280ed0ebeb9e</catalogUuid>
<type>O</type>
<title>Law 360. Health law.</title>
</CatalogTitle>
<CatalogTitle>
<uuid>e5e2bc53ac1001f808cddc299ddfe49d</uuid>
<timeChanged class="sql-timestamp">2010-12-14 09:17:10.707</timeChanged>
<timeEntered class="sql-timestamp">2010-12-14 09:17:10.707</timeEntered>
<whoChanged>B23DE2FFE8DD49B0B0A03D1FEB3E7DA2</whoChanged>
<whoEntered>B23DE2FFE8DD49B0B0A03D1FEB3E7DA2</whoEntered>
<updateSearchIndex>true</updateSearchIndex>
<corpId>RopesGray</corpId>
<catalogUuid>a20b6b4bac1001f86d28280ed0ebeb9e</catalogUuid>
<type>O</type>
<title>Health law 360</title>
</CatalogTitle>
<CatalogTitle>
<uuid>e5e2bc53ac1001f808cddc29ec1d959b</uuid>
<timeChanged class="sql-timestamp">2010-12-14 09:17:10.707</timeChanged>
<timeEntered class="sql-timestamp">2010-12-14 09:17:10.707</timeEntered>
<whoChanged>B23DE2FFE8DD49B0B0A03D1FEB3E7DA2</whoChanged>
<whoEntered>B23DE2FFE8DD49B0B0A03D1FEB3E7DA2</whoEntered>
<updateSearchIndex>true</updateSearchIndex>
<corpId>RopesGray</corpId>
<catalogUuid>a20b6b4bac1001f86d28280ed0ebeb9e</catalogUuid>
<type>O</type>
<title>Health law three hundred sixty</title>
</CatalogTitle>
</catalogTitles>
<catalogUrls class="list"/>
<gmd>
<uuid>f8f123acc0a816070192e296a6a71715</uuid>
<timeChanged class="sql-timestamp">2006-10-10 15:23:37.813</timeChanged>
<timeEntered class="sql-timestamp">2005-01-27 00:00:00.0</timeEntered>
<whoChanged>25db9fcd3fd247f4a20485b40cc134ad</whoChanged>
<whoEntered>user</whoEntered>
<updateSearchIndex>true</updateSearchIndex>
<corpId>RopesGray</corpId>
<isRuleDefault>false</isRuleDefault>
<ruleName>text</ruleName>
<term>electronic resource</term>
<preferCollection>false</preferCollection>
<isTechnicalManual>false</isTechnicalManual>
<sip2IsMagnetic>false</sip2IsMagnetic>
</gmd>
<issues class="list"/>
</Catalog>
</valueObjects>
</OpacResult>
As you can see, there are other elements under sibling nodes, but I don't care about these and only want to see the first one.
I'm using this code to call a template with the string of desired elements as the parameter
and a template to loop through the asterisk-delimited string parameter: (title*url*notes*)
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="columns" />
<xsl:template match="/OpacResult/valueObjects">
<html>
<body>
<table border="1">
<!-- Header row -->
<tr>
<xsl:call-template name="print-headers">
<xsl:with-param name="columns" select="$columns"/>
</xsl:call-template>
</tr>
<!-- Value rows -->
<xsl:for-each select="Catalog">
<tr>
<xsl:call-template name="print-values">
<xsl:with-param name="columns" select="$columns"/>
</xsl:call-template>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<!-- Split up string of column names and create header field names based on element names-->
<xsl:template name="print-headers">
<xsl:param name="columns"/>
<xsl:variable name="newList" select="$columns"/>
<xsl:variable name="first" select="substring-before($newList, '*')" />
<xsl:variable name="remaining" select="substring-after($newList, '*')" />
<th>
<xsl:apply-templates select="Catalog/*[name()=$first]">
<xsl:with-param name="header">true</xsl:with-param>
</xsl:apply-templates>
</th>
<xsl:if test="$remaining">
<xsl:call-template name="print-headers">
<xsl:with-param name="columns" select="$remaining"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="print-values">
<xsl:param name="columns"/>
<xsl:variable name="newList" select="$columns"/>
<xsl:variable name="first" select="substring-before($newList, '*')" />
<xsl:variable name="remaining" select="substring-after($newList, '*')" />
<td>
<xsl:apply-templates select="Catalog/*[name()=$first]"/>
</td>
<xsl:if test="$remaining">
<xsl:call-template name="print-values">
<xsl:with-param name="columns" select="$remaining"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="title">
<xsl:param name="header"/>
<xsl:choose>
<xsl:when test="$header='true'">
<xsl:text>Title</xsl:text>
</xsl:when>
<xsl:otherwise>
<a>
<xsl:attribute name="href">
<xsl:value-of select="//*[name()='url']"/>
</xsl:attribute>
<xsl:value-of select="//*[name()='title']"/>
</a>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="url">
<xsl:param name="header"/>
<xsl:choose>
<xsl:when test="$header='true'">
<xsl:text>URL</xsl:text>
</xsl:when>
<xsl:otherwise>
<a>
<xsl:attribute name="href">
<xsl:value-of select="//*[name()='url']"/>
</xsl:attribute>
<xsl:value-of select="//*[name()='url']"/>
</a>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="notes">
<xsl:param name="header"/>
<xsl:choose>
<xsl:when test="$header='true'">
<xsl:text>Notes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="//*[name()='notes']"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="holdingNotes">
<xsl:param name="header"/>
<xsl:choose>
<xsl:when test="$header='true'">
<xsl:text>Holding Notes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="//*[name()='holdingNotes']"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="relatedUrl">
<xsl:param name="header"/>
<xsl:choose>
<xsl:when test="$header='true'">
<xsl:text>Related URL</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="//*[name()='relatedUrl']"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="bibliographicType/hasDataFile">
<xsl:param name="header"/>
<xsl:choose>
<xsl:when test="$header='true'">
<xsl:text>File</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="Catalog/*[name()='hasDataFile']"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The only way I can access this template is to use the //*[name()=$first] syntax to extract the value of the element based on the name from the $first parameter.
Any help is greatly appreciated. Thanks very much in advance. Not including the full XML as there are thousands of lines of unnecessary text.
This stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:h="header"
exclude-result-prefixes="h">
<h:h>
<title>Title</title>
<url>URL</url>
<notes>Notes</notes>
</h:h>
<xsl:param name="pColumns" select="'title url notes'"/>
<xsl:template match="/OpacResult/valueObjects">
<html>
<body>
<table border="1">
<tr>
<xsl:apply-templates
select="document('')/*/h:h"
mode="filter"/>
</tr>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="Catalog">
<tr>
<xsl:call-template name="filter"/>
</tr>
</xsl:template>
<xsl:template match="h:h/*">
<th>
<xsl:value-of select="."/>
</th>
</xsl:template>
<xsl:template match="Catalog/*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
<xsl:template match="node()" mode="filter" name="filter">
<xsl:apply-templates select="*[contains(
concat(' ',$pColumns,' '),
concat(' ',name(),' '))]">
<xsl:sort select="substring-before(
concat(' ',$pColumns,' '),
concat(' ',name(),' '))"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
Output:
<html>
<body>
<table border="1">
<tr>
<th>Title</th>
<th>URL</th>
<th>Notes</th>
</tr>
<tr>
<td>Health law360. </td>
<td>http://health.law360.com/</td>
<td> Daily newsletter available via e-mail.
IP authenticated. Login not needed within firm. </td>
</tr>
</table>
</body>
</html>
Note: Inline data for headers, pseudo sequence parameter for filtering and sorting, modes not for processing the same element in different way but for processing different elements in the same way also.
I've found a solution, but I'm sure it's not the best way to do it. Within the templates for each of my expected fields, I have added:
<xsl:if test=position()=1">
.. process data here ..
</xsl:if>
Ideally, there would be a way to tell this to process only the first element it finds:
<th>
<xsl:apply-templates select="//*[name()=$first]">
<xsl:with-param name="header">true</xsl:with-param>
</xsl:apply-templates>
</th>
Edit: As I suspected, this will not work when there is more than one Catalog element to parse. So instead of grabbing the first element for each catalog parent element, it's grabbing the first element in the document every time

Rendering a node sequence as M x N table

#Oded: Sorry to have been poor in my exposition... My input document has a fragment like this:
<recordset name="resId" >
<record n="0">example 1</record>
<record n="1">example 2</record>
<record n="2">example 1</record>
....
<record n="N">example 1</record>
</recordset>
containing an arbitrarily long node sequence. The attribute "n" reports the order of the node in the sequence. I need to arrange as output that sequence in a M (rows) x N (columns) table and I have some trouble doing that. I cannot call a template
<xsl:template match="recordset">
<table>
<xsl:apply-templates select="record"/>
</table>
</xsl:template>
with something like:
<xsl:template match="record">
<xsl:if test="#n mod 3 = 0">
<tr>
</xsl:if>
........
<td><xsl:value-of select"something"></td>
because code is invalid (and I should repeat it at the end of the template in some way)
and I must put some (maybe too much) trust in the presence of the numbered attribute. Someone has a hint? Thanks!
You must ensure that nesting is never broken. Things you want nested in the output must be nested in the XSLT.
<xsl:variable name="perRow" select="3" />
<xsl:template match="recordset">
<table>
<xsl:apply-templates
mode = "tr"
select = "record[position() mod $perRow = 1]"
/>
</table>
</xsl:template>
<xsl:template match="record" mode="tr">
<tr>
<xsl:variable name="td" select="
. | following-sibling::record[position() < $perRow]
" />
<xsl:apply-templates mode="td" select="$td" />
<!-- fill up the last row -->
<xsl:if test="count($td) < $perRow">
<xsl:call-template name="filler">
<xsl:with-param name="rest" select="$perRow - count($td)" />
</xsl:call-template>
</xsl:if>
</tr>
</xsl:template>
<xsl:template match="record" mode="td">
<td>
<xsl:value-of select="." />
</td>
</xsl:template>
<xsl:template name="filler">
<xsl:param name="rest" select="0" />
<xsl:if test="$rest">
<td />
<xsl:call-template name="filler">
<xsl:with-param name="rest" select="$rest - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Using xslt 2.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:param name="rows">3</xsl:param>
<xsl:template match="recordset">
<table>
<xsl:for-each-group select="record" group-by="count(preceding-sibling::*) mod $rows ">
<xsl:value-of select="current-grouping-key()"/>
<tr>
<xsl:for-each select="current-group()">
<td>
<xsl:apply-templates/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0, using a general n-per-row template.
With the row element name as a parameter, the n-per-row template is not tied to you input or output format.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="recordset">
<table>
<xsl:call-template name="n-per-row">
<xsl:with-param name="select" select="record" />
<xsl:with-param name="row-size" select="2"/>
<xsl:with-param name="row-element" select="'tr'"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template match="record">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template name="n-per-row">
<xsl:param name="select" />
<xsl:param name="row-size" />
<xsl:param name="row-element" />
<xsl:param name="start">
<xsl:text>1</xsl:text>
</xsl:param>
<xsl:variable name="count" select="count($select)" />
<xsl:variable name="last-tmp" select="number($start) + number($row-size)" />
<xsl:variable name="last">
<xsl:choose>
<xsl:when test="$last-tmp > $count">
<xsl:value-of select="$count"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$last-tmp"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$row-element}">
<xsl:apply-templates select="$select[position() <= $last]"/>
</xsl:element>
<xsl:if test="count($select) > $last">
<xsl:call-template name="n-per-row">
<xsl:with-param name="select" select="$select[position() > $last]"/>
<xsl:with-param name="row-size" select="$row-size"/>
<xsl:with-param name="row-element" select="$row-element"/>
<xsl:with-param name="start" select="$start"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>