Présentation de notre projet

Bienvenue sur notre Projet Boîte à Outils

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.

Bao 1

Extraction des informations contenues dans les fils.

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.

PARCOURS DE L'ARBORESCENCE

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);
} 

on a aussi une autre façon d’extraction via les expressions régulières et voici le code

#/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/&#39;/'/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);  
}

Bao 2

Etiquetage des informations via TreeTagger et Cordial.

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.

La syntaxe de TreeTagger est la suivante:

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.

Dans notre script nous utilisons les paramètres :

- 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

Pour l'annotation avec TreeTagger nous avons ajouté une fonction au script de la BAO1.

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.

La première colonne est le token, la deuxième est lemme, le troisième est la catégorie grammaticale.

Centrafrique Centrafrique NPFS
: : PCTFORTE
l' le DETDMS
UE ue NCI
lance lancer VINDP3S

BAO3

extraction de patrons syntaxiques

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 :

Voici les structures de ces deux formats d'étiquetage :

étiquetage_cordial :

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
PRI

étiquetage_Treetagger :

<?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.

Étapes importantes :

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

code de fonction 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);
}

code de fonction extract_pattern:

# 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

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.

code de parcours de fichier de sortie Cordial :

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

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.

code en entier pour traiter le fichier de sortie Cordial, pattern NOM PRP NOM :

#! 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";
    }
}

Bao 4

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.

La commande pour l'utiliser sous windows :

./patron2graphe.exe "encodage" extraction_des_patrons.txt 

Par exemple, on le lance pour le fichier de patrons VER DET NOM de la rubrique Sport :

./patron2graphe.exe "iso-8859-1" res_extract-VER_DET_NOM
sans_motif.jpg

On peut voir que les patrons sont de quantité énorme, donc si on veut étudier les cooccurrences de quelques motifs, on doit ajouter un argument pour le programme :

./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.

Rubrique économie :

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.

nom_adj_chomage.jpg
nom_prp_nom_chomage.jpg

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.

nom_adj_politique.jpg
nom_prp_nom_politique.jpg

demander : le pattern VER-DET-NOM montre que le verbe est seulement utilisé avec les termes du domaine économique.

ver_det_nom_demander.jpg

Rubrique europe :

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.

nom_adj_chomage.jpg
nom_prp_nom_europ.jpg
nom_prp_nom_chomage.jpg
ver_prp_nom_pris.jpg
ver_det_nom_demander.jpg

Rubrique international :

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.

nom_adj_politique.jpg

chômage : le graphe du pattern NOM-PRP-NOM ressemble beaucoup à celui de la rubrique Europe

nom_prp_nom_chomage.jpg

pris : les syntagmes "pris pour cible(s)" et "pris en charge" sont les plus fréquents

ver_prp_nom_pris.jpg
nom_prp_nom_europ.jpg
ver_det_nom_demander.jpg

Rubrique la une :

nom_adj_europ.jpg
nom_adj_gouvernement.jpg
nom_adj_polit.jpg
nom_prp_nom_chomage.jpg
nom_prp_nom_finale.jpg
ver_det_nom_demander.jpg
ver_prp_nom_pris.jpg

Rubrique sport :

politique : "politique antidopage", "politique répressive" sont très spécifiques dans le domaine sportif

nom_adj_politique.jpg

finale : beaucoup plus utilisé que dans les autres rubriques

nom_prp_nom_finale.jpg

pris : on voit aussi beaucoup plus de ses cooccurrences comme c'est un mot de sens large

ver_det_nom_pris.jpg
ver_prp_nom_pris.jpg

Contacter

Institut national des langues et civilisations orientales
Traitement automatique des langues
65 Rue des Grands Moulins,
75013 Paris
Nathalie VALMOND-LEBLANC
n.valmondleblanc@yahoo.fr
Yuming ZHAI
zhaiyuming9@gmail.com
Purit KANCHANAWIRA
purit.kanchanawira@gmail.com

Pour télécharger nos programmes, cliquez ici

Pour télécharger nos siteweb, cliquez ici