Nesting of xsl:when conditions - xslt

I have a query related to xsl choose , inside xsl choose is being used
Now i have two xsl:when condition is there like as hsown below
<xsl:choose>
<xsl:when test=$labcVar=$cde>
<xsl:when test="$erf=$der">
<xsl:value-of select="'edr'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'fre'" />
</xsl:otherwise>
please advise is it the correct approach since insise xsl:when ..I have to begain only when first when condition becomes true the only I should go for second xsl:when condition, please advsie it is aloowed or not

You can't nest a when directly inside a when, but you can use another choose:
<xsl:choose>
<xsl:when test=$labcVar=$cde>
<xsl:choose>
<xsl:when test="$erf=$der">
<xsl:value-of select="'edr'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'fre'" />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
</xsl:choose>

How about using and
<xsl:when test="$labcVar=$cde and $erf=$der">
<xsl:value-of select="'edr'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'fre'" />
</xsl:otherwise>

Related

Implement dynamic xpath in XSLT: for-each-group

I am trying to use saxon:evaluate to reduce repeated code in an xsl function.
However anything I try returns an error.
This is a section of the repetitive code.
<!--Select by outputclass and group by attribute-->
<xsl:when test="$from='oclass-attribute'">
<xsl:for-each-group select="$compose//*[contains(#outputclass,$sel)]" group-by="#*[name()=$group]">
<xsl:sort select="current-grouping-key()" />
<element key="{current-grouping-key()}">
<xsl:if test="number(current-group()[1])=number(current-group()[1])">
<xsl:attribute name="max" select="max(current-group())"/>
<xsl:attribute name="sum" select="sum(current-group())"/>
</xsl:if>
<xsl:copy-of select="current-group()[1]"/>
</element>
</xsl:for-each-group>
</xsl:when>
<!--Select by node and group by child node-->
<xsl:when test="$from='node-childnode'">
<xsl:for-each-group select="$compose//*[name()=$sel]" group-by="child::*[name()=$group]">
<xsl:sort select="current-grouping-key()" />
<element key="{current-grouping-key()}">
<xsl:if test="number(current-group()[1])=number(current-group()[1])">
<xsl:attribute name="max" select="max(current-group())"/>
<xsl:attribute name="sum" select="sum(current-group())"/>
</xsl:if>
<xsl:copy-of select="current-group()[1]"/>
</element>
</xsl:for-each-group>
</xsl:when>
What I want is to pass a parameter dictating whether it is an element-name or outputclass-attribute that is selected.
Then another parameter dictating what to group by: attribute or parent, child, following or preceding node.
What I have tried is below:
<xsl:variable name="oSel">
<xsl:choose>
<xsl:when test="starts-with($from,'oclass-')">
<xsl:value-of select="$compose//*[contains(#outputclass,$sel)]"/>
</xsl:when>
<xsl:when test="starts-with($from,'node-')">
<xsl:value-of select="$compose//*[name()=$sel]"/>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="oGroup">
<xsl:choose>
<xsl:when test="ends-with($from,'-attribute')">
<xsl:value-of select="*/#*[local-name()=$group]"/>
</xsl:when>
<xsl:when test="ends-with($from,'-childnode')">
<xsl:value-of select="*/*[name()=$group]"/>
</xsl:when>
<xsl:when test="ends-with($from,'-parentnode')">
<xsl:value-of select="parent::*[name()=$group]"/>
</xsl:when>
<xsl:when test="ends-with($from,'-followingnode')">
<xsl:value-of select="following-sibling::*[name()=$group][1]"/>
</xsl:when>
<xsl:when test="ends-with($from,'-precedingnode')">
<xsl:value-of select="preceding-sibling::*[name()=$group][1]"/>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="test">
<!--<xsl:for-each-group select="saxon:evaluate($oSel)" group-by="saxon:evaluate($oGroup)">-->
<xsl:for-each-group select="saxon:evaluate($oSel)" group-by="saxon:evaluate($oGroup)">
<element key="{current-grouping-key()}">
<xsl:if test="number(current-group()[1])=number(current-group()[1])">
<xsl:attribute name="max" select="max(current-group())"/>
<xsl:attribute name="sum" select="sum(current-group())"/>
</xsl:if>
<xsl:copy-of select="current-group()[1]"/>
</element>
</xsl:for-each-group>
</xsl:variable>
I have looked into the saxon documentation and tried all sorts of solutions but none of them are working. Is it possible to do this?
Should have added that I am using Saxon 9.1.0.8 - Sorry, still new to XSLT
Firstly, saxon:evaluate() isn't the right tool for the job.
Now, why isn't it working? To declare the value of $oSel, you've done something like this:
<xsl:value-of select="$compose//*[contains(#outputclass,$sel)]"/>
which evaluates the expression in the select attribute and returns its result. But you're then passing $oSel to saxon:evaluate(), which expects a string containing an XPath expression. I think you're trying to bind the variable to the expression "$compose//*[contains(#outputclass,$sel)]", not to the result of evaluating this expression. To do that you would have to write
<xsl:value-of select="'$compose//*[contains(#outputclass,$sel)]'"/>
Note the extra quotes; but that would now fail because the expression passed to saxon:evaluate() can't explicitly use variables such as $compose (there's a mechanism to pass parameters, but you don't really want to go there).
In XSLT 3.0 saxon:evaluate is superseded by the standard instruction xsl:evaluate; but you don't want that one either.
The right mechanism to be using here is higher order functions.
In XSLT 3.0 you can write
<xsl:for-each-group select="$compose//*[$predicate(.)]" group-by="$grouping-key(.)">
Where $predicate and $grouping-key are variables bound to user-defined functions. You can bind these variables with logic like this:
<xsl:variable name="predicate" as="function(element()) as xs:boolean">
<xsl:choose>
<xsl:when test="starts-with($from,'oclass-')">
<xsl:sequence select="function($n){contains($n/#outputclass,$sel)}"/>
</xsl:when>
<xsl:when test="starts-with($from,'node-')">
<xsl:sequence select="function($n){name($n)=$sel}"/>
</xsl:when>
</xsl:choose>
</xsl:variable>
Change the xpath as below.
$compose//*[name()=$sel or contains(#outputclass,$sel)]
Here is the final code looks like
<xsl:when test="$from='oclass-attribute'">
<xsl:for-each-group select="$compose//*[name()=$sel or contains(#outputclass,$sel)]" group-by="#*[name()=$group]">
<xsl:sort select="current-grouping-key()" />
<element key="{current-grouping-key()}">
<xsl:if test="number(current-group()[1])=number(current-group()[1])">
<xsl:attribute name="max" select="max(current-group())"/>
<xsl:attribute name="sum" select="sum(current-group())"/>
</xsl:if>
<xsl:copy-of select="current-group()[1]"/>
</element>
</xsl:for-each-group>
Try
select="if ($from='oclass-attribute') then $compose//*[contains(#outputclass,$sel)] else $compose//*[name()=$sel]"
and use the same approach for the group-by attribute:
group-by="if ($from='oclass-attribute') then #*[name()=$group] else child::*[name()=$group]"

reformatting dates with square brackets

When transferring my XML-files to TeX I try to reformat dates – my publisher said I had to use a smaller horizontal space between dates – and can't manage to go beyond the first step.
My input file is this
<a>
<date>January 1900</date>
<date>2. 2. 1902</date>
<date>3. [3]. 1903</date>
<date>[4. 4. 1904]</date>
</a>
where brackets mean that the date is not certain. There are all possible combination of brackets, e.g. second number of the year: 1[9]00. I created a command \mini which makes small space in TeX:
\newcommand{\mini}{\,}
The result after the xslt should be:
January 1900
2.{\mini}2.{\mini}1902
3.{\mini}[3].{\mini}1903
[4.{\mini}4.{\mini}1904]
I wrote a function, which tries to extract the square brackets and store their position to a variable and afterwards concat them back again. But as I don't manage to get the variables to show correct positions, I'm stuck:
<xsl:function name="foo:date-translate">
<xsl:param name="date-string" as="xs:string"/>
<xsl:variable name="opening-square-bracket" as="xs:integer" select="count(substring-before($date-string,'['))"/>
<xsl:variable name="closing-square-bracket" as="xs:integer" select="count(substring-before($date-string,'['))"/>
<xsl:variable name="date-string-without-square-brackets" as="xs:string" select="replace(replace($date-string,'\[',''),'\]','')"/>
<xsl:choose>
<xsl:when test="matches($date-string-without-square-brackets,'\d{1,2}. \d{1,2}. \d{4}')">
<xsl:choose>
<xsl:when test="not(contains($date-string,'['))">
<xsl:value-of select="replace($date-string,'(\d{1,2}). (\d{1,2}). (\d{4})','$1\\mini$2\\mini$3')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(substring(replace($date-string-without-square-brackets,'(\d{1,2}). (\d{1,2}). (\d{4})','$1\\mini$2\\mini$3'),0,$opening-square-bracket),'[',substring(replace($date-string-without-square-brackets,'(\d{1,2}). (\d{1,2}). (\d{4})','$1\\mini$2\\mini$3'),$opening-square-bracket, $closing-square-bracket))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$date-string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
I finally managed to solve it with recursion. Basically I have the test whether it suits the regex dd. mm. yyyy when the brackets are removed. as this works i can now rebuild the whole string.
<xsl:function name="foo:date-repeat">
<xsl:param name="date-string" as="xs:string"/>
<xsl:param name="amount" as="xs:integer"/>
<xsl:param name="counter" as="xs:integer"/>
<xsl:choose>
<xsl:when test="substring($date-string,$counter,1) =' '">
<xsl:text>\mini</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($date-string,$counter,1)"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$counter <= $amount">
<xsl:value-of select="foo:date-repeat($date-string, $amount,$counter+1)"/>
</xsl:if>
</xsl:function>
<xsl:function name="foo:date-translate">
<xsl:param name="date-string" as="xs:string"/>
<xsl:variable name="date-string-without-square-brackets" as="xs:string" select="replace(replace($date-string,'\[',''),'\]','')"/>
<xsl:choose>
<xsl:when test="matches($date-string-without-square-brackets,'\d{1,2}. \d{1,2}. \d{4}')">
<xsl:choose>
<xsl:when test="not(contains($date-string,'['))"> <!-- Daten ohne eckige Klammer -->
<xsl:value-of select="replace($date-string,'(\d{1,2}). (\d{1,2}). (\d{4})','$1\\mini$2\\mini$3')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="foo:date-repeat($date-string, string-length($date-string),1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$date-string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
I have even expanded my solution, now the function checks tests every space ' ' if before is a dot or a dot and a bracket and afterwards is a number or a bracket and a number:
<xsl:function name="foo:date-repeat">
<xsl:param name="date-string" as="xs:string"/>
<xsl:param name="amount" as="xs:integer"/>
<xsl:param name="counter" as="xs:integer"/>
<xsl:choose>
<xsl:when test="substring($date-string,$counter,1) =' ' and ((substring($date-string,$counter -1,1) = '.' and number(substring($date-string,$counter -2,1)) = number(substring($date-string,$counter -2,1))) or (substring($date-string,$counter -2,2) = '.]' and number(substring($date-string,$counter -3,1)) = number(substring($date-string,$counter -3,1))))">
<xsl:choose>
<xsl:when test="number(substring($date-string,$counter +1,1)) = number(substring($date-string,$counter +1,1))">
<xsl:text>\mini</xsl:text>
</xsl:when>
<xsl:when test="substring($date-string,$counter +1,1) ='[' and number(substring($date-string,$counter +2,1)) = number(substring($date-string,$counter +2,1))">
<xsl:text>\mini</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($date-string,$counter,1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="substring($date-string,$counter,1) ='['">
<xsl:text>{[}</xsl:text>
</xsl:when>
<xsl:when test="substring($date-string,$counter,1) =']'">
<xsl:text>{]}</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($date-string,$counter,1)"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$counter <= $amount">
<xsl:value-of select="foo:date-repeat($date-string, $amount,$counter+1)"/>
</xsl:if>

putting the xsl condition in a better way

Please advise is there any better way to express this below xsl condition or this below one is correct.. we are using xslt 1.0
<xsl:if test="$abcPeriod_first=gfd_Rate">
<xsl:value-of select="'AAA'" />
</xsl:if>
<xsl:value-of select="'BBB'" />
If you want an if...else then I believe your current condition is not sufficient. You could use the following:
<xsl:choose>
<xsl:when test="$abcPeriod_first=gfd_Rate">
<xsl:value-of select="'AAA'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'BBB'" />
</xsl:otherwise>
</xsl:choose>

embeding od condition in xsl when getting error

below is the xsl tag on which I am getting the error as i have used xsl:when intead of xsl:if , folks please advise how can I re correct it so that i do not get compilation exception while transforming the xsl again I am using xslt 1.0
<xsl:when test="abcid=dec_id">
<xsl:for-each select="$qq_Obj/ert_Period/ytr_Period">
<xsl:variable name="ABC_Rate">
<xsl:value-of select="$Sds/oht_Period/rew_Period/#vgRate" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$iue_first=#vgRate">
<xsl:value-of select="'AAA'" />
</xsl:when>
<xsl:value-of select="'BBB'" />
</xsl:choose>
</xsl:for-each>
</xsl:when>
You need to enclose the "else" part of an xsl:choose in an xsl:otherwise:
<xsl:choose>
<xsl:when test="$iue_first=#vgRate">
<xsl:value-of select="'AAA'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'BBB'" />
</xsl:otherwise>
</xsl:choose>

How to transform a number (1,2,3, etc) into an ordinal number (1st, 2nd, 3rd, etc) using xslt

Pretty simple question, how can I transform a number (1, 2, 3, etc) into a print friendly ordinal number (1st, 2nd, 3rd, etc) using xslt?
Currently the following works for 1-20 but we may be seeing larger sets of entities getting ranked soon:
<xsl:template name="FormatRanking">
<xsl:param name="Value"></xsl:param>
<xsl:choose>
<xsl:when test="$Value = '1'">
<xsl:value-of select="$Value"/>st
</xsl:when>
<xsl:when test="$Value = '2'">
<xsl:value-of select="$Value"/>nd
</xsl:when>
<xsl:when test="$Value = '3'">
<xsl:value-of select="$Value"/>rd
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Value"/>th
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The only way I would know how to do this would be to change the xsl:when's:
<xsl:when test="$Value = '1'">
<xsl:when test="$Value = '2'">
<xsl:when test="$Value = '3'">
to (not even sure if this is correct):
<xsl:when test="$Value = '1' or $Value = '21' or $Value = '31' ...">
<xsl:when test="$Value = '2' or $Value = '22' or $Value = '33' ...">
<xsl:when test="$Value = '3' or $Value = '22' or $Value = '33' ...">
I'd like to do something similar to this Is there an easy way to create ordinals in C#? but I'm not sure if it's possible in Xslt.
At this point we only need an English solution.
Here's the solution from "Is there an easy way to create ordinals in C#?", translated to XSLT:
<xsl:template name="FormatRanking">
<xsl:param name="Value" select="0" />
<xsl:value-of select="$Value"/>
<!-- a little parameter sanity check (integer > 0) -->
<xsl:if test="
translate($Value, '0123456789', '') = ''
and
$Value > 0
">
<xsl:variable name="mod100" select="$Value mod 100" />
<xsl:variable name="mod10" select="$Value mod 10" />
<xsl:choose>
<xsl:when test="$mod100 = 11 or $mod100 = 12 or $mod100 = 13">th</xsl:when>
<xsl:when test="$mod10 = 1">st</xsl:when>
<xsl:when test="$mod10 = 2">nd</xsl:when>
<xsl:when test="$mod10 = 3">rd</xsl:when>
<xsl:otherwise>th</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
I'm not saying it's a good idea to do this in xslt, but...
<xsl:template name="FormatRanking">
<xsl:param name="Value"></xsl:param>
<xsl:choose>
<xsl:when test="substring($Value,string-length($Value)-1) = '11'">
<xsl:value-of select="$Value"/>th
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)-1) = '12'">
<xsl:value-of select="$Value"/>th
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)-1) = '13'">
<xsl:value-of select="$Value"/>th
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)) = '1'">
<xsl:value-of select="$Value"/>st
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)) = '2'">
<xsl:value-of select="$Value"/>nd
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)) = '3'">
<xsl:value-of select="$Value"/>rd
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Value"/>th
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Edit: or a bit neater solution:
<xsl:template name="FormatRanking">
<xsl:param name="Value"></xsl:param>
<xsl:variable name="penultimateChar" select="substring($Value,string-length($Value)-1, 1)"/>
<xsl:variable name="lastChar" select="substring($Value,string-length($Value))"/>
<xsl:value-of select="$Value"/>
<xsl:choose>
<xsl:when test="$penultimateChar= '1'">
<xsl:text>th </xsl:text>
</xsl:when>
<xsl:when test="$lastChar = '1'">
<xsl:text>st </xsl:text>
</xsl:when>
<xsl:when test="$lastChar = '2'">
<xsl:text>nd </xsl:text>
</xsl:when>
<xsl:when test="$lastChar = '3'">
<xsl:text>rd </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>th </xsl:text>
</xsl:otherwise>
</xsl:choose>
IMO, by using regular expressions (XSLT 2.0), this can be done more concisely:
<xsl:template name="FormatRanking">
<xsl:param name="Value"/>
<xsl:choose>
<xsl:when test="matches(string($Value), '.+[^1][123]$')">
<xsl:value-of select="replace(replace(replace(string($Value), '1$', '1st'), '2$', '2nd'), '3$', '3rd')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(string($Value), 'th')" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This checks for applicability of the special rule before using a regular expression which recognises number strings ending on 1/2/3, excluding 11/12/13. Only then special handling is applied. Otherwise the "th" is just appended.
Please note that the $Value is converted explicitly to a string datatype before applying string operations to avoid potentially funny situations. The return value is anyway implicitly a string.
However, just as a side comment, this only works for the English language. I'd be interested in a true multilingual approach....