Skip to content

11. Esercizio IMPOTS con un servizio WEB e un'architettura a tre livelli

Riprenderemo l'esercizio IMPOTS (cfr. paragrafi 4.2, 4.3, 6) e ne faremo un'applicazione client/server. Lo script del server sarà suddiviso in tre elementi:

  • un livello denominato [dao] (Data Access Objects) che si occuperà degli scambi con il database MySQL
  • un livello denominato [métier] che effettuerà il calcolo dell’imposta
  • un livello denominato [web] che si occuperà delle interazioni con i client web.

Lo script client [1]:

  • trasmette allo script server le tre informazioni ($marié, $enfants, $salaire) necessarie per il calcolo dell’imposta
  • visualizza la risposta del server sulla console

Lo script server [2] è costituito dal livello [web] del server.

  • All'avvio di una nuova sessione client, inserirà in tabelle i dati del database MySQL e [dbimpots]. A tal fine, ricorrerà al livello [dao]. Gli array così creati verranno inseriti nella sessione del cliente per poter essere utilizzati nelle successive richieste del cliente.
  • Al momento di una richiesta del cliente, trasmetterà le tre informazioni ($marié, $enfants, $salaire) al livello [métier], che calcolerà l’imposta $impot.
  • Lo script del server restituirà l’imposta $impôt calcolata.

11.1. Lo script client (clients_impots_05_web)

Lo script client fungerà da client del servizio web di calcolo dell’imposta. Invierà (POST) al server i parametri nella forma:

params=$coniuge,$figli,$stipendio dove

  • $marié sarà la stringa oui o non,
  • $enfants sarà il numero di figli,
  • $salaire lo stipendio del contribuente

Trova i tre parametri precedenti in un file di testo [data.txt] nella forma (stato civile, figli, stipendio):

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

Lo script client

  • leggerà il file di testo [data.txt] riga per riga
  • invierà la stringa params=$marié,$enfants,$salaire al servizio web di calcolo delle imposte
  • recupererà la risposta del servizio. Questa potrà presentarsi in due forme:
<erreur>message d'erreur</erreur>
<impot>montant de l'impôt</impot>
  • salverà la risposta del server in un file di testo [resultats.txt] in una delle due forme seguenti:
marié:enfants:salaire:erreur
marié:enfants:salaire:impôt

Il codice dello script client è il seguente:


<?php

// cliente fiscale
// gestione degli errori
ini_set("display_errors", "off");

// ---------------------------------------------------------------------------------
// una classe di funzioni di utilità
class Utilitaires {

  function cutNewLinechar($ligne) {
    ...
  }

}

// main -----------------------------------------------------
// definizione delle costanti
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
// dati del server
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/impots_05_web.php";

// i parametri dei contribuenti (stato civile, numero di figli, stipendio annuo)
// sono stati inseriti nel file di testo $DATA con una riga per ogni contribuente
// i risultati (stato civile, numero di figli, stipendio annuo, imposta da pagare) 
// oppure (stato civile, numero di figli, stipendio annuo, messaggio di errore) sono stati inseriti nel
// nel file di testo $RESULTATS, con un risultato per riga

// classe Utilità
$u = new Utilitaires();

// apertura del file dei dati dei contribuenti
$data = fopen($DATA, "r");
if (!$data) {
  print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
  exit;
}

// apertura del file dei risultati
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
  print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
  exit;
}

// si elabora la riga corrente del file dei dati dei contribuenti
while ($ligne = fgets($data, 100)) {
  // si rimuove l'eventuale carattere di fine riga
  $ligne = $u->cutNewLineChar($ligne);
  // si recuperano i 3 campi coniuge:figli:stipendio che formano $ligne
  list($marié, $enfants, $salaire) = explode(",", $ligne);
  // si calcola l'imposta
  list($erreur, $impôt) = calculerImpot($HOTE, $PORT, $urlServeur, $cookie, array($marié, $enfants, $salaire));
  // si inserisce il risultato
  $résultat = $erreur ? "$marié:$enfants:$salaire:$erreur" : "$marié:$enfants:$salaire:$impôt";
  fputs($résultats, "$résultat\n");
  // dato successivo
}
// si chiudono i file
fclose($data);
fclose($résultats);

// fine
print "Terminé...\n";
exit;

function calculerImpot($HOTE, $PORT, $urlServeur, &$cookie, $params) {
  // collega il cliente a ($HOTE,$PORT,$urlServeur)
  // invia il cookie $cookie se quest'ultimo non è vuoto. $cookie viene passato per riferimento
  // invia $params al server
  // elabora l'unica riga restituita dal server
  
  // apertura di una connessione sulla porta 80 di $HOTE
  $connexion = fsockopen($HOTE, $PORT);
  // errore?
  if (!$connexion)
    return array("erreur lors de la connexion au serveur ($HOTE, $PORT)");
  // le intestazioni (header) del protocollo HTTP devono terminare con una riga vuota
  // POST
  fputs($connexion, "POST $urlServeur HTTP/1.1\n");
  // Host
  fputs($connexion, "Host: localhost\n");
  // Connessione
  fputs($connexion, "Connection: close\n");
// il cookie viene inviato se non è vuoto
  if ($cookie) {
    fputs($connexion, "Cookie: $cookie\n");
  }//if
  // ora si invia l'istruzione al client dopo averla codificata
  $infos = "params=" . urlencode(implode(",", $params));
  // si specifica il tipo di informazioni che verranno inviate
  fputs($connexion, "Content-type: application/x-www-form-urlencoded\n");
  // si invia la dimensione (numero di caratteri) delle informazioni che verranno inviate
  fputs($connexion, "Content-length: " . strlen($infos) . "\n");
  // si invia una riga vuota
  fputs($connexion, "\n");
  // si inviano le informazioni
  fputs($connexion, $infos);
  // si visualizza la risposta del server web
  // e ci si assicura di recuperare l'eventuale cookie
  while ($ligne = fgets($connexion, 1000)) {
    // cookie - solo alla prima risposta
    if (!$cookie) {
      if (preg_match("/^Set-Cookie: (.*?)\s*$/", $ligne, $champs)) {
        $cookie = $champs[1];
      }//if
    }
    // non appena si trova una riga vuota, la risposta HTTP è terminata
    if (trim($ligne) == "") {
      break;
    }
  }//while
  // lettura della riga del risultato
  $ligne = fgets($connexion, 1000);
  // si chiude la connessione
  fclose($connexion);
  // calcolo del risultato
  $erreur="";
  $impôt="";
  if (preg_match("/^<erreur>(.*?)<\/erreur>\s*$/", $ligne, $champs)) {
    $erreur = $champs[1];
  } else {
    if (preg_match("/^<impot>(.*?)<\/impot>\s*$/", $ligne, $champs)) {
      $impôt = $champs[1];
    }else{
      $erreur="résultat du serveur non exploitable";
    }
  }
  // ritorno
  return array($erreur, $impôt);
}

Commenti

Il codice dello script client riprende elementi già visti:

  • righe 9-15: la classe [Utilitaires] è stata presentata nella versione 3, paragrafo 6
  • righe 17-68: il programma principale è analogo a quello della versione 1, paragrafo 4.2. Si differenzia da esso solo per il calcolo dell'imposta, riga 56.
  • riga 56: la funzione di calcolo dell’imposta accetta i seguenti parametri:
    • $HOTE, $PORT, $urlServeur: consentono di connettersi al servizio web
    • $cookie: è il cookie di sessione. Questo parametro viene passato per riferimento. Il suo valore è stabilito dalla funzione di calcolo dell’imposta. Al primo richiamo non ha alcun valore. Successivamente ne ha uno.
    • array($marié, $enfants, $salaire): rappresenta una riga del file [data.txt]

La funzione di calcolo dell’imposta restituisce un array con due risultati ($erreur, $impôt) dove $erreur è un eventuale messaggio di errore e $impôt l'importo dell'imposta.

  • righe 70 – 134: si tratta di un classico client HTTP come ne abbiamo incontrati molti. Si notino i seguenti punti:
  • riga 83: i parametri ($marié, $enfants, $salaire) vengono trasmessi al server tramite un POST
  • righe 89-91: se il client dispone di un identificativo di sessione, lo invia al server
  • riga 93: creazione del parametro params
  • riga 101: invio del parametro params
  • righe 104-115: il client legge tutte le intestazioni HTTP inviate dal server fino a quando non incontra la riga vuota che segna la fine delle intestazioni. Ne approfitta per recuperare l'identificativo di sessione nella risposta alla sua prima richiesta.
  • righe 123-125: si elabora un'eventuale riga della forma <erreur>message</erreur>
  • righe 126-128: si procede allo stesso modo con un'eventuale riga della forma <impot>montant</impot>
  • riga 133: si restituisce il risultato

11.2. Il servizio web per il calcolo delle imposte

Ci interessano qui i tre script che compongono il server:

Il progetto NetBeans corrispondente è il seguente:

In [1], il server è costituito dai seguenti script PHP:

  • [impots_05_entites] contiene le classi utilizzate dal server
  • [impots_05_dao] contiene le classi e le interfacce del livello [dao]
  • [impots_05_metier] contiene le classi e le interfacce del livello [metier]
  • [impots_05_web] contiene le classi e le interfacce del livello [dao]

Iniziamo presentando due classi utilizzate dai diversi livelli del servizio web.

11.2.1. Le entità del servizio web (impots_05_entites)

Il database MySQL [dbimpots] contiene una tabella [impots] che contiene i dati necessari per il calcolo dell’imposta [1]:

Memorizzeremo i dati delle tabelle MySQL e [impots] in un array di oggetti Tranche, dove Tranche è la seguente classe:


<?php

// una fascia di imposta
class Tranche {

  // campi privati
  private $limite;
  private $coeffR;
  private $coeffN;

  // getter e setter
  public function getLimite() {
    return $this->limite;
  }

  public function setLimite($limite) {
    $this->limite = $limite;
  }

  public function getCoeffR() {
    return $this->coeffR;
  }

  public function setCoeffR($coeffR) {
    $this->coeffR = $coeffR;
  }

  public function getCoeffN() {
    return $this->coeffN;
  }

  public function setCoeffN($coeffN) {
    $this->coeffN = $coeffN;
  }

  // costruttore
  public function __construct($limite, $coeffR, $coeffN) {
    $this->setLimite($limite);
    $this->setCoeffR($coeffR);
    $this->setCoeffN($coeffN);
  }

  // toString
  public function __toString(){
    return "[$this->limite,$this->coeffR,$this->coeffN]";
  }
}

I campi privati [$limite, $coeffR, $coeffN] serviranno a memorizzare le colonne [limites, coeffR, coeffN] di una riga della tabella MySQL [impots].

Inoltre, il codice server utilizzerà un’eccezione specifica, la classe ImpotsException:

1
2
3
4
5
6
7
class ImpotsException extends Exception {

  public function __construct($message, $code=0) {
    parent::__construct($message, $code);
  }

}
  • riga 1: la classe [ImpotsException] deriva dalla classe [Exception] predefinita in PHP 5
  • riga 3: il costruttore della classe [ImpotsException] accetta due parametri:
    • $message: un messaggio di errore
    • $code: un codice di errore

11.2.2. Il livello [dao] (impots_05_dao)

Il livello [dao] garantisce l’accesso ai dati del database:

Il livello [dao] presenta la seguente interfaccia:

1
2
3
4
5
6
// interfaccia Dao
interface IImpotsDao {

// restituisce un array di entità Tranche
  function getData();
}

L'interfaccia IImpotsDao espone solo la funzione getData. Questa funzione inserisce in un array di oggetti Tranche le diverse righe della tabella MySQL [dbimpots.impots].

La classe di implementazione è la seguente:


<?php

// livello Dao
// dipendenze
require_once "impots_05_entites.php";
// costanti
define("TABLE", "impots");

// -----------------------------------------------------------------
// implementazione astratta
abstract class ImpotsDaoWithPdo implements IImpotsDao {

// campi privati
  private $dsn;
  private $user;
  private $passwd;
  private $tranches;

// getter e setter
  public function getDsn() {
    return $this->dsn;
  }

  public function setDsn($dsn) {
    $this->dsn = $dsn;
  }

  public function getUser() {
    return $this->user;
  }

  public function setUser($user) {
    $this->user = $user;
  }

  public function getPasswd() {
    return $this->passwd;
  }

  public function setPasswd($passwd) {
    $this->passwd = $passwd;
  }

  // costruttore
  public function __construct($dsn, $user, $passwd) {
    // si salvano i parametri
    $this->setDsn($dsn);
    $this->setUser($user);
    $this->setPasswd($passwd);
    // si recuperano i dati da SGBD
    // si connette ($user,$pwd) al database $dsn
    try {
      // connessione
      $connexion = new PDO($dsn, $user, $passwd, array(PDO::ATTR_PERSISTENT => true));
      // lettura della tabella $TABLE
      $requête = "select limites,coeffR,coeffN from " . TABLE;
      // esegue la query $requête sulla connessione $connexion
      $statement = $connexion->prepare($requête);
      $statement->execute();
      // elaborazione del risultato della query
      while ($colonnes = $statement->fetch()) {
        $this->tranches[] = new Tranche($colonnes[0], $colonnes[1], $colonnes[2]);
      }
      // disconnessione
      $connexion=NULL;
    } catch (PDOException $e) {
      // ritorno con errore
      throw new ImpotsException($e->getMessage(), 1);
    }
  }

  public function getData(){
    return $this->tranches;
  }
}
  • riga 5: l’implementazione dell’interfaccia [IImpotsDao] richiede le classi definite nello script [impots_05_entites].
  • riga 11: definizione di una classe astratta. Una classe astratta è una classe di cui non è possibile creare un'istanza. Una classe astratta deve essere obbligatoriamente derivata per poter essere istanziata. Una classe può essere dichiarata astratta perché non è possibile istanziarla (alcuni dei suoi metodi non sono definiti) oppure perché non si desidera istanziarla. In questo caso, non si desidera istanziare la classe [ImpotsDaoWithPdo]. Si istanzieranno invece le classi derivate.
  • riga 11: la classe [ImpotsDaoWithPdo] implementa l’interfaccia [IImpotsDao]. Deve quindi definire il metodo getData. Questo metodo si trova alle righe 72-74.
  • riga 14: $dsn (Data Source Name) è una stringa di caratteri che identifica in modo univoco il SGBD e il database utilizzato.
  • riga 15: $user identifica l’utente che si connette al database
  • riga 16: $passwd è la password dell’utente precedente
  • riga 17: $tranches è l'array di oggetti Tranche in cui verrà memorizzata la tabella MySQL [dbimpots.impots].
  • righe 45-70: il costruttore della classe. Questo codice è già stato visto nella versione 4, paragrafo 8.2. Si noti che la creazione dell’oggetto [ImpotsDaoWithPdo] potrebbe non andare a buon fine. In tal caso viene generata un’eccezione di tipo [ImpotsException].
  • righe 72-74: il metodo [getData] dell’interfaccia [IImpotsDao].

La classe [ImpotsDaoWithPdo] è compatibile con qualsiasi SGBD. Il costruttore della classe, alla riga 45, richiede di conoscere il Data Source Name del database. Questa stringa di caratteri dipende dal SGBD utilizzato. Si è scelto di non imporre all'utente della classe di conoscere tale Data Source Name. Per ogni SGBD, ci sarà una classe specifica derivata da [ImpotsDaoWithPdo]. Per il SGBD MySQL, sarà la seguente classe:


class ImpotsDaoWithMySQL extends ImpotsDaoWithPdo {

  public function __construct($host, $port, $base, $user, $passwd) {
    parent::__construct("mysql:host=$host;dbname=$base;port=$port", $user, $passwd);
  }

}
  • alla riga 3, il produttore non richiede il nome della fonte dati (Data Source Name), ma semplicemente il nome del computer host di SGBD ($host), la sua porta di ascolto ($port) e il nome del database ($base).
  • Nella riga 4, viene generato il nome della fonte dati (Data Source Name) del database MySQL, che viene utilizzato per richiamare il costruttore della classe padre.

Si noti che per adattarsi a un altro SGBD, è sufficiente scrivere la classe derivata da [ImpotsDaoWithPdo] che si addice. Si tratta ogni volta di costruire il nome della fonte dati specifico per il SGBD utilizzato.

11.2.3. Il livello [métier] (impots_05_metier)

Il livello [metier] contiene la logica di calcolo dell’imposta:

Il livello [métier] presenta la seguente interfaccia:


<?php

// interfaccia di business
interface IImpotsMetier {
  public function calculerImpot($marié, $enfants, $salaire);
}

L'interfaccia [IImpotsMetier] espone un solo metodo, il metodo [calculerImpot], che consente di calcolare l'imposta di un contribuente in base ai seguenti parametri:

  • $marié: stringa «sì»/«no» a seconda che il contribuente sia sposato o meno
  • $enfants: il numero di figli del contribuente
  • $salaire: il suo stipendio

È il livello [web] che fornirà questi parametri.

L'implementazione dell'interfaccia [IImpotsMetier] è la seguente:


// dipendenze
require_once "impots_05_dao.php";

// ------------------------------------------------------------------
// classe di implementazione
class ImpotsMetier implements IImpotsMetier {

  // livello DAO
  private $dao;
   // array di oggetti [Tranche]
  private $data;

  // getter e setter
  public function getDao() {
    return $this->dao;
  }

  public function setDao($dao) {
    $this->dao = $dao;
  }

  public function setData($data){
    $this->data=$data;
  }
  
  public function __construct($dao) {
    // si recuperano i dati necessari per il calcolo dell'imposta
    $this->setDao($dao);
    $this->setData($this->dao->getData());
  }

  public function calculerImpot($marié, $enfants, $salaire) {
    // $marié: sì, no
    // $enfants: numero di figli
    // $salaire: stipendio annuo
    // numero di quote
    $marié = strtolower($marié);
    if ($marié == "oui")
      $nbParts = $enfants / 2 + 2;
    else
      $nbParts=$enfants / 2 + 1;
    // una quota e mezza in più se si hanno almeno 3 figli
    if ($enfants >= 3)
      $nbParts+=0.5;
    // reddito imponibile
    $revenuImposable = 0.72 * $salaire;
    // quoziente familiare
    $quotient = $revenuImposable / $nbParts;
    // viene inserito alla fine della tabella dei limiti per interrompere il ciclo successivo
    $N = count($this->data);
    $this->data[$N - 1]->setLimite($quotient);
    // calcolo dell'imposta
    $i = 0;
    while ($i < $N and $quotient > $this->data[$i]->getLimite()) {
      $i++;
    }
    // poiché $quotient è stato inserito alla fine della tabella $limites, il ciclo precedente
    // non può andare oltre i limiti della tabella $limites
    // ora è possibile calcolare l’imposta
    return floor($revenuImposable * $this->data[$i]->getCoeffR() - $nbParts * $this->data[$i]->getCoeffN());
  }

}
  • riga 2: il livello [métier] necessita delle classi del livello [dao] e delle entità (Tranche, ImpotsException).
  • riga 6: la classe [ImpotsMetier] implementa l'interfaccia [IimpotsMetier].
  • righe 9-11: i campi privati della classe:
    • $dao: riferimento al livello [dao]
    • $data: array di oggetti di tipo [Tranche] fornito dal livello [dao]
  • righe 26-30: il costruttore della classe inizializza i due campi precedenti. Riceve come parametro un riferimento al livello [dao].
  • righe 32-61: implementazione del metodo [calculerImpot] dell’interfaccia [IimpotsMetier]. Questo metodo è stato riscontrato già a partire dalla versione 1 (paragrafo 4.2).

11.2.4. Il livello [web] (impots_05_web)

Il livello [metier] contiene la logica di calcolo dell’imposta:

Il livello [web] è costituito dal servizio web che risponde ai client web. Si ricorda che questi ultimi inviano una richiesta al servizio web inviando il seguente parametro: params=marié,enfants,salaire. Si tratta di un servizio web simile a quelli che abbiamo realizzato nei paragrafi precedenti. Il suo codice è il seguente:


<?php

// livello applicativo
require_once "impots_05_metier.php";

// gestione degli errori
ini_set("display_errors", "off");

// intestazione UTF-8
header("Content-Type: text/plain; charset=utf-8");

// ------------------------------------------------------------------------------
// il servizio web delle imposte
// definizione delle costanti
$HOTE = "localhost";
$PORT = 3306;
$BASE = "dbimpots";
$USER = "root";
$PWD = "";
// i dati necessari per il calcolo dell'imposta sono stati inseriti nella tabella MySQL IMPOTS
// appartenente al database $BASE. La tabella presenta la seguente struttura
// limiti decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
// i parametri dei contribuenti (stato civile, numero di figli, stipendio annuo)
// vengono inviati dal cliente nel formato params=stato civile, numero di figli, stipendio annuo
// i risultati (stato civile, numero di figli, stipendio annuo, imposta da pagare) vengono restituiti al cliente
// nel formato <imposta>imposta</imposta>
// oppure nella forma <errore>errore</errore>, se i parametri non sono validi

// si recupera il livello [métier] nella sessione
session_start();
if (!isset($_SESSION['metier'])) {
// istanziazione del livello [dao] e del livello [métier]
  try {
    $_SESSION['metier'] = new ImpotsMetier(new ImpotsDaoWithMySQL($HOTE, $PORT, $BASE, $USER, $PWD));
  } catch (ImpotsException $ie) {
    print "<erreur>Erreur : " . utf8_encode($ie->getMessage() . "</erreur>");
    exit;
  }
}
$metier = $_SESSION['metier'];

// si recupera la riga inviata dal cliente
$params = utf8_encode(htmlspecialchars(strtolower(trim($_POST['params']))));
$items = explode(",", $params);
// devono esserci solo 3 parametri
if (count($items) != 3) {
  print "<erreur>[$params] : nombre de paramètres invalides</erreur>\n";
  exit;
}//if
// il primo parametro (stato civile) deve essere sì/no
$marié = trim($items[0]);
if ($marié != "oui" and $marié != "non") {
  print "<erreur>[$params] : 1er paramètre invalide</erreur>\n";
  exit;
}//if
// il secondo parametro (numero di figli) deve essere un numero intero
if (!preg_match("/^\s*(\d+)\s*$/", $items[1], $champs)) {
  print "<erreur>[$params] : 2ième paramètre invalide</erreur>\n";
  exit;
}//if
$enfants = $champs[1];
// il terzo parametro (stipendio) deve essere un numero intero
if (!preg_match("/^\s*(\d+)\s*$/", $items[2], $champs)) {
  print "<erreur>[$params] : 3ième paramètre invalide</erreur>\n";
  exit;
}//if
$salaire = $champs[1];
// si calcola l'imposta
$impôt = $metier->calculerImpot($marié, $enfants, $salaire);
// si restituisce il risultato
print "<impot>$impôt</impot>\n";
// fine
exit;
  • riga 4: il livello [web] necessita delle classi del livello [métier]
  • righe 30-40: il riferimento al livello [métier] viene inserito nella sessione. Se si tiene presente che questo livello [métier] contiene un riferimento al livello [dao] e che quest’ultimo memorizza i dati del livello SGBD, si comprende quindi che:
    • che la prima richiesta del cliente provocherà un accesso a SGBD
    • che le richieste successive dello stesso cliente utilizzeranno i dati memorizzati dal livello [dao]. Non vi è quindi alcun accesso al SGBD.
  • riga 34: creazione di un livello [métier] che opera con un livello [dao] implementato per il SGBD MySQL
  • righe 35-37: gestione di un eventuale errore nell'operazione precedente. In questo caso, viene inviata al cliente una riga <erreur>message</erreur>.
  • riga 43: si recupera il parametro 'params' inviato dal cliente.
  • righe 46-49: si verifica il numero di informazioni presenti in 'params'
  • righe 51-55: si verifica la validità della prima informazione
  • righe 56-60: lo stesso vale per la seconda
  • righe 62-66: lo stesso vale per la terza
  • riga 69: è il livello [métier] che calcola l’imposta.
  • riga 71: invio del risultato al cliente

Risultati

Ricordiamo che il cliente [client_impots_web_05] utilizza il seguente file [data.txt]:

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

Partendo da queste righe (stato civile, figli, stipendio), il client interroga il server di calcolo delle imposte e registra i risultati nel file di testo [resultats.txt]. Dopo l'esecuzione del client, il contenuto di questo file è il seguente:

oui:2:200000:22504
non:2:200000:33388
oui:3:200000:16400
non:3:200000:22504
oui:5:50000:0
non:0:3000000:1354938

dove ogni riga ha la forma (coniuge, figli, stipendio, imposta calcolata).