diff --git a/IRC3.pl b/IRC3.pl new file mode 100755 index 0000000..ecb89ca --- /dev/null +++ b/IRC3.pl @@ -0,0 +1,422 @@ +#!/usr/bin/perl + + +# Déclaration des pragmas +use strict; +use utf8; +use open qw/:std :utf8/; + + +# Appel des modules externes +use Encode qw(is_utf8); +use Getopt::Long; + +# Recherche du nom du programme +my ($programme) = $0 =~ m|^(?:.*/)?(.+)|; +my $usage = "Usage : \n $programme -t table -r répertoire [ -s fichier_sortie ] [ -l log ] [ -cq ]\n" . + " $programme -t table -f fichier_entrée [ -s fichier_sortie ] [ -l log ] [ -cq ]\n" . + " $programme -t table [ -l log ] [ -cq ]\n" . + " $programme -h\n\n"; + +my $version = "4.1.1"; +my $dateModif = "11 Août 2017"; + +my @liste = (); +my @table = (); +my %pref = (); +my %str = (); + +# Initialisation des variables globales +# nécessaires à la lecture des options +my $aide = 0; +my $casse = 0; +my $fichier = ""; +my $log = ""; +my $quiet = 0; +my $repertoire = ""; +my $sortie = ""; +my $table = ""; + +eval { + $SIG{__WARN__} = sub {usage(1);}; + GetOptions( + "casse" => \$casse, + "fichier=s" => \$fichier, + "help" => \$aide, + "log=s" => \$log, + "quiet" => \$quiet, + "repertoire=s" => \$repertoire, + "sortie=s" => \$sortie, + "table=s" => \$table, + ); + }; +$SIG{__WARN__} = sub {warn $_[0];}; + +if ( $aide ) { + print "\nProgramme : \n \"$programme\", version $version ($dateModif)\n"; + print " Permet la reconnaissance et l’extraction dans un corpus de textes de termes \n"; + print " figés (composés chimiques, noms scientifiques d’espèces animales ou végétales, \n"; + print " noms propres, etc.) appartenant à une liste finie. \n"; + print " N.B. : la liste et les textes doivent être en UTF-8. \n\n"; + print $usage; + print "Options :\n"; + print " -c tient compte de la casse (majuscule/minuscule) des termes recherchés \n"; + print " -f indique le nom du fichier texte à traiter \n"; + print " -h affiche cette aide \n"; + print " -l indique le nom du fichier récapitulatif où sera écrit pour chaque fichier \n"; + print " traité le nombre de termes et d’occurrences trouvés\n"; + print " -q supprime l’affichage de la progression du travail \n"; + print " -r indique le répertoire contenant les fichiers textes à traiter \n"; + print " -s indique le nom du fichier où sera écrit le résultat du traitement \n"; + print " -t indique le nom du fichier contenant la ressource, c'est-à-dire la liste \n"; + print " des termes à rechercher \n\n"; + print "Ressource : \n"; + print " Le fichier de ressource contient un terme par ligne. On peut indiquer pour \n"; + print " un terme sa forme préférentielle en ajoutant après le terme une ou plusieurs \n"; + print " tabulations et le préférentiel. \n"; + print " Les lignes vides et celles commençant par le caractère “#” ne sont pas prises \n"; + print " en compte. De plus, la ressource peut être un fichier compressé par “gzip” ou \n"; + print " “bzip2”. \n\n"; + print "Résultat : \n"; + print " Le fichier résultat contient une ligne par occurrence trouvée. Chaque ligne \n"; + print " est formée de 4 champs séparés par une tabulation. On a respectivement le nom \n"; + print " du fichier traité (“STDIN” dans le cas de l'entrée standard), le terme tel \n"; + print " qu'il est dans la ressource, le terme tel qu'il apparait dans le texte analysé \n"; + print " et, dans le cas d'un synonyme, la forme préférentielle du terme. \n\n"; + exit 0; + } + +# Vérification de la présence des options obligatoires +usage(2) if not $table; + +if ( $log ) { + open(LOG, ">:utf8", "$log") or die "$!,"; + } +else { + open(LOG, "> /dev/null") or die "$!,"; + } + +if ( ! -f $table ) { + print STDERR "$programme : fichier \"$table\" absent\n"; + usage(5); + } +elsif ( $table =~ /\.g?[zZ]\Z/ ) { + open(TAB, "gzip -cd $table |") or die "$!, "; + binmode TAB, ":utf8"; + } +elsif ( $table =~ /\.bz2\Z/ ) { + open(TAB, "bzip2 -cd $table |") or die "$!, "; + binmode TAB, ":utf8"; + } +else { + open(TAB, "<:utf8", $table) or die "$!, "; + } + +$SIG{'HUP'} = 'nettoye'; +$SIG{'INT'} = 'nettoye'; +$SIG{'TERM'} = 'nettoye'; + +print STDERR "\r", " " x 75, "\r Chargement de la ressource ... " if not $quiet; + +while () { + next if /^#/o or /^\s*$/o; + chomp; + s/\r//go; + + # Vérification de jeu de caractères (doit être UTF-8) + if ( not is_utf8($_, Encode::FB_QUIET) ) { + print STDERR "Erreur : la table de référence doit être en UTF-8\n"; + exit 6; + } + + my $pref = ""; + my $terme = ""; + if ( /\t+/o ) { + ($terme, $pref) = split(/\t+/o); + } + else { +# $pref = $_; + $terme = $_; + } + $terme =~ s/^\p{IsSpace}+//o; + $terme =~ s/\p{IsSpace}+\z//o; +# $terme =~ s/\p{IsSpace}\p{IsSpace}+/ /o; + my $str = $terme; + if ( $terme =~ m|^\p{IsSpace}*\Z| or $terme =~ m|^\p{IsWord}-?\Z| ) { + print STDERR "Terme refusé : \"$terme\"\n"; + next; + } + $terme = join(" ", grep(/\P{IsSpace}/, split(/(\P{IsWord})/, $terme))); + $terme =~ s/ +/ /g; + if ( not $casse ) { + $terme = lc($terme); + } + if ( not $str{$terme} ) { + push(@liste, $terme); + $str{$terme} = $str; + } + else { +# print STDERR "Erreur : doublon \"$str{$terme}\" et \"$str\"\n"; + print LOG "doublon \"$str{$terme}\" et \"$str\"\n"; + next; + } + if ( $pref ) { + $pref =~ s/^\p{IsSpace}+//o; + $pref =~ s/\p{IsSpace}+\z//o; +# $pref =~ s/\p{IsSpace}\p{IsSpace}+/ /o; + $str = $pref; + if ( $pref =~ m|^\p{IsSpace}*\Z| or $terme =~ m|^\p{IsWord}-?\Z| ) { + print STDERR "Préférentiel refusé : \"$pref\"\n"; + next; + } + $pref = join(" ", grep(/\P{IsSpace}/, split(/(\P{IsWord})/, $terme))); + $pref =~ s/ +/ /g; + if ( not $casse ) { + $pref = lc($pref); + } + if ( not $str{$pref} ) { + push(@liste, $pref); + $str{$pref} = $str; + } + $pref{$terme} = $str; + } + } +close TAB; + +@table = sort @liste; +my $nb = $#table + 1; +@liste = (); + +if ( $nb == 0 ) { + print STDERR "\r", " " x 75, "\r Aucun terme présent dans la liste\n"; + exit 3; + } + +print STDERR "\r", " " x 75, "\r $nb termes présents dans la liste\n" if not $quiet; + +select(STDERR); +$| = 1; +select (STDOUT); + +if ( $sortie ) { + open(OUT, ">:utf8", $sortie) or die "$!,"; + select OUT; + } + +if ( $fichier ) { + traite($fichier); + } +elsif ( $repertoire ) { + opendir(DIR, $repertoire) or die "$!,"; + my @fichiers = grep(!/^\./o, readdir(DIR)); + closedir(DIR); + foreach $fichier (@fichiers) { + traite("$repertoire/$fichier"); + } + } +else { + traite('-'); + } + +nettoye(); + + +exit 0; + + +sub usage +{ +my $retour = shift; + +print STDERR $usage; + +exit $retour; +} + +sub dich +{ +my ($binf) = -1; +my ($bsup) = $nb; +my ($key) = $_[0]; + +while ( $bsup > $binf + 1 ) { + my $bmid = int ( ( $bsup + $binf) / 2 ); + my $comp = $key cmp $table[$bmid]; + return $bmid if $comp == 0; + if ( $comp > 0 ) { + $binf = $bmid; + } + else { + $bsup = $bmid; + } + } +return (- $bsup - 1); +} + +sub traite +{ +my $input = shift; + +my $nom = ""; +if ( $input eq '-' ) { + open(INP, "<&STDIN") or die "Impossible de dupliquer STDIN: $!,"; + binmode(INP, ":utf8"); + $nom = "STDIN"; + } +else { + open(INP, "<:utf8", $input) or die "$!,"; + ($nom) = $input =~ m|^(?:.*/)?(.+)|o; + } + +my $texte = ""; +my @resultats = (); + +print STDERR "\r", " " x 75, "\r Traite le fichier $nom " if not $quiet; + +while() { + # Vérification de jeu de caractères (doit être UTF-8) + if ( not is_utf8($_, Encode::FB_QUIET) ) { + if ( $nom eq 'STDIN' ) { + print STDERR "Erreur : le texte en entrée standard doit être en UTF-8\n"; + } + else { + print STDERR "Erreur : le fichier \"$nom\" doit être en UTF-8\n"; + } + exit 7; + } + + if ( /^\s*$/o ) { + if ( $texte ) { + push(@resultats, recherche($nom, $texte)); + $texte = ""; + } + next; + } + tr/\n\r/ /s; + $texte .= $_; + } + +if ( $texte ) { + push(@resultats, recherche($nom, $texte)); + } + +close INP; + +my %tmp = (); +foreach my $resultat (@resultats) { + print "$nom\t$resultat\n"; + my ($ref, $chaine) = split(/\t/, $resultat); + $tmp{$ref} ++; + } + +my $nb_refs = 0; +my $nb_occs = 0; + +foreach my $ref (keys %tmp) { + $nb_refs ++; + $nb_occs += $tmp{$ref}; + } + +printf LOG "%d\t%d\t%s\n", $nb_refs, $nb_occs, $nom; +} + +sub recherche +{ +my ($cle, $orig) = @_; + +$orig =~ s/^\s+//o; +$orig =~ s/\s+\z//o; + +my $rec = join(" ", grep(/\P{IsSpace}/, split(/(\P{IsWord})/, $orig))); + +if ( ! $casse ) { + $rec = lc($rec); + } + +my $terme = ""; +my @matchs = (); + +while ( length($rec) ) { + my $retour = dich($rec); + if ( $retour > -1 ) { + print STDERR "\r", " " x 75, "\r" if not $quiet; + $terme = $table[$retour]; + $terme =~ s/(\P{IsWord})/\\$1/g; + $terme =~ s/\\ / */og; + $terme =~ s/([^\x20-\x7F])/./og; + if ( $orig =~ /^$terme\b/ or ( ! $casse and $orig =~ /^$terme\b/i ) ) { + push(@matchs, "$str{$table[$retour]}\t$&"); + if ( $pref{$table[$retour]} ) { + $matchs[$#matchs] .= "\t$str{$pref{$table[$retour]}}"; + } + } + else { + push(@matchs, "$str{$table[$retour]}\t*** ERREUR ***"); + print STDERR "ERREUR (1) sur la recherche de l'original $cle\n"; + } + if ( not $quiet ) { + print STDERR "$cle => $str{$table[$retour]}\n"; + print STDERR " Traite le fichier $cle "; + } + } + else { + $retour = - 2 - $retour; + $terme = $table[$retour]; + my ($debut) = $terme =~ m|^(.*?\p{IsWord}+)|; + $debut =~ s/(\P{IsWord})/\\$1/g; + do { + $terme =~ s/(\P{IsWord})/\\$1/g; +# Recherche le début du mot et autorise les préfixes +# if ( $rec =~ /^$terme.*?\b/ ) { + if ( $rec =~ /^$terme\b/ ) { # Mot exact seulement + print STDERR "\r", " " x 75, "\r" if not $quiet; + $terme =~ s/\\ / */og; + $terme =~ s/([^\x20-\x7F])/./og; + if ( $orig =~ /^$terme/ or ( ! $casse and $orig =~ /^$terme/i ) ) { + push(@matchs, "$str{$table[$retour]}\t$&"); + if ( $pref{$table[$retour]} ) { + $matchs[$#matchs] .= "\t$str{$pref{$table[$retour]}}"; + } + } + else { + push(@matchs, "$str{$table[$retour]}\t*** ERREUR ***"); + print STDERR "ERREUR (2) sur la recherche de l'original $cle\n"; + } + if ( not $quiet ) { + print STDERR "$cle => $str{$table[$retour]}\n"; + print STDERR " Traite le fichier $cle "; + } + $retour = 0; + } + if ( $retour > 0 ) { + $terme = $table[--$retour]; + } + else { + $terme = ""; + } + } until $terme !~ /^$debut/; + } + $rec =~ s/^\P{IsSpace}+\p{IsSpace}?//; + if ( $orig =~ /^\p{IsWord}+\p{IsSpace}*/ ) { + $orig =~ s/^\p{IsWord}+\p{IsSpace}*//; + } + elsif ( $orig =~ /^\P{IsWord}\p{IsSpace}*/ ) { + $orig =~ s/^\P{IsWord}\p{IsSpace}*//; + } + else { + print STDERR "ERREUR sur le texte intégral : $orig\n"; + } + } + +return @matchs; +} + +sub nettoye +{ +if ( not $quiet ) { + print STDERR "\r", " " x 75, "\r"; + print STDERR "\n"; + } + +exit 0; +} diff --git a/README.md b/README.md index 888f28e..afb838a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,51 @@ IRC3 =============== -IRC3 \ No newline at end of file +**IRC3** (**I**ndexation par **R**echerche et **C**omparaison de **C**haînes de **C**aractères) +est un programme simple et robuste permettant la reconnaissance et l’extraction dans un corpus +de textes, de termes figés (composés chimiques, noms scientifiques d’espèces animales ou végétales, +noms propres, etc.) appartenant à une liste finie. + +**N.B.** : la liste et les textes doivent être en **UTF-8**. + +### Usage +``` + IRC3.pl -t table -r répertoire [ -s fichier_sortie ] [ -l log ] [ -cq ] + IRC3.pl -t table -f fichier_entrée [ -s fichier_sortie ] [ -l log ] [ -cq ] + IRC3.pl -t table [ -l log ] [ -cq ] + IRC3.pl -h +``` + +### Options +``` + -c tient compte de la casse (majuscule/minuscule) des termes recherchés + -f indique le nom du fichier texte à traiter + -h affiche cette aide + -l indique le nom du fichier récapitulatif où sera écrit pour chaque fichier + traité le nombre de termes et d’occurrences trouvés + -q supprime l’affichage de la progression du travail (notamment pour utiliser + dans un script shell) + -r indique le répertoire contenant les fichiers textes à traiter + -s indique le nom du fichier où sera écrit le résultat du traitement + -t indique le nom du fichier contenant la ressource, c'est-à-dire la liste + des termes à rechercher +``` + +### Ressource + +Le fichier de ressource contient un terme par ligne. On peut indiquer pour un terme sa forme +préférentielle en ajoutant après le terme une ou plusieurs tabulations et le préférentiel. + +Les lignes vides et celles commençant par le caractère “#” ne sont pas prises en compte. De plus, +la ressource peut être un fichier compressé par “gzip” ou “bzip2”. + +### Résultat + +Le fichier résultat contient une ligne par occurrence trouvée. Chaque ligne est formée de 4 +champs séparés par une tabulation. On a respectivement : + +* le nom du fichier traité (“STDIN” dans le cas de l'entrée standard), +* le terme tel qu'il est dans la ressource, +* le terme tel qu'il apparait dans le texte analysé, +* la forme préférentielle du terme dans le cas d'un synonyme. +