Retain number of digits during processing input->number->output during XSLT processing - xslt

For this XSLT:
<xsl:variable name="source0" select="number(num2)"/>
<xsl:variable name="source1" select="number(num3)"/>
s0 plain: <xsl:value-of select="$source0"/>
s1 plain: <xsl:value-of select="$source1"/>
test11: <xsl:value-of select="format-number($source0, '#.#')"/>
test12: <xsl:value-of select="format-number($source0, '#.###############')"/>
test21: <xsl:value-of select="format-number($source1, '#.#')"/>
test22: <xsl:value-of select="format-number($source1, '#.###############')"/>
For XML:
<num2>123456.1234</num2>
<num3>1234567.1234</num3>
I get this output (using Saxon 9.2, XSLT 2.0)
s0 plain: 123456.1234
s1 plain: 1.2345671234E6
test11: 123456.1
test12: 123456.123399999996764
test21: 1234567.1
test22: 1234567.123399999924004
First off... I'm curious why does it suddenly switch between standard and scientific notation when it exceeds 6 digits to the left of decimal place? This is my problem, I want to avoid scientific notation. After various other questions, I discover apparently I'm stuck with putting format-number everywhere.
But format-number doesn't appear to work either. In spite of the fact that the output of "s1 plain" proves that the number of significant digits is known to the processor (I understand about converting to double and back can lose precision, but there is the correct number after such a conversion, so...?), there appears to be no way to output that value in standard non-scientific notation. Is there?

This is my problem, I want to avoid
scientific notation. After various
other questions, I discover apparently
I'm stuck with putting format-number
everywhere.
But format-number doesn't appear to
work either.
Apparently you are not using the appropriate features of XSLT 2.0/XPath 2.0.
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:variable name="vs0" as="xs:decimal"
select="xs:decimal(num2)"/>
<xsl:variable name="vs1" as="xs:decimal"
select="xs:decimal(num3)"/>
s0 plain: <xsl:value-of select="$vs0"/>
s1 plain: <xsl:value-of select="$vs1"/>
test11: <xsl:value-of select="format-number($vs0, '#.#')"/>
test12: <xsl:value-of select="format-number($vs0, '#.###############')"/>
test21: <xsl:value-of select="format-number($vs1, '#.#')"/>
test22: <xsl:value-of select="format-number($vs1, '#.###############')"/>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<nums>
<num2>123456.1234</num2>
<num3>1234567.1234</num3>
</nums>
Produces exact results:
s0 plain: 123456.1234
s1 plain: 1234567.1234
test11: 123456.1
test12: 123456.1234
test21: 1234567.1
test22: 1234567.1234
Conclusion: When in need of good precision, always try to use the xs:decimal data-type.

Related

Implementing product() in XSLT

XPath 2.0 (and 3.0) has convenient aggregate functions sum() and avg(), but nothing that returns the product of a sequence of numeric atomic values.
Although implementing such a function is trivial in a language that allows assignment statements, it seems not to be that easy in XSLT. The only way I've found to get an aggregate product is to use recursion:
<xsl:function name="fn:products">
<xsl:param name="input" />
<xsl:param name="total" />
<xsl:if test="exists($input)">
<xsl:variable name="x" select="$input[1] * $total"/>
<xsl:sequence select="$x"/>
<xsl:sequence select="fn:products(subsequence($input,2),$x)"/>
</xsl:if>
</xsl:function>
But the above is actually my adaptation of a function that appears on p.994 of Michael Kay's book XSLT 2.0 and XPath 2.0 4th Edition. The original function outputs a sequence of running balances by recursively adding a sequence of numbers.
My version just multiplies the numbers, instead of adding them. The result is then a sequence of "running products". For example, fn:products((4, 0.75, 10, 0.7), 1) returns the sequence 4, 3, 30, 21.
Since I'm only interested in the product of all numbers, I just take the last item of the output with a filter expression: fn:products((4, 0.75, 10, 0.7), 1)[last()].
It works. But just for the sake of making good code, I wonder if there is a way to make the function to just directly return the last item as the aggregate product (i.e. just get 21, in the example above).
I tried a couple of things, but they just broke the function. Is there a way to achieve this? Is it possible also to implement it in a better, leaner way (the original function was not meant to return a singleton sequence)?
If you use one of the fancy processors, you can use dyn:evaluate.
<?xml version="1.0" encoding="UTF-8"?>
<test>
<num>4</num>
<num>0.75</num>
<num>10</num>
<num>0.7</num>
</test>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dyn="http://exslt.org/dyn/dyn.xml"
extension-element-prefixes="dyn"
>
<xsl:template match="/">
<xsl:variable name="run">
<xsl:for-each select="test/num"><xsl:value-of select="."/><xsl:if test="position() != last()"> * </xsl:if></xsl:for-each>
</xsl:variable>
<xsl:value-of select="dyn:evaluate(string($run))"/>
</xsl:template >
</xsl:stylesheet>

Incrementer in Nested for-each-group Calls

First post here after looking at tons of awesome suggestions from the community.
I have three fields in XSLT 2.0, all at the same level (shoulders, knees, and toes). I am needing to output sums of toes based on unique combinations of shoulders and knees, so I have created two nested for-each-groups. On each output, I'm also needing to output an incrementer from from 1 to number of unique combinations of shoulders and knees.
This incrementer is where I'm having issues. The closest I've come is by calling position(), but if I call it in the innermost group, the counter resets at each unique shoulder. If I call it in the outermost group, every knee inside of a unique shoulder gets the same value, then it resets at each unique shoulder. If I call it outside of the groups completely, it never gets past 1. I've also tried to use xsl:number , keys, etc., to no avail. In those cases, the correct number of rows are still being printed, but the incrementer values are looking at the individual, non-grouped values.
I read one suggestion about "tunneling" values between templates, but I haven't been able to get that to work, mostly because I don't think I'm invoking the templates correctly (with these fields being same-level and not parent-child). Any thoughts on making this work with for-each-group or otherwise? Many thanks in advance.
Sample XML:
<bodies>
<parts>
<shoulders>shoulders1</shoulders>
<knees>knees1</knees>
<toes>1</toes>
</parts>
<parts>
<shoulders>shoulders2</shoulders>
<knees>knees2</knees>
<toes>2</toes>
</parts>
<parts>
<shoulders>shoulders1</shoulders>
<knees>knees2</knees>
<toes>10</toes>
</parts>
<parts>
<shoulders>shoulders2</shoulders>
<knees>knees1</knees>
<toes>10</toes>
</parts>
<parts>
<shoulders>shoulders1</shoulders>
<knees>knees1</knees>
<toes>9</toes>
</parts>
<parts>
<shoulders>shoulders2</shoulders>
<knees>knees2</knees>
<toes>8</toes>
</parts>
</bodies>
Sample XSLT:
<xsl:stylesheet exclude-result-prefixes="xsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:this="urn:this-stylesheet" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:for-each-group select="bodies/parts" group-by="shoulders">
<xsl:for-each-group select="current-group()" group-by="knees">
<xsl:value-of select="shoulders"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="knees"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="sum(current-group()/toes)"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="position()"/>
<xsl:text>. </xsl:text>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Resulting Output:
shoulders1, knees1, 10, 1. shoulders1, knees2, 10, 2. shoulders2, knees2, 10, 1. shoulders2, knees1, 10, 2.
Desired Output:
shoulders1, knees1, 10, 1. shoulders1, knees2, 10, 2. shoulders2, knees2, 10, 3. shoulders2, knees1, 10, 4.
When you've got two nested xsl:for-each-group instructions like this, then an alternative is to do single level grouping on a composite key, like this:
<xsl:for-each-group select="bodies/parts" group-by="concat(shoulders, '~', knees)">
The position() will then increment the way you are looking for, if I've understood the requirement correctly.
This doesn't work, of course, if you actually want to produce hierarchically structured output in which the outer group is significant.
I've tried this three times now and I think you have to process your results and then count them. I've run out of time to try and figure a way to do the counting inline, because I think it can't be done.
<xsl:stylesheet exclude-result-prefixes="xsl" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:this="urn:this-stylesheet" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:variable name="results" as="xsd:string*">
<xsl:for-each-group select="bodies/parts" group-by="shoulders">
<xsl:for-each-group select="current-group()" group-by="knees">
<xsl:value-of>
<xsl:value-of select="shoulders"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="knees"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="sum(current-group()/toes)"/>
<xsl:text>,</xsl:text>
</xsl:value-of>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:variable>
<xsl:for-each select="$results">
<xsl:value-of select=".,position()"/>
<xsl:text>. </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

is there any operation such as trim in xslt?

i wrote a xslt code which converts a xml file to a html file which contains lot of tables, one of the column contains messages(very long messages), but that line starts with either of the two words "Verification Passed" or "Verification failed"
My requirement is to make the entire table row red if verification failed and make entire table row green if verification passed
<xsl:choose>
<xsl:when test="contains(#message,'Verification failed:')"><td bgcolor="#FF0000"> <xsl:value-of select="#Message"/></td></xsl:when>
<xsl:when test="contains(#message,'Verification passed:')"><td bgcolor="#00FF00"><xsl:value-of select="#Message"/></td></xsl:when>
<xsl:otherwise><td> <xsl:value-of select="#Message"/></td></xsl:otherwise>
</xsl:choose>
Unfortunately you don't say what you expect your "trim()" function to do. But from your description of the requirement, I would guess that normalize-space() is close enough:
starts-with(normalize-space(message), 'Verification passed'))
The XPath normalize-space() function differs from the Java trim() method in that (a) it replaces internal sequences of whitespace characters by a single space, and (b) it has a slightly different definition of whitespace.
is there any operation such as trim in xslt?
I. XSLT 1.0
No, and it is rather difficult to perform "trim" in XSLT 1.0.
Here is the trim function/template from FXSL:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="trim.xsl"/>
<!-- to be applied on trim.xml -->
<xsl:output method="text"/>
<xsl:template match="/">
'<xsl:call-template name="trim">
<xsl:with-param name="pStr" select="string(/*)"/>
</xsl:call-template>'
</xsl:template>
</xsl:stylesheet>
When this transformation is performed (you have to download at least a few other stylesheet modules, which comprise the complete import tree) on this XML document:
<someText>
This is some text
</someText>
the wanted, correct result is produced:
'This is some text'
II In XSLT 2.0 / XPath 2.0
Still a little bit tricky, but very short:
if (string(.))
then replace(., '^\s*(.+?)\s*$', '$1')
else ()
Here is the complete, corresponding transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
"<xsl:sequence select=
"if (string(.))
then replace(., '^\s*(.+?)\s*$', '$1')
else ()
"/>"
</xsl:template>
</xsl:stylesheet>
and when applied on the same XML document (above), the same correct result is produced:
"This is some text"
using XSLT1 with registerLangFunctions
Today, ~10 years after (complex) XSLT2 standard released, many projects yet use (faster) XSLT1. Perhaps the problem is not only "simple vs complex", but XSLT1 is a fact for Perl, PHP, Python, PostgreSQL, and many other communities.
So, a solution for Perl, PHP and Python: use your main language to do trim and another usual functions that not exists into XSLT1.
Here an example of PHP:
https://en.wikibooks.org/wiki/PHP_Programming/XSL/registerPHPFunctions
<xsl:variable name="Colour">
<xsl:choose>
<xsl:when test="contains(#Message,'Verification failed:')">background-color:red; </xsl:when>
<xsl:when test="contains(#Message,'Verification passed:')">background-color:green</xsl:when>
<xsl:otherwise> </xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr style="{$Colour}">
<td> <xsl:value-of select="#Time"/></td>
<td>Line <xsl:value-of select="#Line"/></td>
<td> <xsl:value-of select="#Type"/></td>
<td> <xsl:value-of select="#Message"/></td>
</tr>

Complex XSLT split?

Is it possible to split a tag at lower to upper case boundaries i.e.
for example, tag 'UserLicenseCode' should be converted to 'User License Code'
so that the column headers look a little nicer.
I've done something like this in the past using Perl's regular expressions,
but XSLT is a whole new ball game for me.
Any pointers in creating such a template would be greatly appreciated!
Thanks
Krishna
Using recursion, it is possible to walk through a string in XSLT to evaluate every character. To do this, create a new template which accepts only one string parameter. Check the first character and if it's an uppercase character, write a space. Then write the character. Then call the template again with the remaining characters inside a single string. This would result in what you want to do.
That would be your pointer. I will need some time to work out the template. :-)
It took some testing, especially to get the space inside the whole thing. (I misused a character for this!) But this code should give you an idea...
I used this XML:
<?xml version="1.0" encoding="UTF-8"?>
<blah>UserLicenseCode</blah>
and then this stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="text"/>
<xsl:variable name="Space">*</xsl:variable>
<xsl:template match="blah">
<xsl:variable name="Split">
<xsl:call-template name="Split">
<xsl:with-param name="Value" select="."/>
<xsl:with-param name="First" select="true()"/>
</xsl:call-template></xsl:variable>
<xsl:value-of select="translate($Split, '*', ' ')" />
</xsl:template>
<xsl:template name="Split">
<xsl:param name="Value"/>
<xsl:param name="First" select="false()"/>
<xsl:if test="$Value!=''">
<xsl:variable name="FirstChar" select="substring($Value, 1, 1)"/>
<xsl:variable name="Rest" select="substring-after($Value, $FirstChar)"/>
<xsl:if test="not($First)">
<xsl:if test="translate($FirstChar, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '..........................')= '.'">
<xsl:value-of select="$Space"/>
</xsl:if>
</xsl:if>
<xsl:value-of select="$FirstChar"/>
<xsl:call-template name="Split">
<xsl:with-param name="Value" select="$Rest"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
and I got this as result:
User License Code
Do keep in mind that spaces and other white-space characters do tend to be stripped away from XML, which is why I used an '*' instead, which I translated to a space.
Of course, this code could be improved. It's what I could come up with in 10 minutes of work. In other languages, it would take less lines of code but in XSLT it's still quite fast, considering the amount of code lines it contains.
An XSLT + FXSL solution (in XSLT 2.0, but almost the same code will work with XSLT 1.0 and FXSL 1.x:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:testmap="testmap"
exclude-result-prefixes="f testmap"
>
<xsl:import href="../f/func-str-dvc-map.xsl"/>
<testmap:testmap/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/>
'<xsl:value-of select="f:str-map($vTestMap, 'UserLicenseCode')"
/>'
</xsl:template>
<xsl:template name="mySplit" match="*[namespace-uri() = 'testmap']"
mode="f:FXSL">
<xsl:param name="arg1"/>
<xsl:value-of select=
"if(lower-case($arg1) ne $arg1)
then concat(' ', $arg1)
else $arg1
"/>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on any source XML document (not used), the expected correct result is produced:
' User License Code'
Do note:
We are using the DVC version of the FXSL function/template str-map(). This is a Higher-order function (HOF) which takes two arguments: another function and a string. str-map() applies the function on every character of the string and returns the concatenation of the results.
Because the lower-case() function is used (in the XSLT 2.0 version), we are not constrained to only the Latin alphabet.

Finding max of three numbers in XSL

How can find max of three numbers in XSL ?
More Information : I have three numbers say 1.0, 2.0, 5.0....I don't have any nodes set... I would like to find maximum of 1.0, 2.0 and 5.0.....
Example :
AIM : TO know which type of node <MYNODE>, <DOC>, <PIC>
is having maximum count and whant's the count number ?
<ROOT>
<MYNODES>
<MYNODE>A</MYNODE>
<MYNODE>B</MYNODE>
<MYNODE>C</MYNODE>
<MYNODE>D</MYNODE>
</MYNODES>
<DOCS>
<DOC>1</DOC>
<DOC>2</DOC>
<DOC>3</DOC>
</DOC>
<PICS>
<PIC>a.jpeg</PIC>
<PIC>b.jpeg</PIC>
<PIC>c.jpeg</PIC>
<PIC>d.jpeg</PIC>
<PIC>e.jpeg</PIC>
</PICS>
</ROOT>
With your input XML, you would find the maximum count you are looking for like this:
<xsl:variable name="vMaxChildren">
<xsl:for-each select="/ROOT/*">
<xsl:sort select="count(*)" data-type="number" order="descending" />
<xsl:if test="position() = 1">
<xsl:value-of select="concat(name(), ': ', count(*))" />
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$vMaxChildren" />
Which would produce:
PICS: 5
This question is incorrectly formulated and the provided "XML document' is not well-formed!
Do note that it is generally meaningless to ask about the maximum of a set of numbers. There can be more than one number with the highest value. Therefore, the solutions below show just the first item with the maximum value.
This is one possible XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="vNameMaxCount">
<xsl:for-each select="*/*">
<xsl:sort select="count(*)" data-type="number"
order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="concat(name(),'+', count(*))"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
One element with maximum children is: <xsl:text/>
<xsl:value-of select="substring-before($vNameMaxCount, '+')"/>
Maximum number of children: <xsl:text/>
<xsl:value-of select="substring-after($vNameMaxCount, '+')"/>
</xsl:template>
</xsl:stylesheet>
when the above transformation is applied on the following XML document (produced from the one provided after spending 10 minutes to make it well-formed!):
<ROOT>
<MYNODES>
<MYNODE>A</MYNODE>
<MYNODE>B</MYNODE>
<MYNODE>C</MYNODE>
<MYNODE>D</MYNODE>
</MYNODES>
<DOCS>
<DOC>1</DOC>
<DOC>2</DOC>
<DOC>3</DOC>
</DOCS>
<PICS>
<PIC>a.jpeg</PIC>
<PIC>b.jpeg</PIC>
<PIC>c.jpeg</PIC>
<PIC>d.jpeg</PIC>
<PIC>e.jpeg</PIC>
</PICS>
</ROOT>
the wanted result is produced
One element with maximum children is: PICS
Maximum number of children: 5
An XSLT 2.0 solution (actually just an XPath 2.0 soulution):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"for $vmaxChildrein in max(/*/*/count(*)),
$vmaxNode in */*[count(*) = $vmaxChildrein][1]
return
(name($vmaxNode),
'has the max no of children:',
$vmaxChildrein
)
"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the above document, the wanted result is produced:
PICS has the max no of children: 5
For finding the maximum of more tricky properties that cannot be immediately expressed as an XPath expression and used in <xsl:sort>, do use the f:maximum() function of FXSL.
5.0 is the largest of the three numbers. Hardcode it and be done with it. :-)
Seriously though, you may wish to take another path. Logic like this is trivial in other languages, but can be a pain in XSLT. You should consider writing a simple extension for the XSLT engine rather than messing around trying to make XSLT do what you want it to.