Random numbers using script in XSLT

Today's post goes into how to generate random numbers in an XSL transform. This is an odd thing to do, because XSLT is usually side-effect-free; pulling random numbers from somewhere usually changes something in the generator, and the end result is that you can't run the same transform twice and get the same results. Well, that might be why you wanted random numbers in the first place, perhaps.

In any case, today's trick involves using the msxml:script element. This allows you to include a snippet of script inside a stylesheet, and as you can imagine, it's a very powerful thing - you can interact with web services, file systems, databases - whatever you may need. Of course, I must invariable insert an notion about the 'great responsibility' this entails. You should read and understand XSLT Security before implementing something along these lines.

That said, here is today's .js file, which you can run with cscript from any command prompt console.

function createDocumentFromText(xmlText, allowXsltScript) {
var result = new ActiveXObject("Msxml2.DOMDocument.6.0");
result.setProperty("AllowXsltScript", allowXsltScript);
result.async = false;
result.loadXML(xmlText);
return result;
}
// Main.
var doc = createDocumentFromText("<a><b>10</b><b>100</b></a>", false);
var xsl = createDocumentFromText(
"<xsl:stylesheet version='1.0' " +
" xmlns:xsl='https://www.w3.org/1999/XSL/Transform' " +
" xmlns:msxml='urn:schemas-microsoft-com:xslt' " +
" xmlns:myscript='https://www.example.com/myscript' " +
" exclude-result-prefixes='msxml myscript'>" +
" <xsl:output method='xml' indent='yes' />" +
" <msxml:script implements-prefix='myscript'>" +
" function rnd(nodeList) { return Math.random() * nodeList.nextNode().text ; } " +
" </msxml:script>" +
" <xsl:template match='/'>" +
" <a>" +
" <xsl:for-each select='a/b'>" +
" <b><xsl:value-of select='myscript:rnd(.)' /></b>" +
" </xsl:for-each>" +
" </a>" +
" </xsl:template>" +
"</xsl:stylesheet>", true);
WScript.Echo(doc.transformNode(xsl));
// Output:
// <?xml version="1.0"?>
// <a>
// <b>5.388293479766132</b>
// <b>29.745351611949687</b>
// </a>

Here are some things to note.

  • I'm very, very lazy, so the XML and XSL documents are embedded in the .js file. And, hey, it makes the thing self-contained!
  • createDocumentFromText, which I've used in the past, will now set a AllowXsltScript property on the document, if we are creating a document for a stylesheet. This allows us to opt-in to the scripting functionality.
  • The xsl:stylesheet element contains an exclude-result-prefixes='msxml myscript' attribute. This is necessary to avoid the namespaces propagating to the output document.
  • The msxml:script element is associated with a namespace prefix, myscript, which is later used in the XPath expression of the xsl:value element to refer to the rnd function.
  • The content of the msxml:script is plain JScript. In this case, it takes a node list, expecting it to have a single element with a numeric value. It then returns a random number between 0 and the given value.

Enjoy!