2010-08-21 4 views
1

Le traitement HTML avec regex est une mauvaise idée, mais il semble approprié pour cette situation. Description: Avec un fichier .html, je dois analyser les liens internes, extraire le niveau d'indentation, le texte du lien et le numéro de page sur lequel il réside dans un fichier .txt externe qui est ensuite transmis à quelqu'un d'autre.Perl regex analyse uniquement le fichier; pas de fin au début

Donc, étant donné cet échantillon HTML:

<TR valign="bottom"> 
    <TD valign="top"><DIV style="margin-left:0px; text-indent:-0px"><A href="#101"><FONT style="font-variant:small-caps;">The &#147;Offering&#147;</FONT> 
</A></DIV></TD> 
    <TD>&nbsp;</TD> 
    <TD nowrap align="right" valign="top">&nbsp;</TD> 
    <TD align="right" valign="top">1</TD> 
    <TD nowrap valign="top">&nbsp;</TD> 
</TR> 
<TR valign="bottom"> 
    <TD valign="top"><DIV style="margin-left:15px; text-indent:-0px"><A href="#102">Sales &#038; Property 
</A></DIV></TD> 
    <TD>&nbsp;</TD> 
    <TD nowrap align="right" valign="top">&nbsp;</TD> 
    <TD align="right" valign="top">2</TD> 
    <TD nowrap valign="top">&nbsp;</TD> 
</TR> 

Le fichier externe produira:

0|The "Offering"|4
15|Sales & Property|5

(numéros de page sont différents parce qu'ils sont le numéro de page réelle, pas référence folio).

Je l'ai principalement compris, sauf pour 1 partie, lorsque le texte du lien contient des codes HTML supplémentaires, comme la balise <Font> dans le premier lien.

Voici mon regex pour extraire les liens (note $ string contient le code html ci-dessus):

while ($string =~ m/<DIV style="margin-left:([0-9]+)px; text-indent:[-0-9]+px"><A href="#([0-9]+)">([a-zA-Z0-9\.,:;&#\s]+)<\/A>/gi) { 
    push(@indents,$1); 
    push(@linkIDs,$2); 
    push(@names,escapeHTML($3)); 
}; 

qui va extraire correctement le second, mais pas la première, à cause des> < et d'autres symboles dans le code HTML.

Si je change que le dernier groupe de capture à .+ ou .*, je reçois l'ensemble du fichier HTML (bien, entre le premier <Div><A> et le dernier </A>. Il semble que le modèle commence au début, mais correspondant à partir de la fin . du fichier en arrière

Voici un lien vers un constructeur de regex en ligne: http://regexr.com?2s0po
correctement trouve ce que je dois, mais en Perl je ne reçois pas les mêmes résultats (que le fichier entier comme mentionné)

.

Je n'arrive pas à écrire quoi que ce soit qui capturera chaque grou p correctement - vous penseriez que le "curseur" avance et s'arrête au premier </A> vu depuis le début du fichier.

Toute aide, d'opinions ou de conseils serait grandement appréciée. -Je vous remercie.

+8

Ceci est un scénario _perfect_ pour l'utilisation d'un analyseur HTML. Les expressions régulières sont totalement le mauvais outil. Je ne connais pas le paysage des parseurs Perl HTML, mais quelqu'un devrait pouvoir vous suggérer quelque chose. –

Répondre

3

Vous devez faire attention à la regex lors de l'analyse de structures HTML ou similaires. Il y a deux problèmes avec l'expression rationnelle que vous essayez:

  1. balises imbriquées (police-tag dans la première entrée)
  2. sauts de ligne (avant la première balise d'ancrage de fermeture)

est ici un regex qui traite de ceux-ci:

use HTML::Entities; 
while ($string =~ m/<DIV style="margin-left:([0-9]+)px; text-indent:[-0-9]+px"><A href="#([0-9]+)">(.*?)<\/A>/gis) { 
    my $indent = $1; 
    my $page = $2; 
    (my $name = $3) =~ s/\s+$//; 
    $name =~ s/^\s+//; 
    $name =~ s/<.*?>//g; 
    print $indent, '|', decode_entities($name), '|', $page, "\n"; 
} 
+0

Merci! Ceci est un exemple parfait d'une réponse complète - je dois utiliser ma propre fonction d'entités html en raison du manque de modules externes, mais sinon c'était sur place! – WSkid

2

Je ne le ferais pas avec une expression régulière.

Avec HTML::TreeBuilder, par exemple, vous pourriez construire un arbre avec

#! /usr/bin/perl 

use warnings; 
use strict; 

use HTML::TreeBuilder; 
use HTML::TreeBuilder::XPath; 

my $root = HTML::TreeBuilder->new_from_content(<<'EOHTML'); 
<TR valign="bottom"> 
    <TD valign="top"><DIV style="margin-left:0px; text-indent:-0px"><A href="#101"><FONT style="font-variant:small-caps;">The &#147;Offering&#147;</FONT> 
</A></DIV></TD> 
    <TD>&nbsp;</TD> 
    <TD nowrap align="right" valign="top">&nbsp;</TD> 
    <TD align="right" valign="top">1</TD> 
    <TD nowrap valign="top">&nbsp;</TD> 
</TR> 
<TR valign="bottom"> 
    <TD valign="top"><DIV style="margin-left:15px; text-indent:-0px"><A href="#102">Sales &#038; Property 
</A></DIV></TD> 
    <TD>&nbsp;</TD> 
    <TD nowrap align="right" valign="top">&nbsp;</TD> 
    <TD align="right" valign="top">2</TD> 
    <TD nowrap valign="top">&nbsp;</TD> 
</TR> 
EOHTML 

puis extraire les liens et indentation à l'aide HTML::TreeBuilder::XPath:

sub all_text { 
    my($root) = @_; 

    ref $root 
    ? join "" => map all_text($_) => $root->content_list 
    : $root; 
} 

foreach my $div ($root->findnodes('/html/body//div[.//a]')) { 
    my $indent = 
    $div->attr('style') =~ /\bmargin-left:\s*(\d+)/ ? $1 : 0; 

    foreach my $a ($div->findnodes('.//a')) { 
    (my $text = all_text $a) =~ s/\s+\z//; 
    print "$indent|$text|FIXME\n"; 
    } 
} 

Sortie:

0|The �Offering�|FIXME 
15|Sales & Property|FIXME
+0

Merci, malheureusement en raison de contraintes logicielles Je ne peux pas utiliser les modules non-core sur les machines de production mais j'ai testé cette solution sur une machine de dev et cela fonctionne parfaitement pour ceux qui regardent cette question dans un environnement normal. – WSkid

+0

@WSkid De rien. Je suis content que vous ayez réussi à surmonter le problème que vous aviez. –

1

Vous pourrait essayer un match non-gourmand en utilisant .+? ou .*? pour l'empêcher d'aspirer le reste du fichier.

+0

Ah, merci - je savais que j'étais trop simple! – WSkid

Questions connexes