Random Items in XSLT - xslt

I'm customizing a Google Search appliance, which uses XSLT to present results to the user. Our design calls for one of several images to be included randomly on the results page. Is there a way to use randomness in XSLT? (Pseudo-randomness is just fine for this application.)
Calling random templates would be fine, as would just being able to generate a random number and branch based on that.

You can generate in pure XSLT sequences of random numbers and also random permutations of the numbers in [1 .. N].
Just use the FXSL library (written in pure XSLT) for this.
This article explains the templates to use and has complete examples:
"Casting the Dice with FXSL: Random Number Generation Functions in XSLT".

Depending on your platform XSL allows inject of user code like C#. I don't recommend this. Better, I would have your XSL accept a parameter and whatever is generating your XML payload or XSLT and can also generate the random number, setting the parameter. I've done this exactly using this approach except the data came from Bing, not G.

If you use a Java based XSLT engine, this will allow you to make calls to any static method within the Java libraries, such as java.lang.Math.random(). Here is the syntax...
<?xml version='1.0'?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="java.lang.Math"
version='1.1'>
<xsl:template match="/">
<xsl:variable name="myRandom" select="math:random()"/>
<xsl:value-of select="$myRandom"/>
</xsl:template>
</xsl:stylesheet>

If you are not averse to including libraries, there are many available such as random:random-sequence from EXSLT

If you are doing this for anything Microsoft, I found that using XSLT's function ddwrt:Random works.
I use the following to create the random number
<xsl:variable name="RowCount" select="count($Rows)" />
<xsl:variable name="RandomNumber" select="ddwrt:Random(1, $RowCount)" />
and the following to present
<xsl:for-each select="$Rows[position() = $RandomNumber]">
<xsl:value-of select="#Title" /></xsl:for-each>

Related

xslt - how to convert iso Alpha 2 code to Country name

I have an xml which has the two char alpha code which need to be converted into a fully qualified country name. For example, DE should become Germany. I understand that xslt templates can be used to keep the list within xslt to fetch the relevant names however I am not happy with that design and am looking for an xslt function (or something similar) which will avoid any future maintenance of the mapping. please let me know your thought on this. thanks.
Perhaps call a web service e.g. with XSLT 3 you can process the JSON returned from most REST APIs
<xsl:template match="code">
<country>{json-doc('https://restcountries.com/v2/alpha/' || .)?name}</country>
</xsl:template>
For XSLT 2 you could use a service returning an XML format e.g.
<xsl:template match="code">
<country>
<xsl:value-of
select="doc(concat('https://api.worldbank.org/v2/country/', .))/countries/country/name"
xpath-default-namespace="http://www.worldbank.org"/>
</country>
</xsl:template>

Replacing NameSpaces one from Another using XSLT

I have one XML input file in which I am getting some Namespaces which I wanted to replace from another using XSLT. Actually I am new XSLT so not able to find proper solution. below is the XML input payload and Output payload which I want. Could anyone help me on that.
Input Payload:
<ns:createOrderResponse xmlns:ns="http://services.oms.ecom.ecc.com"><ns:return type="com.ecc.ecom.oms.beans.xsd.CreateOrderResponse"><ns:omsGeneratedOrderId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" /><ns:responseCode>99</ns:responseCode><ns:responseDesc>INVALID ORDER</ns:responseDesc><ns:sellerSiteId>10196</ns:sellerSiteId><ns:serverProcElapsedTime>8</ns:serverProcElapsedTime><ns:siteGeneratedOrderId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" /><ns:subResponse><responseCode xmlns="http://beans.oms.ecom.ecc.com/xsd">1144</responseCode><responseDescription xmlns="http://beans.oms.ecom.ecc.com/xsd">Order Total mismatch</responseDescription></ns:subResponse><ns:subResponse><responseCode xmlns="http://beans.oms.ecom.ecc.com/xsd">1147</responseCode><responseDescription xmlns="http://beans.oms.ecom.ecc.com/xsd">Order Grand Total and sum of OrderItem Grand Total mismatch</responseDescription></ns:subResponse><ns:transactionNumber>0717299145</ns:transactionNumber></ns:return></ns:createOrderResponse>
Desired Output:
<ns:createOrderResponse xmlns:ns="http://services.oms.ecom.ecc.com"><ns:return type="com.ecc.ecom.oms.beans.xsd.CreateOrderResponse"><ns:omsGeneratedOrderId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" /><ns:responseCode>99</ns:responseCode><ns:responseDesc>INVALID ORDER</ns:responseDesc><ns:sellerSiteId>10196</ns:sellerSiteId><ns:serverProcElapsedTime>8</ns:serverProcElapsedTime><ns:siteGeneratedOrderId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true" /><ns:subResponse><responseCode xmlns="http://services.oms.ecom.ecc.com">1144</responseCode><responseDescription xmlns="http://services.oms.ecom.ecc.com">Order Total mismatch</responseDescription></ns:subResponse><ns:subResponse><responseCode xmlns="http://services.oms.ecom.ecc.com">1147</responseCode><responseDescription xmlns="http://services.oms.ecom.ecc.com">Order Grand Total and sum of OrderItem Grand Total mismatch</responseDescription></ns:subResponse><ns:transactionNumber>0717299145</ns:transactionNumber></ns:return></ns:createOrderResponse>
Basically I wanted to replace:
Namespace :http://beans.oms.ecom.shc.com/xsd
from
Namespace :http://services.oms.ecom.ecc.com
CAVEAT: The following is untested but demonstrates the ideas.
If you know which specific elements and attributes to expect, that's relatively easy -- it's the same as any other "when you see this element, output this other element instead" template, recursing to cover the whole document. Start with the identity stylesheet and add appropriate templates of the form:
<xsl:template match="wrongnamespace:fred"
xmlns:wrongnamespace="http://services.oms.ecom.ecc.com">
<rightnamespace:fred xmlns:rightnamespace="http://beans.oms.ecom.shc.com/xsd">
<xsl:apply-templates/>
</rightnamespace:fred>
</xsl:template>
and (for attributes)
<xsl:template match="#wrongnamespace:george"
xmlns:wrongnamespace="http://services.oms.ecom.ecc.com">
<xsl:attribute name="ns:george" value="."
namespace="http://beans.oms.ecom.shc.com/xsd"/>
</xsl:template>
... and so on.
If you don't know what elements or attributes to expect, this becomes uglier. In XSLT 1.0 and XPath 1.0 there's no easy way to say "any element node" (though I believe that was fixed in the 2.0 version of these specs). Sticking with 1.0 because it's more widely supported, the easiest solution is to use priorities: have a higher-priority template that catches the attributes, then have another template which handles other namespaced nodes (which would therefore be elements).
<xsl:template match="#*[namespace-uri(.)="http://services.oms.ecom.ecc.com"
priority="1">
<xsl:attribute name="concat('ns:',localname(.))" value="."
namespace="http://beans.oms.ecom.shc.com/xsd"/>
</xsl:template>
<xsl:template match="node()[namespace-uri(.)="http://services.oms.ecom.ecc.com"
priority="1">
<xsl:element name="concat('ns:',localname(.))"
namespace="http://beans.oms.ecom.shc.com/xsd">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
Again, adding these to the canonical identity stylesheet will do what you need; it'll catch and change these two cases while copying everything else unaltered.
CAVEAT: THIS WILL NOT CHANGE URIS APPEARING IN STRINGS. You could add a template which looks for text() nodes containing the old URI and outputs the new one instead; I'm leaving that as an exercise for the reader.
CAVEAT: THIS WILL NOT CHANGE NAMESPACE NODES. You may wind up with leftover declarations of the old prefix and namespace. Another template could be added to clean that up; this too is left as an exercise for the reader.
That should give you enough to get you started. Have fun. Programming in XSLT does require learning to think in terms of rules and replacements rather than procedures, but when you get used to that it can be a very expressive tool.
(Standard plug: If you want an off-the-shelf tested solution, you can probably find one on Dave Pawson's XSL FAQ website. That's a resource you really need to be aware of if working in XSLT; it'll save you a lot of work, and it has some solutions that are very clever and very non-obvious.)

Cannot find external method "..." (must be public) XSLT

I'm trying to do some operation on dates using XSL. I found a tutorial on IBM developerWorks with this script.
<?xml version="1.0" encoding="utf-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times"
version="1.0"
>
<xsl:output method="html"/>
<!-- B -->
<xsl:variable name="now" select="date:date-time()"/>
<xsl:template match="/">
<!-- The rest of the Web site HTML material would go here -->
<xsl:call-template name="date-section"/>
</xsl:template>
<xsl:template name="date-section">
<p>This page was loaded at <xsl:text/>
<!-- C -->
<xsl:value-of select="concat(date:hour-in-day($now), ':',
date:minute-in-hour($now), ':',
date:second-in-minute($now))"/>
<xsl:text> on </xsl:text>
<xsl:value-of select="concat(date:day-in-month($now), ' ',
date:month-name($now), ' ',
date:year($now))"/>
</p>
<p>
<!-- D -->
<xsl:variable name="days-elapsed"
select="concat('-P',date:day-in-month($now),'D')"/>
<xsl:variable name="one-month-hence"
select="date:add($now, 'P1M')"/>
<xsl:variable name="next-month-start"
select="date:add($one-month-hence, $days-elapsed)"/>
<xsl:variable name="seconds"
select="date:seconds(
date:difference($now, $next-month-start)
)"/>
<xsl:text>The next month starts in </xsl:text>
<xsl:value-of select="$seconds div (3600*24)"/>
<xsl:text> days</xsl:text>
</p>
</xsl:template>
I get the following errors:
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.add' (must be public).
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.difference' (must be public).
[ERROR]: Cannot find external method 'com.sun.org.apache.xalan.internal.lib.ExsltDatetime.seconds' (must be public).
[ERROR]: Cannot convert data-type 'void' to 'real'.
[FATAL]: Could not compile stylesheet
Any ideas how to fix it. I'm using IntelliJ to run the xsl.
Ultimately I want to to be able to add days to a date. For some reason when I try to use xsl 2 date functions it just tells me they do not exist(And yes I do change the header to version 2 :) ). So I'm trying to make it work with a 3rd party library. The date-time() function from the http://exslt.org/dates-and-times namespace is working but I can't seam to be able to call other functions form that namespace.
the question is a bit old but the answers were, IMHO, not quite correct and my answer might help some other people stumbling upon this thread (as I just did):
The functions used are XSLT 1.0 extension functions, there are different modules (e.g. dates-and-times), each with mandatory and optional functions (and elements). See EXSLT
The issue here is that XALAN does support the module dates-and-times, but not all optional functions.
As suggested, you can surely try to find a "better" XSLT-Processor, but it might require some work to integrate. I personally use on the Linux command line xsltproc, which supports more of the dates-and-times module (try xsltproc --dumpextensions | sort for an overview).
Alternatively, you could try the approach describing how to get current date and time in XSLT 1.0. I haven't tried it myself yet (as said, xsltproc is a good enough workaround), but using a similar approach you should be able to implement what you need without having to touch your tool/java code.
For sake of completeness, my tool with the same problem is Freeplane from freeplane.org, which also uses XALAN (I guess that it's part of standard Java libraries) and fails on similar dates-and-times functions.
Hope this helps,
Eric
I don't know Xalan, so I don't know why it's failing to find these methods.
But if you want to use the XSLT 2.0 date/time functions, you need to invoke an XSLT 2.0 processor, and the obvious one for the Java environment is Saxon (current version is 9.4). Switching processor requires a little bit more than changing the version number: for example, if you are running it from the command line then you will need to use a different command.

EXSLT func:return problems in xsl:for-each "loop" and func:function

My problem:
I have a wealth of atom RSS feed files which have many different atom entries in them and a few overlapping entries between files. I need to find and return an entry based on a URL from any one of the RSS feeds.
Technologies:
This code is being run through PHP 5.2.10's XLSTProcessor extension, which uses XSLT 1, has support for EXSLT and ability to run built in PHP functions. Saxan, Xalan or other similar solutions are not too helpful in this particular situation.
The following code is greatly simplified, but represents my situation.
rss-feed-names.xml:
<feeds>
<feed name="travel.xml"/>
<feed name="holidays.xml"/>
...
<feed name="summer.xml"/>
<feed name="sports.xml"/>
</feeds>
stylesheet.xsl
<xsl:stylesheet ...>
...
<func:function name="cozi:findPost">
<xsl:param name="post-url"/>
<xsl:variable name="blog-feeds" select="document('rss-feed-names.xml')/feeds"/>
<xsl:for-each select="$blog-feeds/feed">
<xsl:variable name="feed-file" select="document(#name)/atom:feed"/>
<xsl:variable name="feed-entry" select="$feed-file/atom:entry[atom:link[contains(#href, $post-url)]]"/>
<xsl:if test="$feed-entry">
<func:result select="$feed-entry"/><!-- this causes errors if more than one result is found -->
</xsl:if>
</xsl:for-each>
</func:function>
</xsl:stylesheet>
...
This code works just fine iff the atom entry that we're looking for appears in ONE of the files we look through. It may appear multiple times within that file, but as soon as it appears in two or more files, the code breaks because func:result was already instantiated and is being over-written, which is a no-no in XSLT.
If there is a way to ACTUALLY exit an EXSLT function or xsl:for-each "loop" (you can assign a return variable for a function, but the function continues; and for-each's are actually not loops, but more similar to function maps), that would be ideal but I have not found a way yet.
I have considered combining all feeds into one variable and removing the for-each loop altogether, but have had problems getting this to work from the beginning.
Any other possible solutions, ideas or pointers are much appreciated! The file relationship here and XML is pretty hard to change, so solutions suggesting such a change are not ideal.
Thanks in advance,
Tristan Eastburn
The general answer (as with Jim's response) is that you shouldn't put <func:result> inside <xsl:for-each>. My more specific solution for this case doesn't require you to use <xsl:for-each> or even <xsl:variable>. You can use just XPath alone:
<func:result select="(document
(document('rss-feed-names.xml')/feeds/#name)
/atom:feed
/atom:entry
[atom:link
[contains(#href, $post-url)]]
)[1]"/>
This works because document() can take a node-set. When it does, it goes and gets the document that's referenced by each node in the argument. Multiple input/multiple output.
That said, judicious use of explaining variables would be good to help readability. Still, you don't need to use <xsl:for-each>.
Since you can't force exit from the loop, you have to build the complete list and then return only the first element:
<func:function name="cozi:findPost">
<xsl:param name="post-url"/>
<xsl:variable name="blog-feeds" select="document('rss-feed-names.xml')/feeds"/>
<xsl:variable name="feedList">
<xsl:for-each select="$blog-feeds/feed">
<xsl:variable name="feed-file" select="document(#name)/atom:feed"/>
<xsl:variable name="feed-entry" select="$feed-file/atom:entry[atom:link[contains(#href, $post-url)]]"/>
<xsl:if test="$feed-entry">
<xsl:value-of select="$feed-entry"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<func:result select="$feedList[1]"/>
</func:function>
Handling of the empty-list condition is left as an exercise :-)

Varying xpath-default-namespace in XML source files

I have a set of XML files that I am processing with an XSL transform. They have a default namespace, so my XSL transform must contain the declaration:
xpath-default-namespace="urn:CZ-RVV-IS-VaV-XML-NS:data-1.2.2"
The problem is that this value changes from time to time, and my transform suddenly stops working, until I look at an example from the new file, extract this namespace ID and put it in the transform, whereby the transform stops working for old files. Is there a way to pass this as a parameter, or set it somehow at runtime? I have tried the parameter syntaxes that I looked up in various tutorials, but none have worked for this particular use.
I have searched all sorts of forums and found references to namespace-agnostic coding of XSL, but not figured out how to do it. Ian Williams' book "XSLT and Xpath" states that the default namespace must be declared, or you get nothing in the output stream, which is how it has worked for me. But I really don't want to have to change this by hand regularly, I want to give the user something that will work, without needing constant attention from me.
The only 100% reliable way I have invented so far is to use a standard programming language to open both the XML source and XSL transform as text files, extract the URI from the XML source, paste it into the XSL transform, close both files and then, finally run the actual transform. This works, but is incredibly dorky, at least to my taste. How can I better deal with changing default namespaces?
Pete
The value of xpath-default-namespace must be a static URI, so you'll have to pre-process the stylesheet if you want it to vary. One way to do that would be to use XSLT. Apply the following meta-stylesheet to your primary stylesheet each time, and then invoke the pre-processed result instead.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Pass in the new namespace URI as a stylesheet parameter -->
<xsl:param name="new-uri" required="yes"/>
<!-- By default, copy everything as is -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- But update the value of #xpath-default-namespace -->
<xsl:template match="#xpath-default-namespace">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}">
<xsl:value-of select="$new-uri"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is a bit of a strange use case though, because namespaces weren't really designed to be so dynamic. They were designed to qualify names, i.e. make up part of a name. When you look at it that way, dynamic namespaces don't make a lot of sense. Imagine a database whose table and field names arbitrarily changed every once in a while, forcing you to rewrite all your SQL scripts to keep up with the changes. That's what this is akin to.
Have you tried defining a stylesheet parameter <xsl:param name="xpdn"/> and using it in the stylesheet declaration or top level template declaration as in
<xsl:template match="...." xpath-default-namespace="$xpdn">
I can't find anything in the spec that says this won't work (but I'm not in a position to try it just now).