Re: [xsl] XPath MOD 10 calculation

Subject: Re: [xsl] XPath MOD 10 calculation
From: Abel Braaksma <abel.online@xxxxxxxxx>
Date: Fri, 25 May 2007 16:03:13 +0200
Jesper Tverskov wrote:
I have a feeling that my original code for UPC was right, as seen also
in the "Check digit calculation" section here,
http://en.wikipedia.org/wiki/Universal_Product_Code.

which proofs to be different then the Luhn test I posted. Though the tests are quite similar. I also read that the UPC (not the courier) is always 12 digits long, assuming you only work with UPC-A. This removes the necessity of counting from the right, which removes the necessity of the reverse() function.


Inspired by Abel, I have now made an improved version of my original
code for testing if UPC code is legal. The two versions, both tested,
are shown side by side.


<improved>


              <xsl:variable name="y" select="for $i in
string-to-codepoints(substring(., 1, string-length(.) - 1)) return
codepoints-to-string($i)"/>

<xsl:variable name="z" select="sum((for $i in $y[(position()) mod 2 =
1] return xs:integer($i)*3, for $i in $y[(position()) mod 2 = 0]
return xs:integer($i))) mod 10"/>

              <xsl:if test="(if ($z ne 0) then (10 - $z) else $z) ne
xs:integer(substring(., string-length(.), 1))">UPC not legal</xsl:if>

</improved>

Thanks for the credit, but this was not really what I had in mind. I used string-to-codepoints one-way for easier calculation and removing of xs:integer, which is not what happens above.


You asked for 'elegant' in your original post, here's a new go on that. Taken the new information of the wikipedia article, I'd use my first attempt and alter it to the following (using David's suggestion of simplifying the mod10 minus 10 etc). Here's a simple one liner, assuming the number to check is in the current node:

sum((number(substring(., 12, 1)),
     for $i in (1,3,5,7,9,11) return number(substring(., $i, 1)) * 3,
     for $i in (2,4,6,8,10) return number(substring(., $i, 1)))) mod 10 = 0


If you care for the string-to-codepoints, this can be reduced (??? the word obfuscated comes to mind...) as such (note the impossibility to use position() mod 2 on the even numbers because of the checknum):


sum((number(substring(., 12, 1)),
for $i in string-to-codepoints(.)[position() mod 2 = 1] return ($i - 48) * 3,
for $i in string-to-codepoints(.)[position() = (2,4,6,8,10)] return $i - 48)) mod 10 = 0



Cheers, -- Abel Braaksma

Current Thread