Boîte à Outils n°1

La première étape de ce projet a pour but d'extraire les titres et les descriptions contenus dans chaque fichier XML de l'arborescence.
Cette tâche s'effectue via un script perl, deux méthodes étant à notre disposition : la première en utilisant des expressions régulières, et la seconde via le module Perl XML::RSS.

1. Méthode REGEX

(Télécharger le programme bao1_regex.pl)

Le programme contient plusieurs sous-programmes : un pour parcourir l'arborescence et effectuer les traitements, un second pour repérer les noms des rubriques, et un autre servant à "nettoyer" les titres et les descriptions. Le programme aura donc la structure suivante :

#!/usr/bin/perl use strict; # modules use Unicode::String qw(utf8); use HTML::Entities; # un répertoire en argument my $rep="$ARGV[0]"; # on s'assure que le nom du répertoire ne se termine pas par un "/" $rep=~ s/[\/]$//; # -- Création d'un répertoire de sorties -- my $sortie="Sorties/"; if (! -e $sortie){ mkdir($sortie) or die ("Problème à la création du répertoire : $!"); } $sortie="Sorties/BAO1/"; if (! -e $sortie){ mkdir($sortie) or die ("Problème à la création du répertoire : $!"); } # -- fonction qui repère les rubriques -- &repererubriques($rep); # ----------------------------------------------------- # pour chaque rubrique, écriture des en-têtes des fichiers de sorties xml my @liste_rubriques = keys(%dicoRUB); foreach my $rub (@liste_rubriques) { my $output1 = $sortie.$rub.".xml"; my $output2 = $sortie.$rub.".txt"; if (!open (OUTXML,">:encoding(utf-8)", $output1)) { die "Can't open file $output1"}; if (!open (OUTTXT,">:encoding(iso-8859-1)", $output2)) { die "Can't open file $output2"}; print OUTXML "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"; print OUTXML "<EXTRACTION>\n"; close(OUTXML); close(OUTTXT); } # -- fonction parcourant l'arborescence et effectuant les traitements -- &parcoursarborescencefichiers($rep); # -------------------------------------------------------------------------------------------------- # écriture de la balise fermante finale des fichiers xml foreach my $rub (@liste_rubriques) { my $output1 = $sortie.$rub.".xml"; if (!open (OUTXML,">:encoding(utf-8)", $output1)) { die "Can't open file $output1"}; print OUTXML "</EXTRACTION>\n"; close(OUTXML); } exit;

Commençons tout d'abord par la fonction parcoursarborescencefichiers :

Le parcours d'arborescence se fait avec des conditions : on utilise 'if (-d $file)' pour identifier les dossiers, et 'id (-f $file)' pour les fichiers.

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) { next if $file =~ /^\.\.?$/; $file = $path."/".$file; if (-d $file) { &parcoursarborescencefichiers($file); } if (-f $file) { # commencer le traitement ... }

On commence ensuite le traitement si le fichier trouvé se termine par ".xml", et s'il ne commence pas par "fil" (autres fichiers faisant partie de l'arborescence, qui ne nous intéressent pas) :

if (($file=~/\.xml$/) && ($file!~/\/fil.+\.xml$/)

Une autre condition nécessaire au traitement est l'identification de l'encodage, via l'expression :

$texte=~/encoding ?= ?[\'\"]([^\'\"]+)[\'\"]/i;

On récupère l'encodage via la commande my $encodage=$1; où $1 désigne le contenu des parenthèses. Si la variable $encodage n'est pas vide, on continue le traitement (écriture des balises dans les sorties xml et nettoyage) :

if ($encodage ne "") { my $texteXML="<file>\n"; $texteXML.="<name>$file</name>"; $texteXML.="<items>\n"; my $texteBRUT=""; open(FILE,"<:encoding($encodage)", $file); $texte=""; # suppression des sauts de ligne while (my $ligne=<FILE>) { $ligne =~ s/\n//g; $ligne =~ s/\r//g; $texte .= $ligne; } close(FILE); # supprimer les espaces entre les balises $texte=~s/> *</></g;

Vient ensuite la détection des rubriques :

# recherche de la rubrique $texte=~/[<channel>|<atom.+>]<title>([^<]+)<\/title>/; my $rub=$1; $rub=~s/Le ?Monde.fr ?://g; $rub=~s/ ?: ?Toute l'actualité sur Le Monde.fr.//g; $rub=~s/\x{E8}/e/g; # è $rub=~s/\x{E0}/a/g; # à $rub=~s/\x{E9}/e/g; # é $rub=~s/\x{C9}/e/g; # É $rub=~s/ //g; $rub=uc($rub); # mise en majuscules $rub=~s/-LEMONDE.FR//g; $rub=~s/:TOUTEL'ACTUALITESURLEMONDE.FR.//g;

Les noms des rubriques se situent entre les balises title, suivant directement la balise <channel>. Or, dans certains cas, une balise <atom> suit la balise <channel>, ce qui a créé des problèmes lors de ma première tentative en utilisant simplement l'expression : /<channel><title>([^<]+)<\/title>/. J'ai donc intégré cette information via la disjonction [<channel>|<atom.+>], ce qui a résolu le problème.

Comme vu précédemment, la récupération de la rubrique se fait avec la variable $1, que l'on normalise (en supprimant les accents et les informations en trop) et met en majuscules. J'ai ici choisi d'utiliser les codes de caractères Unicode \x{...} pour identifier les lettres accentuées.

On passe ensuite à la détection des titres et descriptions, que l'on stocke dans des variables pour effectuer plusieurs traitements :

while ($texte =~ /<item><title>(.+?)<\/title>.+?<description>(.+?)<\/description>/g) { my $titre=$1; my $desc=$2; # si l'encodage n'est pas UTF-8, réencoder via le module Unicode::String if (uc($encodage) ne "UTF-8"){ utf8($titre); utf8($desc); } # traiter les caractères diachrités avec le module HTML::Entities $titre = HTML::Entities::decode($titre); $desc = HTML::Entities::decode($desc); $titre = &clean($titre); $desc = &clean($desc);

Le premier traitement se fait ici avec le module Unicode::String qw(utf8) permettant d'encoder des chaînes en UTF-8. Ensuite, un second module, HTML::Entities, est utilisé pour convertir les caractères en entités HTML afin d'éviter des problèmes de codage lors de la création des nouveaux fichiers.
La fonction clean appelée ici permet de supprimer les balises non pertinentes intégrées aux titres et descriptions, ainsi qu'à filtrer certains caractères restants posant problème.

La fonction clean

Enfin, après s'être chargé de stocker et de traiter les titres et descriptions, il faut s'assurer de ne pas les extraire plusieurs fois. Pour éviter les doublons, une méthode consiste à placer les titres et descriptions rencontrés dans des tables de hashage et d'ajouter une condition : si le titre ou la description ne se trouvent pas déjà dans les tables, alors on les extrait. On commence donc par initialiser les tables au début du programme (juste avant l'utilisation de la fonction repererubriques) :

my %dicoTITRES=();
my %dicoDESC=();

Puis à ajouter la condition après l'étape de nettoyage ci-dessus :

if (!(exists $dicoTITRES{$titre}) and (!(exists $dicoDESC{$desc}))){ $dicoTITRES{$titre}++; $dicoDESC{$desc}++; $texteXML.="<item>\n<title>$titre</title>\n<description>$desc</description>\n</item>\n"; print OUTTXT "$titre\n"; print OUTTXT "$desc\n"; }

La fonction repererubriques conserve la même structure que parcoursarborescencefichiers : on commence par parcourir l'arborescence avec les commandes if (-d $file) et if (-f $file), on recherche l'encodage et supprime les sauts de lignes puis on repère les rubriques. Par contre, on ajoute en plus une table de hashage au début du programme (my %dicoRUB=();) pour mémoriser les noms de rubriques :

2. Méthode XML::RSS

(Télécharger le programme bao1_RSS.pl)

L'utilisation de XML::RSS ne change pas la configuration du programme précédent : il suffit simplement de remplacer la recherche des titres et descriptions via les expressions régulières par les commandes prévues par le module, comme suit :

Début de la boucle avec regex :

while ($texte =~ /<item><title>(.+?)<\/title>.+?<description>(.+?)<\/description>/g) { my $titre=$1; my $desc=$2;

Début de la boucle avec module RSS :

# Création d'un objet XML::RSS stocké dans la variable $rss + parcours du fichier my $rss = new XML::RSS; $rss->parsefile($file); # Extraction des titres et descriptions pour chaque noeud sous 'items' foreach my $item(@{$rss->{ 'items' }}) { my $titre = $item->{ 'title' }; my $desc = $item->{ 'description' };

L'utilisation du module a l'avantage de pouvoir se déplacer directement à l'intérieur de l'arborescence des fichiers xml, l'objet RSS créé à la première ligne permettant d'associer des références à des clés. On obtient donc un tableau associatif liant des noeuds entre eux, dans ce cas précis le noeud 'item' associé aux noeuds 'title' et 'description' grâce à l'opérateur '->'.

Les deux méthodes donnent au final les mêmes résultats, que nous allons voir maintenant. A noter que la méthode par XML::RSS prend plus de temps que celle par expressions régulières.

Résultats

Exemple de résultat XML : SPORT.xml
Exemple de résultat TXT : SPORT.txt