Numeric variable/parameter in XSLT - variables

I have this XSLT:
<xsl:template match="/">
<xsl:variable name="errorCount" select="count($orders/*[1]/cm:Error)" />
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="errorCount" select="$errorCount" tunnel="yes" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="status">
<xsl:param name="errorCount" tunnel="yes" />
<xsl:copy>
<xsl:choose>
<xsl:when test="$errorCount > 0">
<xsl:text>ERROR</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>OK</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
The tunneling and all seems to work, but the transformation fails with the following error:
Required item type of first operand of '>' is numeric; supplied value has item type xs:string
I first had the variable declaration in the template where it is it used, and then it worked fine. Moved it because I need to use the same count in other templates too.
How/Where do I declare that this variable/parameter is in fact a number?

Since you are using XSLT 2.0, then you should also add an as attribute to your xsl:param in the template. For example (you may have to use a different as value depending on what you need the resulting number to be, such as if you'll have values that have decimals; you would also need to correct the tunnel value, per Michael Kay's point):
<xsl:param name="errorCount" tunnel="yes" as="xs:integer" />
The transform would fail if it couldn't be converted to the as type (in this case, an integer). Eero's solution may look cleaner because you'll still have to check to see if the value is greater than zero. But, because you are using XSLT 2.0, the best practice is to type your parameters/variables.

You can use number() to convert a string to a number:
<xsl:when test="number($errorCount) > 0">
<xsl:text>ERROR</xsl:text>
</xsl:when>

My suspicion is that because you wrote tunnel="true" rather than tunnel="yes", the fact that you specified tunnel at all is being (incorrectly) ignored, and the parameter is being given its default value, which is a zero-length string.

Related

xslt 3.0 conditional dynamic element creation

I have a variable:
<xsl:variable name="courseType" select="Record[1]/course-type"/>
and based on a value I would like to create a dynamic element:
<xsl:if test="$courseType ='B'">
<xsl:element name="newElement">
</xsl:if>
...
other nodes
...
<xsl:if test="$courseType ='B'">
</xsl:element>
</xsl:if>
The problem is that:
The element type "xsl:element" must be terminated by the matching end-tag "".
is there any way to achieve this?
You haven't provided much content but you can certainly use e.g.
<xsl:variable name="other-nodes">
...
</xsl:variable>
<xsl:choose>
<xsl:when test="$course-type = 'B'">
<newElement>
<xsl:sequence select="$other-nodes"/>
</newElement>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$other-nodes"/>
</xsl:otherwise>
</xsl:choose>
Note that I have used a literal result element for newElement instead of using xsl:element as I only consider the use of xsl:element necessary if you want to compute the element name at run-time but it does not matter for the solution of the original problem whether you use a literal result element or xsl:element.

How to multiply numbers with each other in xslt 1.0

I have a number for example 123, this is stored in an element let's say
<Number>123</Number>. I am looking for a solution written in XSLT 1.0 which can do something like: 1*2*3 and provide me the result as 6. The value in Number element can be in any length. I know i can do this through substring function and by storing the values one by one in variables but the problem is, i dont know the length of this field.
I could not write any xslt for this.
Can anyone help or suggest a solution for this?
You can do this by calling a recursive named template:
<xsl:template name="digit-product">
<xsl:param name="digits"/>
<xsl:param name="prev-product" select="1"/>
<xsl:variable name="product" select="$prev-product * substring($digits, 1, 1)" />
<xsl:choose>
<xsl:when test="string-length($digits) > 1">
<!-- recursive call -->
<xsl:call-template name="digit-product">
<xsl:with-param name="digits" select="substring($digits, 2)"/>
<xsl:with-param name="prev-product" select="$product"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$product"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Working example: http://xsltransform.net/3NJ38YJ
Note that this assumes the value of the passed digits parameter is an integer.

XSL : How to pass variable to for each

<xsl:for-each select="$script/startWith">
<xsl:variable name = "i" >
<xsl:value-of select="$script/startWith[position()]"/>
</xsl:variable>
<xsl:for-each select="JeuxDeMots/Element">
<xsl:variable name = "A" >
<xsl:value-of select="eName"/>
</xsl:variable>
<xsl:if test="starts-with($A,$i)= true()">
<xsl:variable name="stringReplace">
<xsl:value-of select="substring($i,0,3)"/>
</xsl:variable>
<xsl:value-of select="$stringReplace"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
Problem : variable $i, can not pass in an xsl for-each.
Please help me.
Try replacing the declaration of i with
<xsl:variable name="i" select="string(.)"/>
The context item (i.e. ".") is different for each evaluation of the for-each instruction, but the value of the expression $script/startWith[position()] does not change. (Here, you are making a sequence of startWith elements, and testing each one for the effective boolean value of the expression position(). The expression position() returns a positive integer, so its effective boolean value is always true. So the predicate [position()] is doing no work at all here.
Also, you'll want to replace the declaration of stringReplace with
<xsl:variable name="stringReplace" select="substring($i,1,3)"/>
(String offsets begin with 1 in XPath, not with 0.)
I am guessing that you want to process all of the startWith children of $script, and for each one emit the first three characters of the startWith value once, for each startWith/JeuxDeMots/Element whose eName child begins with the value of startWith.
Actually, the whole thing might be a little easier to read if it were briefer and more direct:
<xsl:for-each select="$script/startWith">
<xsl:variable name = "i" select="string()"/>
<xsl:for-each select="JeuxDeMots/Element">
<xsl:if test="starts-with(eName,$i)">
<xsl:value-of select="substring($i,1,3)"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
It makes perfect sense to create the variables $A and $stringReplace if they are used several times in code you're not showing us, but if not, ...
I think the problem is that you change context in first for-each. Then the element JeuxDeMots is not "visible" for second for-each. You can for example try to store it in variable and use this variable in second for-each (there are also another ways how to figure out this problem).
<xsl:template match="/">
<xsl:variable name="doc" select="JeuxDeMots"/>
<xsl:choose>
<xsl:when test="$script/startWith">
<xsl:for-each select="$script/startWith">
<xsl:variable name="i">
<xsl:value-of select="."/>
</xsl:variable>
<xsl:for-each select="$doc/Element">
<xsl:variable name="A">
<xsl:value-of select="eName"/>
</xsl:variable>
<xsl:if test="starts-with($A,$i) = true()">
<xsl:variable name="stringReplace">
<xsl:value-of select="substring($i,0,3)"/>
</xsl:variable>
<xsl:value-of select="$stringReplace"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:when>
</xsl:choose>
</xsl:template>
Although I'm not sure what exactly you are dealing with it seems to output desired value AsAt.
You could also consider C.M.Sperberg-McQueen post about string offset in XPath.

Can disable-output-escaping be set using a template parameter?

Why won't the following work in XSLT1.0?
<xsl:template name="GenerateSummaryOld">
<xsl:param name="Content" />
<xsl:param name="Length" />
<xsl:param name="DisableOutputEscaping" />
<xsl:value-of select="substring($Content, 1, $Length)" disable-output-escaping="$DisableOutputEscaping" />
<xsl:if test="string-length($Content) > $Length"><i>...text has been shortened</i></xsl:if>
</xsl:template>
I'm using the following when calling the template:
<xsl:with-param name="DisableOutputEscaping">no</xsl:with-param>
I'm trying this in a SharePoint Content Query WebPart but I get a web part error. If I hard-code disable-output-escaping as "yes" or "no" in the template, i get no error.
Short answer: the value of disable-output-escaping must be specified literally in the XSLT stylesheet; it cannot be calculated at stylesheet execution time.
That is, the behavior you are observing is the behavior prescribed by the language definition.
Longer answer: The XSLT 1.0 spec shows the syntax of xsl:value-of like this (more or less):
<!-- Category: instruction -->
<xsl:value-of
select = string-expression
disable-output-escaping = "yes" | "no" />
Note that "string-expression" is italicized here; it means that the select attribute has as its value not the string "string-expression" but any XPath expression which can be evaluated and coerced to a string. But the "yes" and "no" of disable-output-escaping are not italicized, not described as being an expression, and not described as being an attribute-value template. The "yes" or "no" value must be given literally.
The closest the spec comes to saying this explicitly (that I could find) is the note in section 7.6.2 on attribute value templates:
NOTE:Not all attributes are interpreted as attribute value templates. Attributes whose value is an expression or pattern, attributes of top-level elements and attributes that refer to named XSLT objects are not interpreted as attribute value templates. ...
This is one of a number of early-binding constraints in XSLT designed to ensure that stylesheets could be compiled and not just interpreted.
The explanation was provided in the good answer by C. M. Sperberg-McQueen.
Here is a workaround:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vAmp">&</xsl:variable>
<xsl:variable name="vYesNo" select="'yes'"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="$vYesNo = 'yes'">
<xsl:value-of select="$vAmp" disable-output-escaping="yes"/>
</xsl:when>
<xsl:when test="$vYesNo = 'no'">
<xsl:value-of select="$vAmp" disable-output-escaping="no"/>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the result is:
&
If we replace:
<xsl:variable name="vYesNo" select="'yes'"/>
with:
<xsl:variable name="vYesNo" select="'no'"/>
the result of the modified transformation now is:
&

xslt - how to create a variable with text content of space characters of a dynamically determined length that will perform well

I would like to create a variable that contains a text value that is a number of space characters but the number of characters is not known until runtime. This needs to be done a great many times so i would like to use something that will perform very well.
One option is substring() function on a previously declared node, but this limits the length to no more than that of the original text.
Another would be to use a recursive template with concat() function, but not sure about the performance implications.
What is a way to do this that will perform very well?
I would create a global variable of length say 4000 spaces. Then if the desired string is less than 4000 spaces, use substring(); if greater, use a recursive approach as outlined by Durkin. In 2.0 of course the code is much simpler to write but choosing an approach that performs well is still an interesting little problem.
You can use recursion and divide and conquer. This will give you run-time cost of order (log N). Alternatively, if your XSLT processor implements tail-end recursion optimisation, then you can use a tail-end recursion solution for better safety at scale. Here are the two solutions...
For non tail-end recursion optimising XSLT processors...
<xsl:template name="spaces">
<xsl:param name="count" />
<xsl:choose>
<xsl:when test="$count <= 10">
<xsl:value-of select="substring(' ',1,$count)" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="spaces">
<xsl:with-param name="count" select="$count idiv 2" />
</xsl:call-template>
<xsl:call-template name="spaces">
<xsl:with-param name="count" select="$count - ($count idiv 2)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
For tail-end recursion optimising XSLT processors...
<xsl:template name="spaces">
<xsl:param name="count" />
<xsl:choose>
<xsl:when test="$count <= 10">
<xsl:value-of select="substring(' ',1,$count)" />
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
<xsl:call-template name="spaces">
<xsl:with-param name="count" select="$count - 10" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
For an efficient (both time and space) XSLT 1.0 solution, see this:
http://www.sourceware.org/ml/xsl-list/2001-07/msg01040.html