How do I call a template within a template? - xslt

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>

Related

Attaching parent attributes to child nodes with multiple children

this is linked from Attaching ancestor attributes to child nodes
I'm extracting names from a large xml dataset, I need to extract displayname, and the other name types (currently I am only picking out Synonyms and SystematicNames). Right now with the help of an awesome person I've gotten so far, but it only extracts the first of each type...
Sample XML
<Chemical id="0000103902" displayFormula="C8-H9-N-O2" displayName="Acetaminophen [USP:JAN]">
<NameList>
<DescriptorName>Acetaminophen<SourceList><Source>MeSH</Source></SourceList></DescriptorName>
<NameOfSubstance>Acetaminophen<SourceList><Source>HSDB</Source><Source>MeSH</Source></SourceList></NameOfSubstance>
<NameOfSubstance>Acetaminophen [USP:JAN]<SourceList><Source>NLM</Source></SourceList></NameOfSubstance>
<MixtureName>Actifed Plus<SourceList><Source>MeSH</Source></SourceList></MixtureName>
<MixtureName>Jin Gang<SourceList><Source>NLM</Source></SourceList></MixtureName>
<MixtureName>Talacen<SourceList><Source>NLM</Source></SourceList></MixtureName>
<SystematicName>Acetamide, N-(4-hydroxyphenyl)-<SourceList><Source>EPA SRS</Source><Source>MeSH</Source><Source>TSCAINV</Source></SourceList></SystematicName>
<SystematicName>Acetaminophen<SourceList><Source>CCRIS</Source></SourceList></SystematicName>
<SystematicName>Acetanilide, 4'-hydroxy-<SourceList><Source>RTECS</Source></SourceList></SystematicName>
<SystematicName>Paracetamol<SourceList><Source>ECHA</Source><Source>EINECS</Source></SourceList></SystematicName>
<Synonyms>4-13-00-01091 (Beilstein Handbook Reference)<SourceList><Source>RTECS</Source></SourceList></Synonyms>
<Synonyms>Abensanil<SourceList><Source>HSDB</Source><Source>RTECS</Source></SourceList></Synonyms>
<Synonyms>Acetagesic<SourceList><Source>HSDB</Source><Source>RTECS</Source></SourceList></Synonyms>
<Synonyms>Acetamide, N-(p-hydroxyphenyl)-<SourceList><Source>RTECS</Source></SourceList></Synonyms>
</NameList>
</Chemical>
Current code
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:variable name="FS">
<!-- Field seperator -->
<xsl:text>;</xsl:text>
</xsl:variable>
<xsl:variable name="LT">
<!-- Line terminator -->
<xsl:text>
</xsl:text>
</xsl:variable>
<xsl:strip-space elements="*" />
<xsl:template match="/">
<xsl:for-each select="//Chemical[#displayName != '' and #displayName != 'INDEX NAME NOT YET ASSIGNED']">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="#id" />
<xsl:with-param name="val2" select="#displayName" />
</xsl:call-template>
<xsl:if test="normalize-space(NameList/SystematicName/text()) != ''">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="#id" />
<xsl:with-param name="val2" select="normalize-space(NameList/SystematicName/text())" />
</xsl:call-template>
</xsl:if>
<xsl:if test="normalize-space(NameList/Synonyms/text()) != ''">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="#id" />
<xsl:with-param name="val2" select="normalize-space(NameList/Synonyms/text())" />
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="printValues">
<xsl:param name="val1" />
<xsl:param name="val2" />
<!-- constants -->
<xsl:variable name="url" select="'https://chem.nlm.nih.gov/chemidplus/sid/startswith/'" />
<xsl:variable name="src" select="'nlm'" />
<xsl:text>"</xsl:text>
<xsl:call-template name="escapeQuote">
<xsl:with-param name="paramStr" select="$val2" />
</xsl:call-template>
<xsl:text>"</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="concat($url, $val1)" />
<xsl:text>"</xsl:text>
<xsl:text>,</xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="$src" />
<xsl:text>"</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="escapeQuote">
<xsl:param name="paramStr" />
<xsl:if test="string-length($paramStr) > 0">
<xsl:value-of select="substring-before(concat($paramStr, '"'), '"')" />
<xsl:if test="contains($paramStr, '"')">
<xsl:text>\"</xsl:text>
<xsl:call-template name="escapeQuote">
<xsl:with-param name="paramStr" select="substring-after($paramStr, '"')" />
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
However this only gives:-
"Acetaminophen [USP:JAN]","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetamide, N-(4-hydroxyphenyl)-","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"4-13-00-01091 (Beilstein Handbook Reference)","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
How do I go about extracting all the children in the same manner?
Printing of the text values in the <SystematicName> and <Synonyms> nodes can be achieved by adding <xsl:for-each> loop for those elements. The <xsl:if> condition can also be handled in the <xsl:for-each> selection.
Please modify the <xsl:template match="/"> as shown below.
<xsl:template match="Chemical[#displayName != '' and #displayName != 'INDEX NAME NOT YET ASSIGNED']">
<xsl:variable name="idValue" select="#id" />
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="$idValue" />
<xsl:with-param name="val2" select="#displayName" />
</xsl:call-template>
<xsl:for-each select="NameList/SystematicName[text() != '']">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="$idValue" />
<xsl:with-param name="val2" select="normalize-space(text())" />
</xsl:call-template>
</xsl:for-each>
<xsl:for-each select="NameList/Synonyms[text() != '']">
<xsl:call-template name="printValues">
<xsl:with-param name="val1" select="$idValue" />
<xsl:with-param name="val2" select="normalize-space(text())" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
Output
"Acetaminophen [USP:JAN]","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetamide, N-(4-hydroxyphenyl)-","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetaminophen","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetanilide, 4'-hydroxy-","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Paracetamol","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"4-13-00-01091 (Beilstein Handbook Reference)","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Abensanil","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetagesic","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"
"Acetamide, N-(p-hydroxyphenyl)-","https://chem.nlm.nih.gov/chemidplus/sid/startswith/0000103902","nlm"

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>

Complex selection of XSL 1.0 node set

(This question is a less simplified version of my problem. The more simplified version which was already answered can be found here. I'm posting this more complicated question due to a comment by michael.hor257k who suggested that there may be an alternative approach that could solve it - possibly using select in a loop, or possibly a completely different approach.)
I'd like to process an XML file, over whose format I have no control, to generate C++ code. I need to process functions defined in XML in several different ways to produce different parts of the code. As part of this I need to select a subset of function parameters that match a complicated criteria and pass this selection to several named templates; the named templates need to be able to access the original document.
This example creates a complex selection of C++ function parameters that do not have constant values (ie the same min and max), where the min and max may be decimal or hexadecimal, using the "GenerateNonFixedParameters" template. The parameters refer to enumerations which are located elsewhere in the document, and these definitions are referenced by the named template call "ListParameterValues".
There are two problems.
The creation of the variable "nonFixedParameters" does not use select. I cannot work out how to use select for such a complicated case (XSL 1.0), but maybe there is a way.
A copy of the nodes does not suffice, as the "ListParameterValues" template as it currently stands needs to operate on an original set of nodes from the document.
Example XSL with the locations of these two problems marked:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:for-each select="//function">
<!-- 1. This does not use 'select' therefore it does not work. This is XSL 1.0 so as="node()*" cannot be used. -->
<xsl:variable name="nonFixedParameters">
<xsl:call-template name="GenerateNonFixedParameters"/>
</xsl:variable>
<xsl:call-template name="ListParameterValues">
<xsl:with-param name="parameters" select="$nonFixedParameters"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="ListParameterValues">
<xsl:param name="parameters"/>
<xsl:for-each select="$parameters">
<xsl:value-of select="#name"/>
<xsl:text>[</xsl:text>
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<!-- 2. This must be executed in the context of a document node, therefore this does not work. -->
<xsl:for-each select="//enum[#name=current()/#enum]/value">
<xsl:if test="#val >= $min and #val <= $max">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="GenerateNonFixedParameters">
<xsl:for-each select="parameter">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$min != $max">
<!-- Here a copy is clearly the wrong approach! -->
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:transform>
Simple XML to feed the above:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<dictionary>
<enum name="EnumName">
<value name="firstValue" val="1" />
<value name="secondValue" val="2" />
<value name="thirdValue" val="3" />
<value name="forthValue" val="4" />
<value name="fifthValue" val="5" />
</enum>
</dictionary>
<function name="FunctionOne">
<parameter name="p1" type="enum" enum="EnumName" min="2" max="0x4"/>
<parameter name="p2" type="enum" enum="EnumName" min="0x03" max="3"/>
</function>
</body>
Wanted output. Note that p1 has all names within [min..max] listed, but p2 has none listed because min and max have the same value.
p1[secondValue thirdValue forthValue ] p2[]
I think your stylesheet can be made to work with XSLT 1.0 if you use an extension function like exsl:node-set to convert your result tree fragment into a node-set and if you store the root node of the primary input tree into a global variable or parameter as then you will be able to compare nodes in your primary input document to nodes of the newly constructed, temporary tree.
Based on these suggestions the code would look like
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:exsl="http://exslt.org/common">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:variable name="main-root" select="/"/>
<xsl:template match="/">
<xsl:for-each select="//function">
<!-- 1. Using exsl:node-set or similar you can convert that result tree fragment into a node set to process it further -->
<xsl:variable name="nonFixedParameters">
<xsl:call-template name="GenerateNonFixedParameters"/>
</xsl:variable>
<xsl:call-template name="ListParameterValues">
<xsl:with-param name="parameters" select="$nonFixedParameters"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="ListParameterValues">
<xsl:param name="parameters"/>
<!-- <xsl:for-each xmlns:ms="urn:schemas-microsoft-com:xslt" select="ms:node-set($parameters)/parameter"> for MSXML or XslTransform -->
<xsl:for-each select="exsl:node-set($parameters)/parameter">
<xsl:value-of select="#name"/>
<xsl:text>[</xsl:text>
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<!-- 2. This must be executed in the context of a document node, therefore using the global variable works. -->
<xsl:for-each select="$main-root//enum[#name=current()/#enum]/value">
<xsl:if test="#val >= $min and #val <= $max">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="GenerateNonFixedParameters">
<xsl:for-each select="parameter">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$min != $max">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:transform>
The example is online at http://xsltransform.net/94hvTzi/1.
Let me show a different approach that actually selects and processes the original nodes, in their original context - as was discussed in the previous thread. Consider:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text" encoding="utf-8"/>
<xsl:template match="/">
<xsl:for-each select="body/function">
<xsl:call-template name="select-parameters">
<xsl:with-param name="input-set" select="parameter"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="select-parameters">
<xsl:param name="input-set"/>
<xsl:param name="output-set" select="dummy-node"/>
<xsl:variable name="current-node" select="$input-set[1]" />
<xsl:choose>
<xsl:when test="$current-node">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="$current-node/#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="$current-node/#max" />
</xsl:call-template>
</xsl:variable>
<!-- recursive call -->
<xsl:call-template name="select-parameters">
<xsl:with-param name="input-set" select="$input-set[position() > 1]"/>
<xsl:with-param name="output-set" select="$output-set | $current-node[$min != $max]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- call a template to process the currently selected node-set -->
<xsl:call-template name="process-parameters">
<xsl:with-param name="input-set" select="$output-set"/>
</xsl:call-template>
<!-- call more templates here, if required -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:key name="enum-by-name" match="enum" use="#name" />
<xsl:template name="process-parameters">
<xsl:param name="input-set"/>
<xsl:for-each select="$input-set">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat(#name, '[')"/>
<xsl:for-each select="key('enum-by-name', #enum)/value[#val >= $min and #val <= $max]">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The problem with this approach is that it works exactly as advertised; the nodes selected at the end of the selecting processes are the original, unmodified parameters. As a result, they still carry the mixture of decimal and hexadecimal values, and you must convert these again when processing the selected set.
So it might well be more worthwhile to pre-process the parameters by normalizing the values to a common base, then use the result (converted to a node-set) for the rest of the processing. I wouldn't spend so much effort at selecting those that meet the criteria - because once the values are consistent, the selection becomes trivial. If you like, I will post a demo showing that.

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.