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>
badtz7229Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Geert BormansInformation ArchitectCommented:
It is a bit more involving than you might have expected

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     version="1.0">
    
    <xsl:output indent="yes"/>
    
    <xsl:template match="/">
        <xsl:apply-templates select="//Text"/>
    </xsl:template>
    
    <xsl:template match="Text">
        <xsl:variable name="codes">
            <xsl:call-template name="get-item">
                <xsl:with-param name="str" select="normalize-space(.)"/>
                <xsl:with-param name="nr" select="5"/>
            </xsl:call-template>
        </xsl:variable>
        <Taxes>
            <xsl:attribute name="Total">
                <xsl:call-template name="get-total">
                    <xsl:with-param name="codes" select="$codes"/>
                    <xsl:with-param name="curr-tot" select="0"/>
                </xsl:call-template>
            </xsl:attribute>
            <xsl:call-template name="create-tax">
                <xsl:with-param name="codes" select="$codes"/>
            </xsl:call-template>
        </Taxes>
    </xsl:template>
    
    <xsl:template name="create-tax">
        <xsl:param name="codes"/>
        <xsl:choose>
            <xsl:when test="contains($codes, '/')">
                <Tax>
                    <xsl:attribute name="Amount">
                        <xsl:call-template name="get-val-from-code">
                            <xsl:with-param name="cd" select="substring-before($codes, '/')"/>
                        </xsl:call-template>
                    </xsl:attribute>
                    <Code>
                        <xsl:call-template name="get-code-from-code">
                            <xsl:with-param name="cd" select="substring-before($codes, '/')"/>
                        </xsl:call-template>
                    </Code>
                </Tax>
                <xsl:call-template name="create-tax">
                    <xsl:with-param name="codes" select="substring-after($codes, '/')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <Tax>
                    <xsl:attribute name="Amount">
                        <xsl:call-template name="get-val-from-code">
                            <xsl:with-param name="cd" select="$codes"/>
                        </xsl:call-template>
                    </xsl:attribute>
                    <Code>
                        <xsl:call-template name="get-code-from-code">
                            <xsl:with-param name="cd" select="$codes"/>
                        </xsl:call-template>
                    </Code>
                </Tax>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template name="get-total">
        <xsl:param name="codes"/>
        <xsl:param name="curr-tot"/>
        <xsl:choose>
            <xsl:when test="contains($codes, '/')">
                <xsl:variable name="this-val">
                    <xsl:call-template name="get-val-from-code">
                        <xsl:with-param name="cd" select="substring-before($codes, '/')"/>
                    </xsl:call-template>
                </xsl:variable>
                <xsl:call-template name="get-total">
                    <xsl:with-param name="codes" select="substring-after($codes, '/')"/>
                    <xsl:with-param name="curr-tot" select="$curr-tot + $this-val">
                    </xsl:with-param>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="this-val">
                    <xsl:call-template name="get-val-from-code">
                        <xsl:with-param name="cd" select="$codes"/>
                    </xsl:call-template>
                </xsl:variable>
                <xsl:value-of select="$this-val + $curr-tot"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
    <xsl:template name="get-val-from-code">
        <xsl:param name="cd"/>
        <xsl:value-of select="number(translate($cd, translate($cd, '1234567890', ''), ''))"/>
    </xsl:template>

    <xsl:template name="get-code-from-code">
        <xsl:param name="cd"/>
        <xsl:value-of select="translate($cd, '1234567890', '')"/>
    </xsl:template>
    
    <xsl:template name="get-item">
        <xsl:param name="str"/>
        <xsl:param name="nr"/>
        <xsl:choose>
            <xsl:when test="$nr > 1 and contains($str, '/') ">
                <xsl:call-template name="get-item">
                    <xsl:with-param name="str" select="substring-after($str, '/')"/>
                    <xsl:with-param name="nr" select="$nr - 1"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="substring-before($str, ' ')"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
</xsl:stylesheet>

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Geert BormansInformation ArchitectCommented:
Honestly, tasks like this are a very good reason for moving to XSLT2
0
Geert BormansInformation ArchitectCommented:
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

0
Big Business Goals? Which KPIs Will Help You

The most successful MSPs rely on metrics – known as key performance indicators (KPIs) – for making informed decisions that help their businesses thrive, rather than just survive. This eBook provides an overview of the most important KPIs used by top MSPs.

Geert BormansInformation ArchitectCommented:
both solutions tested and found working on both your examples
0
badtz7229Author Commented:
Great. I will test on my end. Thx so much
0
badtz7229Author Commented:
What is tokenize() and the purpose of xmlns:fun="#internal"
     exclude-result-prefixes="fun"?
0
Geert BormansInformation ArchitectCommented:
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"
0
badtz7229Author Commented:
I'm getting tokenize is an unknown xslt function.
0
Geert BormansInformation ArchitectCommented:
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)
0
badtz7229Author Commented:
ok, that seemed to do the trick.
0
badtz7229Author Commented:
thanks very much
0
Geert BormansInformation ArchitectCommented:
welcome
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
CSS

From novice to tech pro — start learning today.