Dave Pawson. 4 Feb 2007. Version 3.0, Built under Saxon 8.8 to Rec, at www.dpawson.co.uk
Meant to be run
against itself: saxon document.xsl document.xsl
Note: All the
current syntax could change. Be aware of that. Its not yet a
recommendation. But it is getting very close!
The source XSLT is Here too
You can access the base URI for a node using the base-uri() function in XPath 2.0. So if you want to know the filename for
the input document, you can do:
<xsl:variable name="input-uri" select="base-uri(/)" /> which gives
file:/sgml/site2/2src/exampler2.xsl
and if you want to know the filename for the stylesheet, you can do:
<xsl:variable name="stylesheet-uri" select="base-uri(doc(''))" /> which gives
file:/sgml/site2/2src/exampler2.xsl
Error raised, <xsl:value-of select="error('Error found')"/> -- function used at end of program. The string Error found is output on the error stream.. As you see I haven't used it, since it terminates the program :-)
Note: This using Saxon, SAXON 8.8 from Saxonica
Version, 2.0
Vendor, SAXON 8.8 from Saxonica
Vendor-url, http://www.saxonica.com/
product-name, SAXON
product-version, 8.8
Schema aware, no
Supports serialization, yes
Supports backwards compatibility,
supports-serialization
supports-backwards-compatibility
Declared variables are
<xsl:variable name="date" select="current-date()"/> <xsl:variable name="dateTime" select="current-dateTime() "/>
Obtain values using <xsl:value-of select="(function)" : Functions are:
current-dateTime() - 2007-02-04T11:25:18.93Z
current-date() - 2007-02-04Z
current-time() - 11:25:18.93Z
implicit-timezone() - PT0S - GMT here as of Feb 05
day-from-date() 4 - which is the day of the month.
year-from-date() 2007
date from dateTime, xs:date(xs:dateTime($dateTime)) = Date from dateTime: 2007-02-04Z
month-from-date() 2
day-from-date($date) gives - 4
Basic date formatting: 4 february, 2007 from <xsl:value-of select=" format-date($date,'[D] [Mn], [Y]')"/>
timezone-from-dateTime($datetime) gives - [PT0S ]
Current time is : 11:25:18.93Z - Using <xsl:value-of select="$time"/>
hours-from-time() gives 11
minutes-from-time() gives 25
seconds-from-time() gives 18.93
timezone-from-time() [time zone ]gives PT0S
Subtract dates or $xs:date() - xs:date(): In this case, subtract 2002-09-01Z from 2007-02-04Z e.g. 1617 days - The duration since I stopped smoking, in days.
Note that it is now a simple subtraction, rather than a special function.
Except it doesn't produce nice months/days format.. The failure comes down to irregular months. As Mike Kay pointed out, a March date - a Jan date will vary dependent on leap years etc. The logic goes, convert each date to the units you want, then do the maths.
So its 4 years and 5 months Now using the function dp:subDates, (2007-02-04Z - 2002-09-01) 4 years 5 months Boy that's messy! See the source to find out how messy - but it can be done!
External Java call to get date: <xsl:value-of select="Date:toString(Date:new())" xmlns:Date="/java.util.Date" /> Sun Feb 04 11:25:19 GMT 2007 is no longer needed.
Basic date and time formatting. Uses a picture string. As shown below in the more common forms.
Y year M month in year D day in month d day in year F day of week W week in year w week in month H hour in day (24 hours) h hour in half-day (12 hours) P am/pm marker m minute in hour s second in minute f fractional seconds
So, for example, to format the date and time a format might be
<xsl:variable name="now" select="current-dateTime()"/> <xsl:value-of select="format-dateTime($now,'[Y0001][M][D01]T[H]:[m][z]','en',(),())"/> gives 20070204T11:25GMT
Similarly, for a time
<xsl:variable name="now" select="current-time()"/> <xsl:value-of select="format-time($now,'[H]:[m] [P]','en',(),())"/> gives 11:25 a.m.
Default Collation is: http://www.w3.org/2005/xpath-functions/collation/codepoint,
obtained by
<xsl:value-of select="default-collation()"/>. This seems to be
the Unicode codepoint default.
If you don't know what the collation is all about, then it's of no
interest to you.
<xsl:function name="dp:add"> <xsl:param name="val1" /> <xsl:param name="val2" /> <xsl:sequence select="$val1 + $val2" /> </xsl:function> (Thanks Jeni) Used by <xsl:value-of select="dp:add(3,4)"/>
Which gives, 7
And from the XSLT rec, a string reversal function
<xsl:function name="dp:reverse">
<xsl:param name="sentence" as="xs:string"/>
<xsl:sequence
select="if (contains($sentence, ' '))
then concat(str:reverse(substring-after($sentence, ' ')),
' ',
substring-before($sentence, ' '))
else $sentence"
as="xs:string"/>
</xsl:function>
which is called up as:
<xsl:value-of select="dp:reverse('DOG BITES MAN')"/>
A more constrained version of addition, using types:
<xsl:function name="dp:add2"> <xsl:param name="val1" as="xs:integer" /> <xsl:param name="val2" as="xs:integer" /> <xsl:sequence select="$val1 + $val2" as="xs:integer" /> </xsl:function>
Provides, using <xsl:value-of select="dp:add(1, 189)" Result is : 190 [Note: It should fail with 189.3 as second param.]
And the string reversal function gives, MAN BITES DOG
Tests if a node is of a particular data type. Testing the current node, the stylesheet element
Looking at <xsl:value-of select="name()"/>
<xsl:if test=". instance of node()">
Its an node
</xsl:if>
<xsl:if test=". instance of element()">
Its an element
</xsl:if>
<xsl:if test=". instance of attribute()">
Its an attribute
</xsl:if>
<xsl:if test=". instance of item()">
Its an item
</xsl:if>
<xsl:if test=" empty(.)">
Its empty
</xsl:if>
<xsl:if test=". instance of text()">
Its text
</xsl:if>
<xsl:if test=". instance of processing-instruction()">
Its a pi
</xsl:if>
<xsl:if test=". instance of comment()">
Its a comment
</xsl:if>
<xsl:if test=". instance of document-node()">
Its a document-node
</xsl:if>
<xsl:if test="$decVar instance of xs:decimal">
$decVar is xs:decimal
</xsl:if>
.... and don't ask me what the full list of types is:
Many wonder, and few seem to know.
Looking at element xsl:stylesheet
Its an node
Its an item
Its a document-node
$decVar is xs:decimal
<xsl:value-of select=" if ($intTest instance of xs:integer) then 'True' else 'False'"/> and with intTest defined as <xsl:variable name="intTest" select="5" as="xs:integer"/>
with $decVar defined as
<xsl:variable name="decVar"
select="3.14159" as="xs:decimal"/>
gives
False
Almost a temporary cast? Again Mike Kay's book offers a useful example. A stock level element may contain either an integer count of the items or a string indicating that the item is 'out of stock'. The example decrements the stock, but only if it has a numeric value. A zero or negative value indicates this out of stock count.
<xsl:variable name="stock1" select="3" as="xs:integer"/>
<xsl:variable name="stock2" select="'Out of Stock'" as="xs:string"/>
<xsl:value-of select="
if (data($stock1) instance of xs:integer) then
$stock1 -1
else
-2
"/>
and
<xsl:value-of select="
if (data($stock2) instance of xs:integer) then
$stock1 -1
else
-2
"/>
Testing $stock1 gives
2
and testing $stock2 gives
-2Yes, I agree it looks pretty meaningless doesn't it. However. As of CR, its pretty essential, especially if you want to use types. And want to generate, say, a nodeset.. sorry sequence, in a variable.
<xsl:variable name="a" select="(//h3)[position() < 3]" as="item()*"/>This creates a variable you can hack into using xpath quite readily. I.e. remember item()*.
From an explanatory email from Mike Kay, thanks Mike.
Examples:
<xsl:param name="x" as="item()"/>the parameter value can be any item (i.e. a node or atomic value). But it must be a single item.
<xsl:param name="x" as="item()?"/>the parameter can be a single item or an empty sequence
<xsl:param name="x" as="item()+"/>the parameter must be a sequence of one or more items - an empty sequence is not allowed
<xsl:param name="x" as="item()*"/>the parameter can be any sequence of zero or more items - this places no constraints on its value.
<xsl:param name="x" as="node()*"/>the parameter can be any sequence of zero or more nodes
<xsl:param name="x" as="xs:atomicValue*"/>the parameter can be any sequence of zero or more atomic values (e.g. integers, strings, or booleans).
item()* is the most general type possible, it matches everything, like "Object" in Java. For that reason, it can usually be omitted. But not always, for example the default type in xsl:variable is not item()* but document-node(), to ensure that
<xsl:variable name="rtf"> <a>thing</a> </xsl:variable>continues to behave like XSLT 1.0
Use these to specify parameters, variable types etc.
The long xpath expressions that David Carlisle and Jeni T come up with can now be commented.
For example:
<xsl:value-of select="//h3[@id='cmnts'] (:The h3 element with xml:id comnts, :)
/following-sibling::p[1] (:The first p following-sibling :)
/xsl:value-of/@select (:the value-of child, its select attribute :)
"/>
This expression gives -
//h3[@id='cmnts'] (:The h3 element with xml:id comnts, :) /following-sibling::p[1] (:The p
element child (:Oh yes it is:):) /xsl:value-of/@select (:the value-of child, its select attribute
:) which as you can see, includes the comments! Note they can be nested too!
Tests whether a given value is castable into a given target type. Returns boolean.
<xsl:variable name="decVar" select="3.14159" as="xs:decimal"/>
<xsl:variable name="intVar" select="3" as="xs:integer"/>
<xsl:if test="$intVar castable as xs:decimal">
Yes, we can convert an int to a decimal
</xsl:if>
<xsl:if test="$decVar castable as xs:float">
and we can change decimals to floats.
</xsl:if>
Then do it, using
<xsl:value-of select="$decVar cast as xs:float"/>
Yes, we can convert an int to a decimal
and we can change decimals to floats.
Then do it, using
3.14159<xsl:value-of select="namespace-uri-for-prefix('dp',document('')//dp:analyze-string)"/> This provides the reverse mapping from prefix (dp in this case) to a full namespace and gives - http://www.dpawson.co.uk/ns#
<xsl:value-of select="string-join(('Now', 'is', 'the', 'time', ' '), ")/>
The output is: Now is the time
Note the difference between this and
<xsl:value-of select="concat('Now', 'is', 'the', 'time')" />Which gives:Nowisthetime
Michael Kay put a clever example on the list: The requirement was to reverse a string. This
<xsl:variable name="in" select="'Now is the time'"/>
<xsl:value-of select=" string-join(
for $i in string-length($in) to 1 return substring($in, $i, 1),'')"/>
This gives: A function to reverse a sequence. Needed since the WG have removed the option to use 'for n to n-x', Hence, taking a sequence one to N, which we really want to process by decrement:
<xsl:variable name="in" select="'Now is the time'"/> <xsl:sequence select="reverse((1 to string-length($in)))"/> produces...15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
A boolean returning true if param 1 ends with param 2. Both parameters are strings. Complements the existing 'starts-with' functionality.
<xsl:value-of select='ends-with("goldenrod","rod")'/> returns "true"
Case changing:
<xsl:value-of
select="upper-case('The quick Brown fox Jumps over the lazy dog'
Gives: "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG "
Case changing:
<xsl:value-of
select="lower-case('The Quick Brown Fox Jumps Over The Lazy Dog'
Gives: "the quick brown fox jumps over the lazy dog "
Matches on the entire input, returns true or false.
Testing the matches function, with a string variable having value 'abd124' and using the regexp "[a..d|0..9]*" Hence
<xsl:value-of select="matches ($inputString,'[a..d|0..9]*')"/>
Returns true
replace() function. E.g. turn spaces into *** in "the quick brown
fox jumps over the lazy dog"
Uses:
<xsl:value-of select="replace($str, ' ', '***')" />
Gives - the***quick***brown***fox***jumps***over***the***lazy***dog
[So any occurrences of param2 in param1 are replaced with param3]
Tokenise!!! (One of my favourites). Do a for-each over the output of tokenize("The cat sat on the mat", "\s+") to process each item as if it were a child of the input element. Enables easy processing of regularly split text content
<xsl:for-each select='tokenize("The cat sat on the mat", "\s+")'>
<token>
<xsl:value-of select="."/>
</token>
</xsl:for-each>
<token>The</token>
<token>cat</token>
<token>sat</token>
<token>on</token>
<token>the</token>
<token>mat</token>
Note that
<quote>If a separator occurs at the start of the $input string,
the result sequence will start with a zero-length string.
</quote>,
and that the \s+ caters for a tab, space and newline character in the input string.
Normalisation: Input as
[The quick brown .... fox ]
Function used
<xsl:value-of select="normalize-space('The
quick
brown
fox
')"/>
Result: [The quick brown fox]
String comparison; With and without a character entity.
Definitions: <xsl:variable name="stSt" select="'The quick brown fox ....'"/> <xsl:variable name="entStr" select="'The quick brown fox ....'"/> <xsl:value-of select="compare($stSt, $entStr)"/>
Result is : 0
Interpret the results as below. -1 => less than 0 => Equal +1 => Greater than
Which indicates equality when the XSLT processor makes the comparison.
URI rsolution - absolute, or against a base URI.
<xsl:value-of select="resolve-uri('./test1.xsl')"/>
(Which is this file)
Gives: file:/sgml/site2/2src/test1.xsl Indicating the full path name of the file, in this case. The parameter is meant to be a relative uri.
The second form takes an absolute location as a second parameter, such as might be used for a server root.
<xsl:value-of select="resolve-uri('test1.xsl','file://c:/')"/>
This gives file://c/sgml/test1.xsl which resolves the relative URI against the base.
I'm told that the Java docs explains it. Baffles me.
E.g, using the dp:months element, create a QName dp:newdoc,
<xsl:value-of select="resolve-QName('dp:newdoc',//dp:months)"/> gives dp:newdoc
Then check if it is a qname... using
<xsl:if test="resolve-QName('dp:newdoc',//dp:months) instance of xs:QName">
Its a QName!!
</xsl:if>
Its a QName!!
Used to find the root node of the current document, with respect to the current-node(),
<xsl:value-of select="name(root(.)/*[1])gives: xsl:stylesheet
Sequence based. Remember that we've lost the node-list idea, life's full of sequences now. Load variable with a number of items. E.g.
<xsl:variable name="seq" select="$root//h3"/>pick the 5th item.
<xsl:variable name="oneChild" select="$seq[5]"/> Test it using <xsl:value-of select="index-of($seq,$oneChild)" />
The result is
5
or, using a simple text example,
<xsl:value-of select="index-of(('a','b','c','d','e','f' ),'c')"/>
gives a result of 3Empty tests: Input document reads
<dp:mttests> <dp:t1/> <dp:t2><el/> </dp:t2> <dp:t3>xxx </dp:t3> </dp:mttests> Tested with t1 shows as: <xsl:value-of select="empty($root//dp:mttests/t1/*)"/> t2 shows as: <xsl:value-of select="empty($root//dp:mttests/t2/*)"/> t3 shows as: <xsl:value-of select="empty($root//dp:mttests/t3/*)"/>Each is tested using the empty() function.
Using <xsl:if test="exists($root//dp:mttests/t1)"> mttests/t1 is a non-empty sequence </xsl:if> <xsl:if test="exists($root//dp:mttests/t2)"> mttests/t2 is a non-empty sequence </xsl:if> <xsl:if test="not(exists($root//dp:mttests/t3/el)"> mttests/t3 is an empty sequence </xsl:if>
Gives:
dp:mttests/t1 is a non-empty sequence
dp:mttests/t2 is a non-empty sequence
dp:mttests/t3 is an empty sequence
Note the inversion on the last test.
We can now test for distinct nodes and values. Note the difference!
1. Define a variable with one duplicate item - the 3.
<xsl:variable name="ns1" select="(1,2,3,4,5,3)"/>2. Count its contents
<xsl:value-of select="count($ns1)"/>Gives 6
<xsl:value-of select="count(distinct-values($ns1))"/>gives: 5
Then picking out the two distinct values with
<xsl:for-each select="distinct-values(//dp:distinct/dp:val1)">gives: Single value
New attribute to the copy function. Copy without copying
namespace. Adds the attribute copy-namespaces="no".
View source
should show a <para> element without namespaces, then the same with
a normal copy-of
[
[
The two forms are:
1. <xsl:copy-of select="document('')//d:doc/revhistory/purpose/para"
copy-namespaces = "no"/>
2. <Test Harness for XSLT and xpath 2 constructs the output is shown below. Here's one I prepared earlier, I think is the phrase.
<para>Test Harness for XSLT and xpath 2 constructs</para> and <para xmlns:d="rnib.org.uk/tbs#" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2002/11/xquery-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema-datatypes" xmlns:op="http://www.w3.org/2002/11/xquery-operators" xmlns:dp="http://www.dpawson.co.uk/namespace" xmlns:pref="http://www.dpawson.co.uk" xmlns:saxon="http://saxon.sf.net/">Test Harness for XSLT and xpath 2 constructs</para>] (has ns)
Now you can see why this functionality is helpful.
Obtain the qualified name for a node. E.g.
[<xsl:value-of select="node-name(//xsl:template[1])"/>] whose namespace is <xsl:value-of select="namespace-uri(//xsl:template[1])"/> [xsl:template] whose namespace is http://www.w3.org/1999/XSL/Transform
Returns the namespace URI of the node passed as a parameter, or the current node. Example immediately above.
Creates one of these new sequences!
E.g. <xsl:sequence select="(1,2,3,4)" />This shows it using a simple sequence of numbers.
<xsl:value-of select="(1,2,3,4)" separator=", "/>works just the same, but with the separator,
Another oddity is the need to use double brackets (( when specifying a sequence, as in
1. <xsl:value-of select="min((1,2,3,4))"/> 2. <xsl:value-of select="min(1,2,3,4)"/>
If the second form is used an error is reported. See this for an explanation.
Another use of this is to create a sequence containing duplicates.
<xsl:sequence select="(//h3, //h3[5])"/>Should you ever need it.
Another rather subtle use of sequences. The min() function takes a single argument, which is a sequence. So to find the minumum of two values X and Y, write
min((X, Y))Note the double parentheses: the outer ones say that this is a list of arguments, the inner ones say that (X, Y) is a sequence containing X and Y.
It's not technically a constructor, the "," is a list concatenation operator: A,B is the concatenation of two lists. The inner parens in min((A, B)) are needed because a function argument must be an ExprSingle rather than an Expr. An ExprSingle is effectively an expression that does not contain a top-level comma. A top-level comma is not allowed here for obvious reasons - it would be taken as the separator between arguments in the function call.
Note that string-length() first parameter needs to be a single expression, ExprSingle!
An additional attribute, case-order, can take values of upper-first or lower-first, which helps with sorting.
Sorting: descending order, upper case first.
Given:
<xsl:variable name="rString"
select="('AB','aA','c', 'dEF', 'ghij','GHj' ,'KLmno','PqrSt','uvwxy','z',
'123', '456789')"/>
and
<xsl:for-each select="$rString">
<xsl:sort select="." case-order ="upper-first" order="descending" />
<xsl:value-of select="." /> <xsl:text> </xsl:text>
</xsl:for-each>
gives:
z uvwxy PqrSt KLmno GHj ghij dEF c AB aA 456789 123
Note in the above that the sort order has placed upper case first and changed the order to descending. A very useful addition.
Trying to figure out the difference between xsl:perform-sort and xsl:sort. It seems to wrap xsl:sort, so I've no idea of its functionality.
Mike Kay tells me: quote. xsl:sort specifies how for-each and apply-templates should do their work. xsl:perform-sort is an instruction that does sorting and nothing else; it's useful for example if you want to sort a sequence of elements before doing a grouping operation. It's really just a shorthand for
<xsl:for-each select="xxx"> <xsl:sort select="yyy"/> <xsl:sequence select="."/> </xsl:for-each>
analyze-string function. With a string, whose original content was "
There is some need to view this differently.I Want to change the word need into <par>. This is done using
<xsl:value-of select="document('')//dp:analyze-string"/>." Change need into <par>
<xsl:analyze-string select="document('')//dp:analyze-string"
regex="need">
<xsl:non-matching-substring><xsl:text/>
(<xsl:value-of select="."/>)<xsl:text/>
</xsl:non-matching-substring>
<xsl:matching-substring>
<span style="color:blue"><xsl:text><par> </xsl:text> </span>
</xsl:matching-substring>
</xsl:analyze-string>
Result is :
(There is some )<par>
( to view this differently.)
Note the two child elements, matching and non-matching. I've added brackets round the 'non-matching' parts of the input, so you can see the output.
With a more complex String, the regex-group(n) can be used to pick out individual sub-groups.
Jeni Tennison gave a slightly more educational example on the list which I changed to show all the matches. It brings out a few points.
<xsl:variable name="regex" select="'(([^_]*)_PARA)'"/>
<xsl:variable name="input" select="'ABC_PARA__PARA'"/>
<xsl:variable name="res" as="item()*">
<xsl:analyze-string select="$input" regex="{$regex}">
<xsl:matching-substring>
<xsl:for-each select="for $i in (1 to 10) return $i">
<xsl:if test="not(string-length(regex-group(.)) = 0)">
<match><xsl:value-of select="regex-group(.)"/> </match>
</xsl:if>
</xsl:for-each>
</xsl:matching-substring>
<xsl:non-matching-substring>
<mismatch><xsl:value-of select="."/></mismatch>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:copy-of select="$res"/>
The output is:
<match>ABC_PARA</match>
<match>ABC</match>
<mismatch>_</mismatch>
<match>_PARA</match>
From this, its possible to see just what is filtered within each group.
There are a total of 3 matches, sequenced as two matches, a mismatch and the last match. Note the sequence? It is ordered, and will be different if you write it directly to the output. Note also that the regex attribute is an Attribute Value Template. I presume less than 11 matches, and test each for a zero string length of the regexp-group() function. This provides a simple structure which can then be accessed programmatically to obtain the nth match, or the nth mismatch, should that be what you want.
With a defined function
<xsl:function name="dp:reverse">
<xsl:param name="sentence" as="xs:string"/>
<xsl:sequence
select="if (contains($sentence, ' '))
then concat(dp:reverse(substring-after($sentence, ' ')),
' ',
substring-before($sentence, ' '))
else $sentence"
as="xs:string"/>
</xsl:function>
An If then else conditional, within an attribute.
Called by
<xsl:value-of select="dp:reverse('DOG BITES MAN')"/>
which (from the rec) reverses the words in a sentence. Getting it wrong, Wrong data type that is. (who needs that!). Firstly the correct use. Again from the rec, a function,
<xsl:value-of select="dp:roman(34)"/>which should turn the 34 into XXXIV.
<xsl:value-of select="dp:roman('34')"/>
Result is .... well it would be, the stylesheet crashes, or more exactly, the program terminates.
Saxon 8.8 gives an error of
Required type is xs:integer; supplied value has type xs:string Transformation failed: Failed to compile stylesheet. 1 error detected.
The instruction
<xx:head> <xsl:namespace name='xx'>http://www.dpawson.co.uk/namespace#</xsl:namespace> </xx:head>adds a namespace definition to the nearest ancestor element, so in the example, the head element would be in the xx namespace. I don't think stylesheet authors are that lazy? Do you?
Given a string such as
<xsl:variable name="qStr" select='"Some ""Content"" with quotes"'/>
^^ ^^
(Note the quote doubling)
using the translate function,
<xsl:value-of select="translate($qStr, '"', ''' ')"/>which is a (single => double) gives
Formalising the various processor extensions, this provides the way to generate more than one output document.
No idea what the schema related ... attributes are all about. The basics are shown below.
<xsl:result-document
format = qname
href = { uri-reference }>
<!-- Content: sequence-constructor -->
</xsl:result-document>
The example here writes to res-doc.html all the titles from this document and links them back into this document. Just to see what happens, I've added an additional instruction into the output. After writing the <head> element, I've added the
<xsl:namespace name='xx'>http://www.dpawson.co.uk/namespace#</xsl:namespace>instruction. If you look at the output file (res-doc.html), you'll see this adds the namespace declaration into the element! The code is:
<xsl:result-document
href="res-doc.html"
>
<html>
<head>
<xsl:namespace name='xx'>http://www.dpawson.co.uk/namespace#</xsl:namespace>
<title>Test output.</title>
</head>
<body>
<ul>
<xsl:for-each select="$root//h3">
<li> <a href="exampler.html#{@id}"> <xsl:value-of select="."/></a></li>
</xsl:for-each>
</ul>
</body>
</html>
</xsl:result-document>
See also an indirect use of this
Just checking. The time now is 11:25:18.93Z according to <xsl:value-of select="current-time()"/>. This should be the same time as shown at -here, since xpath says If invoked multiple times during the execution of a query or transformation, these functions always returns a consistent result
See the W3C definitions.
Addresses the need to replace 'special' characters in such as a URL. Escapes a URI in the manner html user agents handle attribute values that expect URIs.
Given a string such as
http://www.example.com/~bébé
Using
<xsl:value-of
select='escape-html-uri("http://www.example.com/~bébé",true())'/>
gives http://www.example.com/~b%C3%83%C2%A9b%C3%83%C2%A9Since this requires input, I've created a separate file for this. See this separate xslt file and this xml file . Certainly answers most of the needs of xslt users. Run the stylesheet against the xml source file and have a look at the output.
This uses the predicate
'every $x in ....'to check that each h3 element has an id value which is not empty. The syntax is:
<xsl:if test="not(every $id in (//h3) satisfies (not(empty($id/@id)) and $id/@id )) "
Of course, to show it up, there is one without the requisite id value.
Warning, h3 element found with id attribute missing/empty
Content is: This heading is included to produce an error.
Those nice simple days of = and != are over. The list now is quite long. The list is:
eq equality ne not equal lt less than le less than or equal gt greater than ge grater than or equalNotable points are:
Examples?
<xsl:if test="p eq ../'The cat sat on the mat'">
Normal string equality... except that if there is more than one p element, its an error. Now you see what they mean about comparing single values.
Node comparisons occur in the same way, except they apparently need a separate verb,
<xsl:if test="a is b">is and isnot are the two verbs in question. The two nodes must be the same for is to return true, different for isnot to return true. The rec gives an example of the same node accessed via two different means, e.g.
<xsl:if test=" //*[@id = 'nodeA'] is title[1]">
Returns true if the only node with the id attribute equal to
string value nodeA is also first child named title.
General syntax is
some variable in //xpath satisfies (boolean expression using $variable)Returns true if at least one selection satisfies the criterion. E.g. to search this document to make sure that at least one h3 links out to the xpath Working Draft, http://www.w3.org/TR/xpath20. The expression is:
<xsl:if test="$x in //h3/a/@href satisfies (contains($x,'http://www.w3.org/TR/xpath20'))"> Yes, at least one h3 satisfies the condition
This gives Yes, at least one h3 satisfies the condition
Range expressions could be useful here too. E.g.
<xsl:if test="some $x in (10, 1 to 4) satisfies ($x = 3)"> Shows up as true, i.e. the range value contains 3. </xsl:if>
The result is
Shows up as true, i.e. the range value contains 3.
Can also be used in conjunction with the predicates to act as a filter
<xsl:value-of select="(1 to 30)[. mod 5 eq 0]" separator=", "/>gives: 5, 10, 15, 20, 25, 30
The Union, Intercept and except operators work to combine sets.
<xsl:variable name="a" select="(//h3)[position() < 3]"/> <xsl:variable name="b" select="(//h3)[(position() > 2) and (position() < 5)]"/> <xsl:variable name="c" select="(//h3)[(position() > 3) and (position() < 6)]"/> <xsl:variable name="all" select="//h3"/>
Note the variable definition. I was corrected by Mike.
<xsl:variable name="a" select="(//h3)[position() < 3]"/>provides the document position of the first two h3 elements. Note the (//h3). There can be lots of h3 elements in the document all satisfying //h3[1].
exploring $c to find its position within the document using
<xsl:for-each select="$c"> Value is: <xsl:value-of select="index-of($all, .)"/>... <xsl:value-of select="text()[1]"/> </xsl:for-each>Value is: 4 ... date and time
Using the union function, as in
There are <xsl:value-of select="count($a union $b)"/><br /> <xsl:for-each select="($a union $a)"> Number <xsl:value-of select="index-of($all, .)"/>is <xsl:value-of select="text()[1]"/> in the Union. </xsl:for-each>
Intersection of b and c using:
There are <xsl:value-of select="count($b intersect $c)"/> in the intersection<br /> <xsl:for-each select="($b intersect $c)"> Number <xsl:value-of select="index-of($all, .)"/> is <xsl:value-of select="text()[1]"/> <br /> </xsl:for-each>There are 1 in the intersection
$b except $c, as in
There are <xsl:value-of select="count($b except $c)"/><br /> <xsl:for-each select="($b except $c)"> Number <xsl:value-of select="index-of($all, .)"/> is <xsl:value-of select="text()[1]"/> <br /> </xsl:for-each>There are 1
Given some sequence, insert (or remove )an item at a given position.
<xsl:value-of select="insert-before($target-seq, $position, $what-to-insert)"/>
E.g. given a sequence
<xsl:variable name="target-seq" select="(1,2,3,4,7)"/> <xsl:variable name="what-to-insert" select="(5,6)"/> <xsl:variable name="position" select="4"/>
Inserting at position 4,
<xsl:variable name="result" select="insert-before($target-seq, $position, $what-to-insert)"/>gives
Similarly, removing the 5th element
<xsl:variable name="result1" select="remove($target-seq, 5)"/>gives (1, 2, 3, 4)
<xsl:variable name='ss' select = subsequence( $target-seq $position $length)"/>which returns the sub-sequence starting at $position and extending to $position+$length.
<xsl:variable name="result2" select="subsequence($target-seq, 3, 2)"/> (<xsl:value-of select="$result2" separator=", "/>)
Given a numeric variable:
<xsl:variable name="n1" select="(3,4,6,8,-1)"/>
We can play with them just a little more:
E.g. The number of items in a sequence, count($n1) gives 5
The average, avg($n1) gives 4
The max($n1) gives gives 8
The min($n1) gives -1
The sum($n1) gives 20
The absolute value of the last item gives 1
There is some type information there, but it works well without it.
idref(String) provides a source node for the value passed in as a parameter. E.g. this heading is linked to from the end of the document. So, using this function, I can return the content of the element linking to it, i.e.
[<xsl:value-of select="idref('lnk')/../text()"/>]
The element having idref 'lnk' contains [A link for testing the idref function]
The for loop is provided in xpath, with syntax as shown below:
for $x in (expression) return (expression)
Using
<xsl:for-each select="for $i in ((//h3)[position() < 3]) return ($i/text()[1])"> Value: <xsl:value-of select="."/> </xsl:for-each>gives Value: base-uri()
A better example might be to use the for statement to generate a sequence, then to operate on that sequence using a function which takes that as its input. For example; I wanted to find the max number of columns in a table, so in the template for the column, I had
max(for $i in ../../row return count($i/*))
this for statement returned a sequence, and the max function provides the maximum number in that sequence.
Note: Unlike pretty much every other xpath or xslt construct, for does not change the current node so ".", relative path expressions, etc all do not work as you might expect: they all stay relative to whatever was the context outside the for. DC Apr 03.
Creating a variable of specific types, using the 'as' attribute.
E.g.
<xsl:variable name="ls" as="item()*"
<xsl:sequence select="(2,5,6,7,8)" />
</xsl:variable>
<xsl:variable name="ls1" select="for $i in (//h3[position() < 6]) return string-length($i) "/>returns 14, 13, 23, 19, 38
<xsl:variable name='today' select='xs:date("2003-06-25")'/>
Then used,
<xsl:value-of select="$today" />which gives Today is 2003-06-25
String content can now contain an easier mix of double (")
and single quote (’) characters. (Note they are spread out here
for clarity.) the principle is to 'escape' a quote with another
one.
E.g.
<xsl:value-of select=' "He said, " " Go! " " " '/>gives: He said, "Go!"
These are 'variables' which take the form of a
sequence of items, and are created using the for, some and every
syntax already mentioned above.
The general form is 'some $variable in Expression satisfies
SingleExpression' E.g.
1 <xsl:variable name="rv1" select="(1,3,6,6 to 16)"/> or 2 <xsl:variable name='rv2' select="some $i in (//h3) satisfies (string-length($i) < 12)"/> or 3 <xsl:variable name='rv3' select="every $i in (//h3) satisfies (string-length($i) > 5) "/>Gives:
The range variables can also be used as predicates.
<xsl:for-each select="(//h3)[@id][some $x in position() satisfies ($x lt 3)]"> <xsl:value-of select="@id"/> | </xsl:for-each>
This gives: buri | err | which is the id value of the first two h3 elements.
And finally, a use to retrieve a fixed length string. This could replace the pad function often requested, if wrapped in a function call
<xsl:variable name="param" select="5"/> <xsl:value-of select="string-join(for $i in 1 to $param return 'X','')"/> gives XXXXX
I.e. It returns a $param length string.
View source for this xsl:copy, with namespace
1. Using
<xsl:for-each select="document('')//dp:distinct">
<xsl:copy >
<xsl:copy-of select="*|@*" copy-namespaces = "yes" />
</xsl:copy>
</xsl:for-each>
and
2.
<xsl:for-each select="document('')//dp:distinct">
<xsl:copy >
<xsl:copy-of select="*|@*" copy-namespaces = "no" />
</xsl:copy>
</xsl:for-each>
This gives Resulting content - which won't show correctly in a browser.
<dp:distinct xmlns:dp="http://www.dpawson.co.uk/namespace"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:pref="http://www.dpawson.co.uk"
xmlns:saxon="http://saxon.sf.net/"
xmlns:d="rnib.org.uk/tbs#">
<dp:val1>Single value</dp:val1>
<dp:val1>Single value</dp:val1>
<dp:val1>Single value with a difference.</dp:val1>
</dp:distinct>
And for the second one
<dp:distinct xmlns:dp="http://www.dpawson.co.uk/namespace"> <dp:val1>Single value</dp:val1> <dp:val1>Single value</dp:val1> <dp:val1>Single value with a difference.</dp:val1> </dp:distinct>
Which is quite a difference? The only ns in the second one I'd agree is required.
View source for actual output
The second is restricted to the dp namespace, the first has all ns included. Seems about right.
Converts the input Unicode code points to a string. DC gave me the example of using it to reverse a string!
<xsl:variable name="in" select="'Now is the time'"/> <xsl:value-of select="codepoints-to-string(reverse(string-to-codepoints($in)))"/> Gives:emit eht si woN
A simpler case. Given the sequence (41,42,x5E,30), the function converts this to AB^0
Provides the reciprocal conversion, converting a string to a sequence of codepoints
<xsl:value-of select="string-to-codepoints('Now is the time')"/>
78 111 119 32 105 115 32 116 104 101 32 116 105 109 101This function applies the URI escaping rules defined in section 2 of RFC 3986 to the string supplied
E.g. encode-for-uri ("http://www.example.com/00/Weather/CA/Los%20Angeles#ocean") gives
http%3A%2F%2Fwww.example.com%2F00%2FWeather%2FCA%2FLos%2520Angeles%23ocean
See also W3C, which has the variant function 'iri-to-uri()'
Compares two strings according to the Unicode codepoint collation. Usage
<xsl:if test="codepoint-equal($s1,$s2)">
Using the definition
<!ENTITY thisFile SYSTEM "ndata.txt" NDATA txt>and a use of
<xsl:value-of select="unparsed-entity-uri('thisFile')"/>
Returned value is [file:/sgml/site2/2src/ndata.txt ]... which is the value of the entity, not its content.
Definition,
<xsl:value-of select="unparsed-text($strng,$encoding)"/>treats the first parameter as a sequence of uri's. The second is the encoding. Using it
<xsl:value-of
select="unparsed-text('ndata.txt','utf-8')"/>
with a minimal file which contains some content,
returns the contents of that file: This is a plain text file, no markup, but containing & and < , which you should see escaped.
Hence this is the one to use when you want to include plain text or similar. NDATA implies that the parser informs the processing system that it has encountered an NDATA entity reference and gives it the relevant information. It is up to the processing system to decide what to do with it. It is not parsed as XML
As above, this uses the xsl:result-document, but here its referenced from the xsl:output element, using the format attribute to reference the xsl:result-document.
As in <xsl:result-document
href="newfile.xml"
format="newFormat"
validation="preserve">
(then, as a top level element)
<xsl:output name="newFormat" method="xml" indent="yes"/> See file newfile.xml for output. Note that the format is specified in an output statement, which now has a name
A list of functions, tested for availability. Check by running the stylesheet. This run on 04 Feb 2007. (Note: There are 111 functions in total
<-- Note that this is a top level element -->
<xsl:character-map name="html">
<xsl:output-character character=" "
string="&nbsp;" />
<!-- tabs as four spaces -->
<xsl:output-character character="	" string=" " />
</xsl:character-map>
Another oft-requested feature. Translate character x into string
Y, or even entity Z.
In the example: Non breaking space to the entity
and a tab character into 4 spaces.
When the <xsl:output element is used, the attribute
character-map="name" is used to reference the mappings.
<xsl:output method="xhtml" use-character-map="html" indent="yes"/>would invoke the above character map. We can now use this to get our beloved entities into the output file without using DOE. Implemented in Saxon 8.8, but read the comments.
Using base-uri to obtain the stylesheet name as in
<xsl:value-of select="base-uri(document(''))"/>
Gives file:/sgml/site2/2src/exampler2.xsl
and for source document use
<xsl:value-of select="base-uri(.)"/>gives file:/sgml/site2/2src/exampler2.xsl, which happens to be the same in this case. Without a context, the '.' in this case, the function appears to return the stylesheet value.
That's it... for version 3.0, Built under Saxon 8.8 to Rec.
WARNING: You'll screw this up if you set xml:base prior to using this stylsheet.
Ever used an expression like
value-of select="vis:Visiodocument/vis:Pages/vis:Page[0]/vis:shapes/vis:shape"Where it appears to get longer and longer the more you type? Help is at hand. Its now possible to abbreviate this, using
<xsl:template match="Visiodocument/Pages/Page[0]/shapes/shape" xpath-default-namespace="vis"> etc.For example to access a stylesheet template, use
<xsl:template match="template"
default-xpath-namespace="http://www.w3.org/1999/XSL/Transform">
[<xsl:value-of select="value-of/@select"
default-xpath-namespace="http://www.w3.org/1999/XSL/Transform"/>]
</xsl:template>
Note that the select attribute does not have to be
reverse-engineered, since this default xpath namespace does not apply
to attributes... even if its not clear in the specification.
Also see Constructor Functions for XML Schema Built-in Types , a page or so down from the link target, which is a list of types. Unsure if its complete.
It would appear almost impossible, despite the WG's assurances, to avoid using data types and still gain the benefit of the good features of XSLT 2.0. This section shows how to convert from one to another.
See also W3C Schema, part 2, for the data type list.
I've declared a number of variables, mostly as strings, which contain other types, for example "6.0" as a string, which ensures it can be converted to a decimal. The variable list is:
<xsl:variable name="elAS" as="element()">
<p><em>Elements</em></p>
</xsl:variable>
Gives Elements
<xsl:variable name="decAS" select="'3.1519'" as="xs:string"/>
<xsl:variable name="intAS" select="'2'" as="xs:string"/>
<xsl:variable name="boolAS" select="'true'" as="xs:string"/>
<xsl:variable name="bool2AS" select="'1'" as="xs:string"/>
<xsl:variable name="dateAS" select="'2003-06-16'" as="xs:string"/>
<xsl:variable name="ymdAS" select="'P1Y2M3'" as="xs:string"/>
<xsl:variable name="dtdurAS" select="'P3DT10H30M'" as="xs:string"/>
<xsl:variable name="datetimeAS" select="'2003-06-19T08:18:05.2810Z'" as="xs:string"/>
<xsl:variable name="uriAS" select="'http://www.dpawson.co.uk/xsl'" as="xs:string"/>
<xsl:variable name="timeAS" select="'13:29:26.109Z'" as="xs:string"/>
<xsl:variable name="xAS" select="' '" as="xs:string"/>
<xsl:variable name="durAS" select="'P1Y2M3DT10H30M'" as="xs:string"/>
<xsl:variable name="qnameAS" select="'dp:element'" as="xs:string"/>
<xsl:variable name="base64AS" select="'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCg=='" as="xs:string"/>
<xsl:variable name="strAS" select="' A string with lots
of white space in it '" as="xs:string"/>
<xsl:variable name="langAS" select="'en-UK'" as="xs:string"/>
<xsl:variable name="nameAS" select="'ident'" as="xs:string"/>
<xsl:variable name="itemAS" select="('ident', 'item', 'list3')" as="item()*"/>
<xsl:variable name="attset" as="attribute()+">
<xsl:attribute name="x">attval1</xsl:attribute>
<xsl:attribute name="y">attval2</xsl:attribute>
<xsl:attribute name="z">attval3</xsl:attribute>
</xsl:variable>
Using the following value-of statements....
String => decimal - <xsl:value-of select="xs:decimal($decAS)"/> String => float - <xsl:value-of select="xs:float($decAS)"/> String => double - <xsl:value-of select="xs:double($decAS)"/> String => integer - <xsl:value-of select="xs:integer($intAS)"/> String => duration - <xsl:value-of select="xs:duration($durAS)"/> String => yearMonthDuration - <xsl:value-of select="xs:yearMonthDuration($ymdAS)"/> (note xdt ns) String => dayTimeDuration - <xsl:value-of select="xs:dayTimeDuration($dtdurAS)"/> (note xdt ns) String => dateTime - <xsl:value-of select="xs:dateTime($datetimeAS)"/> String => date - <xsl:value-of select="xs:date($dateAS)"/> String => time - <xsl:value-of select="xs:time($timeAS)"/> String => anyURI - <xsl:value-of select="xs:anyURI($uriAS)"/> String => qname - <xsl:value-of select="xs:QName($qnameAS)"/> String => untyped () - <xsl:value-of select="xs:untypedAtomic($qnameAS)"/> String => base64Binary - <xsl:value-of select="xs:base64Binary($base64AS)"/> String => boolean - <xsl:value-of select="xs:boolean($boolAS)"/>(input is true) String => boolean - <xsl:value-of select="xs:boolean($bool2AS)"/> (input is string 1) attribute =>String - [<xsl:value-of select="$attset[1]"/>] item()*=> String - <xsl:for-each select="$itemAS"> Item <xsl:value-of select="position()"/> [<xsl:value-of select="."/>] </xsl:for-each> Noting that item()* is the new lowest common denominator.
This gives:
String => decimal - 3.1519
String => float - 3.1519
String => double - 3.1519
String => integer - 2
String => duration - P1Y2M3DT10H30M
String => yearMonthDuration - P1Y2M (note xdt ns)
String => dayTimeDuration - P3DT10H30M (note xdt ns)
String => dateTime - 2003-06-19T08:18:05.281Z
String => date - 2003-06-16
String => time - 13:29:26.109Z
String => anyURI - http://www.dpawson.co.uk/xsl
String => untyped () - dp:element
String => base64Binary - <xsl:value-of select="xs:base64Binary($base64AS)"/> Not in 8.8
String => boolean - true(input is true)
String => boolean - true (input is string 1)
attribute =>String - [attval1, attval2, attval3]
item()*=> String -
Item 1 [ident]
Item 2 [item]
Item 3 [list3]
Noting that item()* is the new lowest common denominator.
One of the additions that I find useful is the ability to test if some piece of source document content is one from a,b,c,d. For instance, in 1.0 to test if an attribute value is a or b or c or d its necessary to write
<xsl:if test="@attVal = 'a' or @attval='b' ...... ">
With XSLT 2.0 its possible to write
<xsl:if test="@attVal = ('a','b','c','d')">
Note that this is usable in any test scenario, e.g. xsl:if, xsl:when etc.
This is novel.. for me. If you need a specific sort sequence, then this is helpful. As an example, I'm using a suite of input data as follows:
<?xml version="1.0" encoding="utf-8" ?> <doc> <word>Hello</word> <word>hello</word> <word>[hello]</word> <word>Mword</word> <word>Nword</word> <word>Mother</word> <word>Nato</word> <word>:Mother</word> <word>5Mother</word> <word>ünter</word> <word>unter</word> <word>!unter</word> <word>$unter</word> </doc>Of note is the Mword Nword pair. I want to sort these in reverse order, N before M. The xslt is shown below.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema-datatypes"
xmlns:my="http://www.dpawson.co.uk/mylang"
xmlns:saxon="http://saxon.sf.net/"
version="2.0">
Note the saxon namespace.
<saxon:collation
class="mylang"
name="mylang"
default="yes"/>
Note the name attribute, referred to later, and the class attribute... a java collator; see below.
The specification states that N comes before M
Then the actual sort:
<xsl:template match="/">
<html>
<head>
<title>Collation</title>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8" />
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="doc">
<p>
Before: <br />
<xsl:for-each select="word">
<xsl:value-of select="."/> <br />
</xsl:for-each>
</p>
<hr />
<p>
<b>After</b> <br />
<xsl:for-each select="word">
<xsl:sort select="."
data-type="text"
lang="mylang"
collation="mylang"/>
<xsl:value-of select="."/> <br />
</xsl:for-each>
</p>
</xsl:template>
The sort element refers back to the named collation, mylang.
Finally the java that does the sort.
import java.text.Collator;
import java.text.RuleBasedCollator;
import java.text.ParseException;
import java.lang.StringBuffer;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.Serializable;
import net.sf.saxon.xpath.XPathException;
import net.sf.saxon.om.Item;
import java.io.File;
/**
*Class mylang. Implements java.util.Comparator as a collator
*
*
*
*
***/
public class mylang implements java.util.Comparator,Serializable {
public String ruleFileName="collator.txt";
/**
* Constructor
* Inits the rules from the given rules file.
*
**/
public mylang (){
String rules = getRules(ruleFileName);
}
/**
* Compare two objects.
* o1 < o2 return -1
* o1== o2 return 0
* o1 > o2 return 1
* @param o1 first object
* @param o2 second object
* @return int: -1 less than, 0 equal, +1 gt
***/
public int compare (Object o1, Object o2) {
int res= 0;
String s1 ="";
String s2 = "";
try {
s1 = (o1 instanceof String ? (String)o1 : ((Item)o1).getStringValue());
} catch (XPathException err) {
throw new ClassCastException("Cannot convert sort key from " + o1.getClass() + " to a string");
}
try {
s2 = (o2 instanceof String ? (String)o2 : ((Item)o2).getStringValue());
} catch (XPathException err) {
throw new ClassCastException("Cannot convert sort key from " + o2.getClass() + " to a string");
}
RuleBasedCollator collator = null;
String rule = getRules(ruleFileName);
try {
collator = new RuleBasedCollator(rule);}
catch (ParseException err) {
System.out.println("collator error: " +err.getMessage() + "Quitting" );
System.exit(2);
}
return collator.compare(s1,s2);
}
/**
* Compares another collator with this one.
* @param o1, the collator to be compared with this one.
* @return boolean, always false. Not implemented.
*
**/
public boolean equals (Object o1) {
return false;
}
/**
*Read a set of rules into a String
*@param filename name of the file containing the rules
*@return String, the rules
*
**/
private String getRules(String filename) {
String res="";
try{
BufferedReader reader =
new BufferedReader (new FileReader (filename));
StringBuffer buf=new StringBuffer();
String text;
try {
while ((text=reader.readLine()) != null)
buf.append(text + "\n");
reader.close();
}catch (java.io.IOException e) {
System.err.println("Unable to read from rules file "+ filename);
System.exit(2);
}
res=buf.toString();
}catch (java.io.FileNotFoundException e) {
System.out.println("Unable to read Rules file, quitting");
System.exit(2);
}
return res;
}// end of getRules()
/**
*sort an array of strings according to the collator .
*@param collator collator to use
*@param words - words to sort
*@return null.
**/
public static void sortStrings(Collator collator, String[] words) {
String tmp;
for (int i = 0; i < words.length; i++) {
for (int j = i + 1; j < words.length; j++) {
// Compare elements of the words array
if( collator.compare(words[i], words[j] ) > 0 ) {
// Swap words[i] and words[j]
tmp = words[i];
words[i] = words[j];
words[j] = tmp;
}
}
}
}
/**
*print an array of strings.
*src: http://java.sun.com/docs/books/tutorial/i18n/text/rule.html
*
*
**/
public static void printStrings(String [] words) {
for (int i = 0; i < words.length; i++) {
System.out.println(words[i]);
}
}
/**
*Default entry point for command line testing.
*
*
*
*
**/
public static void main (String [] argv) {
RuleBasedCollator collator = null;
mylang mc = new mylang();
String rule = mc.getRules(mc.ruleFileName);
System.err.println(rule);
try {
collator = new RuleBasedCollator(rule);}
catch (ParseException err) {
System.out.println("collator error: " +err.getMessage() );
}
String [] words = {
"Hello",
"hello",
".hello",
" hello",
"&hello",
"Back",
"back",
"Mother","Not", "4hello",
"!Hi",
"\u00FCnter",
"under",
"Under"
};
// print before sorting
System.out.println("Initial Strings\n\t\t");
mc.printStrings(words);
mc.sortStrings(collator, words);
System.out.println("\t\tPost sort");
mc.printStrings(words);
}
}
You either follow this or not I guess. The actual rules are read in from a file, collator.txt,
at runtime. This file is as follows:
' ' , ':' , ';' , '<' , '=' , '>' , '?' , '@', '!',
'[' , '\' , ']' , '^' , '_' , '`',
'{' , '|' , '}' , '~'
'!' , '"' , '#' , '$' , '%' , '&', ''' , '(' , ')' , '*' , '+' , ',' , '-' , '.' , '/'
< A,a < B,b < C,c < D,d < E,e < F,f < G,g
< H,h < I,i < J,j < K,k < L,l < N,n < M,m
< O,o < P,p < Q,q < R,r < S,s < T,t
< U,u < ü < V,v < W,w < X,x < Y,y < Z,z
< 0 < 1 < 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9
Note that N is specified to sort before M.
On running this the following output is generated:
Before:
Hello
hello
[hello]
Mword
Nword
Mother
Nato
:Mother
5Mother
ünter
unter
!unter
$unter
After
:Mother
Hello
hello
[hello]
Nato
Nword
Mother
Mword
unter
!unter
$unter
ünter
5Mother
Which has sorted, N before M, as required.
Note: As of Aug 03, Saxon uses saxon:collation as follows.
Integer division. Produces an int from a pair of ints.
<xsl:variable name="i1" select="5" as="xs:integer"/>
<xsl:variable name="i2" select="2" as="xs:integer"/>
a result of <xsl:value-of select="$i1 idiv $i2"/>
5 idiv 2 gives
a result of 2
When using XSLT to mark up what is essentiall plain text, the grouping facility of the regexp facility may be used, as in this example provided by David Carlisle.
With an input file such as:
<doc> <r>Mateus 3.1-12; Lucas 3.1-20; </r> <r>words 1.2-12; Name 1.2-30</r> <r>LongString 1234.3-45; swd 1.2.1-2</r> </doc>The following stylesheet fragment produces output as:
<?xml version="1.0" encoding="UTF-8"?> <wrap> <canonRef Book="Mateus" chapter="3" verse="1" verseend="12" /> <canonRef Book="Lucas" chapter="3" verse="1" verseend="20" /> <canonRef Book="words" chapter="1" verse="2" verseend="12" /> <canonRef Book="Name" chapter="1" verse="2" verseend="30" /> <canonRef Book="LongString" chapter="1234" verse="3" verseend="45" /> </wrap>
<xsl:template match="doc">
<wrap>
<xsl:apply-templates/>
</wrap>
</xsl:template>
<xsl:template match="r">
<xsl:analyze-string regex="[^;]+" select=".">
<xsl:matching-substring>
<xsl:analyze-string regex="([A-Za-z]+) *([0-9]+)\.([0-9]+)-([0-9]+)(.*$)" select=".">
<xsl:matching-substring>
<canonRef
Book="{regex-group(1)}"
chapter="{regex-group(2)}"
verse="{regex-group(3)}"
verseend="{regex-group(4)}"
/>
</xsl:matching-substring>
</xsl:analyze-string >
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
Note that each pattern in the regular expression, as defined by the braces, results in a seperable attribute.
The final group (.*$) is there for debug purposes, catching the remainder of output until the correct regexp strings are in place.
Weird one this. From the rec, This function takes a sequence or more typically, an expression, that evaluates to a sequence, and indicates that the result
sequence may be returned in any order.
For example.
<xsl:variable name="ls" select="unordered(1 to 3)"/>
<xsl:for-each select ="$ls">
<xsl:value-of select="."/> <xsl:text> </xsl:text>
</xsl:for-each>
Gives:
1 2 3
Which appears of little use to me
Maybe due to confusion over namespaces, or just to save typing, when dealing with a namespaced document, whereas in 1.0, we had to write
<xsl:template match="ns:elementName" xmlns:ns="somethingOrOther">
we can now write
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="xyz">and the processor assumes all xpath expressions are in that namespace. Neat!
WD says, Summary: data() takes a sequence of items and returns a sequence of atomic values. It returns the typed value of the node passed as parameter. For how it gets from one to the other see the WD. MK offered a use case; testing to see if this node a boolean?
<xsl:if test="data(.) instance of xs:boolean">
This node holds some data of type boolean.
</xsl:if>
Tests for equality. The spec defines this as (it is not straightforward)
they must either be atomic values that compare equal, or nodes of the same kind, with the same name, whose children are deep-equal.
With two simple variables it shows as follows:
<xsl:variable name="de1" select="(1,2,3,4,5)" as="item()*"/> <xsl:variable name="de2" select="(1,2,3,4,5)" as="item()*"/> <xsl:value-of select="if (deep-equal($de1,$de2)) then 'equal' else 'not equal'"/> Gives: equal
An attempt to catch errors early. You judge if it succeeded.
Note that this seems a little pointless. It does not inform a user that a variable has more than one item or not. It raises an error if the parameter has more than one item! Hence I can write
<xsl:variable name="singular" select="(1)" as="item()*"/> <xsl:variable name="multiple" select="(1,2,3)" as="item()*"/> <xsl:if test="exactly-one($singular)"> Singular has exactly one item. </xsl:if> yet I get an error with <xsl:if test="exactly-one($multiple)"> Singular has exactly one item. </xsl:if>
It seems to be a 'desperate measures' check on the typing system. Note also one-or-more and zero-or-one(), both similar in operation
The rule for named templates is exactly the same as for apply-templates: you only declare the parameter at the two ends of the tunnel: when the parameter is first created using xsl:with-param, and in a template that actually uses it with xsl:param.
A tunnel parameter is created by using an xsl:with-param element that specifies tunnel="yes". A template that requires access to the value of a tunnel parameter must declare it using an xsl:param element that also specifies tunnel="yes".
The available forms of formatted numbers seems to have grown. Mike Kay posted an example to the list,
<xsl:variable name="val" select="1356" as="xs:integer"/> <xsl:number value="$val" format="w"/>Which produces, one thousand three hundred and fifty six
Or, given a sequence,
<xsl:variable name="seq" select="(1, 3, 5)" />The result of
<xsl:number value="$seq" format="A I w"/>is A III five
remove($target, n ) as item()* is the description. From the target remove the nth item. This struck me as useful for recursive templates, where you want to process the cdr of the current node-set, i.e. everything except the first element. In which case the use would be
<xsl:template name="recurse">
<xsl:param name="ns"/>
... test for exit condition
... if more
<xsl:call-template name="recurse">
<xsl:with-param name="ns" select="remove($ns,1"/>
</xsl:call-template>
</xsl:template>
Bit more elegant than 1.0?
Where you want to have something like <xsl:template match="X" mode="*"/>,XSLT 2.0 now allows mode="#all"
Mike Kay offers this example of use. Given
$a= 1,2,3 $b= 2,3,5 $a minus $b = 1 $a differences to $b = 1,5except implements a mathematical set difference operation: $a except $b returns all nodes that are in $a excluding those that are also in $b. So it is asymmetric, as you observe. If you want all the nodes that are in one set and not both you could do
($a union $b) except ($a intersection $b)or
($a except $b) union ($b except $a)
Returns true if a parsable entity is passed as a parameter
<xsl:value-of select="if (doc-available('newfile.xml')) then
'File newfile.xml exists, and is XML' else 'File not found'"/>
gives
File not found
since the file exists.
Document() is no different from the same function in XSLT 1.0
doc(), Retrieves a document using a URI supplied as an string. If the URI is not valid an error is raised. If it is a relative URI Reference, it is resolved relative to the value of the base URI property from the context. I.e. its simpler.
Returns the URI of the node passed as a parameter, e.g. for this document it is:
<xsl:value-of select="document-uri(.)"/> gives file:/sgml/site2/2src/exampler2.xsl
Provides the associated prefix given a namespace. E.g.
<xsl:value-of select="prefix-from-QName(node-name(/d:doc))"/> gives dwhich is clearly correct!
Provides the qualified name from the given parameter. E.g.
<xsl:value-of select="QName('http://www.dpawson.co.uk/ns#','dp:doc')"/> gives dp:doc
As to a purpose for this... type conversion I guess.
Just as it says on the tin? E.g. for the stylesheet root this gives xml xsl saxon xs xdt d pref dp for
<xsl:value-of select="in-scope-prefixes(/xsl:stylesheet)"/>
Boolean, checking if the language of the node specified matches that of the first param. E.g. I've set the language of this stylesheet to en-UK, of which en is a subset. Hence
<xsl:value-of select="if (lang('en',/xsl:stylesheet)) then 'English' else 'not English'"/> gives:
English
Convert the argument to a number... if possible
E.g. xsl:value-of select="number('3.151492')"/> gives 3.151492
rounds a numerical value. Precision may be specified in second parameter.
3.5 rounds to 4
2.8 rounds to 3
1.567,2 rounds to 1.57
-1.4 rounds to -1
-1.5 rounds to -2
-1.567,2 rounds to -1.57
The second parameter is the required precision.
rounds a numerical value.
3.5 rounds to 4
2.8 rounds to 3
1.567 rounds to 2
-1.4 rounds to -1
-1.5 rounds to -1
-1.567 rounds to -2
The second parameter is the required precision.
Returns the base URI of the context. E.g.
<xsl:value-of select="static-base-uri()"/> shows file:/sgml/site2/2src/exampler2.xsl
Note that this file is the source file (as well as the stylesheet).
Note we have a number of ways of comparing nodes, items, atomic values etc. Just take care and use the right one.
Using <xsl:variable name="s1" select="(1,2,3,4)"/> <xsl:variable name="s2" select="(1,2,3,4)"/> <xsl:variable name="s3" select="(1,2)"/> $s1 = $s2 gives true //h3[@id='buri'] is (//h3)[1] gives true first h3 is before second h3, gives true $s1[1] eq $s2[1] gives true $s1 = $s2 gives true and the odd ones, from Mike Kays book. <xsl:value-of select = "$s2 = $s3"/> shows as true since '2' is common to both sequences!
A slightly more useful case is testing for a value being one of a number of options. E.g.
<xsl:variable name="color" select="'blue'"/>
<xsl:if test="$color = ('red','blue','green')">
color is in the set!
</xsl:if>
gives
color is in the set!
indicating that the value of the color variable has one of the stated values.
The rec says The xsl:next-match instruction considers all other
template rules of lower import precedence and/or priority.
A
practical use of this might be to perform two distinct sets of
processing on the same node.
Rather than perform this in one template, two templates matching the same node, of differing priority, may be used. The higher priority template is run first and the xsl:next-match is used to invoke the lower priority template.
Basic references. operators, XSLT the data model, the XPATH 2 Functions and Operators and xpath 2. And in case you were wondering, the silly data types are borrowed from Schema jobbie, part 2 and Data model
And if you are as confused about namespaces, see the top of this xslt source file, or look at 2.0 Basics, in XPath, near the end of that section. . Of particular note is that xmlns:xs="http://www.w3.org/2001/XMLSchema" has changed. Don't be caught out.