XSLT uuid generation duplicates id for siblings - xslt

I am using xslt with Saxon HE and I used the method specified here to generate UUIDs but I am getting the same id for all nodes of the same level.
What I am getting now:
<aaa uid="6EE63184-6950-11E8-9859-09173F13E4C5">
<discount uid="6EE63183-6950-11E8-9859-09173F13E4C5">
<li id="#d2e17">Dark CoffeeUSD 1.8
</li>
</discount>
</aaa>
<aaa uid="6EE63184-6950-11E8-9859-09173F13E4C5">
<discount uid="6EE63183-6950-11E8-9859-09173F13E4C5">
<li id="#d2e32">Milk ShakeUSD 2.6
</li>
</discount>
</aaa>
<aaa uid="6EE63184-6950-11E8-9859-09173F13E4C5">
<discount uid="6EE63183-6950-11E8-9859-09173F13E4C5">
<li id="#d2e47">Iced CoffeeUSD 1.5
</li>
</discount>
</aaa>
<aaa uid="6EE63184-6950-11E8-9859-09173F13E4C5">
<discount uid="6EE63183-6950-11E8-9859-09173F13E4C5">
<li id="#d2e62">Bottled WaterUSD 2.5
</li>
</discount>
</aaa>
I want the ids of each aaa tag and discount tag to be unique from each other. what change should I do to achieve this.
Below is the xslt I used.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/02/xpath-functions" xmlns:m0="http://services.samples" xmlns:math="http://exslt.org/math" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:uuid="http://www.uuid.org" version="2.0" exclude-result-prefixes="m0 fn">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="drink_name"/>
<xsl:template match="/">
<Payment xmlns="http://ws.apache.org/ns/synapse">
<xsl:for-each select="//Order/lunch">
<xsl:element name="aaa">
<xsl:attribute name="uid" select="uuid:get-uuid()"/>
<discount>
<xsl:attribute name="uid" select="uuid:get-uuid()"/>
<li id="#{generate-id(drinkName)}">
<xsl:value-of select="drinkName"/>
<a href="#{generate-id(drinkPrice)}">
<xsl:value-of select="drinkPrice"/>
</a>
</li>
</discount>
</xsl:element>
</xsl:for-each>
</Payment>
</xsl:template>
<!-- Returns the UUID --><xsl:function name="uuid:get-uuid" as="xs:string*">
<xsl:variable name="ts" select="uuid:ts-to-hex(uuid:generate-timestamp())"/>
<xsl:value-of separator="-" select=" substring($ts, 8, 8), substring($ts, 4, 4), string-join((uuid:get-uuid-version(), substring($ts, 1, 3)), ''), uuid:generate-clock-id(), uuid:get-network-node()"/>
</xsl:function>
<!-- internal aux. fu with saxon, this creates a more-unique result with
generate-id then when just using a variable containing a node
--><xsl:function name="uuid:_get-node">
<xsl:comment/>
</xsl:function>
<!-- generates some kind of unique id --><xsl:function name="uuid:get-id" as="xs:string">
<xsl:sequence select="generate-id(uuid:_get-node())"/>
</xsl:function>
<!-- should return the next nr in sequence, but this can't be done
in xslt. Instead, it returns a guaranteed unique number
--><xsl:function name="uuid:next-nr" as="xs:integer">
<xsl:variable name="node">
<xsl:comment/>
</xsl:variable>
<xsl:sequence select=" xs:integer(replace( generate-id($node), '\D', ''))"/>
</xsl:function>
<!-- internal fu for returning hex digits only --><xsl:function name="uuid:_hex-only" as="xs:string">
<xsl:param name="string"/>
<xsl:param name="count"/>
<xsl:sequence select=" substring(replace( $string, '[^0-9a-fA-F]', '') , 1, $count)"/>
</xsl:function>
<!-- may as well be defined as returning the same seq each time --><xsl:variable name="_clock" select="uuid:get-id()"/>
<xsl:function name="uuid:generate-clock-id" as="xs:string">
<xsl:sequence select="uuid:_hex-only($_clock, 4)"/>
</xsl:function>
<!-- returns the network node, this one is 'random', but must
be the same within calls. The least-significant bit must be '1'
when it is not a real MAC address (in this case it is set to '1')
--><xsl:function name="uuid:get-network-node" as="xs:string">
<xsl:sequence select="uuid:_hex-only('09-17-3F-13-E4-C5', 12)"/>
</xsl:function>
<!-- returns version, for timestamp uuids, this is "1" --><xsl:function name="uuid:get-uuid-version" as="xs:string">
<xsl:sequence select="'1'"/>
</xsl:function>
<!-- Generates a timestamp of the amount of 100 nanosecond
intervals from 15 October 1582, in UTC time.
--><xsl:function name="uuid:generate-timestamp"><!-- date calculation automatically goes
correct when you add the timezone information, in this
case that is UTC.
--><xsl:variable name="duration-from-1582" as="xs:dayTimeDuration">
<xsl:sequence select=" current-dateTime() - xs:dateTime('1582-10-15T00:00:00.000Z')"/>
</xsl:variable>
<xsl:variable name="random-offset" as="xs:integer">
<xsl:sequence select="uuid:next-nr() mod 10000"/>
</xsl:variable>
<!-- do the math to get the 100 nano second intervals --><xsl:sequence select=" (days-from-duration($duration-from-1582) * 24 * 60 * 60 + hours-from-duration($duration-from-1582) * 60 * 60 + minutes-from-duration($duration-from-1582) * 60 + seconds-from-duration($duration-from-1582)) * 1000 * 10000 + $random-offset"/>
</xsl:function>
<!-- simple non-generalized function to convert from timestamp to hex --><xsl:function name="uuid:ts-to-hex">
<xsl:param name="dec-val"/>
<xsl:value-of separator="" select=" for $i in 1 to 15 return (0 to 9, tokenize('A B C D E F', ' ')) [ $dec-val idiv xs:integer(math:power(16, 15 - $i)) mod 16 + 1 ]"/>
</xsl:function>
<xsl:function name="math:power">
<xsl:param name="base"/>
<xsl:param name="power"/>
<xsl:choose>
<xsl:when test="$power lt 0 or contains(string($power), '.')">
<xsl:message terminate="yes">
The XSLT template math:power doesnt support negative or
fractional arguments.
</xsl:message>
<xsl:text>NaN</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="math:_power">
<xsl:with-param name="base" select="$base"/>
<xsl:with-param name="power" select="$power"/>
<xsl:with-param name="result" select="1"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template name="math:_power">
<xsl:param name="base"/>
<xsl:param name="power"/>
<xsl:param name="result"/>
<xsl:choose>
<xsl:when test="$power = 0">
<xsl:value-of select="$result"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="math:_power">
<xsl:with-param name="base" select="$base"/>
<xsl:with-param name="power" select="$power - 1"/>
<xsl:with-param name="result" select="$result * $base"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

(Preliminary incomplete answer, I will come back to this later)
This whole stylesheet depends very strongly on the notion that when a function creates new nodes, then it will create different nodes each time it is called, and different nodes will have different results for generate-id(). That is, it's relying on the fact that calling a function has a subtle side-effect, with the consequence that it's possible to call the same function twice and get different results. This of course is a departure from the normal rules of functional programming, and it has nasty consequences for optimization, because it means that function calls can't be pulled out of loops (if you call f(3) within a loop, you can't pull that call out of the loop and only do it once).
This is discussed in detail at https://www.w3.org/TR/xslt-30/#function-determinism
XSLT 3.0 allows you to declare the function's expectations using the attribute new-each-time="yes". This is supposed to be the default, but because Saxon has a history of attempting optimization in the absence of strict rules in this area, it's probably trying to make its own assessment of whether multiple calls are necessary.
Using the -explain option when you run the stylesheet gives you feedback on what optimizations Saxon is applying. You can also switch off selected optimizations, for example -opt:-fl switches off loop-lifting and function inlining.
A much better approach to the whole problem would be to use the new fn:random-number-generator() function. Unfortunately this uses higher order functions so it is not available in Saxon-HE.

Related

Better way to cycle xsl:for-each letter of the alphabet?

I have a long XML file from which I ned to pull out book titles and other information, then sort it alphabetically, with a separator for each letter. I also need a section for items that don't begin with a letter, say a number or symbol. Something like:
#
1494 - hardcover, $9.99
A
After the Sands - paperback, $24.95
Arctic Spirit - hardcover, $65.00
B
Back to the Front - paperback, $18.95
…
I also need to create a separate list of authors, created from the same data but showing different kinds of information.
How I'm currently doing it
This is simplified, but I basically have this same code twice, once for titles and once for authors. The author version of the template works with different elements and does different things with the data, so I can't use the same template.
<xsl:call-template name="BIP-letter">
<xsl:with-param name="letter" select="'#'" />
</xsl:call-template>
<xsl:call-template name="BIP-letter">
<xsl:with-param name="letter" select="'A'" />
</xsl:call-template>
…
<xsl:call-template name="BIP-letter">
<xsl:with-param name="letter" select="'Z'" />
</xsl:call-template>
<xsl:template name="BIP-letter">
<xsl:param name="letter" />
<xsl:choose>
<xsl:when test="$letter = '#'">
<xsl:text>#</xsl:text>
<xsl:for-each select="//Book[
not(substring(Title,1,1) = 'A') and
not(substring(Title,1,1) = 'B') and
…
not(substring(Title/,1,1) = 'Z')
]">
<xsl:sort select="Title" />
<xsl:appy-templates select="Title" />
<!-- Add other relevant data here -->
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$letter" />
<xsl:for-each select="//Book[substring(Title,1,1) = $letter]">
<xsl:sort select="Title" />
<xsl:appy-templates select="Title" />
<!-- Add other relevant data here -->
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
My questions
The code above works just fine, but:
Manually cycling through each letter gets very long, especially having to do it twice. Is there a way to simplify that? Something like a <xsl:for-each select="[A-Z]"> that I could use to set the parameter when calling the template?
Is there a simpler way to select all titles that don't begin with a letter? Something like //Book[not(substring(Title,1,1) = [A-Z])?
There may be cases where the title or author name starts with a lowercase letter. In the code above, they would get grouped with under the # heading, rather than with the actual letter. The only way I can think to accommodate that—doing it manually—would significantly bloat up the code.
This solution answers all questions asked:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vLowercase" select="'abcdefghijklmnopqrstuvuxyz'"/>
<xsl:variable name="vUppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="vDigits" select="'0123456789'"/>
<xsl:key name="kBookBy1stChar" match="Book"
use="translate(substring(Title, 1, 1),
'abcdefghijklmnopqrstuvuxyz0123456789',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ##########'
)"/>
<xsl:template match="/*">
<xsl:apply-templates mode="firstInGroup" select=
"Book[generate-id()
= generate-id(key('kBookBy1stChar',
translate(substring(Title, 1, 1),
concat($vLowercase, $vDigits),
concat($vUppercase, '##########')
)
)[1]
)
]">
<xsl:sort select="translate(substring(Title, 1, 1),
concat($vLowercase, $vDigits),
concat($vUppercase, '##########')
)"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Book" mode="firstInGroup">
<xsl:value-of select="'
'"/>
<xsl:value-of select="translate(substring(Title, 1, 1),
concat($vLowercase, $vDigits),
concat($vUppercase, '##########')
)"/>
<xsl:apply-templates select=
"key('kBookBy1stChar',
translate(substring(Title, 1, 1),
concat($vLowercase, $vDigits),
concat($vUppercase, '##########')
)
)">
<xsl:sort select="Title"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Book">
<xsl:value-of select="'
'"/>
<xsl:value-of select="concat(Title, ' - ', Binding, ', $', price)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following xml document (none provided in the question!):
<Books>
<Book>
<Title>After the Sands</Title>
<Binding>paperback</Binding>
<price>24.95</price>
</Book>
<Book>
<Title>Cats Galore: A Compendium of Cultured Cats</Title>
<Binding>hardcover</Binding>
<price>5.00</price>
</Book>
<Book>
<Title>Arctic Spirit</Title>
<Binding>hardcover</Binding>
<price>65.00</price>
</Book>
<Book>
<Title>1494</Title>
<Binding>hardcover</Binding>
<price>9.99</price>
</Book>
<Book>
<Title>Back to the Front</Title>
<Binding>paperback</Binding>
<price>18.95</price>
</Book>
</Books>
the wanted, correct result is produced:
#
1494 - hardcover, $9.99
A
After the Sands - paperback, $24.95
Arctic Spirit - hardcover, $65.00
B
Back to the Front - paperback, $18.95
C
Cats Galore: A Compendium of Cultured Cats - hardcover, $5.00
Explanation:
Use of the Muenchian method for grouping
Use of the standard XPath translate() function
Using mode to process the first book in a group of books starting with the same (case-insensitive) character
Using <xsl:sort> to sort the books in alphabetical orser
The most problematic part is this:
I also need a section for items that don't begin with a letter, say a number or symbol.
If you have a list of all possible symbols that an item can begin with, then you can simply use translate() to convert them all to the # character. Otherwise it gets more complicated. I would try something like:
XSLT 1.0 (+ EXSLT node-set())
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:key name="book" match="Book" use="index" />
<xsl:template match="/Books">
<!-- first-pass: add index char -->
<xsl:variable name="books-rtf">
<xsl:for-each select="Book">
<xsl:copy>
<xsl:copy-of select="*"/>
<index>
<xsl:variable name="index" select="translate(substring(Title, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
<xsl:choose>
<xsl:when test="contains('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $index)">
<xsl:value-of select="$index"/>
</xsl:when>
<xsl:otherwise>#</xsl:otherwise>
</xsl:choose>
</index>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="books" select="exsl:node-set($books-rtf)/Book" />
<!-- group by index char -->
<xsl:for-each select="$books[count(. | key('book', index)[1]) = 1]">
<xsl:sort select="index"/>
<xsl:value-of select="index"/>
<xsl:text>
</xsl:text>
<!-- list books -->
<xsl:for-each select="key('book', index)">
<xsl:sort select="Title"/>
<xsl:value-of select="Title"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="Binding"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="Price"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
However, this still leaves the problem of items that begin with a diacritic, e.g. "Österreich" or say a Greek letter. Under this method they too will be clumped under #.
Unfortunately, the only good solution for this is to move to XSLT 2.0.
Demo: https://xsltfiddle.liberty-development.net/jyRYYjj/2

XSLT time zone conversion

I'm working with a system that will only output the server time in Central Time Zone (CT). I need to convert this in XSLT to US Eastern Time.
Is there a built in method to translate this or do I need to use Regex?
<node time="02:14 pm CT" />
Current Output: 02:14 pm CT
Desired Output: 03:14 pm ET
There are at least two main paths to choose between, converting it into a time and using a time based library, or taking it as a string and doing straight string manipulation. The following is string manipulation:
<xsl:variable name="time" select="'11:14 pm CT'"/> <!-- the input value -->
<xsl:variable name="hours" select="number(substring-before($time,':'))"/> <!-- numeric hours -->
<xsl:variable name="mer" select="substring($time,7,2)"/> <!-- the am or pm part -->
<xsl:choose>
<xsl:when test="$hours < 12"> <!-- if we are 01-11 -->
<xsl:value-of select="substring(concat('0', $hours + 1), string-length(concat('0', $hours + 1)) - 1, 2)"/> <!-- add an hour and repad the string with leading zero, messy -->
</xsl:when>
<xsl:otherwise>
<xsl:text>01</xsl:text> <!-- we were 12, so just use 01 -->
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="substring($time, 3,4)"/> <!-- pull the minutes forward -->
<xsl:choose>
<xsl:when test="not($hours = 11)"> <!-- if we were not 11 for hours we keep the same am/pm -->
<xsl:value-of select="$mer"/>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$mer = 'pm'"> <!-- otherwise we flip it -->
<xsl:text>am</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>pm</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
<xsl:text> ET</xsl:text>
There is no built-in method for this in XSLT 1.0. Regardless, it would have been fairly trivial to do - except for the fact that your time input is in 12-hour format. This makes the process rather tedious, so I have split it off to a processing template:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<node>
<xsl:attribute name="time">
<xsl:call-template name="time-offset">
<xsl:with-param name="time" select="node/#time"/>
</xsl:call-template>
</xsl:attribute>
</node>
</xsl:template>
<xsl:template name="time-offset">
<xsl:param name="time"/>
<xsl:param name="offset" select="1"/>
<xsl:param name="h12" select="substring($time, 1, 2)"/>
<xsl:param name="pm" select="contains($time,'p') or contains($time,'P')"/>
<xsl:param name="h24" select="$h12 mod 12 + 12*$pm"/>
<xsl:param name="newH24" select="($h24 + $offset + 24) mod 24"/>
<xsl:param name="newH12" select="($newH24 + 11) mod 12 + 1"/>
<xsl:param name="am.pm" select="substring('AMPM', 1 + 2*($newH24 > 11), 2)"/>
<xsl:value-of select="concat(format-number($newH12, '00'), substring($time, 3, 4), $am.pm, ' ET')"/>
</xsl:template>
</xsl:stylesheet>
When the above stylesheet is applied to the example input:
<node time="12:14 am CT" />
the result is:
<?xml version="1.0" encoding="UTF-8"?>
<node time="01:14 AM ET"/>

XSLT : Looping from 1 to 60

What is the best way to loop in XSLT from 1 to 60?
I research in net, there are some templates to do this, is there any other way for example like a built-in function?
In XSLT 2.0,
<xsl:for-each select="1 to 60">...</xsl:for-each>
But I guess that you must be using XSLT 1.0, otherwise you wouldn't be asking.
In XSLT 1.0 you should use recursion: a template that calls itself with a counter that's incremented on each call, and the recursion terminates when the required value is reached.
Alternatively there's a workaround in XSLT 1.0: provided your source document contains at least 60 nodes, you can do
<xsl:for-each select="(//node())[60 >= position()]">...</xsl:for-each>
The problem with simple recursion when processing long sequences is that often the space for the call stack becomes insufficient and the processing ends due to stack overflow. This typically happens with sequence length >= 1000.
A general technique to avoid this (implementable with any XSLT processor, even if it doesn't recognize tail-recursion) is DVC (Divide and Conquer) style recursion.
Here is an example of a transformation that successfully prints the numbers from 1 to 1000000 (1M):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="1000000"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="displayNumbers">
<xsl:param name="pStart"/>
<xsl:param name="pEnd"/>
<xsl:if test="not($pStart > $pEnd)">
<xsl:choose>
<xsl:when test="$pStart = $pEnd">
<xsl:value-of select="$pStart"/>
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vMid" select=
"floor(($pStart + $pEnd) div 2)"/>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$pStart"/>
<xsl:with-param name="pEnd" select="$vMid"/>
</xsl:call-template>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$vMid+1"/>
<xsl:with-param name="pEnd" select="$pEnd"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied on any XML document (not used) this transformation produces the wanted result -- all the numbers from 1 to 1000000.
You can use/adapt this transformation for any task that needs to "do something N times".
Very simple check inside the foreach-loop
<xsl:if test="$maxItems > position()">
do something
</xsl:if>
Based on Dimitre Novatchev's answer.
Example:
<xsl:variable name="maxItems" select="10" />
<xsl:variable name="sequence" select="any-sequence"/>
<xsl:for-each select="$sequence">
<!-- Maybe sort first -->
<xsl:sort select="#sort-by" order="descending" />
<!-- where the magic happens -->
<xsl:if test="$maxItems > position()">
do something
</xsl:if>
</xsl:for-each>
The basic example for V1.0 using recursion would it be like this:
<xsl:template match="/">
<Root>
<!-- Main Call to MyTemplate -->
<xsl:call-template name="MyTemplate" />
</Root>
</xsl:template>
<xsl:template name="MyTemplate">
<xsl:param name="index" select="1" />
<xsl:param name="maxValue" select="60" />
<MyCodeHere>
<xsl:value-of select="$index"/>
</MyCodeHere>
<!-- < represents "<" for html entities -->
<xsl:if test="$index < $maxValue">
<xsl:call-template name="MyTemplate">
<xsl:with-param name="index" select="$index + 1" />
<xsl:with-param name="total" select="$maxValue" />
</xsl:call-template>
</xsl:if>
</xsl:template>
XSLT works based on templates and you'll need a template do run that loop.
You'll need to build a template receiving start and end values and, inside it, make a recursive call computing with start + 1. When $start equals $end, you do return your template, without another call.
In practice: http://www.ibm.com/developerworks/xml/library/x-tiploop/index.html

Custom format for <xsl:number>

Is it possible to define a custom format for <xsl:number>?
I have the case where a standard alpha-based format is desired, but certain characters in the alphabet are forbidden (strange requirement, but it is what the client requires). For example, the letter i cannot be used, so when using <xsl:number> I should get the sequence: a, b, c, d, e, f, g, h, j, k, ..., aa, ab, ..., ah, aj, ...
The project is using XSLT 2.0 and Saxon, so if a solution exists that is specific to Saxon, that is okay.
Does XSLT 2.0 provide the capability to define a custom format sequence? Does Saxon provide a capability to register a custom sequence for use with <xsl:number>?
XSLT 2.0 provides the format attribute for xsl:number by which you can use the format token aa for example. The computed number depends by the expression evaluated inside value attribute and will be formatted accordingly to format.
Given this, you can think of first evaluating the correct sequence of numbers excluding those that will match for a particular letter.
For instance, the following instruction:
<xsl:number value="$sequence" format="aa"/>
will print (notice i excluded):
a.b.c.d.e.f.g.h.j.k.l.m
if $sequence evaluates to (notice 9 skipped):
1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13
Notice that if you have 12 elements your expression should be able to skip the unwanted number (9 for i) and increase the following of one. The last element with position 12, should have corresponding number 13.
So what you need, is just the algorithm that computes the wanted sequence; which depends definitely from your input document.
References: XSLT 2.0 Rec.
You can customize the output of xsl:number in Saxon by writing an implementation of the interface net.sf.saxon.lib.Numberer: probably you will want to make this a subclass of net.sf.saxon.expr.number.Numberer_en. You'll need to study the source code and work out what needs overriding.
In Saxon PE/EE you can register the Numberer to be used for a given language in the Saxon configuration file. For Saxon HE it requires a bit more work: you have to implement the interface LocalizerFactory and register your LocalizerFactory with the Configuration.
EDIT: An alternate, more general, solution exists and is posted as a separate answer. I'm leaving this answer since it still may be of value to some.
I like #empo's thinking (I mod'ed it up), but I think it may be hard to get a working solution. A clever algorithm/equation is required to come up with the correct sequence number based on the raw sequence to avoid getting a label that does not contain the forbidden characters. At this time, such an algorithm escapes me.
One method I came up with is to create my own function, and not use <xsl:number>. In essence, we are dealing with a base 23 set, the letters a to z, but excluding the characters i, l, and o. The function I came up with only goes up to zz, but that should be sufficient for what is needed (provides labelling up to 552 items).
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ewh="http://www.earlhood.com/XSL/Transform"
exclude-result-prefixes="#all">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="letters" select="'abcdefghjkmnpqrstuvwxyz'"/>
<xsl:variable name="lbase" select="23"/>
<xsl:function name="ewh:get-alpha-label" as="xs:string">
<xsl:param name="number" as="xs:integer"/>
<xsl:variable name="quotient" select="$number idiv $lbase"/>
<xsl:variable name="remainder" select="$number mod $lbase"/>
<xsl:variable name="p1">
<xsl:choose>
<xsl:when test="($quotient gt 0) and ($remainder = 0)">
<xsl:value-of select="substring($letters,($quotient - 1),1)"/>
</xsl:when>
<xsl:when test="($quotient gt 0) and ($remainder gt 0)">
<xsl:value-of select="substring($letters,$quotient,1)"/>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="p0">
<xsl:choose>
<xsl:when test="$remainder = 0">
<xsl:value-of select="substring($letters,$lbase,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($letters,$remainder,1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat($p1,$p0)"/>
</xsl:function>
<xsl:template match="/">
<result>
<value n="9"><xsl:value-of select="ewh:get-alpha-label(9)"/></value>
<value n="12"><xsl:value-of select="ewh:get-alpha-label(12)"/></value>
<value n="15"><xsl:value-of select="ewh:get-alpha-label(15)"/></value>
<value n="23"><xsl:value-of select="ewh:get-alpha-label(23)"/></value>
<value n="26"><xsl:value-of select="ewh:get-alpha-label(26)"/></value>
<value n="33"><xsl:value-of select="ewh:get-alpha-label(33)"/></value>
<value n="46"><xsl:value-of select="ewh:get-alpha-label(46)"/></value>
<value n="69"><xsl:value-of select="ewh:get-alpha-label(69)"/></value>
<value n="70"><xsl:value-of select="ewh:get-alpha-label(70)"/></value>
<value n="200"><xsl:value-of select="ewh:get-alpha-label(200)"/></value>
<value n="552"><xsl:value-of select="ewh:get-alpha-label(552)"/></value>
</result>
</xsl:template>
</xsl:stylesheet>
When I execute the above, I get the following output:
<result>
<value n="9">j</value>
<value n="12">n</value>
<value n="15">r</value>
<value n="23">z</value>
<value n="26">ac</value>
<value n="33">ak</value>
<value n="46">az</value>
<value n="69">bz</value>
<value n="70">ca</value>
<value n="200">hs</value>
<value n="552">zz</value>
</result>
It would be nice of XSLT provided the capability to define a custom character sequence for use with <xsl:number>. Seems like such a capability would generalize <xsl:number> w/o relying on custom extensions, which I do not know if any XSLT engine provides for <xsl:number>.
I came up with the following, more generalized solution, after posting my original solution to the problem. The solution is pure XSLT and at the base, still uses <xsl:number>, so should be applicable to any format type.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ewh="http://www.earlhood.com/XSL/Transform"
exclude-result-prefixes="#all">
<!-- Description: XSLT to generate a alpha formatted sequence
label (via <xsl:number>), but disallowing specific characters
from being used.
-->
<!-- Algorithm: Given the index value of the item to generate
a label for via <xsl:number>, we adjust the value so the resulting
label avoids the use of the forbidden characters.
This is achieved by converting the index value into a baseX
number, with X the number of allowed characters.
The baseX number will be converted into a reverse sequence
of numbers for each ^E place. For example, the number 12167
converted to base23 will generate the following reverse sequence:
Place: (23^0, 23^1, 23^2, 23^3)
Sequence: ( 0, 0, 0, 1) // 1000 in base23
Having it in right-to-left order makes processing easier.
Each item in the sequence will be a number from 0 to baseX-1.
With the sequence, we can then just call <xsl:number> on
each item and reverse concatenate the result.
NOTE: Since <xsl:number> does not like 0 as a given value,
the sequence must be processed so each item is within the
range of 1-to-baseX. For example, the above base23 example
will be translated to the following:
(23, 22, 22)
-->
<xsl:output method="xml" indent="yes"/>
<!-- Number of allowed characters: This should be total number of chars of
format-type desired minus the chars that should be skipped. -->
<xsl:variable name="lbase" select="23"/>
<!-- Sequence of character positions not allowed, with 1=>a to 26=>z -->
<xsl:variable name="lexcs" select="(9,12,15)"/> <!-- i,l,o -->
<!-- Helper Function:
Convert integer to sequence of number of given base.
The sequence of numbers is in reverse order: ^0,^1,^2,...^N.
-->
<xsl:function name="ewh:get_base_digits" as="item()*">
<xsl:param name="number" as="xs:integer"/>
<xsl:param name="to" as="xs:integer"/>
<xsl:variable name="Q" select="$number idiv $to"/>
<xsl:variable name="R" select="$number mod $to"/>
<xsl:sequence select="$R"/>
<xsl:if test="$Q gt 0">
<xsl:sequence select="ewh:get_base_digits($Q,$to)"/>
</xsl:if>
</xsl:function>
<!-- Helper Function:
Compute carry-overs in reverse-base digit sequence. XSLT starts
numbering at 1, so we cannot have any 0s.
-->
<xsl:function name="ewh:compute_carry_overs" as="item()*">
<xsl:param name="digits" as="item()*"/>
<xsl:variable name="d" select="subsequence($digits, 1, 1)"/>
<xsl:choose>
<xsl:when test="($d le 0) and (count($digits) = 1)">
<!-- 0 at end of list, nothing to do -->
</xsl:when>
<xsl:when test="$d le 0">
<!-- If digit <=0, need to perform carry-over operation -->
<xsl:variable name="next" select="subsequence($digits, 2, 1)"/>
<xsl:choose>
<xsl:when test="count($digits) le 2">
<xsl:sequence select="$lbase + $d"/>
<xsl:sequence select="ewh:compute_carry_overs($next - 1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$lbase + $d"/>
<xsl:sequence select="ewh:compute_carry_overs(($next - 1,
subsequence($digits, 3)))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="count($digits) le 1">
<xsl:sequence select="$d"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$d"/>
<xsl:sequence select="ewh:compute_carry_overs(subsequence($digits, 2))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<!-- Helper Function:
Given a number in the base range, determine number for
purposes of <xsl:number>. We loop thru the exclusion
list and add 1 for each exclusion letter that has
been passed. The $digit parameter should be a number
in the range [1..$lbase].
-->
<xsl:function name="ewh:compute_digit_offset" as="xs:integer">
<xsl:param name="digit" as="xs:integer"/>
<xsl:param name="excludes" as="item()*"/>
<xsl:variable name="l" select="subsequence($excludes, 1, 1)"/>
<xsl:variable name="result">
<xsl:choose>
<xsl:when test="$digit lt $l">
<xsl:value-of select="0"/>
</xsl:when>
<xsl:when test="count($excludes) = 1">
<xsl:value-of select="1"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="rest">
<xsl:value-of select="ewh:compute_digit_offset($digit+1,
subsequence($excludes,2))"/>
</xsl:variable>
<xsl:value-of select="1 + $rest"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$result"/>
</xsl:function>
<!-- Retrieve alpha sequence label.
This is the main function to call.
-->
<xsl:function name="ewh:get-alpha-label" as="xs:string">
<xsl:param name="number" as="xs:integer"/>
<xsl:variable name="basedigits"
select="ewh:get_base_digits($number,$lbase)"/>
<xsl:variable name="digits"
select="ewh:compute_carry_overs($basedigits)"/>
<xsl:variable name="result" as="item()*">
<xsl:for-each select="$digits">
<xsl:variable name="digit" select="."/>
<!-- Should not have any 0 values. If some reason we do,
we ignore assuming they are trailing items. -->
<xsl:if test="$digit != 0">
<xsl:variable name="value">
<xsl:value-of select="$digit +
ewh:compute_digit_offset($digit,$lexcs)"/>
</xsl:variable>
<xsl:variable name="number">
<xsl:number value="$value" format="a"/>
</xsl:variable>
<xsl:sequence select="$number"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string-join(reverse($result),'')"/>
</xsl:function>
<!-- For testing -->
<xsl:template match="/">
<result>
<xsl:for-each select="(1 to 1000,12166,12167,12168,279840,279841,279842)">
<value n="{.}"><xsl:value-of select="ewh:get-alpha-label(.)"/></value>
</xsl:for-each>
</result>
</xsl:template>
</xsl:stylesheet>

How to show a character n times in XSLT?

I have a template with a parameter. How can I insert a tab character n times?
n is the value of the parameter.
In XSLT 2.0:
<xsl:for-each select="1 to $count"> </xsl:for-each>
(Sadly though, I suspect that if you were using XSLT 2.0 you wouldn't need to ask the question).
Another technique often used with XSLT 1.0 is the hack:
<xsl:for-each select="//*[position() <= $count]"> </xsl:for-each>
which works provided the number of elements in your source document is greater than the number of tab characters you want to output.
Just call it recursively; output a tab, then call the same template again with n-1 passed in, if n > 1.
<xsl:template name="repeat">
<xsl:param name="output" />
<xsl:param name="count" />
<xsl:if test="$count > 0">
<xsl:value-of select="$output" />
<xsl:call-template name="repeat">
<xsl:with-param name="output" select="$output" />
<xsl:with-param name="count" select="$count - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
As has been pointed out, this example will actually output a minimum of one. In my experience where the output is whitespace, it's usually needed. You can adapt the principle of a recursive template like this any way you see fit.
This seems the simplest and most flexible to me.
For XSLT 1.0 (or perhaps 1.1).
<xsl:variable name="count">10</xsl:variable>
<xsl:variable name="repeat"><xsl:text> </xsl:text></xsl:variable>
<xsl:sequence select="string-join((for $i in 1 to $count return $repeat),'')"/>
Of course the count variable is where you assign your n parameter.
I used the variable repeat to hold the tab character, but you could just replace the $repeat with the tab character in single quotes in the sequence element. Note: This variable can be of a length greater than 1, which creates a whole bunch of possibilities.
It does not use recursion, so it won't run into a recursion limit.
I don't know the maximum value you can use for count, but I tested it up to 10,000.
Globally define a long enough array of tabs:
<xsl:variable name="TABS" select="' '" />
Then use like this:
<xsl:value-of select="fn:substring($TABS, 1, fn:number($COUNT))" />
(XSLT 1.0)
<xsl:template name="tabs">
<xsl:param name="n"/>
<xsl:if test="$n > 0"> <!-- When n = 0, output nothing. -->
<xsl:call-template name="tabs"> <!-- Recursive call: call same template... -->
<xsl:with-param name="n" select="$n - 1"/> <!-- ... for writing n - 1 tabs. -->
</xsl:call-template>
<xsl:text> </xsl:text> <!-- Add one tab character. -->
</xsl:if>
</xsl:template>
Example usage:
<xsl:call-template name="tabs">
<xsl:with-param name="n" select="3"/>
</xsl:call-template>
I've discovered an LGPL-licensed library for doing this called functx, as I was sure someone had to have already done this... This is a "standard library" type XSLT library, which contains a function called repeat-string. From the docs:
The functx:repeat-string function returns a string consisting of a given number of copies of $stringToRepeat concatenated together.
Where I use it like this in my code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:functx="http://www.functx.com">
<xsl:import href="../buildlib/functx-1.0.xsl"/>
<xsl:output omit-xml-declaration="yes" />
<xsl:variable name="INDENT" select="' '" />
....
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="data-pusher-properties">
<xsl:for-each select="property">
<xsl:choose>
...
<xsl:when test="boolean(#value = '${pusher.notifications.server}')">
<xsl:value-of select="functx:repeat-string($INDENT, #indent)" />
<xsl:text>"</xsl:text>
<xsl:value-of select="#name" />
<xsl:text>": </xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="$pusher.notifications.email.server" />
<xsl:text>"\
</xsl:text>
</xsl:when>
...
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So for printing a tab character n times, call it like this:
<xsl:value-of select="functx:repeat-string(' ', n)" />
I know this question is old, but I hope this can still help someone.
Documentation for the repeat-string function