#/usr/bin/perl use HTML::Entities (); <<DOC; Axel COURT & Marjorie SEIZOU FEVRIER 2010 usage : perl Parcours-fils-Grep.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, filtreur.pl & nettoyeur.pl DOC #----------------------------------------------------------- ######################## # Opérations initiales # ######################## # test des paramètres if ($ARGV[0] eq "") { print " syntaxe : perl parcours-arborescence-fichiers-Grep.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 '<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 "<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é if ($file =~ /.+(2009.+0,2-$num,1-0,0\.xml)$/) { $file=$1; } $DUMPTMP .= "<$file>\n"; $DUMPTMP2 .= "DEBUTDEFICHIER \n"; # parcours du fichier RSS ligne à ligne 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 =~ /.*(\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"; } # séparation par un saut de ligne si deux balises se trouvent sur la même ligne # (supprime l'ambiguïté d'extraction du contenu des balises voulues) $ligne =~ s/></>\n</g; $texte .= $ligne; } # Formatage du texte $texte =~ s/\n/\ /g; $texte =~ s/\s/\ /g; $texte =~ s/\ \ +/\ /g; $texte =~ s/</\</g; $texte =~ s/>/\>/g; $texte =~ s/"/\"/g; $texte =~ s/°/e/g; $texteformate = HTML::Entities::decode($texte); # Remplacement des entités HTML # Remplacement de certaines entités nécessaire pour certaines rubriques $texteformate =~ s/'/\'/g; $texteformate =~ s/"/\"/g; # on supprime les appels à lire la suite de l'article $texteformate =~ s/\[suite\.\.\.\]//gi; # Récupération du titre et du résumé, contenus à l'intérieur de chaque balise <item> while ($texteformate =~ /<item>\ <title>(.+?)<\/title>.+?<description>(.+?)<\/description>/g) { $titre = $1; $resume = $2; # Formatage du titre chomp($titre); $titre = HTML::Entities::decode($titre); # Remplacement des entités HTML $titre =~ s/&/et/g; # remplacement des caractères spéciaux $titre =~ s/<.+>//g; # suppression des balises s'il y en a # 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é 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; # Formatage du résumé chomp($resume); $resume = HTML::Entities::decode($resume); # Remplacement des entités HTML $resume =~ s/&/et/g; # remplacement des caractères spéciaux $resume =~ s/<.+>//g; # suppression des balises s'il y en a # ponctuation : si le dernier caractère du résumé est un chiffre, mettre un espace entre le chiffre et le point $resume =~ s/(\d)\./$1 \./g; # 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 ($resume =~ /Retrouvez l'ensemble des dépêches sur http:\/\/www.lemonde.fr/) { $resume = "<RESUME/>"; $resumetxt = ""; } # Si le résumé est vide, on écrit une balise auto-fermante <RESUME/> elsif ($resume =~ /^ *$/) { $resume = "<RESUME/>"; $resumetxt = ""; } # 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 ($resume !~ /[!\?\.] *$/) { $resume .= "\."; } $resumetxt = $resume; $resume = "<RESUME>$resume<\/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$resume\n<\/ARTICLE>\n"; $DUMPTXT.="$titre $resumetxt\n"; $DUMPTMP.="<BLOC> $titre <BLOC> $resumetxt\n"; $DUMPTMP2.="BLOCDEPHRASE $titre BLOCDEPHRASE $resumetxt \n"; $DUMPLEXICO.="<num_article=$j>\n<titre_num_article=$j>\n$titre\n\n<resume_num_article=$j>\n$resumetxt\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); # ... en vidant au préalable la variable $texte $texte = ""; } } } # 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 }