out commence par le filtrage et l'extraction...
Il s'agit pour cette première boîte à outils de récupérer les informations qui nous intéressent à partir d'un ensemble de fils RSS, c'est-à-dire de documents XML structurés, et au moyen d'un (ou plutôt plusieurs) script(s) Perl. Pour cela, plusieurs étapes à accomplir:

  1. Parcours de l'arborescence du corpus de fils RSS pour pouvoir lire tous les fichiers qui s'y trouvent
  2. Récupération des informations qui nous intéressent... d'une part, (a) grâce à des expressions régulières, et d'autre part, (b) grâce au module XML::RSS de Perl
  3. Diviser les données récupérées en rubriques

Ces différents points ne sont donc bien évidemment pas indépendants les uns des autres, au contraire, ils s'enchaînent de sorte à obtenir en sortie le nécessaire à la BàO 2. Parmi ces méthodes imposées, seule celle qui concerne la récupération des informations va réellement faire une différence et proposer deux chemins différents. On présentera également deux solutions au point (3) suite à des choix personnels.

I. Parcours de l'arborescence

Le corpus que l'on veut traiter se trouve éparpillé dans des fichiers différents qui constituent les feuilles d'un arbre suivant la structure "REP\année > REP\mois > REP\jour > FIC\fils RSS". La première chose à faire est donc d'aller chercher ces fichiers en parcourant cette arborescence. En script glosé, ça donne cette fonction:

On récupère le chemin vers un répertoire racine
On ouvre ce répertoire, on récupère le nom de tous les objets qu'il contient puis on le ferme
Pour chacun de ces objets...
	... on vérifie qu'il ne s'agit pas du répertoire courant ou parent (resp . et ..)
	-> si cet objet est un répertoire (-d):
		-> on repart du début!
	... si cet objet est un fichier (-f):
		... on vérifie qu'il s'agit bien d'un fichier XML
		-> on ouvre ce fichier, on lit son contenu, le ferme.
	sinon, on le ferme.

On note la présence de deux opérateurs unaires Perl intéressants, -d et -f qui permettent de tester respectivement l'existence d'un répertoire ou d'un fichier, mais surtout la construction de cette structure, basée sur la récursivité (d'où le "on repart du début!").

ENCODAGE: A partir de cette fonction de base, il est possible de parcourir l'arborescence de notre corpus, et de récupérer tous les fichiers qu'elle contient. Mais pour être sûr de ne pas avoir de problèmes d'encodage gênants pour la suite, nous avons dû ajouter la reconnaissance en amont d'un encodage via la commande Unix file -i que l'on spécifiera au moment de l'ouverture du fichier. Malheureusement, aussi pratique qu'elle soit, cette méthode n'est pas infaillible. En effet, il s'agit d'une commande qui provient et ne fonctionne que dans les environnements Unix, et qui de plus se base sur la structure des octets pour déterminer un encodage, ce qui n'est réellement possible que pour ASCII et UTF-8 (c'est bien connu, la GIM c'est toujours utile!)... d'où les "unknown 8-bit" et bizarreries.

Il existe une autre solution pour récupérer l'encodage de nos fichiers RSS: aller la chercher directement dans l'en-tête XML , présente dans tous les fichiers, sans quoi ils ne seraient pas bien formés (!). Mais cette méthode a aussi ses inconvénients: si l'en-tête est obligatoire, l'attribut encoding ne l'est pas, l'encodage inscrit n'est pas forcément juste (errare humanum est, n'est-ce pas) et enfin, il faudrait ouvrir le fichier, détecter son encodage, le refermer, et le rouvrir avec le bon encodage! Autant de problèmes qui nous ont poussées à finalement abandonner cette alternative...

Enfin pas tout à fait. Nous avons utilisé cette alternative dans le cas où l'encodage renvoyé était binary:

if ($encodage eq "binary") { # si l'encodage détecté est binaire, on ouvre le fichier pour récupérer la valeur txt de l'encodage
	print "$file\n";
	open(FILEIN1,"<",$file);
	while (my $ligne=) {
		chomp($ligne); # on supprime le retour chariot	
		$texte.= $ligne; # on concatene chaque ligne 
	}
	# Récupération de l'encodage
	if ($texte =~ m/encoding=\'(.+?)\'/g) {
		$encodage1=$1;
	}
	close(FILEIN1);
	
	# Réouverture avec l'encodage récupéré 
	open(FILEIN,"<:encoding($encodage1)",$file);
}
Et nous sommes loin d'être au bout de nos peines concernant l'encodage...

II. Récupération des informations

Une fois le contenu de chaque fichier XML récupéré dans une variable, il faut pouvoir trier ce que l'on veut de ce qui ne nous intéresse pas, soit repérer les titres, les dates et les résumés des articles du Monde. Cette étape confronte les capacités de deux méthodes: l'une basée sur les expressions régulières, et l'autre sur la structure d'un arbre XML. Mais d'abord, un petit rappel.

REGEX(P), kézako? Tout simplement un des plus puissants et des plus importants outils de la trousse de tout TAListe! Il s'agit en fait d'un système de caractères et d'opérateurs qui permet de trouver un motif et d'effectuer des modifications sur des chaînes de caractères. Un outil monstre, donc. Littéralement.

XML::RSS? Comme c'est indiqué dans son nom, eXtensible Markup Language, XML est un langage de balisage extensible, utilisé, entre autres, pour sa capacité à structurer les informations. Les documents XML sont donc constitués de balises et de texte, organisés en arbre de manière très stricte; organisation que le module XML::RSS comprend puisqu'il s'agit d'un parser spécialisé dans un format XML particulier: le RSS. Pour plus d'information, docStruct!

REGEXPXML::RSS
date $ligne =~ m/<pubDate>([^<]*)<\/pubDate>/
titre $ligne =~ m/<title>([^<]*)<\/title>/ my $titre=$item->{'title'}
résumé $ligne =~ m/<description>([^<]*)<\/description>/ my $description=$item->{'description'}

Si l'aspect des expressions régulières peut être rebutant, c'est pourtant la compréhension et l'application du module XML::RSS qui est plus compliquée (système de tableaux et de clés). En dehors de cette difficulté liée à l'utilisation de ces deux méthodes, le module a peu de défauts. Il a effectivement plusieurs avantages: une application intelligente pour notre projet (module RSS pour un corpus RSS), et une grande adaptabilité compte tenu du fait que, contrairement aux expressions régulières, ce module navigue dans l'arbre XML de nœud en nœud. Là où les REGEXP ne voient qu'une suite de caractère en structure plate, XML::RSS, lui, voit une véritable structure hiérarchique. Par ailleurs, RSS étant un format universel d'application XML (voir accueil ), il est peu probable que sa structure change et nous oblige à modifier nos expressions. Pour les REGEXP, c'est une autre histoire...

MULTILIGNE: En testant notre script sur une petite portion du corpus principal, on se rend très vite compte d'un problème: les fils RSS ne sont pas tout à fait uniformes. Même si la structure globale est respectée (XML oblige), on trouve tantôt des fils ayant une balise par ligne, et tantôt des fils où quasiment tout est regroupés sur 2 ou 3 lignes seulement. Ces changements ne sont pas significatifs pour XML::RSS, mais nous contraignent à réadapter nos expressions régulières. Solution drastique: tout concaténer sur une ligne, en ayant pris soin de nettoyer chaque ligne auparavant, puis on récupère toutes les informations que l'on veut en une seule REGEXP!

while (my $ligne=<FILEIN>) {
	chomp($ligne); # suppression retour chariot	
	$texte.= $ligne; # concaténation des lignes
}

$texte =~ s/>[[:space:]]</></g; # suppression des blancs entre balises
[...]
$texte =~ /<item>.+?<title>([^<]*)<\/title>.+?
<description>([^<]*)<\/description>.+?<pubDate>([^<]*)<\/pubDate>/g

Un autre problème se pose lors de la récupération des informations, et une nouvelle fois à cause de l'hétérogénéité des fichiers du corpus. Il s'agit cette fois d'un problème lié à l'encodage. Nous avions, dans la première utilisé un moyen de repérer l'encodage d'un fichier et de l'ouvrir dans cet encodage et ainsi éviter les problèmes de lecture; mais tout ceci n'agit en rien sur l'écriture: l'encodage du texte même n'a pas changé! Il faut donc convertir les éléments récupérés. Et pour cela, nous avons utilisé une fonction de transcodage disponible dans un autre module de Perl, Unicode::String qw(utf8).

Le script avec le module XML::RSS nous a également permis de détecter une complication liée au corpus: parmi les encodages renvoyés par file , on trouve binary. Si tout fonctionnait bien dans le script des REGEXP, le second plante systématiquement à chaque rencontre avec un binary. La preuve en image:
Probleme dans XML::RSS lie a l'encodage binary

Un problème dans le script du parser XML? Probablement pas. Rapide print $file, coup d'œil au fichier qui pose problème et consternation: les fichiers encodés binary sont en réalité vides, d'où une mauvaise lecture de variables vides dans le parser, d'où le plantage total... Solution? On passe! Aucun traitement donc dès qu'on tombe sur un document binary, puisqu'un document vide ne nous intéresse nullement. Et voilà comment se règle un problème de plusieurs heures et touffes de cheveux arrachées: next if $encodage eq "binary";

III. Division en rubriques

Jusqu'ici, nos sorties contiennent les informations des fichiers de l'ensemble du corpus du Monde 2011, qui lui fait très exactement 313,7 Mo. Sachant que nos scripts produisent plusieurs sorties à cause des différentes méthodes employées et des entrées à produire pour les prochaines boîtes, y a de quoi affoler gedit. Résultat, il a été jugé pertinent de diviser les sorties globales en sorties par rubriques du Monde. Ce choix est d'autant plus pratique que le logiciel Cordial ne peut pas prendre en entrée des fichiers de plus de 2 Mo.

Diviser en rubriques signifie concrètement qu'il doit y avoir un seul fichier par type de sortie, pour chacune des rubriques du Monde. Tous les articles de rubrique "Culture" doivent, par exemple, être récupérés dans le même fichier XML de sortie. Décomposons les étapes:

  1. Recupération des noms des rubriques
  2. Nettoyage des noms récupérés
  3. Création d'un fichier par rubrique (/!\ uniquement si ce fichier n'existe pas déjà)
  4. Ecriture de chacun des documents dans le bon fichier de sortie (soit, le fichier portant le même nom de rubrique

Traduction en script dans la fonction suivante:

sub Rubriques {
my $txt = shift(@_);

# Récupération de la rubrique (étape 1)
if ($txt=~ /<channel><title>([^>]+)<\/title>/) {
	my $rub=$1;
	
	# Nettoyage dans une fonction (étape 2)
	$rub=&nettoieRub($rub);
		
		# Création d'un dictionnaire pour tester l'existence d'un fichier (étape 3)
		if (!(exists $dicoRubriques{$rub})) {
		$dicoRubriques{$rub}++;
		
		# Création des fichiers (étape 3)
		$output1="./SORTIES/SORTIEregexp-$rub.txt";
		open(FILEOUTTXT,">>:encoding($encodagesortie)",$output1);
		$output2="./SORTIES/SORTIEregexp-$rub.xml";
		open(FILEOUTXML,">>:encoding($encodagesortie)",$output2);

		print FILEOUTXML "<?xml version=\"1.0\" encoding=\"$encodagesortie\" ?>\n";
		print FILEOUTXML "<PARCOURS>\n";
		print FILEOUTXML "<NOM>CG&IW</NOM>\n";
		print FILEOUTXML "<FILE>\n";
		print FILEOUTXML "<NAME>$ARGV[0]</NAME>\n";
	}
	# Réouverture des bons fichiers "rubriques" sans création
	$output1="./SORTIES/SORTIEregexp-$rub.txt";
	open(FILEOUTTXT,">>:encoding($encodagesortie)",$output1);
	$output2="./SORTIES/SORTIEregexp-$rub.xml";
	open(FILEOUTXML,">>:encoding($encodagesortie)",$output2);
	}

Cette solution est une première alternative. La deuxième, encore plus économe en temps de traitement et en espace mémoire (arguments non négligeables!), consiste à ne créer de sorties que pour les rubriques demandées par l'utilisateur. Pour que cela soit possible, il faut ajouter une étape majeure à la solution précédente: l'utilisateur devant choisir entre les différentes rubriques du corpus, il faudrait déjà que ces rubriques ait été récupérées en amont! Autrement dit, il doit y avoir deux grands parcours du corpus: le premier pour récupérer le nom des rubriques, que l'on soumettra à l'utilisateur, puis un deuxième pour récupérer effectivement les fichiers de la rubrique demandée.

La construction d'une fonction Parcours pour les rubriques (première boucle) a été calquée sur celle du Parcours arborescence et la fonction qui récupère les rubriques et crée les fichiers adéquats a également été reprise. On remarque simplement quelques ajouts:

if (($rub eq $choix) && ($etat eq 0)) {
		$output1="./SORTIES/SORTIEregexp-$rub.txt";
		open(FILEOUTTXT,">>:encoding($encodagesortie)",$output1);
		$output2="./SORTIES/SORTIEregexp-$rub.xml";
		open(FILEOUTXML,">>:encoding($encodagesortie)",$output2);

		print FILEOUTXML "<?xml version=\"1.0\" encoding=\"$encodagesortie\" ?>\n";
		print FILEOUTXML "<PARCOURS>\n";
		print FILEOUTXML "<NOM>CG&IW</NOM>\n";
		print FILEOUTXML "<FILE>\n";
		print FILEOUTXML "<NAME>$ARGV[0]</NAME>\n";

		$etat=1;	# etat devient True!
		}
	if ($rub eq $choix) {
		# Réouverture des bons fichiers "rubriques", sinon print dans le vide
		$output1="./SORTIES/SORTIEregexp-$rub.txt";
		open(FILEOUTTXT,">>:encoding($encodagesortie)",$output1);
		$output2="./SORTIES/SORTIEregexp-$rub.xml";
		open(FILEOUTXML,">>:encoding($encodagesortie)",$output2);
	}

On impose dans la fonction Rubrique deux conditions que l'on glose: "si la rubrique récupérée pour le fichier courant correspond à celui du choix de l'utilisateur ($rub eq $choix) ET si un fichier n'a pas encore été créé ($etat eq 0) alors, crée-les et ouvre-les" puis "si la rubrique correspond bien au choix de l'utilisateur, ouvre les fichiers déjà créés". Petite remarque: ici une variable $etat a été créée pour que l'on sache si oui non un fichier avait déjà été créé; on a choisi pour valeur les booléens 0 et 1, à défaut de pouvoir utiliser False et True, qui eux fonctionnent sur Python (!).

Outils


Download