问题
Need to add alternate digits in a number receiving from XML file using XSLT, for instance If I am receiving a 123456789, I need to calculate alternate digit sum from right most using XSLT function, can i have any suggestions on this ?
Thanks, Laxmikanth.S
回答1:
This is extremely easy to do with XSLT 2.0 (actually just with a single XPath 2.0 expression):
The following XSLT transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"sum(for $n in reverse(string-to-codepoints('123456789'))
[position() mod 2 eq 1]
return
$n - string-to-codepoints('0')
)
"/>
</xsl:template>
</xsl:stylesheet>
when aplied on any XML document (not used), produces the correct result:
25
Do note the use of the XPath 2.0 functions: string-to-codepoints(), codepoints-to-string() and reverse().
UPDATE: A similar, but a little simpler expression is:
sum(for $n in reverse(string-to-codepoints('123456789'))
[position() mod 2 eq 1]
return
xs:integer(codepoints-to-string($n))
)
In order for this expression to compile, the xs prefix must be bound to the namespace: "http://www.w3.org/2001/XMLSchema"
回答2:
Just in case you are doing this because you need to calculate the Luhn (mod 10) checksum for something, this was published on IBM developerWorks.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:tns="http://www.example.org/tns" xmlns:dp="http://www.datapower.com/extensions" extension-element-prefixes="dp" exclude-result-prefixes="dp regexp" xmlns:fn="http://www.w3.org/2005/02/xpath-functions">
<xsl:template match="/">
<xsl:call-template name="recLuhn">
<xsl:with-param name="index" select="1"/>
<xsl:with-param name="parity" select="string-length(CreditCard) mod 2"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="recLuhn">
<xsl:param name="index"/>
<xsl:param name="parity"/>
<xsl:choose>
<xsl:when test="$index <= string-length(CreditCard) ">
<xsl:variable name="sum">
<xsl:call-template name="recLuhn">
<xsl:with-param name="index" select="$index + 1"/>
<xsl:with-param name="parity" select="$parity"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="thisSum">
<xsl:choose>
<xsl:when test="$index mod 2 != $parity">
<xsl:choose>
<xsl:when test="substring(CreditCard, $index, 1)*2 > 9">
<xsl:value-of select="(number(substring(CreditCard, $index, 1)) * 2) - 9"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="number(substring(CreditCard, $index, 1)) * 2"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="number(substring(CreditCard, $index, 1))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$thisSum + $sum"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
回答3:
For an XSLT 1.0 solution I would use FXSL.
This transformation first reverses the string, using the "str-reverse" template, then performs two calls to the "str-foldl" template and does the work:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dvc-foldl-func="dvc-foldl-func"
exclude-result-prefixes="xsl dvc-foldl-func"
>
<xsl:import href="str-foldl.xsl"/>
<xsl:import href="strReverse.xsl"/>
<dvc-foldl-func:delEven/>
<dvc-foldl-func:add/>
<xsl:variable name="vFoldlDelEven"
select="document('')/*/dvc-foldl-func:delEven[1]"/>
<xsl:variable name="vFoldlAdd"
select="document('')/*/dvc-foldl-func:add[1]"/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="vReversed">
<xsl:call-template name="strReverse">
<xsl:with-param name="pStr" select="'123456789'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vOddDigits">
<xsl:call-template name="str-foldl">
<xsl:with-param name="pFunc" select="$vFoldlDelEven"/>
<xsl:with-param name="pStr" select="$vReversed"/>
<xsl:with-param name="pA0" select="''"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="str-foldl">
<xsl:with-param name="pFunc" select="$vFoldlAdd"/>
<xsl:with-param name="pStr" select="$vOddDigits"/>
<xsl:with-param name="pA0" select="0"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="dvc-foldl-func:add">
<xsl:param name="arg1" select="0"/>
<xsl:param name="arg2" select="0"/>
<xsl:value-of select="$arg1 + $arg2"/>
</xsl:template>
<xsl:template match="dvc-foldl-func:delEven">
<xsl:param name="arg1"/>
<xsl:param name="arg2"/>
<xsl:copy-of select=
"concat($arg1,
$arg2 * (string-length($arg1) mod 2 = 0)
)
"/>
</xsl:template>
</xsl:stylesheet>
When the above transformation is applied on any source XML (ignored), the wanted result is obtained:
25
Do note:
- The first template call reverses the string.
- The second template call replaces every even-th-placed digit of the reversed string with 0.
- The last template call produces the sum of all digits in the string produced by the second template call
回答4:
If you have the EXSL extensions (http://exslt.org) you can just do something like:
sum(str:tokenize($x,'')[position() mod 2 = 0])
if not you can do something similar by looping with the substring function.
回答5:
If you are not able to use XSLT 2.0, it is still possible using XSLT 1.0. One way could be be recursively calling a named template:
<xsl:template match="number">
<xsl:call-template name="sumdigits">
<xsl:with-param name="number" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="sumdigits">
<xsl:param name="number" />
<xsl:param name="runningtotal">0</xsl:param>
<xsl:choose>
<xsl:when test="string-length($number) = 0">
<xsl:value-of select="$runningtotal" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="lastdigit" select="substring($number, string-length($number), 1)" />
<xsl:variable name="newrunningtotal" select="number($runningtotal) + number($lastdigit)" />
<xsl:call-template name="sumdigits">
<xsl:with-param name="number" select="substring($number, 1, string-length($number) - 2)" />
<xsl:with-param name="runningtotal" select="$newrunningtotal" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
It works by getting the last digit in the input, adds it on to a 'running total' and recursively calling the named template with the last two digits of the input removed. When there are no digits left, the running total can be returned.
This XSLT fragment assumes the input is in the XML within the element named 'number'
<number>123456789</number>
The result should be 25. As you can see, doing it in XSLT2.0 would be much nicer.
回答6:
Here's another implementation of the Luhn check digit generation algorithm, in XSLT 2 and XQuery, building on @Dimitre's approach to reversing and filtering even / odd digits.
The handling of the (reversed) odd digits is pre-mapped into an array, and then substituted.
Note also that the final step can be done either (equivalent):
- 10's complement of Sum
- (Sum * 9) Modulo 10
<xsl:variable name="luhnIndexMap" as="element()*">
<Item>0</Item><!--0 * 2 = 0-->
<Item>2</Item><!--1 * 2 = 2-->
<Item>4</Item><!--2 * 2 = 4-->
<Item>6</Item><!--3 * 2 = 6-->
<Item>8</Item><!--4 * 2 = 8-->
<Item>1</Item><!--5 * 2 = 10. 1 + 0 = 1-->
<Item>3</Item><!--6 * 2 = 12. 1 + 2 = 3-->
<Item>5</Item><!--7 * 2 = 14. 1 + 4 = 5-->
<Item>7</Item><!--8 * 2 = 16. 1 + 6 = 7-->
<Item>9</Item><!--9 * 2 = 18. 1 + 8 = 9-->
</xsl:variable>
<xsl:function name="my:getLuhnCheckDigit" as="xs:string">
<xsl:param name="digits" as="xs:string"/>
<xsl:variable name="reversedDigits" select="reverse(string-to-codepoints($digits))"></xsl:variable>
<xsl:variable name="codePoint0"
select="string-to-codepoints('0')"></xsl:variable>
<xsl:variable name="sumEvens"
select="sum(for $n in $reversedDigits[position() mod 2 eq 0] return $n - $codePoint0)"></xsl:variable>
<xsl:variable name="sumMappedOdds"
select="sum(for $n in $reversedDigits[position() mod 2 eq 1] return $luhnIndexMap[$n - $codePoint0 + 1])"></xsl:variable>
<xsl:variable name="luhnCheckDigit"
select="(($sumEvens + $sumMappedOdds) * 9) mod 10"></xsl:variable>
<xsl:value-of select="$luhnCheckDigit"></xsl:value-of>
</xsl:function>
Notes
- Place the function in a suitable namespace
- Invoke it like so:
<xsl:value-of select="my:getLuhnCheckDigit('448508566208738')"></xsl:value-of>
Returns 3
回答7:
Yes; don't use XSLT or XML for this, it doesn't make sense.
来源:https://stackoverflow.com/questions/1281062/sum-of-alternate-numbers-xml-xsl