Is it possible to define a custom format for <xsl:number>?
I have the case where a standard alpha-based format is desired, but certain characters in the alphabet are forbidden (strange requirement, but it is what the client requires). For example, the letter i cannot be used, so when using <xsl:number> I should get the sequence: a, b, c, d, e, f, g, h, j, k, ..., aa, ab, ..., ah, aj, ...
The project is using XSLT 2.0 and Saxon, so if a solution exists that is specific to Saxon, that is okay.
Does XSLT 2.0 provide the capability to define a custom format sequence? Does Saxon provide a capability to register a custom sequence for use with <xsl:number>?
XSLT 2.0 provides the format attribute for xsl:number by which you can use the format token aa for example. The computed number depends by the expression evaluated inside value attribute and will be formatted accordingly to format.
Given this, you can think of first evaluating the correct sequence of numbers excluding those that will match for a particular letter.
For instance, the following instruction:
<xsl:number value="$sequence" format="aa"/>
will print (notice i excluded):
a.b.c.d.e.f.g.h.j.k.l.m
if $sequence evaluates to (notice 9 skipped):
1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13
Notice that if you have 12 elements your expression should be able to skip the unwanted number (9 for i) and increase the following of one. The last element with position 12, should have corresponding number 13.
So what you need, is just the algorithm that computes the wanted sequence; which depends definitely from your input document.
References: XSLT 2.0 Rec.
You can customize the output of xsl:number in Saxon by writing an implementation of the interface net.sf.saxon.lib.Numberer: probably you will want to make this a subclass of net.sf.saxon.expr.number.Numberer_en. You'll need to study the source code and work out what needs overriding.
In Saxon PE/EE you can register the Numberer to be used for a given language in the Saxon configuration file. For Saxon HE it requires a bit more work: you have to implement the interface LocalizerFactory and register your LocalizerFactory with the Configuration.
EDIT: An alternate, more general, solution exists and is posted as a separate answer. I'm leaving this answer since it still may be of value to some.
I like #empo's thinking (I mod'ed it up), but I think it may be hard to get a working solution. A clever algorithm/equation is required to come up with the correct sequence number based on the raw sequence to avoid getting a label that does not contain the forbidden characters. At this time, such an algorithm escapes me.
One method I came up with is to create my own function, and not use <xsl:number>. In essence, we are dealing with a base 23 set, the letters a to z, but excluding the characters i, l, and o. The function I came up with only goes up to zz, but that should be sufficient for what is needed (provides labelling up to 552 items).
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ewh="http://www.earlhood.com/XSL/Transform"
exclude-result-prefixes="#all">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="letters" select="'abcdefghjkmnpqrstuvwxyz'"/>
<xsl:variable name="lbase" select="23"/>
<xsl:function name="ewh:get-alpha-label" as="xs:string">
<xsl:param name="number" as="xs:integer"/>
<xsl:variable name="quotient" select="$number idiv $lbase"/>
<xsl:variable name="remainder" select="$number mod $lbase"/>
<xsl:variable name="p1">
<xsl:choose>
<xsl:when test="($quotient gt 0) and ($remainder = 0)">
<xsl:value-of select="substring($letters,($quotient - 1),1)"/>
</xsl:when>
<xsl:when test="($quotient gt 0) and ($remainder gt 0)">
<xsl:value-of select="substring($letters,$quotient,1)"/>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:variable>
<xsl:variable name="p0">
<xsl:choose>
<xsl:when test="$remainder = 0">
<xsl:value-of select="substring($letters,$lbase,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($letters,$remainder,1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat($p1,$p0)"/>
</xsl:function>
<xsl:template match="/">
<result>
<value n="9"><xsl:value-of select="ewh:get-alpha-label(9)"/></value>
<value n="12"><xsl:value-of select="ewh:get-alpha-label(12)"/></value>
<value n="15"><xsl:value-of select="ewh:get-alpha-label(15)"/></value>
<value n="23"><xsl:value-of select="ewh:get-alpha-label(23)"/></value>
<value n="26"><xsl:value-of select="ewh:get-alpha-label(26)"/></value>
<value n="33"><xsl:value-of select="ewh:get-alpha-label(33)"/></value>
<value n="46"><xsl:value-of select="ewh:get-alpha-label(46)"/></value>
<value n="69"><xsl:value-of select="ewh:get-alpha-label(69)"/></value>
<value n="70"><xsl:value-of select="ewh:get-alpha-label(70)"/></value>
<value n="200"><xsl:value-of select="ewh:get-alpha-label(200)"/></value>
<value n="552"><xsl:value-of select="ewh:get-alpha-label(552)"/></value>
</result>
</xsl:template>
</xsl:stylesheet>
When I execute the above, I get the following output:
<result>
<value n="9">j</value>
<value n="12">n</value>
<value n="15">r</value>
<value n="23">z</value>
<value n="26">ac</value>
<value n="33">ak</value>
<value n="46">az</value>
<value n="69">bz</value>
<value n="70">ca</value>
<value n="200">hs</value>
<value n="552">zz</value>
</result>
It would be nice of XSLT provided the capability to define a custom character sequence for use with <xsl:number>. Seems like such a capability would generalize <xsl:number> w/o relying on custom extensions, which I do not know if any XSLT engine provides for <xsl:number>.
I came up with the following, more generalized solution, after posting my original solution to the problem. The solution is pure XSLT and at the base, still uses <xsl:number>, so should be applicable to any format type.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:ewh="http://www.earlhood.com/XSL/Transform"
exclude-result-prefixes="#all">
<!-- Description: XSLT to generate a alpha formatted sequence
label (via <xsl:number>), but disallowing specific characters
from being used.
-->
<!-- Algorithm: Given the index value of the item to generate
a label for via <xsl:number>, we adjust the value so the resulting
label avoids the use of the forbidden characters.
This is achieved by converting the index value into a baseX
number, with X the number of allowed characters.
The baseX number will be converted into a reverse sequence
of numbers for each ^E place. For example, the number 12167
converted to base23 will generate the following reverse sequence:
Place: (23^0, 23^1, 23^2, 23^3)
Sequence: ( 0, 0, 0, 1) // 1000 in base23
Having it in right-to-left order makes processing easier.
Each item in the sequence will be a number from 0 to baseX-1.
With the sequence, we can then just call <xsl:number> on
each item and reverse concatenate the result.
NOTE: Since <xsl:number> does not like 0 as a given value,
the sequence must be processed so each item is within the
range of 1-to-baseX. For example, the above base23 example
will be translated to the following:
(23, 22, 22)
-->
<xsl:output method="xml" indent="yes"/>
<!-- Number of allowed characters: This should be total number of chars of
format-type desired minus the chars that should be skipped. -->
<xsl:variable name="lbase" select="23"/>
<!-- Sequence of character positions not allowed, with 1=>a to 26=>z -->
<xsl:variable name="lexcs" select="(9,12,15)"/> <!-- i,l,o -->
<!-- Helper Function:
Convert integer to sequence of number of given base.
The sequence of numbers is in reverse order: ^0,^1,^2,...^N.
-->
<xsl:function name="ewh:get_base_digits" as="item()*">
<xsl:param name="number" as="xs:integer"/>
<xsl:param name="to" as="xs:integer"/>
<xsl:variable name="Q" select="$number idiv $to"/>
<xsl:variable name="R" select="$number mod $to"/>
<xsl:sequence select="$R"/>
<xsl:if test="$Q gt 0">
<xsl:sequence select="ewh:get_base_digits($Q,$to)"/>
</xsl:if>
</xsl:function>
<!-- Helper Function:
Compute carry-overs in reverse-base digit sequence. XSLT starts
numbering at 1, so we cannot have any 0s.
-->
<xsl:function name="ewh:compute_carry_overs" as="item()*">
<xsl:param name="digits" as="item()*"/>
<xsl:variable name="d" select="subsequence($digits, 1, 1)"/>
<xsl:choose>
<xsl:when test="($d le 0) and (count($digits) = 1)">
<!-- 0 at end of list, nothing to do -->
</xsl:when>
<xsl:when test="$d le 0">
<!-- If digit <=0, need to perform carry-over operation -->
<xsl:variable name="next" select="subsequence($digits, 2, 1)"/>
<xsl:choose>
<xsl:when test="count($digits) le 2">
<xsl:sequence select="$lbase + $d"/>
<xsl:sequence select="ewh:compute_carry_overs($next - 1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$lbase + $d"/>
<xsl:sequence select="ewh:compute_carry_overs(($next - 1,
subsequence($digits, 3)))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="count($digits) le 1">
<xsl:sequence select="$d"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$d"/>
<xsl:sequence select="ewh:compute_carry_overs(subsequence($digits, 2))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<!-- Helper Function:
Given a number in the base range, determine number for
purposes of <xsl:number>. We loop thru the exclusion
list and add 1 for each exclusion letter that has
been passed. The $digit parameter should be a number
in the range [1..$lbase].
-->
<xsl:function name="ewh:compute_digit_offset" as="xs:integer">
<xsl:param name="digit" as="xs:integer"/>
<xsl:param name="excludes" as="item()*"/>
<xsl:variable name="l" select="subsequence($excludes, 1, 1)"/>
<xsl:variable name="result">
<xsl:choose>
<xsl:when test="$digit lt $l">
<xsl:value-of select="0"/>
</xsl:when>
<xsl:when test="count($excludes) = 1">
<xsl:value-of select="1"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="rest">
<xsl:value-of select="ewh:compute_digit_offset($digit+1,
subsequence($excludes,2))"/>
</xsl:variable>
<xsl:value-of select="1 + $rest"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$result"/>
</xsl:function>
<!-- Retrieve alpha sequence label.
This is the main function to call.
-->
<xsl:function name="ewh:get-alpha-label" as="xs:string">
<xsl:param name="number" as="xs:integer"/>
<xsl:variable name="basedigits"
select="ewh:get_base_digits($number,$lbase)"/>
<xsl:variable name="digits"
select="ewh:compute_carry_overs($basedigits)"/>
<xsl:variable name="result" as="item()*">
<xsl:for-each select="$digits">
<xsl:variable name="digit" select="."/>
<!-- Should not have any 0 values. If some reason we do,
we ignore assuming they are trailing items. -->
<xsl:if test="$digit != 0">
<xsl:variable name="value">
<xsl:value-of select="$digit +
ewh:compute_digit_offset($digit,$lexcs)"/>
</xsl:variable>
<xsl:variable name="number">
<xsl:number value="$value" format="a"/>
</xsl:variable>
<xsl:sequence select="$number"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string-join(reverse($result),'')"/>
</xsl:function>
<!-- For testing -->
<xsl:template match="/">
<result>
<xsl:for-each select="(1 to 1000,12166,12167,12168,279840,279841,279842)">
<value n="{.}"><xsl:value-of select="ewh:get-alpha-label(.)"/></value>
</xsl:for-each>
</result>
</xsl:template>
</xsl:stylesheet>
Related
I am in need to transform the below coding using XSLT 1.0 based on the separators attributes given. The text should be separated based on the separators given:
Input:
<chapter xmlns="http://www.w3.org/1998/Math/MathML">
<math display="inline"><mfenced separators=", : . ;"><mn>1</mn><mtext>b</mtext><mo>%</mo><mi>d</mi><mi>e</mi></mfenced></math>
<math display="inline"><mfenced separators=", ;"><mi>a</mi><mi>b</mi><mi>c</mi><mi>d</mi><mi>e</mi></mfenced></math>
<math display="inline"><mfenced separators=", : . ; ; : . ;"><mi>a</mi><mi>b</mi><mi>c</mi><mi>d</mi><mi>e</mi></mfenced></math>
</chapter>
output required:
1,b:%.d;e
a,b;c;d;e
a,b:c.d;e
Also please note that if there are too many separator characters, the extra ones are ignored. If separator characters are given, but there are too few, the last one is repeated as necessary
I could not able to get the output only if the separator characters are lesser than the child elements.
XSLT 1.0 tried:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://www.w3.org/1998/Math/MathML">
<xsl:template match="m:mfenced">
<xsl:variable name="text" select="#separators"/>
<xsl:for-each select="child::*">
<xsl:apply-templates/>
<xsl:choose>
<xsl:when test="contains($text,' ')">
<xsl:variable name="attr" select="string-length(translate($text, ' ', ''))"/>
<xsl:variable name="ch" select="count(parent::*/child::*)-1"/>
<xsl:if test="$ch=$attr"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
<xsl:if test="$ch gt $attr">
<xsl:if test="not(substring($text,count(preceding-sibling::*)+position(),1)='')"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
<xsl:if test="(substring($text,count(preceding-sibling::*)+position(),1)='')"><xsl:value-of select="substring($text,count(preceding-sibling::*)+1,1)"/></xsl:if>
</xsl:if>
<xsl:if test="$ch lt $attr and count(following-sibling::*)>0"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
</xsl:when>
<xsl:otherwise><xsl:if test="count(following-sibling::*)>0"><xsl:value-of select="$text"/></xsl:if></xsl:otherwise></xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The following solution is based on obtaining the position of each <m:mi> within the <m:fenced> elements to obtain the next operator to be outputted.
Note. I am assuming that the string length used to represent each operator is 1.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://www.w3.org/1998/Math/MathML">
<xsl:output method="text" />
<!-- Ignore all text nodes (just for demo) -->
<xsl:template match="text()" />
<xsl:template match="m:mfenced">
<!-- Print children values and operators -->
<xsl:apply-templates select="*" mode="list-op">
<xsl:with-param name="separator-str" select="#separators" />
<xsl:with-param name="separator-len" select="string-length(#separators)" />
</xsl:apply-templates>
<!-- Print new line -->
<xsl:text>
</xsl:text>
</xsl:template>
<!-- Last m:mi elements for each m:mfenced are just printed -->
<xsl:template match="*[last()]" mode="list-op">
<xsl:value-of select="."/>
</xsl:template>
<!-- In this template we use the position() function to calculate the next
operator that is going to be outputted -->
<xsl:template match="*" mode="list-op">
<xsl:param name="separator-str" />
<!-- This parameter is not required, but allows us to cache
the length of the separators string instead of calculating it
for each m:mi element -->
<xsl:param name="separator-len" />
<!-- Print current value -->
<xsl:value-of select="." />
<!-- Calculate the separator position within the string -->
<xsl:variable name="string-position" select="2*position() - 1" />
<!-- Check if the position oveflows the position in the array, and
if it does, print the last separator in the string. -->
<xsl:choose>
<xsl:when test="$separator-len >= $string-position">
<xsl:value-of select="substring($separator-str, $string-position, 1)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($separator-str, $separator-len)" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I have a value like integer="1,2,3,4,5" in the xml. How can I count the total number using XSLT. So that the output gives me a count of 5
Regards,
Sam
Here's one way (there may be others). Simply translate all commas into empty strings, and then compare in difference in length of strings:
<xsl:value-of
select="string-length(#integer)
- string-length(translate(#integer, ',', '')) + 1" />
If you need to handle empty strings, try this instead
<xsl:value-of
select="string-length(#integer)
- string-length(translate(#integer, ',', ''))
+ 1 * (string-length(#integer) != 0)" />
If you want to count the comma-separated-values, but ALSO be able to reference the individual items, you can use a recursive template like such.
This XSLT 1.0 style-sheet will convert the comma-separated-values into nodes and then count them ...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="as-nodes">
<xsl:call-template name="parse-comma-separated-values">
<xsl:with-param name="csv" select="t/#csv" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="count(msxsl:node-set($as-nodes)/*)" />
</xsl:template>
<xsl:template name="parse-comma-separated-values">
<xsl:param name="csv" />
<xsl:choose>
<xsl:when test="$csv = ''"/>
<xsl:when test="not( contains( $csv, ','))">
<value-node value="{$csv}" />
</xsl:when>
<xsl:otherwise>
<value-node value="{substring-before($csv,',')}" />
<xsl:call-template name="parse-comma-separated-values">
<xsl:with-param name="csv" select="substring-after($csv,',')"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
... when applied to this input document ...
<t csv="1,2,3,4,5"/>
... produces ...
5
I have a template with a parameter. How can I insert a tab character n times?
n is the value of the parameter.
In XSLT 2.0:
<xsl:for-each select="1 to $count"> </xsl:for-each>
(Sadly though, I suspect that if you were using XSLT 2.0 you wouldn't need to ask the question).
Another technique often used with XSLT 1.0 is the hack:
<xsl:for-each select="//*[position() <= $count]"> </xsl:for-each>
which works provided the number of elements in your source document is greater than the number of tab characters you want to output.
Just call it recursively; output a tab, then call the same template again with n-1 passed in, if n > 1.
<xsl:template name="repeat">
<xsl:param name="output" />
<xsl:param name="count" />
<xsl:if test="$count > 0">
<xsl:value-of select="$output" />
<xsl:call-template name="repeat">
<xsl:with-param name="output" select="$output" />
<xsl:with-param name="count" select="$count - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
As has been pointed out, this example will actually output a minimum of one. In my experience where the output is whitespace, it's usually needed. You can adapt the principle of a recursive template like this any way you see fit.
This seems the simplest and most flexible to me.
For XSLT 1.0 (or perhaps 1.1).
<xsl:variable name="count">10</xsl:variable>
<xsl:variable name="repeat"><xsl:text> </xsl:text></xsl:variable>
<xsl:sequence select="string-join((for $i in 1 to $count return $repeat),'')"/>
Of course the count variable is where you assign your n parameter.
I used the variable repeat to hold the tab character, but you could just replace the $repeat with the tab character in single quotes in the sequence element. Note: This variable can be of a length greater than 1, which creates a whole bunch of possibilities.
It does not use recursion, so it won't run into a recursion limit.
I don't know the maximum value you can use for count, but I tested it up to 10,000.
Globally define a long enough array of tabs:
<xsl:variable name="TABS" select="' '" />
Then use like this:
<xsl:value-of select="fn:substring($TABS, 1, fn:number($COUNT))" />
(XSLT 1.0)
<xsl:template name="tabs">
<xsl:param name="n"/>
<xsl:if test="$n > 0"> <!-- When n = 0, output nothing. -->
<xsl:call-template name="tabs"> <!-- Recursive call: call same template... -->
<xsl:with-param name="n" select="$n - 1"/> <!-- ... for writing n - 1 tabs. -->
</xsl:call-template>
<xsl:text> </xsl:text> <!-- Add one tab character. -->
</xsl:if>
</xsl:template>
Example usage:
<xsl:call-template name="tabs">
<xsl:with-param name="n" select="3"/>
</xsl:call-template>
I've discovered an LGPL-licensed library for doing this called functx, as I was sure someone had to have already done this... This is a "standard library" type XSLT library, which contains a function called repeat-string. From the docs:
The functx:repeat-string function returns a string consisting of a given number of copies of $stringToRepeat concatenated together.
Where I use it like this in my code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:functx="http://www.functx.com">
<xsl:import href="../buildlib/functx-1.0.xsl"/>
<xsl:output omit-xml-declaration="yes" />
<xsl:variable name="INDENT" select="' '" />
....
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="data-pusher-properties">
<xsl:for-each select="property">
<xsl:choose>
...
<xsl:when test="boolean(#value = '${pusher.notifications.server}')">
<xsl:value-of select="functx:repeat-string($INDENT, #indent)" />
<xsl:text>"</xsl:text>
<xsl:value-of select="#name" />
<xsl:text>": </xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="$pusher.notifications.email.server" />
<xsl:text>"\
</xsl:text>
</xsl:when>
...
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So for printing a tab character n times, call it like this:
<xsl:value-of select="functx:repeat-string(' ', n)" />
I know this question is old, but I hope this can still help someone.
Documentation for the repeat-string function
I would like to store the path of the current node so I can reused it in an expression in XSLT. Is it possible?
<!-- . into $path? -->
<xsl:value-of select="$path" />
Hi, I would like to store the path of
the current node so I can reused it in
an expression in XSLT. Is it possible?
It is possible for any given node to construct an XPath expression that, when evaluated, selects exactly this node. In fact more than one XPath expression exists that selects the same node.
See this answer for the exact XSLT code that constructs such an XPath expression.
The problem is that this XPath expression cannot be evaluated during the same transformation in XSLT 1.0 or XSLT 2.0, unless the EXSLT extension function dyn:evaluate is used (and very few XSLT 1.0 processors implement dyn:evaluate() ).
What you want can be achieved in an easier way in XSLT using the <xsl:variable> instruction:
<xsl:variable name="theNode" select="."/>
This variable can be referenced anywhere in its scope as $theNode, and can be passed as parameter when applying or calling templates.
No, this is not possible with vanilla XSLT 1.0. There is no easy way to retrieve an XPath expression string for a given node, and there is definitely no way to evaluate a string that looks like XPath as if it was XPath.
There are extensions that support dynamic evaluation of XPath expressions, but these are not compatible with every XSLT processor.
In any case, if you provide more detail around what you are actually trying to do, there might be another way to do it.
As #Dimitre and #Tomalak have point out, I don't think it has some value in the same transformation to obtain a string representing an XPath expression for a given node, and then select the node "parsing" such string. I could see some value in performing those operations in different transformations.
Besides that, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:for-each select=".|//node()|//#*">
<xsl:variable name="vPath">
<xsl:apply-templates select="." mode="getPath"/>
</xsl:variable>
<xsl:value-of select="concat($vPath,'
')"/>
<xsl:call-template name="select">
<xsl:with-param name="pPath" select="$vPath"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="/|node()|#*" mode="getPath" name="getPath">
<xsl:apply-templates select="parent::*" mode="getPath"/>
<xsl:text>/</xsl:text>
<xsl:choose>
<xsl:when test="self::*">
<xsl:value-of select="concat(name(),'[',
count(preceding-sibling::*
[name() =
name(current())]) + 1,
']')"/>
</xsl:when>
<xsl:when test="count(.|../#*)=count(../#*)">
<xsl:value-of select="concat('#',name())"/>
</xsl:when>
<xsl:when test="self::text()">
<xsl:value-of
select="concat('text()[',
count(preceding-sibling::text()) + 1,
']')"/>
</xsl:when>
<xsl:when test="self::comment()">
<xsl:value-of
select="concat('comment()[',
count(preceding-sibling::comment()) + 1,
']')"/>
</xsl:when>
<xsl:when test="self::processing-instruction()">
<xsl:value-of
select="concat('processing-instruction()[',
count(preceding-sibling::
processing-instruction()) + 1,
']')"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="select">
<xsl:param name="pPath"/>
<xsl:param name="pContext" select="/"/>
<xsl:param name="pInstruction" select="'value-of'"/>
<xsl:variable name="vPosition"
select="number(
substring-before(
substring-after($pPath,
'['),
']'))"/>
<xsl:variable name="vTest"
select="substring-before(
substring-after($pPath,
'/'),
'[')"/>
<xsl:variable name="vPath" select="substring-after($pPath,']')"/>
<xsl:choose>
<xsl:when test="$vPath">
<xsl:call-template name="select">
<xsl:with-param name="pPath" select="$vPath"/>
<xsl:with-param name="pContext"
select="$pContext/*[name()=$vTest]
[$vPosition]"/>
<xsl:with-param name="pInstruction"
select="$pInstruction"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vContext"
select="$pContext/node()
[self::*[name()=$vTest]|
self::comment()[$vTest='comment()']|
self::text()[$vTest='text()']|
self::processing-instruction()
[$vTest =
'processing-instruction()']]
[$vPosition]|
$pContext[$pPath='/']|
$pContext/#*[name() =
substring($pPath,3)]
[not($vTest)]"/>
<xsl:choose>
<xsl:when test="$pInstruction='value-of'">
<xsl:value-of select="$vContext"/>
</xsl:when>
<xsl:when test="$pInstruction='copy-of'">
<xsl:copy-of select="$vContext"/>
</xsl:when>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this input:
<?somePI pseudoAttributes?>
<root>
<!-- This is a comment -->
<node attribute="Value">text</node>
</root>
Output:
/
text
/processing-instruction()[1]
pseudoAttributes
/root[1]
text
/root[1]/comment()[1]
This is a comment
/root[1]/node[1]
text
/root[1]/node[1]/#attribute
Value
/root[1]/node[1]/text()[1]
text
I have the following value in my XML -1.8959581529998104E-4. I want to format this to the exact number it should be using XSL to give me -0.000189595815299981.
format-number(-1.8959581529998104E-4,'0.000000;-0.000000') gives me NaN.
Any ideas?
Cheers
Andez
XSLT 1.0 does not have support for scientific notation.
This: number('-1.8959581529998104E-4')
Result: NaN
This: number('-0.000189595815299981')
Result: -0.000189595815299981
XSLT 2.0 has support for scientific notation
This: number('-1.8959581529998104E-4')
Result: -0.000189595815299981
EDIT: A very simple XSLT 1.0 workaround:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="number[substring-after(.,'E')]">
<xsl:variable name="vExponent" select="substring-after(.,'E')"/>
<xsl:variable name="vMantissa" select="substring-before(.,'E')"/>
<xsl:variable name="vFactor"
select="substring('100000000000000000000000000000000000000000000',
1, substring($vExponent,2) + 1)"/>
<xsl:choose>
<xsl:when test="starts-with($vExponent,'-')">
<xsl:value-of select="$vMantissa div $vFactor"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$vMantissa * $vFactor"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this input:
<number>-1.8959581529998104E-4</number>
Output:
-0.00018959581529998104
This is based on user357812 answer. But I made it act like a function and handle non-scientific notation
<xsl:template name="convertSciToNumString" >
<xsl:param name="inputVal" select="0"/>
<xsl:variable name="vExponent" select="substring-after($inputVal,'E')"/>
<xsl:variable name="vMantissa" select="substring-before($inputVal,'E')"/>
<xsl:variable name="vFactor"
select="substring('100000000000000000000000000000000000000000000',
1, substring($vExponent,2) + 1)"/>
<xsl:choose>
<xsl:when test="number($inputVal)=$inputVal">
<xsl:value-of select="$inputVal"/>
</xsl:when>
<xsl:when test="starts-with($vExponent,'-')">
<xsl:value-of select="format-number($vMantissa div $vFactor, '#0.#############')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number($vMantissa * $vFactor, '#0.#############')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Usage:
<xsl:template match="X">
<X>
<xsl:call-template name="convertSciToNumString">
<xsl:with-param name="inputVal" select="text()"/>
</xsl:call-template>
</X>
</xsl:template>
This should handle a mix of scientific notation and decimal values.
Another possible workaround without a template:
<xsl:stylesheet version="1.0" ... xmlns:java="http://xml.apache.org/xslt/java">
...
<xsl:value-of select="format-number(java:java.lang.Double.parseDouble('1E-6'), '0.000')"/>
The logic doesn't appear to work correctly in the above answers by Moop and user357812 when determining vFactor in one particular scenario.
If vExponent is a single-digit positive number (without a preceding '+' sign), then vFactor is set to an empty string. This is because an assumption was made that the 1st character of vExponent would be a plus/minus sign and therefore the 2nd character onwards were of interest. The vMantissa variable is then multiplied by an empty string which results in the template outputting NaN.
If vExponent is a multi-digit positive number (without a preceding '+' sign), then vFactor is set to an incorrect value. Because of the aforementioned assumption, the 1st digit is ignored and the vMantissa is then multiplied by an incorrect vFactor.
Therefore, I've modified the previously posted code a little so that it can handle scientific numbers of the forms: 2E-4, 2E+4 and 2E4.
<xsl:template name="convertSciToNumString" >
<xsl:param name="inputVal" select="0"/>
<xsl:variable name="vMantissa" select="substring-before(., 'E')"/>
<xsl:variable name="vExponent" select="substring-after(., 'E')"/>
<xsl:variable name="vExponentAbs" select="translate($vExponent, '-', '')"/>
<xsl:variable name="vFactor" select="substring('100000000000000000000000000000000000000000000', 1, substring($vExponentAbs, 1) + 1)"/>
<xsl:choose>
<xsl:when test="number($inputVal)=$inputVal">
<xsl:value-of select="$inputVal"/>
</xsl:when>
<xsl:when test="starts-with($vExponent,'-')">
<xsl:value-of select="format-number($vMantissa div $vFactor, '#0.#############')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number($vMantissa * $vFactor, '#0.#############')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Just tried this with xsltproc using libxslt1.1 in version 1.1.24 under Linux:
XSLT 1.1 is able to read in exponential/scientific format now even without any dedicated template, it seems to simply work :-))