Version HTML du script Parcours-fils-XMLRSS.pl

Pour télécharger le script : cliquez ici

#/usr/bin/perl
use XML::RSS;
use HTML::Entities ();
<<DOC; 
Axel COURT & Marjorie SEIZOU
FEVRIER 2010

usage : perl Parcours-fils-XMLRSS.pl	chemin-du-repertoire-a-parcourir
 
Le programme prend en entrée le nom du répertoire contenant les fichiers à traiter et demande à l'utilisateur quelle est la rubrique à traiter.
 
Le programme construit en sortie plusieurs fichiers contenant de résultat du filtrage selon l'usage qui en sera fait :
 - un fichier structuré au format XML :
<PARCOURS>
	<DESC>du fichier</DESC> 
	<FILTRAGE rubrique="">
		<FICHIER num="" date="">
			<ARTICLE num="">
				<TITRE>titre de l'article<\/TITRE>   					
				<RESUME>résumé de l'article</RESUME>
			</ARTICLE>						
		</FICHIER>
	</FILTRAGE>
</PARCOURS>

- un fichier texte structuré au format de balisage Lexico3 :
<filrss_rubrique=num>
<num_article=num>
<titre_num_article=num>
titre de l'article
<resume_num_article=num>
résumé de l'article
   
- un fichier non structuré au format texte ;

- un fichier contenant des balises comme <nom_du_fichier> pour la structuration du tableau des résultats des étiquetages de TTagger (feuille de style xsl).

- même chose pour Cordial, mais les repérages ne sont pas sous la forme de balises mais plutôt de mots-clés (type "BLOCDEPHRASE"), puisque Cordial s'entête à vouloir
étiqueter chaque élément contenu à l'intérieur d'une balise (contrairement à TT qui n'étiquette pas les balises si l'option -sgml est spécifiée)


Remarque : la construction de ce programme s'appuie sur les scripts donnés en cours : parcours-arborescence-fichiers.pl, extract-txt-avec-xml-rss.pl & nettoyeur.pl

DOC
#-----------------------------------------------------------
########################
# Opérations initiales #
########################

# test des paramètres
if ($ARGV[0] eq "") {
    print " syntaxe : perl parcours-arborescence-fichiers-XML_RSS.pl	chemin_du_repertoire_a_parcourir \n" ;
    exit(-1);
} 
 
# Récupération du chemin du répertoire à parcourir
my $rep="$ARGV[0]";
# on s'assure que le nom du répertoire ne se termine pas par un "/"
$rep=~ s/[\/]$//;

# Initialisation des différentes variables 
# numéro & nom de la rubrique, date de l'article traité
my $num="";
my $rubrique="";
my $date="";
# variables contenant le flux de sortie
my $DUMPXML="";
my $DUMPTXT="";
my $DUMPLEXICO="";
my $DUMPTMP=""; # afin d'utiliser des balises '<nom_fichier>' et de structurer le tableau des résultats
my $DUMPTMP2=""; # idem, mais pour Cordial (on insère des anotations de début de fichier)

# dialogue avec l'utilisateur
print "\nBienvenue sur le filtreur-nettoyeur de fils RSS \"Le Monde\" !\n\n";
print "Entrez l'identifiant de la rubrique a traiter : \n";
print "0 : A la une\t\t\t\t6 : Medias\n
1 : International\t\t\t7 : Rendez-vous\n
2 : Europe\t\t\t\t8 : Sports\n
3 : Livres\t\t\t\t9 : Opinions\n
4 : Cinema\t\t\t\t10 : Planete\n
5 : Technologies\t\t\t11 : Voyages\n";

# Récupération de l'identifiant de la rubrique tapé par l'utilisateur
$id = <STDIN>;
# Suppression du dernier caractère de retour à la ligne s'il y en a un
chomp($id);

# Choix de la rubrique
if ($id eq 0) {$num="3208";}
elsif ($id eq 1) {$num="3210";}
elsif ($id eq 2) {$num="3214";}
elsif ($id eq 3) {$num="3260";}
elsif ($id eq 4) {$num="3476";}
elsif ($id eq 5) {$num="651865";}
elsif ($id eq 6) {$num="3236";}
elsif ($id eq 7) {$num="3238";}
elsif ($id eq 8) {$num="3242";}
elsif ($id eq 9) {$num="3232";}
elsif ($id eq 10) {$num="3244";}
elsif ($id eq 11) {$num="3546";}
else { print "Hum... Je n'ai pas compris : je traiterai donc la rubrique 'A la Une'\n"; $num="3208"; }

# Stockage des chemins & noms des différentes sorties
my $outputxml="SORTIE_$num.xml";
my $outputtxt="SORTIE_$num.txt";
my $outputlexico="SORTIE_formatlexico3_$num.txt";
my $outputtmp="SORTIE_txtbaliseTreeTagger_$num.txt";
my $outputtmp2="SORTIE_txtbaliseCordial_$num.txt";
# Ouverture des fichiers de sortie (précision de l'encodage : Latin-1)
if (!open (OUTPUTXML,">:encoding(iso-8859-1)", "$outputxml")) { die "Pb a l'ouverture du fichier $outputxml"};
if (!open (OUTPUTTXT,">:encoding(iso-8859-1)", "$outputtxt")) { die "Pb a l'ouverture du fichier $outputtxt"};
if (!open (LEXICO,">:encoding(iso-8859-1)", "$outputlexico")) { die "Pb a l'ouverture du fichier $outputlexico"};
if (!open (TMP,">:encoding(iso-8859-1)", "$outputtmp")) { die "Pb a l'ouverture du fichier $outputtmp"};
if (!open (TMP2,">:encoding(iso-8859-1)", "$outputtmp2")) { die "Pb a l'ouverture du fichier $outputtmp2"};


############################################################
# Lancement de la procédure sur l'arborescence de fils RSS #
############################################################
# initialisation du compteur de fichiers et du hash qui contiendra les articles déjà extraits
my $i=1;
%DUMPARTICLES;
# le chemin du répertoire est passé en paramètre
&parcoursarborescencefichiers($rep);

########################
# Création des sorties #
########################

# Une fois que le parcours des dossiers + traitement des fichiers est terminé
# Création du fichier XML
print OUTPUTXML "<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"yes\"?>\n";
#print OUTPUTXML "<?xml-stylesheet href=\"style.css\" type=\"text/css\"?>\n";
print OUTPUTXML "<PARCOURS>\n";
print OUTPUTXML "<DESC>Ce fichier contient les titres et les résumés des articles de la rubrique ".$rubrique." des fils RSS du journal Le Monde ".$annee."</DESC>\n";
print OUTPUTXML "<FILTRAGE rubrique=\"".$rubrique."\" identifiant=\"$num\">\n".$DUMPXML."</FILTRAGE>\n";
print OUTPUTXML "</PARCOURS>\n";
close(OUTPUTXML);

# Création du fichier texte
print OUTPUTTXT "$DUMPTXT";
close (OUTPUTTXT);

# sortie au format lexico3 (balises/"clés" spécifiques, format texte)
print LEXICO "<filrss_rubrique=".$num.">\n\n";
print LEXICO "$DUMPLEXICO";
close(LEXICO);

# sortie temporaire pour TreeTagger
print TMP "<$rubrique>\n\n";
print TMP "$DUMPTMP";
close(TMP);

# sortie temporaire pour Cordial
print TMP2 "LIGNEDERUBRIQUE \n\n";
print TMP2 "$DUMPTMP2";
close(TMP2);


#########################################################
# Lancement de la procédure d'étiquetage par Treetagger #
#########################################################
&lancetreetagger;

# procédures d'extraction des patrons avec xslt
# &extractionxslt_NA
# &extractionxslt_NPN


######################
# Fin du programme ! #
######################
exit;


#----------------------------------------------
##############
# PROCEDURES #
##############

# Procédure parcoursarborescencefichiers
sub parcoursarborescencefichiers {
	
	# @_ ('tableau spécial') contient l'ensemble des arguments passés en paramètre à la procédure (ici, un seul)
	# la fonction 'shift' extrait le premier élément du tableau et en renvoie sa valeur, stockée ici sous $path
	my $path = shift(@_);
	# Ouverture du répertoire contenu dans variable $path 
    opendir(DIR, $path) or die "Problème à l'ouverture de $path: $!\n";
	# lecture et stockage du contenu du répertoire dans @files
    my @files = readdir(DIR);
	# fermeture du répertoire
    closedir(DIR);

# Note : à la premire itération, l'argument passé en paramètre est $rep, càd le chemin du répertoire à parcourir
# @files ne contient alors que les premiers éléments de l'arborescence => ici des dossiers avec les noms des mois
# Il faut donc au moins 4 itérations pour avoir accès aux fichiers effectifs et les stocker sous @files : (corpus/)mois/jour/heure/filRSS.xml

# ---------------
	# on trie le contenu de @files (dossiers ou fichiers) alphabétiquement/ordinalement (ici, seulement les dossiers "mois" posent problème)
	# de cette façon, l'extraction et l'écriture en sortie du contenu des fils RSS se fera dans l'ordre mois/jour/heure
	@files = sort par_num @files;
	
    foreach my $file (@files) {
	# passe au fichier suivant si jamais le nom du fichier est . ou .. (fichiers système)
	next if $file =~ /^\.\.?$/;
	# modification du fichier étudié pour que son nom indique clairement l'arborescence
	# concaténation => nom de $file est en réalité $path + $file
	$file = $path."/".$file;
	# si $file contient en réalité un répertoire (-d, directory)
	# on relance la procédure, avec comme argument $file pour qu'il le parcourt également
	if (-d $file) {
	    &parcoursarborescencefichiers($file);	
	}
    
	# initialisation d'un compteur pour numéroter les articles du fichier
	my $j=1;
	# si l'argument passé $file est de type fichier (et non pas un fichier système . ou ..) 
	# et que c'est un fichier RSS contenant le numéro de la rubrique à traiter
	if ((-f $file) && ($file =~ /0,2-$num,1-0,0\.xml$/)) {
		# on l'ouvre en précisant l'encodage en UTF-8
		open(FICHIER, "<:encoding(utf-8)", "$file") or die "Le fichier de la rubrique choisie est introuvable...\n";
		# Indication pour l'utilisateur
		print "Ouverture du fichier $file\n";
		
		# On récupère le nom du fichier en vue de l'inscrire dans le tableau des résultats et qu'il y ait une ligne par fichier taggé
		my $nomdefile = "";
		if ($file =~ /.+(2009.+0,2-$num,1-0,0\.xml)$/) {
			$nomdefile = $1;
		}
		$DUMPTMP .= "<$nomdefile>\n";
		$DUMPTMP2 .= "DEBUTDEFICHIER \n";
				
		# Construction d'un objet de type RSS
		my $rss = new XML::RSS;
		# Construction de l'arborescence xml à partir du fil RSS
		# de cette façon, perl tient compte de l'arborescence XML
		$rss->parsefile ($file);
		# Lecture ligne à ligne de l'arborescence du fil
		while ($ligne = <FICHIER>) {
		# Récupération du nom de la rubrique
			if ($ligne =~ /<channel><title>(.*?Le Monde\.fr.*?)<\/title><link>/) {
				$rubrique = $1;
			}
			# Récupération de la date de l'article
			if ($ligne =~ /<lastBuildDate>(.+200.).+\ ?<\/lastBuildDate>/) {
				$date = $1;
				# récupération de l'année pour la balise <DESC> de la sortie xml
				# if ($date =~ /.+(20\d\d).*/) {
				if ($date =~ /.*(\d+)\s(\w+)\s(20\d\d).*/) {
					$jour=$1;
					$mois=$2;
					$annee=$3;
				}
				$DUMPXML.="<FICHIER num=\"$i\" date=\"$date\">\n";
				$DUMPLEXICO .= "<num_fichier=$i nom_fichier=$file jour=$jour mois=$mois annee=$annee>\n";
			}
		}
			# Pour chaque balise <item></item> (contient l'article)
			foreach my $item (@{$rss->{'items'}}) {
				# Récupération du titre de l'article dans la balise <title></title>
				$titre = $item->{'title'};
			# Formatage du titre 
				$titre =~ s/&/et/g; # remplacement des caractères spéciaux pour les titres
				# on ajoute des points à la fin de chaque titre afin d'éviter un étiquetage "contextuel" entre la fin du titre et le début du résumé
				chomp($titre);
				if ($titre !~ /[!\?\.] *$/) {
					$titre .= "\.";
				}
				# ponctuation : si le dernier caractère du titre est un chiffre, mettre un espace entre le chiffre et le point 
				# (sinonTTagger considère que c'est une abréviation...)
				$titre =~ s/(\d)\./$1 \./g;
				
				# Récupération du contenu de la balise <description></description> (résumé de l'article)
				$contenu = $item->{'description'};
			# Formatage du résumé
				$contenu =~ s/\n/\ /g;
				$contenu =~ s/\s/\ /g;
				$contenu =~ s/\ \ +/\ /g;
				$contenu =~ s/</\</g;
				$contenu =~ s/>/\>/g;
				$contenu =~ s/"/\"/g;
				$contenu =~ s/°/e/g;
				# Remplacement de certaines entités nécessaire pour certaines rubriques (WTF ?!)
				$contenu =~ s/&#39;/\'/g;
				$contenu =~ s/&#34;/\"/g;
				# Remplacement des entités HTML
				$contenuformate = HTML::Entities::decode($contenu);
				chomp($contenuformate);
				$contenuformate =~ s/&/et/g; # remplacement des caractères spéciaux pour 'xml_rss'
				$contenuformate =~ s/<.+>//g; # suppressions des balises html
				# on supprime les appels à lire la suite de l'article
				$contenuformate =~ s/\[suite\.\.\.\]//gi;

				
				# Si le résumé ne contient que la phrase ci-dessous, on ne récupère rien et on écrit une balise auto-fermante <RESUME/> 
				# (pour le fichier de sortie xml)
				if ($contenuformate =~ /Retrouvez l'ensemble des dépêches sur http:\/\/www.lemonde.fr/) {
					$contenuxml = "<RESUME/>";
					$contenutxt = "";
				}
				# Si le résumé est vide, on écrit une balise auto-fermante <RESUME/>
				elsif ($contenuformate =~ /^ *$/) {
					$contenuxml = "<RESUME/>";
					$contenutxt = "";
				}
				# Dans tous les autres cas, on a un contenu potentiellement exploitable :
				else {
					# si le résumé ne se termine pas par un point, on en rajoute un (but double : pas d'étiquetage contextuel 
					# et séparation par phrase dans le tableau des résultats)
					if ($contenuformate !~ /[!\?\.] *$/) {
						$contenuformate .= "\.";
					}
					# ponctuation : si le dernier caractère du résumé est un chiffre, mettre un espace entre le chiffre et le point 
					$contenuformate =~ s/(\d)\./$1 \./g;
					$contenutxt = $contenuformate;
					$contenuxml = "<RESUME>$contenuformate<\/RESUME>";
				}
				
				# Filtrage des doublons d'articles 
				#(pas d'ajout si le titre est déjà présent dans le hash %DUMPARTICLES, contenant les titres des contenus déjà extraits)
				unless (exists $DUMPARTICLES{$titre}) {
					$DUMPXML.="<ARTICLE num=\"$j\">\n<TITRE>$titre<\/TITRE>\n$contenuxml\n<\/ARTICLE>\n";
					$DUMPTXT.="$titre $contenutxt\n";
					$DUMPTMP.="<BLOC> $titre <BLOC> $contenutxt\n";
					$DUMPTMP2.="BLOCDEPHRASE $titre BLOCDEPHRASE $contenutxt \n";
					$DUMPLEXICO.="<num_article=$j>\n<titre_num_article=$j>\n$titre\n\n<resume_num_article=$j>\n$contenutxt\n\n\n";
					
					# Incrémentation du compteur d'articles
					$j++;
					# Ajout dans le hash %DUMPARTICLES du titre du résumé venant d'être extrait, pour le filtrage
					$DUMPARTICLES{$titre}++;
				}
			}
			# Incrémentation du compteur de fichiers
			$i++;
			# Fermeture de la balise </FICHIER> dans le fichier XML
			$DUMPXML.="<\/FICHIER>\n";
			# saut de ligne pour le tableau des résultats
			$DUMPTMP.="\n";
			$DUMPTMP2.="\n";
			# passage au fichier suivant (# recurse!)
			close(FICHIER);
		}
    }
}


# Procédure d'étiquetage Treetagger
sub lancetreetagger {
	system("perl TreeTagger/tokenise-fr.pl $outputtmp | tree-tagger TreeTagger/french.par -lemma -token -no-unknown -sgml > treetagger.txt");
}

# Procédure de tri alphabétique
sub par_num { return $a <=> $b }