iteong
iteong

Reputation: 735

Trying to increment a variable although XSLT variables are immutable

I'm trying to use XSLT to convert 1 XML file to another, where the resulting XML file is a result of a query performed. Unfortunately, from what I understand, variables in XSLT are immutable and cannot be changed. I was wondering if there is any other way that I can add up the values so that I can get the total value for any query returned for relevant part.

As you can see in the original XML file, there is a total that counts the total value of all the age-groups number in each entry. I used the XSLT query to extract out different age groups based on the queried age range of $AGE_GROUP1 and $AGE_GROUP2, but since any declared variables are immutable, I can't think of a way to add up the queried values of the age groups.

The following is the source XML file:

<?xml version="1.0" encoding="UTF-8"?>
<Replacements>
   <Entry>
      <Year>2005</Year>
      <Month>DEC</Month>
      <Reason>Blemish on Card</Reason>
      <Age>
         <Group>
            <Range>Total</Range>
            <value>16</value>
         </Group>
         <Group>
            <Range>16-17</Range>
            <value>3</value>
         </Group>
         <Group>
            <Range>18-25</Range>
            <value>12</value>
         </Group>
         <Group>
            <Range>26-29</Range>
            <value>1</value>
         </Group>
      </Age>
   </Entry>
   <Entry>
      <Year>2005</Year>
      <Month>DEC</Month>
      <Reason>Damaged</Reason>
      <Age>
         <Group>
            <Range>Total</Range>
            <value>7</value>
         </Group>
         <Group>
            <Range>16-17</Range>
            <value>6</value>
         </Group>
         <Group>
            <Range>18-25</Range>
            <value>0</value>
         </Group>
         <Group>
            <Range>26-29</Range>
            <value>1</value>
         </Group>
      </Age>
   </Entry>
</Replacements>

The following XSLT extracts only the relevant 2 of 3 age groups (only extracts age groups from age 14 to 24), so 1 of the age group for each entry in the original data set is not output to the resulting XML file:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0">

    <xsl:variable name = "QUERY_YEAR1" select = "2005" />
    <xsl:variable name = "QUERY_YEAR2" select = "2007" />
    <xsl:variable name = "AGE_GROUP1" select = "14" />
    <xsl:variable name = "AGE_GROUP2" select = "24" />
    <xsl:variable name = "TOTAL" select = "0" />

    <xsl:output method="xml" indent="yes" />
    <xsl:strip-space elements="*" />

    <xsl:template match="/">

        <xsl:for-each select="Replacements/Entry">
            <xsl:if test="$QUERY_YEAR1 &lt;= Year/text() and $QUERY_YEAR2 &gt;= Year/text()">
                <Entry>
                    <Year>
                        <xsl:value-of select="Year/text()" />
                    </Year>
                    <Month>
                        <xsl:value-of select="Month/text()" />
                    </Month>
                    <Reason>
                        <xsl:value-of select="Reason/text()" />
                    </Reason>
                    <xsl:for-each select="Age/Group">

                        <!-- $AGE_GROUP1 < 16 & 17, $AGE_GROUP2 > 16 & 17 for age group 16-17 (e.g. $AGE_GROUP1 = 14 & $AGE_GROUP2 = 30 -->
                        <xsl:if test="$AGE_GROUP1 &lt;= number(substring(Range/text(), 1, 2)) and $AGE_GROUP1 &lt;= number(substring(Range/text(), 4, 2)) and $AGE_GROUP2 &gt;= number(substring(Range/text(), 1, 2)) and $AGE_GROUP2 &gt;= number(substring(Range/text(), 4, 2)) and not(Range/text() = 'Total') and not(Range/text() = '80+')">

                            <!-- Add to $TOTAL value -->
                            <xsl:variable name = "TOTAL" select = "number($TOTAL) + number(value/text())" />

                            <AgeGroup>
                                <Range>
                                    <xsl:value-of select="Range/text()" />
                                </Range>
                                <value>
                                    <xsl:value-of select="value/text()" />
                                </value>
                            </AgeGroup>
                        </xsl:if>

                        <!-- $AGE_GROUP1 < 30 & 39, $AGE_GROUP2 = 30 & < 39 for age group 30-39 (e.g. $AGE_GROUP1 = 14 & $AGE_GROUP2 = 30 -->
                        <!-- $AGE_GROUP1 < 26 & 29, $AGE_GROUP2 > 26 & < 29 for age group 26-29 (e.g. $AGE_GROUP1 = 19 & $AGE_GROUP2 = 27 -->
                        <xsl:if test="$AGE_GROUP1 &lt;= number(substring(Range/text(), 1, 2)) and $AGE_GROUP1 &lt;= number(substring(Range/text(), 4, 2)) and $AGE_GROUP2 &gt;= number(substring(Range/text(), 1, 2)) and $AGE_GROUP2 &lt;= number(substring(Range/text(), 4, 2)) and not(Range/text() = 'Total') and not(Range/text() = '80+')">

                            <!-- Add to $TOTAL value -->
                            <xsl:variable name = "TOTAL" select = "number($TOTAL) + number(value/text())" />

                            <AgeGroup>
                                <Range>
                                    <xsl:value-of select="Range/text()" />
                                </Range>
                                <value>
                                    <xsl:value-of select="value/text()" />
                                </value>
                            </AgeGroup>
                        </xsl:if>

                        <!-- $AGE_GROUP1 > 18 & < 25, $AGE_GROUP2 > 18 & 25 for age group 18-25 (e.g. $AGE_GROUP1 = 19 & $AGE_GROUP2 = 27 -->
                        <xsl:if test="$AGE_GROUP1 &gt;= number(substring(Range/text(), 1, 2)) and $AGE_GROUP1 &lt;= number(substring(Range/text(), 4, 2)) and $AGE_GROUP2 &gt;= number(substring(Range/text(), 1, 2)) and $AGE_GROUP2 &gt;= number(substring(Range/text(), 4, 2)) and not(Range/text() = 'Total') and not(Range/text() = '80+')">

                            <!-- Add to $TOTAL value -->
                            <xsl:variable name = "TOTAL" select = "number($TOTAL) + number(value/text())" />

                            <AgeGroup>
                                <Range>
                                    <xsl:value-of select="Range/text()" />
                                </Range>
                                <value>
                                    <xsl:value-of select="value/text()" />
                                </value>
                            </AgeGroup>
                        </xsl:if>

                    </xsl:for-each>

                    <!-- Total calculation for all values within returned age groups -->
                    <AgeGroup>
                        <Range>
                            <xsl:value-of select="'Total'" />
                        </Range>
                        <value>
                            <xsl:value-of select="$TOTAL" />
                        </value>
                    </AgeGroup>

                </Entry>
            </xsl:if>
        </xsl:for-each>

    </xsl:template>

</xsl:stylesheet>

What I get now is the following, which does not have the total value of the extracted age group values as I cannot assign the variable $TOTAL at each juncture:

<?xml version="1.0" encoding="UTF-8"?>
<Entry>
   <Year>2005</Year>
   <Month>DEC</Month>
   <Reason>Blemish on Card</Reason>
   <AgeGroup>
      <Range>16-17</Range>
      <value>3</value>
   </AgeGroup>
   <AgeGroup>
      <Range>18-25</Range>
      <value>12</value>
   </AgeGroup>
   <AgeGroup>
      <Range>Total</Range>
      <value>0</value>
   </AgeGroup>
</Entry>
<Entry>
   <Year>2005</Year>
   <Month>DEC</Month>
   <Reason>Damaged</Reason>
   <AgeGroup>
      <Range>16-17</Range>
      <value>6</value>
   </AgeGroup>
   <AgeGroup>
      <Range>18-25</Range>
      <value>0</value>
   </AgeGroup>
   <AgeGroup>
      <Range>Total</Range>
      <value>0</value>
   </AgeGroup>
</Entry>

What I need is this:

<?xml version="1.0" encoding="UTF-8"?>
<Entry>
   <Year>2005</Year>
   <Month>DEC</Month>
   <Reason>Blemish on Card</Reason>
   <AgeGroup>
      <Range>16-17</Range>
      <value>3</value>
   </AgeGroup>
   <AgeGroup>
      <Range>18-25</Range>
      <value>12</value>
   </AgeGroup>
   <AgeGroup>
      <Range>Total</Range>
      <value>15</value>
   </AgeGroup>
</Entry>
<Entry>
   <Year>2005</Year>
   <Month>DEC</Month>
   <Reason>Damaged</Reason>
   <AgeGroup>
      <Range>16-17</Range>
      <value>6</value>
   </AgeGroup>
   <AgeGroup>
      <Range>18-25</Range>
      <value>0</value>
   </AgeGroup>
   <AgeGroup>
      <Range>Total</Range>
      <value>6</value>
   </AgeGroup>
</Entry>

The command I'm using to do the XSLT is this: java -jar saxon9he.jar sample1.xml sample1.xslt > out.xml

I'm just wondering if anyone has any idea how I can do the addition of values like what is seen in the XSLT whenever an if condition is entered, so that the value for that age group is added to the total, and the total can be returned inside the output XML file?

Upvotes: 0

Views: 452

Answers (1)

Tim C
Tim C

Reputation: 70638

I think, rather than have three separate expressions to test for overlapping ranges, you can reduce it to just one condition....

 <xsl:for-each select="Age/Group
                       [$AGE_GROUP1 &lt;= number(substring(Range/text(), 4, 2))]
                       [$AGE_GROUP2 &gt;= number(substring(Range/text(), 1, 2))]">

But in terms of getting the total, you would just use the sum function to sum up all the value elements. You could put the above expression in a variable...

<xsl:variable name="groups" select="Age/Group[$AGE_GROUP1 &lt;= number(substring(Range/text(), 4, 2))][$AGE_GROUP2 &gt;= number(substring(Range/text(), 1, 2))]" />

Then you get the total as follows:

<xsl:value-of select="sum($groups/value)" />

Try this XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="2.0">

    <xsl:variable name = "QUERY_YEAR1" select = "2005" />
    <xsl:variable name = "QUERY_YEAR2" select = "2007" />
    <xsl:variable name = "AGE_GROUP1" select = "14" />
    <xsl:variable name = "AGE_GROUP2" select = "24" />
    <xsl:variable name = "TOTAL" select = "0" />

    <xsl:output method="xml" indent="yes" />
    <xsl:strip-space elements="*" />

    <xsl:template match="/">

        <xsl:for-each select="Replacements/Entry">
            <xsl:if test="$QUERY_YEAR1 &lt;= Year/text() and $QUERY_YEAR2 &gt;= Year/text()">
                <Entry>
                    <Year>
                        <xsl:value-of select="Year/text()" />
                    </Year>
                    <Month>
                        <xsl:value-of select="Month/text()" />
                    </Month>
                    <Reason>
                        <xsl:value-of select="Reason/text()" />
                    </Reason>
                    <xsl:variable name="groups" select="Age/Group[$AGE_GROUP1 &lt;= number(substring(Range/text(), 4, 2))][$AGE_GROUP2 &gt;= number(substring(Range/text(), 1, 2))]" />
                    <xsl:for-each select="$groups">
                            <AgeGroup>
                                <Range>
                                    <xsl:value-of select="Range/text()" />
                                </Range>
                                <value>
                                    <xsl:value-of select="value/text()" />
                                </value>
                            </AgeGroup>
                    </xsl:for-each>
                    <!-- Total calculation for all values within returned age groups -->
                    <AgeGroup>
                        <Range>
                            <xsl:value-of select="'Total'" />
                        </Range>
                        <value>
                            <xsl:value-of select="sum($groups/value)" />
                        </value>
                    </AgeGroup>
                </Entry>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

EDIT: In answer to your comment, if you had an age range of the form "80+" I think you could tweak the groups variable like so:

<xsl:variable name="groups" select="Age/Group
                    [substring(Range/text(), 3, 1) = '+' or $AGE_GROUP1 &lt;= number(substring(Range/text(), 4, 2))]
                    [$AGE_GROUP2 &gt;= number(substring(Range/text(), 1, 2))]" />

Upvotes: 1

Related Questions