When test hanging in an infinite loop - xslt

I'm tokenising a string with XSLT 1.0 and trying to prevent empty strings from being recognised as tokens. Here's the entire function, based on XSLT Cookbook:
<xsl:template name="tokenize">
<xsl:param name="string" select="''" />
<xsl:param name="delimiters" select="';#'" />
<xsl:param name="tokensplitter" select="','" />
<xsl:choose>
<!-- Nothing to do if empty string -->
<xsl:when test="not($string)" />
<!-- No delimiters signals character level tokenization -->
<xsl:when test="not($delimiters)">
<xsl:call-template name="_tokenize-characters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="delimiters" select="$delimiters" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="_tokenize-characters">
<xsl:param name="string" />
<xsl:param name="tokensplitter" />
<xsl:if test="$string">
<token><xsl:value-of select="substring($string, 1, 1)"/></token>
<xsl:call-template name="_tokenize-characters">
<xsl:with-param name="string" select="substring($string, 2)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="_tokenize-delimiters">
<xsl:param name="string" />
<xsl:param name="delimiters" />
<xsl:param name="tokensplitter" />
<!-- Extract a delimiter -->
<xsl:variable name="delimiter" select="substring($delimiters, 1, 1)"/>
<xsl:choose>
<!-- If the delimiter is empty we have a token -->
<xsl:when test="not($delimiter) and $string != ''">
<xsl:text>£</xsl:text>
<token><xsl:value-of select="$string"/></token>
<xsl:text>$</xsl:text>
<xsl:value-of select="$tokensplitter"/>
</xsl:when>
<!-- If the string contains at least one delimiter we must split it -->
<xsl:when test="contains($string, $delimiter)">
<!-- If it starts with the delimiter we don't need to handle the before part -->
<xsl:if test="not(starts-with($string, $delimiter))">
<!-- Handle the part that comes before the current delimiter with the next delimiter. -->
<!-- If there is no next the first test in this template will detect the token. -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="substring-before($string, $delimiter)" />
<xsl:with-param name="delimiters" select="substring($delimiters, 2)" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:if>
<!-- Handle the part that comes after the delimiter using the current delimiter -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiters" select="$delimiters" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- No occurrences of current delimiter so move on to next -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="delimiters" select="substring($delimiters, 2)" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Value for string that I'm passing in is:
Europe;#6;#Global;#3;#Middle East,
Africa and
Caucasus;2;#Europe;#6;#Global;#3;#Middle
East, Africa and Caucasus
(The £ and $ indicators are just there so I can see no empty strings are output. This is within SharePoint so is difficult to debug.)
This code hangs processing of the XSLT. The line causing the problem is <xsl:when test="not($delimiter) and $string != ''">. As soon as I remove the second and test it works again. I've also tried and string($string) without success.
Anyone know why this is happening and how to resolve it?

I believe my suspicion was correct: you're falling through to your <xsl:otherwise> clause when $string has a value, but $delimiter does not, causing an infinite loop, as you say.
Add the following new <xsl:when> clause after the first one:
<xsl:when test="not($delimiter) and $string = ''" />
That will prevent the execution from entering the <xsl:otherwise> block when it shouldn't.
A more elaborate explanation of what's going on and why it's looping:
There are three branches in the <xsl:choose> block.
<xsl:when test="not($delimiter) and $string != ''">
<xsl:when test="contains($string, $delimiter)">
<xsl:otherwise>
So, when neither $string nor $delimiter contain values, the first condition fails (because $string != '' is false). The second condition passes (because contains(nil,nil) always returns true (confirmed in Visual Studio)), which calls the template again with the same parameters (because the substring-before returns the empty string since it doesn't contain the empty delimiter). Ergo, an infinite loop.
The fix is to add a new, empty condition:
<xsl:when test="not($delimiter) and $string != ''">
<xsl:when test="not($delimiter) and $string = ''" />
<xsl:when test="contains($string, $delimiter)">
<xsl:otherwise>
EDIT: I've poked around and I can't find a reference to the defined behaviour of contains when the second parameter is empty or nil. Tests have shown that Microsoft Visual Studio's XSLT engine returns true when the second parameter is either empty or nil. I'm not sure if that's the defined behaviour or if it's up to the implementor to decide. Does anyone have a conclusive answer to this? Tomalak, I'm looking at you.

Isn't string a reserved word? Can you try to replace that name for anything else?
EDIT: Supplied code ran without problem here: XSLT Tryit Editor v1.0 using:
<xsl:call-template name="tokenize">
<xsl:with-param name="string">Europe;#6;#Global...</xsl:with-param>
</xsl:call-template>

Related

How can I split a string in xslt?

I want to split a string at the double quotation. Input string is as follow,
<S>Test Example "{test1}" is "{equal}" "{test2}"</S>
The xslt code that I'm using is,
<xsl:template name="SplitString">
<xsl:param name="text" select="''" />
<xsl:variable name="tag" select="substring-before(substring-after($text, '"'), '"')" />
<xsl:variable name="Remainder" select="substring-after($text, '"')" />
<xsl:choose>
<xsl:when test="$tag != ''">
<xsl:element name = "NP">
<xsl:value-of select = "$tag"/>
</xsl:element>
<!--recursive loop -->
<xsl:call-template name="SplitString">
<xsl:with-param name="text" select="$Remainder" />
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
The output that I'm getting is as follow,
<NP>{test}</NP>
<NP>is</NP>
<NP>{equal}</NP>
<NP> </NP>
<NP>{test2}</NP>
How can avoid the creation of empty element?
the desired output would be,
<NP>{test}</NP>
<NP>is</NP>
<NP>{equal}</NP>
<NP>{test2}</NP>
Just add a condition:
<xsl:if test="normalize-space($tag)">
<xsl:element name = "NP">
<xsl:value-of select = "$tag"/>
</xsl:element>
</xsl:if>

How to format Sharepoint lookup column XSL values

I have a Sharepoint list, and one of the columns is a lookup column that returns multiple values, separated by a semi-colon. I would like to display these items as separate lines in the output, instead of as a single line with the separator. The xsl for the field in question is as follows:
<xsl:template match="FieldRef[(#Encoded) and #Name='Project_x0020_Tasks']" ddwrt:dvt_mode="body" mode="Lookup_body" ddwrt:ghost="show">
<xsl:param name="thisNode" select="."/>
<xsl:value-of select="$thisNode/#*[name()=current()/#Name]" disable-output-escaping="yes" />
</xsl:template>
currently the view displays the data inside a table cell as:
Task 1; Task 2; Task 3;
I would like it to display as
Task 1
Task 2
Task 3
I've spent plenty of hours searching online but haven't found any solution that helps me so far.
What you could do is have a recursive template that converts semi-colons to <br /> tags, like so:
<xsl:template name="CharToLineBreak">
<xsl:param name="text" />
<xsl:param name="char" />
<xsl:choose>
<xsl:when test="contains($text, $char)">
<xsl:value-of select="substring-before($text, $char)" />
<br />
<xsl:call-template name="CharToLineBreak">
<xsl:with-param name="text" select="substring-after($text, $char)" />
<xsl:with-param name="char" select="$char" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Then, instead of doing xsl:value-of as shown in your question, do xsl:call-template like so...
<xsl:call-template name="CharToLineBreak">
<xsl:with-param name="text" select="$thisNode/#*[name()=current()/#Name]" />
<xsl:with-param name="char" select="';'" />
</xsl:call-template>
I am not sure why you have so much complexity with getting the attribute value though. It could be simplified to just this
<xsl:call-template name="CharToLineBreak">
<xsl:with-param name="text" select="#Project_x0020_Tasks" />
<xsl:with-param name="char" select="';'" />
</xsl:call-template>

Removing &#160 with XSLT

I hope someone can help me with this...
I am using SharePoint 2013 and trying to render a CQWP to show the latest blog posts. The problem I have is that when displaying the 'content' from the post I get '&#160' added and quotes are shown as '&quot'. I have managed to strip the HTML mark up but can't seem to get rid of these.
My code is as follows - any help would be much appreciated, thanks.
Generate Summary and Remove HTML
<!-- Generate Summary -->
<xsl:template name="GenerateSummary">
<xsl:param name="Content"/>
<xsl:param name="Length"/>
<xsl:param name="Suffix"/>
<xsl:variable name="cleanContent">
<xsl:call-template name="RemoveHtml">
<xsl:with-param name="String" select="$Content"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="SubstringBeforeLast">
<xsl:with-param name="String"
select="substring($cleanContent, 1, $Length)"/>
<xsl:with-param name="Char" select="' '"/>
</xsl:call-template>
<xsl:if test="string-length($cleanContent) > $Length">
<xsl:value-of select="$Suffix"
disable-output-escaping="yes"/>
</xsl:if>
</xsl:template>
<!-- RemoveHTML -->
<xsl:template name="RemoveHtml">
<xsl:param name="String"/>
<xsl:choose>
<xsl:when test="contains($String, '<')">
<xsl:value-of select="substring-before($String, '<')"/>
<xsl:call-template name="RemoveHtml">
<xsl:with-param name="String"
select="substring-after($String, '>')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$String"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="SubstringBeforeLast">
<xsl:param name="String" />
<xsl:param name="Char" />
<xsl:param name="subsequent"/>
<xsl:choose>
<xsl:when test="contains($String, $Char)">
<xsl:if test="$subsequent = 1">
<xsl:value-of select="$Char"/>
</xsl:if>
<xsl:value-of select="substring-before($String, $Char)"/>
<xsl:call-template name="SubstringBeforeLast">
<xsl:with-param name="String"
select="substring-after($String, $Char)" />
<xsl:with-param name="Char" select="$Char" />
<xsl:with-param name="subsequent" select="1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$subsequent != 1">
<xsl:value-of select="$String"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Calling it here
<div class="fcItemContent">
<xsl:call-template name="GenerateSummary">
<xsl:with-param name="Content" select="#content" />
<xsl:with-param name="Length" select="200" />
<xsl:with-param name="Suffix" select="'...'"/>
</xsl:call-template>
</div>
Use function of XSLT
normalize-space()
**<xsl:value-of select="normalize-space()"/>**
White space is normalized by stripping leading and trailing white space and replacing sequences of white space characters with a single space. If the argument is omitted, the string-value of the context node is normalized and returned.
referred Link :
Remove Space using XSLT

How to add all comma separated values in XSLT

I have a question in XSLT.
I have one variable, suppose :
<xsl:variable name="myVar" select="string('1.3,2.1,3.3,5.1,11.4')">
I want to add all comma separated values.
This is my current code, where I am trying to sum up all csv values which are stored in $sum variable.
<xsl:template name="getCount" >
<xsl:param name="str" /> <!-- $str is having '0.001,0.003' value -->
<xsl:param name="delimiter" />
<xsl:param name="summation" />
<xsl:choose>
<xsl:when test="contains(string($str),string($delimiter))">
<xsl:variable name="beforecomma" select="substring-before(string($str),string($delimiter))" />
<xsl:variable name="aftercomma" select="substring-after(string($str),string($delimiter))" />
<xsl:choose>
<xsl:when test="$aftercomma=''">
<xsl:value-of select="$summation + $beforecomma" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="getindexvalue">
<xsl:with-param name="str" select="$aftercomma" />
<xsl:with-param name="delimiter" select="string($delimiter)" />
<xsl:with-param name="summation" select="$summation + beforecomma" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
</xsl:choose>
</xsl:template>
Assuming that the call-template is a typo and should be a recursive call to the same template
<xsl:call-template name="getCount">
then you're actually very very close to a working solution. The error is in
<xsl:with-param name="summation" select="$summation + beforecomma" />
where you're missing a $ in front of beforecomma:
<xsl:with-param name="summation" select="$summation + $beforecomma" />
Without the dollar you're looking for a child element named beforecomma rather than taking the value of the variable.
A few other comments:
you have a lot of redundant string() calls, you could simply say e.g. contains($str,$delimiter) instead of contains(string($str),string($delimiter)), as the functions you're using automatically coerce their arguments to string.
your current code requires at least two comma-separated values, it can't cope with being given just one value (for which the sum would just be the value itself). Structuring your template to cope with this would actually make it simpler:
<xsl:template name="getCount" >
<xsl:param name="str" /> <!-- $str is having '0.001,0.003' value -->
<xsl:param name="delimiter" />
<xsl:param name="summation" select="0" />
<xsl:choose>
<xsl:when test="contains($str,$delimiter)">
<xsl:variable name="beforecomma" select="substring-before($str,$delimiter)" />
<xsl:variable name="aftercomma" select="substring-after($str,$delimiter)" />
<xsl:call-template name="getCount">
<xsl:with-param name="str" select="$aftercomma" />
<xsl:with-param name="delimiter" select="$delimiter" />
<xsl:with-param name="summation" select="$summation + $beforecomma" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$summation + $str" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>

How to replace double quotes with typographer's quotes (aka curly quotes)

I need to programatically replace "regular double quotes" with “typographer's quotes”
My initial thought is something like this:
<xsl:variable name="text">
<xsl:call-template name="replace-string"><!-- FYI: replace-string is a custom method that works like you would expect-->
<xsl:with-param name="text" select="."/>
<xsl:with-param name="replace" select="string(' "')" /><!-- left quote because of space before -->
<xsl:with-param name="with" select="string('“')"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="text2">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="replace" select="string('" ')" /><!-- right quote because of space after -->
<xsl:with-param name="with" select="string('”')"/>
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="$text2" />
My worry is situations where there is not a determining space by the quote. Such as these.
They say "this is great".
I like tigers ("big large cats").
Has anyone ever had to do this before that knows some extra rules to apply or a different strategy?
Thanks!
A solution that works without extension functions would be:
<xsl:template name="typograpic-quotes">
<xsl:param name="text" select="''" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="open" select="'“'" />
<xsl:param name="close" select="'”'" />
<xsl:param name="inquote" select="false()" />
<xsl:choose>
<xsl:when test="contains($text, $quote)">
<xsl:value-of select="substring-before($text, $quote)" />
<xsl:choose>
<xsl:when test="$inquote">
<xsl:value-of select="$close" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$open" />
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="typograpic-quotes">
<xsl:with-param name="text" select="substring-after($text, $quote)" />
<xsl:with-param name="quote" select="$quote" />
<xsl:with-param name="open" select="$open" />
<xsl:with-param name="close" select="$close" />
<xsl:with-param name="inquote" select="not($inquote)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Needless to say that unbalanced quotes in the input will make this fail.
Regular Expressions.
regex:replace($textVariable, '"([^"]*)"' , 'gi', '“$1”')
Havent tested this, but this is an easy approach IMHO. you match all "(anything not ")zero or more times" and replace that with your other typographical quotes. The $1 is a back-reference to the first match. While there are issues here such as incorrectly nested text, quotes that are not closed etc., I am suggesting something like this. You may rewrite the regex for this one and test it out.
This is assuming that your XSLT processor supports the EXSLT extensions.
Because we have some unbalanced quotes. I am working on something a little more pragmatic. The below seems to work in all cases I have tried.
<!-- Get text. All quotes defaulted to right quote -->
<xsl:variable name="text">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="."/>
<xsl:with-param name="replace" select="string('"')" />
<xsl:with-param name="with" select="string('”')"/>
</xsl:call-template>
</xsl:variable>
<!-- Turn quotes preceded by a space into left quote -->
<xsl:variable name="text2">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="replace" select="string(' ”')" />
<!-- right quote because of space after -->
<xsl:with-param name="with" select="string(' “')"/>
</xsl:call-template>
</xsl:variable>
<!-- Turn quotes preceded by a parenthesy into left quote -->
<xsl:variable name="text3">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$text2"/>
<xsl:with-param name="replace" select="string('(”')" />
<!-- right quote because of space after -->
<xsl:with-param name="with" select="string('(“')"/>
</xsl:call-template>
</xsl:variable>
<!-- Turn quotes that are the first character in the text into left quote -->
<!-- Note: this one is still a little funky. For some reason the first character is always whitespace. So I am checking the second character because it is really the first. -->
<xsl:variable name="text4" >
<xsl:choose>
<xsl:when test="normalize-space(substring( $text3, 2, 2 )) = string('”')">
<xsl:value-of select="string('“')"/>
<xsl:value-of select="substring($text3, 3)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text3"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:apply-templates select="$text4" />