XSL Coding
Documentation on writing XSL
February 22, 2016
Here is my running documentation for coding XSL. It’s geared towards OU Campus, but should be universally applicable.
Table of Contents
- Foundation
- Semantics
- Naming
- Structure
- Characters
- Comments
- XSL is a transform language
- Containment
- Define Output
- Named Templates
- Value-Of to Output Data
- Predicates
- Copy-of vs Apply-Templates vs Call-Template
- Functions
- Passing Parameter
- Templating
- Format Output
- Sort
- Conditionals
- Operators
- Variables
- Loops
- Navigating Directories
Foundation
XSL stands for eXtensible Stylesheet Language and is a subset of language sets
- xslt (instruction elements)
- XPath (syntax for navigating and manipulating)
- XQuery (SQL like format - not really used in OU)
All tags in XML are yours and you need to define the tags. It is not a format in itself. HTML is a defined example based on XML.
Semantics
tag:
<a>
closing tag:
</a>
element:
<a>data</a>
tag with metadata:
<a i="metadata">data</a>
-for example, class or id name
element with no data:
<a></a>
<a/>
<a i="metadata"/>
Don’t put white space when declaring an attribute.
<!—- do -—> <a i="metadata">
<!—- don’t do -—> <a i ="metadata"/>
Naming
tags are case sensitive
- Camel Case
- snake_case
- dash_case
Structure
Must begin with xml tag, no white space before hand.
<?xml?>
but include version number
<?xml version=“1.0”?>
but actually include encoding as well, see below.
Characters
Encoding Background
Apple created Unicode to include all known characters. But Unicode needs to be encoded to bytes. Use encoding utf-8 (most used)
<?xml version=“1.0” encoding=UTF-8”?>
###Encode Punctuation
In metadata,
must encode " with "
' use '
or can use ' if inside ", or vice-versa
In data, must encode
< with <
> >
& and & (because of entity)
Other entity does not exist native/default. Can import others.
Comments
output comments with
<xsl:comment>this is a comment</xsl:comment>
regular comment will not output
In comments, don’t use – as text comment.
<!-- this is an illegal comment -- don't use a double dash inside a comment. -->
XSL is a transform language
- XSL files take one piece and convert to another.
- OUCampus: .pcf file is a normal xml file, plus a separate tag that points to where the transform goes
-
.pcf stands for publish control file
<?pcf-stylesheet path="/xsl/numbers.xsl" extension="html"?>
- pcf-stylesheet is a specific tag to OUCampus
- Could add multiple processing instructions
- Everything that precedes the first/primary element is called the prologue
Containment
Only 1 top level tag.
like <html>
Then nest additional elements inside the top element
Stylesheet Primary Element
In .xsl files, use stylesheet as the first/primary element (OUCampus)
<xsl:stylesheet></xsl:stylesheet><!-- or could use version 3.0 -->
Define version number
version="2.0"
<!-- or could use version 3.0 -->
“xsl:stylesheet” is an alias, but need to define the alias with namespace
xmlns:xsl=“http://www.w3.org/1999/XSL/Transform"
xmlns:xs=“http://www.w3.org/2001/XMLSchema" <!-- defines variable types -->
exclude-result-prefixes=“xs” <!—don’t output xs -->
final element code:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
Define Output
<xsl:output method="html" version="4.01" indent="yes" encoding="UTF-8"/>
<xsl:output doctype-public="-//W3C//DTD HTML 4.01//EN"/>
<xsl:output doctype-system="http://www.w3.org/TR/html4/strict.dtd"/>
remove doctype if html5
Processing vs Output
Begin element with xsl: for xsl tags and others will be default, which in our case will be assumed for output
<xsl:template match="/document">
<html>
<head>
<title><xsl:value-of select="title"/></title>
<!-- define variable with the path to the variable, including the element structure. document is the top level element, then title -->
</head>
<body>
hello world
</body>
</html>
</xsl:template>
Named Templates
Define a template with
<xsl:template name=“stuff”/>
Named templates is similar to functions in scripting - it doesn’t actually do anything until called/run. It is predefining some content for re-use.
Value-Of to Output Data
Define variable with the path to the variable, including the element structure.
XSL file:
<title><xsl:value-of select="/document/title"/></title>
or if document already defined above can skip the /document.
<xsl:template match="/document">
<title><xsl:value-of select="title"/></title>
</xsl:template>
or if title already defined above, can pair down to just a .
<xsl:value-of select="."/>
PCF file:
<document>
<title>Example Document</title>
</document>
xsl:value-of strips tags and just includes data (so no HTML tags)
Metadata and @
Use slightly different syntax when pulling in metadata
PCF File:
<css type="text/css">
span.red { color: red }
</css>
XSL file
- use {} when inside metadata instead of just inside element
-
in value of, use @ in path for metadata
<style type="{css/@type}"> <!-- use @ in path for metadata --> <xsl:value-of select="css/@type"/> <xsl:value-of select="css"/> </style> <!-- or --> <xsl:apply-templates select="$superheroes/profile[@source='Marvel']">
And use @ on match template as well
<xsl:template match="@heading">
Predicates
Filter secondary information with [ ]
<xsl:apply-templates select="student[number cast as xs:integer ge 10 and number cast as xs:integer le 20]">
<!-- Just pull students with number between 10 and 20 -->
select="superheroes/profile[1]/element()"
<!-- shorthand for position = 1 -->
select="superheroes/profile[position()=1]/element()"
<!-- longhand for position = 1 -->
INJECT XPATH INTO HTML WITH {}
<span class="label label-{}"> <span class="label label-{if(ancestor::profile/@source = 'Marvel') then 'danger' else 'default'}">
Copy-of vs Apply-Templates vs Call-Template
copy-of may pull in namespace into output
apply-template will sift through details for additional parsing and matching templates - passive, but potential to be smarter
call-template for more procedural dropping in content
Import Element With or Without Tags
xsl:value-of strips tags and just includes data (so no HTML tags) select=”.” strips tags and just includes data
<xsl:value-of select="structure/." />
to extract data and tags use
<xsl:copy-of select="structure/node()"/>
Applying Templates (match)
- Define how an element should be transformed
- Can be used instead of for-each
- Modular - use for when content is embedded in other content
- Use for when content should be reused
- Can still pass parameters
- Can target specific elements within template, and sort
- When target is embedded in other content
- Priority
- Import precedence
- Highest priority based on priority attribute
- Specific definition
- Node() < specific element
Format
<xsl:template match="course"/>
Using Modes
- Transforming source content into different output
- Takes effect before priority rules
Example Rule:
<xsl:template match="course" mode="table"/>
<tr>
<td><xsl:value-of select="id" /></td>
<td><xsl:value-of select="title" /></td>
<tr>
</xsl:template>
Example Call:
<xsl:apply-templates select="/courses/course" mode="table"/>
Functions
xslt tag:
<xsl:function ></xsl:function>
- Requires name attribute - unique identifier that utilized a namespace
- Could have same name, but would need different paramaters
- Parameters/arguments: cannot have default values
- build output in the sequence construtor
Define function Example
<xsl:function name="ou:hello" as="xs:string">
<xsl:param name="friend" as="xs:string" />
<xsl:value-of select="concat('Hello ', $friend, '!')" />
</xsl:function>
Call a function
- XPath
- Pass all parameters in order
- Correct data type
Call function example
<h3>
<xsl:value-of select="ou:hello-friend('Robert')"/>
<!-- or -->
<xsl:value-of select="ou:hello-friend(powers/text())"/>
</h3>
Function or Template
- Function
- Best for enhancing XPath functionality
- Great for string/data
- stricter requirements
- Named Templates
- structured xml
- default values ok
- don’t need to pass parameters
Passing Parameter
-
Define template with parameter
<xsl:template name="hero-graphic"> <!-- define parameter --> <xsl:param name="image-url" /> <!-- background image css in head --> <style> .background-image-container { background-image: url(<xsl:value-of select="image-url"/>); } </style> </xsl:template>
-
Call template and define parameter
<xsl:call-template name="hero-graphic" > <xsl:with-parameter name=“image-url" select="/document/img/@src" /> </xsl:call-template>
Notes on passing parameters
- Multiple parameters can be passed
- Paramater names need to match (order is just for readability)
- Variable types should match
- Can be set with default value
Templating
Cascading Templates
When apply or call templates of same name, location precedence trumps. Import gets trumped by code in same document. Could use this feature to leverage for overrides. For example, build out default page and XSL on common.xsl. Then put template overrides on template-specific.xsl
Modular Templates
Break up processes into separate templates. And then apply by nesting.
Key Code
<xsl:apply-templates select="css"/>
xsl with content
<xsl:template match="/document">
<html>
<head>
<title><xsl:value-of select="title"/></title>
</head>
<xsl:apply-templates select="css"/>
<body>
<span class="green">hello world</span>
</body>
</html>
</xsl:template>
<xsl:template match="css">
<style type="{@type}">
<xsl:value-of select="@type"/>
<xsl:value-of select="."/>
</style>
</xsl:template>
Break HTML pages into XSL templates
Template Types
Consider separating layouts by
- Common HTML structure
- Use caes
- Functionality
Break Down Page Into
- PCF properties (OU feature)
- common.xsl
- global include
- template specific.xsl
- global include
- content regions
XSL or Include
- XSL Template
- Structure, non content
- Output might change depending on folder or template - can write those conditions into XSL
- Update requires site publish (OU feature)
- Include
- Content, not structure
- Good for updating easily
- Publish single file, entire site is automatically updated
XSL or Server Side Script
- XSL
- Content doesn’t need to change very often
- Logic is based on location or section
- Example: related content sidebar
- Server Side Script
- Content needs to be updated on page load
- Source content located externally
- Example: latest news from blog or social media
OU Specific Templating
Directory
Set Directory (folder variable) and then modify output
-
Prep Variable in common.xsl
<xsl:param name="ou:theme-color" />
- Add actual value in Directory (through interface)
- variable name - theme color
- value with actual value
- Insert actual value
-
Test if string exists, then insert
<xsl:if test="string-length($ou:theme-color) gt 0"> <xsl:value-of select="$ou:theme-color" /> </xsl:if>
-
Edit Fields
In the .pcf
<ouc:div label="column2" group="Everyone" button-text="Column2">
<ouc:editor csspath="/workshop-xsl-advanced/xtras/wysiwyg.css" cssmenu="/workshop-xsl-advanced/xtras/styles.txt" wysiwyg-class="maincontent"/>
<p>column2</p>
</ouc:div>
label is the variable button-text appears on edit window after the editor comes the value
In the .xsl
<xsl:copy-of select="ouc:div[@label='column2']" />
Format Output
Date Type
Initial Format
<xsl:value-of select=“date"/>
Change Format
<xsl:value-of select="format-date(date, '[M01]/[D01]/[Y0001]')"/>
Add Attribute on Output
add/replace attribute
xsl:attribute
Even/Odd
Test if even
(position() - 1) mod 2 eq 0
position() - 1 shifts to 0 based number system mod 2 divides by 2 if remainder equals 0, then the number is even and the test is true.
Include within if statement
<tr>
<xsl:if test="(position() - 1) mod 2 eq 0">
<xsl:attribute name="class">even</xsl:attribute>
</xsl:if>
</tr>
Add class based on url depth
<li>
<xsl:attribute name="class">depth-<xsl:value-of select="string-length(concat($link)) - string-length(translate(concat($link), '/', ''))"/></xsl:attribute>
<xsl:value-of select="$title"/>
</li>
- Determine depth by subtracting / from the URL
- Add attribute to selector
Trim String
Just show 2 characters and beyond
<xsl:value-of select="substring($class, 2)"/>
Tokenize and Normalize
tokenize(normalize-space($text, ' '))
- Tokenize breaks up strings into separate pieces
- Normalize removes spaces
Add Space
If you need to print a space,
will cause an error use. Try:
<xsl:text> </xsl:text>
Sort
Sort output by applying a sort command immediately following pulling the data.
<xsl:apply-templates select="student">
<xsl:sort select="name" order="descending"/>
</xsl:apply-templates>
Conditionals
If
- Simple check against condition
- If true
- Content enabled
- No else statement
<xsl:if test=“$x = 4”> </xsl:if>
Choose
- 1 or 1+ when
- First match condition
-
0 or 1 otherwise (equivalent to else)
<xsl:choose> <xsl:when test=""> </xsl:when> <xsl:otherwise> </xsl:otherwise> </xsl:choose>
XPath Conditional
- Used inside XPath expression - like an attribute
- Need if(), then, else 0 could put else as empty
-
Can nest
<xsl:value-of select=" if ($temp le 0) then 'Solid' else if ($temp lt 100) then 'Liquid' else 'Gas' "/>
For-Each
- Iterate for all matching in select
- Cctive context is current iteration’s matched items
- Can pass parameters
-
Output is concatenated - may need variabalize to adjust
<xsl:for-each select="items/item"> </xsl:for-each>
-
Sort directly after for-each with
<xsl:sort select=“[XPath]" order=“descending"/>
For-Each-Group
- Output groups
- Sort will sort groups
- Could for-each items within to sort items
- Or sort prior to for-each-group
-
Many grouping methods
<xsl:for-each-group select="items/item" group-by="type"> </foreach>
Operators
- not()
- and, or
- gt, ge, lt, le
- ne for not equal
- '' for empty
Examples
<xsl:when test="$days ge xs:dayTimeDuration('P90D') ">
<xsl:if test="$class ne ''">
String length
Check for length
<xsl:if test="string-length($ou:color-theme) gt 0">
Ends with
Check if string ends with string
<xsl:template match="a[ends-with(@href, '.pdf' ) or ends-with(@href, '.doc' ) or ends-with(@href, '.docx' )]">
<!-- do something -->
</xsl:template>
First
Check if first of type
<!-- matches a tag with blockquote-last closing quote-->
<xsl:template match="blockquote/p[1]">
<p class="{@class}"><span class="blockquote-start">“</span><xsl:apply-templates /></p>
</xsl:template>
Last
Check if last of type
<!-- matches a tag with blockquote and adds a start quote to first paragraph-->
<xsl:template match="blockquote/p[last()]">
<p class="{@class}"><xsl:apply-templates /><span class="blockquote-mark">”</span></p>
</xsl:template>
Only
Check if no siblings
<!-- matches a tag with blockquote and adds a start quote to first paragraph-->
<xsl:template match="blockquote/p[not(preceding-sibling::p) and not(following-sibling::p)]">
<p class="{@class}"><span class="blockquote-start">“</span><xsl:apply-templates /><span class="blockquote-mark">”</span></p>
</xsl:template>
Variables
Create variable
<xsl:variable name="days">
value
</xsl:variable>
if global variable, put it at top. can’t be changed. if local variable, put inside tag. changes based on context.
Variable Types
XML is a strongly typed language, meaning that variables have specific types.
Define Variable Type with as
<xsl:variable name="days" as="xs:dayTimeDuration">
Test If Data is Within Data Type
<xsl:when test="date castable as xs:date">
Change Variable Type
Change variable type with select
<xsl:value-of select="xs:dayTimeDuration('P90D')"/>
Or Transform Data for Another Data Type
<xsl:value-of select="date cast as xs:date "/>
Calculate variable value
Global Variable
<xsl:variable name="today" select="current-date()"/>
Equation
<xsl:variable name="days" as="xs:dayTimeDuration">
<xsl:value-of select="$today - date"/>
</xsl:variable>
The variable days is today - date
Call variable with $
<xsl:value-of select="$today"/>
Loops
<xsl:for-each select="">
</xsl:for-each>
Navigating Directories
- Use ancestor:: to back out of a directory. Similar ../ in PHP.
- There are also rules for preceding, following, etc.