Sum of attributes after truncating a character from the value - xslt

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, '*', ''))))" />

Related

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>

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 do I call a template within a template?

I've got the following value in an XML file:
<document>
<effectiveTime value="20131008"/>
<item>
<effectiveTime>
<low value=20131008"/>
</effectiveTime>
</item>
</document>
I have the following as part of my xsl file:
<xsl:variable name="today">
<xsl:call-template name="formatDate">
<xsl:with-param name="date" select ="/Document/effectiveTime/#value" />
</xsl:call-template>
</xsl:variable>
<!-- template for date formatting from xml document -->
<xsl:template name="formatDate">
<xsl:param name="date" />
<xsl:variable name="year" select="substring($date, 1, 4)" />
<xsl:variable name="month" select="number(substring($date, 5, 2))" />
<xsl:variable name="day" select="substring($date, 7, 2)" />
<xsl:value-of select="concat($month, '/', $day, '/', $year)" />
</xsl:template>
<!-- template for comparing against the date of visit -->
<xsl:template name="compareToday">
<xsl:param name="date"/>
<xsl:if test="$date = $today">
<xsl:text>true</xsl:text>
</xsl:if>
</xsl:template>
I need to compare the /document/item/effectivetime/low/#value to the value I have stored in the variable $today so that I can make a line in the output (html) be bold format. This is what I have currently to do the compare:
<xsl:variable name="IsToday">
<xsl:call-template name="compareToday">
<xsl:with-param name="date" select="/document/item/effectiveTime/low/#value"/>
</xsl:call-template>
</xsl:variable>
<span>
<xsl:if test="$IsToday = 'true'">
<xsl:attribute name="style">
<xsl:text>font-weight:bold;</xsl:text>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="/document/item/effectiveTime/low/#value" />
</span>
This doesn't work because it's trying to compare 20131008 against 10/08/2013. I can't seem to get the format to be done first before doing the compare. Most (but not all) of the dates in my document are in the YYYYMMDD format.
Thanks
I realized what I needed to do. I have to make a variable with the current date first that is formatted correctly. Then pass that variable name to the compare.
<xsl:variable name="itemDate">
<xsl:call-template name="formatDate">
<xsl:with-param name="date" select="/document/item/effectiveTime/low/#value"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="IsToday">
<xsl:call-template name="compareToday">
<xsl:with-param name="date" select="$itemDate"/>
</xsl:call-template>
</xsl:variable>
This allows me to compare apples to apples in terms of formatting.
Try following adjustment
<xsl:variable name="IsToday">
<!-- Store formated date in temporary variable -->
<xsl:variable name="tmp">
<xsl:call-template name="formatDate">
<xsl:with-param name="date" select="/document/item/effectiveTime/low/#value"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="compareToday">
<!-- Pass temporary variable into compare template -->
<xsl:with-param name="date" select="$tmp"/>
</xsl:call-template>
</xsl:variable>
Or you could nest calling of another named template into xsl:with-param element like
<xsl:variable name="IsToday">
<xsl:call-template name="compareToday">
<xsl:with-param name="date">
<!-- Another named template call nested in xsl:with-param -->
<xsl:call-template name="formatDate">
<xsl:with-param name="date" select="/document/item/effectiveTime/low/#value"/>
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:variable>

"Standard" for in XSLT1

I have a template with a parameter. How can I print something n times?
n is the value of the parameter. I must use XSLT 1.0...
There's a cleaner way insted of this:
<xsl:for-each select="//*[position() <= $count]"> </xsl:for-each>
Your approach will work if your source XML has $count elements, but I wouldn't say that's a very good way to go about it. This is a lot more verbose, but I'd suggest defining a template like this:
<xsl:template name="RepeatValue">
<xsl:param name="times" />
<xsl:param name="value" />
<xsl:if test="$times > 0">
<xsl:value-of select="$value" />
<xsl:call-template name="RepeatValue">
<xsl:with-param name="times" select="$times - 1" />
<xsl:with-param name="value" select="$value" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Then you would call this template using:
<xsl:call-template name="RepeatValue">
<xsl:with-param name="times" select="$count" />
<xsl:with-param name="value" select="' '" />
</xsl:call-template>

How do I do a str:replace in XSLT/XPATH 1.0?

In XPATH 2.0 there is a function that allows me to replace a substring in a string with another string. I'd like to do this using xalan. Unfortunately, it doesn't support the EXSLT method str:replace and it only uses XSLT 1.0 stylesheets. Including the function from exslt.org doesn't seem to work. If I try using the function style, it complains that it can't find str:replace. If I try using the template style, it complains that it can't find node-set, even though it is supported. translate is useless since it's just a character swap. Any ideas?
You can write your own function which can immitate xslt 2.0 replace :
<xsl:template name="replace">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="replace">
<xsl:with-param name="text"
select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
If you call it like this :
<xsl:variable name="replacedString">
<xsl:call-template name="replace">
<xsl:with-param name="text" select="'This'" />
<xsl:with-param name="replace" select="'This'" />
<xsl:with-param name="by" select="'That'" />
</xsl:call-template>
Your resulting $replacedString will have the value "That"