Create node set and pass as a parameter - xslt

Using XSLT 1.0, I'm trying to essentially create a small node set and then pass it as a parameter to a template, something like the following:
<xsl:call-template name="widget">
<xsl:with-param name="flags">
<items>
<item>widget.recent-posts.trim-length=100</item>
<item>widget.recent-posts.how-many=3</item>
<item>widget.recent-posts.show-excerpt</item>
</items>
</xsl:with-param>
</xsl:call-template>
The idea is that then from within the widget template I could write something like:
<xsl:value-of select="$flags/item[1]" />
Obviously I get compile errors.. how can I achieve this sort of thing?

There is a way (non-standard) in XSLT 1.0 to create temporary trees dynamically and evaluate XPath expressions on them, however this requires using the xxx:node-set() function.
Whenever nodes are dynamically created inside the body of an xsl:variable or an xsl:param, the type of that xsl:variable / xsl:param is RTF (Result Tree Fragment) and the W3 XSLT 1.0 Spec. limits severyly the kind of XPath expressions that can be evaluated against an RTF.
As a workaround, almost every XSLT 1.0 vendor has their own xxx:node-set() extension function that takes an RTF and produces a normal node-set from it.
The namespace to which the xxx prefix (or any other prefix you choose) is bound is different for different vendors. For MSXML and the two .NET XSLT processor it is: "urn:schemas-microsoft-com:xslt". The EXSLT library uses the namespace: "http://exslt.org/common". This namespace EXSLT is implemented on many XSLT 1.0 processors and it is recommended to use its xxx:node-set() extension, if possible.
Here is a quick example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext msxsl"
>
<xsl:template match="/">
<xsl:variable name="vTempRTF">
<a>
<b/>
</a>
</xsl:variable>
<xsl:copy-of select="ext:node-set($vTempRTF)/a/*"/>
</xsl:template>
</xsl:stylesheet>

Well, I managed to get around this in the following way:
First add a custom namespace to your stylesheet, e.g. xmlns:myns="http://my.ns.com"
Then define the nodeset at the top of the stylesheet:
<myns:recent-posts-flags>
<item>widget.recent-posts.trim-length=100</item>
<item>widget.recent-posts.how-many=3</item>
<item>widget.recent-posts.show-excerpt</item>
</myns:recent-posts-flags>
Then reference in the following way:
<xsl:call-template name="widget">
<xsl:with-param name="flags" select="document('')/*/myns:recent-posts-flags" />
</xsl:call-template>
This works, but it would still be ideal for me to define the node-set within the <xsl:with-param> tag itself, as in the first example I gave.. anyone think that would be possible?

Related

xsl for-each to create an iterable node list

I need to iterate over the elements of an XML file in sorted order of their language field many times. What I try is to get an iterable list of the languages as follows:
<xsl:variable name="languages">
<xsl:for-each select="elem/FIELD[#NAME='language']">
<xsl:sort select="."/>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
While I can verify with a
<xsl:value-of select="$languages"/>
that the sorting works, I cannot iterate like
<xsl:for-each select="$langauges">...</xsl:for-each>
because the XSL processor complains that select expression does not evaluate to a node set.
Edit: Not sure whether this is important, but I have
<xsl:output encoding="UTF-8"
method="xml"
media-type="text/xml"
indent="yes" />
What do I have to insert in the loop to make the result into a node set? Is this at all possible?
Given you say that
XSL processor complains that select expression does not evaluate to a node set.
I assume you're using XSLT 1.0 rather than 2.0. In XSLT 1.0 when you declare a variable with content rather than a select attribute, the resulting variable contains something called a "result tree fragment" rather than a node set. You can apply value-of and copy-of to a RTF to send it to the output but you can't navigate into it using XPath expressions.
Most XSLT processors provide some sort of extension function to convert a RTF into a real node set - msxsl for the Microsoft processor or exslt for most others.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<!-- .... -->
<xsl:variable name="languagesRTF">
<xsl:for-each select="elem/FIELD[#NAME='language']">
<xsl:sort select="."/>
<lang><xsl:value-of select="."/></lang>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="languages" select="exsl:node-set($languagesRTF)/lang" />
In XSLT 2.0 there is no distinction between result tree fragments and node sets - they're both treated as sequences - so you don't need the extension function in that version.
Try the following:
To declare variable:
<xsl:variable name="languages">
<xsl:for-each select="elem/FIELD[#NAME='language']">
<xsl:sort select="."/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
And to loop:
<xsl:for-each select="$languages/*">
You need to convert the result tree fragment in your variable into a node-set, using the EXSLT node-set() function.
XSLT 1.0 allows you to process a node-set in sorted order, but it doesn't allow you to save a sorted sequence in a variable (the data model only has sets, not sequences). The only way you can save sorted data in 1.0 is to construct a new tree containing copies of the original elements in a different order, and then use the node-set() extension to make this tree processable.
This changes in XSLT 2.0, which has a data model based on sequences. In 2.0 you can save a sorted sequence of nodes in a variable without copying the nodes into a new tree.

How do I know how many tags an xsl:variable contains?

In XSLT 1.0, if I have an <xsl:variable> declared like that:
<xsl:variable name="ListeEcheances">
<bla/><bli/>
</xsl:variable>
How do I know if it's empty? Or even better: how do I know how many tags it contains? (I know there are 2 tags here, but my real code is a little bit more complex :))
<xsl:when test="$ListeEcheances=''"> returns true (it doesn't count the tags, only the text) ;
<xsl:when test="count($ListeEcheances/*) > 0"> sadly doesn't compile.
Thank you for your help.
This is indeed incorrect and your compiler is correct in throwing an error. You can only count a node set, you cannot count a result tree fragment. What you need is transform the variable in a node-set by using an extension function.
For Saxon 6.5 this would be exsl:node-set.This works with Saxon 6.5 and any processor that supports the EXSLT node-set function (most do). EDIT: Jirka Kosek wrote down a list of node-set extensions per processor, I'm sure yours is in the list.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0" xmlns:exsl="http://exslt.org/common">
<xsl:variable name="ListeEcheances">
<bla/><bli/>
</xsl:variable>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="count(exsl:node-set($ListeEcheances)/*) > 0">
<xsl:text>Larger then zero!</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="utf-8"?>Larger then zero!
Note: if you were to use XSLT 2.0, everything is a node-set and you don't run into this awkwardness of XSLT 1.0, where result tree fragments are next to useless.
If the content of the variable is declared in the XSLT as shown in your example, rather than dynamically evalueated, you can use the document() function to parse the XSLT file(which is an XML file) and evaluate an XPath expression to count the elements in the variable:
count(document('')/*/xsl:variable[#name='ListeEcheances']/*)
Using the document function with an empty path will load the base URI of the current stylesheet.
Try <xsl:when test="count($ListeEcheances/*) > 0">
or wait - maybe you get something like
Expression must evaluate to a node-set.
count(-->$ListeEcheances<--/*) > 0
The reason is that the variable is a result tree fragment, not a node-set.
In XSLT 1.0 you will need to apply the node-set function, available in a namespace dependent on the processor.
For instance: <xsl:when test="count(msxsl:node-set($ListeEcheances/*)) > 0">
If that does not work, or if you can't discover the namespace to use, then a trick might help:
<xsl:variable name="temp" select="$ListeEcheances"/>
<xsl:when test="count($temp/*) > 0">
The reason that this works can be found in stackoverflow rtf to node-set

Best approach for combining several XSLT 1.0 passes which process the same nodes

I'm doing some very complex XSLT 1.0 transformation (currently using 8 XSLT passes). I want to combine this 8 passes without merging them in one file (this would be too complex). My solution would be using xsl:include and exsl:node-set to merge the passes and store temporary results in variables.
But I have one problem: My transformation passes copies most of the nodes and modifying only certain aspects. Therefore I need to process the same nodes in every pass, but with different xsl:template! But how do I do that? How to tell that after the first pass I want to apply templates from other XSLT stylesheet?
Very simplified example what I'm currently doing (2 XSLT passes):
Source:
<h>something here</h>
After XSLT pass 1:
<h someattribute="1">something here</h>
After XSLT pass 2:
<h someattribute="1" somemoreattribute="2">something here, and even more</h>
My current approach is to call the XSLT processor twice and saving the results temporary on disk:
xsltproc stylesheet1.xsl input.xml >temp.xml
xsltproc stylesheet2.xsl temp.xml >finalresult.xml
One possible solution would be to change each of the stylesheets to use a distinct mode. Then you could import them all to your master stylesheet and do multiple passes by applying templates using each mode in turn:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="1.0">
<xsl:import href="stylesheet1.xsl"/> <!-- assuming mode="stylesheet1" -->
<xsl:import href="stylesheet2.xsl"/> <!-- assuming mode="stylesheet2" -->
<xsl:import href="stylesheet3.xsl"/> <!-- assuming mode="stylesheet3" -->
<xsl:template match="/">
<xsl:variable name="temp1">
<xsl:apply-templates select="." mode="stylesheet1"/>
</xsl:variable>
<xsl:variable name="temp2">
<xsl:apply-templates mode="stylesheet2" select="exsl:node-set($temp1)"/>
</xsl:variable>
<xsl:apply-templates mode="stylesheet3" select="exsl:node-set($temp2)"/>
</xsl:template>
</xsl:stylesheet>
The downside is that you need to modify the original stylesheets, adding appropriate mode attributes to each xsl:templateand xsl:apply-templates. You can still make the stylesheets also work independently by adding an extra template like this in each of them:
<xsl:template match="/">
<xsl:apply-templates select="." mode="stylesheet1"/>
</xsl:template>
Why not use
<xsl:param name="iteration"/>
And pass the iteration number to the stylesheet? You can then use it like this
<xsl:if test="$iteration = 1">
...
</xsl:if>
...or in other contexts
You can set the parameter with
javax.xml.transform.Transformer.setParameter("iteration", 1);
Or with ant:
<xslt ...>
<param name="iteration" expression="1"/>
</xslt>

Automating exslt:node-set?

Not sure if this is possible, but trying set up something that doesn't make me have to type exslt:node-set when pulling values from a dynamically created node block. I am storing the entire set of nodes in a variable, and wrapping it in exslt:node-set, but why does it not work when I then try to pull from it. Is this possible?
<xsl:variable name="LANG">
<xsl:variable name="tmp">
<xsl:element name="foo">
<xsl:element name="bar">Hello</xsl:element>
</xsl:element>
</xsl:variable>
<xsl:value-of select="exslt:node-set($tmp)"/>
</xsl:variable>
<!-- Love to be able to do this -->
<xsl:value-of select="$LANG/foo/bar"/>
<!-- This does work -->
<xsl:value-of select="exslt:node-set($LANG)/foo/bar"/>
In XSLT 1.0, the variable defined as in your example are called result tree fragments (RTF) and you can only use xsl:copy-of to copy the entire fragment to the result tree or xsl:value-of to copy the entire content. Example
<xsl:copy-of select="$LANG"/>
If you want treat the variable as a temporary tree you need the node-set() extension.
The common way to deal with static tree fragments (like lookup tables) in XSLT 1.0 is to define them as children of the stylesheet root elements (using a custom namespace). Then you can use the document() function to retrieve the wanted value.
Note If you are using Saxon (v>6.5), you could simply set the stylesheet version to 1.1 and you will be able to manage the RTF without any node-set extension.
[XSLT 1.0]
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:empo="http://stackoverflow.com/users/253811/empo">
<empo:LANG>
<empo:foo>
<empo:bar>Hello</empo:bar>
</empo:foo>
</empo:LANG>
<xsl:template match="/">
<xsl:variable name="LANG" select="document('')/*/empo:LANG"/>
<xsl:value-of select="$LANG/empo:foo/empo:bar"/>
</xsl:template>
</xsl:stylesheet>

Parameters not being passed into template when using the .Net transform classes

I am using the .Net XslCompiledTranform to run some simple XSLT (see below for a simplified example).
The example XSLT is meant to do simply show the value of the parameter that is passed in to the template. The output is what I expect it to be (i.e.
<result xmlns:p1="http://www.doesnotexist.com">
<valueOfParamA>valueA</valueOfParamA>
</result>
when I use Saxon 9.0, but when I use XslCompiledTransform (XslTransform) in .net I get
<result xmlns:p1="http://www.doesnotexist.com">
<valueOfParamA></valueOfParamA>
</result>
The problem is that that the parameter value of paramA is not being passed into the template when I use the .Net classes. I completely stumped as to why. when I step through in Visual Studio, the debugger says that the template will be called with paramA='valueA' but when execution switches to the template the value of paramA is blank.
Can anyone explain why this is happening? Is this a bug in the MS implementation or (more likely) am I doing something that is forbidden in XSLT?
Any help greatly appreciated.
This is the XSLT that I am using
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:extfn="http://exslt.org/common" exclude-result-prefixes="extfn" xmlns:p1="http://www.doesnotexist.com">
<!--
Replace msxml with
xmlns:extfn="http://exslt.org/common"
xmlns:extfn="urn:schemas-microsoft-com:xslt"
-->
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="resultTreeFragment">
<p1:foo>
</p1:foo>
</xsl:variable>
<xsl:variable name="nodeset" select="extfn:node-set($resultTreeFragment)"/>
<result>
<xsl:apply-templates select="$nodeset" mode="AParticularMode">
<xsl:with-param name="paramA" select="'valueA'"/>
</xsl:apply-templates>
</result>
</xsl:template>
<xsl:template match="p1:foo" mode="AParticularMode">
<xsl:param name="paramA"/>
<valueOfParamA>
<xsl:value-of select="$paramA"/>
</valueOfParamA>
</xsl:template>
</xsl:stylesheet>
There is nothing strange -- this is the expected behavior of any XSLT 1.0 -compliant processor.
Explanation:
The $nodeset variable defined as:
<xsl:variable name="nodeset" select="extfn:node-set($resultTreeFragment)"/>
in XSLT 1.0 contains a complete xml document -- a document node, denoted in XPath 1.0 by / .
Therefore,
<xsl:apply-templates select="$nodeset" mode="AParticularMode">
<xsl:with-param name="paramA" select="'valueA'"/>
</xsl:apply-templates>
Will aplly a template matching the tree (the document node /) in the specified mode, if such template exists. In your case no such template exists. Therefore the built-in XSLT 1.0 template for / is applied (which belongs to every mode).
The text of the built-in template can be found in the spec:
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
As per spec:
"There is also a built-in template rule for each mode, which allows recursive processing to continue in the same mode in the absence of a successful pattern match by an explicit template rule in the stylesheet. This template rule applies to both element nodes and the root node. The following shows the equivalent of the built-in template rule for mode m.
<xsl:template match="*|/" mode="m">
<xsl:apply-templates mode="m"/>
</xsl:template>
"
Of course, the built-in template doesn't know anything about your parameter $paramA and it doesn't pass it down to the applied templates.
Thus, finally, your template matching p1:foo" in mode="AParticularMode" is selected for processing. Nothing is passed as value for the parameter, so it has no value -- thus the <xsl:value-of> doesn't produce even a single character or node.
To correct this problem, simply add a template matching / and in mode "AParticularMode":
<xsl:template match="/" mode="AParticularMode">
<xsl:param name="paramA"/>
<xsl:apply-templates mode="AParticularMode">
<xsl:with-param name="paramA" select="$paramA"/>
</xsl:apply-templates>
</xsl:template>
and now you get the desired result.
In XSLT 2.0 (Saxon 9) you observe different behavior, because the built-in templates in XSLT 2.0 by definition retransmit all parameters with which they were applied -- see the XSLT 2.0 Spec :
"If the built-in rule was invoked with parameters, those parameters
are passed on in the implicit xsl:apply-templates instruction."
Answer for your question,
Why my first attempt failed?
As you are using node-set() comfortably in your code I guess you might be well aware of Result Tree Fragment. If not, then go through this link.
Well. By taking advantage of RTF[Result tree fragment] you are able to treat "foo" as a node.
The variable $nodeset has stored the tree-structure of the node, so that you can treat it's value as node-set, where as variable $nodeset is still a variable. If you want to apply-template then apply on, it's child nodes[precisely elements] appearing as it's value,
Instead of * you could have used,
<xsl:apply-templates select="$nodeset/p1:foo" mode="AParticularMode">
This is more precise,
Well after more experimenting I found that altering the apply-templates to
<xsl:apply-templates select="$nodeset/*" mode="AParticularMode">
<xsl:with-param name="paramA" select="'valueA'"/>
</xsl:apply-templates>
(note the select="$nodeset/*" instead of select="nodeset") made it work as I wanted it to in .Net and Saxon.
I would however still be grateful if someone can explain why my first attempt failed.