We have to transform some XML that contain numbers in exponent (aka scientific) notation
eg.
<Value>12.34e12</Value>
<Value>-12.34e-12</Value>
rather irritatingly, we cannot use the sum() function and the like because the XSLT parser expects numbers to be in decimal format.
[We are using the .Net XslCompiledTransform class to do the transform but I think this problem is common to all XSLT implementations]
The only solution to this problem that we have so far is to transform the string value to a number using a javascript function (see below) and then write our own sum template which calls this function.
It seems to me that there must be a better way - is there?
/*
This function attempts to coerce the input into a number.
*/
function toNumber( x ) {
if(!x) {
return Number.NaN;
}
if(typeof x === 'number') {
return x;
}
return parseFloat(x);
};
Saxon-B 9.0.0.6 works fine here. Outputs 0.
EDIT: Now I see that Saxon 6 (processor only for xslt 1.0) returns NaN. But if you try xslt 2.0, you won't ever want anything else. :-)
XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<Value>12.34e12</Value>
<Value>-12.34e12</Value>
</root>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="root">
<xsl:value-of select="sum(Value)"/>
</xsl:template>
</xsl:stylesheet>
Related
I have a fairly convoluted XML file and I need to do a weighted average of a few values within it using XSL. I am able to complete a sum of the weights OR of the values, but I cannot get the multiplication to work. I get an error:
XPTY0004: A sequence of more than one item is not allowed as the first
operand of '*'
I am not able to share the XML, but I have simplified the XML to the following example (assume there are a large number of foos):
<group>
<fooList>
<foo>
<attributeList>
<Attribute ID="1" Weight="0.5">
<otherParams />
</Attribute>
</attributeList>
<Properties>
<PhysicalProperties>
<Volume Average="125" Unknown="50" />
</PhysicalProperties>
</Properties>
</foo>
</fooList>
</group>
My current attempt to get the weighted average is the following:
<xsl:variable name="WeightedVolume" select="sum(/group/fooList/foo[attributeList/Attribute/[#ID=$test_id]]/attributeList/Attribute/#Weight * /group/fooList/foo[attributeList/Attribute/[#ID=$test_id]]/Properties/PhysicalProperties/Volume/#Average)"/>
I know there are similar questions available - but most of them deal with something like summing and multiplying foo
<foo>
<Weight>0.5</Weight>
<VolumeAverage>125</VolumeAverage>
</foo>
The answer on this StackOverflow Question appeals to me, but I cannot seem to make it work.
I'm using Saxon-HE 9.5.1.1N from Saxonica, with Visual Studio 2013.
Edited
I was able to get something to work for XSL 2, but need to have a fall-back for XSL1.
<xsl:variable name="WeightedVolume" select="sum(for $i in /group/FooList/foo[attributeList/Attribute[#ID=$test_id] return $i/AttributeList/Attribute/#Weight * $i/Properties/PhysicalProperties/Volume/#Average)"/>
To follow the example in that question you linked to, you would use this in XSLT 2.0/XPath 2.0:
<xsl:variable name="FoosToCalculate"
select="/group/fooList/foo[attributeList/Attribute/#ID = $test_id]" />
<xsl:variable name="WeightedVolume"
select="sum($FoosToCalculate/(attributeList/Attribute/#Weight *
Properties/PhysicalProperties/Volume/#Average)
)"/>
Doing this summing in XSLT 1.0 is considerably more involved and typically involves either using recursive templates or some manifestation of the node-set() function. Here is an example of the latter:
<xsl:stylesheet version="1.0"
xmlns:ex="http://exslt.org/common"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<!-- determine $test_id however you need to -->
<xsl:variable name="products">
<xsl:for-each
select="/group/fooList/foo[attributeList/Attribute/#ID = $test_id]">
<product>
<xsl:value-of select="attributeList/Attribute/#Weight *
Properties/PhysicalProperties/Volume/#Average" />
</product>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(ex:node-set($products)/product)"/>
</xsl:template>
</xsl:stylesheet>
For completeness, if you want to sum over a computed quantity in XSLT 1.0, there are three ways of doing it:
(a) recursion: write a recursive template that processes the items in the sequence one by one, computing the total as it goes.
(b) create an XML tree in which the computed quantities are node values, and then process this tree using the sum() function. To do this in a single stylesheet you will need the exslt:node-set() extension function.
(c) use an extension function provided by the XSLT vendor, or user-written using the facilities provided by the vendor for calling external functions.
In XSLT 2.0, it can always be done using the construct
sum(for $x in node-set return f($x))
where f is a function that computes the quantity.
I'm attempting to create an XSLT mapping that properly converts a fairly large integer value coming through in a text field into the appropriate integer value. The problem is that since 1.0 only supports converting to type number, I get a value like 1.234567890E9 back for input of "1234567890"
I'm using Altova MapForce with XSLT1.0 as the coding platform. XSLT2.0 doesn't appear to be an option, as the XSLT has to be processed using a pre-existing routine that only supports XSLT1.0
By default Mapforce generates
<xsl:value-of select="string(floor(number(string(.))))"/>
and I've tried every combination of functions I can think of, but always get a float for large values.
Further testing shows the problem lies in Mapforce, which insists on using the number() function when mapping from text to int.
Let me try and move this forward by answering a question that you did not ask, but perhaps should have. Suppose you have the following input:
XML
<input>
<value>1234567890000000.9</value>
<value>9876543210000000</value>
</input>
and you want to make sure that the input values (which are all numbers, but some of them are not integers) are converted to integers at the output, you could apply the following transformation:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<output>
<xsl:for-each select="input/value">
<value><xsl:value-of select="format-number(., '#')"/></value>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
to obtain the following output:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<value>1234567890000001</value>
<value>9876543210000000</value>
</output>
Note that the results here are rounded, not floored.
Are you sure that mapforce isn't using xslt-2.0?
If I do in XSLT-1.0 (with either saxon or Altova's processor):
<xsl:value-of select="number('1234567890')"/>
I get -> 1234567890
If I use XSLT-2.0 I get -> 1.23456789E9
So I think it is very strange that an XSLT 1 transformation supposedly returns you the floating point representation of the number.
Formatting the number with format-number(1.23456789E9,'#') will always give you 1234567890 in both XSLT-1.0 and 2.0. Edit: saxon will not convert 1.23456789E9 to number in xslt-1.0, altova's processor however will.
The problem lies within Mapforce, so I've decided to let mapforce generate it's code, then overwrite it for this one field that's causing all the trouble.
#Tobias #Michael Thanks to you both for your help. I've +1'ed both your answers and a few comments since your help led to the answer.
context: from OSB to xslt call. Please help with the samples to support the below requirement.
I am looking for a sample xslt file, which will take multiple param values from OSB.
From OSB I want to pass four parameter values to an xslt function, will have to take four attribute values in the input message($body) if any of them matches to the passed value, then it should return true other wsie false.
Suppose, my param values to be passed to xslt are mango, carrot, pepsi, venilla, and in the input xml the sections are such as
<fruits fruit="apple" .../>
<vegetables vegetable="tomato".../>
<drinks drink ="cola" ... />
<icecreams icecream="vanilla"/>
only in the input if the values passed from OSB to xslt are present in any of the respective attribute, then it should return true other wise false.
Thanks.
Please note the xsl:param tag and the $ tag which indicates that I am using the parameter. This is a working XSLT is some code that I have written.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:ns3="com.namespace3" xmlns:ns7="com.namesapce7" xmlns:ns23="com.namespace23">
<xsl:param name="ParamName"/>
<xsl:template match="/">
<ns7:SourceXMLRoot>
<ns23:interactionId>
<xsl:value-of select="/ns7:request/ID"/>
</ns23:interactionId>
<ns23:processId>
<xsl:value-of select="$ParamName/ns3:ParamRoot/ID"/>
</ns23:processId>
</ns7:SourceXMLRoot>
</xsl:template>
</xsl:stylesheet>
I'm trying to count the elements my transformation generates (must use XLST1). For example, my transformation creates:
<Parent>
<ElementX Att1="2"/>
<ElementY Att1="1"/>
<ElementZ Att1="6"/>
</Parent>
I need to print 3 within the same transformation, because there are 3 child elements.
Can this be done?
Thanks.
It would help a lot if you provide some extract of your XSLT.
I cn't give you a XSLT code without it. I'll try to give some "way" to the answer :
One solution could be to store the output into a nodeset (use the XSLT 1.0 extension which provides the nodeset() function) and apply the XPath count() function on this variable. After that just output your variable with xsl:value-of, and your count result the same way.
Here is a demo how to do this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="count(ext:node-set($vrtfPass1)/*/*)"/>
</xsl:template>
<xsl:template match="/*">
<Parent>
<ElementX Att1="2"/>
<ElementY Att1="1"/>
<ElementZ Att1="6"/>
</Parent>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used in this Demo), the wanted, correct result is produced:
3
Explanation:
A general way to process the result of the transformation (in a single transformation), is to organize it in two passes where we save the result of the first pass in a variable.
In the second pass we access the result and do the additional processing.
Do note that in XSLT 1.0 if the variable that captures the result of the first pass is of the infamous RTF (Result Tree Fragment) type and needs to be converted to a regular tree in order of any nodes inside this tree to be accessible (xsl:copy-of and string() are still allowed on an RTF).
This conversion to a regular tree is done by an extension function, which most often has the name node-set and always belongs to a vendor-defined namespace. In this demo we are using the node-set() extension function that belongs to the EXSLT namespace -- because most XSLT 1.0 processors implement EXSLT.
For more information on multi-pass processing, see this: Two phase processing: Do not output empty tags from phase-1 XSLT 2.0 processing
I would like to do a string replacement with a value from a variable, like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:str="http://exslt.org/strings" extension-element-prefixes="str" >
<xsl:variable name="my-variable">bar</xsl:variable>
<xsl:template match="text()">
<xsl:value-of select="str:replace( . ,'foo', $my-variable )"/>
</xsl:template>
</xsl:stylesheet>
When I run this on a file, e.g.,
<?xml version="1.0" encoding="utf-8"?>
<root>foobar</root>
I this error from xsltproc / libxslt
XPath error : Invalid type
xmlXPathCompiledEval: evaluation failed
runtime error: file C:/Users/Adam/Documents/WakhiLD/XSL/replace.xsl line 6 element value-of
XPath evaluation returned no result.
It seems reasonable to use variables in function calls, so I am at a loss as to what I should be doing.
Simply use <xsl:variable name="my-variable" select="'bar'"/> and it should work. You are currently creating variable of type result tree fragment and it looks as if the extension function is not doing any conversion to a string when passed such an argument. But simply creating a string variable as shown above should solve it.
If you really need to create the variable as a result tree fragment then try converting to a string explicitly, as in <xsl:value-of select="str:replace( . ,'foo', string($my-variable) )"/>.