Boîte à outils

Bienvenue sur notre site web,

Ici seront présentés les différents travaux réalisés dans le cadre du cours «Projet encadré 2» du master 1 Plurital.

Ces travaux ont pour objectif de fournir des outils permettant l’extraction et le classement en rubriques d’informations contenues dans des fils RSS, l’annotation morphosyntaxique de ces informations, l’extraction de patrons, et la représentation de ces derniers sous forme de graphes.
Les fichiers traités sont issus d’une extraction automatique de fils RSS à partir du site-web du quotidien Le Monde sur l’ensemble de l’année 2013.

Nous espérons que ce site web sera utile aux étudiants des nouvelles promotions, n’hésitez pas à nous contacter en cas de besoin.
Nous tenons à remercier messieurs Serge Fleury, Jean-Michel Daube et Rachid Belmouhoub pour leur aide.

Nous vous souhaitons une agréable visite !
Extraction des contenus des fils RSS et leur classement en rubrique.
Etiquetage morpho-syntaxique de ces contenus avec TreeTagger et Cordial.
Extraction de patrons syntaxiques à partir des sorties de la BAO 2.
Génération de graphes avec patron2graphe.exe.

Boîte à outils 1



L’objectif de cette première BAO consiste à parcourir les répertoires contenant les fils RSS triés par catégories et d’en extraire des informations précises, en l’occurrence les titres et les descriptions. Les fils RSS étant des fichiers de la famille XML, répondent de ce fait à une structure précise simplifiant l’extraction de ces informations.

Il existe deux solutions pour cette tâche d’extraction:

  • - La méthode pure Perl avec les expressions régulières.
  • - La méthode via la bibliothèque XML::RSS.


Les programmes ont pour but l'extraction pour chaque <item> les balises <titre> et <description> à partir les fils RSS. Les programmes prennent en entrée le répertoire le corpus est stocké et doivent produire deux sorties : Une sortie en TXT et une sortie en XML par catégorie et une sortie globale dans les deux formats (tous en utf-8).

Exemple



Ci-dessous est un exemple de fichier RSS. Il est clair que dans l’état, il est très difficilement exploitable. En effet on constate déjà la présence d’un grand nombre de balises dont le contenu n’est pas pertinent avec nos objectifs. D’autre part, l’encodage n’est pas forcément de l’UTF-8, il faudra gérer cela avec notre programme. Au niveau du formatage, on constate la présence d’entités et de retours chariots qu’il faut impérativement gérer pour obtenir une sortie propre.


<?xml version="1.0" encoding="iso-8859-1"?>
<rss version="2.0" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<channel>
<title>Le Monde.fr : A la une</title>
<link>http://www.lemonde.fr</link>
<description>Toute l'actualité au moment de la connexion</description>
<copyright>Copyright Le Monde.fr</copyright>
<image><url>http://medias.lemonde.fr/mmpub/img/lgo/lemondefr_rss.gif</url><title>
Le Monde.fr
</title><link>
http://www.lemonde.fr</link></image>
<pubDate>Wed, 02 Jan 2008 17:48:09 GMT</pubDate> <item>
  <title>Scotland Yard va assister le Pakistan dans l'enquête sur la mort de Benazir Bhutto</title>
  <link>http://www.lemonde.fr/web/article/0,1-0@2-3216,36-995377,0.html?xtor=RSS-3208</link>
  <description>Une équipe de la police criminelle britannique arrivera 
  &#38;#34;avant la fin de la semaine&#38;#34; au Pakistan, a indiqué Londres, 
  confirmant l&#38;#39;annonce faite un peu plus tôt par le président Musharraf dans 
  un discours à la nation.</description>
  <pubDate>Wed, 02 Jan 2008 17:32:39 GMT</pubDate>
  <guid isPermaLink="false">http://www.lemonde.fr/web/article/0,1-0@2-3216,36-995377,0.html?xtor=RSS-3208</guid>
  <enclosure url="http://medias.lemonde.fr/mmpub/edt/ill/2008/01/02/h_1_ill_995454_pak.jpg" 
  type="image/jpeg" 
  length="2194"></enclosure>
</item>
<item>
  <title>Les laboratoires sont contraints de révolutionner leur recherche</title>
  <link>http://www.lemonde.fr/web/article/0,1-0@2-3234,36-995266,0.html?xtor=RSS-3208</link>
  <description>Le lancement de nouveaux médicaments est de plus en plus coûteux et rapporte de moins en moins. 
  Aussi plusieurs laboratoires pharmaceutques ont entrepris de remettre à plat l&#38;#39;organisation de 
  ce qui constitue le coeur de leur métier : la recherche et développement.</description>
  <pubDate>Wed, 02 Jan 2008 13:43:36 GMT</pubDate>
  <guid isPermaLink="false">http://www.lemonde.fr/web/article/0,1-0@2-3234,36-995266,0.html?xtor=RSS-3208</guid>
  <enclosure url="http://medias.lemonde.fr/mmpub/edt/ill/2008/01/02/h_1_ill_913157_inserm-labo.jpg" 
  type="image/jpeg" length="2317"></enclosure>
</item>

</channel>
</rss>



Parcours de l'aborescence



Le corpus Le Monde RSS de l’année 2013 se compose de plusieurs répertoires organisés en rubriques et date d’aspiration des fils. Afin de pouvoir traiter ces fichiers, il est nécessaire dans un premier temps de parcourir ces répertoires et de discriminer automatiquement les fichiers des répertoires. On crée une fonction « parcour » pour parcourir le répertoire de corpus. On interrogera les éléments rencontrés par le script avec 'if (-d $file)' pour déterminer leur nature, s’il s’agit d’un dossier on continue de parcourir les répertoires jusqu’à atteindre l’ensemble des fichiers (identifiés par 'if (-f $file)'). que contient l’arborescence. On ne prend compte les fichiers au format xml, avec la condition : « if ($file =~ /^[^f]+?$rub.*\.xml$/) ».



sub parcour
{
    #créer une variable du répertoire
    my $dossier = shift(@_);
    #ouverture du dossier
    opendir(DOSSIER,$dossier);
    #créer une liste qui contient les noms des contenus de ce dossier
    my @files = readdir(DOSSIER);
    closedir(DOSSIER);
    #pour chaque élément de ce dossier
    foreach my $file(@files)
    {
        #ignorer les éléments cachés, ceux qui commencent par . ou .. en cas renconter les cachés, passer au suivant
        next if $file =~ /^\.\.?$/;
        #le chemin absolu du élément
        $file = $dossier."/".$file;
        #si c'est un répertoire, on regarde son contenu
        if (-d $file)
        {
            &parcour($file);
        }
        #si c'est un fichier
        if (-f $file)
        {
            #et si le fichier est un fichier xml
            if ($file =~ /^[^f]+?$rub.*\.xml$/)



Normaliser l’encodage



Afin de réduire les erreurs potentielles, il est important d’emblée de traiter les éventuelles variations d’encodage et d’harmoniser les sorties.


Pour le traitement d’encodage, on a utilisé la bibliothèque Unicode::String. On défini au début de programme, une variable qui indique l’encodage en UTF-8.

my $encodagesortie="utf-8";


On vérifie l’encodage de chaque fichier en utilisant une commande une Bash
my $encodage=`file -i $file | cut -d= -f2`;
Le résultat de cette commande est stocké dans la variable $encodage. Si l’encodage du fichier n’est pas utf-8, on le convertit.

if (uc($encodage) ne "UTF-8") {utf8($date);}
if (uc($encodage) ne "UTF-8") {utf8($titre);utf8($resume);}

Toutes les sorties sont en utf-8.

open(OUT1,">>:encoding($encodagesortie)","sortie-textebrut.txt");
open(OUT2,">>:encoding($encodagesortie)","sortie-textexml.xml");



open (IN,$file);
                my $encodage=`file -I $file | cut -d= -f2`;
                print "ENCODAGE : $encodage \n";
                chomp($encodage);
                open(IN,"<:encoding($encodage)", $file);
                open(OUT1,">>:encoding($encodagesortie)","sortie-textebrut.txt");
                open(OUT2,">>:encoding($encodagesortie)","sortie-textexml.xml");



Normaliser le formatage



Comme nous l’avons constaté sur notre exemple. Il arrive parfois que les fichiers d’entrée contiennent des retours chariots, des espaces blancs..Etc alors que dans d’autres cas l’ensemble du contenu se trouve être sur une seule ligne. Il est donc important de traiter ces variations pour faciliter les tâches de traitement ultérieures et d’obtenir des sorties sous la même forme. Nous avons fait le choix de normaliser le formatage des fichiers d’entrée en retirant les retours chariots et les espaces blancs et d’obtenir des fichiers sur une seule ligne.


         while (my $ligne=<IN>) {
                    #print $ligne;
                    $ligne =~ s/\n//g;
            $ligne =~ s/\r//g;
                    $texte .= $ligne;
                }
                $texte =~ s/> *</></g;


Nettoyage



Une étape importante du script, consiste à nettoyer le texte en remplaçant toutes entités indésirables par le caractère correspondant.
On crée la fonction nettoietexte pour réaliser le nettoyage des balises.


$titre = &nettoietexte($titre);
$resume = &nettoietexte($resume);



sub nettoietexte {
    my $texte=shift;
    $texte =~ s/&lt;/</g;
    $texte =~ s/&gt;/>/g;
    $texte =~ s/<a href[^>]+>//g;
    $texte =~ s/<img[^>]+>//g;
    $texte =~ s/<\/a>//g;
    $texte =~ s/&#38;#39;/'/g;
    $texte =~ s/&#38;#34;/"/g;
    $texte =~ s/<[^>]+>//g;
    .....
    return $texte;
}

L’extraction des rubriques se fait au sein des fichiers ce qui fait qu’on peut se trouver confronter à des problèmes liés au espace en trop, ou aux caractères diacritiques. Au même titre que le nettoyage du texte nous allons intégrer une série d’instructions à notre script dont le but est de nettoyer les rubriques.

    my $rub=$1;
            # nettoyer les rubriques
            $rub=~s/e/gi;
            $rub=~s/e/gi;
            $rub=~s/e/gi;
            $rub=~s/e/gi;
            $rub=~s/a/gi;
            $rub=~s/i/gi;
            $rub=~s/i/gi;
            $rub=~s/u/gi;
            $rub=~s/c/gi;
            $rub=~s/e/gi;
            $rub=~s/e/gi;
            $rub=~s/e/gi;
            $rub=~s/a/gi;
            $rub=~s/Le Monde.fr//gi;
            $rub=~s/LeMonde.fr//gi;
            $rub=~s/Le *Monde *\. *fr *://gi;
            $rub=~s/ //g;
            $rub=~s/s$//;
            $rub=~s/,/_/g;
            $rub=~s/[\.\:;\'\"\-]+//g;
            $rub=uc($rub);



Extraire le texte avec un méthode « rustique » : les expressions régulières



L’extraction des informations est la partie centrale de cette BAO. Il est possible de procéder à cela de deux manières. La première consiste à utiliser les expressions régulières. A l’aide de celles-ci, il est possible de demander au script de s’intéresser à des parties spécifiques des fichiers et d’en extraire des contenus. Pour le cas présent, nous avons besoin d’extraire le titre et le contenu de chaque fil RSS.

On va demander au script de parcourir les fichiers et de trouver les parties qui contiennent les balises <title> </title>, <description> </description> et en extraire les contenus



my $texte="";
                while (my $ligne=<IN>) {
                    #print $ligne;
                    $ligne =~ s/\n//g;
                    $texte .= $ligne;
                }
                $texte =~ s/> *</></g;
                $texte=~/<pubDate>([^<]+)<\/pubDate>/;
                my $date=$1;
                if (uc($encodage) ne "UTF-8") {utf8($date);}
                print OUT2 "<date>".$date."</date>\n";
                print OUT2 "<items>\n";
                while ($texte =~ /<item><title>(.+?)<\/title>.+?<description>(.+?)<\/description>/g) {
                    my $titre=$1;
                    my $resume=$2;
                    if (uc($encodage) ne "UTF-8") {utf8($titre);utf8($resume);}
                    $titre = &nettoietexte($titre);
                    $resume = &nettoietexte($resume);
                    print OUT1 "Titre : $titre \n";
                    print OUT1 "Resume : $resume \n";;
                    print OUT2 "<item><title>$titre</title><abstract>$resume</abstract></item>\n";
                }
                print OUT2 "</items>\n</file>\n";
                close(OUT1);
                #close(OUT2);
                close(IN);
            }
        }
    }


Extraire le texte avec des outils adaptés : la bibliothèque XML::RSS



La deuxième méthode consiste à arriver aux mêmes résultats en utilisant une approche plus appropriée aux fichiers de la famille XML (dont le RSS fait partie). Le contenu d’un fichier RSS est sous une forme arborescente. On peut utiliser le module Perl XML::RSS pour trouver les informations dont on a besoin d’extraire. L’originalité de cette approche est que le module exploite la structure arborescente des fichiers RSS pour atteindre les nœuds pertinents. Seule la partie d’extraction change par rapport à la version « expressions régulières » du script. On obtient en sortie des fichiers txt et XML pour chaque rubrique.



  my $file="$file";
                my $rss=new XML::RSS;
                #-----------------------------------------------------------
                eval {$rss->parsefile($file); };
                if( $@ ) {
                    $@ =~ s/at \/.*?$//s;               # remove module line number
                    print STDERR "\nERROR in '$file':\n$@\n";
                }
                else {
                    my $date=$rss->{'channel'}->{'pubDate'};
                    print OUT2 "<date>$date</date>\n";
                    print OUT2 "<items>\n";
                    foreach my $item (@{$rss->{'items'}}) {
                        my $titre=$item->{'title'};
                        my $resume=$item->{'description'};
                        $titre=&nettoietexte($titre);
                        $resume=&nettoietexte($resume);
                        if (uc($encodage) ne "UTF-8") {utf8($titre);utf8($resume);}


Les sorties



Comme énoncé précédemment, nous souhaitons obtenir deux types de sorties. La première au format TXT et la seconde au format XML. Ceci requière bien évidemment deux manipulations différentes. La sortie TXT ne contiendra que les titres et les descriptions séparés par un retour à la ligne. Les sorties XML nécessitent que les fichiers répondent à une structure, il faut donc insérer des balises spécifiques pour organiser cela.

Ci-dessous est présentée la partie du script gérant les sorties. La sortie texte (OUT1) et la sortie XML (OUT2), on constate que les éléments récupérés sont insérés dans des balises, title pour le titre, et abstract pour la description.


                        my $titre=$item->{'title'};
                        my $resume=$item->{'description'};
                        $titre=&nettoietexte($titre);
                        $resume=&nettoietexte($resume);
                        if (uc($encodage) ne "UTF-8") {utf8($titre);utf8($resume);}
                        print OUT1 "Titre : $titre \n";
                        print OUT1 "Resume : $resume \n";;
                        print OUT2 "<item><title>$titre</title><abstract>$resume</abstract></item>\n";
                    }
                }
                #----------------------------------------------------------
                print OUT2 "</items>\n</file>\n";
                close(OUT1);
                #close(OUT2);
                close(IN);
            }


Exemple de sortie XML



<?xml version="1.0" encoding="utf-8" ?>
<ROOT>
<file>
<name>2013/Oct//25/19-00-00/0,2-3246,1-0,0.xml</name>
<date>Fri, 25 Oct 2013 15:46:58 GMT</date>
<items>
<item><title>Quand Roman Polanski compare son cas à l'affaire Dreyfus</title><abstract>Poursuivi depuis plus
 de trente ans pour crime sexuel sur mineur, le réalisateur franco-polonais trouve des résonances
 à sa propre histoire dans l'acharnement médiatique dont a été victime l'officier 
Alfred Dreyfus en 1894.</abstract></item>
<item><title>La jeune photographie s'expose à la Samaritaine</title><abstract>En marge de sa rénovation
, la Samaritaine, fermée depuis 2005, organise une exposition  dédiée à onze jeunes photographes, 
auxquels le grand magasin a donné carte blanche.</abstract></item>
<item><title>"Second Chris" : vagabondage virtuel dans l'œuvre de Marker</title><abstract>De la vidéo analogique à
YouTube en passant par les disquettes, les CD-ROM et  "Second Life", l'auteur de "Lettre de Sibérie" 
n'a jamais cessé d'explorer les frontières de la technologie et de l'art.</abstract></item>
<item><title>Arcade Fire met en ligne "Reflektor" cinq jours avant sa sortie</title><abstract>Le groupe de rock
 canadien a mis à disposition l'intégralité de son nouvel album sur son site et sur YouTube.</abstract></item>
</items>
</file>
</ROOT>


Exemple sortie TXT



Titre : L'Eglise, ou la longévité par la gouvernance 
Resume : La communauté catholique est confrontée à une situation sous-estimée : la croissance massive et rapide 
du nombre de ses membres. La gouvernance actuelle est encore trop centrée sur l'Europe. Le nouveau pape devra 
trouver les clés de la gouvernance répondant à cette étape de la mondialisation de l'Eglise. 
Titre : Quand une partie de la "Manif pour tous" voulait occuper les Champs-Elysées 
Resume : Depuis plusieurs semaines, une frange minoritaire des anti-mariage avait prévu d'occuper l'avenue malgré 
les interdictions. Et certains ont appelé dimanche à franchir les barrages policiers en force si nécessaire. 
Titre : A l'école de la désobéissance civile 
Resume : Les Désobéissants, collectif fondé en 2006, organisent à intervalles réguliers des stages. Les 
participants partagent une envie commune, celle d'agir, et une même frustration, celle de l'impuissance 
véhiculée par les modes de protestation classiques. 
Titre : Nicolas Sarkozy : "La vérité finira par triompher" 
Resume : Nicolas Sarkozy s'est exprimé lundi 25 mars pour la première fois depuis sa mise en examen dans 
l'affaire Bettencourt avec un message posté sur sa page Facebook. 

Téléchargements






Boîte à outils 2



Cette BAO s’inscrit dans la continuité de la BAO1. En effet, La BAO2 a pour l’objectif l’étiquetage morphosyntaxique des résultats de la BAO1. Il existe deux solutions pour réaliser cela : le logiciel Cordial ou via TreeTagger.

Pour pouvoir bien identifier chaque mot syntaxiquement il est indispensable que celui-ci soit traité individuellement par rapport aux autres éléments qui l’entourent. Pour isoler chaque élément nous allons d’abord procéder à une tâche de prétraitement qui consiste à produire des tokens séparés par des délimiteurs (espace). Nous allons utiliser pour cela un programme déjà existant, un tokenizer.



CORDIAL



Cordial est le premier outil que nous utiliserons pour tagger nos fichiers morpho-syntaxiquement. Une étape de préparation est cependant nécessaire, en effet Cordial offre certaines particularités qu’on doit gérer avant de pouvoir procéder à l’étiquetage. La taille des fichiers d’entrée ne doit pas excéder une certaine limite, nous devons donc procéder à l’étiquetage des fichiers par rubrique. D’autre part, il ne prend en entrée que des fichiers TXT dont l’encodage est ISO-8859-1. Le problème est que les résultats qu’on a obtenu de BAO 1 sont en UTF-8. Donc on a utilisé quelques lignes de commandes Bash pour convertir les fichiers en ISO-8859-1.



#!/bin/bash
# Prendre 2 arguments: 
# une entree repertoire qui contient les fichiers en utf-8
# une sortie repertoire ou tous les fichiers vont etre stocker
IN=${1%/};
OUT=${2%/};
# pour tous les fichierdans repertoire entree
for fichier in `ls $IN`
{
# utiliser la commande iconv pour transferer l'encodage
echo $fichier; 
(iconv -f UTF-8 -t ISO-8859-1 "$IN/$fichier") > $OUT/$fichier
}


Nous pouvons à présent procéder à l’étiquetage de nos fichiers. Pour cela il faut cliquer au niveau du menu sur le bouton syntaxique puis sur étiquetage. Une fenêtre s’ouvre, une fois les paramètres définis, il faut cliquer sur OK pour lancer l’étiquetage.



On obtient des fichiers avec l'extension ".cnr" . Le contenu est tabulaire. La première colonne est le token, la deuxième est lemme, le troisième est la catégorie grammaticale.



Marseille   Marseille   NPSIG
:   :   PCTFORTE
:
la  le  DETDFS
bouillabaisse       bouillabaisse       NCFS
infernale   infernal    ADJFS
(   (   PCTFAIB
1/12    1/12    NCMIN
)   )   PCTFAIB
\r
Marseille   Marseille   NPSIG
-Provence   Provence    NPFS
2013	2013	NCMIN


TreeTagger



La seconde stratégie d’étiquetage consiste à utiliser Treetagger. L’avantage de cet outil est qu’il est possible de l’utiliser en ligne de commande ce qui facilite comme on va le voir grandement son intégration à notre flux de travail, en d’autre termes directement au niveau de la BAO1. La solution de TreeTagger est donc plus automatique que celle de Cordial, mais prend beaucoup plus de temps.

On ajoute une fonction de Treetagger dans le programme BAO1 pour tokeniser (on utilisera le tokenzier fourni par Treetagger pour cette étape) et étiquetter le texte.

On obtient en résultat tabulaire comme celui via Cordial, le token, le lemme et la catégorie grammaticale. Mais cette fois, on utilise en plus un programme treetagger2xml-utf8.pl pour obtenir en sortie un fichier au format XML. Donc il est plus structuré que le résultat de Cordial.

Cette partie contient plusieurs étapes, on crée donc un fichier temporaire pour stocker les résultats des différentes étapes.

La syntaxe de Treetagger est la suivante :

treetagger.exe [options] <parameters> <input> <output>

Input et output correspondent aux fichiers d’entrée et de sortie. Il faut également préciser le chemin vers le fichier de paramètre pour la langue qu’on traite ainsi que les options d’étiquetage :

  • -lemma : affiche le lemme en sortie
  • -token : Imprime le mot dans la même forme qu’en entrée
  • -sgml : Imprime les informations morphologiques.

sub etiquetageavectreetagger {
    my ($titre,$texte)=@_; # titre
    my $codage="utf-8";
    my $tmptag="texteaetiqueter.txt";
    open (TMPFILE,">:encoding(utf-8)", $tmptag);
    print TMPFILE $titre,"\n";
    close(TMPFILE);
    system("perl tokenise-utf8.pl $tmptag | 
    /Users/Yunhe/Desktop/treetagger/tree-tagger-MacOSX-3.2-intel/bin/tree-tagger 
    -lemma -token /Users/Yunhe/Desktop/treetagger/lib/french-utf8.par > treetagger.txt");
    
    system("perl treetagger2xml-utf8.pl treetagger.txt $codage "); # lecture du resultat tagge en xml :
    open(OUT,"<:encoding(utf-8)","treetagger.txt.xml");
    my $fistline=<OUT>;
    my $titreetiquete="";
    while (my $l=<OUT>) {
    $titreetiquete.=$l;
    }
    close(OUT);
    # le resume
    open (TMPFILE,">:encoding(utf-8)", $tmptag);
    print TMPFILE $texte,"\n";
    close(TMPFILE);
    system("perl tokenise-utf8.pl $tmptag | 
    /Users/Yunhe/Desktop/treetagger/tree-tagger-MacOSX-3.2-intel/bin/tree-tagger 
    -lemma -token /Users/Yunhe/Desktop/treetagger/lib/french-utf8.par > treetagger.txt");  
    system("perl treetagger2xml-utf8.pl treetagger.txt $codage");
    # lecture du resultat tagge en xml :
    open(OUT,"<:encoding(utf-8)","treetagger.txt.xml");
    my $fistline=<OUT>;
    my $texteetiquete="";
    while (my $l=<OUT>) {
    $texteetiquete.=$l;
    }
    close(OUT);
    # renvoie les resultats :
    return ($titreetiquete,$texteetiquete);
}

Le programme tokenise-utf8.pl prend en entrée un fichier et il segmente le texte de fichier en tokens.
Le programme treetagger2xml-utf8.pl permet de transformer le résultat tabulaire de tree-tagger au format xml.



#!/usr/bin/perl
use Unicode::String qw(utf8);
<<DOC;
Format d\'entree : un texte étiqueté et lemmatisé par tree tagger et un format d'encodage
Format de Sortie : le même texte au format xml (en utf-8)
DOC


# Usage
$ChaineUsage="Usage : tt2xml.pl <Fichier> <encodage>\n";
if (@ARGV!=2) {
 die $ChaineUsage;
}

&ouvre;
&entete;
&traitement;
&fin;
&ferme;

##############################################################################################
# Récupération des arguments et ouverture des tampons
sub ouvre {
    $FichierEntree=$ARGV[0];
    $encodage=$ARGV[1];
    open(Entree,"<:encoding($encodage)",$FichierEntree);
    $FichierSortie=$FichierEntree . ".xml";
    open(Sortie,">:encoding(utf-8)",$FichierSortie);
}

# Entête de document XML
sub entete {
    print Sortie "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n";
    print Sortie "<document>\n";
    print Sortie "<article>\n";
}

# Traitement
sub traitement {
    while ($Ligne = <Entree>) {
    if (uc($encodage) ne "UTF-8") {utf8($Ligne);}
    if ($Ligne!~/\ô\¯\:\\ô\¯\:\\/) {
    # Remplacement des guillemets par <![CDATA["]]> (évite erreur d'interprétation XML)
        $Ligne=~s/\"/<![CDATA[\"]]>/g;
        $Ligne=~s/([^\t]*)\t([^\t]*)\t(.*)/<element>\n 
	<data type=\"type\">$2<\/data>\n 
<data type=\"lemma\">$3<\/data>\n <data type=\"string\">$1<\/data>\n<\/element>/;
        $Ligne=~s/<unknown>/unknown/g;
        print Sortie $Ligne;
    }
    }
}
# Fin de fichier
sub fin {
    print Sortie "</article>\n";
    print Sortie "</document>\n";
}

# Fermeture des tampons
sub ferme {
    close(Entree);
    close(Sortie);
}

Les sorties



<title>
<document>
<article>
<element><data type="type">NAM</data><data type="lemma">Mini-Transat</data><data type="string">
Mini-Transat</data></element>

<element><data type="type">PUN</data><data type="lemma">:</data><data type="string">
:</data></element>

<element><data type="type">PUN:cit</data><data type="lemma">«</data><data type="string">
«</data></element>

<element><data type="type">VER:simp</data><data type="lemma">J'ai</data><data type="string">
J'ai</data></element>

<element><data type="type">VER:pper</data><data type="lemma">savoir</data><data type="string">
su</data></element>

</article>
</document>
</title>


Nous allons utiliser cette feuille de style (cliquez ici pour la télécharger) pour visualiser les résultats en HTML. Cliquez ici pour voir le résultat

Bonus BAO2

Pour ce petit bonus, nous allons présenter une petite variante de la BAO2 intégrant deux nouveaux outils. Le premier est l’étiqueteur morphosyntaxique MXPOST très semblable à Treetagger mais permet d’avoir des résultats non pas sous une forme tabulaire mais sur la même ligne, concevant ainsi la cohérence de la phrase d’input. Le choix de cet outil est motivé par le fait qu’il s’agisse du seul étiqueteur compatible avec le second outil qui est Collin-parser. Ce dernier est un outil d’annotation syntaxique permettant de générer des arbres de dépendances et de faire des analyses très approfondies sur un corpus, d’extraire les régularités, d’en déduire une grammaire. Malheureusement ces deux outils ne fonctionnent faute de Treebank en français que sur de l’anglais. Nous avons donc créé un petit fils RSS en anglais contenant une phrase lambda.

....
<description>A new besom sweeps clean</description>
.....

Nous avons donc procédé à modification de la BAO2 TreeTagger en y intégrant un script tiers à la place. L’étiquetage en parties du discours avec MXPOST se fait de façon transparente car cette partie est gérée par le script qu’on appelle ici, en l’occurrence Collin-en-parser.perl. Après avoir modifié la fonction d’étiquetage avec Treetagger et y avoir intégré Collin-en-parser.pl, la BAO extrait le contenu de notre fichier de test le soumet à MXPOST, puis à Collin-en-parser.

    my $codage="utf-8";
    my $tmptag="temptag.txt";
    open (TMPFILE,">:encoding(utf-8)", $tmptag);
    print TMPFILE $titre,"\n";
    close(TMPFILE);
    system("perl parse-en-collins2.perl < $tmptag > collin_parser.txt");
    
Le programme s'exécute
Répertoire de départ : /disque2/Massi/test/
/disque2/Massi/test//retest/file.xml
Read 11692 items 
from /disque2/Preparation/Factored/mxpost/tagger.project/word.voc
Read 45 items 
from /disque2/Preparation/Factored/mxpost/tagger.project/tag.voc
Read 42680 items
 from /disque2/Preparation/Factored/mxpost/tagger.project/tagfeatures.contexts
Read 42680 contexts, 117558 numFeatures
 from /disque2/Preparation/Factored/mxpost/tagger.project/tagfeatures.fmap
Read model /disque2/Preparation/Factored/mxpost/tagger.project/model : numPredictions=45, numParams=117558
Read tagdict 
from /disque2/Preparation/Factored/mxpost/tagger.project/tagdict
*This is MXPOST (Version 1.0)*
*Copyright (c) 1997 Adwait Ratnaparkhi*
Sentence: 0 Length: 10 Elapsed Time: 0.052 seconds.
Initialised lexicons
Initialised grammar
Loaded non-terminals
Loaded lexicon
Loaded grammar
NUMSENTENCES 1
Hash table: 100000 lines read
Hash table: 200000 lines read
Hash table: 300000 lines read
Hash table: 400000 lines read

Le résultat

On obtient en sortie notre phrase étiquetée en parties du discours et représentée sous la forme d’un arbre.

<tree label="TOP"> <tree label="S"> <tree label="NPB"> <tree label="DT"> A </tree> <tree label="JJ"> new </tree>
 <tree label="NN"> besom </tree> <tree label="NNS"> sweeps </tree> </tree> <tree label="VP"> 
<tree label="VB"> clean </tree> </tree> </tree> </tree>

Téléchargements




Boîte à outils 3

À l’issue de la BAO2, nous disposons désormais de deux jeux de données, le premier au format XML annoté en utilisant Treetagger, le second au format TXT annoté par Cordial. L’objectif de cette boite à outils est de proposer une méthode d’extraction des partons à partir de ces deux sorties. Pour cela nous mettrons en œuvre deux techniques différentes, la première s’appuie sur le module XML ::XPATH, le second sur une méthode en Perl.

    Les patrons syntaxiques à extraire sont :

  • NOM PREP NOM
  • NOM ADJ

Solution 1:

L'extraction de patrons sur les sorties texte brute étiquetés via Cordial issues de la Boîte à Outils via un script perl.

Ce script prend en entrée le fichier .cnr et un fichier où stocker les patrons. Le fichier .cnr contient le texte tabulaire en trois colonnes. (token, lemme, catégorie grammaticale).
On ne traite que les lignes qui contiennent une tabulation. « \t » correspond une tabulation.

while (my $ligne = <TEXTE>) { # lecture ligne à ligne du fichier
    $ligne =~ s/\r//g;
    if ($ligne =~ /\t/) { # on ne traite que les lignes qui contiennent une tabulation (issue de CORDIAL)

On segmente la ligne par la tabulation et stocke les trois colonnes dans trois listes. Le PCTF correspond à la ponctuation forte, qui est un marqueur de la fin de phrase, cela nous permet de regarder le texte phrase par phrase. Quand on rencontre la ponctuation forte, on vide les listes.



        chomp($ligne);
        my @LISTE=split(/\t/,$ligne); 
        # on découpe la ligne via les tabulations : résultat, une liste de 3 éléments (FORME, LEMME, POS)   
    # $LISTE[0]<-forme, $LISTE[1] <-lemme, $LISTE[2] <-pos  
        if ($LISTE[2] !~ /PCTF/) {
            push(@TOKEN,$LISTE[0]);
            push(@LEMME,$LISTE[1]);
            push(@POS,$LISTE[2]);       
        }

On regroupe tous les éléments de POS et les relie par espace et on les stocke dans une variable SUITEDEPOS. Si on trouve le patron dans SUITEDEPOS, on affiche son lemme correspondant.


else {          
                my $SUITEDEPOS=join(" ",@POS); # joindre les elements de pos par un espace
                open(PATRON,$ARGV[1]); # le fichier patron est le second argument du script
                while (my $patron=<PATRON>) {
                    chomp($patron);
                    $patron=~s/\r//g;
                    my $compteur=0;
                    while ($SUITEDEPOS=~/$patron/g) {
                        my $avant=$`;
                        my $j=0;
                        while ($avant =~/ /g) {
                            $j++;
                        }
                        my $k=0;
                        while ($patron =~/ /g) {
                            $k++;
                        }
                        print "@TOKEN[$j..$j+$k]\n";                    
                    }   
                }
                
            close(PATRON);
            @TOKEN=(); # quand on est arrive sur une PONCT on vide les listes...
            @LEMME=();
            @POS=();
        }
    }


} 


Les sorties


Voici ci-dessous un exemple de sortie pour le patron NOM ADJ sur la rubrique Economie
cimentier mondial
carcan financier
place financière
emploi marchand
collectivités locales
années précédentes
producteurs chinois
marronniers high-tech
mur budgétaire
coupes budgétaires
mur budgétaire
falaise fiscale
Bourse Destinés
investisseurs institutionnels
indices boursiers
moyen simple


Solution 2:

Nous allons maintenant nous intéresser à la méthode XML ::XPATH réalisée par R. Belmouhoub. Les fichiers d’input étant du XML, le module XML::XPATH nous permettra d’intégrer et d’exécuter des requêtes XPATH avec notre script.
Le script a deux arguments, le fichier XML à traiter et un fichier de patrons. L’extraction se déroule de la façon suivante.
Le script procède d’abord à la lecture du fichier de patrons (chaque patron correspond à une ligne du fichier). Pour chaque patron, on appelle extract_pattern, avec le patron comme argument. Cette dernière produit un fichier sortie contenant le résultat de l’extraction de patron. Les patrons utilisés pour notre expérience sont NOM PRP NOM et NOM ADJ. Le script comporte deux étapes principales :

La construction du chemin XPATH


La construction du chemin XPATH, est réalisée par la procédure construit_XPath. Celle-ci prend un patron comme argument. Après avoir transformé ce dernier en tableau, un chemin est initialisé pour le premier élément avant de rajouter les éléments suivants un par un dans l’ordre.

sub construit_XPath{
    # On récupère la ligne du motif recherché
    my $local_ligne=shift @_;
    
    # initialisation du chemin XPath
    my $search_path="";
    
    # on supprime avec la fonction chomp un éventuel retour à la ligne
    chomp($local_ligne);
    
    # on élimine un éveltuel retour chariot hérité de windows
    $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é 
    my $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="]";
    
    # 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;
    
    # le chemin XPath final
    $search_path.="[following-sibling::element[1][contains(data[\@type=\"type\"],\"".$tokens[$#tokens]."\")]"
                                .$search_path_suffix;
        # print  "$search_path\n";

    # on renvoie à la procédure appelante le chein XPath et le tableau des éléments du motif
    return ($search_path,@tokens);
}

L’Extraction des patrons


Une fois le chemin créé, celui-ci est récupéré par &extract_pattern. On utilisera par la suite ‘findnodes’ pour repérer les nœuds pertinents à notre recherche.

sub extract_pattern{
    # On récupère la ligne du motif recherché
    my $ext_pat_ligne= shift @_;

    # Appel de la fonction construit_XPath pour le motif lu à la ligne courrante du fichier de motif
    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,">:encoding(UTF-8)", "$match_file") or die "can't open $match_file: $!\n";
    
    # création de l'objet XML::XPath pour explorer le fichier de sortie tree-tagger XML
    my $xp = XML::XPath->new( filename => $tag_file ) or die "big trouble";
    
    # 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"
    foreach my $noeud ( $xp->findnodes($search_path)) {
        # 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'éxtraction des formes correspondants
        # 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 $xp->find($form_xpath,$noeud)->get_node(1)->string_value," ";
        
        # Boucle d'éxtraction des formes correspondants 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'éxtraction 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 $folowing 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 $xp->find($form_xpath,$noeud)->get_node(1)->string_value," ";
        
            # Incrémentation du compteur $following de cette boucle d'éxtraction des formes
            # $following++;
        }
        print MATCHFILE "\n";
    }
    # Fermeture du fichiers de motifs
    close(MATCHFILE);
}


Les sorties


Voici ci-dessous un exemple de sortie pour le patron NOM ADJ sur la rubrique Economie
cimentier mondial
compagnie indienne 
cuisine gastronomique 
écran multimédia 
velléité d'exil 
compositeur français 
cimentier mondial 
carcan financier 
croissance positifs 
l'économie espagnol 
navettes fluviales 
capitale grecque 
place financière 



Alternative BAO3


Bien que encore très perfectible, l’objectif de ce programme est de démontrer qu’il est possible à partir d’une combinaison entre le bash et perl de repérer et de filtrer des patrons à partir de fichiers TXT. L’intérêt de cette approche est que celle-ci est agnostique par rapport aux fichiers d’entrée et aux patrons demandés. Nous allons pour cela utiliser un grep contextuel avec pcregrep –m (pour traiter plusieurs lignes) permettant d’intégrer des expressions régulières et de préciser le chemin entre les patrons (séparés par des retours chariot et une chaine de caractères) '$patron.\n.*$patron2'. L’utilisateur devra spécifier les patrons dans l’ordre en tant qu’arguments du script . On appelle par la suite la commande Unix cut –f1 pour imprimer que les formes correspondantes aux patrons. Ce script est très basique, il faudra également faire en sorte que les patrons repérés apparaissent sur la même ligne, gérer l’encodage…Etc, mais l’objectif ici est de montrer qu’il existe une alternative en grep contextuel.

Syntaxe : BAO3_alternative fichier forme1 forme2

#!/usr/bin/perl 
use utf8;
my $fichier=$ARGV[0];
my $patron=$ARGV[1];
my $patron2=$ARGV[2];
#my $patron3=$ARGV[3];
open(TOTO,"$fichier")  or die "Impossible d'ouvrir le fichier $fichier en écriture";



while( defined( $l = <TOTO> ) ) {


chomp $l;
$l=~s/\r?\n//g;
 $resultat =system "pcregrep -M '$patron.\n.*$patron2' $fichier | cut -f1 ";
 

}

close(TOTO);




Téléchargements




Boîte à outils 4

L’objectif de cette dernière BAO consiste à visualiser les patrons syntaxiques issus de l’extraction réalisée par la BAO3 et de réaliser une analyse textomértique. Pour cela nous allons utiliser le programme patron2graphe.exe. Ce dernier prend en entrée un fichier de patron, l’encodage de ce dernier et éventuellement un fichier de motif à afficher.

Exemple : patron2graphe.exe "iso-8859-1" patrons-1.txt

Exemple 1

Exemple : patron2graphe.exe "utf-8" ECONOMIEres_extract-NOM_PRP_NOM
Cliquez sur l'image ci-dessous pour l'agrandir


Il est clair que le résultat dans l’état ne peut être exploitable en vue d’une analyse texométrique. En raison d’un grand nombre d’informations soumises au programme. Il est donc nécessaire d’affiner notre recherche en fournissant un fichier de motif au programme permettant d’obtenir des résultats plus précis

Exemple 2

Nous allons à présent soumettre le fichier de motif contenant MOTIF=(\bcrise\b)|(\beurope\b) au programme toujours sur la catégorie Economie et visualiser le graphe obtenu

Exemple : patron2graphe.exe "utf-8" ECONOMIEres_extract-NOM_PRP_NOM motif-utf-8.txt

Les résultats sont désormais plus lisibles . Le choix des motifs est complètement aléatoire. On constate cependant une forte présence des mots vides.

Exemple 3

Recherche des motifs MOTIF=(\bgauche\b)|(\bdroite\b) dans la catégorie politique.
patron2graphe.exe "utf-8" Politiqueres_extract-NOM_ADJ motif-utf-8.txt

Exemple 4


Il est également possible d’utiliser des expressions régulières pour gérer la casse ou visualiser plusieurs graphes à partir d’une même racine
Exemple: MOTIF=(\bjeune.*?\b) dans la catégorie UNE.
patron2graphe.exe "utf-8" ALAUNEres_extract-NOM_PRP_NOM motif-utf-8.txt