Skip to content

15. Exercise [Tax Calculation] with XML

In this exercise, which we have already covered many times, the server returns the results to the client in the form of an XML stream:

  • <response><error>msg</error></response> in case of an error;
  • <response><tax>value</tax></response> if the tax could be calculated.

We use what we have just learned about parsing an XML document.

15.1. The Web Service

The web service is no different from the one studied previously, except that the XML response sent to the client is slightly different. The architecture remains the same:


The web service (impots_web_02)


#!D:\Programs\ActivePython\Python2.7.2\python.exe

# -*- coding=Utf-8 -*-
# Import the Impots* class module
from impots import *
import cgi, cgitb, re

# Enable display of debug information
cgitb.enable()

# ------------------------------------------------ 
# the tax web service
# ------------------------------------------------ 

# The data required to calculate the tax has been placed in the MySQL table TABLE
# belonging to the BASE database. The table has the following structure
# limits decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
# The parameters for taxable individuals (marital status, number of children, annual salary)
# are sent by the client in the form params=marital status, number of children, annual salary
# the results (marital status, number of children, annual salary, tax due) are returned to the client
# in the form <tax>value</tax>
# or in the form <error>msg</error>, if the parameters are invalid

# the server returns unformatted text to the client
print "Content-Type: text/plain\n"
# start of response
print "<response>"

# definition of constants
USER="root"
PWD=""
HOST="localhost"
DATABASE="dbimpots"
TABLE="taxes"

# instantiate [business] layer
try:
    business=TaxBusiness(TaxMySQL(HOST,USER,PWD,DATABASE,TABLE))
except (IOError, TaxError) as info:
    print("<error>An error occurred: {0}</error>".format(infos))
    sys.exit()

# retrieve the line sent by the client to the server
params = cgi.FieldStorage().getlist('params')
# if no parameters, then error
if not params:
    print "<error>The parameter [params] is missing<error></response>"
    sys.exit()

# process the params parameter  
#print "Parameters received --> %s\n" %(params)
items = params[0].strip().lower().split(',')
# there must be exactly 3 fields
if len(items) != 3:
    print "<error>[%s]: invalid number of parameters</error></response>" % (params[0])
    sys.exit()
# the first parameter (marital status) must be yes/no
marie = items[0].strip()
if married != "yes" and married != "no":
    print "<error>[%s]: Invalid first parameter</error></response>\n" % (params[0])
    sys.exit()
# The second parameter (number of children) must be an integer
match = re.match(r"^\s*(\d+)\s*$", items[1])
if not match:
    print "<error>[%s]: Invalid second parameter</error></response>\n" % (params[0])
    sys.exit()
children = int(match.groups()[0])
# the third parameter (salary) must be an integer
match = re.match(r"^\s*(\d+)\s*$", items[2])
if not match:
    print "<error>[%s]: Invalid second parameter</error></response>\n" % (params[0])
    sys.exit()
salary = int(match.groups()[0])
# calculate the tax
tax = job.calculate(spouse, children, salary)
# return the result
print "<tax>%s</tax></response>\n" % (tax)
# end

Notes:

This web service differs from the previous one only in the nature of its response:

<response><error>msg</error></response> in case of an error instead of <error>msg</error>

<response><tax>value</tax></response> if the tax could be calculated instead of <tax>value</tax>

15.2. The client application

Our client must parse the XML response sent by the web service. We apply what we learned in the analysis of an XML document.


The program (client_impots_web_02)


# -*- coding=utf-8 -*-

import httplib, urllib, re
import xml.sax, xml.sax.handler
# XML handling class
class XmlHandler(xml.sax.handler.ContentHandler):

    # function called when an opening tag is encountered
    def startElement(self, name, attributes):
        # note the current element
        global currentElement
        currentElement = name.strip().lower()

    # Function called when an end tag is encountered
    def endElement(self, name):
        # do nothing
        pass

    # the data handling function
    def characters(self, data):
        # data
        global current_element, elements

        # the data is retrieved
        match = re.match(r"^\s*(.+?)\s*$", data)
        if match:
            elements[currentElement] = match.groups()[0].lower()
    
def getXmlResults(response):
    # parse the XML response
    xml.sax.parseString(response, XmlHandler())
    # return the results
    if elements.has_key('error'):
        return (elements['error'], "")
    else:
        return ("", elements['tax'])

# ------------------------------------------------------------ main
# constants
HOST="localhost"
URL="/cgi-bin/impots_web_02b.py"
data=("yes,2,200000","no,2,200000","yes,3,200000","no,3,200000","x,y,z,t","x,2,200000", "yes,x,200000","yes,2,x");
# global variables
current_element=""
elements={}

# connection
connection = httplib.HTTPConnection(HOST)
# logging
#connection.set_debuglevel(1)
# loop through the data to send to the server
for params in data:
    # parameters must be encoded before being sent to the server
    params = urllib.urlencode({'params': params})
    # send the request
    connection.request("POST", URL, parameters)
    # process the response (XML stream)
    response = connection.getresponse().read()
    # parse the XML file
    (error, result) = getXmlResults(response)
    if not error:
        print "impot[%s]=%s" % (params, impot)
    else:
        print "error[%s]=%s" % (params, error)

Notes:

  • The XML feed from the web service is processed by the getResultatsXml function (line 60);
  • line 29: the getResultatsXml function;
  • line 31: the web service's XML response is parsed by an instance of the XmlHandler class defined on line 6;
  • line 6: the XmlHandler class implements the three methods startElement, endElement, and characters. Using these three methods, a dictionary is created. The keys are the names of the <error> and <import> tags, and the values are the data associated with these two tags;
  • lines 33–36: the getResultatsXml function returns a tuple with two elements:
    • (error, "") if the XML stream analysis detected the <error> tag. error then represents the content of this tag;
    • ("", impot) if the XML stream analysis revealed the presence of the <impot> tag. impot then represents the content of this tag.
  • Line 60: The result of the getResultatsXml function is retrieved and then processed in lines 61–64.

15.3. The results

1
2
3
4
5
6
7
8
impot[yes,2,200000]=22504.0
impot[no,2,200000]=33388.0
impot[yes,3,200000]=16400.0
tax[no,3,200000]=22504.0
error[x,y,z,t]=[x,y,z,t]: invalid number of parameters
error[x,2,200000]=[x,2,200000]: invalid first parameter
error[yes,x,200000]=[yes,x,200000]: 2nd parameter invalid
error[yes,2,x]=[yes,2,x]: 2nd parameter invalid