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

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:
&

Related

How to preserve attributes in xsl:variable body? [duplicate]

This question already has an answer here:
Setting a variable conditionally with no cut of ancestor axis
(1 answer)
Closed 5 months ago.
data
<mydoc>
<setting>foo</setting>
<foo sound="aaaa"/>
<bar sound="brrr"/>
<cat sound="meow"/>
</mydoc>
if no conditional logic is needed,
<xsl:variable name="result1" select="/mydoc/foo"/>
works fine.
but if I need conditional logic,
<xsl:variable name="result2">
<xsl:choose>
<xsl:when test="/setting='foo'">
<!-- what to put here -->
</xsl:when>
<xsl:when test="/setting='cat'">
<!-- what to put here -->
</xsl:when>
<xsl:otherwise>
<!-- what to put here -->
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
desired output:
when setting = 'foo', result = <foo sound="aaa"/> (the whole element complete with attribute)
when setting = 'cat', result = <cat sound="meow"/> (the whole element complete with attribute)
otherwise, result = <bar sound="brrr"/> (the whole element complete with attribute)
How do I get the result2 variable to be an element with attributes, as it does in result1? I want to use $result2/sound, and some other elements / attributes of $result2 and / or it's children.
<xsl:variable name="result2" select="/mydoc[setting='foo']/foo |
/mydoc[setting='cat']/cat |
/mydoc[not(setting='foo' or setting='cat')]/bar"/>
Because of the conditions in square brackets exactly one member of the union will effectively be selected.
If you put something inside the <xsl:variable> element rather than use select, your variable will contain a result tree fragment, not a nodeset.
If you insist on using xsl:choose (why?) then the only option open to you is to use xsl:copy. However, this has some side effects:
This will create a copy of the selected node/s and make it a child of the variable; in effect, it will create a separate document and the copied nodes will no longer be descendants of the XML input's root;
The variable will be a result-tree-fragment and you won't be able to process it in any way unless you convert it to a node-set first. Here, "process" includes accessing individual nodes inside the variable.
The following example demonstrates this:
XML
<mydoc>
<setting>foo</setting>
<foo bar="a"/>
<bar foo="b"/>
</mydoc>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/mydoc">
<xsl:variable name="temp">
<xsl:choose>
<xsl:when test="setting='foo'">
<xsl:copy-of select="foo"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="bar"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<output>
<xsl:value-of select="exsl:node-set($temp)/foo/#bar" />
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>a</output>
Do note that if you change:
<xsl:value-of select="exsl:node-set($temp)/foo/#bar" />
to:
<xsl:value-of select="$temp/foo/#bar" />
you will get an error.

How to put double and single quote in value of AllowedSymbols variable to use in XSLT Translation

Using XSLT 1.0, in the XLST template below, I want to add the single and double quote to the list of allowed values. Getting error on vAllowedSymbols2 saying that "string literal not closed".
<xsl:template name="CleanAlphaField">
<xsl:param name="inputText" />
<xsl:param name="maxLength" />
<xsl:variable name="vAllowedSymbols2" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !#$%()+-_,.;:=[]{}\?"&apos;'"/>
<xsl:variable name="vAllowedSymbols" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !#$%()+-_,.;:=[]{}\?'"/>
<xsl:variable name="truncatedInputText" select="substring($inputText,1,$maxLength)" />
<!-- return the revised string -->
<xsl:value-of select="translate($truncatedInputText,translate($truncatedInputText, $vAllowedSymbols, ''),'')"/>
</xsl:template>
You might need to create these as separate variables for XML escaping reasons.
<xsl:variable name="singleQuote" select='"&apos;"' />
<xsl:variable name="doubleQuote" select="'"'" />
Having done that, you can concat these together
<xsl:variable name="vAllowedSymbols2"
select="concat($vAllowedSymbols, $singleQuote, $doubleQuote)" />
This happens because the XML entity expansion happens before things reach the XSLT processor, so in the minimal case
<xsl:variable name="invalid" select="'&apos;'" />
the value of #select gets expanded and the XSLT engine sees an attribute (name={}select, value=''') and doesn't know that it came from an entity expansion; it just knows that three single quotes doesn't make a valid XPath expression.
You could do simply:
<xsl:variable name="vAllowedSymbols2">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !#$%()+-_,.;:=[]{}\?"'</xsl:variable>

Can we declare global variable and add/Remove/Check value from it?

I want to achieve
Declare global variable having no value
<xsl:variable name="IsEqual"/>
Check variable value and change according to condition
<xsl:choose>
**// Checking value equal or not**
<xsl:when test="name=$name">
<xsl:choose>
**//Checking variable value**
<xsl:when test="$IsEqual !='Unequal'">
**//Setting variable value**
<xsl:variable name="IsEqual" Select="Equal"/>
</xsl:when>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
**//Setting variable value**
<xsl:variable name="IsEqual" Select="Unequal"/>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$IsEqual"/>
Expected output value of variable $IsEqual.. IF it is not possible then what is another way to achieve this? What should I use instead of variable?
"It's not a bug, it's a feature": XSLT variables are designed not to be changeable. Actually they could be named constants. Working around that is difficult, it can be done using parameters. In most cases that isn't necessary if you use the XSLT programming attempt, where the programm is driven by the data through templates.
The answer to your question is no.
Copied this (own) text from XML and Variables
#Sam: What do you want to accomplish using a global variable? Where do you want to check for equality? I have no idea what you want to do so I can only give you a general example.
Try this xml file:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<root>
<data check="value1">This is data 1</data>
<data check="value2">This is data 2</data>
<data check="value3">This is data 3</data>
</root>
with this xslt file:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="v">value2</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates select="root/data[#check = $v]"/>
</xsl:template>
<xsl:template match="data">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Only the second data element will show up as this one matches the global variable, which stays the same all the time. If you want different values to match with, you can put them into your data file instead using a variable and compare the different elements.
For testing just save the two files (test.xml and test.xsl) into one directory and open test.xml with your browser.
#Sam again: As you insist on changing an xslt variable I have to repeat that this can't be done. Maybe there is a way around using the environment xslt is running in. E.g. PHP, where you can pass functions into the script. I described the technique here: Can PHP communicate with XSLT?
The xslt spec says:
XSLT does not provide an equivalent to the Java assignment operator
x = "value";
because this would make it harder to create an implementation that processes a document other than in a batch-like way, starting at the beginning and continuing through to the end.
(See http://www.w3.org/TR/xslt#variables to prove my answer "no" to your question is correct :-)

Looping a variable length array with namespaces in XSLT

My previous question[1] is related to this. I found the answer for that. Now I want to loop a variable length array with namespaces. My array:
<ns:array xmlns:ns="http://www.example.org">
<value>755</value>
<value>5861</value>
<value>4328</value>
<value>2157</value>
<value>1666</value>
</ns:array>
My XSLT code:(have added the namespace in the root)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="http://www.example.org">
<xsl:template match="/">
<xsl:variable name="number" select="ns:array" />
<xsl:for-each select="$number">
<xsl:value-of select="$number" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
[1]https://stackoverflow.com/questions/20287219/looping-a-variable-length-array-in-xslt
IMHO you confused yourself by introducing a variable called number which actually contains a node set of value tags. Then, as a consequence you used your variable as singe item/node which does not yield the desired result (presumingly, since you did not really tell us what you want to do with the values).
Also, I think your question does not really have anything to with namespace issues as such. You just have to make sure that the namespaces in your select expressions match the namespaces in your input file.
I would suggest to do without the variable and change the way you retrieve the current value:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://www.example.org">
<xsl:template match="/">
<xsl:for-each select="ns:array">
<!-- Inside here you can work with the `value` tag as the _current node_.
There are two most likely ways to do this. -->
<!-- a) Copy the whole tag to the output: -->
<xsl:copy-of select="." />
<!-- or b1) Copy the text part contained in the tag to the output: -->
<xsl:value-of select="." />
<!-- If you want to be on the safe side with respect to white space
you can also use this b2). This would handle the case that your output
is required not to have any white space in it but your imput XML has
some. -->
<xsl:value-of select="normalize-space(.)" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Numeric variable/parameter in XSLT

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.