Git --projet-boite-à-outils

Extraction par chaîne

Extraction « manuelle »

Pour cela, on est censé utiliser des expressions régulières mais étant donné que pour récupérer le contenu des titres et des descriptions des items, je voulais d’abord récupérer le contenu d’un item, puis en extraire le titre et la description, ce n’était pas possible.

Il aurait en effet fallu pouvoir écrire une expression comme celle-ci :
/([^<]*)<\/item>/
pour matcher le contenu d’un item.

Mais cela ne marche pas, car elle va reconnaître le contenu d’un item jusqu’à rencontrer la première fermeture de balise, puis la présence d’item dans l’expression, qui n’est pas le tag de fermeture effectivement rencontré, va faire qu’il n’y a pas reconnaissance du contenu.

Attention
En réalité il est possible d'obtenir le comportement désiré en utilisant une extension Perl des expression régulières, qui permet d'indiquer qu'on souhaite un match minimal, et d'éviter le comportement glouton par défaut.

extract_tag_content

C’est pourquoi j’ai eu l’idée d’une procédure (extract_tag_content) qui à partir d’un nom de balise donné, va chercher dans le texte donné en paramètre la position de la première balise ouvrante ainsi que celle de la première balise fermante concernée et renvoyer le contenu présent entre les deux. L’implémentation est faite avec les fonctions index et substr de Perl.

Cette fonction permet donc d’extraire du contenu où plusieurs balises de noms différents sont présentes. Si une balise contient au moins une autre balise du même nom, le résultat renvoyé ne sera pas le résultat attendu.

La procédure effectivement implémentée renvoie la liste des contenus contenu dans une balise donnée, dans leur ordre d’apparition dans le fichier. Ainsi, un appel pour obtenir le contenu de la balise title va renvoyer en premier lieu la balise title enfant du channel, puis celles se trouvant dans les différents items du flux.

lib-parcours-string.pl
use warnings;
use strict;

# param $content : texte xml à traiter
# param $tag : nom (sans crochets) du tag xml dont il faut récupérer le contenu
# return : la liste des contenus du tag contenu dans le texte
# note : la balise qui entoure le XML à extraire n'est pas retirée par cette fonction
sub extract_tag_content
{
    my ($content, $tag) = @_;
    my @list = ();
    extract_tag_content_helper($content, $tag, \@list);
    return @list;
}

sub extract_tag_content_helper
{
    my ($content, $tag, $list) = @_;
    
    my $start_tag = "<$tag>";
    my $end_tag = "</$tag>";
    my $start_tag_index = index($content, $start_tag);  
    my $end_tag_index = index $content, $end_tag;
        
    if($start_tag_index == -1)
    {
        return;
    }
    
    push @$list, substr($content, $start_tag_index, 
        $end_tag_index - $start_tag_index + length($end_tag));
    
    $content = substr($content, $end_tag_index + length($end_tag));
    
    extract_tag_content_helper($content, $tag, $list);
}

1;

On notera que la fonction est récursive terminale. Ainsi, de nombreux appels de fonctions sont effectués et l'interpréteur s'en plaint lorsque la chaîne passée en paramètre est très longue.

Dérécursivation

Je voulais dérécursiver cette fonction, car Perl n'optimise visiblement pas tout seul ce genre de fonction, comme peut le faire certains langages fonctionnels. En outre, sur les très gros fichiers, l'interpréteur affiche un avertissement plus la pronfondeur d'appel de cette fonction.

J'ai donc essayé d'en écrire une version itérative, dont le code est très proche de celui indiqué plus haut, mais en la testant sur les fichiers du corpus de texte j'avais parfois des occurrences vides surnuméraires se glissant dans les résultats.

Les outils de débuggage de Perl étant ce qu'ils sont, j'ai décidé de ne pas perdre plus de temps sur ce problème au fond assez mineur.