Sample XSLT code for transforming XCRI CAP 1.1 into HTML web pages

From Xcri

Jump to: navigation, search

These code samples take an XCRI CAP 1.1 source document and transform it into two separate sections of HTML, which could be included in a course listings page (with links); and a course details page, with a table of presentations on offer. The XSLT (extensible style language transform) code is not intended to show best practice, but rather to give an indication of what is possible, and to work on a low-specification XML processor. You should check that the HTML output is valid, and suppress the XML declaration and namespaces if possible.

The idea would be to populate your website by automatically generating the CAP XML as a feed from your database and saving it to your web server, say on an overnight schedule. This static XML file could then be transformed by XSLT stylesheets like the samples below.

Contents

[edit] XCRI CAP 1.1 input

We will start with the sample CAP 1.1 output from Sample SQL Server 2005 code for generating XCRI XML from a simplified relational course database.

<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms" generated="2010-03-13T00:35:48.240" xsi:schemaLocation="http://xcri.org/profiles/catalog/terms http://www.xcri.org/bindings/xcri_cap_terms_1_1.xsd">
	<provider xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms">
		<identifier>0000X00XX0</identifier>
		<title>Acme University</title>
		<course xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms">
			<identifier>BSCAN</identifier>
			<title>BSc Applied Networking Technologies</title>
			<subject>Information Technology and Information</subject>
			<description xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms" xsi:type="xcriTerms:aim">
				<div xmlns="http://www.w3.org/1999/xhtml">
					<p>Industry analysts are currently predicting a shortfall of trained networking staff with a Business knowledge.  Building on from our HND this course is designed to equip students with the very latest skills and qualities that they will need to succeed in the vibrant IT orientated world.  You will develop specialist skills in security technologies and techniques and in particular the use of intrusion techniques and penetration testing as methods of determining the robustness of IT security.</p>
				</div>
			</description>
			<qualification>
				<title>BSc</title>
				<level>9</level>
			</qualification>
			<presentation xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms">
				<identifier>2008/2009/BSCAN/F1/BK/08</identifier>
				<start>2008-09-01T00:00:00</start>
				<studyMode>Full Time</studyMode>
			</presentation>
			<presentation xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms">
				<identifier>2008/2009/BSCAN/I1/BK/08</identifier>
				<start>2008-09-01T00:00:00</start>
				<studyMode>Part Time Day</studyMode>
			</presentation>
		</course>
		<course xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms">
			<identifier>UWBKW</identifier>
			<title>Underwater Basketweaving</title>
			<subject>Unreal crafts</subject>
			<description xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms" xsi:type="xcriTerms:aim">
				<div xmlns="http://www.w3.org/1999/xhtml">
					<p>Learn this traditional craft, using quality local materials, in the comfort of a swimming pool.</p>
					<p>Full snorkelling kit and reed thimbles are provided.</p>
				</div>
			</description>
			<qualification>
				<title />
				<level>0</level>
			</qualification>
			<presentation xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms">
				<identifier>2008/2009/UWBKW/X1/08</identifier>
				<start>2008-04-01T00:00:00</start>
				<studyMode>Day</studyMode>
			</presentation>
			<presentation xmlns="http://xcri.org/profiles/catalog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms">
				<identifier>2008/2009/UWBKW/Y1/08</identifier>
				<start>2008-04-30T00:00:00</start>
				<studyMode>Part Time Day</studyMode>
			</presentation>
		</course>
	</provider>
</catalog>

[edit] XSLT for course listings

This XSLT stylesheet finds all subjects, sorts them alphabetically, and outputs a section of HTML with course titles, wrapped in hyperlinks, grouped by subject. It is a fairly simple method, and not necessarily the most efficient (the preceding axis can be processor-intensive).

HTML heading levels are optionally passed in as parameters, or you can just keep the defaults.

You would likely have to change the URL generated in the hyperlink to match your site's pattern.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"
	xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xhtml="http://www.w3.org/1999/xhtml"
	xmlns:xcri="http://xcri.org/profiles/catalog">
	<xsl:output method="xml" version="1.0" encoding="UTF-8" omit-xml-declaration="yes" indent="yes"/>
	<xsl:param name="headingLevel" select="'h1'"/>
	<xsl:param name="subjectHeadingLevel" select="'h2'"/>
	<xsl:variable name="subjects"
		select="/xcri:catalog/xcri:provider/xcri:course/xcri:subject[not(. = preceding::xcri:subject)]"/>
	<xsl:template match="/">
		<xsl:element name="div">
			<xsl:element name="{$headingLevel}">
				<xsl:text>Courses from </xsl:text>
				<xsl:value-of select="xcri:catalog/xcri:provider/xcri:title"/>
			</xsl:element>
			<xsl:for-each select="$subjects">
				<xsl:sort select="."/>
				<xsl:variable name="thisSubject" select="."/>
				<xsl:element name="{$subjectHeadingLevel}">
					<xsl:value-of select="$thisSubject"/>
				</xsl:element>
				<xsl:element name="ul">
					<xsl:for-each select="/xcri:catalog/xcri:provider/xcri:course[xcri:subject = $thisSubject]">
						<xsl:sort select="title"/>
						<xsl:element name="li">
							<xsl:element name="a">
								<xsl:attribute name="href">
									<xsl:value-of select="concat('/courses(', xcri:identifier, ')')"/>
								</xsl:attribute>
								<xsl:value-of select="xcri:title"/>
							</xsl:element>
						</xsl:element>
					</xsl:for-each>
				</xsl:element>
			</xsl:for-each>
		</xsl:element>
	</xsl:template>
</xsl:stylesheet>

[edit] XSLT for course details page

This XSLT stylesheet expects to be passed one course identifier as a parameter.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml"
	xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms"
	exclude-result-prefixes="xhtml xcri xcriTerms xsi">
	<xsl:output method="xml" version="1.0" encoding="UTF-8" omit-xml-declaration="yes" indent="yes"/>
	<xsl:param name="course"/>
	<xsl:template match="/">
		<xsl:element name="div" namespace="http://www.w3.org/1999/xhtml">
			<xsl:apply-templates/>
		</xsl:element>
	</xsl:template>
	<xsl:template match="xcri:catalog/xcri:provider/xcri:course">
		<xsl:if test="xcri:identifier = $course">
			<xsl:element name="h1">
				<xsl:value-of select="xcri:title"/>
			</xsl:element>
			<xsl:element name="h2">
				<xsl:text>Aim</xsl:text>
			</xsl:element>
			<xsl:copy-of select="xcri:description[@xsi:type='xcriTerms:aim']/*"/>
			<xsl:if test="xcri:presentation">
				<table summary="Course offerings by start date and study mode.">
					<caption>List of course presentations</caption>
					<tr>
						<th scope="col">Identifer</th>
						<th scope="col">Start date</th>
						<th scope="col">Study mode</th>
					</tr>
					<xsl:for-each select="xcri:presentation">
						<tr>
							<td><xsl:value-of select="xcri:identifier"/></td>
							<td><xsl:value-of select="xcri:start"/></td>
							<td><xsl:value-of select="xcri:studyMode"/></td>
						</tr>
					</xsl:for-each>
				</table>
			</xsl:if>
		</xsl:if>
	</xsl:template>
	<!-- Do nothing with provider identifier and title, for now. -->
	<xsl:template match="xcri:catalog/xcri:provider/xcri:identifier"/>
	<xsl:template match="xcri:catalog/xcri:provider/xcri:title"/>
</xsl:stylesheet>

This sample HTML output shows the transformation for the parameter $course equal to 'BSCAN'.

<div xmlns="http://www.w3.org/1999/xhtml">
   <h1>BSc Applied Networking Technologies</h1>
   <h2>Aim</h2>
   <div xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xcri="http://xcri.org/profiles/catalog" xmlns:xcriTerms="http://xcri.org/profiles/catalog/terms">
      <p>Industry analysts are currently predicting a shortfall of trained networking staff with a Business knowledge.  Building on
         from our HND this course is designed to equip students with the very latest skills and qualities that they will need to succeed
         in the vibrant IT orientated world.  You will develop specialist skills in security technologies and techniques and in particular
         the use of intrusion techniques and penetration testing as methods of determining the robustness of IT security.
      </p>
   </div>
   <table xmlns:xhtml="http://www.w3.org/1999/xhtml" summary="Course offerings by start date and study mode.">
      <caption>List of course presentations</caption>
      <tr>
         <th scope="col">Identifer</th>
         <th scope="col">Start date</th>
         <th scope="col">Study mode</th>
      </tr>
      <tr>
         <td>2008/2009/BSCAN/F1/BK/08</td>
         <td>2008-09-01T00:00:00</td>
         <td>Full Time</td>
      </tr>
      <tr>
         <td>2008/2009/BSCAN/I1/BK/08</td>
         <td>2008-09-01T00:00:00</td>
         <td>Part Time Day</td>
      </tr>
   </table>
</div>

[edit] XSLT for course discovery page

This style sheet produces a section of course listing HTML markup with paged results and groups of links which refine (filter) the result set. Note: this sample uses XSLT 2.0 and XPath 2.0 (for efficiency, otherwise workarounds would make the code more verbose) and is not suitable for frameworks/XSLT processors which only support XSLT 1.0 or XPath 1.0. The code was tested with Saxon-HE 9.3.0.4.

The idea is to show the first 20 (or so) courses on a web page, generating links to further pages and to values of qualifications, study modes and subjects to allow the user to narrow down their search. It is expected that the web page server code handles the parameters being passed back and forth. As a link (say, a specific qualification) is clicked on, the course subset is regenerated to only return courses with that qualification. This is a simpler version of the type of search refinement used by sites such as Amazon.

In practice, the desired course properties used will vary, and depend on the quality of the data, and whether controlled vocabularies or free text is used. This approach may be combined with a free text or controlled vocabulary keyword search.

To keep the number of choices in a property group manageable, a higher level of grouping may be desirable. This could be provided by a separate XML document, say grouping subjects hierarchically (to drill down through them), or by relying on data already in the CAP document, such as grouping several qualifications together based on level.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:xhtml="http://www.w3.org/1999/xhtml"
	xmlns:xcri="http://xcri.org/profiles/catalog"
	xmlns:fn="http://www.w3.org/2005/xpath-functions"
	exclude-result-prefixes="xs xd dc xhtml xcri fn"
	version="2.0">
	<xsl:output method="xml" version="1.0" encoding="UTF-8" omit-xml-declaration="yes" indent="yes"/>
	<xd:doc scope="stylesheet">
		<xd:desc>
			<xd:p><xd:b>Created on:</xd:b> 2011-05-22</xd:p>
			<xd:p><xd:b>Author:</xd:b> Tavis Reddick</xd:p>
			<xd:p>Takes an XCRI CAP 1.1 document and outputs an XHTML section containing results and filtering links.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="qualification" />
	<xsl:param name="studyMode" />
	<xsl:param name="subject" />
	<xsl:param name="headingLevel" select="'h1'"/>
	<xsl:param name="linkGroupHeadingLevel" select="'h2'"/>
	<xsl:param name="page" select="1" />
	<xsl:param name="pageSize" select="20" />
	<xsl:variable name="queryStringBase" select="concat('?qualification=', $qualification, '&studymode=', $studyMode,
		'&subject=', $subject, '&pagesize=', $pageSize)" />
	<!-- The $courses variable holds the currently selected subset of courses, filtered by the parameters. -->
	<xsl:variable name="courses" select="/xcri:catalog/xcri:provider/xcri:course[($qualification = '' or $qualification =
		xcri:qualification/xcri:title) and ($studyMode = '' or $studyMode = xcri:presentation/xcri:studyMode) and ($subject =
		'' or $subject = xcri:subject)]" />
	<xsl:variable name="qualifications" select="fn:distinct-values($courses/xcri:qualification/xcri:title)"/>
	<xsl:variable name="studyModes" select="fn:distinct-values($courses/xcri:presentation/xcri:studyMode)"/>
	<xsl:variable name="subjects" select="fn:distinct-values($courses/xcri:subject)"/>
	<xsl:template match="/">
		<xsl:element name="div">
			<xsl:element name="{$headingLevel}">
				<xsl:text>Courses from </xsl:text>
				<xsl:value-of select="xcri:catalog/xcri:provider/xcri:title"/>
			</xsl:element>
			<!-- Create a section to hold the filtering links. -->
			<div style="float: left;">
				<!-- Qualification links. -->
				<xsl:element name="{$linkGroupHeadingLevel}"><xsl:text>Qualifications</xsl:text></xsl:element>
				<ul>
					<xsl:if test="not($qualification = '')">
						<li><a href="{concat('?qualification=&studymode=', $studyMode, '&subject=', $subject, '&pagesize=', $pageSize)}">All qualifications</a></li>
					</xsl:if>
					<xsl:for-each select="$qualifications">
						<xsl:sort data-type="text" order="ascending" />
						<!-- Create a link with existing parameter values, with the addition of each existing qualification value in the current
							course selection. -->
						<xsl:variable name="queryString" select="concat('?qualification=', fn:encode-for-uri(.), '&studymode=', $studyMode,
							'&subject=', $subject, '&pagesize=', $pageSize)" />
						<li><a href="{$queryString}"><xsl:value-of select="." /></a></li>
					</xsl:for-each>
				</ul>
				<!-- Study mode links. -->
				<xsl:element name="{$linkGroupHeadingLevel}"><xsl:text>Study Modes</xsl:text></xsl:element>
				<ul>
					<xsl:if test="not($studyMode = '')">
						<li><a href="{concat('?qualification=', $qualification, '&studymode=&subject=', $subject,
							'&pagesize=', $pageSize)}">All study modes</a></li>
					</xsl:if>
					<xsl:for-each select="$studyModes">
						<xsl:sort data-type="text" order="ascending" />
						<!-- Create a link with existing parameter values, with the addition of each existing study mode value in the current
							course selection.  -->
						<xsl:variable name="queryString" select="concat('?qualification=', $qualification, '&studymode=', fn:encode-for-uri(.),
							'&subject=', $subject, '&pagesize=', $pageSize)" />
						<li><a href="{$queryString}"><xsl:value-of select="." /></a></li>
					</xsl:for-each>
				</ul>
				<!-- Subject links. -->
				<xsl:element name="{$linkGroupHeadingLevel}"><xsl:text>Subjects</xsl:text></xsl:element>
				<ul>
					<xsl:if test="not($subject = '')">
						<li><a href="{concat('?qualification=', $qualification, '&studymode=', $studyMode,
							'&subject=&pagesize=', $pageSize)}">All subjects</a></li>
					</xsl:if>
					<xsl:for-each select="$subjects">
						<xsl:sort data-type="text" order="ascending" />
						<!-- Create a link with existing parameter values, with the addition of each existing subject value in the current
							course selection.  -->
						<xsl:variable name="queryString" select="concat('?qualification=', $qualification, '&studymode=', $studyMode,
							'&subject=', fn:encode-for-uri(.), '&pagesize=', $pageSize)" />
						<li><a href="{$queryString}"><xsl:value-of select="." /></a></li>
					</xsl:for-each>
				</ul>
			</div>
			<!-- Create a section to hold the results. -->
			<!-- Create links to other pages of results as required. -->
			<p style="display: inline;">Page: </p>
			<ul style="display: inline;">
				<xsl:for-each select="$courses[(position() - 1) mod $pageSize = 0]">
					<xsl:choose>
						<xsl:when test="position() = $page">
							<li style="display: inline; padding: 1em;"><xsl:value-of select="position()" /></li>
						</xsl:when>
						<xsl:otherwise>
							<li style="display: inline; padding: 1em;"><a href="{concat($queryStringBase, '&page=', position())}"><xsl:value-of select="position()" /></a></li>
						</xsl:otherwise>
					</xsl:choose>
				</xsl:for-each>
			</ul>
			<!-- Create a table with course information. -->
			<table>
				<thead>
					<th>Course</th>
					<th>Qualification</th>
					<th>Subject</th>
					<th>Study modes</th>
				</thead>
				<tbody>
					<xsl:for-each select="$courses">
						<xsl:sort select="xcri:title"/>
						<xsl:if test="position() > $pageSize * ($page - 1) and position() < ($pageSize * $page) + 1">
							<tr>
								<td>
									<xsl:element name="a">
										<xsl:attribute name="href">
											<!-- Link to course details page. -->
											<xsl:value-of select="xcri:url" />
										</xsl:attribute>
										<xsl:value-of select="xcri:title"/>
									</xsl:element>
								</td>
								<td><xsl:value-of select="xcri:qualification/xcri:title"></xsl:value-of></td>
								<td><xsl:value-of select="xcri:subject"></xsl:value-of></td>
								<td><xsl:for-each select="fn:distinct-values(xcri:presentation/xcri:studyMode)"><xsl:value-of select="concat(., '; ')" /></xsl:for-each></td>
							</tr>
						</xsl:if>
					</xsl:for-each>
				</tbody>
			</table>
		</xsl:element>
	</xsl:template>
</xsl:stylesheet>

[edit] Further types of web page

You might also look at populating course subject or department pages from your XCRI CAP feed. You could also use your feed for course search purposes.

[edit] Working example

You can see a basic working example (using a feed generated from a SQL Server 2005 database, with the XSLT transformations handled by ASP.NET) at Courses from Kelpie College, a test site which also introduces a URL Rewriting rule to make passing the course identifier parameter in a bit friendlier:

RewriteRule ^courses\(([A-Za-z0-9]+)\) courses/course/?courseid=$1 [QSA]

--Tavis 00:58, 23 May 2011 (BST)

Personal tools