Oh Oui, Vous Pouvez Utiliser les Regexes pour Parser du HTML!
Pour la tâche que vous essayez, regexes sont parfaitement bien!
Il est vrai que la plupart des gens sous-estiment la difficulté de l'analyse de HTML avec des expressions régulières, et donc de faire mal.
Mais ce n'est pas une faille fondamentale liée à la théorie de calcul. Que la bêtise est effacèrent beaucoup ici, mais ne croyez-vous pas.
Ainsi, alors qu'il peut certainement être fait (cet affichage sert de preuve de l'existence de ce fait incontestable), qui ne signifie pas qu'il devrait être.
Vous devez décider pour vous-même si vous êtes à la hauteur de la tâche d'écriture ce qui équivaut à un dédié, à des fins spéciales de l'analyseur HTML de regexes. La plupart des gens ne le sont pas.
Mais je suis. ☻
EDIT: Général Regex HTML Basés sur l'Analyse des Solutions
D'abord, je vais vous montrer comment il est facile d'analyser arbitraire HTML avec regexes. Le programme complet est à la fin de cette annonce, mais le cœur de l'analyseur est:
for (;;) {
given ($html) {
last when (pos || 0) >= length;
printf "\@%d=", (pos || 0);
print "doctype " when / \G (?&doctype) $RX_SUBS /xgc;
print "cdata " when / \G (?&cdata) $RX_SUBS /xgc;
print "xml " when / \G (?&xml) $RX_SUBS /xgc;
print "xhook " when / \G (?&xhook) $RX_SUBS /xgc;
print "script " when / \G (?&script) $RX_SUBS /xgc;
print "style " when / \G (?&style) $RX_SUBS /xgc;
print "comment " when / \G (?&comment) $RX_SUBS /xgc;
print "tag " when / \G (?&tag) $RX_SUBS /xgc;
print "untag " when / \G (?&untag) $RX_SUBS /xgc;
print "nasty " when / \G (?&nasty) $RX_SUBS /xgc;
print "text " when / \G (?&nontag) $RX_SUBS /xgc;
default {
die "UNCLASSIFIED: " .
substr($_, pos || 0, (length > 65) ? 65 : length);
}
}
}
Voir comment facile qui consiste à lire?
Comme l'a écrit, il identifie chaque morceau de HTML et indique où il a trouvé cette pièce. Vous pouvez facilement le modifier pour faire ce que vous voulez avec n'importe quel type de pièce, ou pour plus de types particuliers de ces.
Je n'ai pas de défaut cas de test (à gauche :): j'ai réussi à exécuter ce code sur plus de 100 000 fichiers HTML - tous seul je pouvais facilement et rapidement obtenir mes mains sur. Au-delà de ces domaines, j'ai aussi l'exécuter sur les fichiers spécialement conçus pour briser naïf d'analyseurs.
Ce n'est pas un naïf de l'analyseur.
Oh, je suis sûr qu'il n'est pas parfait, mais je n'ai pas réussi à le briser encore. Je me dis que même si quelque chose l'a fait, la solution serait facile de s'intégrer en raison de la structure claire. Même regex lourde les programmes doivent avoir stucture.
Maintenant que cela est hors de la voie, laissez-moi répondre à l'OP de la question.
Démonstration de la Résolution de l'OP de la Tâche à l'Aide de Regexes
Le peu d' html_input_rx
program-je inclure ci-dessous génère la sortie suivante, de sorte que vous pouvez voir que l'analyse HTML avec regexes fonctionne très bien pour ce que vous voulez faire:
% html_input_rx Amazon.com-_Online_Shopping_for_Electronics,_Apparel,_Computers,_Books,_DVDs_\&_more.htm
input tag #1 at character 9955:
class => "searchSelect"
id => "twotabsearchtextbox"
name => "field-keywords"
size => "50"
style => "width:100%; background-color: #FFF;"
title => "Search for"
type => "text"
value => ""
input tag #2 at character 10335:
alt => "Go"
src => "http://g-ecx.images-amazon.com/images/G/01/x-locale/common/transparent-pixel._V192234675_.gif"
type => "image"
Analyser Les Balises Input, Ne Pas Voir Le Mal D'Entrée
Voici le code source du programme qui a produit le résultat ci-dessus.
#!/usr/bin/env perl
#
# html_input_rx - pull out all <input> tags from (X)HTML src
# via simple regex processing
#
# Tom Christiansen <tchrist@perl.com>
# Sat Nov 20 10:17:31 MST 2010
#
################################################################
use 5.012;
use strict;
use autodie;
use warnings FATAL => "all";
use subs qw{
see_no_evil
parse_input_tags
input descape dequote
load_patterns
};
use open ":std",
IN => ":bytes",
OUT => ":utf8";
use Encode qw< encode decode >;
###########################################################
parse_input_tags
see_no_evil
input
###########################################################
until eof(); sub parse_input_tags {
my $_ = shift();
our($Input_Tag_Rx, $Pull_Attr_Rx);
my $count = 0;
while (/$Input_Tag_Rx/pig) {
my $input_tag = $+{TAG};
my $place = pos() - length ${^MATCH};
printf "input tag #%d at character %d:\n", ++$count, $place;
my %attr = ();
while ($input_tag =~ /$Pull_Attr_Rx/g) {
my ($name, $value) = @+{ qw< NAME VALUE > };
$value = dequote($value);
if (exists $attr{$name}) {
printf "Discarding dup attr value '%s' on %s attr\n",
$attr{$name} // "<undef>", $name;
}
$attr{$name} = $value;
}
for my $name (sort keys %attr) {
printf " %10s => ", $name;
my $value = descape $attr{$name};
my @Q; given ($value) {
@Q = qw[ " " ] when !/'/ && !/"/;
@Q = qw[ " " ] when /'/ && !/"/;
@Q = qw[ ' ' ] when !/'/ && /"/;
@Q = qw[ q( ) ] when /'/ && /"/;
default { die "NOTREACHED" }
}
say $Q[0], $value, $Q[1];
}
print "\n";
}
}
sub dequote {
my $_ = $_[0];
s{
(?<quote> ["'] )
(?<BODY>
(?s: (?! \k<quote> ) . ) *
)
\k<quote>
}{$+{BODY}}six;
return $_;
}
sub descape {
my $string = $_[0];
for my $_ ($string) {
s{
(?<! % )
% ( \p{Hex_Digit} {2} )
}{
chr hex $1;
}gsex;
s{
& \043
( [0-9]+ )
(?: ;
| (?= [^0-9] )
)
}{
chr $1;
}gsex;
s{
& \043 x
( \p{ASCII_HexDigit} + )
(?: ;
| (?= \P{ASCII_HexDigit} )
)
}{
chr hex $1;
}gsex;
}
return $string;
}
sub input {
our ($RX_SUBS, $Meta_Tag_Rx);
my $_ = do { local $/; <> };
my $encoding = "iso-8859-1"; # web default; wish we had the HTTP headers :(
while (/$Meta_Tag_Rx/gi) {
my $meta = $+{META};
next unless $meta =~ m{ $RX_SUBS
(?= http-equiv )
(?&name)
(?&equals)
(?= (?"e)? content-type )
(?&value)
}six;
next unless $meta =~ m{ $RX_SUBS
(?= content ) (?&name)
(?&equals)
(?<CONTENT> (?&value) )
}six;
next unless $+{CONTENT} =~ m{ $RX_SUBS
(?= charset ) (?&name)
(?&equals)
(?<CHARSET> (?&value) )
}six;
if (lc $encoding ne lc $+{CHARSET}) {
say "[RESETTING ENCODING $encoding => $+{CHARSET}]";
$encoding = $+{CHARSET};
}
}
return decode($encoding, $_);
}
sub see_no_evil {
my $_ = shift();
s{ <! DOCTYPE .*? > }{}sx;
s{ <! \[ CDATA \[ .*? \]\] > }{}gsx;
s{ <script> .*? </script> }{}gsix;
s{ <!-- .*? --> }{}gsx;
return $_;
}
sub load_patterns {
our $RX_SUBS = qr{ (?(DEFINE)
(?<nv_pair> (?&name) (?&equals) (?&value) )
(?<name> \b (?= \pL ) [\w\-] + (?<= \pL ) \b )
(?<equals> (?&might_white) = (?&might_white) )
(?<value> (?"ed_value) | (?&unquoted_value) )
(?<unwhite_chunk> (?: (?! > ) \S ) + )
(?<unquoted_value> [\w\-] * )
(?<might_white> \s * )
(?<quoted_value>
(?<quote> ["'] )
(?: (?! \k<quote> ) . ) *
\k<quote>
)
(?<start_tag> < (?&might_white) )
(?<end_tag>
(?&might_white)
(?: (?&html_end_tag)
| (?&xhtml_end_tag)
)
)
(?<html_end_tag> > )
(?<xhtml_end_tag> / > )
) }six;
our $Meta_Tag_Rx = qr{ $RX_SUBS
(?<META>
(?&start_tag) meta \b
(?:
(?&might_white) (?&nv_pair)
) +
(?&end_tag)
)
}six;
our $Pull_Attr_Rx = qr{ $RX_SUBS
(?<NAME> (?&name) )
(?&equals)
(?<VALUE> (?&value) )
}six;
our $Input_Tag_Rx = qr{ $RX_SUBS
(?<TAG> (?&input_tag) )
(?(DEFINE)
(?<input_tag>
(?&start_tag)
input
(?&might_white)
(?&attributes)
(?&might_white)
(?&end_tag)
)
(?<attributes>
(?:
(?&might_white)
(?&one_attribute)
) *
)
(?<one_attribute>
\b
(?&legal_attribute)
(?&might_white) = (?&might_white)
(?:
(?"ed_value)
| (?&unquoted_value)
)
)
(?<legal_attribute>
(?: (?&optional_attribute)
| (?&standard_attribute)
| (?&event_attribute)
# for LEGAL parse only, comment out next line
| (?&illegal_attribute)
)
)
(?<illegal_attribute> (?&name) )
(?<required_attribute> (?#no required attributes) )
(?<optional_attribute>
(?&permitted_attribute)
| (?&deprecated_attribute)
)
# NB: The white space in string literals
# below DOES NOT COUNT! It's just
# there for legibility.
(?<permitted_attribute>
accept
| alt
| bottom
| check box
| checked
| disabled
| file
| hidden
| image
| max length
| middle
| name
| password
| radio
| read only
| reset
| right
| size
| src
| submit
| text
| top
| type
| value
)
(?<deprecated_attribute>
align
)
(?<standard_attribute>
access key
| class
| dir
| ltr
| id
| lang
| style
| tab index
| title
| xml:lang
)
(?<event_attribute>
on blur
| on change
| on click
| on dbl click
| on focus
| on mouse down
| on mouse move
| on mouse out
| on mouse over
| on mouse up
| on key down
| on key press
| on key up
| on select
)
)
}six;
}
UNITCHECK {
load_patterns();
}
END {
close(STDOUT)
|| die "can't close stdout: $!";
}
Là vous allez! Rien à! :)
Seulement vous pouvez juger si votre habileté avec les regexes est à tout particulier à l'analyse de la tâche. Le niveau de compétence est différente, et chaque nouvelle tâche est différente. Pour les emplois où vous avez une bien défini d'entrée de jeu, regexes sont de toute évidence le bon choix, parce que c'est facile à mettre ensemble quand vous avez un sous-ensemble restreint de HTML à traiter. Même regex pour les débutants doivent être traiter ces emplois avec regexes. Tout le reste est inutile.
Cependant, une fois que le HTML commence à devenir de moins en moins à clouer, une fois qu'il commence à se ramifier dans les moyens que vous ne peut pas prédire, mais qui sont parfaitement légales, une fois que vous avez à correspondre à plus de différentes sortes de choses ou avec dépendances plus complexe, vous finirez par atteindre un point où vous devez travailler plus dur pour effet une solution qui utilise les regexes que vous auriez à l'aide d'une analyse de classe. Où que le break-even point de chute dépend encore une fois sur votre propre niveau de confort avec regexes.
Alors Que Dois-Je Faire?
Je ne vais pas vous dire ce que vous devez faire ou ce que vous ne peut pas faire. Je pense que c'est Faux. Je veux juste vous présenter possibilités, ouvrez les yeux un peu. Vous aurez à choisir ce que vous voulez faire et comment vous voulez le faire. Il n'y a pas d'absolus - et personne d'autre ne sait votre propre situation ainsi que vous-même faire. Si quelque chose semble trop de travail, peut-être qu'il est. La programmation doit être amusant, vous savez. Si ce n'est pas le cas, vous pouvez avoir tout faux.
On peut regarder mon html_input_rx
programme dans un certain nombre de moyens valables. Un exemple est que vous pouvez analyser HTML avec des expressions régulières. Mais une autre est que c'est beaucoup, beaucoup, beaucoup plus difficile que presque tout le monde pense jamais qu'il est. Cela peut facilement conduire à la conclusion que mon programme est un témoignage de ce que vous devriez pas faire, parce que c'est vraiment trop dur.
Je ne vais pas en désaccord avec cela. Certes, si tout ce que je fais dans mon programme ne fait pas de sens pour vous après certaines études, alors vous ne devriez pas tenter d'utiliser regexes pour ce genre de tâche. Pour HTML, regexes sont super, mais pour le générique de HTML, ils sont l'équivalent de la folie. J'utilise l'analyse des classes de tous les temps, surtout si c'est du HTML, je n'ai pas généré de moi-même.
Regexes optimale pour les petites HTML problèmes de l'analyse, pessimal pour les grandes
Même si mon programme est pris comme une illustration de pourquoi vous devriez ne pas utiliser regexes pour l'analyse générale HTML qui est ok, parce que j'ai un peu destinés à être - il encore ouvert les yeux de sorte que plus de gens se cassent le terriblement commun et méchant, méchante habitude de l'écriture illisible, non structurées, et difficile à maintenir les modèles.
Les modèles n'ont pas à être laid, et ils n'ont pas à être difficile. Si vous créez laid modèles, c'est une réflexion sur vous, pas pour eux.
Incroyablement Exquis Regex Langue
J'ai demandé à souligner que mes proferred solution à votre problème a été écrit en Perl. Êtes-vous surpris? N'avez-vous pas remarqué? Est-ce la révélation d'une bombe?
Je dois avouer que je trouve cette demande bizarre à l'extrême, puisque quelqu'un qui ne peut pas le savoir en regardant la première ligne de mon programme a sûrement d'autres handicaps mentaux.
Il est vrai que tous les autres outils et langages de programmation sont tout aussi pratique, expressif et puissant quand il s'agit de regexes que Perl est. Il y a un grand spectre de là, certains étant plus adaptées que d'autres. En général, les langues qui ont exprimé regexes dans le cadre de la linguistique de base, plutôt que comme une bibliothèque sont plus faciles à travailler avec. Je n'ai rien fait avec regexes que vous ne pouvait pas faire, dire, PCRE, bien que vous la structure du programme différemment si vous étiez à l'aide de C.
Finalement, d'autres langues seront rattraper où Perl est maintenant en termes de regexes. Je dis cela parce que, quand Perl a commencé, personne d'autre n'avait rien comme Perl regexes. Dire ce que vous voulez, mais c'est là que Perl clairement gagné: tout le monde a copié Perl regexes mais à des stades différents de leur développement. Perl est le pionnier presque (mais pas tout à tous, mais presque) tout ce que vous avez appris à compter sur les tendances modernes aujourd'hui, n'importe quel outil ou la langue que vous utilisez. Donc finalement, les autres vont le rattraper.
Mais ils ne se rattraper où Perl est un moment du passé, tout comme il est maintenant. Tout progrès. Dans regexes si rien d'autre, où Perl conduit, d'autres suivent. Où vais-Perl-être une fois tout le monde enfin captures où Perl est maintenant? Je n'ai aucune idée, mais je sais que nous aussi nous avons déménagé. Probablement, nous allons être plus proche de Perl₆ du style de l'artisanat des modèles.
Si vous aimez ce genre de chose, mais que vous souhaitez l'utiliser Perl₅, vous pourriez être intéressé par Damian Conway merveilleux Regexp::Grammaires module. C'est complètement génial, et fait ce que j'ai fait ici, dans mon programme semble tout aussi primitive que la mienne fait les modèles qui s'entassent sans espaces ou alphabétique identifiants. Check it out!
EDIT: HTML Simple Chunker
Voici la source de l'analyseur j'ai montré la pièce maîtresse de au début de cette publication.
Je suis pas ce qui suggère que vous devriez l'utiliser sur un rigoureusement testé l'analyse de classe. Mais je suis fatigué de gens qui font semblant que personne ne peut analyser HTML avec regexes juste parce qu' ils ne le peuvent pas. De toute évidence, vous pouvez, et ce programme est la preuve de cette affirmation.
Bien sûr, il n'est pas facile, mais c' est possible!
Et en essayant de le faire est une terrible perte de temps, car une bonne analyse des classes existent dont vous devriez utiliser pour cette tâche. Le droit de réponse des personnes qui tentent d'analyser arbitraire HTML, c'est pas que c'est impossible. C'est une facile et hypocrite réponse. La bonne et la réponse honnête est qu'ils ne devraient pas essayer parce que c'est trop de la peine à comprendre à partir de zéro; ils ne doivent pas casser le dos en s'efforçant de reïnvent une roue qui fonctionne parfaitement bien.
D'autre part, le code HTML qui tombe à l'intérieur d'un prévisible sous-ensemble est ultra-facile à analyser avec regexes. Il n'est pas étonnant que les gens essaient de les utiliser, parce que pour les petits problèmes, jouet des problèmes peut-être, rien ne pourrait être plus facile. C'est pourquoi il est si important de distinguer les deux tâches spécifiques vs générique - si elles ne sont pas nécessairement à la demande de la même démarche.
J'espère que dans le futur ici pour voir une plus juste et honnête, le traitement de questions sur le langage HTML et les regexes.
Voici mon code HTML lexer. Il n'essayez pas de faire une validation d'analyser; simplement, il identifie les éléments lexicaux. Vous pourriez penser qu'il est plus que HTML chunker qu'un analyseur HTML. Il n'est pas très facile à cassé HTML, bien qu'il en fait des très petites indemnités dans cette direction.
Même si vous n'avez jamais analyser HTML vous-même (et pourquoi devriez-vous? c'est un problème résolu!), ce programme a beaucoup de fraîcheur des regex peu que je crois que beaucoup de gens peuvent apprendre beaucoup. Profitez-en!
#!/usr/bin/env perl
#
# chunk_HTML - a regex-based HTML chunker
#
# Tom Christiansen <tchrist@perl.com
# Sun Nov 21 19:16:02 MST 2010
########################################
use 5.012;
use strict;
use autodie;
use warnings qw< FATAL all >;
use open qw< IN :bytes OUT :utf8 :std >;
MAIN: {
$| = 1;
lex_html(my $page = slurpy());
exit();
}
########################################################################
sub lex_html {
our $RX_SUBS; ###############
my $html = shift(); # Am I... #
for (;;) { # forgiven? :)#
given ($html) { ###############
last when (pos || 0) >= length;
printf "\@%d=", (pos || 0);
print "doctype " when / \G (?&doctype) $RX_SUBS /xgc;
print "cdata " when / \G (?&cdata) $RX_SUBS /xgc;
print "xml " when / \G (?&xml) $RX_SUBS /xgc;
print "xhook " when / \G (?&xhook) $RX_SUBS /xgc;
print "script " when / \G (?&script) $RX_SUBS /xgc;
print "style " when / \G (?&style) $RX_SUBS /xgc;
print "comment " when / \G (?&comment) $RX_SUBS /xgc;
print "tag " when / \G (?&tag) $RX_SUBS /xgc;
print "untag " when / \G (?&untag) $RX_SUBS /xgc;
print "nasty " when / \G (?&nasty) $RX_SUBS /xgc;
print "text " when / \G (?&nontag) $RX_SUBS /xgc;
default {
die "UNCLASSIFIED: " .
substr($_, pos || 0, (length > 65) ? 65 : length);
}
}
}
say ".";
}
#####################
# Return correctly decoded contents of next complete
# file slurped in from the <ARGV> stream.
#
sub slurpy {
our ($RX_SUBS, $Meta_Tag_Rx);
my $_ = do { local $/; <ARGV> }; # read all input
return unless length;
use Encode qw< decode >;
my $bom = "";
given ($_) {
$bom = "UTF-32LE" when / ^ \xFf \xFe \0 \0 /x; # LE
$bom = "UTF-32BE" when / ^ \0 \0 \xFe \xFf /x; # BE
$bom = "UTF-16LE" when / ^ \xFf \xFe /x; # le
$bom = "UTF-16BE" when / ^ \xFe \xFf /x; # be
$bom = "UTF-8" when / ^ \xEF \xBB \xBF /x; # st00pid
}
if ($bom) {
say "[BOM $bom]";
s/^...// if $bom eq "UTF-8"; # st00pid
# Must use UTF-(16|32) w/o -[BL]E to strip BOM.
$bom =~ s/-[LB]E//;
return decode($bom, $_);
# if BOM found, don't fall through to look
# for embedded encoding spec
}
# Latin1 is web default if not otherwise specified.
# No way to do this correctly if it was overridden
# in the HTTP header, since we assume stream contains
# HTML only, not also the HTTP header.
my $encoding = "iso-8859-1";
while (/ (?&xml) $RX_SUBS /pgx) {
my $xml = ${^MATCH};
next unless $xml =~ m{ $RX_SUBS
(?= encoding ) (?&name)
(?&equals)
(?"e) ?
(?<ENCODING> (?&value) )
}sx;
if (lc $encoding ne lc $+{ENCODING}) {
say "[XML ENCODING $encoding => $+{ENCODING}]";
$encoding = $+{ENCODING};
}
}
while (/$Meta_Tag_Rx/gi) {
my $meta = $+{META};
next unless $meta =~ m{ $RX_SUBS
(?= http-equiv ) (?&name)
(?&equals)
(?= (?"e)? content-type )
(?&value)
}six;
next unless $meta =~ m{ $RX_SUBS
(?= content ) (?&name)
(?&equals)
(?<CONTENT> (?&value) )
}six;
next unless $+{CONTENT} =~ m{ $RX_SUBS
(?= charset ) (?&name)
(?&equals)
(?<CHARSET> (?&value) )
}six;
if (lc $encoding ne lc $+{CHARSET}) {
say "[HTTP-EQUIV ENCODING $encoding => $+{CHARSET}]";
$encoding = $+{CHARSET};
}
}
return decode($encoding, $_);
}
########################################################################
# Make sure to this function is called
# as soon as source unit has been compiled.
UNITCHECK { load_rxsubs() }
# useful regex subroutines for HTML parsing
sub load_rxsubs {
our $RX_SUBS = qr{
(?(DEFINE)
(?<WS> \s * )
(?<any_nv_pair> (?&name) (?&equals) (?&value) )
(?<name> \b (?= \pL ) [\w:\-] + \b )
(?<equals> (?&WS) = (?&WS) )
(?<value> (?"ed_value) | (?&unquoted_value) )
(?<unwhite_chunk> (?: (?! > ) \S ) + )
(?<unquoted_value> [\w:\-] * )
(?<any_quote> ["'] )
(?<quoted_value>
(?<quote> (?&any_quote) )
(?: (?! \k<quote> ) . ) *
\k<quote>
)
(?<start_tag> < (?&WS) )
(?<html_end_tag> > )
(?<xhtml_end_tag> / > )
(?<end_tag>
(?&WS)
(?: (?&html_end_tag)
| (?&xhtml_end_tag) )
)
(?<tag>
(?&start_tag)
(?&name)
(?:
(?&WS)
(?&any_nv_pair)
) *
(?&end_tag)
)
(?<untag> </ (?&name) > )
# starts like a tag, but has screwed up quotes inside it
(?<nasty>
(?&start_tag)
(?&name)
.*?
(?&end_tag)
)
(?<nontag> [^<] + )
(?<string> (?"ed_value) )
(?<word> (?&name) )
(?<doctype>
<!DOCTYPE
# please don't feed me nonHTML
### (?&WS) HTML
[^>]* >
)
(?<cdata> <!\[CDATA\[ .*? \]\] > )
(?<script> (?= <script ) (?&tag) .*? </script> )
(?<style> (?= <style ) (?&tag) .*? </style> )
(?<comment> <!-- .*? --> )
(?<xml>
< \? xml
(?:
(?&WS)
(?&any_nv_pair)
) *
(?&WS)
\? >
)
(?<xhook> < \? .*? \? > )
)
}six;
our $Meta_Tag_Rx = qr{ $RX_SUBS
(?<META>
(?&start_tag) meta \b
(?:
(?&WS) (?&any_nv_pair)
) +
(?&end_tag)
)
}six;
}
# nobody *ever* remembers to do this!
END { close STDOUT }