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 ZOLAEt 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 BaptistePour 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 : masquer la 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 traitementOn 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 Jeanne1.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 traitementRésultats :
$gh> gawk -f tripnv2.awk nomsprenoms3.txt POQUELIN Jean Baptiste COHN BENDIT Daniel DE MAILL Jeanne-Marie2. 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 TINTIN2.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(" exemple ","exemple","bouton_fin nou bleu_pastel","onclick='window.document.getElementById(\"personnes\").value=\"$xmps\" ; return false'") ; nbsp(10) ; input_submit(" vider ","vider","bouton_fin nou bleu_pastel","onclick='window.document.getElementById(\"personnes\").value=\"\" ; return false'") ; nbsp(10) ; input_submit(" générer ","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 ") ; 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 :
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 : masquer la 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.
Code-source PHP de cette page ; code Javascript associé.
Retour à la page principale de (gH)