Kim Andersen
Kim Andersen

Reputation: 1915

Randomly choose a node in XSLT

I have a question about some sort af random function in XSLT.

I have an XML-file that very simplified look similar to this:

<node id="1198">
  <node id="1201">
    <data alias="name">Flemming</data>
    <data alias="picture">1200</data>
  </node>
  <node id="1207">
    <data alias="name">John</data>
    <data alias="picture">1205</data>
  </node>
  <node id="1208">
    <data alias="name">Michael</data>
    <data alias="picture">1206</data>
  </node>
</node>

I would like to have some XSLT, that ramdomly took one of the nodes id's and put it into a variable called "choosenNode". Like this, if the node with the ID of 1207 was the selected one:

<xsl:variable name="choosenNode" value="1207" />

How can i do this? Is there a random-function in XSLT? By the way, I would like the variable to be refreshed on every page where the XSLT is included.

And I work in Umbraco CMS, if that helps you guys.

Thanks, -Kim

Upvotes: 7

Views: 5382

Answers (5)

Darren Weber
Darren Weber

Reputation: 1674

The following assumes the XSLT processor supports EXSLT extensions (e.g., xsltproc).

This will return the content of the randomly selected "node" (it must be a child of a "node", i.e. a "node/node" element).

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:math="http://exslt.org/math"
  extension-element-prefixes="math" >

<xsl:template match="/">
  <xsl:apply-templates/>
</xsl:template>

<xsl:template match="node">
  <xsl:variable name='selected'>
    <xsl:value-of select="ceiling(math:random() * count(node))"/>
  </xsl:variable>
  <xsl:copy-of select="node[position() = $selected]"/>
</xsl:template>
</xsl:stylesheet>

This might be a useful snippet to process the content of the selected node:

<xsl:variable name="randomNode" select="node[position() = $selectNode]"/>
<id><xsl:value-of select="$randomNode/@id"/></id>
<name><xsl:value-of select="$randomNode/data[@alias='name']"/></name>
<picture><xsl:value-of select="$randomNode/data[@alias='picture']"/></picture>

Note that the above does not return the xslt definition of the variable, it uses that variable to copy the selected node.

To set the 'value' attribute of an xsl:variable element, try an attribute template like:

<xsl:variable name='chosenNode' value='{node[position() = $selected]/@id}'/>

Upvotes: 0

Darren Weber
Darren Weber

Reputation: 1674

This solution works in a shell script that uses xsltproc and text utilities.

RandomElement=$(xsltproc style.xsl file.xml | tr ' ' '\n' | sort -uR | head -n 1)

It's assumed that the style.xsl file will select the required element set and return it's values, one per line in the output text file. The tr command should put each element onto a separate line. The sort -uR should produce a unique, random list of the elements selected by the style.xsl style sheet commands. The head -n 1 then pulls out the first line of the unique, random list.

Upvotes: 0

Tim
Tim

Reputation: 4410

In Umbraco you can do something like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:Stylesheet [ <!ENTITY nbsp "&#x00A0;"> ]>
<xsl:stylesheet 
version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library"
xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath"
exclude-result-prefixes="msxml umbraco.library Exslt.ExsltMath">

<xsl:output method="xml" omit-xml-declaration="yes"/>

<xsl:param name="currentPage"/>

<!-- This should probably be a macro parameter so you can use this elsewhere-->
<xsl:variable name="parentNode" select="1048"/>

<xsl:template match="/">

        <xsl:variable name="numberOfNodes" select="count(umbraco.library:GetXmlNodeById($parentNode)/node)"/>

        <xsl:variable name="randomPosition" select="floor(Exslt.ExsltMath:random() * $numberOfNodes) + 1"/>

        <xsl:variable name="randomNode" select="umbraco.library:GetXmlNodeById($parentNode)/node [position() = $randomPosition]"/>

        <!--
          You now have the node in the $randomNode variable
          If you just want the id then you can do an XPath query on the variable
          or you can modify the XPath above to get the property you are after rather than
          the whole node
        -->

    <xsl:value-of select="$randomNode/@nodeName" />

</xsl:template>
</xsl:stylesheet>

Hope this helps.

Tim

Upvotes: 7

Wim ten Brink
Wim ten Brink

Reputation: 26682

All you need is a random number generator. There is none in XSLT thus the random number must be provided by something outside of XSLT. You will need to call a method from an external library to do this and the implemention of this library will depend on if you're on Windows (.NET or WIN32) or Linux and the XSLT processor. XSLT can do math but it lacks a lot of date/time related functions which happens to include a random number generator.

However, XSLT does have a XPath function called generate-id() which will generate an unique ID. If you could transform this to a nuimber somehow, it might be used to create a random number, although it would be predictable and some numbers might occur more often than others. I wouldn't use it.

If you use MSXSL to process your stylesheet then you can include JavaScript to generate random numbers within the stylesheet. (Or C# script when using .NET.)

Getting the node itself is easy, once you know the number of child nodes. Just ask for the node at the random position. Something like /node/node[5] would return the 5th node.

Upvotes: 0

Kamil Szot
Kamil Szot

Reputation: 17817

Getting random number in xslt is not an easy task.

There's something that can do it but you probably has to provide seed for random generator http://fxsl.sourceforge.net/articles/Random/Casting%20the%20Dice%20with%20FXSL-htm.htm

Maybe the processor you are using to do xsl transformation has ability to extend xsl expressions with outside functions. In that case maybe you can use outside random function.

Upvotes: 0

Related Questions