How can I group homogenous data in xsl? - xslt

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>

Related

Dynamic/Generic XSLT for Verical and Horizontal Layouts

I have the following XSLT. It works great for horizontal layouts. However when there are too many columns I need it to flip to a vertical layout. Ultimately being able to configure or specify the number of columns in a variable would be best. Can anyone kindly help?
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/*">
<table>
<thead>
<tr>
<xsl:apply-templates select="*[1]/*" mode="th"/>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="*"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/*" mode="th">
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:template>
<xsl:template match="/*/*">
<tr>
<xsl:apply-templates select="*"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
I am not sure I have understood the question correctly but if you just want to output several tables, each having $cols-per-table columns, then I think the adaption of the presented code to
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="cols-per-table" select="10"/>
<xsl:output method="html" indent="yes" doctype-system="about:legacy-doctype"/>
<xsl:template match="/">
<xsl:apply-templates select="*/*[1]/*[position() mod $cols-per-table = 1]" mode="page"/>
</xsl:template>
<xsl:template match="*" mode="page">
<xsl:param name="page-no" select="position()"/>
<table border="1">
<thead>
<xsl:apply-templates select=". | following-sibling::*[position() < $cols-per-table]" mode="th"/>
</thead>
<tbody>
<xsl:apply-templates select="/*/*">
<xsl:with-param name="page-no" select="$page-no"/>
</xsl:apply-templates>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/*" mode="th">
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:template>
<xsl:template match="/*/*">
<xsl:param name="page-no"/>
<xsl:variable name="first-col" select="1 + $cols-per-table * ($page-no - 1)"/>
<xsl:variable name="last-col" select="$first-col + $cols-per-table - 1"/>
<tr>
<xsl:apply-templates select="*[position() >= $first-col and position() <= $last-col]"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYjr/1
I have used XSLT 1.0 as the original code used that version although with XSLT 2/3 and positional grouping using for-each-group and/or a key computing the "pag" a "column" element belongs to might be more efficient:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="cols-per-table" as="xs:integer" select="10"/>
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:key name="page-no" match="/*/*/*">
<xsl:variable name="index" as="xs:integer">
<xsl:number/>
</xsl:variable>
<xsl:sequence select="($index - 1) idiv $cols-per-table + 1"/>
</xsl:key>
<xsl:template match="/">
<xsl:for-each-group select="*/*[1]/*" group-adjacent="(position() - 1) idiv $cols-per-table">
<xsl:apply-templates select="." mode="page">
<xsl:with-param name="page-no" select="position()" tunnel="yes"/>
</xsl:apply-templates>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="*" mode="page">
<xsl:param name="page-no" tunnel="yes"/>
<table border="1">
<thead>
<xsl:apply-templates select="current-group()" mode="th"/>
</thead>
<tbody>
<xsl:apply-templates select="/*/*"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="/*/*/*" mode="th">
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:template>
<xsl:template match="/*/*">
<xsl:param name="page-no" tunnel="yes"/>
<tr>
<xsl:apply-templates select="key('page-no', $page-no, .)"/>
</tr>
</xsl:template>
<xsl:template match="/*/*/*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyRYYjr/4
At https://xsltfiddle.liberty-development.net/jyRYYjr/5 I have implemented a pure XSLT 3 approach solely using for-each-group and storing the "pages" and "columns" in a map.

Xpath - How to get all existing attributes name of siblings

The goal is simple. I'm trying to build a table with attributes in columns.
My problem : Some siblings don't have the same attributes as the first one.
When I build the table head I retrieve the attributes name of the first node and then hope that attributes of the following siblings will be the same in the same order. That is not the case.
I get only columns id, key, value, myattr1 and the myattr2 attribute is placed in the myattr1 column.
For building the table, I want to get columns : id, key, value, myattr1, myattr2
How can I retrieve the whole list of existing attributes for the node I am working on and loop over it?
I am still working the same js and form (see link at the bottom). It now requires bootstrap (css and js).
I slightly changed the xml. Here it is :
<?xml version="1.0" encoding="UTF-8"?>
<Domain>
<Properties id="myid">
<Property id="DOM00000" key="mykey1" value="value1" myattr2="Mail"/>
<Property id="DOM00001" key="mykey2" value="value2" myattr1="EveryDay"/>
</Properties>
<Tokens>
<Token name="token1" comment="" ><![CDATA[mydata1---blah-blah-blah]]></Token>
<Token name="token2" comment="" ><![CDATA[mydata2---blah-blah-blah]]></Token>
</Tokens>
<Resources>
<Resource name="res1" type="W" current="0">
<Value><![CDATA[10]]></Value>
</Resource>
<Resource name="res2" type="W" current="0">
<Value><![CDATA[10]]></Value>
</Resource>
</Resources>
</Domain>
The current state of the xsl :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<xsl:element name="div">
<xsl:attribute name="class">container</xsl:attribute>
<ul class="nav nav-tabs">
<xsl:for-each select="./*">
<xsl:call-template name="tabs" />
</xsl:for-each>
</ul>
</xsl:element>
<xsl:element name="div">
<xsl:attribute name="class">tab-content</xsl:attribute>
<xsl:for-each select="./*">
<xsl:call-template name="tabcontent" />
</xsl:for-each>
</xsl:element>
</xsl:template>
<xsl:template name="tabs">
<xsl:variable name="active">
<xsl:choose>
<xsl:when test="preceding-sibling::*"></xsl:when>
<xsl:otherwise>active</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="li">
<xsl:attribute name="class">nav-item <xsl:value-of select="$active" /></xsl:attribute>
<xsl:element name="a">
<xsl:attribute name="href">#<xsl:value-of select="name(.)" /></xsl:attribute>
<xsl:attribute name="class">nav-link</xsl:attribute>
<xsl:attribute name="data-toggle">tab</xsl:attribute>
<xsl:value-of select="name(.)" />
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template name="tabcontent">
<xsl:variable name="activetab">
<xsl:choose>
<xsl:when test="preceding-sibling::*"></xsl:when>
<xsl:otherwise>active in</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="div">
<xsl:attribute name="id"><xsl:value-of select="name(.)" /></xsl:attribute>
<xsl:attribute name="class">container tab-pane fade <xsl:value-of select="$activetab" /></xsl:attribute>
<h3><xsl:value-of select="name(.)" /></h3>
<table class="table table-striped table-hover">
<thead>
<tr><xsl:for-each select="./*[1]/#*"><th><xsl:value-of select="name(.)" /></th></xsl:for-each></tr>
</thead>
<tbody>
<xsl:for-each select="./*"><tr>
<xsl:for-each select="./#*"><td><xsl:value-of select="." /></td></xsl:for-each></tr>
</tr></xsl:for-each>
</tbody>
</table>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
XSLT - How to manage CDATA as common content?
Edit :
Thanks to Tim-C answer
Here is the full xsl working for my use case :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="attrs" match="/*/*/*/#*" use="concat(name(../..), '|', name())" />
<xsl:template match="/*">
<xsl:element name="div">
<xsl:attribute name="class">container</xsl:attribute>
<ul class="nav nav-tabs">
<xsl:for-each select="./*">
<xsl:call-template name="tabs" />
</xsl:for-each>
</ul>
</xsl:element>
<xsl:element name="div">
<xsl:attribute name="class">tab-content</xsl:attribute>
<xsl:for-each select="./*">
<xsl:call-template name="tabcontent" />
</xsl:for-each>
</xsl:element>
</xsl:template>
<xsl:template name="tabs">
<xsl:variable name="active">
<xsl:choose>
<xsl:when test="preceding-sibling::*"></xsl:when>
<xsl:otherwise>active</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="li">
<xsl:attribute name="class">nav-item <xsl:value-of select="$active" /></xsl:attribute>
<xsl:element name="a">
<xsl:attribute name="href">#<xsl:value-of select="name(.)" /></xsl:attribute>
<xsl:attribute name="class">nav-link</xsl:attribute>
<xsl:attribute name="data-toggle">tab</xsl:attribute>
<xsl:value-of select="name(.)" />
</xsl:element>
</xsl:element>
</xsl:template>
<xsl:template name="tabcontent">
<xsl:variable name="activetab">
<xsl:choose>
<xsl:when test="preceding-sibling::*"></xsl:when>
<xsl:otherwise>active in</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="attrs" select="*/#*[generate-id() = generate-id(key('attrs', concat(name(../..), '|', name()))[1])]" />
<xsl:element name="div">
<xsl:attribute name="id"><xsl:value-of select="name(.)" /></xsl:attribute>
<xsl:attribute name="class">container tab-pane fade <xsl:value-of select="$activetab" /></xsl:attribute>
<h3><xsl:value-of select="name(.)" /></h3>
<table class="table table-striped table-hover">
<thead>
<tr>
<xsl:for-each select="$attrs">
<th>
<xsl:value-of select="name()" />
</th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:for-each select="*">
<tr>
<xsl:variable name="current" select="." />
<xsl:for-each select="$attrs">
<td>
<xsl:value-of select="$current/#*[name() = name(current())]" />
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:element>
</xsl:template>
<xsl:template name="toto"></xsl:template>
</xsl:stylesheet>
What you make use of here is a technique called Muenchian Grouping to get a list of distinct attributes, based on their name.
However, although the question just mentions about id, key, value, myattr1, myattr2, which are the Property attributes, it looks like you want to repeat it for the Token and Resource nodes too (i.e. you are trying to be generic). In this case, you define a key like so, which takes into account the main element name
<xsl:key name="attrs" match="/*/*/*/#*" use="concat(name(../..), '|', name())" />
Then, for a given element (such as Properties) you can get distinct attributes like so:
<xsl:variable name="attrs" select="*/#*[generate-id() = generate-id(key('attrs', concat(name(../..), '|', name()))[1])]" />
This can then be used to get the headers, and access the relevant attributes for each row.
Try this (abridged) XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="attrs" match="/*/*/*/#*" use="concat(name(../..), '|', name())" />
<xsl:template match="/*">
<xsl:for-each select="*">
<xsl:call-template name="tabcontent" />
</xsl:for-each>
</xsl:template>
<xsl:template name="tabcontent">
<xsl:variable name="attrs" select="*/#*[generate-id() = generate-id(key('attrs', concat(name(../..), '|', name()))[1])]" />
<table class="table table-striped table-hover">
<thead>
<tr>
<xsl:for-each select="$attrs">
<th>
<xsl:value-of select="name()" />
</th>
</xsl:for-each>
</tr>
</thead>
<tbody>
<xsl:for-each select="*">
<tr>
<xsl:variable name="current" select="." />
<xsl:for-each select="$attrs">
<td>
<xsl:value-of select="$current/#*[name() = name(current())]" />
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</tbody>
</table>
</xsl:template>
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/jyRYYi7

Line Break in XSLT for a comma separated value

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.

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>