Apa yang harus dilakukan Pola persamaan reguler tidak cocok dengan string mana pun?

182

Saya mencoba untuk mencocokkan <input>jenis "tersembunyi" menggunakan pola ini:

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

Ini adalah contoh formulir data:

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

Tapi saya tidak yakin bahwa type, name, dan valueatribut akan selalu muncul dalam urutan yang sama. Jika typeatribut datang terakhir, kecocokan akan gagal karena dalam pola saya itu di awal.

Pertanyaan:
Bagaimana saya bisa mengubah pola saya sehingga akan cocok terlepas dari posisi atribut pada <input>tag?

PS: Ngomong-ngomong saya menggunakan RegEx Desktop Tool berbasis Adobe Air untuk menguji ekspresi reguler.

Salman
sumber
4
regex adalah solusi yang sangat baik jika Anda memiliki kontrol atas html yang dihasilkan karena itu adalah perdebatan biasa vs tidak teratur, tetapi saya kasus saya saya tidak tahu bagaimana html akan berubah di masa depan sehingga hal terbaik adalah menggunakan parser daripada regex, saya telah menggunakan regex dalam proyek saya di bagian yang saya dapat kontrol
Salman

Jawaban:

108

Bertentangan dengan semua jawaban di sini, untuk apa yang Anda coba lakukan regex adalah solusi yang sangat valid. Ini karena Anda TIDAK mencoba mencocokkan tag seimbang - ITU tidak mungkin dengan regex! Tetapi Anda hanya mencocokkan apa yang ada dalam satu tag, dan itu biasa saja.

Inilah masalahnya. Anda tidak dapat melakukannya hanya dengan satu regex ... Anda perlu melakukan satu kecocokan untuk mendapatkan <input>tag, kemudian melakukan pemrosesan lebih lanjut. Perhatikan bahwa ini hanya akan berfungsi jika tidak ada nilai atribut yang memiliki >karakter di dalamnya, jadi itu tidak sempurna, tetapi itu sudah cukup untuk input yang masuk akal.

Berikut ini beberapa kode Perl (pseudo) untuk menunjukkan kepada Anda apa yang saya maksud:

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
}

Prinsip dasar di sini adalah, jangan mencoba melakukan terlalu banyak dengan satu ekspresi reguler. Seperti yang Anda perhatikan, ekspresi reguler memberlakukan sejumlah pesanan. Jadi yang perlu Anda lakukan adalah mencocokkan dulu KONTEKS dari apa yang Anda coba ekstrak, kemudian lakukan pengiriman data yang Anda inginkan.

EDIT: Namun, saya akan setuju bahwa secara umum, menggunakan parser HTML mungkin lebih mudah dan lebih baik dan Anda benar-benar harus mempertimbangkan mendesain ulang kode Anda atau memeriksa ulang tujuan Anda. :-) Tapi saya harus memposting jawaban ini sebagai lawan dari reaksi spontan yang menguraikan subset HTML apa pun adalah tidak mungkin: HTML dan XML sama-sama tidak beraturan ketika Anda mempertimbangkan keseluruhan spesifikasi, tetapi spesifikasi tag cukup teratur , tentu saja dalam kekuatan PCRE.

Platinum Azure
sumber
14
Tidak bertentangan dengan semua jawaban di sini. :)
tchrist
6
@tchrist: Jawaban Anda tidak ada di sini ketika saya memposting milik saya. ;-)
Platinum Azure
7
yah yah - untuk beberapa alasan butuh waktu lebih lama untuk mengetik daripada milik Anda. Saya pikir keyboard saya harus dioleskan. :)
tchrist
6
Itu HTML yang tidak valid - seharusnya bernilai = "& lt; Apakah Anda benar-benar yakin tentang ini? & Gt;" Jika tempat yang dikoreknya melakukan pekerjaan yang buruk untuk menghindari hal-hal seperti ini, maka dia akan membutuhkan solusi yang lebih canggih - tetapi jika mereka melakukannya dengan benar (dan jika dia memiliki kendali atas itu, dia harus memastikan itu benar) maka dia baik-baik saja.
Ross Snyder
14
Tautan wajib ke jawaban SO terbaik pada subjek (mungkin periode jawaban SO terbaik): stackoverflow.com/questions/1732348/…
Daniel Ribeiro
682

Oh Ya, Anda Dapat Menggunakan Regex untuk Mengurai HTML!

Untuk tugas yang Anda coba, regex benar - benar baik-baik saja!

Ini adalah benar bahwa kebanyakan orang meremehkan kesulitan parsing HTML dengan ekspresi reguler dan karena itu melakukannya buruk.

Tetapi ini bukan kelemahan mendasar yang terkait dengan teori komputasi. Konyol itu banyak dibiakkan di sekitar sini , tetapi jangan Anda percayai mereka.

Jadi sementara itu pasti bisa dilakukan (postingan ini berfungsi sebagai bukti keberadaan fakta yang tidak bisa dibantah ini), itu tidak berarti  harus begitu  .

Anda harus memutuskan sendiri apakah Anda siap untuk menulis apa yang dianggap sebagai pengurai HTML khusus dan khusus dari regex. Kebanyakan orang tidak.

Tapi saya . ☻


Solusi Parsing HTML Berbasis Regex Umum

Pertama saya akan menunjukkan betapa mudahnya untuk mem - parsing HTML sewenang-wenang dengan regex. Program lengkap ada di akhir postingan ini, tetapi inti dari pengurai adalah:

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

Lihat betapa mudahnya membaca?

Seperti yang tertulis, ini mengidentifikasi setiap bagian dari HTML dan memberitahu di mana ia menemukan bagian itu. Anda dapat dengan mudah memodifikasinya untuk melakukan apa pun yang Anda inginkan dengan jenis karya yang diberikan, atau untuk jenis yang lebih khusus dari ini.

Saya tidak memiliki test case yang gagal (kiri :): Saya telah berhasil menjalankan kode ini pada lebih dari 100.000 file HTML - setiap yang saya dapat dengan cepat dan mudah saya dapatkan. Selain itu, saya juga menjalankannya pada file yang khusus dibuat untuk memecah parser yang naif.

Ini bukan parser yang naif.

Oh, aku yakin itu tidak sempurna, tapi aku belum berhasil memecahkannya. Saya pikir meskipun ada sesuatu, perbaikannya akan mudah dilakukan karena struktur program yang jelas. Bahkan program regex-berat harus memiliki struktur.

Nah, itu tidak mungkin, izinkan saya menjawab pertanyaan OP.

Demo Memecahkan Tugas OP Menggunakan Regex

Program kecil html_input_rxyang saya sertakan di bawah ini menghasilkan output berikut, sehingga Anda dapat melihat bahwa parsing HTML dengan regex berfungsi dengan baik untuk apa yang ingin Anda lakukan:

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

Masukkan Tag Input Input, See No Evil Input

Inilah sumber untuk program yang menghasilkan output di atas.

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

Ini dia! Tidak ada apa-apa! :)

Hanya Anda yang bisa menilai apakah keahlian Anda dengan regex sesuai dengan tugas parsing tertentu. Tingkat keahlian setiap orang berbeda, dan setiap tugas baru berbeda. Untuk pekerjaan di mana Anda memiliki set input yang terdefinisi dengan baik, regex jelas merupakan pilihan yang tepat, karena itu sepele untuk menyatukan beberapa ketika Anda memiliki subset HTML yang terbatas untuk ditangani. Bahkan pemula regex harus menangani pekerjaan itu dengan regex. Ada lagi yang berlebihan.

Namun , begitu HTML mulai berkurang, setelah mulai bercampur dengan cara-cara yang tidak dapat Anda prediksi tetapi yang legal, setelah Anda harus mencocokkan lebih banyak hal yang berbeda atau dengan dependensi yang lebih kompleks, Anda akhirnya akan mencapai titik di mana Anda harus bekerja lebih keras untuk menghasilkan solusi yang menggunakan regex daripada menggunakan kelas parsing. Di mana titik impas jatuh tergantung lagi pada tingkat kenyamanan Anda sendiri dengan regex.

Jadi apa yang harus aku lakukan?

Saya tidak akan memberi tahu Anda apa yang harus Anda lakukan atau apa yang tidak dapat Anda lakukan. Saya pikir itu salah. Saya hanya ingin memberi Anda kemungkinan, buka sedikit mata Anda. Anda bisa memilih apa yang ingin Anda lakukan dan bagaimana Anda ingin melakukannya. Tidak ada yang absolut - dan tidak ada orang lain yang tahu situasi Anda sendiri sebaik Anda sendiri. Jika sesuatu sepertinya terlalu banyak bekerja, yah, mungkin memang begitu. Pemrograman harus menyenangkan , Anda tahu. Jika tidak, Anda mungkin salah melakukannya.

Orang dapat melihat html_input_rxprogram saya dengan sejumlah cara yang valid. Salah satunya adalah Anda memang dapat mem - parsing HTML dengan ekspresi reguler. Tetapi yang lain adalah bahwa itu jauh, jauh, jauh lebih sulit daripada yang pernah dipikirkan oleh siapa pun. Ini dapat dengan mudah mengarah pada kesimpulan bahwa program saya adalah bukti dari apa yang tidak boleh Anda lakukan, karena itu terlalu sulit.

Saya tidak akan tidak setuju dengan itu. Tentu saja jika semua yang saya lakukan dalam program saya tidak masuk akal bagi Anda setelah beberapa penelitian, maka Anda seharusnya tidak mencoba menggunakan regex untuk tugas semacam ini. Untuk HTML spesifik, regex itu bagus, tetapi untuk HTML umum, itu sama saja dengan kegilaan. Saya menggunakan kelas parsing sepanjang waktu, terutama jika itu HTML saya belum membuat sendiri.

Regex optimal untuk masalah parsing HTML kecil , pessimal untuk yang besar

Sekalipun program saya dianggap sebagai ilustrasi mengapa Anda tidak boleh menggunakan regex untuk mem-parsing HTML umum - yang tidak apa-apa, karena saya agak bermaksud demikian - ☺ - masih harus menjadi pembuka mata sehingga lebih banyak orang mematahkan hal yang sangat umum dan kebiasaan menulis yang tidak menyenangkan, tidak terstruktur, dan tidak terpelihara.

Pola tidak harus jelek, dan tidak harus sulit. Jika Anda membuat pola jelek, itu adalah refleksi Anda, bukan mereka.

Bahasa Regex Fenomenal Indah

Saya telah diminta untuk menunjukkan bahwa solusi profesional saya untuk masalah Anda telah ditulis dalam Perl. Apakah kamu terkejut? Apakah kamu tidak memperhatikan? Apakah wahyu ini mengejutkan?

Memang benar bahwa tidak semua alat dan bahasa pemrograman lainnya cukup nyaman, ekspresif, dan kuat ketika datang ke regex seperti Perl. Ada spektrum besar di luar sana, dengan beberapa lebih cocok daripada yang lain. Secara umum, bahasa yang telah menyatakan regex sebagai bagian dari bahasa inti alih-alih sebagai perpustakaan lebih mudah digunakan. Saya tidak melakukan apa pun dengan regex yang tidak dapat Anda lakukan, katakanlah, PCRE, meskipun Anda akan menyusun program secara berbeda jika Anda menggunakan C.

Akhirnya bahasa-bahasa lain akan menyusul di mana Perl sekarang dalam hal regex. Saya mengatakan ini karena ketika Perl dimulai, tidak ada orang lain yang memiliki regex Perl. Katakan apa pun yang Anda suka, tetapi di sinilah Perl jelas menang: semua orang menyalin regex Perl meskipun pada berbagai tahap perkembangan mereka. Perl memelopori hampir (tidak semua, tetapi hampir) segala sesuatu yang Anda andalkan dalam pola modern saat ini, tidak peduli alat atau bahasa apa yang Anda gunakan. Jadi akhirnya yang lain akan menyusul.

Tapi mereka hanya akan mengejar dimana Perl di masa lalu, sama seperti sekarang. Semuanya maju. Dalam regex jika tidak ada yang lain, di mana Perl memimpin, yang lain mengikuti. Di mana Perl akan setelah semua orang akhirnya mengejar ke tempat Perl sekarang? Saya tidak tahu, tapi saya tahu kita juga akan pindah. Mungkin kita akan lebih dekat dengan gaya pola kerajinan Perl₆ .

Jika Anda menyukai hal semacam itu tetapi ingin menggunakannya dalam Perl₅, Anda mungkin tertarik pada modul Regexp :: Grammars yang luar biasa dari Damian Conway . Ini benar-benar luar biasa, dan membuat apa yang saya lakukan di sini dalam program saya tampak sama primitifnya dengan saya membuat pola-pola yang dijejali orang tanpa spasi putih atau pengidentifikasi alfabet. Saksikan berikut ini!


Chunker HTML sederhana

Berikut adalah sumber lengkap untuk parser yang saya perlihatkan bagian tengahnya dari pada awal posting ini.

Saya tidak menyarankan Anda harus menggunakan ini di atas kelas parsing yang teruji. Tapi saya bosan dengan orang-orang yang berpura-pura tidak ada yang bisa menguraikan HTML dengan regex hanya karena mereka tidak bisa. Anda jelas bisa, dan program ini adalah bukti dari pernyataan itu.

Tentu, itu tidak mudah, tapi itu adalah mungkin!

Dan mencoba melakukannya adalah pemborosan waktu, karena ada kelas parsing yang baik yang harus Anda gunakan untuk tugas ini. Jawaban yang tepat untuk orang yang mencoba menguraikan HTML sewenang-wenang bukanlah bahwa itu tidak mungkin. Itu adalah jawaban yang lancar dan tidak jujur. Jawaban yang benar dan jujur ​​adalah bahwa mereka tidak boleh mencobanya karena terlalu merepotkan untuk mencari tahu dari awal; mereka tidak harus mematahkan punggungnya untuk berusaha menemukan roda yang berfungsi dengan baik.

Di sisi lain, HTML yang termasuk dalam subset yang dapat diprediksi sangat mudah diurai dengan regex. Tidak heran orang mencoba menggunakannya, karena untuk masalah kecil, masalah mainan mungkin, tidak ada yang lebih mudah. Itu sebabnya sangat penting untuk membedakan dua tugas - spesifik vs generik - karena ini tidak selalu menuntut pendekatan yang sama.

Saya berharap di masa depan di sini untuk melihat perlakuan yang lebih adil dan jujur ​​atas pertanyaan tentang HTML dan regex.

Inilah lexer HTML saya. Itu tidak mencoba melakukan pengesahan parse; itu hanya mengidentifikasi unsur-unsur leksikal. Anda mungkin menganggapnya lebih sebagai chunker HTML daripada parser HTML. Ini tidak terlalu memaafkan HTML yang rusak, meskipun membuat beberapa kelonggaran sangat kecil ke arah itu.

Bahkan jika Anda tidak pernah menguraikan HTML lengkap sendiri (dan mengapa Anda harus melakukannya? Ini adalah masalah yang dipecahkan!), Program ini memiliki banyak bit regex keren yang saya percaya banyak orang dapat belajar banyak dari. Nikmati!

#!/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
sumber
23
dua sorotan dari komentar Anda "Saya menggunakan kelas parsing sepanjang waktu, terutama jika itu HTML saya belum membuat sendiri." dan "Pola tidak harus jelek, dan tidak harus sulit. Jika Anda membuat pola jelek, itu adalah refleksi Anda, bukan mereka." Saya sepenuhnya setuju dengan apa yang Anda katakan, jadi saya menilai kembali masalahnya. terima kasih banyak untuk jawaban terinci seperti itu
Salman
168
Bagi mereka yang tidak tahu, saya pikir saya akan menyebutkan bahwa Tom adalah penulis bersama "Programming Perl" (alias buku Unta) dan salah satu otoritas Perl terkemuka. Jika Anda ragu bahwa ini adalah Tom Christiansen yang asli, kembalilah dan baca posnya.
Bill Ruppert
20
Singkatnya: RegEx salah nama. Saya pikir itu memalukan, tetapi itu tidak akan berubah. Mesin 'RegEx' yang kompatibel tidak diizinkan untuk menolak bahasa yang tidak biasa. Karena itu mereka tidak dapat diimplementasikan dengan benar hanya dengan Mesin Finte State. Konsep yang kuat di sekitar kelas komputasi tidak berlaku. Penggunaan RegEx tidak menjamin O (n) waktu eksekusi. Kelebihan dari RegEx adalah sintaksnya yang singkat dan domain pengenalan karakter yang tersirat. Bagi saya, ini adalah kecelakaan kereta yang bergerak lambat, tidak mungkin untuk berpaling, tetapi dengan konsekuensi mengerikan yang terjadi.
Steve Steiner
27
@tchrist, ini tidak pernah menjawab pertanyaan awal OP. Dan parsing istilah yang tepat di sini? Afika regex melakukan tokenizing / analisis leksikal, tetapi parsing terakhir dilakukan dengan kode Perl, bukan regex itu sendiri.
Qtax
65
@tchrist Sangat mengesankan. Anda jelas seorang programmer Perl yang sangat terampil dan berbakat, dan sangat berpengetahuan tentang ekspresi reguler modern. Saya akan menunjukkan, bahwa apa yang Anda tulis bukan benar-benar ekspresi reguler (modern, reguler, atau sebaliknya), melainkan sebuah program Perl yang menggunakan ekspresi reguler dengan berat. Apakah posting Anda benar-benar mendukung klaim bahwa ekspresi reguler dapat mem-parsing HTML dengan benar? Atau lebih seperti bukti bahwa Perl dapat mem-parsing HTML dengan benar? Either way, kerja bagus!
Mike Clark
126
  1. Anda dapat menulis novel seperti yang dilakukan tchrist
  2. Anda dapat menggunakan pustaka DOM, memuat HTML dan menggunakan xpath dan gunakan saja //input[@type="hidden"]. Atau jika Anda tidak ingin menggunakan xpath, dapatkan semua input dan filter yang tersembunyi getAttribute.

Saya lebih suka # 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>';
}

Hasil:

hide yo kids<br>hide yo wife<br>
meder omuraliev
sumber
72
Sebenarnya itu maksud saya. Saya ingin menunjukkan betapa sulitnya itu.
tchrist
19
Sangat bagus di sana. Saya benar-benar berharap orang-orang akan menunjukkan betapa mudahnya menggunakan kelas parsing, jadi terima kasih! Saya hanya ingin contoh kerja masalah ekstrim yang harus Anda lalui untuk melakukannya dari awal menggunakan regex. Saya yakin sebagian besar orang menyimpulkan untuk menggunakan pabrikan cetakan pada HTML generik alih-alih menggulirkan sendiri. Regex masih bagus untuk HTML sederhana yang mereka buat sendiri, karena itu menghilangkan 99,98% dari kompleksitas.
tchrist
5
Apa yang akan menyenangkan setelah membaca 2 pendekatan yang sangat menarik adalah membandingkan kecepatan / penggunaan memori / CPU dari satu pendekatan terhadap yang lain (yaitu kelas parsing VS berbasis regex).
the_yellow_logo
1
@ Avt'W Ya, bukan berarti Anda harus menulis 'novel' jika Regex lebih cepat, tetapi sebenarnya itu hanya akan menarik untuk diketahui. :) Tapi tebakan saya adalah, bahwa parser membutuhkan lebih sedikit sumber daya juga ..
Dennis98
Inilah sebenarnya mengapa XPath diciptakan sejak awal!
Thorbjørn Ravn Andersen
21

Dalam semangat solusi lexer Tom Christiansen, inilah tautan ke artikel 1998 yang tampaknya terlupakan oleh Robert Cameron, REX: XML Parsing Dangkal dengan Ekspresi Reguler.

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

Abstrak

Sintaks XML cukup sederhana sehingga dimungkinkan untuk mem-parsing dokumen XML ke dalam daftar markup dan item teks menggunakan ekspresi reguler tunggal. Penguraian dokumen XML yang dangkal seperti itu bisa sangat berguna untuk konstruksi berbagai alat pemroses XML yang ringan. Namun, ekspresi reguler yang kompleks bisa sulit dikonstruksikan dan bahkan lebih sulit dibaca. Menggunakan bentuk pemrograman melek huruf untuk ekspresi reguler, makalah ini mendokumentasikan serangkaian ekspresi parsing XML dangkal yang dapat digunakan sebagai dasar untuk parsing XML dangkal yang sederhana, benar, efisien, kuat, dan mandiri dalam bahasa. Implementasi parser dangkal lengkap masing-masing kurang dari 50 baris dalam Perl, JavaScript dan Lex / Flex juga diberikan.

Jika Anda senang membaca tentang ekspresi reguler, makalah Cameron sangat menarik. Tulisannya ringkas, menyeluruh, dan sangat rinci. Dia tidak hanya menunjukkan kepada Anda bagaimana membangun ekspresi reguler REX tetapi juga pendekatan untuk membangun regex kompleks dari bagian yang lebih kecil.

Saya telah menggunakan ekspresi reguler REX hidup dan mati selama 10 tahun untuk memecahkan masalah yang ditanyakan oleh poster awal (bagaimana cara mencocokkan tag khusus ini tetapi tidak dengan tag yang sangat mirip lainnya?). Saya telah menemukan regex yang dikembangkannya untuk sepenuhnya dapat diandalkan.

REX sangat berguna ketika Anda berfokus pada detail leksikal dokumen - misalnya, ketika mengubah satu jenis dokumen teks (misalnya, teks biasa, XML, SGML, HTML) menjadi yang lain, di mana dokumen tersebut mungkin tidak valid, terbentuk dengan baik, atau bahkan dapat diuraikan untuk sebagian besar transformasi. Ini memungkinkan Anda menargetkan pulau markup di mana saja di dalam dokumen tanpa mengganggu sisa dokumen.

David
sumber
7

Sementara saya suka isi dari sisa jawaban ini, mereka tidak benar-benar menjawab pertanyaan secara langsung atau dengan benar. Bahkan jawaban Platinum terlalu rumit, dan juga kurang efisien. Jadi saya terpaksa mengatakan ini.

Saya pendukung besar Regex, bila digunakan dengan benar. Tetapi karena stigma (dan kinerja), saya selalu menyatakan bahwa XML atau HTML yang terbentuk dengan baik harus menggunakan XML Parser. Dan bahkan kinerja yang lebih baik adalah penguraian string, meskipun ada garis antara keterbacaan jika itu terlalu di luar kendali. Namun, itu bukan pertanyaannya. Pertanyaannya adalah bagaimana cara mencocokkan tag input tipe tersembunyi. Jawabannya adalah:

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

Tergantung pada rasa Anda, satu-satunya opsi regex yang perlu Anda sertakan adalah opsi ignorecase.

Suamere
sumber
5
<input type='hidden' name='Oh, <really>?' value='Try a real HTML parser instead.'>
Ilmari Karonen
4
Contoh Anda menutup diri. Harus diakhiri dengan />. Juga, sementara kemungkinan memiliki >di bidang nama hampir tidak ada, memang mungkin untuk ada >dalam menangani tindakan. EG: Panggilan javascript inline di properti OnClick. Yang sedang berkata, saya memiliki parser XML untuk mereka, tetapi juga memiliki Regex untuk mereka di mana dokumen yang saya berikan terlalu kacau untuk parser XML untuk menangani, tetapi Regex bisa. Selain itu, ini bukan pertanyaannya. Anda tidak akan pernah mengalami situasi ini dengan input tersembunyi, dan jawaban saya adalah yang terbaik. Ya, <really>!.
Suamere
3
/>adalah XML-isme; itu tidak diperlukan dalam versi HTML apa pun, kecuali XHTML (yang tidak pernah benar-benar mendapatkan banyak daya tarik, dan telah semuanya digantikan oleh HTML5). Dan Anda benar bahwa ada banyak HTML berantakan yang tidak benar-benar valid, tetapi parser HTML ( bukan XML) yang bagus harus bisa mengatasi sebagian besar; jika tidak, kemungkinan besar tidak akan ada peramban.
Ilmari Karonen
1
Jika satu-satunya penguraian atau pencarian yang Anda butuhkan adalah satu klik untuk mengembalikan koleksi bidang input tersembunyi, regex ini akan sempurna. Menggunakan salah satu .NET XML Document class (es), atau mereferensikan pihak XML Parser XML / HTML hanya untuk memanggil satu metode akan berlebihan ketika Regex dibangun. Dan Anda benar bahwa situs web jadi kacau sehingga HTML yang baik parser tidak bisa mengatasinya bahkan mungkin bukan sesuatu yang akan dilihat oleh dev. Tetapi perusahaan saya diserahkan jutaan halaman sebulan yang digabungkan dan didongkrak dalam banyak cara sehingga kadang-kadang (tidak selalu), Regex adalah pilihan terbaik.
Suamere
1
Hanya menunjukkan bahwa kami tidak yakin dengan alasan seluruh perusahaan dev ini menginginkan jawaban ini. Tapi itu yang dia minta.
Suamere
3

Anda dapat mencoba ini:

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

dan untuk hasil yang lebih dekat Anda dapat mencoba ini:

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

Anda dapat menguji pola regex Anda di sini http://regexpal.com/

pattens ini baik untuk ini:

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

dan untuk urutan acak type, namedan valueAnda dapat menggunakan ini:

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

atau

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

hal ini :

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

`

omong-omong saya pikir Anda menginginkan sesuatu seperti ini:

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

itu tidak baik tetapi bekerja dengan cara apa pun.

mengujinya di: http://regexpal.com/

Shamshirsaz.Navid
sumber
1

Saya ingin menggunakan **DOMDocument**untuk mengekstrak kode 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, Anda bisa mengujinya di sini - regex101.com. Ini menunjukkan hasilnya secara real time. Beberapa aturan tentang Regexp: http://www.eclipse.org/tptp/home/downloads/installguide/gla_42/ref/rregexp.html Reader .

Pengembang HTML5
sumber
0

misalkan konten html Anda disimpan dalam string html maka untuk mendapatkan setiap input yang mengandung tipe tersembunyi Anda dapat menggunakan ekspresi reguler

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

pencarian regex di atas <inputdiikuti oleh sejumlah karakter sampai mendapat type="hidden"atau ketik = 'disembunyikan' diikuti oleh sejumlah karakter hingga>

/ g beri tahu ekspresi reguler untuk menemukan setiap substring yang cocok dengan pola yang diberikan.

Nitin9791
sumber