Valid XHTML     Valid CSS2    

DECRA, T.P. 1 :

     Gestion de noms et de prénoms, prototypes

Décomposition, Conception et Réalisation d'Applications

                    gilles.hunault "at" univ-angers.fr

Table des matières cliquable

  1. Trier des noms et des prénoms avec AWK et PHP

  2. Conception : maquettes et prototypes pour le calcul de co-occurrences

Il est possible d'afficher toutes les solutions via ?solutions=1 et de les masquer via ?solutions=0.

 

Rappel : pour utiliser PHP 7 avec une "bonne configuration" dans les locaux du département informatique, vous devez dans un terminal lancer la commande _php7 c'est-à-dire php7 précédé du souligné _. Comme cet alias lance un "docker" il est conseillé d'utiliser, dans ces salles, deux terminaux pour la ligne de commandes et deux explorateurs de fichiers, de façon à pouvoir échanger facilement des fichiers entre le conteneur et votre répertoire usuel de stockage. Attention : gawk et wget ne doivent pas être exécutés dans le terminal du conteneur, mais bien dans l'autre terminal. De plus seul le conteneur dispose de phpunit.

Dans le même genre d'idées, utiliser un répertoire pour tous vos développements de ce T.P., par exemple nommé tp1/ ou DCRA-tp1/ est une pratique conseillée. Le chemin absolu pour vos fichiers du conteneur est, quant à lui, /home/php_dev/Mes_projets_web/.

 

1. Trier des noms et des prénoms avec AWK et PHP

On dispose des deux fichiers de données, nommés nomsprenoms1.txt et nomsprenoms2.txt. On voudrait afficher rapidement et "proprement" ces fichiers par ordre croissant de nom ou de prénom. Trouver une solution en ligne de commande avec AWK et PHP (prototypes) puis fournir une solution Web en PHP avec choix du fichier et choix du critère de tri. On écrira des classes d'objets Chaines et Personnes qu'on viendra tester avec phpunit.

Voici le fichier nomsprenoms1.txt :


     Jeanne Calment
     Jean  Valjean
     Brigitte BARDOT
     Emile ZOLA
     
     

Et le fichier nomsprenoms2.txt :


     Jean Baptiste POQUELIN
     Daniel COHN BENDIT
     Jeanne-Marie de Maillé
     

Voici un exemple de fichier "propre" issu de nomsprenoms2.txt. On a trié par ordre croissant de nom et on a aligné les noms convertis en majuscules.


     COHN BENDIT         Daniel
     DE MAILLE           Jeanne-Marie
     POQUELIN            Jean Baptiste
     

Pour les plus fort(e)s, on produira aussi un fichier Word, Excel et PDF des listes de noms.

On réfléchira aux extensions et généralisations possibles.

Remarque : pour éviter de perdre du temps, on rapatriera en local les fichiers-texte des noms et prénoms via wget.

Solution :  

Avant de commencer à programmer quoi que ce soit, comme d'habitude, il faut bien réfléchir au problème. Il est clair qu'à moins de connaitre tous les prénoms possibles -- et encore, en novembre 2017 le premier ministre se nomme Edouard Philippe -- il est difficile de savoir où se situe la partie nom et la partie prénom, même à l'aide du fichier national français des prénoms. Sauf à disposer d'un fichier de données avec seulement deux mots, le prénom puis le nom comme dans nomsprenoms1.txt, il faut certainement choisir un séparateur entre ces deux parties. On trouvera dans la page tp1exo1.php un plan de développement possible pour cet exercice.

1. Développement AWK (maquette)

1.1 Script AWK simple, fichiers avec deux mots

Utiliser dans un premier temps le fichier nomsprenoms1.txt est toutefois un bon choix car cela permet de traiter «les bons cas». Un simple script Gawk suffit pour convertir le nom en majuscules, comme par exemple le fichier tripnv1.awk :


     # # (gH)   -_-  tripnv1.awk
     
     # on suppose qu'il n'y a que deux champs (deux mots)
     # le premier est le prénom, le second est le nom qu'on convertit en majuscules
     # on ignore les lignes vides
     
     #  exemple d'utilisation :
     #     gawk -f tripnv1.awk nomsprenoms1.txt | sort
     
     (length($0)>0) { # sans doute pas aussi bien que (NF>0) pour les lignes vides
       prenom = $1
       nom    = toupper($2)
       print sprintf("%-20s %-20s",nom,prenom)
     } # fin de traitement
     

On notera que le tri n'est pas effectué par le script et que seules les lignes non vides sont utilisées. Voici des exemples d'utilisation pour vérifier cela et voir comment trier sur le nom ou sur le prénom :


     @ghchu3-ET2700I~/public_html/Decra|(~gH) > \grep -n ""  nomsprenoms1.txt
     1:Jeanne Calment
     2:Jean  Valjean
     3:Brigitte BARDOT
     4:Emile ZOLA
     5:
     
     @ghchu3-ET2700I~/public_html/Decra|(~gH) > gawk -f tripnv1.awk nomsprenoms1.txt | \grep -n ""
     1:CALMENT            Jeanne
     2:VALJEAN            Jean
     3:BARDOT             Brigitte
     4:ZOLA               Emile
     
     @ghchu3-ET2700I~/public_html/Decra|(~gH) > gawk -f tripnv1.awk nomsprenoms1.txt
     CALMENT              Jeanne
     VALJEAN              Jean
     BARDOT               Brigitte
     ZOLA                 Emile
     
     @ghchu3-ET2700I~/public_html/Decra|(~gH) > gawk -f tripnv1.awk nomsprenoms1.txt | sort
     BARDOT               Brigitte
     CALMENT              Jeanne
     VALJEAN              Jean
     ZOLA                 Emile
     
     @ghchu3-ET2700I~/public_html/Decra|(~gH) > gawk -f tripnv1.awk nomsprenoms1.txt | sort -k 2
     BARDOT               Brigitte
     ZOLA                 Emile
     VALJEAN              Jean
     CALMENT              Jeanne
     
     

1.2 Script AWK avancé, fichiers avec plusieurs mots et séparateur

Pour traiter proprement les deux parties noms et prénoms, nous allons utiliser le point-virgule comme séparateur, comme dans le fichier suivant, nommé nomsprenoms3.txt


     Jean baptiste ; Poquelin
     Daniel ; COHN BENDIT
     Jeanne-Marie ; de Maillé
     

Au passage, nous avons mis des «pièges» pour couvrir tous les cas possibles : plusieurs prénoms, un prénom multiple avec un tiret. Le script Awk doit être adapté en conséquence. Dans la mesure où il est possible d'avoir à passer plusieurs mots avec l'initiale en majucule et le reste en minuscules, il est judicieux d'écrire ici une fonction. Voici donc le code du nouveau script :

Fichier tripnv2.awk :


     ## (gH)   -_-  tripnv2.awk
     
     # on suppose qu'il y a plusieurs champs (mots) avec un point virgule comme séparateur
     # avant, c'est le prénom, après c'est le nom qu'on convertit en majuscules
     # on convertit le(s) prénom(s) avec une initiale majuscule, le reste en minuscules
     # on ignore les lignes vides
     
     #  exemple d'utilisation :
     #     gawk -f tripnv2.awk nomsprenoms3.txt | sort
     
     
     function initialeMajuscule( chaine ) {
         deb     = toupper(substr(chaine,1,1))
         fin     = tolower(substr(chaine,2))
         return deb fin
     } # fin de fonction initialeMajuscule
     
     
     /;/ { # début de traitement de chaque ligne non vide avec des points-virgules
     
       split($0,mots,";")
       nom     = toupper(mots[2])
       pren1   = mots[1]
       n       = split(pren1,pren2," ")
       prenoms = ""
       for (p=1;p<=n;p++) {
           prenom   = pren2[p]
           postiret = index(prenom,"-")
           if (postiret==0) {
              prenom   = initialeMajuscule(prenom)
           } else {
              avant    = substr(prenom,1,postiret)
              apres    = substr(prenom,1+postiret)
              prenom   = initialeMajuscule(avant) initialeMajuscule(apres)
           } # fin si sur postiret
           prenoms  = prenoms prenom " "
       } # fin pour chaque prenom
       print sprintf("%-20s %-20s",nom,prenoms)
     
     } # fin de traitement
     

Résultats :


     $gh> gawk -f tripnv2.awk nomsprenoms3.txt
     
     POQUELIN            Jean Baptiste
     COHN BENDIT         Daniel
     DE MAILL            Jeanne-Marie
     

2. Développement PHP

2.1 Script PHP cli simple

On peut désormais considérer que la maquette en AWK est terminée. On passe désormais à une première implémentation en PHP cli (ligne de commandes) avec une fonction initialeMajuscule() codée directement dans le code-source PHP. Dans la mesure où on écrira ensuite une page Web pour la même action, on sépare bien la lecture des noms/prénoms et leur traitement.

Voici la première version du code PHP, fichier tripnv1.php :


     <?php
     #   # (gH)   -_-  tripnv1.php  ;  TimeStamp (unix) : 10 Novembre 2017 vers 18:08
     
     error_reporting(E_ALL | E_NOTICE | E_STRICT ) ;
     
     # on découpe des lignes non vides en nom(s) et prénom(s)
     # selon le séparateur point-virgule
     # voir http://forge.info.univ-angers.fr/~gh/Decra/tp1.php pour les consignes
     
     function initialeMajuscule($chaine) {
        $deb = strtoupper(substr($chaine,0,1)) ;
        $fin = strtolower(substr($chaine,1)) ;
        return $deb.$fin ;
     } # fin de fonction initialeMajuscule
     
     ## 1. vérification des paramètres
     
     if ($argc<2) {
     
       echo "\n" ;
       echo "Appel incorrect du programme. Il faut donner un nom de fichier.\n " ;
       echo "Exemple : php tripnv1.php nomsprenoms1.txt\n\n" ;
       exit(-1)  ;
     
     } ; # fin de si
     
     # pour debug : print_r($argv) ;
     
     $nomFic = $argv[1] ;
     
     if (!file_exists($nomFic)) {
     
       echo "\n" ;
       echo "Exécution impossible, le fichier indiqué, soit $nomFic n'est pas vu.\n " ;
       exit(-2)  ;
     
     } ; # fin de si
     
     ## 2. lecture du fichier
     
     $nomsPrenoms = file_get_contents($nomFic) ;
     
     ## 3. conversion et mise en forme via la fonction initialeMajuscule()
     
     # on découpe selon les retours-charriot
     
     $tabNP = preg_split("/\n/",$nomsPrenoms) ;
     # pour debug : print_r($tabNP) ;
     
     # on traite chacune des lignes non vides
     
     $nbl = 0 ;
     foreach ($tabNP as $ligne) {
     
       $nbl++ ;
       if (strlen($ligne)>0) {
     
         $nomPrenom = preg_split("/;/",$ligne) ;
         # pour debug : print_r($nomPrenom) ;
     
         if (count($nomPrenom)==1) {
           echo "Impossible de traiter la ligne $nbl, soit $ligne\n" ;
           echo "Il manque le séparateur point-virgule. STOP.\n" ;
           exit(-2)  ;
     
         } else {
     
            # pour le nom, c'est simple : juste une conversion en majuscules
     
            $nom     = strtoupper(trim($nomPrenom[1])) ;
     
            # pour le prénom, on gère les parties multiples comme jean marie ou jean-marie
     
            $pren1   = trim($nomPrenom[0]) ;
            $pren2   = preg_split("/\s+/",$pren1) ;
            $prenoms = "" ;
            # pour debug : print_r($pren2) ;
               for ($p=0;$p<count($pren2);$p++) {
                   $prenom   = $pren2[$p] ;
                   $postiret = strpos($prenom,"-") ;
                   if ($postiret==0) {
                      $prenom   = initialeMajuscule($prenom) ;
                   } else {
                      $avant    = substr($prenom,0,$postiret+1) ;
                      $apres    = substr($prenom,1+$postiret) ;
                      $prenom   = initialeMajuscule($avant) . initialeMajuscule($apres) ;
        # pour debug : echo "nom : $nom ; prenoms : $prenoms via $prenom et $avant $apres \n" ;
                   } # fin si sur postiret
                   $prenoms  .= $prenom . " " ;
               } # fin pour chaque prenom
            echo sprintf("%-20s %-20s",$nom,$prenoms)."\n" ;
          } ; # fin si  sur séparateur
     
       } ; # fin si sur ligne vide
     
     } ; # fin pour chaque ligne
     
     ?>
     

Voici le résultat des tests élémentaires (on ne s'est pas occupé du tri, pour l'instant) :


     @ghchu5~/public_html/Decra|(~gH) > php tripnv1.php
     
     Appel incorrect du programme. Il faut donner un nom de fichier.
     Exemple : php tripnv1.php nomsprenoms1.txt
     
     @ghchu5~/public_html/Decra|(~gH) > php tripnv1.php rate.txt
     
     Exécution impossible, le fichier indiqué, soit rate.txt n'est pas vu.
     
     @ghchu5~/public_html/Decra|(~gH) > php tripnv1.php nomsprenoms3.txt
     POQUELIN             Jean Baptiste
     COHN BENDIT          Daniel
     DE MAILLé            Jeanne-Marie
     
     @ghchu5~/public_html/Decra|(~gH) > php tripnv1.php nomsprenoms1.txt # pour information
     Impossible de traiter la ligne 1, soit Jeanne Calment
     Il manque le séparateur point-virgule. STOP.
     
     

2.2 Script PHP cli avancé

Il faut maintenant passer à une version cli plus conséquente. Voici ce qu'il manque :

  • Il faut prendre en compte les lignes à deux mots sans séparateur car c'est un cas usuel. On invente au passage une classe Chaine, les méthodes nbmots() et mot(). On décide aussi qu'une ligne à un mot correspond à un nom seulement. On utilisera désormais nomsprenoms4.txt comme jeu d'essai.

  • Les champs noms prénoms ne sont pas vraiment liés pour l'instant. Une implémentation objet de nom/prénom doit faire référence à une classe Personne.

  • Le tri n'est pas réalisé, supposer qu'il sera fait en ligne de commande ne se généralisera pas à une page Web.

  • La fonction initialeMajuscule() n'est qu'un début, le code pour la gestion des prénoms multiples doit être intégré à une méthode initialeMajusculePrenom() de la classe Chaine.

Commençons par la partie objet : on définit une classe Chaine et une classe Personne dans deux fichiers séparés.

Fichier tripn-chaines.php :


     <?php
     #   # (gH)   -_-  tripn-chaines.php  ;  TimeStamp (unix) : 11 Novembre 2017 vers 22:48
     
     class Chaine {
     
       var $chaine = "" ;
     
       ############################################################
     
       public function __construct($laChaine="") {
     
       ############################################################
     
         $this->chaine = trim($laChaine) ;
     
       } # fin de fonction __construct
     
       ############################################################
     
       public function getChaine() {
     
       ############################################################
     
         return $this->chaine ;
     
       } # fin de fonction nom
     
       ############################################################
     
       public function nbmots() {
     
       ############################################################
     
         # renvoie le nombre de mots de la chaine
     
         $phrase = $this->getChaine()                 ;
         $phrase = chop(trim($phrase))                ; # trim est sans doute inutile ici
         if (strlen(trim($phrase))==0) { return 0 ; } ; # ici aussi
         $tmots  = preg_split("/\s+/",trim($phrase))  ; # ici aussi
         $nbm    = count($tmots)                      ;
         return($nbm)                                 ;
     
       } # fin de fonction nbmots
     
       ############################################################
     
       public function mot ($num) {
     
       ############################################################
     
         # renvoie le n-ième mot d'une phrase
     
         $phrase = $this->getChaine()                  ;
         $phrase = chop(trim($phrase))                 ;
         if (strlen(trim($phrase))==0) { return "" ; } ;
         if ($num<=0)                  { return "" ; } ;
         $tmots  = preg_split("/\s+/",trim($phrase))   ;
         if ($num>count($tmots))       { return "" ; } ;
         $lm     = $tmots[$num-1]                      ;
         return trim($lm)                              ;
     
       } # fin de fonction mot
     
       ############################################################
     
       public function initialeMajuscule() {
     
       ############################################################
     
         $chaine = trim($this->getChaine())        ;
         $deb    = strtoupper(substr($chaine,0,1)) ;
         $fin    = strtolower(substr($chaine,1))   ;
         return $deb.$fin                          ;
     
       } # fin de fonction initialeMajuscule
     
       ############################################################
     
       public function initialeMajusculePrenom() {
     
       ############################################################
     
         $laChaine = $this->getChaine() ;
         $tabPren  = preg_split("/\s+/",$laChaine) ;
         $prenoms  = "" ;
         for ($p=0;$p<count($tabPren);$p++) {
           $prenom   = $tabPren[$p] ;
           $postiret = strpos($prenom,"-") ;
           if ($postiret==0) {
               $lePrenom = new Chaine($prenom) ;
               $prenom   = $lePrenom->initialeMajuscule() ;
           } else {
               $avant   = new Chaine(substr($prenom,0,$postiret+1)) ;
               $apres   = new Chaine(substr($prenom,1+$postiret)) ;
               $prenom  = $avant->initialeMajuscule() . $apres->initialeMajuscule() ;
           } # fin si sur postiret
           $prenoms  .= $prenom . " " ;
         } # fin pour chaque prenom
         return trim($prenoms) ;
     
       } # fin de fonction initialeMajusculePrenom
     
     } # fin de classe Chaine
     
     ?>
     

Fichier tripn-personnes.php :


     <?php
     
     include_once("tripn-chaines.php") ;
     
     class Personne {
     
     
       var $nom    = "" ;
       var $prenom = "" ;
     
       ##################################################################
     
       public function __construct($sonNom="",$sonPrenom="") {
     
       ##################################################################
     
         $this->nom    = strtoupper(trim($sonNom)) ;
         $lePrenom     = new Chaine(trim($sonPrenom)) ;
         $this->prenom = $lePrenom->initialeMajusculePrenom() ;
     
       } # fin de fonction __construct
     
       ##################################################################
     
       public function getNom() {
     
       ##################################################################
     
         return trim($this->nom) ;
     
       } # fin de fonction getNom
     
       ##################################################################
     
       public function getPrenom() {
     
       ##################################################################
     
         return trim($this->prenom) ;
     
       } # fin de fonction getPrenom
     
     } # fin de classe Personne
     
     ?>
     

On trouvera ci-dessous le code en PHPunit pour tester les méthodes intéressantes des classes d'objets que nous venons de définir :

Fichier chainesTest.php :


     <?php
     error_reporting(E_ALL | E_NOTICE ) ;
     require 'tripn-chaines.php';
     
     use PHPUnit\Framework\TestCase;
     
     class ChainesTest extends TestCase {
     
         private $chaine1, $chaine2, $chaine3 ;
     
     ## ------------------------------------------------------------------------------
     
     # 1. l'initialisation/instanciation doit produire une chaine vide
     
         public function test_GetChaine() {
             $chaine1 = new Chaine()          ;
             $result  = $chaine1->getChaine() ;
             $this->assertEquals("", $result) ;
         } # fin de fonction test_GetChaine
     
     ## ------------------------------------------------------------------------------
     
     # 2.1 le nombre de mots fonctionne avec des espaces
     
         public function test_NbMots_1() {
             $chaine2 = new Chaine("un test simple")  ;
             $result  = $chaine2->nbmots()            ;
             $this->assertEquals(3, $result)          ;
         } # fin de fonction test_NbMots_1
     
     # 2.2 le nombre de mots ne tient pas compte des espaces de début ou de fin
     
         public function test_NbMots_2() {
     
             $chaine1 = new Chaine("  un test simple") ;
             $result  = $chaine1->nbmots()             ;
             $this->assertEquals(3, $result)           ;
     
     
             $chaine2 = new Chaine("un test simple   ") ;
             $result  = $chaine2->nbmots()              ;
             $this->assertEquals(3, $result)            ;
     
             $chaine3 = new Chaine("  un test simple   ") ;
             $result  = $chaine2->nbmots()                ;
             $this->assertEquals(3, $result)              ;
     
         } # fin de fonction test_NbMots_2
     
     # 2.3 le nombre de mots ne supprime pas la ponctuation
     
         public function test_NbMots_3() {
     
             $chaine1 = new Chaine("php test.php --x=2  un test simple") ;
             $result  = $chaine1->nbmots()                               ;
             $this->assertEquals(6, $result)                             ;
     
             $chaine2 = new Chaine("un. test; simple. Dommage !") ;
             $result  = $chaine2->nbmots()                        ;
             $this->assertEquals(5, $result)                      ;
     
         } # fin de fonction test_NbMots_3
     
     # 2.4 il y a zéro mots dans la chaine vide
     
         public function test_NbMots_4() {
     
             $chaine1 = new Chaine("")       ;
             $result  = $chaine1->nbmots()   ;
             $this->assertEquals(0, $result) ;
     
         } # fin de fonction test_NbMots_4
     
     ## ------------------------------------------------------------------------------
     
     # 3.1 la fonction mot commence à compter à l'indice 1
     
         public function test_Mot_1() {
     
             $chaine1 = new Chaine("le chat mange la souris")  ;
             $result  = $chaine1->mot(1)                       ;
             $this->assertEquals("le", $result)                ;
     
         } # fin de fonction test_Mot_1
     
     # 3.2 le mot numéro 0 (ou un nombre négatif) est la chaine vide
     
         public function test_Mot_2() {
     
             $chaine1 = new Chaine("le chat mange la souris")  ;
             $result  = $chaine1->mot(0)                       ;
             $this->assertEquals("", $result)                  ;
     
             $chaine2 = new Chaine("le chat mange la souris")  ;
             $result  = $chaine2->mot(-4)                      ;
             $this->assertEquals("", $result)                  ;
     
         } # fin de fonction test_Mot_2
     
     # 3.3 le mot numéro x dans p où x est supérieur au nombre de mots de p est la chaine vide
     
         public function test_Mot_3() {
     
             $chaine1 = new Chaine("le chat mange la souris")  ;
             $result  = $chaine1->mot(5)                       ;
             $this->assertEquals("souris", $result)            ;
     
             $chaine2 = new Chaine("le chat mange la souris")  ;
             $result  = $chaine2->mot(10)                      ;
             $this->assertEquals("", $result)                  ;
     
         } # fin de fonction test_Mot_3
     
     ## ------------------------------------------------------------------------------
     
     # 4.1 la fonction initialeMajuscule renvoie vide pour une chaine vide
     
         public function test_initialeMajuscule_1() {
     
             $chaine1 = new Chaine("")                         ;
             $result  = $chaine1->initialeMajuscule()          ;
             $this->assertEquals("", $result)                  ;
     
         } # fin de fonction test_initialeMajuscule_1
     
     # 4.2 la fonction initialeMajuscule retire les espaces de tete et de fin
     
         public function test_initialeMajuscule_2() {
     
             $chaine1 = new Chaine("   voilà")                 ;
             $result  = $chaine1->initialeMajuscule()          ;
             $this->assertEquals("Voilà", $result)             ;
     
         } # fin de fonction test_initialeMajuscule_2
     
     # 4.3 avec la fonction initialeMajuscule seule l'initiale est en majuscules
     
         public function test_initialeMajuscule_3() {
     
             $chaine1 = new Chaine("essai")                    ;
             $result  = $chaine1->initialeMajuscule()          ;
             $this->assertEquals("Essai", $result)             ;
     
             $chaine2 = new Chaine("un essai")                 ;
             $result  = $chaine2->initialeMajuscule()          ;
             $this->assertEquals("Un essai", $result)          ;
     
             $chaine3 = new Chaine("   un essai")              ;
             $result  = $chaine3->initialeMajuscule()          ;
             $this->assertEquals("Un essai", $result)          ;
     
         } # fin de fonction test_initialeMajuscule_3
     
     ## ------------------------------------------------------------------------------
     
     # 5.1 la fonction initialeMajusculePrenom renvoie vide pour une chaine vide
     
         public function test_initialeMajusculePrenom_1() {
     
             $chaine1 = new Chaine("")                         ;
             $result  = $chaine1->initialeMajusculePrenom()    ;
             $this->assertEquals("", $result)                  ;
     
         } # fin de fonction test_initialeMajuscule_1
     
     # 5.2 la fonction initialeMajusculePrenom retire les espaces de tete et de fin
     
         public function test_initialeMajusculePrenom_2() {
     
             $chaine1 = new Chaine("   voilà")                 ;
             $result  = $chaine1->initialeMajusculePrenom()    ;
             $this->assertEquals("Voilà", $result)             ;
     
         } # fin de fonction test_initialeMajusculePrenom_2
     
     # 5.3 avec la fonction initialeMajusculePrenom seule l'initiale est en majuscules
     
         public function test_initialeMajusculePrenom_3() {
     
             $chaine1 = new Chaine("essai")                    ;
             $result  = $chaine1->initialeMajusculePrenom()    ;
             $this->assertEquals("Essai", $result)             ;
     
             $chaine2 = new Chaine("Jean pierre")              ;
             $result  = $chaine2->initialeMajusculePrenom()    ;
             $this->assertEquals("Jean Pierre", $result)       ;
     
             $chaine3 = new Chaine("anne-marie")               ;
             $result  = $chaine3->initialeMajusculePrenom()    ;
             $this->assertEquals("Anne-Marie", $result)        ;
     
         } # fin de fonction test_initialeMajusculePrenom_3
     
     } # fin de classe ChainesTest
     
     ?>
     

Fichier personnesTest.php :


     <?php
     error_reporting(E_ALL | E_NOTICE ) ;
     require 'tripn-personnes.php';
     
     class PersonnesTest extends PHPUnit_Framework_TestCase {
     
         private $pers1, $pers2, $pers3 ;
     
     # 1. l'initialisation/instanciation doit produire deux chaines vides
     
         public function test_Personne_1() {
     
             $pers1   = new Personne()         ;
             $result1 = $pers1->getNom()       ;
             $this->assertEquals("", $result1) ;
     
         } # fin de fonction test_Personne_1
     
         public function test_Personne_2() {
     
             $pers2   = new Personne()         ;
             $result2 = $pers2->getPrenom()    ;
             $this->assertEquals("", $result2) ;
     
         } # fin de fonction test_Personne_2
     
     # 2. le prénom peut être simple ou multiple, sans espaces de début ou de fin
     
         public function test_Prenom1() {
     
             $pers1    = new Personne($nom=" goldman ",$prenom="  JEAN-jacques ")  ;
             $result1  = $pers1->getPrenom()              ;
             $this->assertEquals("Jean-Jacques", $result1)        ;
     
         } # fin de fonction test_Prenom1
     
         public function test_Prenom2() {
     
             $pers2    = new Personne(" lehnon "," john    paul ")  ;
             $result2  = $pers2->getPrenom()                      ;
             $this->assertEquals("John Paul", $result2)           ;
     
         } # fin de fonction test_Prenom2
     
     # 3. le nom peut être simple ou multiple, sans espaces de début ou de fin
     
         public function test_Nom() {
     
             $pers1    = new Personne(" lehnon "," john ")  ;
             $result1  = $pers1->getNom()                   ;
             $this->assertEquals("LEHNON", $result1)        ;
     
             $pers2    = new Personne(" mac cartney "," paul ") ;
             $result2  = $pers2->getNom()                       ;
             $this->assertEquals("MAC CARTNEY", $result2)       ;
     
         } # fin de fonction test_Nom
     
     } # fin de classe PersonnesTest
     
     ?>
     

Comme le montre l'encadré suivant, les 14 tests (23 assertions) pour les chaines et les 5 tests (6 assertions) pour les personnes sont tous validés.


     ## PHPUnit 5.1.3 by Sebastian Bergmann and contributors.
     
     $gh> phpunit chainesTest.php
     
          OK (14 tests, 23 assertions)
     
     $gh> phpunit personnesTest.php
     
          OK (5 tests, 6 assertions)
     
     

Remarque : suivant la version de phpunit utilisée, le code PHP est légèrement différent, comme suit :


     <?php
     error_reporting(E_ALL | E_NOTICE ) ;
     require 'tripn-chaines.php';
     
     ## OK pour PHPUnit 5.1.3
     
     class ChainesTest extends PHPUnit_Framework_TestCase {
     
         private $chaine1, $chaine2, $chaine3 ;
     
     [...]
     

     <?php
     error_reporting(E_ALL | E_NOTICE ) ;
     require 'tripn-chaines.php';
     
     ## ok pour PHPUnit 6.5.5
     
     use PHPUnit\Framework\TestCase;
     
     class ChainesTest extends TestCase {
     
         private $chaine1, $chaine2, $chaine3 ;
     
     [...]
     

On s'intéresse maintenant à la gestion fine des lignes, soit avec deux champs sans séparateur, soit avec séparateur à l'aide des classes Chaine et Personne.

Fichier tripnv2.php :


     <?php
     #   # (gH)   -_-  tripnv2.php  ;  TimeStamp (unix) : 11 Novembre 2017 vers 21:58
     error_reporting(E_ALL | E_NOTICE | E_STRICT ) ;
     
     include_once("tripn-chaines.php") ;
     include_once("tripn-personnes.php") ;
     
     ## 1. vérification des paramètres
     
     if ($argc<2) {
     
       echo "\n" ;
       echo "Appel incorrect du programme. Il faut donner un nom de fichier.\n" ;
       echo "Exemple : php tripnv1.php nomsprenoms4.txt\n\n" ;
       exit(-1)  ;
     
     } ; # fin de si
     
     # pour debug : print_r($argv) ;
     
     $nomFic = $argv[1] ;
     
     if (!file_exists($nomFic)) {
     
       echo "\n" ;
       echo "Exécution impossible, le fichier indiqué, soit $nomFic n'est pas vu.\n " ;
       exit(-2)  ;
     
     } ; # fin de si
     
     ## 2. lecture du fichier
     
     $nomsPrenoms = file_get_contents($nomFic) ;
     
     ## 3. conversion et mise en forme via les méthodes de la classe Chaine et de la classe Personne
     
     # on découpe selon les retours-charriot
     
     $tabNP = preg_split("/\n/",$nomsPrenoms) ;
     
     # on traite les lignes non vides
     # on distingue deux cas : les lignes à deux mots et les lignes avec le séparateur point-virgule
     
     $nbl = 0 ; # nombre de lignes
     $nbp = 0 ; # nombre de personnes
     $taP = array() ; # tableau des personnes
     
     foreach ($tabNP as $laLigne) {
     
       $nbl++ ;
       $ligne = new Chaine($laLigne) ;
     
       if (strlen($laLigne)>0) {
     
         $nbp++ ;
         if ($ligne->nbmots()==2) {
     
           $prenom = $ligne->mot(1) ;
           $nom    = $ligne->mot(2) ;
     
         } else { # on force une ligne à un seul mot à devenir un nom seulement
     
            if ($ligne->nbmots()==1) { $ligne = new Chaine(" ; $laLigne ") ; } ;
     
               $nomPrenom = preg_split("/;/",$ligne->getChaine()) ;
               $prenom    = $nomPrenom[0] ;
               $nom       = $nomPrenom[1] ;
     
         } ; # fin si sur le nombre de mots
     
         $personne  = new Personne($nom,$prenom) ;
         $taP[$nbp] = $personne ;
     
       } ; # fin si sur ligne vide
     
     } ; # fin pour chaque ligne
     
     ## 4. tri des personnes
     
     ## 5. affichage
     
     foreach ($taP as $personne) {
         echo sprintf("%-20s %-20s",$personne->getNom(),$personne->getPrenom())."\n" ;
     } ; # fin si
     
     ?>
     

Il reste enfin à écrire la partie de code pour trier les personnes (partie 4 du code précédent).

Voici ce qu'on doit trouver :


     $gh> php tripnv2.php nomsprenoms4.txt
     
     ZOLA                 Emile
     POQUELIN             Jean Baptiste
     ULMAN                Anne-Aude
     COHN BENDIT          Daniel
     DE MAILLé            Jeanne-Marie
     MARTINET             Jacques
     TINTIN
     

2.3 Script PHP web simple

Pour passer à la première version Web, aucune difficulté, sauf à bien vérifier que l'on transmet bien les noms et prénoms... Voici un exemple de formulaire possible nommé tripnv3_f.php qui peut servir de point d'entrée pour la version Web, où l'utilisateur doit copier/coller ses prénoms/noms dans cet ordre. On laisse au lecteur le soin de compléter le formulaire pour fournir une version :

  • qui permet de télécharger un fichier des noms et prénoms,

  • où la gestion Javascript des boutons est externalisée,

  • qui utilise une technologie AJAX pour afficher les résultats,

  • qui dispose d'un switch si les données sont dans l'ordre noms, prénoms plutôt que prénoms, noms,etc.


     <?php
     #   # (gH)   -_-  tripnv3_f.php  ;  TimeStamp (unix) : 11 Novembre 2017 vers 15:59
     error_reporting(E_ALL | E_NOTICE ) ;
     header('Content-Type "text/html; charset=iso-8859-1"') ;
     ini_set( 'default_charset', 'ISO-8859-1' );
     include("../std7.php") ;
     
     ####################################################################################
     ####################################################################################
     
     # redirection éventuelle de www.info vers forge.info (à cause de LaTeX)
     
     $host = getenv("HTTP_HOST") ; #echo " HOST : $host " ;
     
     if ($host=="www.info.univ-angers.fr") {
     
        $titre  = "liste bien formatée de noms et de prénoms" ;
        $temps  = 0 ;
        $newUrl = "http://forge.info.univ-angers.fr/~gh/Decra/tripnv1_f.php" ;
     
        debutPageRedir($titre,$temps,$newUrl) ;
     
     } ; # fin si
     
     ##################################################################################
     ##################################################################################
     
     debutPage("liste bien formatée de noms et de prénoms","strict") ;
     debutSection() ;
     
     h1("Production d'une liste bien formatée de noms et de prénoms") ;
     
     ####################################################################################
     
     blockquote() ;
     blockquote() ;
     
     p("texte") ;
     echo "Pour obtenir une liste bien formatée de noms et de prénoms, remplissez la zone de texte suivante " ;
     echo " et cliquez sur le bouton ".b("générer").". " ;
     finp() ;
     
     form("tripnv3.php","post") ;
     
     p() ;
     textarea("personnes","cadrejaune",8,60,"personnes") ;
     fintextarea() ;
     finp() ;
     
     p() ;
       nbsp(10) ;
       $xmps = "Gilles HUNAULT\\nJean valjean\\nMarie CURIE\\nPaul Emile ; ViCTOR\\nJean pierre ; PINPIN\\nAnne-marie RIHAN\\nSociété IBMGSA\\nTINtin" ;
       input_submit("&nbsp;exemple&nbsp;","exemple","bouton_fin nou bleu_pastel","onclick='window.document.getElementById(\"personnes\").value=\"$xmps\" ; return false'") ;
       nbsp(10) ;
       input_submit("&nbsp;vider&nbsp;","vider","bouton_fin nou bleu_pastel","onclick='window.document.getElementById(\"personnes\").value=\"\" ; return false'") ;
       nbsp(10) ;
       input_submit("&nbsp;générer&nbsp;","ok","bouton_fin nou vert_pastel") ;
     finp() ;
     
     finform() ;
     
     ## -------------------------------------------------------------------------------------------
     
     p() ;
       echo s_span(" Format conseillé","gbleu")." : une personne par ligne, avec le prénom suivi du nom, comme par exemple ".b("Victor HUGO")."."  ;
     finp() ;
     
     p() ;
      echo " En cas de prénoms ou noms multiples, séparer les prénoms des noms par un point virgule, comme par exemple " ;
      echo " ".b("Jean Marie ; DUPOND DURAND").". " ;
     finp() ;
     
     
     finblockquote() ;
     finblockquote() ;
     
     ####################################################################################
     ####################################################################################
     finSection() ;
     finPage() ;
     
     ?>
     

Dans la mesure où la version cli avancée a permis de définir de nombreux fichiers inclus, la page Web de gestion peut réutiliser de nombreux composants. Voici le code PHP qui correspond au formulaire présenté précédemment.

Fichier tripnv3.php :


     <?php
     error_reporting(E_ALL | E_NOTICE ) ;
     header('Content-Type "text/html; charset=iso-8859-1"') ;
     ini_set( 'default_charset', 'ISO-8859-1' );
     include("../std7.php") ;
     include_once("tripn-chaines.php") ;
     include_once("tripn-personnes.php") ;
     debutPage("DECRA","strict") ;
     debutSection() ;
     #####################################################################
     
     h1("Gestion de noms et prénoms") ;
     
     #####################################################################
     
     blockquote() ;
     
     ## 1. vérification de la présence de la variable "personnnes"
     
     if (!isset($_POST["personnes"])) {
        p("grouge") ;
           echo "Variable \"personnes\" non renseignée. Stop." ;
        finp() ;
        finblockquote() ;
        finSection() ;
        finPage() ;
        exit(-1) ;
     }  ; # fin si
     
     ## 2. vérification que cette variable "personnnes" est non vide
     
     $personnes = trim($_POST["personnes"]) ;
     
     if (strlen($personnes)==0) {
        p("grouge") ;
           echo "Vous n'avez pas rempli le formulaire de la page ".href("tripnv3_f.php").". Stop." ;
        finp() ;
        finblockquote() ;
        finSection() ;
        finPage() ;
        exit(-1) ;
     }  ; # fin si
     
     ## 3. conversion et mise en forme via les méthodes de la classe Chaine et de la classe Personne
     
     $tabNP = preg_split("/\n+/",$personnes) ; # on découpe selon les retours-charriot
     
     #pre("cadre") ;
     #print_r($tabNP) ;
     #finpre() ;
     
     # on traite les lignes non vides
     # on distingue deux cas : les lignes à deux mots et les lignes avec le séparateur point-virgule
     
     $nbl = 0 ; # nombre de lignes
     $nbp = 0 ; # nombre de personnes
     $taP = array() ; # tableau des personnes
     
     foreach ($tabNP as $laLigne) {
     
       $nbl++ ;
       $laLigne = str_replace("\n","",$laLigne) ;
       $laLigne = str_replace("\r","",$laLigne) ;
       if (strlen($laLigne)>0) {
          # pour debug : pre() ; echo "LIGNE : *".$laLigne."*" ;
          $ligne = new Chaine($laLigne) ;
     
     
            $nbp++ ;
            if ($ligne->nbmots()==2) {
     
              $prenom = $ligne->mot(1) ;
              $nom    = $ligne->mot(2) ;
     
            } else { # on force une ligne à un seul mot à devenir un nom seulement
     
               if ($ligne->nbmots()==1) { $ligne = new Chaine(" ; $laLigne ") ; } ;
     
                  $nomPrenom = preg_split("/;/",$ligne->getChaine()) ;
                  $prenom    = $nomPrenom[0] ;
                  $nom       = $nomPrenom[1] ;
     
            } ; # fin si sur le nombre de mots
     
            $personne  = new Personne($nom,$prenom) ;
            $taP[$nbp] = $personne ;
     
       } ; # fin si sur ligne vide
     
     } ; # fin pour chaque ligne
     
     ## 4. tri des personnes
     
     ## 5. affichage XHTML
     
     h2("Voici les personnes vues&nbsp;") ;
     
     table(1,15,"collapse bleu_pastel") ;
     entetesTableau("Numéro Prénom(s) NOM(S)") ;
     $nbPers = 0 ;
     
     foreach($taP as $personne) {
         $nbPers++ ;
         tr() ;
            td("R","jaune_pastel")  ;  echo $nbPers.s_nbsp(3) ;     ; fintd() ;
            td("L","orange_pastel") ;  echo $personne->getPrenom()  ; fintd() ;
            td("L","orange_pastel") ;  echo b($personne->getNom())  ; fintd() ;
         fintr() ;
     } ; # fin pour chaque personne
     
     fintable() ; pvide() ;
     
     finblockquote() ;
     
     #####################################################################
     finSection() ;
     finPage() ;
     ?>
     

Pour disposer des fonctionnalités du php conceptuel utilisé dans les pages ci-dessus, et pour disposer aussi de la feuille de style et autres éléments associés, on pourra télécharger puis décompresser l'archive std7.zip.

2.4 Script PHP web avancé

Il est clair que, sauf à utiliser en maitrisant totalement un framework de développement en PHP comme Joomla, Symphony, ou Laravel, il n'est pas possible en un temps aussi court de développer les fonctionnalités demandées.

2.5 Script PHP web consolidé

Là encore, le niveau de technicité demandé dépasse largement le cadre du T.P.

A titre d'exemple de page web qui produit un fichier PDF à propos d'informations similaires, on pourra consulter la page affichettes de présentation dont le code-source est disponible.

 

2. Conception : maquettes et prototypes pour le calcul de co-occurrences

Faut-il toujours écrire des prototypes ? Dans quel(s) langage(s) ? Comment utiliser la méthode Agile pour le problème ci-dessous, de calcul de co-occurrences ?

On s'intéresse ici à la production d'un tableau dit "tri croisé amélioré" qui comptabilise les croisements de deux variables qualitatives avec des marges correspondant aux pourcentages globaux.

Voici un exemple de fichier de données nommé elf1.txt :

Et le fichier de résultats recherché elf2.txt sachant qu'on s'intéresse aux champs SEXE et ETUD :


                Homme   Femme   pct Etudes
     NR          2       1        6.1 %
     Primaire    1       5       12.2 %
     Secondaire  3      13       32.7 %
     Bac         5       4       18.4 %
     Supérieur   6       9       30.6 %
     pct Sexe   34.7 %  65.3 %
     

On trouvera la description des données à l'adresse ELF.

Comment produire une solution qui fonctionne quelque soit le tableau de données en entrée ? Pour les plus fort(e)s, on produira aussi les histogrammes de fréquences associés, à savoir :

               non su

On réfléchira aux extensions et généralisations possibles. Nous fournissons à ce titre, deux fichiers de configuration possibles, nommés elf2Data.xml et autreCfg.xml.

Fichier elf2Data.xml


     <TRICROISE>
      <variable1>
        <nom>SEXE</nom>
        <modalites>
          <modalite valeur="1">Homme</modalite>
          <modalite valeur="2">Femme</modalite>
        </modalites>
      </variable1>
      <variable2>
        <nom>EtMarital</nom>
        <modalites>
          <modalite valeur="0">Seul</modalite>
          <modalite valeur="1">En couple</modalite>
        </modalites>
      </variable2>
     </TRICROISE>
     

Fichier autreCfg.xml


     <TRICROISE>
      <variable1>
        <nom>nivEtudes</nom>
        <modalites>
          <modalite valeur="0">Sans</modalite>
          <modalite valeur="1">Primaire</modalite>
          <modalite valeur="2">Secondaire</modalite>
          <modalite valeur="3">Bac</modalite>
          <modalite valeur="4">Sup</modalite>
        </modalites>
      </variable1>
      <variable2>
        <nom>clAGE</nom>
        <modalites>
          <modalite valeur="0">Enfant</modalite>
          <modalite valeur="1">Adulte</modalite>
        </modalites>
      </variable2>
     </TRICROISE>
     

Remarque : comme pour la partie PHP, pour éviter de perdre du temps, on rapatriera en local les fichiers-texte des noms et prénoms via wget.

Solution :  

Il n'y a pas de réponse unique à la question « Faut-il toujours écrire des prototypes ? » car cela dépend de la taille du projet, du temps qu'on a pour le réaliser, de sa durée dans le temps...

Utiliser la méthode Agile reviendrait sans doute, une fois qu'on a bien compris le sujet, à développer pas à pas les fonctionnalités de l'application.

Il n'est sans doute pas difficile de comprendre ce qu'il faut calculer et tracer. Par contre, savoir quel langage utiliser pour résoudre simplement et rapidement ce problème (il s'agit d'une petite application) n'est pas si simple car calculer des sommes et des pourcentages est à la portée de tout langage de scripts. À nos yeux, un choix raisonnable semble être celui du langage R car il comporte pour chaque action à exécuter dans ces calculs et mises en forme une fonction prête à l'emploi. A nouveau, il faut penser à écrire un plan de développement, comme par exemple celui défini dans tp1exo2.php.

Si l'on veut généraliser un script pour gérer plus généralement ce type de calcul, la partie facile consiste à rajouter un paramètre correspondant au nom du fichier à traiter. Une partie plus délicate concerne le nom des deux variables, les valeurs numériques et les labels de leurs modalités. Comme cela peut faire beaucoup d'informations à gérer, le mieux est sans doute de mettre ces informations dans un fichier structuré, disons au format XML ou JSON comme ci-dessous.


     <TRICROISE>
      <variable1>
        <nom>nivEtudes</nom>
        <modalites>
          <modalite valeur="0">Sans</modalite>
          <modalite valeur="1">Primaire</modalite>
          <modalite valeur="2">Secondaire</modalite>
          <modalite valeur="3">Bac</modalite>
          <modalite valeur="4">Sup</modalite>
        </modalites>
      </variable1>
      <variable2>
        <nom>clAGE</nom>
        <modalites>
          <modalite valeur="0">Enfant</modalite>
          <modalite valeur="1">Adulte</modalite>
        </modalites>
      </variable2>
     </TRICROISE>
     

Pour les graphiques à produire, à savoir des histogrammes de fréquences (et non pas des histogrammes de classes), on pourra s'inspirer de notre introduction à R, séance 3, exercice 5.

 

       retour au plan de cours  

 

Code-source PHP de cette page ; code Javascript associé.

 

retour gH    Retour à la page principale de   (gH)