Ce site a été réalisé dans le cadre du cours "Programmation et projet encadré 2 " du Master 1 Plurital (INALCO, Paris3 et Paris10). assuré par Serge FLEURY, Jean-Michel DAUBE et Rachid BELMOUHOUB.
Il s’agit de faire la présentation des différents travaux effectués lors des TD. Ces travaux ont pour objectif l’extraction et le traitement des informations contenues dans des fils RSS du journal Le Monde de l’année 2014.
Nos travaux sont répartis en 4 boîtes à outils qui correspondent aux étapes principales du traitement des informations en Perl:
La BAO 1 est l'extraction des informations contenues dans les fils.
La BAO 2 est l'étiquetage de ces informations via TreeTagger et Cordial.
La BAO 3 est l'extraction de patrons syntaxiques à partir des sorties de la BAO 2.
La BAO 4 est la visualisation et filtrage des patrons via le programme patron2graphe.exe.
La première partie du projet consiste à parcourir les répertoires contenant les fils RSS et d'en extraire les titres et les descriptions.
Le programme produit deux fichiers de sortie, un fichier TXT et un fichier XML.
Pour nous faciliter la tâche nous avons ajouté les modules perl XML::RSS et XML::Entities.
XML::RSS permet la manipulation des données RSS. On l'utilise pour pouvoir récupérer le contenu de nos balises.
XML::Entities permet de transformer les entités XML en leurs valeurs littérales. En effet si nous n'effectuons pas un nettoyage sur les fichiers traités, le programme risque de produire des fichiers XML mal formés.
Les fichiers contenant les fils RSS sont rangés dans des répertoires triés par dates.
La fonction parcours_arborescence_fichiers va parcourir le répertoire donné en argument. S'il trouve un répertoire il rentre dedans, s'il trouve un fichier ayant le nom de la rubrique qui a été donnée en deuxième argument, il va extraire ce qui se trouve dans les balisees description et titre.
Pour l'extraction on utilise le module XML::RSS. On fait une boucle pour que dans chaque balise item on récupère le contenu des balises description et titre.Puis on supprime les doublons. Ensuite il faut nettoyer avec XML::Entities pour que les entités s'affichent bien. Il faut aussi supprimer toutes les balises qui se trouvaient dans les balises description et titre.
#! /usr/bin/perl
use Unicode::String qw(utf8);
use XML::RSS;
use XML::Entities;
my $rep="$ARGV[0]";
my $rub="$ARGV[1]";
$rep=~ s/[\/]$//;
# on s'assure que le nom du répertoire ne se termine pas par un "/". attention tjs à l'usage de $ qui est la fin de chaîne
my %dico_titre=() ;
my %dico_description=() ;
#----------------------------------------------------------------------------------------------
# préparation des fichiers de sortie
$dir="SORTIE-$rub";
mkdir $dir;
my $output1="./$dir/SORTIE-$rub.xml";
if (!open (OUT1,">:encoding(utf-8)", $output1)) {die "Pb à l'ouverture du fichier $output1"};
print OUT1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" ;
print OUT1 "<PARCOURS>\n" ;
print OUT1 "<NOM>VALMOND 2015</NOM>\n" ;
print OUT1 "<FILTRAGE>\n" ;
my $output2="./$dir/SORTIE-$rub-UTF8.txt";
if (!open (OUT2,">:encoding(utf-8)", $output2)) {die "Pb à l'ouverture du fichier $output2"};
#-----------------------------------------------------------------------------------------------
$num=0;
&parcours_arborescence_fichiers($rep); #recurse
#---------------------------------------------------------------------------------------------
close (OUT2) ;
print OUT1 "\n</FILTRAGE>\n" ;
print OUT1 "</PARCOURS>\n" ;
close (OUT1) ;
#-------------------------------------------------------------------------------------
#-------------------------------------------------
sub parcours_arborescence_fichiers
{ #créer une variable du répertoire
my $path = shift(@_);
# ouvrir ce dossier
opendir(DIR, $path) or die "can't open $path: $!\n";
#créer une liste qui contient les éléments du répertoire
my @files = readdir(DIR);
closedir(DIR);
# pour chaque élément de cette liste
foreach my $file (@files)
{
# si le rép est . ou .., on passe au suivant
next if $file =~ /^\.\.?$/;
#chemin absolu de l'élément
$file = $path."/".$file;
# si c'est un répertoire
if (-d $file)
{
#recurse
&parcours_arborescence_fichiers($file);
}
# si c'est un fichier
if (-f $file)
{
#si c'est un fihier xml et la taille du fichier n'est pas 0
if (($file=~/0,57-0,64-$rub.+\.xml$/) && (-s $file !=0))
{
#récupère l'encodage de chaque fichier xml
my $rss=new XML::RSS;
eval{$rss->parsefile($file);};
my $encodage=$rss->{'encoding'} ;
print "FILE:$file ---> ENCODAGE : $encodage\n" ;
if ($encodage ne "")
{
foreach my $item (@{$rss->{'items'}})
{
my $description=$item->{'description'};
my $titre=$item->{'title'};
#élimine les doublons
if (!(exists $dico_titre{$titre}) and !(exists $dico_description{$description}))
{
$dico_titre{$titre}++;
$dico_description{$description}++;
# faut pas oublier de les enregistrer si jamais vu
# nettoyer les données pour que les entités s'affichent bien
$titre = XML::Entities::decode('all', $titre);
$description = XML::Entities::decode('all', $description);
# la fonction uc(string) : transforme en majuscule
if (uc($encodage) ne "UTF-8")
{
# si l'encodage initial n'est pas UTF-8, on transcode les chaînes de caractères en UTF-8
utf8($titre);
utf8($description);
}
#supprime balises existantes
$description=~ s/<[^>]+>//g;
print OUT2 $titre.".\n".$description."\x{00A7}\n" ;
#appel de la fonction treetagger
($titre, $description)=&treetagger($titre, $description);
$num++ ;
print OUT1 "<item num=\"$num\">\n<titre>$titre</titre>\n<description>$description</description>\n</item>\n";
}
# si c'est déjà vu, on fait rien avec
}
close (FILE);
}
}
}
}
}
sub treetagger
{
my ($titre, $description)=@_;
my $codage="UTF-8";
my $tmptag="texte_a_etiqueter.txt" ;
# fichier temporaire
open (TMPFILE, ">:encoding(utf-8)",$tmptag);
print TMPFILE $titre ;
close (TMPFILE);
system ("perl ./tokenise-utf8.pl $tmptag | ./tree-tagger -token -lemma -no-unknown -sgml french-utf8.par > treetagger.txt ");
system ("perl ./treetagger2xml-utf8.pl treetagger.txt $codage");
open (OUT, "<:encoding(utf-8)", "treetagger.txt.xml");
my $first_line=<OUT>;
my $titre_etiquete="";
while (my $deux=<OUT>)
{
$titre_etiquete.=$deux;
}
close (OUT);
open (TMPFILE, ">:encoding(utf-8)",$tmptag);
print TMPFILE $description ;
close (TMPFILE);
system ("perl ./tokenise-utf8.pl $tmptag | ./tree-tagger -token -lemma -no-unknown -sgml french-utf8.par > treetagger.txt ");
system ("perl ./treetagger2xml-utf8.pl treetagger.txt $codage");
open (OUT, "<:encoding(utf-8)", "treetagger.txt.xml");
my $first_line=<OUT>;
my $description_etiquete="";
while (my $trois=<OUT>)
{
$description_etiquete.=$trois;
}
close (OUT);
return ($titre_etiquete, $description_etiquete);
}
#/usr/bin/perl
#use strict;
use Unicode::String qw(utf8);
my %dico_titre=() ;
my %dico_description=() ;
my $rep="$ARGV[0]";
# on s'assure que le nom du répertoire ne se termine pas par un "/", si oui, on supprime
my $rub="$ARGV[1]";
$rep=~ s/[\/]$//;
#----------------------------------------
$dir="SORTIE-$rub";
mkdir $dir;
my $output1="./$dir/SORTIE-$rub.xml";
if (!open (FILEOUT1,">:encoding(utf-8)",$output1)) { die "Pb a l'ouverture du fichier $output1"};
#SORTIE XML
print FILEOUT1 "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>\n";
print FILEOUT1 "<PARCOURS>\n";
print FILEOUT1 "<NOM>ZHAI 2015</NOM>\n";
print FILEOUT1 "<FILTRAGE>\n";
#SORTIE TXT
my $output2="./$dir/SORTIE-$rub-UTF8.txt";
if (!open (FILEOUT2,">:encoding(utf-8)",$output2)) { die "Pb a l'ouverture du fichier $output2"};
#----------------------------------------
my $nombre_item=0;
&parcoursarborescencefichiers($rep); #recurse! s'arrête quand il trouve fichier
#----------------------------------------
#SORTIE TXT : fin
close(FILEOUT2);
#SORTIE XML : fin
print FILEOUT1 "\n<FILTRAGE>\n";
print FILEOUT1 "</PARCOURS>\n";
close(FILEOUT1);
#----------------------------------------------
sub parcoursarborescencefichiers
{
my $path = shift(@_);
opendir(DIR, $path) or die "can't open $path: $!\n";
my @files = readdir(DIR);
closedir(DIR);
foreach my $file (@files) # Vérification statut pour chacun des éléments contenus dans rep (rep ou fichier)
{
next if $file =~ /^\.\.?$/;
$file = $path."/".$file;
if (-d $file)
{
&parcoursarborescencefichiers($file); #recurse! #si élément = rep, on relance parcours
}
if (-f $file)
{
if (($file=~/0,.+\.xml$/) and (-s $file !=0))
{
open (FILE, $file);
my $line = <FILE>; # récupérer la première ligne
close(FILE);
$line =~/encoding=['"]([^\"\']+)['"]/;
my $encodage = $1;
if($encodage ne "")
{
open (FILE, "<:encoding($encodage)", $file);
my $chainecomplete = "";
while ($line = <FILE>)
{
chomp $line;
$chainecomplete = $chainecomplete." ".$line; #on ramène tout sur une seule ligne
}
$chainecomplete =~ s/> +</></g; #supprimer les blancs entre balises
while ($chainecomplete =~ /<item><title>([^<]*)<\/title>.*?<description>([^<]*)<\/description>/g)
{
my $titre = $1;
my $description = $2;
if (!(exists $dico_titre{$titre}) and !(exists $dico_description{$description}))
{
$dico_titre{$titre}++;
$dico_description{$description}++;
$titre=&nettoyage($titre);
$description=&nettoyage($description);
if (uc($encodage) ne "UTF-8")
{
utf8($titre);
utf8($description);
}
$description=~ s/<[^>]+>//g;
print FILEOUT2 $titre."\n".$description."\n";
($titre,$description)=&etiquetage($titre,$description);
$nombre_item++;
print FILEOUT1 "<item num=\"$nombre_item\">\n<titre>$titre</titre>\n<description>$description</description>\n</item>\n";
}
close (FILE);
}
}
}
}
}
}
sub nettoyage
{
my $chainetrouvee=shift @_; # my chainetrouvee est locale, n'existe qu'ici;
$chainetrouvee=~s/'/'/g;
$chainetrouvee=~s/'/'/g;
$chainetrouvee=~s/#38;#34;/"/g;
$chainetrouvee=~s/"/"/g;
$chainetrouvee=~s/#233;/é/g;
$chainetrouvee=~s/#234;/è/g;
$chainetrouvee=~s/</</g;
$chainetrouvee=~s/>/>/g;
$chainetrouvee=~s/&/&/g;
$chainetrouvee=~s/<a href[^>]+>//g;
$chainetrouvee=~s/<img[^>]+>//g;
$chainetrouvee=~s/<\/a>//g;
$chainetrouvee=~s/<[^>]+>//g;
$chainetrouvee=~s/[/[/g ;
$chainetrouvee=~s/]/]/g ;
$chainetrouvee=~s/&/&/g ;
$chainetrouvee=~s/e\x{0300}/è/g;
$chainetrouvee=~s/\x{0119}/ę/g;
$chainetrouvee=~s/\x{2192}/→/g;
$chainetrouvee=~s/\x{201c}/"/g;
$chainetrouvee=~s/âœ/"/g;
$chainetrouvee=~s/\x{201d}/"/g;
$chainetrouvee=~s/â/"/g;
$chainetrouvee=~s/âÅ/"/g;
$chainetrouvee=~s/\x{2019}/'/g;
$chainetrouvee=~s/\x{2018}/'/g;
$chainetrouvee=~s/\x{2013}/-/g;
$chainetrouvee=~s/\x{2014}|â/-/g;
$chainetrouvee=~s/\x{2212}/-/g;
$chainetrouvee=~s/\x{2026}/.../g;
$chainetrouvee=~s/\x{0153}/oe/g;
$chainetrouvee=~s/\x{0152}/Œ/g;
$chainetrouvee=~s/\x{0096}/-/g;
$chainetrouvee=~s/\x{009C}/oe/g;
$chainetrouvee=~s/\x{0093}/oe/g;
$chainetrouvee=~s/\x{200a}/ /g;
$chainetrouvee=~s/\x{200b}/ /g;
$chainetrouvee=~s/\x{305a}\x{305a}\x{3063}\x{3068}/zuzutto/g;
$chainetrouvee=~s/ÅÅ/oe/g;
$chainetrouvee=~s/Å/oe/g;
$chainetrouvee=~s/&/and/g;
$chainetrouvee=~s/"/"/g;
$chainetrouvee=~s/\x{20ac}/€/g;
$chainetrouvee=~s/\x{2009}/ /g; #espace court
$chainetrouvee=~s/\x{fffd}/�/g;
$chainetrouvee=~s/<.*?>//g;
$chainetrouvee=~s/<p> \n<\/p>//g;
$chainetrouvee=~s/<[^<]*>//g;
return $chainetrouvee;
}
sub etiquetage
{
my ($titre, $description)=@_;
my $codage="UTF-8";
my $tmptag="texte_a_etiqueter.txt" ;
# fichier temporaire
open (TMPFILE, ">:encoding(utf-8)",$tmptag);
print TMPFILE $titre ;
close (TMPFILE);
system ("perl ./tokenise-utf8.pl $tmptag | ./tree-tagger -token -lemma -no-unknown -sgml french-utf8.par > treetagger.txt ");
system ("perl ./treetagger2xml-utf8.pl treetagger.txt $codage");
open (OUT, "<:encoding(utf-8)", "treetagger.txt.xml");
my $first_line=<OUT>;
my $titre_etiquete="";
while (my $deux=<OUT>)
{
$titre_etiquete.=$deux;
}
close (OUT);
open (TMPFILE, ">:encoding(utf-8)",$tmptag);
print TMPFILE $description ;
close (TMPFILE);
system ("perl ./tokenise-utf8.pl $tmptag | ./tree-tagger -token -lemma -no-unknown -sgml french-utf8.par > treetagger.txt ");
system ("perl ./treetagger2xml-utf8.pl treetagger.txt $codage");
open (OUT, "<:encoding(utf-8)", "treetagger.txt.xml");
my $first_line=<OUT>;
my $description_etiquete="";
while (my $trois=<OUT>)
{
$description_etiquete.=$trois;
}
close (OUT);
return ($titre_etiquete, $description_etiquete);
}
La deuxième partie du projet a pour but de faire un étiquetage morpho-syntaxique des fichiers de sortie de la BAO1. Nous utilisons deux méthodes pour faire l’étiquetage : TreeTagger et le logiciel Cordial.
TreeTagger est un outil qui peut être utilisé en ligne de commande.
tree-tagger [options] <parametres> <textein> <texteout>
IL faut aussi mentionner le chemin du fichier de paramètre de la langue que l'on traite.
Dans notre cas on utilise le fichier french-utf8.par.
- token pour la forme du mot tel qu’il apparaît dans le texte
- lemma pour le lemme
- sgml pour ne pas annoter des annotations SGML, soit des lignes qui commencent par le '<' et finissent par le '>'
- no-unknown pour supprimer tous les unknowns
perl treetagger/cmd/tokenize.pl sortie.txt | treetagger/bin/tree-tagger treetagger/lib/french-utf8.par -lemma -token -no-unknown > test.txt
Cette fonction prend en entrée les titres et les descriptions récupérés.
TreeTagger va annoter dans un fichier temporaire l'information. Ensuite nous utilisons un programme qui va permettre de transformer le résultat tabulaire de TreeTagger au format XML.
<?xml version="1.0" encoding="utf-8" ?>
<PARCOURS>
<NOM>ZHAI 2015</NOM>
<FILTRAGE>
<item num="1">
<titre><article>
<element><data type="type">NOM</data><data type="lemma">marché</data><data type="string">Marchés</data></element>
<element><data type="type">ADJ</data><data type="lemma">financier</data><data type="string">financiers</data></element>
<element><data type="type">PUN</data><data type="lemma">:</data><data type="string">:</data></element>
<element><data type="type">DET:ART</data><data type="lemma">le</data><data type="string">le</data></element>
<element><data type="type">NOM</data><data type="lemma">scandale</data><data type="string">scandale</data></element>
<element><data type="type">PRP:det</data><data type="lemma">du</data><data type="string">du</data></element>
<element><data type="type">NAM</data><data type="lemma">Forex</data><data type="string">Forex</data></element>
<element><data type="type">VER:pres</data><data type="lemma">s'étend</data><data type="string">s'étend</data></element>
<element><data type="type">PRP</data><data type="lemma">à</data><data type="string">à</data></element>
<element><data type="type">NAM</data><data type="lemma">Hongkong</data><data type="string">Hongkong</data></element>
</article>
</titre>
Cordial est un outil d'étiquetage morpho-syntaxique qui dispose d'une interface graphique. Les fichiers d'entrée doivent être encodés en ISO-8859-1.
Les paramètres d'étiquetage peuvent être ajustés grâce à l'interface graphique.
Les fichiers de sortie sont au format ".cnr" ce qui donne un résultat tabulaire.
Centrafrique | Centrafrique | NPFS |
: | : | PCTFORTE |
l' | le | DETDMS |
UE | ue | NCI |
lance | lancer | VINDP3S |
A la sortie de BAO2, pour chaque rubrique, on obtient des fichiers étiquetés par Cordial en format txt et par Treetagger reformatés en xml. Pour la BAO3, on va prendre ces fichiers en entrée, et extraire des patrons selon des patterns morpho-syntaxiques que l'on veut, dans notre cas, on a choisi VER DET NOM, VER PRP NOM, NOM PRP NOM, NOM ADJ.
Voici les structures de ces deux formats d'étiquetage :
Les | Le | DETDPIG |
autorités | autorité | NCFP |
bancaires | bancaire | ADJPIG |
allemande | allemand | NCFS |
, | , | PCTFAIB |
américaine | américain | ADJFS |
, | , | PCTFAIB |
singapourienne | singapourien | ADJFS |
et | et | COO |
suisse | suisse | ADJSIG |
ont | avoir | VINDP3P |
ouvert | ouvrir | VPARPMS |
parallèlement | parallèlement | ADV |
des | un | DETDPIGenquêtes |
enquêtes | enquête | NCFP |
sur | sur | PREP |
ce | ce | DETDEM |
scandale | scandale | NCMS |
où | où | PRI |
<?xml version="1.0" encoding="utf-8" ?>
<PARCOURS>
<NOM>ZHAI 2015</NOM>
<FILTRAGE>
<item num="1">
<titre>
<article>
<element><data type="type">NOM</data><data type="lemma">marché</data><data type="string">Marchés</data></element>
<element><data type="type">ADJ</data><data type="lemma">financier</data><data type="string">financiers</data></element>
<element><data type="type">PUN</data><data type="lemma">:</data><data type="string">:</data></element>
<element><data type="type">DET:ART</data><data type="lemma">le</data><data type="string">le</data></element>
<element><data type="type">NOM</data><data type="lemma">scandale</data><data type="string">scandale</data></element>
<element><data type="type">PRP:det</data><data type="lemma">du</data><data type="string">du</data></element>
<element><data type="type">NAM</data><data type="lemma">Forex</data><data type="string">Forex</data></element>
<element><data type="type">VER:pres</data><data type="lemma">s'étend</data><data type="string">s'étend</data></element>
<element><data type="type">PRP</data><data type="lemma">à</data><data type="string">à</data></element>
<element><data type="type">NAM</data><data type="lemma">Hongkong</data><data type="string">Hongkong</data></element>
</article>
</titre>
<description>
<article>
<element><data type="type">DET:ART</data><data type="lemma">le</data><data type="string">Les</data></element>
<element><data type="type">NOM</data><data type="lemma">autorité</data><data type="string">autorités</data></element>
<element><data type="type">ADJ</data><data type="lemma">bancaire</data><data type="string">bancaires</data></element>
<element><data type="type">ADJ</data><data type="lemma">allemand</data><data type="string">allemande</data></element>
<element><data type="type">PUN</data><data type="lemma">,</data><data type="string">,</data></element>
<element><data type="type">ADJ</data><data type="lemma">américain</data><data type="string">américaine</data></element>
</article>
</description>
On peut voir que dans le fichier de sortie de Cordial, les éléments Token, Lemma et POS sont séparés par la tabulation. Dans celui de Treetagger, les éléments POS, Lemma et Token sont entourés de balise et leur nature est la valeur de l'attribut type. Donc ces deux formats sont bien structurés, et à partir de cela on peut appliquer les programmes pour extraire les patrons morpho-syntaxiques de façon automatique.
Pour atteindre ce but, on est équipé de trois programmes en perl écrits par nos trois professeurs.
Le premier est écrit par Monsieur Belmouhoub, il prend en entrée comme le premier argument le fichier de sortie de Treetagger, et le deuxième le fichier de patterns, où on met un pattern par ligne. Pour chacun de ces patterns, il va générer un fichier correspondant qui contient des patrons extraits.
D'abord, il faut installer le module perl XML::LibXML pour après l'utiliser dans ce programme. Le coeur dans ce programme : comment construire le chemin XPATH pour extraire le contenu textuel de certains noeuds dans le fichier xml selon le pattern décrit. Et aussi pour qu'on puisse modifier le fichier pattern sans toucher le programme.
Prenon comme exemple le pattern NOM ADJ, pour afficher tous les noms de ce pattern :
//element[data[@type="type"][contains(text(), 'NOM')]][following-sibling::element[1][contains(data[1], "ADJ")]]/data[3]
et notre but c'est que le programme puisse écrire ces chemins complexes automatiquement.
Instancier un nouvel objet par l'évocation du méthode :
use strict;
use utf8;
use XML::LibXML;
# Définition globale des encodage d'entrée et sortie du script à utf8
binmode STDIN, ':encoding(utf8)';
binmode STDOUT, ':encoding(utf8)';
# On vérifie le nombre d'arguments de l'appel au script ($0 : le nom du script, $#ARGV : compter les arguments : 0 1 --> 1 )
if($#ARGV!=1){print "usage : perl $0 fichier_tag fichier_motif";exit;}
# Enregistrement des arguments de la ligne de commande dans les variables idoines/appropriées
my $tag_file= shift @ARGV; # ex. SORTIE-3208.xml
my $patterns_file = shift @ARGV; # patterns.txt
# création de l'objet XML::LibXML pour explorer le fichier de sortie tree-tagger XML
my $xp = XML::LibXML->new(XML_LIBXML_RECOVER => 2);
$xp->recover_silently(1);
# charger pour le $xp le fichier $tag_file, à sa clé "location". par l'invocation de la méthode load_xml
my $dom = $xp->load_xml( location => $tag_file );
my $root = $dom->getDocumentElement();
my $xpc = XML::LibXML::XPathContext->new($root);
Pour chaque ligne du pattern, appeler la procédure extract_pattern, et dans celle-ci, appeler la procédure construit_XPath
# routine de construction des chemins XPath
sub construit_XPath{
# On récupère la ligne du motif lue à la ligne courante
my $local_ligne=shift @_;
# initialisation du chemin XPath
my $search_path="";
chomp($local_ligne);
$local_ligne=~ s/\r$//;
# Construction au moyen de la fonction split d'un tableau dont chaque élément a pour valeur un élément du motif recherché
my @tokens=split(/ /,$local_ligne);
# On commence ici la construction du chemin XPath
# Ce chemin correspond au premier noeud "element" de l'arbre XML qui répond au motif cherché
$search_path="//element[contains(data[\@type=\"type\"],\"$tokens[0]\")]";
# Initialisation du compteur pour la boucle de construction du chemin XPath
my $i=1;
while ($i < $#tokens) {
$search_path.="[following-sibling::element[1][contains(data[\@type=\"type\"],\"$tokens[$i]\")]";
$i++; }
my $search_path_suffix="]";
# ex. NOM PRP NOM : $#tokens=2. $i=2 après le while, mais $search_path s'arrête à l'avant-dernier soit PRP
# on utilise l'opérateur x qui permet de répéter la chaine de caractère à sa gauche autant de fois que l'entier à sa droite,
# soit $i fois $search_path_suffix
$search_path_suffix=$search_path_suffix x $i;
# pour compléter le crochet fermant pour following-sibling, par ex NOM PRP NOM, faut 2 crochet fermants à la fin
# le chemin XPath final :
$search_path.="[following-sibling::element[1][contains(data[\@type=\"type\"],\"$tokens[$#tokens]\")]".$search_path_suffix;
# print "search_path : $search_path\n";
# on renvoie à la procédure appelante le chemin XPath et le tableau des éléments du motif
return ($search_path,@tokens);
}
# routine d'extraction du motif
sub extract_pattern{
# On récupère la ligne du motif parcourue
my $ext_pat_ligne= shift @_;
# @_ --> while (my $ligne = <PATTERNSFILE>) { &extract_pattern($ligne); }
my ($search_path,@tokens) = &construit_XPath($ext_pat_ligne);
# définition du nom du fichier de résultats pour le motif en utilisant la fonction join
my $match_file = "res_extract-".join('_', @tokens).".txt";
# Ouverture du fichiers de résultats encodé en UTF-8
open(MATCHFILE,">$match_file") or die "can't open $match_file: $!\n";
# Parcours des noeuds du ficher XML correspondant au motif, au moyen de la méthode findnodes
# qui prend pour argument le chemin XPath construit précédement avec la fonction "construit_XPath"
my @nodes=$root->findnodes($search_path);
foreach my $noeud ( @nodes) {
# Initialisation du chemin XPath relatif du noeud "data" contenant
# la forme correspondant au premier élément du motif
# Ce chemin est relatif au premier noeud "element" du bloc retourné
# et pointe sur le troisième noeud "data" fils du noeud "element"
# en l'identifiant par la valeur "string" de son attribut "type"
my $form_xpath="";
$form_xpath="./data[\@type=\"string\"]";
# Initialisation du compteur pour la boucle d'extraction des formes correspondant
# aux éléments suivants du motif
my $following=0;
# Recherche du noeud data contenant la forme correspondant au premier élément du motif
# au moyen de la fonction "find" qui prend pour arguments:
# 1. le chemin XPath relatif du noeud "data"
# 2. le noeud en cours de traitement dans cette boucle foreach
# la fonction "find" retourne par défaut une liste de noeuds, dans notre cas cette liste
# ne contient qu'un seul élément que nous récupérons avec la fonction "get_node"
# enfin nous en imprimons le contenu textuel au moyen de la méthode string_value
print MATCHFILE $xpc->findvalue($form_xpath,$noeud);
# Boucle d'extraction des formes correspondant aux éléments suivants du motif
###################################
# On descend dans chaque noeud element du bloc
while ( $following < $#tokens) {
# Incrémentation du compteur $following de cette boucle d'extraction des formes
$following++;
# Construction du chemin XPath relatif du noeud "data" contenant
# la forme correspondant à l'élément suivant du motif
# Notez bien l'utilisation du compteur $following tant dans la condition de la boucle ci-dessus
# que dans la construction du chemin relatif XPath
my $following_elmt="following-sibling::element[".$following."]";
$form_xpath=$following_elmt."/data[\@type=\"string\"]";
# Impression du contenu textuel du noeud data contenant la forme correspondant à l'élément suivant du motif
print MATCHFILE " ",$xpc->findvalue($form_xpath,$noeud);
}
print MATCHFILE "\n";
}
close(MATCHFILE);
}
Le deuxième programme est proposé par Monsieur Daube, il prend en entrée deux arguments, dans l'ordre le fichier de patterns et le fichier de sortie de Cordial.
L'idée est que pour chaque ligne de pattern, on lit le fichier de sortie de Cordial ligne par ligne, en considérant la ponctuation comme une frontière de syntagme, jusqu'au moment où on rencontre une ponctuation faible ou forte. Et avant cela, on split la ligne au niveau de la tabulation et met les tokens et les POS dans deux listes différentes.
Entre l'intervalle de deux ponctuations, on va reconstruire la chaîne par la fonction join("séparateur", @liste) puis voir si on va garder les tokens récupérés selon que leur POS (ou une partie de leur POS) correspondent ou pas à notre pattern traité. Si oui, on les imprime, sinon, on continue le parcours.
while (my $ligne=<CORDIAL>)
{
chomp ($ligne) ;
$ligne=~s/\r$//g ;
if ($ligne!~/PCT/)
{
my @LISTE = split(/\t/, $ligne); # ("token", "lemme", "POS")
#print "PATRON LU <$terme> : LIGNE LUE <@LISTE>\n" ;
push (@POS, $LISTE[2]);
push (@TOKEN, $LISTE[0]);
}
else
{
# on est arrivé sur une PCT, on va la traiter
# voir ce qui est trouvé entre l'intervalle des 2 PCT
# transformer une liste en un scalaire : join("séparateur", @liste)
# si match, imprimer
my $pos=join(" ", @POS) ;
my $token=join(" ", @TOKEN) ;
while ($pos=~/$terme/g)
# parcourir ce scalaire, chercher globalement avec le "g"
# tant que le $pos contiennent le $terme cherché
{
my $blanc_terme=0;
while ($terme=~/\s/g)
{
$blanc_terme++ ;
# tant qu'on rencontre un espace dans le terme, incrémente le $blanc_terme
}
my $avant_corr=$` ;
# le string avant la partie de match avec le $terme
my $cpt_blanc=0;
while ($avant_corr=~/\s/g)
{
$cpt_blanc++ ;
}
# compte le nombre de blanc avant la correspondance --> indice pour token
# une autre façon pour compter le nbr d'espaces dans une variable : $cpt=()=$avant_corr~/ /g ;
for (my $i=$cpt_blanc; $i<=$cpt_blanc+$blanc_terme; $i++)
# sauter la partie non correspondante dans le $token, imprimer juste la partie du match
{
print OUT $TOKEN[$i]." " ;
}
print OUT "\n" ;
}
# c'est fini pour la recherche du match, on vide ces 2 listes avant de recommencer
@POS=() ;
@TOKEN=() ;
}
}
Le troisième programme est réalisé par Monsieur Fleury, cette fois, il demande seulement un argument, soit le fichier de Cordial soit celui de Treetagger. Mais pour chaque pattern choisi et selon le format de fichier traité, il faut modifier le programme.
Dans ce programme, on parcourt le fichier d'une nouvelle façon, d'abord, on met tout le fichier dans une liste, puis on parcourt cette liste en supprimant le premier élément, on vérifie si cette ligne contient le premier élément du pattern par l'expression régulière, si oui, on garde le Token par le regroupement en parenthèses. Puis on regarde la ou les lignes suivantes selon la longueur du pattern, fait la concaténation avec les éléments retrouvés s'ils répondent à notre besoin. Si non, on entrera pas dans ces conditions imbriquées, et on continue le parcours.
#! usr/bin/perl
open(FILE,"$ARGV[0]");
# le patron cherché ici est du type NOM PREP NOM;
my @lignes=<FILE>;
# lire un fichier en entier, insérer dans une liste
close(FILE);
while (@lignes) {
# tant qu'il y a encore des éléments restants dans la liste
my $ligne=shift(@lignes);
# chaque fois, enlever le premier élément de la liste et le conserver dans $ligne, puis y a un décalage des indices dans la liste
chomp $ligne;
# n'oublie pas de supprimer le retour à la ligne pour que le match marche
my $sequence="";
my $longueur=0;
# longueur du patron, aussi des extractions attendues
if ( $ligne =~ /^([^\t]+)\t[^\t]+\tNC.*/)
{
my $forme=$1;
$sequence.=$forme;
$longueur=1;
my $nextligne=$lignes[0];
# maintenant la ligne suivante de $ligne est $lignes[0]
if ( $nextligne =~ /^([^\t]+)\t[^\t]+\tPREP/)
{
my $forme=$1;
# chaque fois, on rédéclare la variable $forme
$sequence.=" ".$forme;
$longueur=2;
my $nextligne2=$lignes[1];
if ( $nextligne2 =~ /^([^\t]+)\t[^\t]+\tNC.*/)
{
my $forme=$1;
$sequence.=" ".$forme;
$longueur=3;
}
}
}
if ($longueur == 3) {
print $sequence."\n";
}
}
Grâce à l'étape BAO3, pour chaque rubrique, on a extrait des patrons syntaxiques dont les patterns sont : VER DET NOM, VER PRP NOM, NOM PRP NOM et NOM ADJ.
L’objectif de cette dernière BAO consiste à visualiser ces patrons syntaxiques et de réaliser une analyse textométrique. Pour cela, on va utiliser le programme patron2graphe.exe donné par Monsieur Fleury, qui réalise en fait la même fonctionnalité dans son logiciel Trameur.
./patron2graphe.exe "encodage" extraction_des_patrons.txt
./patron2graphe.exe "iso-8859-1" res_extract-VER_DET_NOM
./patron2graphe.exe "encodage" extraction_des_patrons.txt motif.txt
Le fichier motif est de forme : MOTIF=\bpolit, on peut y ajouter des expressions régulières pour chercher plusieurs motifs en même temps.
Pour les motifs, on a choisi ceux-ci : \bpolit, \bchômage, \beurop, \bdemander, \bpris, \bfinale
et voici dans des rubriques différentes les graphes présentés par ce programme : On peut voir clairement que dans des rubriques différentes, les patrons extraits sont spécifiés dans leur domaine, on peut aussi savoir leur nombre de fréquence par le chiffre sur les flèches.
chômage : les adjectifs qualifiés concernent les pays : les Etats-unis, l'Allemagne et l'Angleterre. On a parlé du chômage massif et stable, et dans le graphe NOM PRP NOM, les mots du mois appraissent toujours, et le "chômage des seniors" est même plus fréquent que celui des jeunes.
politique : étant nom ou adjectif, le mot politique est beaucoup utilisé dans la rubrique Économie, où le syntagme "politique monétaire" est le plus fréquent.
demander : le pattern VER-DET-NOM montre que le verbe est seulement utilisé avec les termes du domaine économique.
europ : avec le pattern NOM-ADJ, on a trouvé de nombreux patrons avec le mot "européen" qui parle de tous les aspects sur ce continent, on a aussi trouvé quelques syntagmes sur le mot "europhobe" qui sont imprévus.
politique : le pattern NOM-ADJ montre que le mot "politique" est beaucoup suivie par des mots de nationalité et de sujets, on a aussi trouvé deux adjectifs péjoratifs : irresponsable et hostile.
chômage : le graphe du pattern NOM-PRP-NOM ressemble beaucoup à celui de la rubrique Europe
pris : les syntagmes "pris pour cible(s)" et "pris en charge" sont les plus fréquents
politique : "politique antidopage", "politique répressive" sont très spécifiques dans le domaine sportif
finale : beaucoup plus utilisé que dans les autres rubriques
pris : on voit aussi beaucoup plus de ses cooccurrences comme c'est un mot de sens large