Skip to content

4. Examples

4.1. Taxes Application: Introduction

Here we introduce the TAX application, which will be used many times later on. This application calculates a taxpayer’s tax liability. We consider the simplified case of a taxpayer who has only a single salary to report:

  • we calculate the number of tax brackets for the employee as nbParts = nbEnfants / 2 + 1 if they are unmarried, and nbEnfants / 2 + 2 if they are married, where nbEnfants is the number of children.
  • if they have at least three children, they receive an additional half-share
  • We calculate their taxable income R = 0.72 * S, where S is their annual salary
  • We calculate their family coefficient QF = R / nbParts
  • we calculate his tax I. Consider the following table:
12620.0
0
0
13,190
0.05
631
15,640
0.1
1,290.5
24,740
0.15
2,072.5
31,810
0.2
3,309.5
39,970
0.25
4900
48,360
0.3
6,898.5
55,790
0.35
9,316.5
92,970
0.4
12,106
127,860
0.45
16,754.5
151,250
0.50
23,147.5
172,040
0.55
30,710
195,000
0.60
39,312
0
0.65
49,062

Each row has 3 fields. To calculate tax I, find the first row where QF <= field1. For example, if QF = 23,000, the row found will be

    24740        0.15        2072.5

Tax I is then equal to 0.15*R - 2072.5*nbParts. If QF is such that the condition QF<=field1 is never met, then the coefficients from the last row are used. Here:

    0                0.65        49062

which gives tax I = 0.65*R - 49062*nbParts.

The data defining the different tax brackets is stored in an ODBC-MySQL database. MySQL is a public-domain DBMS that can be used on various platforms, including Windows and Linux. Using this DBMS, a database named dbimpots was created, containing a single table called impots. Access to the database is controlled by a username/password, in this case admimpots/mdpimpots. The following screenshot shows how to use the dbimpots database with MySQL:


C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> use dbimpots;
Database changed

mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)

mysql> describe taxes;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limits | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)

mysql> select * from impots;
+---------+--------+---------+
| limits | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24,740 |   0.15 |  2,072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55,790 |   0.35 |  9,316.5 |
|   92,970 |    0.4 |   12,106 |
|  127,860 |   0.45 |   16,754 |
|  151,250 |    0.5 | 23,147.5 |
|  172,040 |   0.55 |   30,710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)

mysql>quit

The dbimpots database is converted into an ODBC data source as follows:

  • Launch the 32-bit ODBC Data Source Administrator

Image

  • Use the [Add] button to add a new ODBC data source

Image

  • Select the MySQL driver and click [Finish]
  • The MySQL driver requests some information:
1
The DSN name to give the ODBC data source—it can be anything
2
the machine on which the MySQL DBMS is running—here, localhost. It is worth noting that the database could be a remote database. Local applications using the ODBC data source would not notice this. This would be the case, in particular, for our PHP application.
3
the MySQL database to use. MySQL is a DBMS that manages relational databases, which are sets of tables linked together by relationships. Here, we specify the name of the database being managed.
4
The name of a user with access rights to this database
5
their password

4.2. Tax Application: the ImpotsDSN class

Our application will rely on the PHP class ImpotsDSN, which will have the following attributes, constructor, and method:

<?php
...
    // attributes
    var $limits;        // array of limits
    var $coeffR;        // array of coeffR
    var $coeffN;        // array of coeffN
    var $errors;        // array of errors
    // constructor
    function ImpotsDSN($impots){...}
    // method
    function calculate($person){...}
  • We saw in the problem statement that we needed three sets of data
12620.0
0
0
13190
0.05
631
15,640
0.1
1,290.5
24,740
0.15
2,072.5
31,810
0.2
3,309.5
39,970
0.25
4900
48,360
0.3
6,898.5
55,790
0.35
9,316.5
92,970
0.4
12,106
127,860
0.45
16,754.5
151,250
0.50
23,147.5
172,040
0.55
30,710
195,000
0.60
39,312
0
0.65
49062

The first column will be placed in the $limites attribute of the ImotsDSN class, the second in the $coeffR attribute, and the third in $coeffN. These three attributes are initialized by the class constructor. The constructor retrieves the data from an ODBC data source. Various errors may occur during object construction. These errors are reported in the $erreurs attribute as an array of error messages.

  • The constructor receives a $impots dictionary as a parameter with the following fields:
// dsn: DSN name of the ODBC data source containing the values of the limit, coeffR, and coeffN arrays
// user: name of a user with read access to the ODBC source
// pwd: their password
// table: name of the table containing the values (limits, coeffR, coeffN)
// limits: name of the column containing the limit values
// coeffR: name of the column containing the coeffR values       
// coeffN: name of the column containing the coeffN values

The constructor parameter provides all the information needed to read data from the ODBC data source

  • Once the ImpotsDSN object is created, users of the class can call the object’s calculateTaxes method. This method takes a $person dictionary as a parameter:
      // $person["married"]: yes, no
      // $person["children"]: number of children
      // $person["salary"]: annual salary

The complete class is as follows:

<?php
  // definition of a class objImpots
  class TaxDSN{

    // attributes: the 3 data arrays
    var $limits;        // limits array
    var $coeffR;        // array of coeffR
    var $coeffN;        // array of coeffN
    var $errors;        // array of errors

    // constructor
    function ImpotsDSN($impots){
      // $taxes: dictionary containing the following fields
      // dsn: DSN name of the ODBC data source containing the values of the limit arrays, coeffR, and coeffN
      // user: name of a user with read access to the ODBC source
      // pwd: their password
      // table: name of the table containing the values (limits, coeffR, coeffN)
      // limits: name of the column containing the limit values
      // coeffR: name of the column containing the coeffR values       
      // coeffN: name of the column containing the coeffN values

      // initially no errors
      $this->errors = array();

            // Checking the call
      if (!isset($impots[dsn]) || !isset($impots[user]) || !isset($impots[pwd]) || !isset($impots[table]) ||
          ! isset($impots[limits]) || ! isset($impots[coeffR]) || ! isset($impots[coeffN])){
        // error
        $this->errors[] = "Invalid call";
        // end
        return;
      }//if

      // Open the DSN database
      $connection = odbc_connect($taxes[dsn], $taxes[user], $taxes[pwd]);
      // error?
      if(! $connection){
        // error
        $this->errors[] = "Unable to open the DSN database [$impots[dsn]] (".odbc_error().")";
        // end
        return;
      }//if

            // execute a query on the database
      $query = odbc_prepare($connection, "select $taxes[limits], $taxes[coeffR], $taxes[coeffN] from $taxes[table]");
            if(! odbc_execute($query)){
        // error
        $this->errors[] = "Unable to access the DSN database [$impots[dsn]] (".odbc_error().")";
        // end
        odbc_close($connection);
        return;
      }//if

      // process query results
      $this->limits = array();
      $this->coeffR = array();
      $this->coeffN = array();
      while(odbc_fetch_row($query)){     
          // one more row
        $this->limits[] = odbc_result($query, $taxes[limits]);
        $this->coeffR[] = odbc_result($query, $impots[coeffR]);
        $this->coeffN[] = odbc_result($query, $impots[coeffN]);
      }//while

      // Close database
      odbc_close($connection);
    }//constructor                

    // --------------------------------------------------------------------------
    function calculate($person){
      // $person["married"]: yes, no
      // $person["children"]: number of children
      // $person["salary"]: annual salary

      // Is the object in a valid state?
      if (! is_array($this->errors) || count($this->errors) != 0) return -1;

            // Check the call
      if (! isset($person[married]) || ! isset($person[children]) || ! isset($person[salary])){
        // error
        $this->errors[] = "Invalid call";
        // end
        return -1;
      }//if

      // Are the parameters correct?
      $person[married] = strtolower($person[married]);
      if($person[married] != 'yes' && $person[married] != 'no') {
          // error
        $this->errors[] = "Marital status [$person[married]] is incorrect";
      }//if
      if(! preg_match("/^\s*\d{1,3}\s*$/",$person[children])){
          // error
        $this->errors[] = "Incorrect number of children [$person[children]]";
      }//if
      if(! preg_match("/^\s*\d+\s*$/",$person[salary])){
          // error
        $this->errors[] = "Invalid salary [$person[salary]]";
      }//if
      // any errors?
      if(count($this->errors)!=0) return -1;

      // number of shares
      if($person[married]==true) $numberOfShares=$person[children]/2+2;
        else $nbParts=$person[children]/2+1;
      // add 1/2 share if there are at least 3 children
      if($person[children] >= 3) $numberOfShares += 0.5;
      // taxable income
      $taxableIncome = 0.72 * $person[salary];
      // family quotient
      $quotient = $taxableIncome / $numberOfShares;
      // is placed at the end of the limits array to stop the following loop
      $this->limits[$this->limitCount] = $quota;
      // tax calculation
      $i=0;
      while($quotient > $this->limits[$i]) $i++;
      // Since we placed $quotient at the end of the $limits array, the previous loop
      // cannot go beyond the $limits array
      // now we can calculate the tax
      return floor($taxableIncome * $this->coeffR[$i] - $nbParts * $this->coeffN[$i]);
    }//taxCalculation
  }//taxClass
?>

A test program could look like this:

<?php
    // libraries
  include "ImpotsDSN.php";

    // Create a tax object
  $conf = array(dsn => "mysql-dbimpots", user => "admimpots", pwd => "mdpimpots",
      table=>impots,limits=>limits,coeffR=>coeffR,coeffN=>coeffN);

  $objImpots = new ImpotsDSN($conf);

  // Any errors?
  if(count($objImpots->errors)!=0){
      // msg
    echo "The following errors occurred:\n";
    $errors = $objTaxes->errors;
    for($i=0;$i<count($errors);$i++){
        echo "$errors[$i]\n";
    }//for
    // end
    exit(1);
  }//if

  // test
  calculateTaxes($taxObject, yes, 2, 200000);
  calculateTaxes($taxObj, false, 2, 200000);
  calculateTaxes($taxObj, yes, 3, 200000);
  calculateTaxes($taxObj, false, 3, 200000);
  calculateTaxes($taxObj,array(),array(),array());

  // end
  exit(0);

  // -----------------------------------------------------------
  function calculateTaxes($taxObject, $married, $children, $salary){
      // echo
        echo "taxes($spouse,$children,$salary)\n";    

      // calculate tax
    $person = array(spouse => $spouse, children => $children, salary => $salary);        
      $amount = $taxObject->calculate($person);
    // any errors?
    if(count($objTaxes->errors)!=0){
        // message
      echo "The following errors occurred:\n";
      $errors = $objTaxes->errors;
      for($i=0;$i<count($errors);$i++){
          echo "$errors[$i]\n";
      }//for
    }else echo "amount=$amount\n";
  }//calculateImp
  • The test program makes sure to "include" the file containing the ImpotsDSN class
  • then creates an objImpots object of the ImpotsDSN class by passing the necessary information to the object's constructor
  • Once this object is created, its calculate method will be called five times

The results obtained are as follows:


dos>e:\php43\php.exe test.php
taxes(yes,2,200000)
amount=22504
taxes(no,2,200000)
amount=33388
taxes(yes,3,200000)
amount=16,400
taxes(no,3,200000)
amount=22504
taxes(Array,Array,Array)
The following errors occurred:
Incorrect marital status [array]
Number of children [Array] incorrect
Income [Array] incorrect

We will now use the ImpotsDSN class without redefining it.

4.3. Tax Application: Version 1

We are now presenting version 1 of the IMPOTS application. This is a web application that presents an HTML interface to the user to obtain the three parameters needed to calculate the tax:

  • marital status (married or single)
  • number of children
  • annual income

Image

The form is displayed using the following PHP page:

<?php //tax form ?>

<html>
    <head>
      <title>taxes</title>
    <script language="JavaScript" type="text/javascript">
        function clear(){
          // Clear the form
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtChildren.value = "";
          txtSalary.value="";
          txtTaxes.value="";
        }//with
      }//clear      
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
      <center>
        Tax Calculation
        <hr>
      <form name="frmImpots" method="POST">
          <table>
            <tr>
              <td>Are you married?</td>
            <td>
                <input type="radio" name="optMarie" value="yes" <?php echo $request->chkoui ?>>yes
              <input type="radio" name="optMarie" value="no" <?php echo $request->chkno ?>>no
            </td>
          </tr>
          <tr>
              <td>Number of children</td>
            <td><input type="text" size="5" name="txtEnfants" value="<?php echo $request->children ?>"></td>
          </tr>
          <tr>
              <td>Annual salary</td>
            <td><input type="text" size="10" name="txtSalary" value="<?php echo $query->salary ?>"></td>
          </tr>
          <tr>
              <td><font color="green">Tax</font></td>
            <td><input type="text" size="10" name="txtTaxes" value="<?php echo $request->taxes ?>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="submit" value="Calculate"></td>
            <td><input type="button" value="Clear" onclick="clear()"></td>
          </tr>
        </table>
      </form>
    </center>
    <?php
        // Are there any errors?
      if(count($request->errors)!=0){
          // Display errors
        echo "<hr>\n<font color=\"red\">\n";
        echo "The following errors occurred<br>";
        echo "<ul>";
        for($i=0;$i<count($query->errors);$i++){
            echo "<li>".$request->errors[$i]."</li>\n";
        }
        echo "</ul>\n</font>\n";
      }//if 
   ?>
 </body>
</html>

The PHP page simply displays information passed to it by the application's main program in the $request variable, which is an object with the following fields:

chkoui, chknon
attributes of the "yes" and "no" radio buttons—their possible values are "checked" or "" to enable or disable the corresponding radio button
children
the taxpayer's number of children
salary
their annual salary
taxes
the amount of tax due
errors
a list of any errors—may be empty.

The page sent to the client contains a JavaScript script with a clear function associated with the "Clear" button, whose purpose is to reset the form to its initial state: unchecked button, empty input fields. Note that this result could not be achieved with an HTML "reset" button. Indeed, when this type of button is used, the browser resets the form to the state in which it received it. However, in our application, the browser receives forms that may not be empty.

The application that processes the previous form is as follows:

<?php
    // processes the tax form

    // libraries
  include "ImpotsDSN.php";

  // application configuration
  ini_set("register_globals", "off");
  ini_set("display_errors", "off");
  $taxForm="impots_form.php";
  $taxErrors = "tax_errors.php";
  $taxDB = array(dsn => "mysql-dbimpots", user => "admimpots", pwd => "mdpimpots",
      table=>taxes,limits=>limits,coeffR=>coeffR,coeffN=>coeffN);

  // retrieve the parameters
  $query->married=$_POST["optMarried"];
  $query->children = $_POST["txtChildren"];
  $query->salary=$_POST["txtSalary"];

  // Do we have all the parameters?
  if(!isset($request->spouse) || !isset($request->children) || !isset($request->salary)){
      // prepare empty form
    $request->chkoui = "";
    $request->chkno = "checked";
    $request->children="";
    $request->salary = "";
    $request->taxes="";
    $query->errors = array();
    // display form
    include $taxForm;
    // end
    exit(0);
  }//if

  // parameter validation
  $request = check($request);

  // any errors?
  if(count($request->errors) != 0) {
      // display form
    include "$taxForm";
    // end
    exit(0);
  }//if

  // calculate the response
  $request = calculateTaxes($dbTaxes, $request);

  // any errors?
  if(count($request->errors) != 0) {
      // display error form
    include "$taxErrors";
    // end
    exit(0);
  }//if

  // display form
  include "$taxForm";
  // end
  exit(0);

  // --------------------------------------------------------
  function verify($request){
      // checks the validity of the request parameters

    // initially, no errors
    $request->errors = array();

    // Is the status valid?
    $request->married = strtolower($request->married);
    if($request->married != "yes" && $request->married != "no"){
        // an error
      $request->errors[] = "Invalid marital status [$request->married]";
    }

    // Is the number of children valid?
    if(! preg_match("/^\s*\d+\s*$/",$query->children)){
        // an error
      $request->errors[] = "Incorrect number of children [$request->children]";
    }

    // Is the salary valid?
    if(! preg_match("/^\s*\d+\s*$/", $request->salary)){
        // an error
      $request->errors[] = "Invalid salary [$request->salary]";
    }

    // radio button status
    if($request->married=="yes"){
        $request->chkoui = "checked";
      $request->chkno = "";
    }else{
        $request->chkno = "checked";
      $query->chkoui = "";
    }

    // return the query
    return $query;
  }//check

  // --------------------------------------------------------
  function calculateTaxes($taxDB, $query){
      // calculate the tax amount

    // $dbTaxes: dictionary containing the information needed to read the ODBC data source
    // $query: the query containing information about the taxpayer

    // we construct an ImpotsDSN object 
    $objTaxes = new TaxesDSN($dbTaxes);

    // Any errors?
    if(count($objTaxes->errors) != 0){
        // add the errors to the query
      $query->errors = $objTaxes->errors;
      // done
      return $query;
    }//if

    // calculate the tax
    $person = array(married => "$request->married", children => "$request->children", salary => "$request->salary");
        $request->taxes = $taxObject->calculate($person);

    // return the result
    return $request;
  }//calculateTaxes

Comments:

  • First, the ImpotsDSN class is included. This class forms the basis of the tax calculation and hides database access from us
  • A number of initializations are performed to facilitate application maintenance. If parameters change, their values will be updated in this section. Generally, these configuration values are stored in a file or a database.
  • We retrieve the three form parameters corresponding to the HTML fields optMarie, txtEnfants, and txtSalaire.
  • These parameters may be completely or partially missing. This will be the case, in particular, during the initial request for the form. In that case, we simply send an empty form.
  • Next, the validity of the three retrieved parameters is checked. If they are found to be incorrect, the form is returned as entered but with an additional list of errors.
  • Once the validity of the three parameters has been verified, the tax can be calculated. This is done by the `calculerImpots` function. This function creates an `ImpotsDSN` object and uses that object’s `calculer` method.
  • Creating the ImpotsDSN object may fail if the database is unavailable. In this case, the application displays a specific error page listing the errors that occurred.
  • If everything went well, the form is returned as entered but with the amount of tax due added.

The error page is as follows:

<?php    // error page ?>
<html>
    <head>
      <title>Tax application unavailable</title>
  </head>
  <body background="/poly/impots/images/standard.jpg">
      <h3>Tax Calculator</h3>
    <hr>
    Application unavailable. Please try again later.<br><br>
    <font color="red">
           The following errors occurred<br>
        <ul>
      <?php
          // display errors
        for($i=0;$i<count($query->errors);$i++){
            echo "<li>".$query->errors[$i]."</li>\n";
        }
      ?>
        </ul>
    </font>
  </body>
</html>

Here are a few examples of errors. First, an incorrect password is entered:

Image

Incorrect data is entered into the form:

Image

Finally, correct values are entered:

Image

4.4. Tax Application: Version 2

In the previous example, the server validates the form parameters txtEnfants* and txtSalaire. Here, we propose validating them using a JavaScript script included in the form page. The browser then performs the validation. The server is only contacted if the parameters are valid. This saves on "bandwidth." The display page impots-form.php* becomes the following:

<?php //tax form ?>

<html>
    <head>
      <title>taxes</title>
    <script language="JavaScript" type="text/javascript">
        function clear(){
........
      }//clear

      //------------------------------
      function calculate(){
          // Check parameters before sending them to the server
        with(document.frmImpots){
          //number of children
          champs = /^\s*(\d+)\s*$/ .exec(txtEnfants.value);
          if(champs==null){
            // the form is not validated
            alert("The number of children was not provided or is incorrect");
            nbEnfants.focus();
            return;
          }//if
          //salary
          fields = /^\s*(\d+)\s*$/ .exec(txtSalary.value);
          if(fields==null){
            // the pattern does not match
            alert("Salary was not provided or is incorrect");
            salary.focus();
            return;
          }//if
          // OK—send the form to the server
          submit();
        }//with
      }//calculate            
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
............
              <td><input type="button" value="Calculate" onclick="calculate()"></td>
            <td><input type="button" value="Clear" onclick="clear()"></td>
........
 </body>
</html>

Note the following changes:

  • The Calculate button is no longer a submit button but a button associated with a function called calculate. This function will validate the txtEnfants and txtSalaire fields. If they are valid, the form values will be sent to the server (submit); otherwise, an error message will be displayed.

Here is an example of what is displayed in case of an error:

Image

4.5. Tax Application: Version 3

We are making a slight modification to the application to introduce the concept of a session. We now consider the application to be a tax calculation simulation tool. A user can then simulate different taxpayer "scenarios" and see what the tax liability would be for each one. The web page below provides an example of what could be achieved:

Image

The code for the page above is as follows:

<?php //tax form ?>

<html>
    <head>
      <title>taxes</title>
    <script language="JavaScript" type="text/javascript">
        function clear(){
...
      }//clear

      //------------------------------
      function calculate(){
...
      }//calculate            
        </script>
  </head>

  <body background="/poly/impots/images/standard.jpg">
      <center>
        Tax Calculation
        <hr>
      <form name="frmImpots" method="POST">
          <table>
            <tr>
              <td>Are you married?</td>
            <td>
                <input type="radio" name="optMarie" value="yes" <?php echo $request[chkoui] ?>>yes
              <input type="radio" name="optMarie" value="no" <?php echo $request[chkno] ?>>no
            </td>
          </tr>
          <tr>
              <td>Number of children</td>
            <td><input type="text" size="5" name="txtEnfants" value="<?php echo $request[children] ?>"></td>
          </tr>
          <tr>
              <td>Annual salary</td>
            <td><input type="text" size="10" name="txtSalary" value="<?php echo $query[salary] ?>"></td>
          </tr>
          <tr>
              <td><font color="green">Tax</font></td>
            <td><input type="text" size="10" name="txtTax" value="<?php echo $request[tax] ?>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="button" value="Calculate" onclick="calculate()"></td>
            <td><input type="button" value="Clear" onclick="clear()"></td>
          </tr>
        </table>
      </form>
    </center>
    <?php
        // Are there any errors?
      if(count($request[errors])!=0){
          // display errors
        echo "<hr>\n<font color=\"red\">\n";
        echo "The following errors occurred<br>";
        echo "<ul>";
        for($i=0;$i<count($query[errors]);$i++){
            echo "<li>".$request[errors][$i]."</li>\n";
        }
        echo "</ul>\n</font>\n";
      }//if 
        // Are there any simulations?
      else if(count($request[simulations])!=0){
          // display simulations
        echo "<hr>\n<h2>Simulation results</h2>\n";
        echo "<table border=\"1\">\n";
        echo "<tr><td>Married</td><td>Children</td><td>Annual income (F)</td><td>Taxes payable (F)</td></tr>\n";
        for($i=0;$i<count($query[simulations]);$i++){
            echo "<tr>".
              "<td>".$query[simulations][$i][0]."</td>".
              "<td>".$query[simulations][$i][1]."</td>".
              "<td>".$query[simulations][$i][2]."</td>".
              "<td>".$query[simulations][$i][3]."</td>".
            "</tr>\n";                                    
        }//for
        echo "</table>\n";
      }//if 
   ?>
 </body>
</html>

The display program has minor differences from its previous version:

  • the dictionary receives a $query dictionary instead of a $query object. We therefore write $query[children] and not $query->children.
  • This dictionary has a `simulations` field, which is a two-dimensional array. `$query[simulations][i]` is simulation number i. This is itself an array of four strings: marital status, number of children, annual salary, and tax payable.

The main program has undergone more significant changes:

<?php
    // processes the tax form

    // libraries
  include "ImpotsDSN.php";

  // start session
  session_start();

  // application configuration
  ini_set("register_globals", "off");
  ini_set("display_errors", "off");
  $taxForm = "impots_form.php";
  $taxErrors = "tax_errors.php";
  $taxDB = array(dsn => "mysql-dbimpots", user => "admimpots", pwd => "mdpimpots",
      table=>taxes,limits=>limits,coeffR=>coeffR,coeffN=>coeffN);

    // retrieve session parameters
  $session = $_SESSION["session"];
  // Is the session valid?
  if(!isset($session) || !isset($session[objImpots]) || !isset($session[simulations])){
      // start a new session
    $session = array(objImpots => new ImpotsDSN($bdImpots), simulations => array());
    // any errors?
    if(count($session[objImpots]->errors)!=0){
      $query = array(errors => $session[objImpots]->errors);
        // display error page
        include $taxErrors;
      // end
      $session = array();
      endSession($session);
    }//if
  }//if

  // retrieve the parameters of the current transaction
  $request[married] = $_POST["optMarried"];
  $request[children] = $_POST["txtChildren"];
  $request[salary] = $_POST["txtSalary"];

  // Do we have all the parameters?
  if(!isset($query[married]) || !isset($query[children]) || !isset($query[salary])){
      // display empty form
    $request = array(chkoui => "", chknon => "checked", children => "", salary => "", taxes => "",
            errors=>array(),simulations=>array());    
    include $taxForm;
    // end
    endSession($session);
  }//if

  // parameter validation
  $request = validate($request);

  // any errors?
  if(count($request[errors])!=0){
      // display form
    include "$taxForm";
    // end
    endSession($session);
  }//if

  // calculate tax due
  $query[taxes] = $session[taxObject]->calculate(array(married => $query[married],
        children=>$request[children],salary=>$request[salary]));

  // another simulation
  $session[simulations][] = array($request[married], $request[children], $request[salary], $request[taxes]);
  $request[simulations] = $session[simulations];

  // display form
  include "$taxForm";

  // end
  endSession($session);

  // --------------------------------------------------------
  function verify($request){
      // checks the validity of the request parameters

    // initially, no errors
    $request[errors] = array();

    // Is the status valid?
    $request[married] = strtolower($request[married]);
    if($request[married] != "yes" && $request[married] != "no"){
        // an error
      $request[errors][] = "Invalid marital status [$request[married]]";
    }

    // Is the number of children valid?
    if(! preg_match("/^\s*\d+\s*$/",$request[children])){
        // an error
      $request[errors][] = "Invalid number of children [$request[children]]";
    }

    // Is the salary valid?
    if(! preg_match("/^\s*\d+\s*$/", $query[salary])){
        // an error
      $request[errors][] = "Invalid salary [$request[salary]]";
    }

    // radio button status
    if($request[married]=="yes"){
        $request[chkoui] = "checked";
      $request[chkno] = "";
    }else{
        $request[chkno] = "checked";
      $query[chkoui] = "";
    }

    // return the request
    return $request;
  }//check

  // --------------------------------------------------------
  function endSession($session){
      // store the session
    $_SESSION[session] = $session;
    // end script
    exit(0);
  }//terminateSession
  • First of all, this application manages a session. So we start a session at the beginning of the script:
  session_start();
  • The session stores a single variable: the $session dictionary. This dictionary has two fields:
    • objImpots: an ImpotsDSN object. We store this object in the client’s session to avoid unnecessary repeated accesses to the ODBC database.
    • simulations: the array of simulations to recall simulations from previous exchanges between exchanges.
  • Once the session has started, the $session variable is retrieved. If it does not yet exist, the session is starting. An ImpotsDSN object and an empty simulations array are then created. If necessary, errors are reported.
<?php
...
    // retrieve the session parameters
  $session=$_SESSION["session"];
  // Is the session valid?
  if(!isset($session) || !isset($session[objImpots]) || !isset($session[simulations])){
      // start a new session
    $session = array(objImpots => new ImpotsDSN($bdImpots), simulations => array());
    // any errors?
    if(count($session[objImpots]->errors)!=0){
      $query = array(errors => $session[objImpots]->errors);
        // display error page
        include $taxErrors;
      // end
      $session = array();
      endSession($session);
    }//if
  }//if
  • Once the session is properly initialized, the exchange parameters are retrieved. If not all expected parameters are present, an empty form is sent.
<?php
...
  // retrieve the parameters for the current exchange
  $request[married] = $_POST["optMarie"];
  $request[children] = $_POST["txtChildren"];
  $request[salary] = $_POST["txtSalary"];

  // Do we have all the parameters?
  if(!isset($request[married]) || !isset($request[children]) || !isset($request[salary])){
      // display empty form
    $request = array(chkoui => "", chknon => "checked", children => "", salary => "", taxes => "",
            errors=>array(),simulations=>array());    
    include $taxForm;
    // end
    endSession($session);
  }//if
  • If all parameters are present, they are checked. If there are errors, they are reported:
<?php
...
  // parameter validation
  $request = check($request);

  // Any errors?
  if(count($request[errors])!=0){
      // display form
    include "$taxForm";
    // end
    endSession($session);
  }//if
  • if the parameters are correct, the tax is calculated:
<?php
...
  // calculate the tax due
  $request[taxes] = $session[taxObject]->calculate(array(married => $request[married],
        children=>$request[children],salary=>$request[salary]));
  • The current simulation is added to the simulation array, which is then sent to the client along with the form:
<?php
...
  // one more simulation
  $session[simulations][]=array($query[married],$query[children],$query[salary],$query[taxes]);
  $request[simulations] = $session[simulations];

  // display form
  include "$taxForm";

  // end
  endSession($session);
  • In any case, the script ends by saving the $session variable to the session. This is done in the terminateSession procedure.
<?php
...

  function endSession($session){
      // we store the session
    $_SESSION[session] = $session;
    // end script
    exit(0);
  }//terminerSession

Let’s finish by presenting the program that displays database access errors (impots-erreurs.php):

<?php    // error page ?>
<html>
    <head>
      <title>Tax application unavailable</title>
  </head>
  <body background="/poly/impots/images/standard.jpg">
      <h3>Tax Calculator</h3>
    <hr>
    Application unavailable. Please try again later.<br><br>
    <font color="red">
           The following errors occurred<br>
        <ul>
      <?php
          // display errors
        for($i=0;$i<count($query[errors]);$i++){
            echo "<li>".$query[errors][$i]."</li>\n";
        }
      ?>
        </ul>
    </font>
  </body>
</html>

Here too, the $request object has been replaced by a $request dictionary.

4.6. Taxes Application: Version 4

We will now create a standalone application that will act as a web client for the previous Taxes web application. The application will be a console application launched from a DOS window:


dos>e:\php43\php.exe cltImpots.php
Syntax cltImpots.php urlImpots married children salary [token]

The parameters for the cltImpots web client are as follows:

    // syntax $0 urlImpots married children salary token
  // client of a tax service running on urlImpots
  // sends the following three pieces of information to this service: married, children, salary
  // optionally with the session token if it has been passed
  // displays the table of simulations for the session

Here are a few usage examples. First, an example without a session token.


dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php yes 2 200000

Session token=[a6297317667bc981c462120987b8dd18]

Simulations:
[Married,Children,Annual salary (F),Taxes due (F)]
[yes,2,200000,22504]

We successfully retrieved the amount of tax due (22,504 F). We also retrieved the session token. We can now use it for a second query:


dos>e:\php43\php.exe cltImpots.php http://localhost/poly/impots/6/impots.php yes 3 200000 a6297317667bc981c462120987b8dd18

Session token=[a6297317667bc981c462120987b8dd18]

Simulations:
[Married,Children,Annual salary (F),Taxes due (F)]
[yes,2,200000,22504]
[yes,3,200000,16400]

We successfully receive the table of simulations sent by the web server. The retrieved token remains the same, of course. If we query the web service while the database has not been started, we get the following result:


dos>e:\php43\php.exe cltImpots2.php http://localhost/poly/impots/6/impots.php yes 3 200000

Session token=[8369014d5053212bc42f64bbdfb152ee]

The following errors occurred:
Unable to open DSN database [mysql-dbimpots] (S1000)

When writing a web client application, it is necessary to know exactly what the server sends in response to the various possible requests from a client. The server sends a set of HTML lines containing useful information and other elements that are only there for HTML formatting. PHP regular expressions can help us find the useful information within the stream of lines sent by the server. To do this, we need to know the exact format of the server’s various responses. To do so, we can query the web service using a browser and examine the source code that was sent. Note that this method does not allow us to see the HTTP headers of the web server’s response. It is sometimes useful to know these. We can then use one of the two generic web clients discussed in the previous chapter.

If we repeat the previous examples, here is the HTML code received by the browser for the simulation table:

<h2>Simulation Results</h2>
<table border="1">
<tr><td>Married</td><td>Children</td><td>Annual salary (F)</td><td>Taxes payable (F)</td></tr>
<tr><td>yes</td><td>2</td><td>200000</td><td>22504</td></tr>
<tr><td>yes</td><td>3</td><td>200000</td><td>16400</td></tr>
</table>

This is an HTML table, the only one in the document sent. Therefore, the regular expression "|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|" should allow us to find the various simulations received within the document. When the database is unavailable, the following message is displayed:

    Application unavailable. Please try again later.<br><br>
    <font color="red">
           The following errors occurred<br>
        <ul>
      <li>Unable to open the DSN database [mysql-dbimpots] (S1000)</li>
        </ul>
    </font>

To retrieve the error, the web client must search the web server's response for lines matching the pattern: "|<li>(.+?)</li>|".

We now have the guidelines for what to do when the user requests a tax calculation from the previous console client:

  • verify that all parameters are valid and report any errors.
  • connect to the URL provided as the first parameter. To do this, we will follow the pattern of the generic web client already presented and discussed
  • in the server response stream, use regular expressions to either:
    • find the error messages
    • find the simulation results

The client code cltImpots.php is as follows:

<?php
    // syntax $0 urlImpots married children salary token
  // client of a tax service running on urlImpots
  // sends the following three pieces of information to this service: married, children, salary
  // optionally with the session token if it has been passed
  // displays the table of simulations for the session

  // check number of arguments
  if(count($argv) != 5 && count($argv) != 6){
      // error
    fwrite(STDERR, "Syntax: $argv[0] urlTaxes married children salary [token]\n");
    // end
    exit(1);
  }//if

  // note the URL
  $urlImpots = parseURL($argv[1]);
  if(isset($taxURL[error])){
      // error
    fwrite(STDERR,"$urlImpots[error]\n");
    // end
    exit(1);
  }//if

  // Parse the user's request
  $request = requestAnalysis("$argv[2], $argv[3], $argv[4]");
  // Errors?
  if($request[error]){
      // message
    echo "$request[error]\n";
    // done
    exit(1);
  }//if

  // we can make the request - we use the session token
  $taxes = getTaxes($taxUrl, $request, $argv[5]);

  // display the session token
  echo "Session token=[$impots[token]]\n";

  // any errors?
  if(count($impots[errors])!=0){
      //error messages
    echo "The following errors occurred:\n";
    for($i=0;$i<count($impots[errors]);$i++){
        echo $taxes[errors][$i]."\n";
    }//for
  }else{
      // no errors - display simulations
    echo "Simulations:\n";
    for($i=0;$i<count($taxes[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//for
  }//if

  // end of program
  exit(0);

  // --------------------------------------------------------------
  function analyzeURL($URL){  
      // Check the validity of the URL $URL
    $url = parse_url($URL);
    // the protocol    
    if(strtolower($url[scheme])!="http"){
        $url[error] = "The URL [$URL] is not in the format http://machine[:port][/path]";
      return $url;
    }//if
    // the machine
    if(!isset($url[host])){
        $url[error] = "The URL [$URL] is not in the format http://machine[:port][/path]";
      return $url;
    }//if
    // the port
    if(!isset($url[port])) $url[port]=80;
    // the query
    if(isset($url["query"])){
        $url[error]="The URL [$URL] is not in the format http://machine[:port][/path]";
      return $url;
    }//if
    // return
    return $url;
  }//analyzeURL

  // -----------------------------------------------------------
  function analyzeRequest($request){
      // $request: string to parse
    // must be in the form of married, children, salary
    // valid format
    if(! preg_match("/^\s*(yes|no)\s*,\s*(\d{1,3})\s*,\s*(\d+)\s*$/i",$request,$fields)){
        // Invalid format
      return(array(error=>"Invalid format (married, children, salary)."));
    }    
    // OK
    $married = strtolower($fields[1]);
    return array(married=>$married, children=>$fields[2], salary=>$fields[3]);
  }//analyzeRequest

  // --------------------------------------------------------------
  function getTaxes($taxUrl, $request, $token) {
      // $taxURL: URL to query
    // $request: dictionary containing the married, children, and salary fields
    // $token: a possible session token

    // Open a connection on port $urlImpots[port] of $urlImpots[host]
    $connection = fsockopen($urlImpots[host], $urlImpots[port], &$errno, &$error);
    // return if error
    if(! $connection){
      return array(errors=>array("Failed to connect to the site ($urlImpots[host],$urlImpots[port]): $error"));
    }//if

        // send HTTP headers to the server
    POST($connection, $taxURL, $token,
            array(optSpouse=>$request[spouse],txtChildren=>$request[children],txtSalary=>$request[salary]));

        // read the server's response - first the HTTP headers
    // the first line
    $line = fgets($connection, 10000);

    // URL found?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$line)){
        // URL not found - return with error
      return array(errors=>array("The URL $urlImpots[path] could not be found"));
    }//if

    // read the other HTTP headers
    while(($line = fgets($connection, 10000)) && (($line = rtrim($line)) != "")){
      // search for the token if it hasn't been found yet      
      if(! $token){
          // search for the set-cookie line
        if(preg_match("/^Set-Cookie: PHPSESSID=(.*?);/i",$line,$fields)){
            // found the token - store it          
          $token = $fields[1];            
        }//if
      }//if
    }//next line

    // read the next document
    $document="";
    while($line = fread($connection, 10000)) {
        $document.=$line;
    }//while

    // close the connection
    fclose($connection);

    // analyze the document
    $taxes = getInfo($document);

    // return the result
    return array(token=>$token,errors=>$taxes[errors],simulations=>$taxes[simulations]);
    }//getTaxes

  // --------------------------------------------------------------
  function POST($connection, $url, $token, $parameters) {
      // $connection: the connection to the web server
    // $url: the URL to query
    // $parameters: dictionary of parameters to post

    // prepare the POST
    $post="";
    while(list($parameter, $value) = each($parameters)) {
        $post.=$parameter."=".urlencode($value)."&";
    }//while
    // Remove the last character
    $post = substr($post, 0, -1);

    // prepare the HTTP request in HTTP/1.0 format
    $HTTP="POST $url[path] HTTP/1.0\n";
    $HTTP.="Content-type: application/x-www-form-urlencoded\n";
    $HTTP.="Content-length: ".strlen($post)."\n";
    $HTTP.="Connection: close\n";
    if($token) $HTTP.="Cookie: PHPSESSID=$token\n";
    $HTTP.="\n";
    $HTTP.=$post;

    // send the HTTP request
    fwrite($connection, $HTTP);
    }//POST

  // --------------------------------------------------------------
  function getInfo($document){
      // $document: HTML document
    // we look for either the list of errors
    // or the simulation table

    // preparing the result
    $taxes[errors] = array();
    $taxes[simulations] = array();

    // Is there an error?
    // error line template
    $errorPattern = "|<li>(.+?)</li>|";
    // search the document
    if(preg_match_all($errorPattern, $document, $fields, PREG_SET_ORDER)){
        // retrieve errors
      for($i=0;$i<count($fields);$i++){
          $taxes[errors][] = $fields[$i][1];
      }//for
      // done
      return $taxes;
    }//if

    // example of a row in the simulation table
    // <tr><td>yes</td><td>2</td><td>200000</td><td>22504</td></tr>
    $modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";
    // search in the document
    if(preg_match_all($modSimulation,$document,$fields,PREG_SET_ORDER)){    
        // retrieve simulations
      for($i=0;$i<count($fields);$i++){
          $taxes[simulations][]=array($fields[$i][1],$fields[$i][2],$fields[$i][3],$fields[$i][4]);
      }//for
      // return result
      return $taxes;
    }//if

    // shouldn't normally get here
    return $taxes;
    }//getInfo
?>

Comments:

  • The program begins by checking the validity of the parameters it has received. To do this, it uses two functions: `analyseURL`, which checks the validity of the URL, and `analyseDemande`, which checks the other parameters.
  • The tax calculation is performed by the getImpots function:
  $taxes = getTaxes($taxURL, $request, $argv[5]);
  • The getImpots function is declared as follows:
<?php
...
  // --------------------------------------------------------------
  function getTaxes($taxUrl, $request, $token) {
      // $urlImpots: URL to query
    // $request: dictionary containing the fields spouse, children, salary
    // $token: a possible session token
  • (continued)
    • $urlImpots is a dictionary containing the following fields:
      • host: machine where the web service resides
      • port: its service port
      • path: the path to the requested resource
    • $request is a dictionary containing the following fields:
      • married: yes/no: marital status
      • children: number of children
      • salary: annual salary
    • $token is the session token.

The function returns a dictionary with the following fields:

  • errors: array of error messages
  • simulations: array of simulations, where each simulation is itself an array of four elements (spouse, children, salary, tax)
  • token: the session token
  • Once the result of getImpots is obtained, the program displays the results and terminates:
<?php
...

  // display the session token
  echo "Session token=[$impots[token]]\n";

  // Any errors?
  if(count($impots[errors])!=0){
      //error messages
    echo "The following errors occurred:\n";
    for($i=0;$i<count($impots[errors]);$i++){
        echo $taxes[errors][$i]."\n";
    }//for
  }else{
      // no errors - display simulations
    echo "Simulations:\n";
    for($i=0;$i<count($taxes[simulations]);$i++){
        echo "[".implode(",",$impots[simulations][$i])."]\n";
      }//for
  }//if

  // end of program
  exit(0);
  • Let's now analyze the function getImpots($urlImpots,$request,$token), which must
    • create a TCP/IP connection on port $urlImpots[port] of the machine $urlImpots[host]
    • send the HTTP headers the web server expects, including the session token if there is one
    • send a POST request to the web server with the parameters contained in the $request dictionary
    • parse the web server's response to find either a list of errors or an array of simulations.
  • The HTTP headers are sent using a POST function:
<?php
...
        // send the HTTP headers to the server
    POST($connection, $taxURL, $token,
            array(optMarried=>$request[married],txtChildren=>$request[children],txtSalary=>$request[salary]));
  • Once the HTTP headers have been sent, the getImpots function reads the entire server response and stores it in $document. The response is read without being parsed, except for the first line, which tells us whether or not the web server found the requested URL. In fact, if the URL was found, the web server responds with HTTP/1.X 200 OK, where X depends on the version of HTTP used.
<?php
...
    // URL found?
    if(! preg_match("/^(.+?) 200 OK\s*$/",$line)){
 <?php
...
       // URL not found - return with error
      return array(errors=>array("The URL $urlImpots[path] could not be found"));
    }//if
  • The document is parsed using the getInfos function:
<?php
...
    // the document is analyzed
    $taxes = getInfos($document);
  • The result returned is a dictionary with two fields:
    • errors: list of errors - may be empty
    • simulations: list of simulations - may be empty
  • Once this is done, the getImpots function can return its result as a dictionary.
<?php
...
    // return the result
    return array(token=>$token,errors=>$impots[errors],simulations=>$impots[simulations]);
  • Now let's analyze the POST function:
<?php
...
  // --------------------------------------------------------------
  function POST($connection, $url, $token, $parameters) {
      // $connection: the connection to the web server
    // $url: the URL to query
    // $parameters: dictionary of parameters to post

    // prepare the POST
    $post="";
    while(list($parameter, $value) = each($parameters)) {
        $post.=$parameter."=".urlencode($value)."&";
    }//while
    // Remove the last character
    $post = substr($post, 0, -1);

    // prepare the HTTP request in HTTP/1.0 format
    $HTTP="POST $url[path] HTTP/1.0\n";
    $HTTP.="Content-type: application/x-www-form-urlencoded\n";
    $HTTP.="Content-length: ".strlen($post)."\n";
    $HTTP.="Connection: close\n";
    if($token) $HTTP.="Cookie: PHPSESSID=$token\n";
    $HTTP.="\n";
    $HTTP.=$post;

    // send the HTTP request
    fwrite($connection, $HTTP);
    }//POST

While we’ve previously had the opportunity to request a web resource using a GET request, we haven’t yet had the chance to do so with a POST request. POST differs from GET in how it sends parameters to the server. It must send the following HTTP headers:

POST HTTP path/1.X
Content-type: application/x-www-form-urlencoded
Content-length: N

with

  • path: the path of the requested web resource; here, $url[path]
  • HTTP/1.X: the desired HTTP protocol. Here, we have chosen HTTP/1.0 to receive the response in a single chunk. HTTP/1.1 allows the response to be sent in multiple chunks (chunked).
  • N denotes the number of characters the client is about to send to the server

The N characters constituting the request parameters are sent immediately after the blank line that terminates the HTTP headers sent to the server. These parameters are in the form param1=val1&param2=val2&... where param1 is the parameter name and val1 is its value. The values vali may contain "problematic" characters such as spaces, the & character, the = character, etc. These characters must be replaced by a %XX string, where XX is their hexadecimal code. The PHP urlencode function performs this task. The reverse operation is performed by urldecode.

Finally, note that if there is a token, it is sent with an HTTP Cookie header: PHPSESSID=token.

  • We still need to examine the function that parses the server's response:
<?php
...
  // --------------------------------------------------------------
  function getInfos($document){
      // $document: HTML document
    // we look for either the list of errors
    // or the simulation table

    // preparing the result
    $taxes[errors] = array();
    $taxes[simulations] = array();
  • Note that errors are sent by the server in the form <li>error message</li>. The regular expression "|<li>(.+?)</li>|" should retrieve this information. Here, we used the | character to delimit the regular expression rather than the / character, since the latter also appears within the regular expression itself. The preg_match_all function ($pattern, $document, $fields, PREG_SET_ORDER) retrieves all occurrences of $pattern found in $document. These are placed in the $fields array. Thus, $fields[i] represents the i-th occurrence of $pattern in $document. $champs[$i][0] contains the string matching the pattern. If the pattern contained parentheses, the string corresponding to the first parenthesis is placed in $champs[$i][1], the second in $champs[$i][2], and so on. The code to retrieve the errors is therefore as follows:
<?php
...

    // Is there an error?
    // error message template
    $errorPattern="|<li>(.+?)</li>|";
    // search the document
    if(preg_match_all($errorPattern, $document, $fields, PREG_SET_ORDER)){
        // retrieve errors
      for($i=0;$i<count($fields);$i++){
          $taxes[errors][] = $fields[$i][1];
      }//for
      // done
      return $taxes;
    }//if

4.7. Tax Application: Conclusion

We have shown different versions of our client-server tax calculation application:

  • Version 1: The service is provided by a set of PHP programs; the client is a browser. It performs a single simulation and does not retain any history of previous ones.
  • Version 2: We add some browser-side capabilities by embedding JavaScript scripts in the HTML document loaded by the browser. It validates the form parameters.
  • Version 3: We allow the service to remember the different simulations performed by a client by managing a session. The HTML interface is modified accordingly to display these.
  • Version 4: The client is now a standalone console application. This allows us to revisit the development of programmed web clients.

At this point, a few observations can be made:

  • Versions 1 through 3 support browsers with no capabilities other than the ability to execute JavaScript scripts. Note that a user always has the option to disable the execution of these scripts. The application will then only work partially in version 1 (the Clear option will not work) and not at all in versions 2 and 3 (the Clear and Calculate options will not work). It might be worth considering a version of the service that does not use JavaScript scripts.

When writing a web service, you must consider which types of clients you are targeting. If you want to reach the widest possible audience, you should write an application that sends only HTML to browsers (no JavaScript or applets). If you are working within an intranet and have control over the configuration of the workstations, you can then afford to be more demanding regarding client-side requirements.

Version 4 is a web client that retrieves the information it needs from the HTML stream sent by the server. Very often, we have no control over this stream. This is the case when we have written a client for an existing web service on the network that is managed by someone else. Let’s take an example. Suppose our tax calculation simulation service was written by Company X. Currently, the service sends the simulations in an HTML table, and our client uses this fact to retrieve them. It thus compares each line of the server’s response to the regular expression:

    // example of a row from the simulation table
    // <tr><td>yes</td><td>2</td><td>200000</td><td>22504</td></tr>
    $modSimulation="|<tr>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*<td>(.+?)</td>\s*</tr>|";

Now suppose the application designer changes the visual appearance of the response by placing the simulations not in an array but in a list in the form:

Simulation Results
<ul>
    <li>yes,2,200000,22504
  <li>no,2,200000,33388
</ul>  

In this case, our web client will need to be rewritten. This is the constant threat facing web clients of applications that we do not control ourselves. XML can provide a solution to this problem:

  • instead of generating HTML, the simulation service will generate XML. In our example, this could be
<simulations>
    <headers married="married" children="children" salary="salary" tax="tax"/>
  <simulation married="yes" children="2" salary="200000" tax="22504" />
  <simulation spouse="no" children="2" salary="200000" tax="33388" />
</simulations>
  • A stylesheet could be associated with this response, instructing browsers on the visual presentation of this XML response
  • Programmed web clients would ignore this style sheet and retrieve the information directly from the XML stream of the response

If the service designer wishes to modify the visual presentation of the results provided, they will modify the style sheet rather than the XML. Thanks to the style sheet, browsers will display the new visual format, and programmed web clients will not need to be modified.