XSLT Global count of grouped items - xslt

I have a set of items which I am grouping using the muenchian method using keys. This is working great, however when I try to do things with the first x number of items it is doing it on the x number of items in each group rather than across the whole set of results. How would I get the individual position of each item accross the whole collection?
<xsl:key name="pictures-by-productid" match="/dsQueryResponse/Rows/Row" use="#ProductId" />
<xsl:template match="/">
<div style="border:1px solid red; float:left;">
<xsl:apply-templates select="/" mode="sub">
</xsl:apply-templates>
</div>
</xsl:template>
and the second template
<xsl:template match="/" mode="sub"> <xsl:for-each select="/dsQueryResponse/Rows/Row[count(. | key('pictures-by-productid', #ProductId)[1]) = 1]">
<xsl:for-each select="key('pictures-by-productid', #ProductId)">
<xsl:sort select="#PictureType" />
<div style="float:left; margin:2px;">
<img src="{#ThumbNailUrl}" width="58" /> <br />
Download
<xsl:number value="position()" format="1. " />
<xsl:value-of select="." />
</div>
</xsl:for-each>
</xsl:for-each>
</xsl:template>

<xsl:key
name="pictures-by-productid"
match="/dsQueryResponse/Rows/Row"
use="#ProductId"
/>
<xsl:template match="/">
<div style="border:1px solid red; float:left;">
<!-- iterate the group, sorted by PictureType… -->
<xsl:for-each select="/dsQueryResponse/Rows/Row[
count(. | key('pictures-by-productid', #ProductId)[1]) = 1
]">
<xsl:sort select="#PictureType">
<!-- …and output only if among the fist 6 items -->
<xsl:if test="position() <= 6">
<xsl:apply-templates
mode="picture"
select="key('pictures-by-productid', #ProductId)"
/>
</xsl:if>
</xsl:for-each>
</div>
</xsl:template>
<xsl:template match="/dsQueryResponse/Rows/Row" mode="picture">
<div style="float:left; margin:2px;">
<img src="{#ThumbNailUrl}" width="58" /> <br />
<xsl:text>Download </xsl:text>
<xsl:number value="position()" format="1. " />
<xsl:value-of select="." />
</div>
</xsl:template>

Related

xsl - wrap every 2 items in the for-each with a div

I have an xsl:for-each and I would like to wrap every 2 items in a div. How to do that?
<xsl:for-each select="$datapath/PackageInfoList/PackageInfo">
<!-- lots of html in here -->
</xsl:for-each>
So the result would be:
<div>
<!-- lots of html in here -->
<!-- lots of html in here -->
</div>
<div>
<!-- lots of html in here -->
<!-- lots of html in here -->
</div>
Select the odd <PackageInfo> elements, like this
<xsl:for-each select="$datapath/PackageInfoList/PackageInfo[position() mod 2 = 1]">
<div>
<!-- lots of html in here -->
<!-- do something with following-sibling::PackageInfo[1] -->
</div>
</xsl:for-each>
This runs for elements at positions 1, 3, 5, etc. Handle the respective first following <PackageInfo> manually.
More idiomatic
<xsl:template match="/">
<xsl:apply-templates select="$datapath/PackageInfoList/PackageInfo" mode="group2" />
</xsl:template>
<xsl:template match="PackageInfo" mode="group2">
<xsl:if test="position() mod 2 = 1">
<div>
<xsl:apply-templates select=". | following-sibling::PackageInfo[1]" />
</div>
</xsl:if>
</xsl:template>
<xsl:template match="PackageInfo">
<!-- lots of html in here -->
</xsl:template>
More flexible
<xsl:template match="/">
<xsl:apply-templates select="$datapath/PackageInfoList/PackageInfo" mode="group">
<xsl:with-param name="groupcount" select="2" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="PackageInfo" mode="group">
<xsl:param name="groupcount" select="2" />
<xsl:if test="position() mod $groupcount = 1">
<div>
<xsl:apply-templates select=". | following-sibling::PackageInfo[position() < $groupcount]" />
</div>
</xsl:if>
</xsl:template>
<xsl:template match="PackageInfo">
<!-- lots of html in here -->
</xsl:template>

XSLT Not working

Hi my XML document and XSLT are not working to produce good HTML... what is going on?
This is basically XML file and I have validated this with the XML Schema but
when I use XSLT file to convert it into HTML file it just generates Courses Catalogue heading
with one paragraph of whole bunch of text.
What kinds of problem Do I have here?
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Generate HTML output -->
<xsl:output method="html"/>
<!-- The root template is defined here -->
<xsl:template match="/">
<html>
<head>
<title>Courses Catalogue</title>
</head>
<body>
<h2>Courses catalogue</h2>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="course">
<p>
<xsl:apply-templates select="code" />
<xsl:apply-templates select="title" />
<xsl:apply-templates select="year" />
<xsl:apply-templates select="science" />
<xsl:apply-templates select="area" />
<xsl:apply-templates select="subject" />
<xsl:apply-templates select="updated" />
<xsl:apply-templates select="unit" />
<xsl:apply-templates select="description" />
<xsl:apply-templates select="outcomes" />
<xsl:apply-templates select="incompatibility" />
</p>
</xsl:template>
<xsl:template match="code">
Course Code: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="title">
Course Title: <span style="color:#000">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="year">
Student Year: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="science">
Science Group: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="area">
Area: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="subject">
Course Subject: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="updated">
Page was updated in: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="unit">
Unit: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="description">
Course Description: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="outcomes">
Course Outcomes: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
< xsl:template match="incompatibility">
Incompatible courses: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
</xsl:stylesheet>
AND MY XML FILE
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"
href="courses.xsl"?>
<!--catalogue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="catalogue.xsd"-->
<catalogue xmlns="file://"
xmlns:xsi="http:/e"
xsi:schemaLocation="file://">
<course>
<code>COMP3410</code>
<title> Information Technology in Electronic Commerce </title>
<year>later year</year>
<science>C</science>
<area> Research School of Computer Science </area>
<subject> Computer Science </subject>
<updated>2012-03-13T13:12:00</updated>
<unit>6</unit>
Thanks
Your problem is that all of the elements are in the file://Volumes/u1234567/Assignment namespace, but in your XSLT your templates are matching on elements without a namespace.
If you look closely at the <catalogue> you will see a namespace declaration without a prefix. <catalogue xmlns="file://Volumes/u1234567/Assignment" All of the descendant elements inherit that namespace.
Define that namespace with a prefix in your XSLT and then change the places where you refer to those elements to use that namespace prefix:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="file://Volumes/u1234567/Assignment">
<!-- Generate HTML output -->
<xsl:output method="html"/>
<!-- The root template is defined here -->
<xsl:template match="/">
<html>
<head>
<title>Courses Catalogue</title>
</head>
<body>
<h2>Courses catalogue</h2>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="a:course">
<p>
<xsl:apply-templates select="a:code" />
<xsl:apply-templates select="a:title" />
<xsl:apply-templates select="a:year" />
<xsl:apply-templates select="a:science" />
<xsl:apply-templates select="a:area" />
<xsl:apply-templates select="a:subject" />
<xsl:apply-templates select="a:updated" />
<xsl:apply-templates select="a:unit" />
<xsl:apply-templates select="a:description" />
<xsl:apply-templates select="a:outcomes" />
<xsl:apply-templates select="a:incompatibility" />
</p>
</xsl:template>
<xsl:template match="a:code">
Course Code:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:title">
Course Title:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:year">
Student Year: <span style="color:#C66">
<xsl:value-of select="." /> </span>
<br />
</xsl:template>
<xsl:template match="a:science">
Science Group:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:area">
Area:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:subject">
Course Subject:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:updated">
Page was updated in:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:unit">
Unit:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:description">
Course Description:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:outcomes">
Course Outcomes:
<xsl:value-of select="." />
<br />
</xsl:template>
<xsl:template match="a:incompatibility">
Incompatible courses:
<xsl:value-of select="." />
<br />
</xsl:template>
</xsl:stylesheet>

A variable passed on to a template is tampered with

<xsl:param name="currentPage"/>
<xsl:param name="group-size" select="'2'" />
<xsl:template match="/">
<xsl:variable name="userBlogSource" select="umbraco.library:GetXmlNodeById(umbraco.library:GetDictionaryItem('WeblogNode'))/node [string(data [#alias='umbracoNaviHide']) != '1' and data[#alias='author'] = $currentPage/#id]" />
<xsl:for-each select="$userBlogSource">
<xsl:sort select="data[#alias='dato']" order="descending" />
</xsl:for-each>
<xsl:if test="count($userBlogSource) > 0">
<h3><xsl:value-of select="umbraco.library:GetDictionaryItem('BlogpostsWrittenBy')"/> <xsl:value-of select="$currentPage/#nodeName" />:</h3>
<xsl:apply-templates select="$userBlogSource[(position() mod $group-size) = 1]" />
<ul>
<xsl:for-each select="$userBlogSource">
<li>
<a rel="bookmark" href="{umbraco.library:NiceUrl(#id)}" title="{#nodeName}">
<xsl:value-of select="#nodeName" />
</a>
</li>
</xsl:for-each>
</ul>
</xsl:if>
</xsl:template>
<xsl:template match="node">
<xsl:variable name="postnum" select="position()" />
<div class="weblog-posts-wrap">
<ul>
<xsl:for-each select=". | following-sibling::node[position() < $group-size]">
<li>
<a rel="bookmark" href="{umbraco.library:NiceUrl(#id)}" title="{#nodeName}">
<xsl:value-of select="#nodeName" />
</a>
</li>
</xsl:for-each>
</ul>
</div>
</xsl:template>
When I check the count of $userBlogSource it contains three elements. When I attempt to print the in a regular XSLT for loop it does print the correct elements. But when the variable is passed to the second template matching node, the content is suddenly different. Instead of the three nodes it should contain, it contains 4 nodes by entirely different authors.
Any idea of what I may be doing wrong?

Xslt extracting attributes and generating html

I'm trying to transform the following XML..
<?xml version="1.0" encoding="utf-16"?><Member TextRank="unknown" FullName="My Name" ..etc.. />
Into something like the following,
<div class="member">
<span class="label">
Text Rank (note: i want to override the labels in the xslt)
</div>
<span class="value">
unknown
</span>
<span class="label">
Full Name
</div>
<span class="value">
My Name
</span>
..etc..
</div>
How if possible could I do this using xslt?
Here's a different approach, that does away for the need for the xsl:choose element. Instead, you could take advantage of the matching templates to have specific templates for the cases of attributes who names you want to override, and a generic template for other case.
To avoid repetition of code, you could also make the generic template a named template, with a parameter to override the name
<xsl:template match="#*" name="attribute">
<xsl:param name="label" select="local-name()" />
So, the default for most attributes would be to use the attribute name, but the specific template for #FullName could then call this with a different name.
<xsl:template match="#FullName">
<xsl:call-template name="attribute">
<xsl:with-param name="label" select="'Full Name'" />
</xsl:call-template>
</xsl:template>
Here is the full XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="*">
<div class="{local-name()}">
<div> Title: </div>
<xsl:apply-templates select="#*"/>
</div>
</xsl:template>
<xsl:template match="#FullName">
<xsl:call-template name="attribute">
<xsl:with-param name="label" select="'Full Name'" />
</xsl:call-template>
</xsl:template>
<xsl:template match="#*" name="attribute">
<xsl:param name="label" select="local-name()" />
<span class="label">
<xsl:value-of select="concat($label, ' : ')"/>
</span>
<span class="value">
<xsl:value-of select="."/>
</span>
<br/>
</xsl:template>
</xsl:stylesheet>
When applied to the following XML:
<Member TextRank="unknown" ID="12" FullName="My Name" Dob="01/01/1970" />
The following is output:
<div class="Member">
<div> Title: </div>
<span class="label">TextRank : </span>
<span class="value">unknown</span>
<br>
<span class="label">ID : </span>
<span class="value">12</span>
<br>
<span class="label">Full Name : </span>
<span class="value">My Name</span>
<br>
<span class="label">Dob : </span>
<span class="value">01/01/1970</span>
<br>
</div>
This is the solution I came up with.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes" />
<xsl:template match="*">
<xsl:element name="div">
<xsl:attribute name="class">className</xsl:attribute>
<div>
Title:
</div>
<!-- UID, Name, DBO-->
<xsl:apply-templates select="#ID"/>
<xsl:apply-templates select="#FullName"/>
<xsl:apply-templates select="#Dob"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:element name="span">
<xsl:attribute name="class">label</xsl:attribute>
<xsl:choose>
<xsl:when test="name() = 'FullName'">
Full Name
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="name()"/>
</xsl:otherwise>
</xsl:choose>
:
</xsl:element>
<xsl:element name="span">
<xsl:attribute name="class">value</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
<br/>
</xsl:template>
</xsl:stylesheet>

Sorting a nodeset before passing to xsl:for-each

I have a situation where loop through a sorted nodeset and apply a template on each of the nodes:
<div id="contractscontainer">
<xsl:for-each select="document">
<xsl:sort select="content[#name='ClientName']/text()" />
<xsl:apply-templates select="." mode="client-contract" />
</xsl:for-each>
</div>
I want to do something special with the "first" 5 nodes in the node set and render them nested element. The problem is that they need to be in the same order as if they were sorted (as they are in the loop).
I had planned on doing this by using two xsl:for-each elements, each with the correct nodes selected from the set. I can't do this, however, because they need to be sorted before I can select the "first" 5.
Example:
<div id="contractscontainer">
<div class="first-five">
<xsl:for-each select="document[position() < 6]">
<xsl:sort select="content[#name='ClientName']/text()" />
<xsl:apply-templates select="." mode="client-contract" />
</xsl:for-each>
</div>
<div class="rest-of-them">
<xsl:for-each select="document[position() > 5]">
<xsl:sort select="content[#name='ClientName']/text()" />
<xsl:apply-templates select="." mode="client-contract" />
</xsl:for-each>
</div>
</div>
I don't think this will work because I'm selecting the nodes by position before sorting them, but I can't use xsl:sort outside of the xsl:for-each.
Am I approaching this incorrectly?
Edit: My current solution is to sort them and store the sorted set in another variable:
<xsl:variable name="sorted-docs">
<xsl:for-each select="document">
<xsl:sort select="content[#name='ClientName']/text()" />
<xsl:copy-of select="." />
</xsl:for-each>
</xsl:variable>
It works, but is there a better way?
Here is one way to do this in XSLT 1.0 without the need to use the xxx:node-set() extension function:
<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="/*">
<html>
<div class="first-five">
<xsl:apply-templates select="num">
<xsl:sort select="." data-type="number"/>
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="5"/>
</xsl:apply-templates>
</div>
<div class="rest-of-them">
<xsl:apply-templates select="num">
<xsl:sort select="." data-type="number"/>
<xsl:with-param name="pStart" select="6"/>
</xsl:apply-templates>
</div>
</html>
</xsl:template>
<xsl:template match="num">
<xsl:param name="pStart"/>
<xsl:param name="pEnd" select="999999999999"/>
<xsl:if test="position() >= $pStart
and
not(position() > $pEnd)">
<p><xsl:value-of select="."/></p>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on this simple XML document:
<nums>
<num>5</num>
<num>3</num>
<num>6</num>
<num>8</num>
<num>4</num>
<num>1</num>
<num>9</num>
<num>2</num>
<num>7</num>
<num>10</num>
</nums>
the wanted result is produced:
<html>
<div class="first-five">
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
</div>
<div class="rest-of-them">
<p>6</p>
<p>7</p>
<p>8</p>
<p>9</p>
<p>10</p>
</div>
</html>