Co robić Wzorzec wyrażenia regularnego nie pasuje nigdzie w ciągu?


181

Próbuję dopasować <input>„ukryte” pola typu, używając tego wzorca:

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

To są przykładowe dane formularza:

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

Ale nie jestem pewien, że type, name, oraz valueatrybuty zawsze będą pojawiać się w tej samej kolejności. Jeśli typeatrybut jest ostatni, dopasowanie nie powiedzie się, ponieważ według mojego wzoru jest na początku.

Pytanie:
Jak mogę zmienić swój wzór, aby pasował bez względu na pozycje atrybutów w <input>znaczniku?

PS: Przy okazji używam opartego na Adobe Air RegEx Desktop Tool do testowania wyrażeń regularnych.


4
regex jest doskonałym rozwiązaniem, jeśli masz kontrolę nad generowanym HTMLem, ponieważ jest regularna, a nie regularna debata, ale w moim przypadku nie wiem, jak HTML się zmieni w przyszłości, więc najlepszym rozwiązaniem jest użycie parsera zamiast wyrażenia regularnego, użyłem regex w moim projekcie w częściach, nad którymi mam kontrolę
Salman,

Odpowiedzi:


108

W przeciwieństwie do wszystkich odpowiedzi tutaj, to, co próbujesz zrobić, to doskonałe rozwiązanie. Wynika to z faktu, że NIE próbujesz dopasować zrównoważonych tagów - NIE byłoby to możliwe z regex! Ale dopasowujesz tylko to, co jest w jednym tagu, i to jest idealnie regularne.

Oto problem. Nie możesz tego zrobić za pomocą tylko jednego wyrażenia regularnego ... musisz zrobić jedno dopasowanie, aby przechwycić <input>tag, a następnie wykonać dalsze przetwarzanie. Zauważ, że zadziała to tylko wtedy, gdy żadna z wartości atrybutów nie zawiera >znaku, więc nie jest idealna, ale powinna wystarczyć dla rozsądnych danych wejściowych.

Oto trochę Perl (pseudo) kod, który pokaże ci, co mam na myśli:

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
}

Podstawową zasadą jest tutaj: nie próbuj robić zbyt wiele za pomocą jednego wyrażenia regularnego. Jak zauważyłeś, wyrażenia regularne wymuszają pewną ilość zamówienia. Zamiast tego musisz najpierw dopasować KONTEKST tego, co próbujesz wyodrębnić, a następnie przesłać żądane dane.

EDYCJA: Zgadzam się jednak, że generalnie użycie parsera HTML jest prawdopodobnie łatwiejsze i lepsze i naprawdę powinieneś rozważyć przeprojektowanie kodu lub ponowne zbadanie celów. :-) Ale musiałem opublikować tę odpowiedź jako przeciwstawienie się szarpanemu kolanom reakcji, że parsowanie dowolnego podzbioru HTML jest niemożliwe: zarówno HTML, jak i XML są nieregularne, jeśli wziąć pod uwagę całą specyfikację, ale specyfikacja tagu jest przyzwoicie regularna , na pewno w mocy PCRE.


14
Nie jest to sprzeczne z wszystkimi odpowiedziami tutaj. :)
tchrist

6
@tchrist: Twojej odpowiedzi nie było tutaj, kiedy opublikowałem swoją. ;-)
Platinum Azure,

7
no cóż - z jakiegoś powodu pisanie zajęło mi więcej czasu niż twoje. Myślę, że moja klawiatura musi wymagać smarowania. :)
tchrist

6
To niepoprawny HTML - powinna być wartość = "& Czy naprawdę jesteś tego pewien? & Gt;" Jeśli miejsce, w którym skrobi, nie udaje mu się uciec przed takimi rzeczami, będzie potrzebował bardziej wyrafinowanego rozwiązania - ale jeśli zrobią to dobrze (i jeśli ma nad tym kontrolę, powinien upewnić się, że jest w porządku), to jest w porządku.
Ross Snyder

14
Obowiązkowy link do najlepszej odpowiedzi SO na ten temat (prawdopodobnie najlepszy okres odpowiedzi SO): stackoverflow.com/questions/1732348/…
Daniel Ribeiro

682

O tak, możesz użyć Regexes do parsowania HTML!

W przypadku zadania, które próbujesz, wyrażenia regularne są w porządku!

Prawdą jest, że większość ludzi nie docenia trudności w przetwarzaniu HTML za pomocą wyrażeń regularnych i dlatego robi to słabo.

Ale to nie jest jakaś podstawowa wada związana z teorią obliczeniową. Ta głupota jest tutaj dużo papugowana , ale nie wierzcie im.

Chociaż z pewnością można to zrobić (ten post służy jako dowód istnienia tego niezaprzeczalnego faktu), to nie znaczy, że tak  powinno  być.

Musisz sam zdecydować, czy masz zamiar napisać, co stanowi dedykowany, specjalny parser HTML z wyrażeń regularnych. Większość ludzi nie jest.

Ale ja jestem. ☻


Ogólne oparte na regeksie rozwiązania do analizowania HTML

Najpierw pokażę, jak łatwo można parsować dowolny kod HTML za pomocą wyrażeń regularnych. Pełny program znajduje się na końcu tego ogłoszenia, ale sercem parsera jest:

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

Widzisz, jak łatwo to odczytać?

Jak napisano, identyfikuje każdy fragment HTML i informuje, gdzie go znalazł. Możesz łatwo go zmodyfikować, aby zrobić cokolwiek chcesz z dowolnym rodzajem elementu lub dla bardziej konkretnych typów niż te.

Nie mam nieudanych przypadków testowych (po lewej :): Udało mi się uruchomić ten kod na ponad 100 000 plików HTML - każdego z nich mogłem szybko i łatwo zdobyć. Poza tym uruchomiłem go również na plikach specjalnie skonstruowanych w celu przełamania naiwnych parserów.

To nie jest naiwny parser.

Och, jestem pewien, że nie jest idealny, ale nie udało mi się go jeszcze złamać. Wydaje mi się, że nawet gdyby coś zrobiło, poprawka byłaby łatwa do dopasowania ze względu na przejrzystą strukturę programu. Nawet programy z dużym regexem powinny mieć strukturę.

Teraz, gdy to już nie przeszkadza, pozwolę sobie odpowiedzieć na pytanie OP.

Demo rozwiązania zadania PO przy użyciu Regexes

Mały html_input_rxprogram, który zamieszczam poniżej, generuje następujące dane wyjściowe, dzięki czemu można zobaczyć, że analizowanie kodu HTML za pomocą wyrażeń regularnych działa dobrze dla tego, co chcesz zrobić:

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

Analizuj tagi wejściowe, patrz: Brak zła

Oto źródło programu, który wygenerował wynik powyżej.

#!/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: $!";
} 

Proszę bardzo! Nic do tego! :)

Tylko Ty możesz ocenić, czy Twoja umiejętność wyrażeń regularnych zależy od konkretnego zadania analizy. Poziom umiejętności każdego jest inny, a każde nowe zadanie jest inne. W przypadku zadań, w których masz dobrze zdefiniowany zestaw danych wejściowych, wyrażenia regularne są oczywiście właściwym wyborem, ponieważ składanie ich razem jest trywialne, gdy masz do czynienia z ograniczonym podzbiorem HTML. Nawet początkujący wyrażenia regularne powinni obsługiwać te zadania za pomocą wyrażeń regularnych. Wszystko inne to przesada.

Jednak gdy HTML zacznie być mniej dopracowany, gdy zacznie rozgryźć w sposób, którego nie można przewidzieć, ale które są całkowicie legalne, gdy będziesz musiał dopasować więcej różnych rzeczy lub bardziej skomplikowane zależności, w końcu osiągniesz punkt, w którym musisz pracować ciężej, aby uzyskać rozwiązanie wykorzystujące wyrażenia regularne, niż musiałbyś użyć klasy parsującej. To, gdzie spada ten próg rentowności, zależy ponownie od własnego poziomu komfortu z wyrażeniami regularnymi.

Więc co powinienem zrobić?

Nie powiem ci, co musisz zrobić, a czego nie . Myślę, że to źle. Chcę tylko przedstawić Ci możliwości, otwórz trochę oczy. Możesz wybrać, co chcesz zrobić i jak chcesz to zrobić. Nie ma absolutów - i nikt inny nie zna twojej sytuacji tak dobrze jak ty sam. Jeśli wydaje się, że to za dużo pracy, to może tak jest. Wiesz, programowanie powinno być zabawne . Jeśli tak nie jest, być może robisz to źle.

Na mój html_input_rxprogram można patrzeć na wiele ważnych sposobów. Jednym z nich jest to, że rzeczywiście możesz parsować HTML z wyrażeniami regularnymi. Ale innym jest to, że jest o wiele, wiele, znacznie trudniejsze niż prawie ktokolwiek myśli, że tak jest. Może to prowadzić do wniosku, że mój program jest dowodem na to, co powinno nie robić, bo to naprawdę jest zbyt trudne.

Nie będę się z tym nie zgadzać. Z pewnością jeśli wszystko, co robię w moim programie, nie ma dla ciebie sensu po jakimś badaniu, nie powinieneś próbować używać wyrażeń regularnych do tego rodzaju zadań. W przypadku określonego HTML wyrażenia regularne są świetne, ale w przypadku standardowego HTML są one równoznaczne z szaleństwem. Cały czas używam klas parsujących, zwłaszcza jeśli to HTML, którego sam nie wygenerowałem.

Regeksy optymalne dla małych problemów z parsowaniem HTML, pesymalne dla dużych

Nawet jeśli mój program jest traktowana jako ilustrację dlaczego należy nie używać regexes w celu analizowania ogólnych HTML - co jest OK, bo trochę rozumie ona być że ☺ - to nadal powinna być niespodzianka więc więcej ludzi złamać strasznie powszechne i paskudny, nieprzyjemny nawyk pisania nieczytelnych, nieustrukturyzowanych i niemożliwych do utrzymania wzorów.

Wzory nie muszą być brzydkie i nie muszą być trudne. Jeśli tworzysz brzydkie wzory, jest to odbicie ciebie, a nie ich.

Fenomenalnie wykwintny język regex

Poproszono mnie o wskazanie, że moje profesjonalne rozwiązanie twojego problemu zostało napisane w Perlu. Czy jesteś zaskoczony? Nie zauważyłeś? Czy to objawienie to bomba?

Prawdą jest, że nie wszystkie inne narzędzia i języki programowania są tak wygodne, wyraziste i potężne, jeśli chodzi o wyrażenia regularne, jak Perl. Istnieje duże spektrum, z których niektóre są bardziej odpowiednie niż inne. Ogólnie rzecz biorąc, łatwiej jest pracować z językami, które wyrażają wyrażenia regularne jako część języka podstawowego zamiast jako bibliotekę. Nie zrobiłem nic z wyrażeniami regularnymi, których nie można zrobić, powiedzmy, w PCRE, chociaż program miałby inną strukturę, gdybyś używał C.

W końcu inne języki będą nadążać za tym, gdzie Perl jest teraz pod względem wyrażeń regularnych. Mówię to, ponieważ kiedy Perl zaczął, nikt inny nie miał takich wyrażeń regularnych jak Perl. Mów co chcesz, ale Perl wyraźnie wygrał: wszyscy skopiowali wyrażenia regularne Perla, choć na różnych etapach ich rozwoju. Perl był pionierem prawie (nie do końca, ale prawie) wszystkiego, na czym dzisiaj polegasz w nowoczesnych wzorach, bez względu na to, jakiego narzędzia lub języka używasz. Więc ostatecznie pozostali będą dogonić.

Ale dotrą tylko do miejsca, w którym Perl był kiedyś, tak jak teraz. Wszystko idzie naprzód. W wyrażeniach regularnych, jeśli nic więcej, do czego prowadzi Perl, inni podążają za nimi. Gdzie będzie Perl, gdy wszyscy w końcu dotrą do miejsca, w którym teraz jest Perl? Nie mam pojęcia, ale wiem, że my też się przeprowadzimy. Prawdopodobnie zbliżymy się do stylu tworzenia wzorów Perla .

Jeśli lubisz tego rodzaju rzeczy, ale chciałbyś użyć ich w Perlu, możesz zainteresować się wspaniałym modułem Regexp :: Grammars Damiana Conwaya . Jest całkowicie niesamowity i sprawia, że ​​to, co zrobiłem tutaj w moim programie, wydaje się tak samo prymitywne, jak moje sprawia, że ​​wzorce, które ludzie łączą ze sobą bez białych znaków i alfabetycznych identyfikatorów. Sprawdź to!


Prosty fragment kodu HTML

Oto pełne źródło parsera, który pokazałem na środku tego wpisu na początku tego postu.

Ja nie sugeruje, że należy korzystać z tego ponad rygorystycznie testowane klasy parsowania. Ale mam dość ludzi udających, że nikt nie może analizować HTML za pomocą wyrażeń regularnych tylko dlatego, że nie. Oczywiście możesz, a ten program jest dowodem tego twierdzenia.

Pewnie, że nie jest to łatwe, ale to jest możliwe!

A próba zrobienia tego jest straszną stratą czasu, ponieważ istnieją dobre klasy analizujące, których powinieneś użyć do tego zadania. Prawidłowa odpowiedź dla osób próbujących parsować dowolny kod HTML nie polega na tym, że jest to niemożliwe. To łatwa i nieuczciwa odpowiedź. Prawidłowa i uczciwa odpowiedź jest taka, że ​​nie powinni próbować, ponieważ zbyt trudno jest zrozumieć od zera; nie powinni łamać sobie pleców, starając się odkryć koło, które działa doskonale.

Z drugiej strony HTML, który mieści się w przewidywalnym podzbiorze, jest wyjątkowo łatwy do parsowania za pomocą wyrażeń regularnych. Nic dziwnego, że ludzie próbują ich używać, ponieważ w przypadku drobnych problemów, być może problemów z zabawkami, nic nie może być łatwiejsze. Dlatego tak ważne jest rozróżnienie dwóch zadań - specyficznego od ogólnego - ponieważ niekoniecznie wymagają tego samego podejścia.

Mam nadzieję, że w przyszłości zobaczę bardziej sprawiedliwe i uczciwe traktowanie pytań dotyczących HTML i wyrażeń regularnych.

Oto mój leksykon HTML. Nie próbuje wykonać sprawdzania poprawności; po prostu identyfikuje elementy leksykalne. Możesz myśleć o tym bardziej o chunkrze HTML niż parserze HTML. Nie wybacza bardzo zepsutego HTML, choć wprowadza pewne bardzo niewielkie poprawki w tym kierunku.

Nawet jeśli nigdy nie parsujesz pełnego kodu HTML (a dlaczego miałbyś? To rozwiązany problem!), Ten program ma wiele fajnych bitów wyrażeń regularnych, z których, jak sądzę, wiele osób może się wiele nauczyć. Cieszyć się!

#!/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
dwie najważniejsze uwagi z twojego komentarza „Cały czas korzystam z klas parsujących, szczególnie jeśli to HTML, którego sam nie wygenerowałem”. i „Wzory nie muszą być brzydkie i nie muszą być trudne. Jeśli tworzysz brzydkie wzory, jest to odbicie ciebie, a nie ich”. całkowicie zgadzam się z tym, co powiedziałeś, więc ponownie oceniam problem. wielkie dzięki za tak szczegółową odpowiedź
Salman,

168
Dla tych, którzy nie wiedzą, pomyślałem, że wspomnę, że Tom jest współautorem „Programowania Perla” (znanego również jako książka o wielbłądach) i jednym z najważniejszych autorytetów Perla. Jeśli masz wątpliwości, że to jest prawdziwy Tom Christiansen, wróć i przeczytaj post.
Bill Ruppert

20
Podsumowując: RegEx są źle nazwane. Myślę, że to wstyd, ale się nie zmieni. Zgodne silniki „RegEx” nie mogą odrzucać języków nieregularnych. W związku z tym nie można ich poprawnie zaimplementować tylko z maszynami stanu Finte. Potężne koncepcje wokół klas obliczeniowych nie mają zastosowania. Użycie RegEx nie zapewnia czasu wykonania O (n). Zaletami RegEx są zwięzła składnia i domniemana domena rozpoznawania znaków. Dla mnie jest to powolny wrak pociągu, niemożliwy do odwrócenia wzroku, ale z przerażającymi konsekwencjami.
Steve Steiner,

27
@ tchrist, to nigdy nie odpowiada oryginalne pytanie OPs. I czy parsowanie tutaj jest właściwym terminem? Afaics regex robi tokenizację / analizę leksykalną, ale końcowe parsowanie odbywa się za pomocą kodu Perla, a nie same regex.
Qtax,

65
@tchrist Bardzo imponujące. Jesteś oczywiście wysoko wykwalifikowanym i utalentowanym programistą Perl i masz ogromną wiedzę na temat współczesnych wyrażeń regularnych. Chciałbym jednak zauważyć, że to, co napisałeś, nie jest tak naprawdę wyrażeniem regularnym (nowoczesnym, regularnym lub innym), ale raczej programem Perla, który intensywnie używa wyrażeń regularnych. Czy Twój post naprawdę potwierdza twierdzenie, że wyrażenia regularne mogą poprawnie analizować HTML? A może bardziej przypomina dowód, że Perl potrafi poprawnie parsować HTML? Tak czy inaczej, dobra robota!
Mike Clark

126
  1. Możesz napisać powieść, jak zrobił to tchrist
  2. Możesz użyć biblioteki DOM, załadować HTML i użyć xpath i po prostu użyć //input[@type="hidden"]. Lub jeśli nie chcesz używać Xpath, po prostu pobierz wszystkie dane wejściowe i filtruj, które są ukryte getAttribute.

Wolę # 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>';
}

Wynik:

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

72
Właściwie o to mi chodziło. Chciałem pokazać, jakie to trudne.
tchrist

19
Bardzo dobre rzeczy. Naprawdę miałem nadzieję, że ludzie pokażą, o ile łatwiej jest korzystać z klasy parsującej, więc dzięki! Chciałem tylko praktyczny przykład ekstremalnych problemów, przez które musisz przejść, aby zrobić to od zera za pomocą wyrażeń regularnych. Mam nadzieję, że większość ludzi zdecyduje się na użycie parserów prefabrykatów na ogólnym HTML zamiast tworzenia własnych. Regeksy są nadal świetne dla prostego kodu HTML, który sami stworzyli, ponieważ pozbywa się to 99,98% złożoności.
tchrist

5
Po przeczytaniu tych 2 bardzo interesujących podejść, byłoby miło porównać szybkość / zużycie pamięci / procesor jednego podejścia z innym (tj. Klasa VS oparta na wyrażeniach regularnych).
the_yellow_logo

1
@ Avt'W Tak, nie, że powinieneś napisać „powieść”, jeśli Regexy są szybsze, ale tak naprawdę to byłoby interesujące wiedzieć. :) Ale przypuszczam już, że parser też
zużywa

Właśnie dlatego XPath został wynaleziony w pierwszej kolejności!
Thorbjørn Ravn Andersen

21

W duchu rozwiązania leksykalnego Toma Christiansena, oto link do pozornie zapomnianego artykułu Roberta Camerona z 1998 roku, REX: XML Shallow Parsing with Regular Expressions.

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

Abstrakcyjny

Składnia XML jest na tyle prosta, że ​​możliwe jest parsowanie dokumentu XML na liście jego znaczników i elementów tekstowych za pomocą pojedynczego wyrażenia regularnego. Taka płytka analiza dokumentu XML może być bardzo przydatna do budowy różnych lekkich narzędzi do przetwarzania XML. Jednak złożone wyrażenia regularne mogą być trudne do zbudowania, a nawet trudniejsze do odczytania. Wykorzystując pewien rodzaj umiejętności programowania dla wyrażeń regularnych, ten dokument dokumentuje zestaw płytkich wyrażeń parsujących XML, które można wykorzystać jako podstawę do prostego, poprawnego, wydajnego, niezawodnego i niezależnego od języka płycinowego parsowania. Dostępne są również pełne implementacje płytkiego parsera zawierające mniej niż 50 wierszy w Perlu, JavaScript i Lex / Flex.

Jeśli lubisz czytać o wyrażeniach regularnych, praca Camerona jest fascynująca. Jego pisanie jest zwięzłe, dokładne i bardzo szczegółowe. Nie tylko pokazuje, jak skonstruować wyrażenie regularne REX, ale także podejście do tworzenia dowolnego złożonego wyrażenia regularnego z mniejszych części.

Używam i wyłączam wyrażenie regularne REX od 10 lat, aby rozwiązać problem, o który pytał pierwotny plakat (jak dopasować ten konkretny tag, ale nie inny bardzo podobny tag?). Stwierdziłem, że regex, który opracował, jest całkowicie niezawodny.

REX jest szczególnie przydatny, gdy skupiasz się na szczegółach leksykalnych dokumentu - na przykład podczas przekształcania jednego rodzaju dokumentu tekstowego (np. Zwykłego tekstu, XML, SGML, HTML) w inny, w którym dokument może być nieprawidłowy, dobrze uformowany lub nawet parsowalny przez większość transformacji. Pozwala celować w wyspy znaczników w dowolnym miejscu dokumentu, nie zakłócając reszty dokumentu.


7

Chociaż uwielbiam treść pozostałych odpowiedzi, tak naprawdę nie odpowiedzieli na pytanie bezpośrednio lub tak poprawnie. Nawet odpowiedź Platinum była zbyt skomplikowana, a także mniej wydajna. Więc byłem zmuszony to powiedzieć.

Jestem wielkim zwolennikiem Regex, jeśli jest prawidłowo stosowany. Ale ze względu na piętno (i wydajność) zawsze stwierdzam, że dobrze sformatowany XML lub HTML powinien używać parsera XML. A jeszcze lepsza wydajność to parsowanie ciągów, choć istnieje granica między czytelnością, jeśli to wymyka się spod kontroli. To jednak nie jest pytanie. Pytanie brzmi, jak dopasować znacznik wejściowy typu ukrytego. Odpowiedź to:

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

W zależności od gustu jedyną opcją wyrażenia regularnego, którą musisz uwzględnić, jest opcja ignorowania.


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

4
Twój przykład jest samozamykający. Powinien kończyć się znakiem />. Ponadto, chociaż szanse na posiadanie >pola w nazwie są prawie zerowe, rzeczywiście jest możliwe, aby istniał >uchwyt akcji. EG: wbudowane wywołanie javascript we właściwości OnClick. Biorąc to pod uwagę, mam parser XML dla tych, ale mam również Regex dla tych, w których dokument, który dostałem, jest zbyt zawalony, aby parsery XML mogły go obsłużyć, ale Regex może. Ponadto nie o to pytano. Nigdy nie spotkasz się z takimi sytuacjami z ukrytym wkładem, a moja odpowiedź jest najlepsza. Ya, <really>!.
Suamere

3
/>jest XML-ism; nie jest wymagany w żadnej wersji HTML, z wyjątkiem XHTML (który nigdy tak naprawdę nie zyskał dużej przyczepności i został prawie zastąpiony przez HTML5). I masz rację, że jest tam dużo nieporządnego, niepoprawnego HTML, ale dobry parser HTML ( nie XML) powinien być w stanie poradzić sobie z większością z nich; jeśli nie, najprawdopodobniej nie będą to również przeglądarki.
Ilmari Karonen

1
Jeśli jedynym potrzebnym parsowaniem lub wyszukiwaniem jest pojedyncze trafienie, aby zwrócić kolekcję ukrytych pól wejściowych, to wyrażenie regularne byłoby idealne. Używanie klas dokumentów .NET XML lub odwoływanie się do parsera XML / HTML trzeciej strony tylko w celu wywołania jednej metody byłoby przesadą, gdy Regex jest wbudowany. I masz rację, że witryna tak zawiodła, że ​​dobry HTML parser nie mógł sobie z tym poradzić, prawdopodobnie nie jest to coś, na co patrzyłby deweloper. Ale moja firma otrzymuje miliony stron miesięcznie, które są łączone i przenoszone na wiele sposobów, tak że czasami (nie zawsze) Regex jest najlepszą opcją.
Suamere

1
Chodzi tylko o to, że nie jesteśmy pewni powodu całej firmy, dlaczego ten deweloper chce tej odpowiedzi. Ale o to prosił.
Suamere,

3

możesz spróbować:

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

i dla bliższego rezultatu możesz spróbować:

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

możesz przetestować wzór wyrażenia regularnego tutaj http://regexpal.com/

te patty są do tego dobre:

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

i dla losowej kolejności type, namei valuemożesz użyć tego:

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

lub

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

na to :

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

`

tak przy okazji, myślę, że chcesz czegoś takiego:

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

to nie jest dobre, ale działa w jakikolwiek sposób.

przetestuj w: http://regexpal.com/


1

Chciałbym użyć **DOMDocument**do wyodrębnienia kodu 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, możesz to przetestować tutaj - regex101.com. Pokazuje wynik w czasie rzeczywistym. Niektóre reguły dotyczące Regexp: http://www.eclipse.org/tptp/home/downloads/installguide/gla_42/ref/rregexp.html Reader .


0

załóżmy, że zawartość html jest przechowywana w html ciągu, a następnie aby ukryć wszystkie dane wejściowe zawierające typ, możesz użyć wyrażenia regularnego

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

powyższe wyrażenie regularne znajduje się <inputpo dowolnej liczbie znaków, dopóki się nie pojawi type="hidden"lub wpisz = „ukryty”, a następnie dowolna liczba znaków, aż się pojawi>

/ g powiedz wyrażenie regularne, aby znaleźć każdy podciąg pasujący do podanego wzorca.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.