Saxon error "XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type xs:string" - xslt

I have an XSL program which in turn generates an XSL program, which depending on the input might look like this:
<xsl:variable name="patterns"/> <!--empty in this particular case-->
<xsl:template name="token">
<xsl:for-each select="$patterns/pattern">
...
When I then run the generated stylesheet, Saxon, bless its heart, is apparently doing some kind of static analysis and complains:
XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type xs:string
and won't even compile the stylesheet.
My workaround was to generate a dummy element in the $patterns nodeset, but is there any cleaner approach here, or way to suppress the compile error?

According to http://www.w3.org/TR/xslt20/#variable-values, "If the variable-binding element has empty content and has neither a select attribute nor an as attribute, then the supplied value of the variable is a zero-length string.".
So you need to change that, for instance by doing <xsl:variable name="patterns" select="()"/> to bind an empty sequence as the variable value.

In XSLT 1.0 (the same would work also with XSLT 2.0) use:
<xsl:variable name="patterns" select="/.."/>
This provides to the XSLT processor the information, necessary to conclude that the type of the $patterns variable is node-set.

Related

How to use an XSL variable as a condition to evaluate XPath?

I have faced an issue when using a variable as a condition for XPath evaluation. I have the following template which works fine:
<xsl:template name="typeReasonDic">
<xsl:variable name="dic" select="$schema//xs:simpleType[#name = 'type_reason_et']"/>
<!-- do something with the variable -->
</xsl:template>
However, when I change it to look like this:
<xsl:template name="typeReasonDic">
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
<xsl:variable name="dic" select="$schema//xs:simpleType[$choose_dic]"/>
<!-- do something with the variable -->
</xsl:template>
it fails to find the desired node.
What I wish to get is a template with a default value for $choose_dic which can be overriden where necessary.
What am I missing here?
UPD: there is this link I found with the description of what I'm trying to do, but it doesn't seem to work for me.
You can't do this directly in XSLT 1.0 or 2.0 without an extension function. The problem is that with
<xsl:template name="typeReasonDic">
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
<xsl:variable name="dic" select="$schema//xs:simpleType[$choose_dic]"/>
<!-- do something with the variable -->
</xsl:template>
the <xsl:param> will evaluate its select expression a single time in the current context and store the true/false result of this evaluation in the $choose_dic variable. The <xsl:variable> will therefore select either all xs:simpleType elements under the $schema (if $choose_dic is true) or none of them (if $choose_dic) is false. This is very different from
<xsl:variable name="dic" select="$schema//xs:simpleType[#name = 'type_reason_et']"/>
which will evaluate #name = 'type_reason_et' repeatedly, in the context of each xsl:simpleType, and select those elements for which the expression evaluated to true.
If you store the XPath expression as a string you can use an extension function such as dyn:evaluate or the XSLT 3.0 xsl:evaluate element if you're using Saxon.
By doing
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
the XSL engine will try to evaluate "#name = 'type_reason_et'" as an XPath expression, and will assign the RESULT to your variable.
You should use the following variable declaration instead:
<xsl:param name="choose_dic">#name = 'type_reason_et'</xsl:param>
This is the default value, but you can override it when you call your template by using xsl:with-param.
XSLT is not a macro language where you might be able to concatenate your code at run-time from strings and then evaluate them dynamically. So in general for your purpose you would need an extension function to evaluate an XPath expression stored in a string or you need to look into a new XSLT 3.0 features like http://www.saxonica.com/documentation/xsl-elements/evaluate.xml.
What is possible in the scope of XSLT 1.0 or 2.0 is doing e.g.
<xsl:param name="p1" select="'foo'"/>
<xsl:variable name="v1" select="//bar[#att = $p1]"/>
where the param holds a value you compare to other value, for instance those in a node like an attribute or element node.

in XSLT, what does <xsl:if test=".[foo or #bar]"> mean and what is the cross-browser solution?

XSL:
<xsl:if test=".[foo or #bar]">
something
</xsl:if>
is it testing that if target is with 'foo' tag name or has a 'bar' attribute?
it works only in IE, I'm wondering if there is an error. What is the equivalent sentence but works cross-browser?
The syntax .[x] is not allowed in XSLT 1.0. It was allowed in early drafts of XSLT 1.0, and I suspect it was absent from the final version as a result of an oversight rather than being a deliberate design decision (and as already remarked, it was reinstated in XSLT 2.0). Because IE first shipped its XSLT processor before the 1.0 spec was finalized, it's possible that they support this syntax for backwards compatibility with those early drafts.
<xsl:if test=".[foo or #bar]">
something
</xsl:if>
In XSLT 1.0 this is syntactically invalid and produces an error.
In XSLT 2.0 this is equivalent to:
<xsl:if test="foo or #bar">
something
</xsl:if>
The expression:
foo or #bar
evaluates to true() exactly when the context node (current node) has a child element named foo or the context node has an attribute named bar (or both).
Otherwise this expression evaluates to false().
Therefore, the above code snippet means: If either of these conditions is true: the current node has a child element named foo or the current node has an attribute named bar -- then output the string "something"
It tests whether the current node has a child element named foo or has an attribute called bar. That is, if you change it to
<xsl:if test="foo or #bar">
The syntax you use is rejected in my xsl parser: Invalid XPath expression
Unexpected token - "[foo or #bar]"
The one I use should work cross-browser.
If you wish to select nodes that have name foo or a bar attribute, then use the following:
<xsl:if test="name()='foo' or #bar">

How to know variable has value or not in XSLT

I am creating XSLT file.
I have one variable which take value from XML file.But it may happen that there is no reference in xml for the value and at that time XSL variable will return False/None(don't know).I want keep condition like,If there is no value for the variable use the default one.
How to do that ?
With the few details given in the question, the simplest test you can do is:
<xsl:if test="$var">
...
</xsl:if>
Or you might use xsl:choose if you want to provide output for the else-case:
<xsl:choose>
<xsl:when test="not($var)"> <!-- parameter has not been supplied -->
</xsl:when>
<xsl:otherwise> <!--parameter has been supplied --> </xsl:otherwise>
</xsl:choose>
The second example will also handle the case correctly that the variable or parameter has not been supplied with an actual value, i.e. it equals the empty string. This works because not('') returns true.
You haven't explained what you mean by "has no value". Here is a generic solution:
not($v) and not(string($v))
This expression evaluates to true() iff $v "has no value".
Both conditions need to be met, because a string $v defined as '0' has a value, but not($v) is true().
In XSLT 1.0 using a default can be achieved in different ways if the "value" is a node-set or if the value is a scalar (such as a string, a number or a boolean).
#Alejandro provided one way to get a default value if a variable that is supposed to contain a node-set is empty.
If the variable is supposed to contain a scalar, then the following expression returns its value (if it has a value) or (otherwise) the desired default:
concat($v, substring($default, 1 div (not($v) and not(string($v)))))
You can use string-length to check, if a variable called $reference for example contains anything.
<xsl:choose>
<xsl:when test="string-length($reference) > 0">
<xsl:value-of select="$reference" />
</xsl:when>
<xsl:otherwise>
<xsl:text>some default value</xsl:text>
</xsl:otherwise>
</xsl:choose>
If necessary use normalize-space, too.
First, all variables has values, because XSLT belongs to declarative paradigm: there is no asignation instruction, but when you declare the variable you are also declaring the expression for its value relationship.
If this value it's a node set data type (that looks from your question), then you should test for an empty node set in case nothing was selected. The efective boolean value for an empty node set is false. So, as #0xA3 has answered: test="$node-set".
You wrote:
If there is no value for the variable
use the default one. How to do that ?
Well, that depends on what kind of data type you are looking for.
Suppose the node set data type: if you want $node-set-1 value or $node-set-2 if $node-set-1 is empty, then use:
$node-set-1|$node-set-2[not($node-set-1)]
I tried a lot of solution from SO, my last solution was taken from #dimitre-novatchev, but that one also not working every time. Recently I found one more solution from random google search, thought to share with the community.
In order to check empty variable value, we can declare an empty variable and compare its value against test condition. Here is code snippet:
<xsl:variable name="empty_string"/>
<xsl:if test="testVariableValue != $empty_string">
...
</xsl:if>
Here testVariableValue hold the value of new variable to be tested for empty
scenario.
Hope it would help to test empty variable state.

Select xsl element based on index that is defined in a param

I want to select an element by index with the indexed number being passed in with a param, the param is being passed in via PHP. Here's what I am trying:
//PHP
$xslt->setParameter('','player',$player);
$xslt->importStylesheet( $XSL );
print $xslt->transformToXML( $data );
//xslt
<xsl:param name="player" data-type="number"/>
<template match="/">
<xsl:value-of select="result[$player]/#name" />
</template>
And I know the value of the param is being passed correctly because I can just output the value of the param ($player) and it will output the correct value. If I hard code the indexed number "$player" to any number of index I want like below:
<template match="/">
<xsl:value-of select="result[2]/#name" />
</template>
it works. So, what I am doing wrong here. Can you not use params/variables to select indexes?
It may be evaluating the value of your xsl:param as a string, rather than a number. You can try explicitly converting it to a number using the number() function.
<xsl:value-of select="result[number($player)]/#name" />
The predicate filter specifying a number is short-hand for [position()=$param]. You can use xsl:param inside the predicate filter, like this, and it will evaluate the xsl:param value as a number:
<xsl:value-of select="result[position()=$player]/#name" />
If I hard code the indexed number
"$player" to any number of index I
want like below:
<template match="/">
<xsl:value-of select="result[2]/#name" />
</template>
it works.
No, any compliant XSLT processor will not select anything.
result[2]/#name
is a relative expression against the current node, and the current node is the / -- document-node.
Any well-formed XML document has exactly one top element (never two), therefore
result[2]
is equivalent to:
/result[2]
and doesn't select anything.
Most probably you are dealing with another expression, which you haven't shown (or the template is not matching just /).
Also:
<xsl:param name="player" data-type="number"/>
this is invalid syntax. The <xsl:param> instruction doesn't have a data-type attribute.
In fact, in XSLT 1.0 there isn't any way to specify the type of variables or parameters.
This is why in:
result[$player]/#name
$player is treated as string -- not as an integer.
To achieve the "indexing" you want, use:
result[position()=$player]/#name
The position() function returns a number and this causes the other operand of the = operator to be converted to (and used as) number.

Assigning parameter value to the xsl: for each

Can anybody who has worked with XSLT help me on this?
I am using XSL version 1.0.
I have declared a parameter in XSL file like:
<xsl:param name="HDISageHelpPath"/>
Now I am assigning the value to this parameter from an asp page . The value which I assign is "document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help". Now I want to assign this parameter to the <xsl for each> like
<xsl:for-each select="msxsl:node-set($HDISageHelpPath)" > (This does not work)
But it does not work. I checked the parameter value by debugging it as below
<debug tree="$HDISageHelpPath">
<xsl:copy-of select="$HDISageHelpPath"/>
</debug>
I'm able to print the value and it seems correct. In fact when I assign the static path ("document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help") by hard-coding it, it works
<xsl:for-each select="document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help"> (This works)
Can anyone please let me know why assigning the parameter to xsl:for-each does not work?
Note: I have referred the site "http://www.dpawson.co.uk/xsl/sect2/N1553.html"
You can't easily evaluate dynamic strings as XPath expressions in XSLT 1.0. They must be hard-coded, normally.
There's EXSLT's dyn:evaluate(), but I doubt you can use that with the MXSML processor.
As an alternative approach, you could either try passing the file path only:
<xsl:param name="HDISageHelpFilePath"/>
<!-- ... -->
<xsl:for-each select="document($HDISageHelpFilePath)/HelpFiles/Help">
</xsl:for-each>
or making placeholder, replacing it with search-and-replace before you load the actual XSL code into the processor (as a string). This is a bit messy and error-prone, but it could give you the possibility to use an actual dynamic XPath expression.
<xsl:for-each select="%HELP_FILE_XPATH%">
</xsl:for-each>
Load the file as text, replace %HELP_FILE_XPATH% with your actual XPath, feed it to the processor. If it loads, you are fine, if it doesn't, your input XPath was malformed.