Search code examples
xslt-2.0xslt-3.0

Issues while performing Arithmetic Operations on Strings using XSLT 2.0 or 3.0


I've been having issues while doing arithmetic operations on following XML

Source XML

<?xml version="1.0" encoding="UTF-8"?>
<Compensation>
    <Salary>
        <BasePay>$18600.12</BasePay>
        <Bonus>$3500.99</Bonus>
        <Gym>$670</Gym>
        <Tax>$30,000</Tax>
    </Salary>
    <Salary>
        <BasePay>$28600.12</BasePay>
        <Bonus>$1500.99</Bonus>
        <Gym/>
        <Tax>$50,000</Tax>
    </Salary> 
</Compensation>

Current XSLT

<?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:this="urn:this-stylesheet"
        
        exclude-result-prefixes="xs this"
        version="2.0">
        <xsl:output method="xml" indent="yes"/>
        
       
        
        <xsl:function name = "this:translateCurrency">
            <xsl:param name="stringValue"/>        
            <xsl:value-of select="format-number(xs:decimal(translate(xs:string($stringValue), '$,','')), '#.##')"/>       
        </xsl:function>    
        
        
        <xsl:template match="Compensation">
            <Worker>
            <xsl:for-each select="Salary">
                <Comp>
                <Amount>                
                    <xsl:value-of select="this:translateCurrency(BasePay) - this:translateCurrency(Tax)  "/>
                </Amount>
                
                <NoBonus>
                    <xsl:value-of select="this:translateCurrency(BasePay) + this:translateCurrency(Gym)  "/>
                </NoBonus>
                </Comp>
            </xsl:for-each>       
            </Worker>
            
        </xsl:template>
    </xsl:stylesheet>

Currency symbol and commas will always be present in amount related XML elements such as <BasePay> <Bonus> <Gym> <Tax> which i am translating and converting to decimal before adding or substracting.

There are two issues

1. Since my source XML have many Amount related fields, I have declared a function for translating and converting to decimal. However, I'm unable to get my function rounding to two decimal points. I was expecting following line of code in my function will be able to round to two decimal points.

<xsl:value-of select="format-number(xs:decimal(translate(xs:string($stringValue), '$,','')), '#.##')"/>

2. It's possible that some of the amount fields may be null for e.g. <Gym/> is null in my Source XML and current version of XSLT returns Cannot convert to string "" to xs:decimal no digits in value.

I tried $stringValue!='' in xsl:function statement and Gym!='' but to no avail.

Can anyone help me figure out what i should be doing to get my function round to two decimal points and get past no digits in value error?

<NoBonus>
  <xsl:value-of select="this:translateCurrency(BasePay) + this:translateCurrency(Gym!='')  "/>
</NoBonus>

Expected Result

<?xml version="1.0" encoding="UTF-8"?>
<Worker>
   <Comp>
      <Amount>-11399.88</Amount>
      <NoBonus>19270.12</NoBonus>
   </Comp>
   <Comp>
      <Amount>-21399.88</Amount>
      <NoBonus>28600.12</NoBonus>
   </Comp>
</Worker>

Solution

  • If you want to convert a string to a decimal value then don't use format-number on it. So for your input values to be converted into xs:decimals you need e.g.

    <xsl:function name="this:translateCurrency" as="xs:decimal">
      <xsl:param name="input" as="xs:string"/>
      <xsl:sequence
        select="if ($input = '') 
                then 0
                else xs:decimal(translate($input, '$,', ''))"/>
    </xsl:function>
    

    Then use those xs:decimal values in any arithmetic computations, only where you need to output the final result of an arithmetic computation in a certain format use format-number on that result to ensure e.g. you get two decimals.