Was tun? Muster für reguläre Ausdrücke stimmen nirgendwo in der Zeichenfolge überein?


181

Ich versuche, <input>mit diesem Muster "versteckte" Felder vom Typ abzugleichen:

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

Dies sind Beispielformulardaten:

<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="" />

Aber ich bin nicht sicher , dass das type, nameund valueAttribute werden immer in der gleichen Reihenfolge erscheinen. Wenn das typeAttribut das letzte ist, schlägt die Übereinstimmung fehl, da es in meinem Muster am Anfang steht.

Frage:
Wie kann ich mein Muster so ändern, dass es unabhängig von der Position der Attribute im <input>Tag übereinstimmt ?

PS: Übrigens verwende ich das auf Adobe Air basierende RegEx Desktop Tool zum Testen regulärer Ausdrücke.


4
Regex ist eine ausgezeichnete Lösung, wenn Sie die Kontrolle über generiertes HTML haben, da es regelmäßig und nicht regelmäßig diskutiert wird. In meinem Fall weiß ich jedoch nicht, wie sich HTML in Zukunft ändern wird. Daher ist es am besten, Parser anstelle von Regex zu verwenden, das ich verwendet habe Regex in meinem Projekt in Teilen, über die ich die Kontrolle habe
Salman

Antworten:


108

Im Gegensatz zu allen Antworten hier ist das, was Sie versuchen, Regex zu machen, eine vollkommen gültige Lösung. Dies liegt daran, dass Sie NICHT versuchen, ausgeglichene Tags abzugleichen - DAS wäre mit Regex unmöglich! Aber Sie stimmen nur mit dem überein, was in einem Tag enthalten ist, und das ist vollkommen normal.

Hier ist jedoch das Problem. Sie können es nicht mit nur einem regulären Ausdruck tun ... Sie müssen ein Match durchführen, um ein <input>Tag zu erfassen , und dann die weitere Verarbeitung durchführen. Beachten Sie, dass dies nur funktioniert, wenn keiner der Attributwerte ein >Zeichen enthält. Es ist also nicht perfekt, sollte aber für vernünftige Eingaben ausreichen.

Hier ist ein Perl (Pseudo) Code, der Ihnen zeigt, was ich meine:

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
}

Das Grundprinzip hier ist, nicht zu viel mit einem regulären Ausdruck zu tun. Wie Sie bemerkt haben, erzwingen reguläre Ausdrücke eine bestimmte Reihenfolge. Stattdessen müssen Sie also zuerst den KONTEXT des zu extrahierenden Objekts abgleichen und dann die gewünschten Daten submatching.

BEARBEITEN: Ich werde jedoch zustimmen, dass die Verwendung eines HTML-Parsers im Allgemeinen wahrscheinlich einfacher und besser ist und Sie wirklich in Betracht ziehen sollten, Ihren Code neu zu gestalten oder Ihre Ziele zu überprüfen. :-) Aber ich musste diese Antwort posten, um der Reaktion zu widersprechen, dass das Parsen einer Teilmenge von HTML unmöglich ist: HTML und XML sind beide unregelmäßig, wenn man die gesamte Spezifikation betrachtet, aber die Spezifikation eines Tags ist anständig regelmäßig , sicherlich in der Macht von PCRE.


14
Nicht im Gegensatz zu allen Antworten hier. :)
tchrist

6
@tchrist: Deine Antwort war nicht hier, als ich meine gepostet habe. ;-)
Platinum Azure

7
Na ja - aus irgendeinem Grund habe ich länger zum Tippen gebraucht als bei Ihnen. Ich denke, meine Tastatur muss geschmiert werden. :)
tchrist

6
Das ist ungültiges HTML - es sollte value = "& lt; Sind Sie sich da wirklich sicher? & Gt;" Wenn der Ort, an dem er kratzt, einen schlechten Job macht, um solchen Dingen zu entkommen, dann braucht er eine ausgefeiltere Lösung - aber wenn sie es richtig machen (und wenn er die Kontrolle darüber hat, sollte er sicherstellen, dass es richtig ist), geht es ihm gut.
Ross Snyder

14
Obligatorischer Link zur besten SO-Antwort zu diesem Thema (möglicherweise bester SO- Antwortzeitraum
Daniel Ribeiro

682

Oh ja, Sie können Regexes verwenden, um HTML zu analysieren!

Für die Aufgabe, die Sie versuchen, sind Regexes vollkommen in Ordnung!

Es ist wahr, dass die meisten Leute die Schwierigkeit, HTML mit regulären Ausdrücken zu analysieren, unterschätzen und dies daher schlecht tun.

Dies ist jedoch kein grundlegender Fehler im Zusammenhang mit der Computertheorie. Diese Albernheit wird hier viel nachgeahmt , aber glauben Sie ihnen nicht.

Obwohl dies sicherlich möglich ist (dieses Posting dient als Existenzbeweis für diese unbestreitbare Tatsache), heißt das nicht, dass es so  sein  sollte.

Sie müssen selbst entscheiden, ob Sie in der Lage sind, aus regulären Ausdrücken einen dedizierten, speziellen HTML-Parser zu schreiben. Die meisten Menschen sind es nicht.

Aber ich bin es. ☻


Allgemeine Regex-basierte HTML-Parsing-Lösungen

Zuerst werde ich zeigen, wie einfach es ist, beliebigen HTML-Code mit regulären Ausdrücken zu analysieren . Das vollständige Programm finden Sie am Ende dieses Beitrags, aber das Herzstück des Parsers ist:

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);
    }
  }
}

Sehen Sie, wie einfach das zu lesen ist?

Wie geschrieben, identifiziert es jedes Stück HTML und sagt, wo es dieses Stück gefunden hat. Sie können es leicht modifizieren, um mit einem bestimmten Stücktyp oder für bestimmte Typen als diese zu tun, was Sie wollen.

Ich habe keine fehlgeschlagenen Testfälle (links :): Ich habe diesen Code erfolgreich auf mehr als 100.000 HTML-Dateien ausgeführt - jede einzelne konnte ich schnell und einfach in die Hände bekommen. Darüber hinaus habe ich es auch für Dateien ausgeführt, die speziell dafür entwickelt wurden, naive Parser zu brechen.

Dies ist kein naiver Parser.

Oh, ich bin sicher, dass es nicht perfekt ist, aber ich habe es noch nicht geschafft, es zu brechen. Ich denke, selbst wenn etwas passieren würde, wäre das Update aufgrund der klaren Struktur des Programms leicht zu integrieren. Sogar Regex-schwere Programme sollten strukturiert sein.

Lassen Sie mich nun, da dies nicht möglich ist, auf die Frage des OP eingehen.

Demo zur Lösung der Aufgabe des OP mithilfe von Regexes

Das kleine html_input_rxProgramm, das ich unten einbinde, erzeugt die folgende Ausgabe, sodass Sie sehen können, dass das Parsen von HTML mit regulären Ausdrücken für das, was Sie tun möchten, einwandfrei funktioniert:

% 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"

Analysieren Sie Eingabe-Tags, siehe Keine bösen Eingaben

Hier ist die Quelle für das Programm, das die obige Ausgabe erzeugt hat.

#!/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) 
            (?= (?&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: $!";
} 

Los geht's! Nichts dazu! :) :)

Nur Sie können beurteilen, ob Ihre Fähigkeiten mit regulären Ausdrücken einer bestimmten Analyseaufgabe gewachsen sind. Jeder hat ein anderes Können und jede neue Aufgabe ist anders. Für Jobs, bei denen Sie einen genau definierten Eingabesatz haben, sind reguläre Ausdrücke offensichtlich die richtige Wahl, da es trivial ist, einige zusammenzustellen, wenn Sie eine eingeschränkte Teilmenge von HTML haben. Sogar Regex-Anfänger sollten diese Jobs mit Regexes erledigen. Alles andere ist übertrieben.

Jedoch , sobald die HTML weniger genagelt beginnen immer, wenn es in einer Weise zu verzweigen beginnt kann man nicht vorhersagen , aber die sind vollkommen legal, wenn Sie mehr verschiedene Arten von Dingen oder mit komplexeren Abhängigkeiten entsprechen haben, werden Sie irgendwann einen Punkt erreichen , wo Sie müssen härter arbeiten, um eine Lösung zu erzielen, die reguläre Ausdrücke verwendet, als wenn Sie eine Parsing-Klasse verwenden müssten. Wo dieser Break-Even-Punkt liegt, hängt wiederum von Ihrem eigenen Komfortniveau mit Regexen ab.

Also was soll ich tun?

Ich werde Ihnen nicht sagen, was Sie tun müssen oder was Sie nicht tun können. Ich denke das ist falsch. Ich möchte Ihnen nur die Möglichkeiten vorstellen, Ihre Augen ein wenig öffnen. Sie können wählen, was Sie tun möchten und wie Sie es tun möchten. Es gibt keine Absoluten - und niemand kennt Ihre eigene Situation so gut wie Sie selbst. Wenn etwas zu viel Arbeit zu sein scheint, ist es es vielleicht. Das Programmieren sollte Spaß machen , wissen Sie. Wenn nicht, machen Sie es möglicherweise falsch.

Man kann mein html_input_rxProgramm auf eine beliebige Anzahl gültiger Arten betrachten. Eine davon ist, dass Sie tatsächlich HTML mit regulären Ausdrücken analysieren können . Aber ein anderer ist, dass es viel, viel, viel schwieriger ist, als fast jeder jemals denkt, dass es so ist. Dies kann leicht zu dem Schluss führen, dass mein Programm ein Beweis dafür ist, was Sie nicht tun sollten, weil es wirklich zu schwer ist.

Dem werde ich nicht widersprechen. Wenn alles, was ich in meinem Programm mache, nach einem Studium für Sie keinen Sinn ergibt, sollten Sie sicherlich nicht versuchen, Regexes für diese Art von Aufgabe zu verwenden. Für spezifisches HTML sind reguläre Ausdrücke großartig, aber für generisches HTML sind sie gleichbedeutend mit Wahnsinn. Ich verwende ständig Parsing-Klassen, insbesondere wenn es sich um HTML handelt, das ich nicht selbst generiert habe.

Regexes optimal für kleine HTML-Analyseprobleme, pessimal für große

Selbst wenn mein Programm als Beispiel dafür dient, warum Sie keine regulären Ausdrücke zum Parsen von allgemeinem HTML verwenden sollten - was in Ordnung ist, weil ich es irgendwie so gemeint habe -, sollte es dennoch ein Augenöffner sein, damit mehr Menschen das schrecklich Gemeinsame brechen und böse, böse Angewohnheit, unlesbare, unstrukturierte und nicht wartbare Muster zu schreiben.

Muster müssen nicht hässlich sein und sie müssen nicht hart sein. Wenn Sie hässliche Muster erstellen, ist dies eine Reflexion über Sie, nicht über sie.

Phänomenal exquisite Regex-Sprache

Ich wurde gebeten, darauf hinzuweisen, dass meine professionelle Lösung für Ihr Problem in Perl geschrieben wurde. Bist du überrascht? Hast du es nicht bemerkt? Ist diese Offenbarung eine Bombe?

Es ist wahr, dass nicht alle anderen Tools und Programmiersprachen in Bezug auf Regexes so bequem, ausdrucksstark und leistungsstark sind wie Perl. Es gibt da draußen ein großes Spektrum, von denen einige besser geeignet sind als andere. Im Allgemeinen ist es einfacher, mit den Sprachen zu arbeiten, die reguläre Ausdrücke als Teil der Kernsprache und nicht als Bibliothek ausgedrückt haben. Ich habe nichts mit regulären Ausdrücken gemacht, was Sie beispielsweise in PCRE nicht tun könnten, obwohl Sie das Programm anders strukturieren würden, wenn Sie C verwenden würden.

Irgendwann werden andere Sprachen in Bezug auf reguläre Ausdrücke aufholen, wo Perl jetzt ist. Ich sage das, weil damals, als Perl anfing, niemand so etwas wie Perls Regexe hatte. Sagen Sie alles, was Sie möchten, aber hier hat Perl eindeutig gewonnen: Alle haben Perls Regexe kopiert, wenn auch in unterschiedlichen Stadien ihrer Entwicklung. Perl war Pionier bei fast (nicht allen, aber fast) allem, worauf Sie sich heute in modernen Mustern verlassen, unabhängig davon, welches Werkzeug oder welche Sprache Sie verwenden. Also werden die anderen irgendwann aufholen.

Aber sie werden nur aufholen, wo Perl irgendwann in der Vergangenheit war, so wie es jetzt ist. Alles schreitet voran. In regulären Ausdrücken, wenn nichts anderes, wohin Perl führt, folgen andere. Wo wird Perl sein, wenn alle anderen endlich aufholen, wo Perl jetzt ist? Ich habe keine Ahnung, aber ich weiß, dass auch wir umgezogen sein werden. Wahrscheinlich sind wir näher an Perls Art, Muster herzustellen .

Wenn Sie so etwas mögen, es aber in Perl₅ verwenden möchten, könnte Sie Damian Conways wundervolles Regexp :: Grammars- Modul interessieren . Es ist absolut fantastisch und lässt das, was ich hier in meinem Programm getan habe, genauso primitiv erscheinen wie meine Muster, die Menschen ohne Leerzeichen oder alphabetische Bezeichner zusammenpressen. Hör zu!


Einfacher HTML-Chunker

Hier ist die vollständige Quelle für den Parser, von dem ich das Herzstück zu Beginn dieses Beitrags gezeigt habe.

Ich schlage nicht vor , dass Sie dies für eine streng getestete Parsing-Klasse verwenden sollten. Aber ich habe es satt, dass Leute so tun, als könne niemand HTML mit regulären Ausdrücken analysieren, nur weil sie es nicht können. Sie können es eindeutig, und dieses Programm ist ein Beweis für diese Behauptung.

Sicher, es ist nicht einfach, aber es ist möglich!

Und versucht , so zu tun , ist eine schreckliche Verschwendung von Zeit, weil gute Parsing Klassen existieren , die Sie sollten für diese Aufgabe verwenden. Die richtige Antwort für Leute, die versuchen, willkürliches HTML zu analysieren, ist nicht, dass es unmöglich ist. Das ist eine einfache und unaufrichtige Antwort. Die richtige und ehrliche Antwort ist, dass sie es nicht versuchen sollten, weil es zu mühsam ist, es von Grund auf neu herauszufinden. Sie sollten sich nicht den Rücken brechen, um ein Rad neu zu erfinden, das perfekt funktioniert.

Auf der anderen Seite ist HTML, das in eine vorhersehbare Teilmenge fällt, sehr einfach mit regulären Ausdrücken zu analysieren. Es ist kein Wunder, dass die Leute versuchen, sie zu benutzen, denn für kleine Probleme, Spielzeugprobleme, könnte nichts einfacher sein. Aus diesem Grund ist es so wichtig, die beiden Aufgaben - spezifisch und generisch - zu unterscheiden, da diese nicht unbedingt denselben Ansatz erfordern.

Ich hoffe, dass wir in Zukunft hier eine fairere und ehrlichere Behandlung von Fragen zu HTML und Regexes sehen können.

Hier ist mein HTML-Lexer. Es wird nicht versucht, eine validierende Analyse durchzuführen. es identifiziert nur die lexikalischen Elemente. Sie können sich das eher als HTML-Chunker als als HTML-Parser vorstellen . Es ist nicht sehr verzeihend für kaputtes HTML, obwohl es einige sehr kleine Zulassungen in diese Richtung zulässt.

Selbst wenn Sie niemals selbst vollständiges HTML analysieren (und warum sollten Sie das? Es ist ein gelöstes Problem!), Enthält dieses Programm viele coole Regex-Bits, von denen ich glaube, dass viele Leute viel lernen können. Genieß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)
                            (?&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 }

23
Zwei Highlights aus Ihrem Kommentar "Ich verwende ständig Parsing-Klassen, insbesondere wenn es sich um HTML handelt, das ich nicht selbst generiert habe." und "Muster müssen nicht hässlich sein, und sie müssen nicht hart sein. Wenn Sie hässliche Muster erstellen, ist dies eine Reflexion über Sie, nicht über sie." Ich stimme voll und ganz dem zu, was Sie gesagt haben, also bewerte ich das Problem neu. Vielen Dank für diese detaillierte Antwort
Salman

168
Für diejenigen, die es nicht wissen, dachte ich, ich würde erwähnen, dass Tom der Co-Autor von "Programming Perl" (auch bekannt als das Kamelbuch) und eine der besten Perl-Autoritäten ist. Wenn Sie bezweifeln, dass dies der echte Tom Christiansen ist, gehen Sie zurück und lesen Sie den Beitrag.
Bill Ruppert

20
Zusammenfassend: RegEx sind falsch benannt. Ich finde es schade, aber es wird sich nicht ändern. Kompatible 'RegEx'-Engines dürfen nicht reguläre Sprachen nicht ablehnen. Sie können daher nicht nur mit Finte State Machines korrekt implementiert werden. Die leistungsfähigen Konzepte für Rechenklassen gelten nicht. Die Verwendung von RegEx gewährleistet keine O (n) Ausführungszeit. Die Vorteile von RegEx sind die knappe Syntax und die implizite Domäne der Zeichenerkennung. Für mich ist dies ein sich langsam bewegendes Zugunglück, das man nicht wegsehen kann, aber mit schrecklichen Konsequenzen.
Steve Steiner

27
@tchrist, dies beantwortet niemals die ursprüngliche Frage des OP. Und ist das Parsen hier der richtige Begriff? Afaics, der Regex, führt eine Tokenisierung / lexikalische Analyse durch, aber die endgültige Analyse erfolgt mit Perl-Code, nicht mit dem Regex selbst.
Qtax

65
@ tchrist Sehr beeindruckend. Sie sind offensichtlich ein hochqualifizierter und talentierter Perl-Programmierer und verfügen über umfassende Kenntnisse über moderne reguläre Ausdrücke. Ich möchte jedoch darauf hinweisen, dass das, was Sie geschrieben haben, nicht wirklich ein regulärer Ausdruck ist (modern, regulär oder auf andere Weise), sondern ein Perl-Programm, das reguläre Ausdrücke stark verwendet. Unterstützt Ihr Beitrag wirklich die Behauptung, dass reguläre Ausdrücke HTML korrekt analysieren können? Oder ist es eher ein Beweis dafür, dass Perl HTML korrekt analysieren kann? Wie auch immer, gute Arbeit!
Mike Clark

126
  1. Sie können einen Roman schreiben, wie es Tchrist getan hat
  2. Sie können eine DOM-Bibliothek verwenden, den HTML-Code laden, xpath verwenden und einfach verwenden //input[@type="hidden"]. Oder wenn Sie xpath nicht verwenden möchten, rufen Sie einfach alle Eingaben ab und filtern Sie, mit welchen versteckt werden getAttribute.

Ich bevorzuge # 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>';
}

Ergebnis:

hide yo kids<br>hide yo wife<br>

72
Das war eigentlich mein Punkt. Ich wollte zeigen, wie schwer es ist.
Tchrist

19
Sehr gutes Zeug da. Ich hatte wirklich gehofft, dass die Leute zeigen würden, wie viel einfacher es ist, eine Parsing-Klasse zu verwenden, also danke! Ich wollte nur ein funktionierendes Beispiel für die extremen Probleme, die Sie haben müssen, um es mit Regexen von Grund auf neu zu machen. Ich hoffe sehr, dass die meisten Leute zu dem Schluss kommen, vorgefertigte Parser für generisches HTML zu verwenden, anstatt ihre eigenen zu rollen. Regexes eignen sich immer noch hervorragend für einfaches HTML, das sie selbst erstellt haben, da dadurch 99,98% der Komplexität entfallen.
Tchrist

5
Nach dem Lesen dieser beiden sehr interessanten Ansätze wäre es schön, die Geschwindigkeit / Speichernutzung / CPU eines Ansatzes mit einem anderen zu vergleichen (dh eine auf Regex basierende VS-Parsing-Klasse).
the_yellow_logo

1
@ Avt'W Ja, nicht, dass du einen 'Roman' schreiben solltest, wenn Regexes schneller sind, aber in der Tat wäre es wirklich nur interessant zu wissen. :) Aber ich vermute schon, dass ein Parser auch weniger Ressourcen benötigt ..
Dennis98

Dies ist eigentlich der Grund, warum XPath überhaupt erfunden wurde!
Thorbjørn Ravn Andersen

21

Im Geiste der Lexer-Lösung von Tom Christiansen finden Sie hier einen Link zu Robert Camerons scheinbar vergessenem Artikel von 1998, REX: XML Shallow Parsing with Regular Expressions.

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

Abstrakt

Die Syntax von XML ist so einfach, dass es möglich ist, ein XML-Dokument mit einem einzigen regulären Ausdruck in eine Liste seiner Markup- und Textelemente zu analysieren. Solch eine flache Analyse eines XML-Dokuments kann für die Erstellung einer Vielzahl leichter XML-Verarbeitungswerkzeuge sehr nützlich sein. Komplexe reguläre Ausdrücke können jedoch schwierig zu konstruieren und noch schwieriger zu lesen sein. In diesem Dokument wird eine Reihe von XML-Flachanalyse-Ausdrücken dokumentiert, die als Grundlage für eine einfache, korrekte, effiziente, robuste und sprachunabhängige XML-Flachanalyse dienen können. Es werden auch vollständige flache Parser-Implementierungen mit jeweils weniger als 50 Zeilen in Perl, JavaScript und Lex / Flex bereitgestellt.

Wenn Sie gerne über reguläre Ausdrücke lesen, ist Camerons Artikel faszinierend. Sein Schreiben ist prägnant, gründlich und sehr detailliert. Er zeigt Ihnen nicht nur, wie Sie den regulären Ausdruck REX konstruieren, sondern auch einen Ansatz zum Aufbau komplexer Regex aus kleineren Teilen.

Ich benutze den regulären REX-Ausdruck seit 10 Jahren ein und aus, um das Problem zu lösen, nach dem das erste Poster gefragt hat (wie passe ich zu diesem bestimmten Tag, aber nicht zu einem anderen sehr ähnlichen Tag?). Ich habe festgestellt, dass der von ihm entwickelte Regex absolut zuverlässig ist.

REX ist besonders nützlich, wenn Sie sich auf lexikalische Details eines Dokuments konzentrieren, z. B. wenn Sie eine Art von Textdokument (z. B. Klartext, XML, SGML, HTML) in eine andere umwandeln, bei der das Dokument möglicherweise nicht gültig ist. gut geformt oder sogar für den größten Teil der Transformation analysierbar. Sie können Markup-Inseln an einer beliebigen Stelle innerhalb eines Dokuments anvisieren, ohne den Rest des Dokuments zu stören.


7

Obwohl ich den Inhalt der restlichen Antworten liebe, haben sie die Frage nicht direkt oder so richtig beantwortet. Selbst die Antwort von Platinum war zu kompliziert und auch weniger effizient. Also musste ich das sagen.

Ich bin ein großer Befürworter von Regex, wenn es richtig eingesetzt wird. Aufgrund von Stigmatisierung (und Leistung) sage ich jedoch immer, dass wohlgeformtes XML oder HTML einen XML-Parser verwenden sollte. Und eine noch bessere Leistung wäre das Parsen von Strings, obwohl es eine Grenze zwischen Lesbarkeit gibt, wenn dies zu außer Kontrolle gerät. Das ist jedoch nicht die Frage. Die Frage ist, wie ein Eingabe-Tag vom versteckten Typ abgeglichen werden kann. Die Antwort ist:

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

Abhängig von Ihrem Geschmack ist die einzige Regex-Option, die Sie einschließen müssen, die Option "Ignorieren".


5
<input type='hidden' name='Oh, <really>?' value='Try a real HTML parser instead.'>
Ilmari Karonen

4
Ihr Beispiel schließt sich selbst. Sollte mit /> enden. Auch wenn die Chancen, ein >im Namensfeld zu haben, so gut wie keine sind, ist es tatsächlich möglich, dass es ein >in einem Aktionshandle gibt. EG: Ein Inline-Javascript-Aufruf für die OnClick-Eigenschaft. Davon abgesehen habe ich einen XML-Parser für diese, aber auch einen Regex für diejenigen, bei denen das mir gegebene Dokument für XML-Parser zu durcheinander ist, aber ein Regex kann. Darüber hinaus war dies nicht die Frage. Sie werden niemals mit versteckten Eingaben in diese Situationen geraten, und meine Antwort ist die beste. Ya, <really>!.
Suamere

3
/>ist ein XML-Ismus; Es ist in keiner HTML-Version erforderlich, außer in XHTML (das nie wirklich an Zugkraft gewonnen hat und von HTML5 so gut wie abgelöst wurde). Und Sie haben Recht, dass es viele unordentliche, nicht wirklich gültige HTML-Dateien gibt, aber ein guter HTML- Parser ( nicht XML-Parser) sollte in der Lage sein, das meiste davon zu bewältigen. Wenn dies nicht der Fall ist, werden höchstwahrscheinlich auch keine Browser verwendet.
Ilmari Karonen

1
Wenn Sie nur einen einzigen Treffer analysieren oder suchen müssen, um eine Sammlung versteckter Eingabefelder zurückzugeben, ist dieser reguläre Ausdruck perfekt. Die Verwendung der .NET XML-Dokumentklasse (n) oder das Verweisen auf einen XML / HTML-Parser von Drittanbietern, um nur eine Methode aufzurufen, wäre übertrieben, wenn Regex integriert ist. Und Sie haben Recht, dass eine Website so durcheinander ist, dass ein guter HTML-Code vorhanden ist Parser konnte nicht damit umgehen, wahrscheinlich ist es nicht einmal etwas, was ein Entwickler sich ansehen würde. Aber mein Unternehmen erhält monatlich Millionen von Seiten, die auf viele Arten verkettet und aufgebockt werden, sodass Regex manchmal (nicht immer) die beste Option ist.
Suamere

1
Der einzige Punkt ist, dass wir uns nicht sicher sind, warum dieser Entwickler diese Antwort haben möchte. Aber darum hat er gebeten.
Suamere

3

Sie können dies versuchen:

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

und für ein genaueres Ergebnis können Sie dies versuchen:

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

Sie können Ihr Regex-Muster hier testen. http://regexpal.com/

Diese Patten sind gut dafür:

<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" />

und für zufällige Reihenfolge von type, nameund valueu kann dies verwenden:

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

oder

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

dazu:

<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" />

`

Übrigens denke ich, dass Sie so etwas wollen:

<[ ]*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"[ ]*)+)[ ]*/>

Es ist nicht gut, aber es funktioniert in irgendeiner Weise.

Testen Sie es in: http://regexpal.com/


1

Ich möchte verwenden **DOMDocument**, um den HTML-Code zu extrahieren.

$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') );
}

Übrigens können Sie es hier testen - regex101.com. Es zeigt das Ergebnis in Echtzeit. Einige Regeln zu Regexp: http://www.eclipse.org/tptp/home/downloads/installguide/gla_42/ref/rregexp.html Reader .


0

Angenommen, Ihr HTML-Inhalt wird in der Zeichenfolge HTML gespeichert. Um alle Eingaben mit verstecktem Typ zu erhalten, können Sie reguläre Ausdrücke verwenden

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

Der obige Regex-Fund wird <inputgefolgt von einer beliebigen Anzahl von Zeichen, bis er angezeigt wird, type="hidden"oder geben Sie = 'hidden' ein, gefolgt von einer beliebigen Anzahl von Zeichen, bis er angezeigt wird>

/ g Weisen Sie den regulären Ausdruck an, jeden Teilstring zu finden, der dem angegebenen Muster entspricht.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.