Il y a quelques temps maintenant, mais pas si longtemps que cela quand même, je me suis intéressé aux Context Objects in Spans, ces données bibliographiques que l'on peut insérer dans des pages HTML, et qui permet aux utilisateurs, soit de récupérer les informations bibliographies, c'est ce que fait Zotero, ou encore utiliser cette information pour rebondir sur un serveur OpenURL de son choix, c'est ce que fait OpenURL Referrer.
Les solutions ci-dessus exploitant les COinS sont orientés navigateurs, et même si j'utilise quasi uniquement Mozilla Firefox, j'aime bien garder un certain recul par rapport à certains outils pour ne pas être pieds et poings liés à un seul outil omniprésent et omnipotent. Que pouvais-je donc faire pour exploiter ces COinS, mais sans me lier à un navigateur. Mais un proxy pardi ! Aussitôt dit, aussitôt fait.
Le proxy, ou serveur mandataire, traitera les requêtes entre le navigateur et Internet. Il pourra donc pour chaque page HTML qu'il verra passer, l'analyser, et s'il trouve un COinS, faire une action spécifique. Pour le moment, je ne me suis pas trop cassé la tête pour les actions spécifiques comme vous pourrez le voir. L'essentiel pour moi étant de reconnaître les COinS. Pour les traitements intelligents, on verra une prochaine fois !
Et c'est ainsi que j'ai commencé à programmer un petit proxy en Perl. Ou plus exactement, j'en ai programmé deux. Vous verrez que c'est vraiment trivial, surtout avec l'excellent module HTTP::Proxy de BooK.
Le premier script utilise XPath pour retrouver les COinS. C'est probablement la solution la plus élégante. L'expression XPath permettant de capturer tout les COinS d'une page est la suivante :
//span[@class="Z3988"]
Ce qui nous donne le script suivant :
#!/usr/bin/env perl
use strict;
use warnings;
use HTTP::Proxy;
use HTTP::Proxy::BodyFilter::complete;
use HTTP::Proxy::BodyFilter::simple;
use HTML::TreeBuilder::XPath;
use Data::Dump qw( dump );
use URI;
use URI::QueryParam;
my $proxy = HTTP::Proxy->new( @ARGV );
$proxy->push_filter(
mime => 'text/html',
response => HTTP::Proxy::BodyFilter::complete->new(),
response => HTTP::Proxy::BodyFilter::simple->new(
sub {
my ( $self, $dataref, $message, $protocol, $buffer ) = @_;
my $tree = HTML::TreeBuilder::XPath->new_from_content( $$dataref );
my $coins_xpath = '//span[@class="Z3988"]';
my $coins = $tree->findnodes( $coins_xpath );
my $coins_title_xpath = './@title';
foreach (@$coins) {
my $coins_title = $_->findvalue( $coins_title_xpath );
my $uri = URI->new( 'http://www.bib.ulb.ac.be' . '?' . $coins_title );
my $params = $uri->query_form_hash();
my $issn = $params->{'rft.issn'};
my $link_uri = URI->new( 'http://bib7.ulb.ac.be/uhtbin/ISSN/' . $issn);
my $link = HTML::Element->new( 'a', href => $link_uri->as_string );
$link->push_content('ZOZO');
$_->push_content( $link );
}
$$dataref = $tree->as_HTML;
}
),
);
$proxy->start;
Donc, on charge les modules nécessaires, on crée le proxy, puis on ajoute les filtres nécessaires à ce proxy. Le premier filtre à ajouter permet de travailler sur un corps complet, j'ai en effet besoin d'avoir toute la page HTML avant de faire le traitement. Le second filtre se charge de retrouver les COinS via l'expression XPath présenter ci-dessus, puis pour chaque noeud trouvé, nous allons récupérer l'attribut title qui contient l'information que nous souhaitons récupérer. Après vient le traitement proprement dit, dans notre cas, ajouter un lien ZOZO pointant vers une URI construit par nos soins. Cette dernière partie doit évidemment évoluer vers quelques choses de plus utile, mais bon, mes objectifs étaient plus du domaine du cas d'étude que d'une réelle application.
Mais bon, le problème de ce proxy est l'usage des expressions XPath, d'après ce que j'ai pu comprendre avec quelques recherches, HTML::Parser serait plus rapide pour ce genre de traitement. Et donc, je me suis lancer dans l'aventure d'écrire un second proxy, similaire, mais ce dernier utilisant HTML::Parser pour retrouver les COinS.
#!/usr/bin/env perl
use strict;
use warnings;
use HTTP::Proxy;
use HTTP::Proxy::BodyFilter::complete;
use HTTP::Proxy::BodyFilter::htmlparser;
use HTML::Parser;
use URI;
use URI::QueryParam;
my $parser = HTML::Parser->new( api_version => 3 );
$parser->handler(
start => sub {
my ($self, $tagname, $attr, $attrseq, $text) = @_;
if ($tagname eq 'span' && $attr->{class} eq 'Z3988') {
# Must be updated when OpenURL resolver was found
my $uri = URI->new( 'http://www.bib.ulb.ac.be' . '?' . $attr->{title} );
my $params = $uri->query_form_hash();
my $issn = $params->{'rft.issn'};
my $link_uri = URI->new( 'http://bib7.ulb.ac.be/uhtbin/ISSN/' . $issn);
my $coins_text = '<a href="' . $link_uri->as_string . '">ZOZO</a>';
# End of the 'must be updated' code
$text .= $coins_text;
}
$self->{output} .= $text;
}, "self, tagname, attr, attrseq, text",
);
$parser->handler(
default => sub {
my ($self, $text) = @_;
$self->{output} .= $text;
}, "self,text",
);
my $proxy = HTTP::Proxy->new( @ARGV );
$proxy->push_filter(
mime => 'text/html',
response => HTTP::Proxy::BodyFilter::complete->new(),
response => HTTP::Proxy::BodyFilter::htmlparser->new( $parser, rw => 1 ),
);
$proxy->start;
Vous voyez que le traitement des COinS par HTML::Parser n'est pas beaucoup plus compliqué.
Ces scripts sont disponibles dans un dépôt Git (via GitHub). Et peut-être aurai-je le temps d'ici peu d'exploiter ces deux scripts pour faire quelque chose d'utile : mettre les COinS ainsi collecté dans une base de données, ou encore les publier dans d'autres formats, etc.
Ah oui, une dernière chose. Si vous voulez tester ces scripts, il faudra désactiver OpenURL Referrer s'il est installé. En effet, pour une raison inconnue, quand il est activé, je ne vois pas mes liens ZOZO, même s'ils sont visibles quand je regarde dans les sources de la page. Il faudrait que je regarde comment fonctionne OpenURL Referrer pour résoudre ce mystère.
