How to format Sharepoint lookup column XSL values - xslt

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>

Related

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>

Trying to specify a default value for a dropdown with xslt?

My record nodes have a #code like USA and label like "United States".
<xsl:for-each select="/output/module/countries/data/record">
<xsl:call-template name="option">
<xsl:with-param name="value" select="#code"/>
<xsl:with-param name="label" select="#name"/>
<!--
<xsl:with-param name="select" select="/output/module/formdata/data/record/billing_info/country"/>
-->
<xsl:param name="value" value="USA" />
</xsl:call-template>
</xsl:for-each>
I'm trying to make USA the default value. Tried with:param name="select" select="USA" but that's a no go also. Hmm?
Ideally I'd like to have USA be the default if the other node specified in the comments doesn't have a value.
In XSLT 2.0, you can use an if in the select:
<xsl:with-param name="select" select="if (x) then x else 'USA'"/>
Just replace both instances of x with your xpath (/output/module/formdata/data/record/billing_info/country).
In XSLT 1.0 you can add an xsl:choose to your option template to test the value of the select param that is passed in. Something like:
<xsl:choose>
<xsl:when test="string($select)">
<xsl:value-of select="$select"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>USA</xsl:text>
</xsl:otherwise>
</xsl:choose>
You could put that xsl:choose in an xsl:variable if it's easier to use (like in attribute values or if you need to access the value multiple times).
Here's how you can do this in any version of XSLT:
<xsl:variable name="countryVal"
select="/output/module/formdata/data/record/billing_info/country" />
<xsl:variable name="countryOrDefault"
select="concat($countryVal,
substring('USA', 1, 3 * not(normalize-space($countryVal)))" />
<xsl:for-each select="/output/module/countries/data/record">
<xsl:call-template name="option">
<xsl:with-param name="value" select="#code"/>
<xsl:with-param name="label" select="#name"/>
<xsl:with-param name="select" select="$countryOrDefault"/>
</xsl:call-template>
</xsl:for-each>

Sum of attributes after truncating a character from the value

I have the following xml.
<xml>
<table>
<cols width="1.00*" />
<cols width="2.00*" />
<cols width="4.00*" />
<row><p>Hello</p></row>
</table>
<p>
Life is good.
</p>
</xml>
Explaination:
I need to read the column width from the above xml and display. But in some cases user specifies the width so less that the table columns overlap on each other.
Hence I thought to do this formula.
col1width=col1width/totalWidth*100;
This will give me the table width in % format so that the columns get distributed properly.
But I am not able to take a total count of all these attributes. My xslt just does not work. Please see the xslt below:
XSLT:
<xsl:template match="node()" mode="table">
<fo:table table-layout="fixed">
<fo:table-header>
<fo:table-row>
<xsl:for-each select="current()/cols">
<xsl:variable name="maxWidth"
select="number(substring-before(current()/table/cols/#width, '*')) + number(substring-before(following-sibling::cols/#width, '*'))" />
</xsl:for-each>
</fo:table-row>
</fo:table>
Solutions tried:
I have tried using sum function. But here, before summing, i have to
truncate the '*' character and convert to number and then add. Does
not work.
Written a recursive template to get the sum. I am getting the sum with this. But I am not able to return the total width from the
template. I guess xslt does not support returning of calculated
values. Below is the recursive xslt.
<xsl:template name="maximumTableWidth">
<xsl:param name="total" select="0" />
<xsl:param name="totalCols" />
<xsl:param name="index" select="1" />
<xsl:if test="$index <= $totalCols">
<xsl:variable name="maxWidth"
select="$total + translate(current()/cols[$index]/#width, '*', '')" />
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="total" select="$maxWidth" />
<xsl:with-param name="nodes" select="$totalCols" />
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
XSLT call:
<xsl:template name="main">
<xsl:variable name="maximumWidth">
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="total" select="0" />
<xsl:with-param name="totalCols"
select="count(current()/cols)" />
</xsl:call-template>
</xsl:variable>
</xsl:template>
Here, the variable is of type string and hence has no value.
Please help me with this problem. Also can suggest any other approach for table column width. I am generating pdf output using xsl fo. And my whole xslt is dynamic. I cannot have a direct path like node1/node2/node3.
Thank you.
You've got a couple of problems with your maximumTableWidth template to start with. Firstly, you should probably wrap the translate function in the number function
<xsl:variable name="maxWidth"
select="$total + number(translate(current()/cols[$index]/#width, '*', ''))" />
Secondly, you need to make sure you call it with the correct parameters. For your recursive call you set a parameter called nodes, when it should be totalCols
<xsl:with-param name="totalCols" select="$totalCols" />
But in terms of returning a value, all you need to do it use xsl:value-of to output the value, and your maximumWidth variable will then be set to that value. All you need to do is change the xsl:if in the template to an xsl:choose and output the value in the xsl:otherwise condition:
<xsl:template name="maximumTableWidth">
<xsl:param name="total" select="0" />
<xsl:param name="totalCols" />
<xsl:param name="index" select="1" />
<xsl:choose>
<xsl:when test="$index <= $totalCols">
<xsl:variable name="maxWidth"
select="$total + number(translate(current()/cols[$index]/#width, '*', ''))" />
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="total" select="$maxWidth" />
<xsl:with-param name="totalCols" select="$totalCols" />
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$total" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
There is another way to write this template recursively. Instead of passing in the index, and incrementing it, pass in the cols element itself, and use following-sibling to iterate over them. Try this template instead
<xsl:template name="maximumTableWidth">
<xsl:param name="col" />
<xsl:param name="total" select="0" />
<xsl:choose>
<xsl:when test="$col">
<xsl:variable name="maxWidth"
select="$total + number(translate($col/#width, '*', ''))" />
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="total" select="$maxWidth" />
<xsl:with-param name="col" select="$col/following-sibling::cols[1]" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$total" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
You would call this like so:
<xsl:variable name="maximumWidth">
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="col" select="cols[1]" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$maximumWidth" />
EDIT: If you were able to use XSLT 2.0, then you can do away with the named template altogether, and just set the maximumWidth template to this
<xsl:variable name="maximumWidth" select="sum(cols/(number(translate(#width, '*', ''))))" />

Context error on for-each on tokenize variable

I am using XSLT 2.0 and have a variable who contains dates separated by comma. I try to tokenize this variable in a for-each but in execution, I have the error: "Cannot select a node here: the context item is an atomic value"
Here is my code:
<xsl:variable name="datesMois">
<xsl:call-template name="dayOfMonth">
<xsl:with-param name="pDay" select="01" />
<xsl:with-param name="pMonth" select="/workfile/query/#month" />
<xsl:with-param name="pYear" select="/workfile/query/#year" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="currentstartdate" select="substring-before(., 'T')" />
<xsl:for-each select="tokenize($datesMois,',')">
<xsl:variable name="dateJour" select="." />
...
The template dayOfMonth returns the days for the month given in parameters.
I don't understand what is wrong in my code, could you please help me?
Thanks.
Assuming you have something like
<xsl:variable name="datesMois">
<xsl:call-template name="dayOfMonth">
<xsl:with-param name="pDay" select="01" />
<xsl:with-param name="pMonth" select="/workfile/query/#month" />
<xsl:with-param name="pYear" select="/workfile/query/#year" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="currentstartdate" select="substring-before(., 'T')" />
<xsl:for-each select="tokenize($datesMois,',')">
<xsl:variable name="dateJour" select="." />
<xsl:value-of select="foo[date = $dateJour]"/>
you would get the error you describe, to avoid that you would need to store the context node outside of the for-each in a variable as in
<xsl:variable name="datesMois">
<xsl:call-template name="dayOfMonth">
<xsl:with-param name="pDay" select="01" />
<xsl:with-param name="pMonth" select="/workfile/query/#month" />
<xsl:with-param name="pYear" select="/workfile/query/#year" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="currentstartdate" select="substring-before(., 'T')" />
<xsl:variable name="context" select="."/>
<xsl:for-each select="tokenize($datesMois,',')">
<xsl:variable name="dateJour" select="." />
<xsl:value-of select="$context/foo[date = $dateJour]"/>
I had to guess how your code might look that causes the error, if you still have problems then post the exact line of your code that causes the error.

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" />