E. Williams
E. Williams

Reputation: 425

How to do a calculator with XML and XSLT

I am building an expression calculator using XSD, XML and XSLT.

At the moment only Sum operation is implemented.

I have already developed the xml-schema and a valid xml document.

SCHEMA (XSD)

<xs:complexType name="typeSum">
        <xs:sequence>
            <xs:element ref="exp:Expression"/>
            <xs:element ref="exp:Expression"/>
        </xs:sequence>
    </xs:complexType>

<xs:complexType name="EE">
    <xs:sequence>
        <xs:element ref="exp:Expression" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
</xs:complexType>

<xs:element name="Expression" abstract="true"/>
<xs:element name="EE" type="exp:EE"/>
<xs:element name="Const" type="xs:integer" substitutionGroup="exp:Expression"/>
<xs:element name="Sum" type="exp:typeSum" substitutionGroup="exp:Expression"/>

XML (In this XML I would like to do the following: Sum(Sum(8,4),Sum(1,2)))

<?xml version="1.0" encoding="UTF-8"?>
<e:EE xmlns:e="expr"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="xxx/xxx/xxx...">
    <e:Sum>
       <e:Sum>
           <e:Const>
               8
           </e:Const>
           <e:Const>
               4
           </e:Const>
       </e:Sum>
        <e:Sum>
            <e:Const>
                1
            </e:Const>
            <e:Const>
                2
            </e:Const>
        </e:Sum>
    </e:Sum>
</e:EE>

The problem comes with the XSLT. This is what I have tried so far:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:e="expr"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output
        method="xml"
        indent="yes"
        encoding="iso-8859-1"/>

    <xsl:template match="e:EE">
        <xsl:copy>
            <xsl:apply-templates select="e:Sum" />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="e:Sum">
        <xsl:variable name="v1">
            <xsl:apply-templates select="e:Const[1]"></xsl:apply-templates>
        </xsl:variable>
        <xsl:variable name="v2">
            <xsl:apply-templates select="e:Const[2]"></xsl:apply-templates>
        </xsl:variable>
        <Operation>
            <xsl:value-of select="$v1" />
            +
            <xsl:value-of select="$v2" />
        </Operation>
        <result>
        <xsl:value-of select="$v1 + $v2"/>
        </result>
    </xsl:template>

</xsl:stylesheet>

This works when I sum only 2 Const. i.e. Sum(8,4)

However, I want to recursively apply the Sum template to every expression in the XML.

Upvotes: 1

Views: 2202

Answers (1)

JLRishe
JLRishe

Reputation: 101652

You can do this by using templates effectively - define templates so that they know how to handle the operation at hand.

In the case of Sum, that would be:

  1. Get the result value of the first operand (first child element)
  2. Get the result value of the second operand (second child element)
  3. Add them together.

Sum doesn't need to know what kind of operands it has. It can just apply templates to them and let the XSLT figure that out. This is what we get:

<xsl:template match="e:Sum">
  <xsl:variable name="v1">
    <xsl:apply-templates select="*[1]" />
  </xsl:variable>
  <xsl:variable name="v2">
    <xsl:apply-templates select="*[2]" />
  </xsl:variable>

  <xsl:value-of select="$v1 + $v2"/>
</xsl:template>

It probably makes sense to also define a template for e:Const, just so it's clearly defined:

<xsl:template match="e:Const">
  <xsl:value-of select ="."/>
</xsl:template>

Here's a full XSLT, with the addition of e:Multiply and a unary operation (e:Invert):

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

  <xsl:output
      method="xml"
      indent="yes"
      encoding="iso-8859-1"/>

  <xsl:template match="e:EE">
    <xsl:copy>
      <xsl:apply-templates select="*[1]" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="e:Sum">
    <xsl:variable name="v1">
      <xsl:apply-templates select="*[1]" />
    </xsl:variable>
    <xsl:variable name="v2">
      <xsl:apply-templates select="*[2]" />
    </xsl:variable>

    <xsl:value-of select="$v1 + $v2"/>
  </xsl:template>

  <xsl:template match="e:Multiply">
    <xsl:variable name="v1">
      <xsl:apply-templates select="*[1]" />
    </xsl:variable>
    <xsl:variable name="v2">
      <xsl:apply-templates select="*[2]" />
    </xsl:variable>

    <xsl:value-of select="$v1 * $v2"/>
  </xsl:template>

  <xsl:template match="e:Invert">
  <xsl:variable name="v1">
      <xsl:apply-templates select="*[1]" />
  </xsl:variable>

    <xsl:value-of select="1 div $v1"/>
  </xsl:template>

  <xsl:template match="e:Const">
    <xsl:value-of select ="."/>
  </xsl:template>

</xsl:stylesheet>

When run on this input XML:

<?xml version="1.0" encoding="UTF-8"?>
<e:EE xmlns:e="expr"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="xxx/xxx/xxx...">
  <e:Sum>
    <e:Sum>
      <e:Const>8</e:Const>
      <e:Const>4</e:Const>
    </e:Sum>
    <e:Sum>
      <e:Multiply>
        <e:Invert>
          <e:Const>3</e:Const>
        </e:Invert>
        <e:Const>6</e:Const>
      </e:Multiply>
      <e:Const>2</e:Const>
    </e:Sum>
  </e:Sum>
</e:EE>

The result is:

<e:EE xmlns:e="expr" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">16</e:EE>

Upvotes: 2

Related Questions