Що робити Шаблон регулярного вираження не відповідає ніде в рядку?


181

Я намагаюся відповідати <input>полям «приховані» поля за допомогою цього шаблону:

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

Це зразкові дані форми:

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

Але я не впевнений , що type, name, і valueатрибути будуть завжди з'являтися в тому ж порядку. Якщо typeатрибут останній, збіг не вдасться, оскільки в моєму шаблоні він знаходиться на старті.

Питання:
Як я можу змінити свою картину, щоб вона відповідала незалежно від позицій атрибутів у <input>тегу?

PS: До речі, я використовую RegEx Desktop Tool на базі Adobe Air для тестування регулярних виразів.


4
regex - відмінне рішення, якщо у вас є контроль над згенерованим html, тому що це регулярно проти не регулярних дебатів, але я моєму випадку я не знаю, як html зміниться в майбутньому, тож найкраще використовувати парсер замість регулярного виразу, я використовував Регекс у моєму проекті в частинах, над якими я маю контроль
Салман

Відповіді:


108

Всупереч усім відповідям тут, що те, що ти намагаєшся робити з регулярними виразками, - цілком правильне рішення. Це тому, що ви НЕ намагаєтеся зіставити збалансовані теги - ТО що було б неможливо з регулярним виразом! Але ви співпадаєте лише з тим, що є в одному тезі, і це абсолютно регулярно.

Ось у цьому проблема. Ви не можете зробити це лише одним регулярним виразом ... Вам потрібно зробити один матч, щоб захопити <input>тег, а потім зробити подальшу обробку цього. Зауважте, що це спрацює лише в тому випадку, якщо жодне з значень атрибута не містить >символу, тому воно не є ідеальним, але його повинно вистачити для розумних входів.

Ось код Perl (псевдо), щоб показати вам, що я маю на увазі:

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
}

Основний принцип тут: не намагайтеся робити занадто багато одним регулярним виразом. Як ви помітили, регулярні вирази виконують певну кількість замовлень. Тож, що вам потрібно зробити, це спочатку відповідати КОНТЕКСТУ того, що ви намагаєтеся витягнути, а потім виконати підбір даних на потрібних даних.

EDIT: Однак я погоджуюся, що загалом використовувати HTML-аналізатор, мабуть, простіше і краще, і вам дійсно слід подумати про перероблення коду або перегляд своїх цілей. :-) Але мені довелося опублікувати цю відповідь як протидію реакції колін, що аналізувати будь-який підмножина HTML неможливо: HTML і XML обидва нерегулярні, якщо врахувати всю специфікацію, але специфікація тегу є пристойною регулярною , безумовно, під силу PCRE.


14
Не суперечить усім відповідям тут. :)
tchrist

6
@tchrist: Вашої відповіді не було, коли я розмістив свою. ;-)
Platinum Azure

7
так, мені чомусь було потрібно більше часу, ніж ти. Я думаю, що моя клавіатура повинна потребувати змащення. :)
tchrist

6
Це недійсний HTML - він повинен бути value = "& lt; Ви справді впевнені в цьому? & Gt;" Якщо місце, яке він вичісує, робить погану роботу, уникаючи подібних речей, тоді йому знадобиться більш складне рішення - але якщо вони будуть робити це правильно (і якщо він має контроль над ним, він повинен переконатися, що це правильно), тоді він буде добре.
Росс Снайдер

14
Обов’язкове посилання на найкращу відповідь на тему (можливо, найкращий період відповіді на SO): stackoverflow.com/questions/1732348/…
Даніель Рібейро

682

О так, ви можете використовувати Regexes для розбору HTML!

Для завдання, яке ви намагаєтесь, регулярно виходять регулярні виразки !

Це є правдою , що більшість людей недооцінюють складність розбір HTML з регулярними виразами і , отже , зробити так погано.

Але це не якийсь фундаментальний недолік, пов'язаний з теорією обчислень. Ця дурість тут дуже папугує , але чи не вірите ви їм.

Отже, хоча це, безумовно, можна зробити (ця публікація слугує доказом існування цього незаперечного факту), це не означає, що воно  повинно  бути.

Ви повинні вирішити, чи вирішуєте ви написати те, що означає спеціальний, спеціальний HTML-аналізатор із регулярних виразів. Більшість людей - ні.

Але я є. ☻


Загальні рішення для розбиття HTML на основі Regex

Спершу я покажу, як легко розібрати довільний HTML з регулярними виразами. Повна програма в кінці цієї публікації, але серце аналізатора:

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

Бачите, як легко це читати?

Як написано, він ідентифікує кожен фрагмент HTML і повідомляє, де він знайшов цей фрагмент. Ви можете легко змінити його, щоб робити все, що завгодно, з будь-яким типом деталі або для більш конкретних типів, ніж ці.

У мене немає провальних тестових випадків (зліва :): Я успішно запустив цей код на більш ніж 100 000 HTML-файлах - кожен з них я міг швидко і легко отримати свої руки. Крім того, я також запускав його на файли, спеціально створені для злому наївних аналізаторів.

Це не наївний аналізатор.

О, я впевнений, що він не ідеальний, але я ще не встиг його зламати. Я вважаю, що навіть якщо щось зробить, виправлення було б легко вписати через чітку структуру програми. Навіть програми, важкі для регулярних викидів, повинні мати загрозу.

Тепер, коли це не в змозі, дозвольте мені вирішити питання ОП.

Демонстрація завдання ОП за допомогою реджексів

Маленька html_input_rxпрограма, яку я включаю нижче, дає наступний результат, так що ви можете бачити, що аналіз HTML з регулярними виразами працює чудово для того, що ви хочете зробити:

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

Аналіз вхідних тегів, див. Не злий ввід

Ось джерело для програми, яка виводила результат вище.

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

Ось так! Нічого до цього! :)

Тільки ви можете судити, чи відповідає ваша майстерність з регулярними виразами якесь конкретне завдання розбору. У кожного рівень майстерності різний, і кожне нове завдання різне. Для робочих місць, де у вас чітко визначений набір вводу, регулярно виражаються правильні вибори, оскільки тривіально об'єднувати деякі, коли у вас є обмежений підмножина HTML, з якою ви маєте справу. Навіть новачки з регулярними виразками повинні впоратися з цими роботами за допомогою регулярних виразів. Все інше - надмірне.

Однак , як тільки HTML почне ставати менш прибитим донизу, як тільки він почне розширюватися способами, які ви не можете передбачити, але які є цілком законними, як тільки вам доведеться співставити більше різних речей або зі складнішими залежностями, ви зрештою дістанетесь до точки, коли вам доведеться більше працювати, щоб виконати рішення, яке використовує регулярні вирази, ніж вам доведеться використовувати клас розбору. Де ця точка беззбитковості знову залежить від вашого власного рівня комфорту з регулярними виразами.

Отже, що мені робити?

Я не збираюся розповідати вам, що ви повинні робити або чого не можете зробити. Я думаю, що це неправильно. Я просто хочу представити вам можливості, трохи відкрийте очі. Ви можете вибрати, що ви хочете робити і як ви хочете це зробити. Абсолютів немає - і ніхто не знає вашої власної ситуації так само, як ви самі. Якщо щось здається, що це занадто багато роботи, ну, можливо, так і є. Програмування має бути цікавим . Якщо це не так, ви можете зробити це неправильно.

Мою html_input_rxпрограму можна переглянути будь-якою кількістю дійсних способів. Одне з таких полягає в тому, що ви дійсно можете розбирати HTML за допомогою регулярних виразів. Але інше - це набагато, набагато, набагато важче, ніж майже хто-небудь думає, що це таке. Це може легко призвести до висновку, що моя програма є свідченням того, чого ви не повинні робити, оскільки це дійсно занадто важко.

Я не погоджуюся з цим. Звичайно, якщо все, що я роблю в своїй програмі, не має сенсу для вас після якогось дослідження, то вам не слід намагатися використовувати регулярні вирази для подібних завдань. Для конкретного HTML, режекси є чудовими, але для загального HTML вони рівнозначні безумству. Я постійно використовую класи для розбору, особливо якщо це HTML, який я сам не створив.

Режекси оптимальні для невеликих проблем розбору HTML, песимізовані для великих

Навіть якщо моя програма сприймається як ілюстративний, чому ви не повинні використовувати регулярні вирази для розбору загального HTML - це нормально, тому що я начебто мав на увазі, що це that - це все одно має бути відкривачем очей, щоб більше людей порушувало жахливо загальне явище і бридкий, бридкий звичку писати нечитані, неструктуровані та незрозумілі зразки.

Візерунки не повинні бути потворними, і вони не повинні бути жорсткими. Якщо ви створюєте некрасиві візерунки, це відображення на вас, а не на них.

Феноменально вишукана мова Regex

Мене попросили зазначити, що моє глибоке рішення вашої проблеми було написано в Perl. Ви здивовані? Ви не помітили? Це одкровення бомба?

Це правда, що не всі інші інструменти та мови програмування є такими ж зручними, виразними та потужними, що стосується регулярних виразів, як Perl. Там великий спектр, де деякі є більш підходящими, ніж інші. Взагалі, з мовами, які виражають регекси як частину основної мови, а не як бібліотеку, легше працювати. Я нічого не робив з реджексами, які ти не міг зробити, скажімо, в PCRE, хоча ти б по-іншому структурував програму, якби ти використовував C.

Врешті-решт, інші мови будуть наздогнані там, де зараз Perl з точки зору регулярних виразів. Я говорю це тому, що тоді, коли Перл починав, ніхто інший не мав нічого подібного до виразів Перла. Скажіть що завгодно, але тут явно перемогла Перл: усі копіювали регекси Перла, хоча на різних етапах свого розвитку. Perl був першопрохідцем майже (не зовсім, але майже) все, на що ви покладаєтесь в сучасних моделях, незалежно від того, яким інструментом чи мовою ви користуєтесь. Таким чином , в кінці кінців , інші будуть наздоганяти.

Але вони доганятимуться лише там, де колись був Перл, як і зараз. Все просувається. У реджексах, якщо нічого іншого, куди веде Перл, слідують інші. Де колись буде Перл, коли всі нарешті наздоганяють туди, де зараз знаходиться Перл? Я поняття не маю, але я знаю, що ми теж переїхали. Напевно, ми будемо ближче до стилю Perl₆ майстерності майстерності .

Якщо вам подобається така річ, але ви хочете використовувати її в Perl₅, вам може бути цікавий чудовий модуль Regexp :: Grammars від Damian Conway . Це абсолютно дивовижно, і те, що я робив тут у своїй програмі, здається настільки ж примітивним, як і моя, створює шаблони, які люди стискають разом без пробілів чи алфавітних ідентифікаторів. Перевір!


Простий HTML Chunker

Ось повне джерело до синтаксичного аналізу, який я показав у центрі цього повідомлення.

Я не пропоную вам використовувати це в умовах жорсткого тестування класу розбору. Але я втомився від того, щоб люди робили вигляд, що ніхто не може розібрати HTML з регулярними виразами лише тому, що вони не можуть. Ви однозначно можете, і ця програма є доказом цього твердження.

Звичайно, це не легко, але це це можливо!

І намагатися це зробити - це страшна трата часу, адже існують хороші класи для розбору, які ви повинні використовувати для цього завдання. Правильна відповідь людям, які намагаються розібрати довільний HTML, - це не те, що це неможливо. Це чудова і недоброзичлива відповідь. Правильна і чесна відповідь полягає в тому, що вони не повинні намагатися цього робити, тому що це занадто сильно турбуватися, щоб зрозуміти з нуля; вони не повинні ламати спину, прагнучи відтворити колесо, яке працює чудово.

З іншого боку, HTML, який потрапляє до передбачуваного підмножини, дуже легко проаналізувати з регулярними виразами. Не дивно, що люди намагаються їх використовувати, тому що для невеликих проблем, проблем із іграшками, можливо, нічого не може бути простішим. Ось чому так важливо розмежувати дві задачі - специфічну проти загальної - оскільки вони не обов'язково вимагають однакового підходу.

Я сподіваюсь у майбутньому тут побачити більш справедливе та чесне ставлення до питань щодо HTML та регулярних виразів.

Ось мій HTML лексер. Він не намагається зробити валідаційний синтаксичний аналіз; він просто ідентифікує лексичні елементи. Ви можете подумати про це швидше як HTML-фрагмент, ніж проаналізатор HTML. Це не дуже прощає зламаний HTML, хоча він робить дуже маленькі припущення в цьому напрямку.

Навіть якщо ви ніколи самі не розбираєте повний HTML (а навіщо це вам? Це вирішена проблема!), У цій програмі є безліч крутих шматочків регулярних виразів, з яких я вважаю, що багато людей можуть багато чому навчитися. Насолоджуйтесь!

#!/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
два основні моменти з вашого коментаря: "Я постійно використовую класи для розбору, особливо якщо це HTML, який я сам не створив". і "Шаблони не повинні бути потворними, і вони не повинні бути жорсткими. Якщо ви створюєте потворні візерунки, це відображення на вас, а не на них". Я повністю згоден з тим, що ви сказали, тому я переоцінюю проблему. велике спасибі за таку детальну відповідь
Салман

168
Для тих, хто не знає, я подумав, що зазначу, що Том є співавтором "Програмування Перла" (він же книги "Верблюди") і одним із найвищих авторитетів Перла. Якщо ви сумніваєтесь, що це справжній Том Крістіансен, поверніться і прочитайте повідомлення.
Білл Рупперт

20
Підводячи підсумок: RegEx неправильно називається Я думаю, що це соромно, але це не зміниться. Сумісні системи "RegEx" не можуть відхиляти нерегулярні мови. Тому вони не можуть бути реалізовані належним чином лише за допомогою машин Finte State. Потужні поняття навколо обчислювальних класів не застосовуються. Використання RegEx не забезпечує час виконання O (n) виконання. Перевагами RegEx є стислий синтаксис і мається на увазі область розпізнавання символів. Для мене це повільна рухливість поїзда, неможливо відвести погляд, але з жахливими наслідками розгортається.
Стів Штейнер

27
@tchrist, це ніколи не відповідає оригінальному запиту ОП. І чи розбирається тут відповідний термін? Рефекс Afaics робить токенізуючий / лексичний аналіз, але остаточний аналіз робиться з кодом Perl, а не самим регулярним виразом.
Qtax

65
@tchrist Дуже вражаючий. Ви, очевидно, висококваліфікований та талановитий програміст Perl та надзвичайно обізнаний у сучасних регулярних виразах. Я хотів би зазначити, що те, що ви написали, насправді не є регулярним виразом (сучасним, регулярним чи іншим способом), а швидше програмою Perl, яка сильно використовує регулярні вирази. Чи підтримує ваша публікація твердження про те, що регулярні вирази можуть правильно розбирати HTML? Або це більше схоже на докази того, що Perl може правильно розбирати HTML? У будь-якому випадку, приємної роботи!
Майк Кларк

126
  1. Ви можете написати роман так, як це робив trist
  2. Ви можете використовувати бібліотеку DOM, завантажити HTML і використовувати xpath та просто використовувати //input[@type="hidden"]. Або якщо ви не хочете використовувати xpath, просто отримайте всі входи та відфільтруйте, які з них приховані getAttribute.

Я віддаю перевагу №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>';
}

Результат:

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

72
Фактично це було моєю точкою. Я хотів показати, як це важко.
tchrist

19
Дуже хороші речі там. Я дуже сподівався, що люди покажуть, наскільки простіше це використовувати клас розбору, тож спасибі! Я просто хотів робочий приклад надзвичайної проблеми, яку вам доведеться пережити, щоб зробити це з нуля, використовуючи регулярні вирази. Я впевнений, що більшість людей приходять до висновку використовувати збірний парсер на загальному HTML, а не прокручувати свій власний. Regexes все ще чудово підходить для простого HTML, який вони створили самостійно, оскільки це позбавляється від 99,98% складності.
tchrist

5
Що було б добре після прочитання цих 2 дуже цікавих підходів, це порівняння швидкості / використання пам'яті / процесора одного підходу з іншим (тобто VS-синтаксичний аналіз синтаксичного розбору).
the_yellow_logo

1
@ Avt'W Так, не те, що ви повинні піти писати "роман", якщо Regexes буде швидше, але насправді це було б цікаво знати. :) Але я вже здогадуюсь, що аналізатор теж забирає менше ресурсів ..
Dennis98

Це насправді, чому XPath був винайдений в першу чергу!
Thorbjørn Ravn Andersen

21

У дусі лексемового рішення Тома Крістіанасена, ось посилання на начебто забуту статтю Роберта Кемерона 1998 року, REX: XML Shallow Parsing with Regular Expressions.

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

Анотація

Синтаксис XML є досить простим, що можна розібрати XML-документ у список його розмітки та текстових елементів, використовуючи єдиний регулярний вираз. Такий неглибокий аналіз XML-документа може бути дуже корисним для побудови різноманітних легких інструментів обробки XML. Однак складні регулярні вирази можуть бути важкими для побудови та ще складнішими для читання. Використовуючи форму грамотного програмування для регулярних виразів, цей документ документує набір виразів дрібного розбору XML, які можуть бути використані для простого, правильного, ефективного, надійного та не залежного від мови XML мілкого розбору. Подано також повну реалізацію неглибокого парсера, що містить менше 50 рядків у Perl, JavaScript та Lex / Flex.

Якщо вам подобається читати про регулярні вирази, папір Камерона є захоплюючою. Його написання є стислим, ґрунтовним та дуже детальним. Він не просто показує вам, як побудувати регулярний вираз REX, але і підхід до створення будь-якого складного регулярного вираження з менших частин.

Я використовую регулярний вираз REX увімкнення та вимкнення протягом 10 років, щоб вирішити проблему, про яку питав початковий плакат (як я можу відповідати цьому конкретному тегу, але не якомусь іншому дуже подібному тегу?). Я визнав регулярний вираз, який він розробив, як абсолютно надійний.

REX особливо корисний, коли ви зосереджуєтесь на лексичних подробицях документа - наприклад, при перетворенні одного типу текстового документа (наприклад, простого тексту, XML, SGML, HTML) в інший, де документ може бути недійсним, добре сформований або навіть піддається проходженню для більшості трансформацій. Це дозволяє націлювати на острови розмітки будь-де в документі, не порушуючи решту документа.


7

Хоча я люблю зміст решти цих відповідей, вони насправді не відповіли на це питання прямо чи так правильно. Навіть відповідь Платини була надмірно складною, а також менш ефективною. Тому я був змушений це поставити.

Я величезний прихильник Regex при правильному використанні. Але через стигму (та продуктивність) я завжди констатую, що добре сформований XML або HTML повинен використовувати XML-аналізатор. А ще краща ефективність була б синтаксичною синтаксичною розбіркою, хоча існує ряд між читабельністю, якщо це стає занадто нестандартним. Однак це не питання. Питання полягає в тому, як зіставити тег вводу прихованого типу. Відповідь:

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

Залежно від вашого аромату, єдиним варіантом регулярного вибору, який вам потрібно буде включити, є варіант ignorecase.


5
<input type='hidden' name='Oh, <really>?' value='Try a real HTML parser instead.'>
Ільмарі Каронен

4
Ваш приклад - самозакриття. Закінчується />. Крім того, хоча шанси на те, що >у полі імен майже немає, це дійсно можливо, щоб бути >в ручці дій. EG: Вбудований виклик javascript для властивості OnClick. Зважаючи на це, у мене є аналізатор XML для них, але також є Regex для тих, де документ, який мені дають, занадто заплутаний, щоб парсери XML могли обробляти, але Regex може. Крім того, до цього не було питання. Ви ніколи не зіткнетесь із цими ситуаціями із прихованим входом, і моя відповідь найкраща. Ya, <really>!.
Суамер

3
/>є XML-ism; він не потрібен у будь-якій версії HTML, за винятком XHTML (який ніколи насправді не набирав особливих зусиль і був замінений HTML5). І ви маєте рацію, що там багато безладного не дуже дійсного HTML-коду, але хороший аналізатор HTML ( не XML) повинен мати можливість впоратися з більшістю цього; якщо цього не відбудеться, швидше за все, браузери не будуть.
Ільмарі Каронен

1
Якщо вам потрібен єдиний розбір або пошук - одне звернення, щоб повернути колекцію прихованих полів введення, цей регулярний вираз буде ідеальним. Використання або класів (-ів) .NET XML Document, або посилання на XML / HTML-аналізатор третьої сторони просто для виклику одного методу було б надмірним, коли вбудований Regex. Парсер не міг би впоратися з цим, ймовірно, навіть не те, на що дивиться дивер. Але моя компанія роздає мільйони сторінок на місяць, які об'єднуються і розбиваються багатьма способами, так що іноді (не завжди) Регекс - найкращий варіант.
Суамер

1
Справа лише в тому, що ми не впевнені в цілій компанії, чому цей розробник хоче відповіді. Але це він просив.
Суамер

3

ви можете спробувати це:

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

і для більш детального результату ви можете спробувати це:

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

ви можете протестувати свій шаблон регулярного вибору тут http://regexpal.com/

ці патенти добре для цього:

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

і для випадкового порядку type, nameі valueви можете використовувати це:

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

або

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

на цьому :

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

`

до речі, я думаю, ви хочете щось подібне:

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

це не добре, але воно працює в будь-якому випадку.

протестуйте його: http://regexpal.com/


1

Я хотів би використати **DOMDocument**для вилучення коду 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') );
}

До речі, ви можете протестувати тут - regex101.com. Він показує результат у режимі реального часу. Деякі правила щодо Regexp: http://www.eclipse.org/tptp/home/downloads/installguide/gla_42/ref/rregexp.html Reader .


0

припустимо, що ваш вміст html зберігається у рядку html, то для того, щоб отримати кожен вхід, що містить тип прихованого, ви можете використовувати регулярне вираження

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

вищевказаний регекс знайдеться <inputз будь-якою кількістю символів, поки він не набере type="hidden"або type = 'приховано', а потім будь-яку кількість символів, поки не отримає>

/ g скажіть регулярний вираз, щоб знайти кожну підрядку, яка відповідає заданій схемі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.