XSLT counting "identical" elements based on a subset of descendants - html

I'm transforming XML to HTML. In part of the XML, I need to count elements that seem "identical" when specific descendants are compared, while other descendants and any attributes must be ignored.
Here's a simplified example of my XML. The real thing is much more complicated; I've just removed elements that aren't relevant to the counting:
<complaints>
<lodgedComplaint>
<defendant>First Person</defendant>
<charge>
<actionNumber>1</actionNumber>
<offence>
<offenceSection>
<legislation>SA1</legislation>
<description>Stealing</description>
</offenceSection>
<placeOfOffence>Sydney</placeOfOffence>
<dateOfOffence>2010-01-01</dateOfOffence>
<particular>value=20</particular>
</offence>
</charge>
<charge>
<actionNumber>2</actionNumber>
<offence>
<offenceSection>
<legislation>SA2</legislation>
<description>Theft</description>
</offenceSection>
<placeOfOffence>Sydney</placeOfOffence>
<dateOfOffence>2010-01-01</dateOfOffence>
</offence>
</charge>
<charge>
<actionNumber>3</actionNumber>
<offence>
<offenceSection>
<legislation>SA2</legislation>
<description>Theft</description>
</offenceSection>
<placeOfOffence>London</placeOfOffence>
<dateOfOffence>2010-01-01</dateOfOffence>
</offence>
</charge>
<charge>
<actionNumber>4</actionNumber>
<offence>
<offenceSection>
<legislation>SA1</legislation>
<description>Stealing</description>
</offenceSection>
<placeOfOffence>Sydney</placeOfOffence>
<dateOfOffence>2010-01-01</dateOfOffence>
<particular>value=50</particular>
</offence>
</charge>
<charge>
<actionNumber>5</actionNumber>
<offence>
<offenceSection>
<legislation>SA2</legislation>
<description>Theft</description>
</offenceSection>
<placeOfOffence>Sydney</placeOfOffence>
<dateOfOffence>2010-01-02</dateOfOffence>
</offence>
</charge>
</lodgedComplaint>
<lodgedComplaint>
<defendant>Second Person</defendant>
<charge>
<actionNumber>1</actionNumber>
<offence>
<offenceSection>
<legislation>SA1</legislation>
<description>Stealing</description>
</offenceSection>
<placeOfOffence>Sydney</placeOfOffence>
<dateOfOffence>2010-01-01</dateOfOffence>
<particular>value=35</particular>
</offence>
</charge>
</lodgedComplaint>
</complaints>
In each lodgedComplaint, any two charges should be considered identical if their legislation, description, placeOfOffence and dateOfOffence elements match. The actionNumber and particular elements must be ignored (the particular element is optional and unbounded in the schema I've been given).
The desired output should look something like this:
First Person
2 counts of Stealing at Sydney on 1/1/2010 under SA1
1 count of Theft at Sydney on 1/1/2010 under SA2
1 count of Theft at London on 1/1/2010 under SA2
1 count of Theft at Sydney on 2/1/2010 under SA2
Second Person
1 count of Stealing at Sydney on 1/1/2010 under SA1
Here's an XSLT I tried, based on things I've read on Stack Overflow and elsewhere. It doesn't work; the charge details don't appear at all. I think my use of concat() causes problems.
How can I do this?
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes"/>
<xsl:key name="matchOffence" match="offence" use="concat(offenceSection/legislation,offenceSection/description,placeOfOffence,dateOfOffence)"/>
<xsl:template match="/">
<html>
<head>
<title>CRIMES Listings</title>
<link rel="stylesheet" type="text/css" href="global_styles.css"/>
<link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
<xsl:apply-templates select="complaints/lodgedComplaint"/>
</body>
</html>
</xsl:template>
<xsl:template match="lodgedComplaint">
<br/>
<xsl:value-of select="defendant"/>
<xsl:for-each select="charge/offence[generate-id(concat(offenceSection/legislation,offenceSection/description,placeOfOffence,dateOfOffence))=generate-id(key('matchOffence',.))]">
<br/>
<xsl:value-of select="count(../../charge/offence[(offenceSection/legislation=current()/offenceSection/legislation) and (offenceSection/description=current()/offenceSection/description) and (placeOfOffence=current()/placeOfOffence) and (dateOfOffence=current()/dateOfOffence)])"/>
counts of <b><xsl:value-of select="offenceSection/description"/></b>
at <b><xsl:value-of select="placeOfOffence"/></b>
on <b><xsl:call-template name="date">
<xsl:with-param name="text" select="dateOfOffence"/>
</xsl:call-template></b>
under <b><xsl:value-of select="offenceSection/legislation"/></b>
</xsl:for-each>
</xsl:template>
<xsl:template name="date">
<xsl:param name="text" select="."/>
<xsl:choose>
<xsl:when test="contains($text,'-')">
<xsl:call-template name="date">
<xsl:with-param name="text" select="substring-after($text,'-')"/>
</xsl:call-template>/<xsl:value-of select="substring-before($text,'-')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

There are two problems with your stylesheet.
You are trying to generate-id() using the results of the concat(), which produces a string result. generate-id() only works for nodes.
It should be re-written as: <xsl:for-each select="charge/offence[generate-id(.)=generate-id(key('matchOffence',concat(offenceSection/legislation,./offenceSection/description,placeOfOffence,dateOfOffence)))]">
The other problem is that the string value used for the key is not unique. The offence for the second person has the same values as one of the offense for the first person. This prevents you from generating output for the second person. You can make the key more unique by adding in the value of the defendant element in your key.
After adjusting the key, then you would obviously need to adjust the for-each.
Putting it all together in your stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes"/>
<xsl:key name="matchOffence" match="offence" use="concat(../../defendant,offenceSection/legislation,offenceSection/description,placeOfOffence,dateOfOffence)"/>
<xsl:template match="/">
<html>
<head>
<title>CRIMES Listings</title>
<link rel="stylesheet" type="text/css" href="global_styles.css"/>
<link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
<xsl:apply-templates select="complaints/lodgedComplaint"/>
</body>
</html>
</xsl:template>
<xsl:template match="lodgedComplaint">
<br/>
<xsl:value-of select="defendant"/>
<xsl:for-each select="charge/offence[generate-id(.)=generate-id(key('matchOffence',concat(../../defendant,offenceSection/legislation,./offenceSection/description,placeOfOffence,dateOfOffence)))]">
<br/>
<xsl:value-of select="count(../../charge/offence[(offenceSection/legislation=current()/offenceSection/legislation) and (offenceSection/description=current()/offenceSection/description) and (placeOfOffence=current()/placeOfOffence) and (dateOfOffence=current()/dateOfOffence)])"/>
counts of <b><xsl:value-of select="offenceSection/description"/></b>
at <b><xsl:value-of select="placeOfOffence"/></b>
on <b><xsl:call-template name="date">
<xsl:with-param name="text" select="dateOfOffence"/>
</xsl:call-template></b>
under <b><xsl:value-of select="offenceSection/legislation"/></b>
</xsl:for-each>
</xsl:template>
<xsl:template name="date">
<xsl:param name="text" select="."/>
<xsl:choose>
<xsl:when test="contains($text,'-')">
<xsl:call-template name="date">
<xsl:with-param name="text" select="substring-after($text,'-')"/>
</xsl:call-template>/<xsl:value-of select="substring-before($text,'-')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Related

how to get values from xml to json using XSLT

I got a question, I try to convert an XML to JSON using XSLT version 1.0.
As far as the name goes I get it right but the value is another story.
<datapost>
<fields>
<field>
<id>emailId</id>
<name>emailName</name>
<values>
<value>info#example.com</value>
</values>
</field>
</fields>
AS IT CURRENTLY IS:
At the moment I get only the "name" correct but the "value" (emailIdName & emailId & info#example.com) is all the values squashed together what I obviously don't want.
{
"emailName":{
"emailIdemailNameinfo#example.com"
}
}
EXPECTED TO BE:
I want to get only the "name" and the "value" in values (info#example.com)
This is the result that I WANT to get:
{
"emailName":{
"info#example.com"
}
}
This is the code I use:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no" encoding="UTF-16" omit-xml-declaration="yes"/>
<xsl:template match="/">
<body>{
<xsl:call-template name="fieldsName"></xsl:call-template>
<xsl:text>
</xsl:text>
</body>
</xsl:template>
<xsl:template name="fieldsName">
<xsl:for-each select="//datapost[position()=1]/fields/field">
"
<xsl:value-of select="name"/>" :
<xsl:call-template name="fieldsValue"/>}
</xsl:for-each>
</xsl:template>
<!-- Array Element -->
<xsl:template match="*" mode="ArrayElement">
<xsl:call-template name="fieldsValue"/>
</xsl:template>
<!-- Object Properties -->
<xsl:template name="fieldsValue">
<xsl:variable name="childName" select="//datapost[position()=1]/fields/field/values/value"/>
<xsl:choose>
<xsl:when test="not(*|#*)">"
<xsl:value-of select="."/>"
</xsl:when>
<xsl:when test="count(*[name()=$childName]) > 1">{ "
<xsl:value-of select="$childName"/>" :[
<xsl:apply-templates select="*" mode="ArrayElement"/>] }
</xsl:when>
<xsl:otherwise>{
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
}
</xsl:otherwise>
</xsl:choose>
<xsl:if test="following-sibling::*">,</xsl:if>
</xsl:template>
<!-- Attribute Property -->
<xsl:template match="#*">"
<xsl:value-of select="name"/>" : "
<xsl:value-of select="."/>",
</xsl:template>
</xsl:stylesheet>
The element names in your XML are poorly chosen, and this has probably confused you (it certainly confused me). One would expect "field" to contain a single field, but it actually contains all of them. So change
<xsl:for-each select="//datapost[position()=1]/fields/field">
to
<xsl:for-each select="//datapost[position()=1]/fields/field/*">
or better still,
<xsl:for-each select="datapost/fields/field/*">
since the other parts of the expression are redundant verbiage.
Then you need to look at the template containing the variable
<xsl:variable name="childName" select="//datapost[position()=1]/fields/field/values/value"/>
This is selecting all the values in the document, not just the values of the current element. I'm not entirely sure what you're trying to achieve here, and it doesn't seem to be covered by your test data, but I suspect you're trying to do some kind of grouping of elements that have the same name. For that you need to use grouping facilities: xsl:for-each-group in XSLT 2.0+, Muenchian grouping in XSLT 1.0. You haven't said which XSLT version you're using, but all of this would be an awful lot easier if you used XSLT 2.0+.

structure, under certain conditions

I'm trying my hand at html but I don't know how to do it. That's my but doesnt work:(:
<xsl:template match="*[contains(local-name(), '.')]">
<xsl:element name="{translate(local-name(), '.', '_')}" namespace="{namespace-uri()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
Does anyone have an idea how to deal with it?
It seems a recursive grouping problem though the single example doesn't really spell out when to wrap and/or nest items as lists; nevertheless with XSLT 2 or 3 it could be tackled with a recursive function using for-each-group:
<?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"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="html" indent="yes" html-version="5"/>
<xsl:function name="mf:wrap" as="element()*">
<xsl:param name="elements" as="element()*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:for-each-group select="$elements" group-adjacent="boolean(self::*[matches(local-name(), '^li[' || $level || '-9]+$')])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<ul class="li{$level}">
<xsl:sequence select="mf:wrap(current-group(), $level + 1)"/>
</ul>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="*[matches(local-name(), '^li[0-9]+$')]">
<li>
<xsl:apply-templates/>
</li>
</xsl:template>
<xsl:template match="*[*[matches(local-name(), '^li[0-9]+$')]]">
<div>
<xsl:apply-templates select="mf:wrap(*, 1)"/>
</div>
</xsl:template>
<xsl:template match="uz">
<h5>
<xsl:apply-templates/>
</h5>
</xsl:template>
<xsl:template match="/">
<html>
<head>
<title>.NET XSLT Fiddle Example</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bnnZWK
Note that the list structure the above creates is a bit different, all liX items of the same X level are wrapped into a single ul class="liX" wrapper while your wanted sample at some places seems to wrap several items and at other places wrap only single items.

xpath with xsl:when test

I need of display BIG on planets with diameter higher than average diameter or SMALL without using avg function (XSLT 1.0)
I have tried to use xsl:when with a condition like diameter > sum(....) div count(nom), but it doesn't work :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/systeme_solaire">
<html lang="fr">
<head>
<title>Les planètes</title>
</head>
<body>
<xsl:apply-templates select="planete[nom!='Terre']">
<xsl:sort select ="diametre" order="descending" data-type="number" />
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="planete" >
<ul>
<p><b><xsl:value-of select="nom"/> : </b></p>
<li>Distance au soleil: <xsl:value-of select="distance"/><xsl:value-of select="distance/#unit"/></li>
<li>Masse: <xsl:value-of select="masse"/> <xsl:value-of select="masse/#unit"/></li>
<li>
<xsl:choose>
<xsl:when test="diametre > ((sum(diametre[unit='diamètre terrestre']*sum(diametre[unit='km']))+sum(diametre[unit='km'])) div count(nom))">
BIG
</xsl:when>
<xsl:otherwise>
SMALL
</xsl:otherwise>
</xsl:choose> Diamètre: <xsl:value-of select="diametre"/> <xsl:value-of select="diametre/#unit"/></li>
<xsl:if test="satellite>0"><li>Nombre de satellites: <xsl:value-of select="satellite"/></li></xsl:if>
</ul>
</xsl:template>
</xsl:stylesheet>
XML file used (diameter of planets differents from earth are defined according to earth diameter ratio) :
<?xml version="1.0" encoding="ISO-8859-1" ?>
<systeme_solaire>
<planete type="tellurique">
<nom>Vénus</nom>
<distance unit="UA" >0.7</distance>
<masse unit="masse terrestre">0.8</masse>
<diametre unit="diamètre terrestre">0.9</diametre>
</planete>
<planete type="tellurique">
<nom>Terre</nom>
<distance unit="km" >149600000</distance>
<masse unit="kg">5.98e24</masse>
<diametre unit="km">12756</diametre>
<satellite>1</satellite>
</planete>
<planete type="tellurique">
<nom>Mars</nom>
<distance unit="UA" >1.5</distance>
<masse unit="masse terrestre">0.1</masse>
<diametre unit="diamètre terrestre">0.5</diametre>
<satellite>2</satellite>
</planete>
</systeme_solaire>
You have two problems :
First you need to calculate you average diameter outside the
template planete in order to reach all planets then pass this
average to you template
Then you xpath is incorrect : wrong parenthesis, unit is an attribute so you need to use #. You need something like this :
((sum(//diametre[#unit='diamètre
terrestre'])*//diametre[#unit='km'])+//diametre[#unit='km']) div
count(//nom)
Edit : You also need to calculate the actual diameter of your current planet base on earth diameter, you can do this by adding another parameter <xsl:with-param name="terre" select="//diametre[#unit='km']"/> and using it <xsl:when test="diametre*$terre > $avg">
I've updated you XSLT like so :
Solution 1
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/systeme_solaire">
<html lang="fr">
<head>
<title>Les planètes</title>
</head>
<body>
<xsl:apply-templates select="planete[nom!='Terre']">
<xsl:sort select ="diametre" order="descending" data-type="number" />
<xsl:with-param name="avg" select="((sum(//diametre[#unit='diamètre terrestre'])*//diametre[#unit='km'])+//diametre[#unit='km']) div count(//nom)"/>
<xsl:with-param name="terre" select="//diametre[#unit='km']"/>
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="planete" >
<xsl:param name="avg"/>
<xsl:param name="terre"/>
<ul>
<p><b><xsl:value-of select="nom"/> : </b></p>
<li>Distance au soleil: <xsl:value-of select="distance"/><xsl:value-of select="distance/#unit"/></li>
<li>Masse: <xsl:value-of select="masse"/> <xsl:value-of select="masse/#unit"/></li>
<li>
<xsl:choose>
<xsl:when test="diametre*$terre > $avg">
BIG
</xsl:when>
<xsl:otherwise>
SMALL
</xsl:otherwise>
</xsl:choose> Diamètre: <xsl:value-of select="diametre"/> <xsl:value-of select="diametre/#unit"/></li>
<xsl:if test="satellite>0"><li>Nombre de satellites: <xsl:value-of select="satellite"/></li></xsl:if>
</ul>
</xsl:template>
</xsl:stylesheet>
Edit to add michael suggestion (calculate the average diameter as the ratio) :
Solution 2
<xsl:apply-templates select="planete[nom!='Terre']">
<xsl:sort select ="diametre" order="descending" data-type="number" />
<xsl:with-param name="avg" select="(sum(//diametre[#unit='diamètre terrestre'])+1) div count(//nom)"/>
</xsl:apply-templates>
...
<xsl:template match="planete" >
<xsl:param name="avg"/>
...
<xsl:when test="diametre > $avg">
...

Saxon XSLT 2.0 casting error the doesnt make sense for me

I am trying to perform simple transformation.
The error reported by the proccessor is: An attribute node (name) cannot be created after the children of the containing element. The error is pointing to this line <xsl:apply-templates select="#*|node()"/> into the last template.
EDIT#1:
This is the input:
<root>
<processor>../../library/saxon-he-9-6-0-7j/saxon9he.jar</processor>
<test-case name="test-case-1">
<object-under-test category="template" name="text-align"/>
<parameters>
<input name="text">text</input>
<input name="min-lenght">8</input>
<input name="align">left</input>
<output name="result"/>
</parameters>
<criteria>
<criterion class="equal" to="'text '"/>
</criteria>
</test-case>
</root>
This is the XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslAlt="dummy" version="1.0">
<xsl:namespace-alias stylesheet-prefix="xslAlt" result-prefix="xsl"/>
<!--~~~-->
<xsl:template match="root">
<xslAlt:stylesheet version="1.0">
<xslAlt:output method="xml"/>
<xslAlt:include href="../../../product/templates.xsl"/>
<xslAlt:template name="root" match="/">
<xsl:apply-templates select="test-case"/>
</xslAlt:template>
</xslAlt:stylesheet>
</xsl:template>
<!--~~-->
<xsl:template match="test-case">
<test-case name="{concat('test-case-', string(position()))}">
<xsl:variable name="test-case-return" select="concat('test-case-',string(position()),'-return')"/>
<xslAlt:variable name="{$test-case-return}">
<xslAlt:call-template name="{object-under-test/#name}">
<xsl:for-each select="parameters/input">
<xsl:choose>
<xsl:when test="string(number()) = 'NaN'">
<xslAlt:with-param name="{#name}" select="'{.}'"/>
</xsl:when>
<xsl:otherwise>
<xslAlt:with-param name="{#name}" select="{.}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xslAlt:call-template>
</xslAlt:variable>
<!--~~-->
<xslAlt:variable name="result" select="translate(${$test-case-return},{string('$space')},{string('$nbsp')})"/>
<xsl:apply-templates select="#*|node()"/>
</test-case>
</xsl:template>
<!--~~-->
<xsl:template match="criteria">
<criteria>
<xslAlt:variable name="test-result">
<xslAlt:choose>
<xslAlt:when test="{translate(criterion/#to,' ',' ')} = $result">TEST-PASSED</xslAlt:when>
<xslAlt:otherwise>TEST-FAILED</xslAlt:otherwise>
</xslAlt:choose>
</xslAlt:variable>
<xsl:apply-templates select="#*|node()"/>
</criteria>
</xsl:template>
<!--~~-->
<xsl:template match="#to">
<xsl:attribute name="to">
<xsl:value-of select="translate(.,' ',' ')"/>
<!--translate replace-->
</xsl:attribute>
<xsl:attribute name="result">{<xsl:value-of select="string('$test-result')"/></xsl:attribute>
</xsl:template>
<!--~~-->
<xsl:template match="parameters/output">
<output name="{#name}">
<xslAlt:value-of select="$result"/>
</output>
</xsl:template>
<!--~~-->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/><!--THE ERROR IS POINTING HERE!!!-->
</xsl:copy>
</xsl:template>
<!--~~-->
</xsl:stylesheet>
The error reported by the proccessor is: An attribute node (name)
cannot be created after the children of the containing element.
This is not a "casting error". What it says is that you are trying to create an attribute after some children have already been created.
AFAICT, it happens here:
<xsl:template match="test-case">
<test-case name="{concat('test-case-', string(position()))}">
<!--here child nodes are being created!!! ~-->
<xsl:apply-templates select="#*|node()"/>
</test-case>
</xsl:template>
If you change (line #35):
<xsl:apply-templates select="#*|node()"/>
to:
<xsl:apply-templates select="node()"/>
the error will go away, because now it will stop trying to copy the existing attributes. You would not want this to happen anyway, since it would overwrite the name attribute you have created yourself.
Caveat:
I have not examined your stylesheet in-depth.

Randomly get nodes from XML using XSL

I am making a website using a CMS, in the home page I need to get some Pictures and discriptions randomly,, I need each time i refresh the page to get new nodes.
This is how I am calling the nodes in XSL:
<xsl:for-each select ="TSPRoot/Blocks/Block[#ID=134]/Articles/Article[position() < 4]">
<div class="layout-08">
<a class="title" href="detailed.aspx?id={ID}">
<xsl:choose >
<xsl:when test ="string-length(ImageURL) > 0">
<img src="_handlers/resizeimage.ashx?src={ImageURL}&height=149&width=206" title="{Headline}"/>
</xsl:when>
<xsl:otherwise>
<img src="_frontend/ux/images/en_logo.png" width="206" height="149" title="{Headline}"/>
</xsl:otherwise>
</xsl:choose>
</a>
<div class="bg">
<xsl:value-of select ="Headline"/>
</div>
</div>
</xsl:for-each>
This gets the newest (3) nodes, I need random (3) nodes each time i refresh.
I would give you only some ideas of possible solution.
Technically XSLT doesn't offer a method random numbers. However, this problem can be solved in at least three different ways:
Generate pseudo-random number in Java/C#/PHP code before executing XSLT template and pass the generated number as an XSLT parameter.
Use the extension function: http://exslt.org/random/functions/random-sequence/index.html
Use current-dateTime() (XSLT 2.0) function as a seed for your random number procedure. With a couple of string and math operations you could convert it into desired quasi-random number. I think that for your needs this solution is enough.
EDIT - Ad 3.
I have made some experiments with this idea. And I have created some sample code. I know that this code generate a sequence of really pseudo random numbers, but for simple web page like in the question it might be enough.
The following XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:rnd="http://whatever.org/random">
<xsl:output indent="yes" />
<xsl:function name="rnd:pseudoRandomInternal">
<xsl:param name="numSeed" as="xs:decimal"/>
<xsl:variable name="squareSeed" select="xs:string($numSeed*$numSeed)"/>
<xsl:variable name="oneDivSeed" select="substring($squareSeed, 3, string-length($squareSeed) - 3)"/>
<xsl:variable name="cc" select="if (string-length($oneDivSeed) > 6) then
substring($oneDivSeed, string-length($oneDivSeed) - 6) else
$oneDivSeed"/>
<xsl:value-of select="xs:decimal($cc)"/>
</xsl:function>
<xsl:function name="rnd:pseudoRandom" as="xs:decimal*">
<xsl:param name="range" as="xs:integer"/>
<xsl:choose>
<xsl:when test="$range = 1">
<xsl:variable name="seed" select="substring(xs:string(current-time()), 1, 12)" as="xs:string"/>
<xsl:variable name="numSeed" select="xs:decimal(translate($seed, ':.+-', ''))"/>
<xsl:sequence select="(rnd:pseudoRandomInternal($numSeed))" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="subSequence" select="rnd:pseudoRandom($range - 1)"/>
<xsl:variable name="newValue" select="rnd:pseudoRandomInternal($subSequence[1])" as="xs:decimal*"/>
<xsl:sequence select="($newValue, $subSequence)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="/">
<xsl:variable name="rr" select="rnd:pseudoRandom(10)" as="xs:decimal*"/>
<output>
<xsl:for-each select="$rr">
<item>
<xsl:value-of select=". * 3 mod 11" />
</item>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
generated the following output:
<output>
<item>10</item>
<item>9</item>
<item>6</item>
<item>9</item>
<item>9</item>
<item>10</item>
<item>3</item>
<item>5</item>
<item>9</item>
<item>4</item>
</output>