Link to home
Start Free TrialLog in
Avatar of tesmc
tesmcFlag for United States of America

asked on

XSL: loop through string to obtain values then sum it

I have the following input XML.

<root>
      <Envelope>
            <Body>
             <Response>
              <Itin>
                  <Services Item="005"">
                        <Service Code="SOS">
                        <Extensions>
                            <Type>PAYMENT</Type>
                        </Extensions>
                              <Text>RA UK1 YVRYYZ0702P01DEC/SEAT/ACCPT/CAD1000/095CD/050AB CCVIX0001E 123</Text>
                        </Service>
                  </Services>
             <Itin>                  
            </Response>
            </Body>
      </Envelope>
</root>  


I want to get the Text value and obtain the values after the 4th instance of "/" and before the next empty space.  and build the following XML node
<Taxes Total="145">
      <Tax Amount="95">
            <Code>CD</Code>
      </Tax>
      <Tax Amount="50">
            <Code>AB</Code>
      </Tax>
</Taxes>

Where Total is sum of both Tax/@Amount.

Also, it is possible that Text would only contain only one tax code such as:
 <Text>RA UK1 YVRYYZ0702P01DEC/SEAT/ACCPT/USD1500/075AB CCVIXX1237E 123</Text>  
In which case,
<Taxes Total="75">
      <Tax Amount="75">
            <Code>AB</Code>
      </Tax>
</Taxes>
ASKER CERTIFIED SOLUTION
Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Honestly, tasks like this are a very good reason for moving to XSLT2
75% lines of code removed, code easier to grasp, no recursion and a huge performance difference

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fun="#internal"
     exclude-result-prefixes="fun"
     version="2.0">
    
    <xsl:output indent="yes"/>
    
    <xsl:variable name="regex" select="'^(\d+)([^\d]+)$'"/>
     
    <xsl:template match="/">
        <xsl:apply-templates select="//Text"/>
    </xsl:template>
    
    <xsl:template match="Text">
        <xsl:variable name="codes" select="substring-before(string-join(tokenize(normalize-space(.), '/')[position() > 4], '/'), ' ')"/>
        <Taxes Total="{sum(for $i in tokenize($codes, '/') return number(fun:value-from-code($i)))}">
            <xsl:for-each select="tokenize($codes, '/')">
                <Tax Amount="{fun:value-from-code(.)}">
                     <Code>
                         <xsl:value-of select="fun:code-from-code(.)"/>
                    </Code>
                </Tax>
            </xsl:for-each>
        </Taxes>
    </xsl:template>
    
    <xsl:function name="fun:value-from-code">
        <xsl:param name="code"/>
        <xsl:value-of select="number(replace($code, $regex, '$1'))"/>
    </xsl:function>

    <xsl:function name="fun:code-from-code">
        <xsl:param name="code"/>
        <xsl:value-of select="replace($code, $regex, '$2')"/>
    </xsl:function>
 
    
</xsl:stylesheet>

Open in new window

both solutions tested and found working on both your examples
Avatar of tesmc

ASKER

Great. I will test on my end. Thx so much
Avatar of tesmc

ASKER

What is tokenize() and the purpose of xmlns:fun="#internal"
     exclude-result-prefixes="fun"?
tokenize()  is an XPath2 function that breaks apart a string (argument 1) into a sequence on each match of a regular expression (argument 2)

tokenize('abcbdbe', 'b') will return a sequence ('a', 'c', 'd', 'e')

xmlns:fun="#internal"

functions in XSLT2 need to be in a namespace (the null namespace is reserved for XPath built in functions)

so I make a namespace
noone cares about what the uri for that one is,
in projects I use something like
urn:companyname:customername:project:xslt:functions
eg.
xmlns:fun="urn:gertone:badtz:stringloop:xslt:functions"
I give the namespace a prefix, eg. "fun:"
and I make sure the namespace node does not make it to the output tree
exclude-result-prefixes="fun"
Avatar of tesmc

ASKER

I'm getting tokenize is an unknown xslt function.
OK, that means you are using an XSLT1 processor

I gave you an XSLT1 solution because your other XSLTs are XSLT1

The XSLT2 solution requires an XSLT2 processor such as saxon (www.saxonica.com)
Avatar of tesmc

ASKER

ok, that seemed to do the trick.
Avatar of tesmc

ASKER

thanks very much