Que faire Le modèle d'expression régulière ne correspond nulle part dans la chaîne?

182

J'essaie de faire correspondre les <input>champs de type «masqués» en utilisant ce modèle:

/<input type="hidden" name="([^"]*?)" value="([^"]*?)" />/

Voici un exemple de données de formulaire:

<input type="hidden" name="SaveRequired" value="False" /><input type="hidden" name="__VIEWSTATE1" value="1H4sIAAtzrkX7QfL5VEGj6nGi+nP" /><input type="hidden" name="__VIEWSTATE2" value="0351118MK" /><input type="hidden" name="__VIEWSTATE3" value="ZVVV91yjY" /><input type="hidden" name="__VIEWSTATE0" value="3" /><input type="hidden" name="__VIEWSTATE" value="" /><input type="hidden" name="__VIEWSTATE" value="" />

Mais je ne suis pas sûr que type, name, et valueattributs apparaîtront toujours dans le même ordre. Si l' typeattribut vient en dernier, la correspondance échouera car dans mon modèle, c'est au début.

Question:
Comment puis-je modifier mon modèle pour qu'il corresponde quelle que soit la position des attributs dans la <input>balise?

PS: Au fait, j'utilise l' outil de bureau RegEx basé sur Adobe Air pour tester les expressions régulières.

Salman
la source
4
regex est une excellente solution si vous avez le contrôle sur le html généré car il s'agit d'un débat régulier et non régulier, mais dans mon cas, je ne sais pas comment le HTML va changer à l'avenir, donc la meilleure chose est d'utiliser un analyseur au lieu de regex, j'ai utilisé regex dans mon projet dans les parties sur lesquelles j'ai le contrôle
Salman

Réponses:

108

Contrairement à toutes les réponses ici, car ce que vous essayez de faire regex est une solution parfaitement valable. C'est parce que vous n'essayez PAS de faire correspondre des balises équilibrées - CELA serait impossible avec regex! Mais vous ne faites correspondre que ce qui se trouve dans une balise, et c'est parfaitement normal.

Voici le problème, cependant. Vous ne pouvez pas le faire avec une seule regex ... vous devez faire une correspondance pour capturer une <input>balise, puis effectuer un traitement supplémentaire à ce sujet. Notez que cela ne fonctionnera que si aucune des valeurs d'attribut ne contient de >caractère, donc ce n'est pas parfait, mais cela devrait suffire pour des entrées saines.

Voici un (pseudo) code Perl pour vous montrer ce que je veux dire:

my $html = readLargeInputFile();

my @input_tags = $html =~ m/
    (
        <input                      # Starts with "<input"
        (?=[^>]*?type="hidden")     # Use lookahead to make sure that type="hidden"
        [^>]+                       # Grab the rest of the tag...
        \/>                         # ...except for the />, which is grabbed here
    )/xgm;

# Now each member of @input_tags is something like <input type="hidden" name="SaveRequired" value="False" />

foreach my $input_tag (@input_tags)
{
  my $hash_ref = {};
  # Now extract each of the fields one at a time.

  ($hash_ref->{"name"}) = $input_tag =~ /name="([^"]*)"/;
  ($hash_ref->{"value"}) = $input_tag =~ /value="([^"]*)"/;

  # Put $hash_ref in a list or something, or otherwise process it
}

Le principe de base ici est de ne pas essayer d'en faire trop avec une seule expression régulière. Comme vous l'avez remarqué, les expressions régulières imposent un certain ordre. Donc, ce que vous devez faire à la place est d'abord de faire correspondre le CONTEXTE de ce que vous essayez d'extraire, puis de faire un sous-appariement sur les données que vous voulez.

EDIT: Cependant, je conviendrai qu'en général, utiliser un analyseur HTML est probablement plus facile et meilleur et vous devriez vraiment envisager de reconcevoir votre code ou de réexaminer vos objectifs. :-) Mais j'ai dû poster cette réponse pour contrer la réaction instinctive selon laquelle l'analyse de tout sous-ensemble de HTML est impossible: HTML et XML sont tous les deux irréguliers lorsque l'on considère l'ensemble de la spécification, mais la spécification d'une balise est décemment régulière , certainement à la portée de PCRE.

Platine Azure
la source
14
Pas contraire à toutes les réponses ici. :)
tchrist
6
@tchrist: Votre réponse n'était pas là quand j'ai posté la mienne. ;-)
Platinum Azure
7
oui, pour une raison quelconque, il m'a fallu plus de temps pour taper que le vôtre. Je pense que mon clavier doit avoir besoin d'être graissé. :)
tchrist
6
Ce code HTML n'est pas valide - il devrait être value = "& lt; En êtes-vous vraiment sûr? & Gt;" Si l'endroit qu'il gratte fait un mauvais travail pour échapper à des choses comme celle-ci, alors il aura besoin d'une solution plus sophistiquée - mais s'ils le font correctement (et s'il en a le contrôle, il devrait s'assurer que c'est bien) alors il va bien.
Ross Snyder
14
Lien obligatoire vers la meilleure réponse SO sur le sujet (éventuellement meilleure période de réponse SO): stackoverflow.com/questions/1732348/…
Daniel Ribeiro
682

Oh oui, vous pouvez utiliser des expressions régulières pour analyser le HTML!

Pour la tâche que vous tentez, les expressions régulières sont parfaitement bien!

Il est vrai que la plupart des gens sous-estiment la difficulté d'analyser le HTML avec des expressions régulières et le font donc mal.

Mais ce n'est pas un défaut fondamental lié à la théorie computationnelle. Cette sottise est souvent évoquée ici , mais ne les croyez pas.

Donc, même si cela peut certainement être fait (cette publication sert de preuve d'existence de ce fait incontestable), cela ne signifie pas que cela  devrait l'  être.

Vous devez décider par vous-même si vous êtes à la hauteur de la tâche d'écrire ce qui équivaut à un analyseur HTML dédié et spécial à partir des expressions rationnelles. La plupart des gens ne le sont pas.

Mais je le suis. ☻


Solutions d'analyse HTML générales basées sur les expressions régulières

Je vais d'abord montrer à quel point il est facile d'analyser du HTML arbitraire avec des expressions rationnelles. Le programme complet est à la fin de cette publication, 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);
    }
  }
}

Vous voyez à quel point c'est facile à lire?

Tel qu'il est écrit, il identifie chaque morceau de HTML et indique où il l'a trouvé. Vous pouvez facilement le modifier pour faire tout ce que vous voulez avec n'importe quel type de pièce donné, ou pour des types plus particuliers que ceux-ci.

Je n'ai aucun cas de test défaillant (à gauche :): j'ai exécuté avec succès ce code sur plus de 100 000 fichiers HTML - chacun d'entre eux que je pourrais rapidement et facilement mettre la main sur. Au-delà de ceux-ci, je l'ai également exécuté sur des fichiers spécialement conçus pour briser les analyseurs naïfs.

Ce n'est pas un analyseur naïf.

Oh, je suis sûr que ce n'est pas parfait, mais je n'ai pas encore réussi à le casser. Je pense que même si quelque chose se produisait, le correctif serait facile à intégrer en raison de la structure claire du programme. Même les programmes contenant beaucoup de regex devraient avoir une structure.

Maintenant que ce n'est plus le cas, permettez-moi de répondre à la question du PO.

Démo de la résolution de la tâche du PO à l'aide des expressions rationnelles

Le petit html_input_rxprogramme que j'inclus ci-dessous produit la sortie suivante, afin que vous puissiez voir que l'analyse HTML avec des expressions régulières fonctionne parfaitement pour ce que vous souhaitez 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 d'entrée, voir aucune entrée mauvaise

Voici la source du programme qui a produit la sortie ci-dessus.

#!/usr/bin/env perl
#
# html_input_rx - pull out all <input> tags from (X)HTML src
#                  via simple regex processing
#
# Tom Christiansen <[email protected]>
# 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) 
            (?= (?&quote)? 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>           (?&quoted_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) 
                (?:
                    (?&quoted_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: $!";
} 

Voilà! Rien pour le faire! :)

Vous seul pouvez juger si votre compétence avec les regex est à la hauteur d'une tâche d'analyse particulière. Le niveau de compétence de chacun est différent et chaque nouvelle tâche est différente. Pour les travaux où vous avez un ensemble d'entrées bien défini, les expressions régulières sont évidemment le bon choix, car il est trivial d'en rassembler lorsque vous avez un sous-ensemble restreint de HTML à gérer. Même les débutants en regex devraient gérer ces tâches avec des regex. Tout le reste est exagéré.

Cependant , une fois que le HTML commence à devenir moins cloué, une fois qu'il commence à se ramifier d'une manière que vous ne pouvez pas prédire mais qui est parfaitement légale, une fois que vous devez faire correspondre des sortes de choses plus différentes ou avec des dépendances plus complexes, vous finirez par atteindre un point où vous devez travailler plus dur pour effectuer une solution qui utilise des expressions régulières que vous auriez à utiliser une classe d'analyse. L'endroit où ce seuil de rentabilité tombe dépend encore une fois de votre propre niveau de confort avec les expressions régulières.

Donc qu'est ce que je devrais faire?

Je ne vais pas vous dire ce que vous devez faire ou ce que vous ne pouvez pas faire. Je pense que c'est faux. Je veux juste vous présenter des possibilités, ouvrez un peu les yeux. Vous pouvez choisir ce que vous voulez faire et comment vous voulez le faire. Il n'y a pas d'absolus - et personne d'autre ne connaît votre propre situation aussi bien que vous-même. Si quelque chose semble être trop de travail, eh bien, c'est peut-être le cas. La programmation doit être amusante , vous savez. Si ce n'est pas le cas, vous le faites peut-être mal.

On peut regarder mon html_input_rxprogramme de plusieurs manières valables. L'une d'elles est que vous pouvez en effet analyser le HTML avec des expressions régulières. Mais une autre est que c'est beaucoup, beaucoup, beaucoup plus difficile que presque tout le monde ne le pense. Cela peut facilement conduire à la conclusion que mon programme témoigne de ce que vous ne devriez pas faire, car c'est vraiment trop difficile.

Je ne suis pas en désaccord avec cela. Certes, si tout ce que je fais dans mon programme n'a pas de sens pour vous après quelques études, alors vous ne devriez pas essayer d'utiliser des regex pour ce genre de tâche. Pour du HTML spécifique, les expressions régulières sont excellentes, mais pour du HTML générique, elles équivalent à de la folie. J'utilise des classes d'analyse tout le temps, surtout si c'est du HTML que je n'ai pas généré moi-même.

Les expressions régulières optimales pour les petits problèmes d'analyse HTML, pessimales pour les gros problèmes

Même si mon programme est considéré comme une illustration de la raison pour laquelle vous ne devriez pas utiliser les expressions régulières pour analyser le HTML général - ce qui est OK, parce que je voulais en quelque sorte que ce soit cela ☺ - cela devrait quand même être une révélation afin que plus de gens cassent le terriblement commun et la mauvaise habitude d'écrire des motifs illisibles, non structurés et non maintenables.

Les motifs ne doivent pas être laids et ils ne doivent pas nécessairement être durs. Si vous créez des motifs laids, c'est une réflexion sur vous, pas sur eux.

Langage Regex phénoménalement exquis

On m'a demandé de signaler que ma solution proposée à votre problème a été écrite en Perl. Êtes-vous surpris? N'avez-vous pas remarqué? Cette révélation est-elle une bombe?

Il est vrai que tous les autres outils et langages de programmation ne sont pas aussi pratiques, expressifs et puissants en matière d'expressions rationnelles que Perl. Il existe un large spectre, certains étant plus adaptés que d'autres. En général, les langages qui ont exprimé des expressions rationnelles comme faisant partie du langage de base plutôt que comme une bibliothèque sont plus faciles à utiliser. Je n'ai rien fait avec les expressions régulières que vous ne pourriez pas faire dans, par exemple, PCRE, même si vous structureriez le programme différemment si vous utilisiez C.

Finalement, d'autres langages seront rattrapés par la situation actuelle de Perl en termes de regex. Je dis cela parce qu'à l'époque où Perl a commencé, personne d'autre n'avait rien de tel que les expressions régulières de Perl. Dites ce que vous voulez, mais c'est là que Perl a clairement gagné: tout le monde a copié les expressions rationnelles de Perl, bien qu'à des stades variables de leur développement. Perl a été le pionnier de presque (pas tout à fait, mais presque) tout ce sur quoi vous vous êtes habitué dans les modèles modernes, quel que soit l'outil ou le langage que vous utilisez. Donc , finalement les autres vont rattraper leur retard.

Mais ils ne rattraperont que la position de Perl dans le passé, comme c'est le cas maintenant. Tout avance. Dans les expressions régulières, si rien d'autre, là où Perl mène, d'autres suivent. Où sera Perl une fois que tout le monde aura enfin compris où Perl est maintenant? Je n'en ai aucune idée, mais je sais que nous aussi nous aurons déménagé. Nous serons probablement plus proches du style de création de motifs de Perl₆ .

Si vous aimez ce genre de chose mais que vous souhaitez l'utiliser en Perl₅, vous pourriez être intéressé par le merveilleux module Regexp :: Grammars de Damian Conway . C'est complètement génial, et ce que j'ai fait ici dans mon programme semble tout aussi primitif que le mien, ce qui fait que les gens s'entassent sans espaces ni identificateurs alphabétiques. Vérifiez-le!


Chunker HTML simple

Voici la source complète de l'analyseur dont j'ai montré la pièce maîtresse au début de cette publication.

Je ne suggère pas que vous devriez utiliser ceci sur une classe d'analyse rigoureusement testée. Mais je suis fatigué des gens qui prétendent que personne ne peut analyser du HTML avec des expressions régulières simplement parce qu'ils ne le peuvent pas. Vous pouvez clairement, et ce programme est la preuve de cette affirmation.

Bien sûr, il est pas facile, mais il est possible!

Et essayer de le faire est une terrible perte de temps, car il existe de bonnes classes d'analyse que vous devriez utiliser pour cette tâche. La bonne réponse aux personnes qui essaient d'analyser du HTML arbitraire n'est pas que ce soit impossible. C'est une réponse facile et malhonnête. La réponse correcte et honnête est qu'ils ne devraient pas essayer parce que c'est trop ennuyeux de partir de zéro; ils ne doivent pas se casser le dos en cherchant à réinventer une roue qui fonctionne parfaitement.

D'un autre côté, le HTML qui appartient à un sous - ensemble prévisible est ultra-facile à analyser avec les expressions rationnelles. Il n'est pas étonnant que les gens essaient de les utiliser, car pour de petits problèmes, des problèmes de jouets peut-être, rien de plus facile. C'est pourquoi il est si important de distinguer les deux tâches - spécifiques et génériques - car elles ne nécessitent pas nécessairement la même approche.

J'espère à l'avenir voir ici un traitement plus juste et plus honnête des questions sur le HTML et les expressions régulières.

Voici mon lexer HTML. Il n'essaye pas de faire une analyse de validation; il identifie simplement les éléments lexicaux. Vous pourriez le considérer plus comme un bloc HTML que comme un analyseur HTML. Il ne pardonne pas très bien le HTML cassé, bien que cela fasse de très petites allocations dans cette direction.

Même si vous n'analysez jamais le code HTML complet vous-même (et pourquoi devriez-vous? C'est un problème résolu!), Ce programme a beaucoup de bits regex sympas dont je pense que beaucoup de gens peuvent apprendre beaucoup. Prendre plaisir!

#!/usr/bin/env perl
#
# chunk_HTML - a regex-based HTML chunker
#
# Tom Christiansen <[email protected]
#   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)
                            (?&quote) ?
            (?<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)
            (?= (?&quote)? 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>           (?&quoted_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> (?&quoted_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 }
tchrist
la source
23
deux points forts de votre commentaire "J'utilise des classes d'analyse tout le temps, surtout si c'est du HTML que je n'ai pas généré moi-même." et "Les motifs ne doivent pas être laids, et ils ne doivent pas être durs. Si vous créez des motifs laids, c'est une réflexion sur vous, pas sur eux." Je suis tout à fait d'accord avec ce que vous avez dit, donc je réévalue le problème. merci beaucoup pour cette réponse détaillée
Salman
168
Pour ceux qui ne le savent pas, j'ai pensé mentionner que Tom est le co-auteur de "Programming Perl" (alias le livre Camel) et l'une des principales autorités Perl. Si vous doutez que ce soit le vrai Tom Christiansen, revenez en arrière et lisez le message.
Bill Ruppert
21
Pour résumer: les RegEx sont mal nommés. Je pense que c'est dommage, mais ça ne changera pas. Les moteurs 'RegEx' compatibles ne sont pas autorisés à rejeter les langues non régulières. Ils ne peuvent donc pas être correctement implémentés avec uniquement des machines à états Finte. Les concepts puissants autour des classes de calcul ne s'appliquent pas. L'utilisation de RegEx n'assure pas le temps d'exécution O (n). Les avantages de RegEx sont la syntaxe laconique et le domaine implicite de la reconnaissance des caractères. Pour moi, c'est une épave de train lente, impossible de détourner le regard, mais avec des conséquences horribles.
Steve Steiner
27
@tchrist, cela ne répond jamais à la question initiale des OP. Et analyser le terme approprié ici? Afaics, les regex font une analyse de tokenisation / lexicale, mais l'analyse finale est faite avec du code Perl, pas avec les regex eux-mêmes.
Qtax
66
@tchrist Très impressionnant. Vous êtes évidemment un programmeur Perl hautement qualifié et talentueux, et extrêmement bien informé sur les expressions régulières modernes. Je ferais remarquer, cependant, que ce que vous avez écrit n'est pas vraiment une expression régulière (moderne, régulière ou autre), mais plutôt un programme Perl qui utilise fortement les expressions régulières. Votre message soutient-il vraiment l'affirmation selon laquelle les expressions régulières peuvent analyser correctement le HTML? Ou est-ce plutôt une preuve que Perl peut analyser correctement le HTML? Dans tous les cas, beau travail!
Mike Clark
127
  1. Tu peux écrire un roman comme l'a fait Tchrist
  2. Vous pouvez utiliser une bibliothèque DOM, charger le HTML et utiliser xpath et simplement utiliser //input[@type="hidden"]. Ou si vous ne voulez pas utiliser xpath, récupérez simplement toutes les entrées et filtrez celles qui sont masquées getAttribute.

Je préfère le n ° 2.

<?php

$d = new DOMDocument();
$d->loadHTML(
    '
    <p>fsdjl</p>
    <form><div>fdsjl</div></form>
    <input type="hidden" name="blah" value="hide yo kids">
    <input type="text" name="blah" value="hide yo kids">
    <input type="hidden" name="blah" value="hide yo wife">
');
$x = new DOMXpath($d);
$inputs = $x->evaluate('//input[@type="hidden"]');

foreach ( $inputs as $input ) {
    echo $input->getAttribute('value'), '<br>';
}

Résultat:

hide yo kids<br>hide yo wife<br>
meder omuraliev
la source
72
C'était un peu mon argument, en fait. Je voulais montrer à quel point c'est difficile.
tchrist
19
Très bon truc là-bas. J'avais vraiment espéré que les gens montreraient à quel point il est plus facile d'utiliser une classe d'analyse, alors merci! Je voulais juste un exemple de travail des problèmes extrêmes que vous devez traverser pour le faire à partir de zéro en utilisant des expressions rationnelles. J'espère que la plupart des gens concluront à utiliser des analyseurs préfabriqués sur du HTML générique au lieu de lancer le leur. Les expressions régulières sont toujours idéales pour le HTML simple qu'elles ont créé elles-mêmes, car cela élimine 99,98% de la complexité.
tchrist
5
Ce qui serait bien après avoir lu ces 2 approches très intéressantes serait de comparer la vitesse / l'utilisation de la mémoire / le processeur d'une approche à une autre (c'est-à-dire la classe d'analyse VS basée sur les regex).
the_yellow_logo
1
@ Avt'W Oui, non pas que vous deviez écrire un «roman» si les expressions régulières se trouvent être plus rapides, mais en fait, ce serait vraiment intéressant de savoir. :) Mais je suppose déjà qu'un analyseur prend moins de ressources, aussi ..
Dennis98
C'est en fait pourquoi XPath a été inventé en premier lieu!
Thorbjørn Ravn Andersen
21

Dans l'esprit de la solution lexer de Tom Christiansen, voici un lien vers l'article de 1998 apparemment oublié de Robert Cameron, REX: XML Shallow Parsing with Regular Expressions.

http://www.cs.sfu.ca/~cameron/REX.html

Abstrait

La syntaxe de XML est suffisamment simple pour qu'il soit possible d'analyser un document XML en une liste de ses éléments de balisage et de texte à l'aide d'une seule expression régulière. Une telle analyse superficielle d'un document XML peut être très utile pour la construction d'une variété d'outils de traitement XML légers. Cependant, les expressions régulières complexes peuvent être difficiles à construire et encore plus difficiles à lire. En utilisant une forme de programmation littéraire pour les expressions régulières, cet article documente un ensemble d'expressions d'analyse XML superficielles qui peuvent être utilisées comme base pour une analyse XML superficielle simple, correcte, efficace, robuste et indépendante du langage. Des implémentations complètes d'analyseur superficiel de moins de 50 lignes chacune en Perl, JavaScript et Lex / Flex sont également fournies.

Si vous aimez lire sur les expressions régulières, l'article de Cameron est fascinant. Son écriture est concise, approfondie et très détaillée. Il ne vous montre pas simplement comment construire l'expression régulière REX, mais aussi une approche pour construire une expression régulière complexe à partir de parties plus petites.

J'utilise l'expression régulière REX depuis 10 ans pour résoudre le type de problème sur lequel l'affiche initiale a posé la question (comment faire correspondre cette balise particulière mais pas une autre balise très similaire?). J'ai trouvé la regex qu'il a développée pour être complètement fiable.

REX est particulièrement utile lorsque vous vous concentrez sur les détails lexicaux d'un document - par exemple, lors de la transformation d'un type de document texte (par exemple, texte brut, XML, SGML, HTML) en un autre, où le document peut ne pas être valide, bien formé, voire analysable pour la majeure partie de la transformation. Il vous permet de cibler des îlots de balisage n'importe où dans un document sans déranger le reste du document.

David
la source
7

Bien que j'aime le contenu du reste de ces réponses, elles n'ont pas vraiment répondu à la question directement ou aussi correctement. Même la réponse de Platinum était trop compliquée et aussi moins efficace. J'ai donc été obligé de mettre ça.

Je suis un grand partisan de Regex, lorsqu'il est utilisé correctement. Mais à cause de la stigmatisation (et des performances), je déclare toujours qu'un XML ou HTML bien formé devrait utiliser un analyseur XML. Et des performances encore meilleures seraient l'analyse des chaînes, bien qu'il y ait une ligne entre la lisibilité si cela devient trop incontrôlable. Cependant, ce n'est pas la question. La question est de savoir comment faire correspondre une balise d'entrée de type masqué. La réponse est:

<input[^>]*type="hidden"[^>]*>

En fonction de votre saveur, la seule option regex que vous devez inclure est l'option ignorecase.

Suamere
la source
5
<input type='hidden' name='Oh, <really>?' value='Try a real HTML parser instead.'>
Ilmari Karonen
4
Votre exemple se referme automatiquement. Doit se terminer par />. En outre, alors que les chances d'avoir un >dans le champ de nom sont presque nulles, il est en effet possible qu'il y ait un >dans une poignée d'action. EG: Un appel javascript en ligne sur la propriété OnClick. Cela étant dit, j'ai un analyseur XML pour ceux-ci, mais aussi un Regex pour ceux où le document que je suis donné est trop foiré pour que les analyseurs XML puissent le gérer, mais un Regex le peut. De plus, ce n'était pas la question. Vous ne rencontrerez jamais ces situations avec une entrée cachée, et ma réponse est la meilleure. Ya, <really>!.
Suamere
3
/>est un XML-ism; il n'est requis dans aucune version de HTML, sauf pour XHTML (qui n'a jamais vraiment gagné en popularité, et a été pratiquement remplacé par HTML5). Et vous avez raison de dire qu'il y a beaucoup de HTML malpropre et pas vraiment valide, mais un bon analyseur HTML (et non XML) devrait être capable d'en gérer la plupart; si ce n'est pas le cas, les navigateurs non plus.
Ilmari Karonen
1
Si la seule analyse ou recherche dont vous avez besoin est un seul coup pour renvoyer une collection de champs d'entrée masqués, cette expression régulière serait parfaite. Utiliser soit la (les) classe (s) de document XML .NET, ou référencer un analyseur XML / HTML tiers simplement pour appeler une méthode serait excessif lorsque Regex est intégré. Et vous avez raison qu'un site Web est tellement foiré qu'un bon HTML l'analyseur ne pouvait pas le gérer, ce n'est probablement même pas quelque chose qu'un développeur examinerait. Mais mon entreprise reçoit des millions de pages par mois qui sont concaténées et capturées de nombreuses manières, de sorte que parfois (pas toujours), Regex est la meilleure option.
Suamere
1
Le seul point étant que nous ne sommes pas sûrs de la raison pour laquelle l'entreprise entière veut cette réponse. Mais c'est ce qu'il a demandé.
Suamere
3

vous pouvez essayer ceci:

<[A-Za-z ="/_0-9+]*>

et pour un résultat plus proche, vous pouvez essayer ceci:

<[ ]*input[ ]+type="hidden"[ ]*name=[A-Za-z ="_0-9+]*[ ]*[/]*>

vous pouvez tester votre modèle regex ici http://regexpal.com/

ces pattens sont bons pour cela:

<input type="hidden" name="SaveRequired" value="False" /><input type="hidden" name="__VIEWSTATE1" value="1H4sIAAtzrkX7QfL5VEGj6nGi+nP" /><input type="hidden" name="__VIEWSTATE2" value="0351118MK" /><input type="hidden" name="__VIEWSTATE3" value="ZVVV91yjY" />

et pour un ordre aléatoire de type, nameet valuevous pouvez utiliser ceci:

<[ ]*input[ ]*[A-Za-z ="_0-9+/]*>

ou

<[ ]*input[ ]*[A-Za-z ="_0-9+/]*[ ]*[/]>

sur ce :

<input  name="SaveRequired" type="hidden" value="False" /><input type="hidden" name="__VIEWSTATE1" value="1H4sIAAtzrkX7QfL5VEGj6nGi+nP" /><input type="hidden" name="__VIEWSTATE2" value="0351118MK" /><input  name="__VIEWSTATE3" type="hidden" value="ZVVV91yjY" />

»

au fait, je pense que vous voulez quelque chose comme ça:

<[ ]*input(([ ]*type="hidden"[ ]*name=[A-Za-z0-9_+"]*[ ]*value=[A-Za-z0-9_+"]*[ ]*)+)[ ]*/>|<[ ]*input(([ ]*type="hidden"[ ]*value=[A-Za-z0-9_+"]*[ ]*name=[A-Za-z0-9_+"]*[ ]*)+)[ ]*/>|<[ ]*input(([ ]*name=[A-Za-z0-9_+"]*[ ]*type="hidden"[ ]*value=[A-Za-z0-9_+"]*[ ]*)+)[ ]*/>|<[ ]*input(([ ]*value=[A-Za-z0-9_+"]*[ ]*type="hidden"[ ]*name=[A-Za-z0-9_+"]*[ ]*)+)[ ]*/>|<[ ]*input(([ ]*name=[A-Za-z0-9_+"]*[ ]*value=[A-Za-z0-9_+"]*[ ]*type="hidden"[ ]*)+)[ ]*/>|<[ ]*input(([ ]*value=[A-Za-z0-9_+"]*[ ]*name=[A-Za-z0-9_+"]*[ ]*type="hidden"[ ]*)+)[ ]*/>

ce n'est pas bon mais ça marche en aucune façon.

testez-le sur: http://regexpal.com/

Shamshirsaz.Navid
la source
1

Je voudrais utiliser **DOMDocument**pour extraire le code html.

$dom = new DOMDocument();
$dom ->loadHTML($input);
$x = new DOMXpath($dom );
$results = $x->evaluate('//input[@type="hidden"]');

foreach ( $results as $item) {
    print_r( $item->getAttribute('value') );
}

BTW, vous pouvez le tester ici - regex101.com. Il montre le résultat en temps réel. Quelques règles sur Regexp: http://www.eclipse.org/tptp/home/downloads/installguide/gla_42/ref/rregexp.html Reader .

Développeur HTML5
la source
0

supposons que votre contenu html soit stocké dans la chaîne html, puis pour obtenir chaque entrée qui contient le type masqué, vous pouvez utiliser une expression régulière

var regex = /(<input.*?type\s?=\s?["']hidden["'].*?>)/g;
html.match(regex);

la recherche régulière ci-dessus <inputsuivie d'un nombre quelconque de caractères jusqu'à ce qu'elle obtiennetype="hidden" ou tapez = 'hidden' suivi d'un nombre quelconque de caractères jusqu'à ce qu'elle obtienne>

/ g indique à l'expression régulière de trouver chaque sous-chaîne qui correspond au modèle donné.

Nitin9791
la source