Что делать Шаблон регулярного выражения не соответствует нигде в строке?

183

Я пытаюсь сопоставить <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 на базе Adobe Air для тестирования регулярных выражений.

Салман
источник
4
регулярное выражение - отличное решение, если у вас есть контроль над сгенерированным html, потому что это обычное, а не обычное обсуждение, но в моем случае я не знаю, как html изменится в будущем, поэтому лучше всего использовать синтаксический анализатор вместо регулярного выражения, я использовал regex в моем проекте в частях, которые я контролирую
Салман
Классический вопрос о переполнении стека - это вопрос, ответ на который начинается с «Невозможно проанализировать [X] 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
}

Основной принцип здесь - не пытайтесь слишком много работать с одним регулярным выражением. Как вы заметили, регулярные выражения обеспечивают определенный порядок. Поэтому вместо этого вам нужно сначала сопоставить КОНТЕКСТ того, что вы пытаетесь извлечь, а затем выполнить повторное сопоставление с данными, которые вы хотите.

РЕДАКТИРОВАТЬ: Тем не менее, я согласен, что в целом использование парсера HTML, вероятно, проще и лучше, и вам действительно следует подумать о переделке кода или пересмотре ваших целей. :-) Но мне пришлось опубликовать этот ответ в качестве противодействия непонятной реакции о том, что анализ любого подмножества HTML невозможен: HTML и XML являются нерегулярными, если рассматривать всю спецификацию, но спецификация тега прилично регулярна , конечно, в пределах возможностей PCRE.

Платиновая лазурь
источник
14
Не противоречит всем приведенным здесь ответам. :)
tchrist
6
@tchrist: Твоего ответа здесь не было, когда я опубликовал свой. ;-)
Platinum Azure
7
ну да ладно - почему-то мне понадобилось больше времени, чтобы печатать, чем у вас. Думаю, мою клавиатуру нужно смазать. :)
tchrist
6
Это недопустимый HTML - должно быть value = "& lt; Вы действительно в этом уверены? & Gt;" Если место, которое он соскабливает, плохо справляется с тем, чтобы избежать подобных вещей, тогда ему понадобится более сложное решение, но если они все сделают правильно (и если он контролирует это, он должен убедиться, что это правильно), тогда он в порядке.
Росс Снайдер
14
Обязательная ссылка на лучший ответ SO по этому вопросу (возможно, лучший период ответа SO): stackoverflow.com/questions/1732348/…
Даниэль Рибейро
683

О да, ты можешь использовать регулярные выражения для анализа HTML!

Для задачи, которую вы пытаетесь выполнить, регулярные выражения отлично подходят !

Это является правдой , что большинство людей недооценивают сложность разбор HTML с регулярными выражениями и , следовательно , сделать так плохо.

Но это не какой-то фундаментальный недостаток, связанный с теорией вычислений. Здесь часто повторяют эту глупость , но не верьте им.

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

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

Но я . ☻


Общие решения для анализа HTML на основе регулярных выражений

Сначала я покажу, как легко разбирать произвольный 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 - каждый из которых я мог быстро и легко достать. Помимо этого, я также запускал его для файлов, специально созданных для взлома наивных парсеров.

Это не наивный парсер.

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

Теперь, когда это не так, позвольте мне обратиться к вопросу OP.

Демонстрация решения задачи OP с использованием регулярных выражений

Небольшая 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 - и это нормально, потому что я вроде как хотел, чтобы это было так - она ​​все равно должна открывать глаза, чтобы больше людей нарушали ужасно распространенные и неприятная, неприятная привычка писать нечитаемые, неструктурированные и неподдерживаемые шаблоны.

Выкройки не обязательно должны быть уродливыми и сложными. Если вы создаете уродливые узоры, это отражается на вас, а не на них.

Феноменально изысканный язык регулярных выражений

Меня попросили указать, что предложенное мной решение вашей проблемы написано на Perl. Вы удивлены? Вы не заметили? Это открытие произвело эффект разорвавшейся бомбы?

Верно, что не все другие инструменты и языки программирования столь же удобны, выразительны и мощны, когда дело касается регулярных выражений, как Perl. Существует большой спектр, некоторые из которых подходят больше, чем другие. В общем, с языками, которые выражают регулярные выражения как часть основного языка, а не как библиотеку, легче работать. Я ничего не сделал с регулярными выражениями, что вы не могли бы сделать, скажем, в PCRE, хотя вы бы структурировали программу иначе, если бы использовали C.

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

Но они догонят только то, на чем был Perl когда-то в прошлом, как и сейчас. Все идет вперед. В регулярных выражениях, если ничто иное, куда ведет Perl, следуют другие. Где будет Perl, когда все, наконец, поймут, где Perl находится сейчас? Понятия не имею, но знаю, что мы тоже переедем. Возможно, мы будем ближе к стилю создания шаблонов Perl .

Если вам нравятся такие вещи, но вы хотели бы использовать их в Perl₅, вас может заинтересовать замечательный модуль Regexp :: Grammars Дэмиана Конвея . Это совершенно потрясающе, и то, что я сделал здесь, в своей программе, кажется таким же примитивным, как и мое, что создает шаблоны, которые люди собирают вместе, без пробелов или буквенных идентификаторов. Проверить это!


Простой 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, который я не создавал сам». и «Шаблоны не обязательно должны быть уродливыми, и они не должны быть сложными. Если вы создаете уродливые шаблоны, это отражается на вас, а не на них». Я полностью согласен с тем, что вы сказали, поэтому я переоцениваю проблему. Большое спасибо за такой подробный ответ
Salman
168
Для тех, кто не знает, я подумал, что должен упомянуть, что Том является соавтором "Programming Perl" (также известной как книга Camel) и одним из ведущих авторитетов Perl. Если вы сомневаетесь, что это настоящий Том Кристиансен, вернитесь и прочтите этот пост.
Билл Рупперт
21
Подводя итог: RegEx неправильно названы. Думаю, это обидно, но ничего не изменится. Совместимые движки RegEx не могут отклонять нерегулярные языки. Поэтому они не могут быть правильно реализованы только с помощью Finte State Machines. Мощные концепции вычислительных классов здесь не применяются. Использование RegEx не гарантирует время выполнения O (n). Преимущества RegEx - это краткий синтаксис и подразумеваемая область распознавания символов. Для меня это крушение медленно движущегося поезда, от которого невозможно отвести взгляд, но разворачиваются ужасные последствия.
Стив Штайнер
27
@tchrist, это никогда не отвечает на исходный вопрос OP. И подходит ли здесь синтаксический анализ ? Afaics, регулярное выражение выполняет разметку / лексический анализ, но окончательный синтаксический анализ выполняется с помощью кода Perl, а не самого регулярного выражения.
Qtax
67
@tchrist Очень впечатляет. Вы, очевидно, высококвалифицированный и талантливый программист на Perl и очень хорошо разбираетесь в современных регулярных выражениях. Однако я хотел бы отметить, что то, что вы написали, на самом деле не является регулярным выражением (современным, регулярным или каким-либо другим), а скорее программой Perl, которая интенсивно использует регулярные выражения. Действительно ли ваш пост поддерживает утверждение, что регулярные выражения могут правильно анализировать HTML? Или это больше похоже на доказательство того, что Perl может правильно разбирать HTML? В любом случае, хорошая работа!
Майк Кларк
127
  1. Вы можете написать роман, как это сделал Христос
  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 вместо того, чтобы накатывать свои собственные. Регулярные выражения по-прежнему отлично подходят для простого HTML, который они сделали сами, потому что это избавляет от 99,98% сложности.
tchrist
5
Что было бы неплохо после прочтения этих двух очень интересных подходов, было бы сравнение скорости / использования памяти / ЦП одного подхода с другим (т.е. класс синтаксического анализа VS на основе регулярных выражений).
the_yellow_logo
1
@ Avt'W Да, не то чтобы тебе следовало писать «роман», если регулярные выражения оказываются быстрее, но на самом деле было бы просто интересно узнать. :) Но я уже предполагаю, что синтаксический анализатор тоже требует меньше ресурсов ..
Dennis98
Вот почему вообще был изобретен XPath!
Thorbjørn Ravn Andersen
21

В духе лексического решения Тома Кристиансена вот ссылка на, казалось бы, забытую статью Роберта Кэмерона 1998 года, REX: поверхностный анализ XML с регулярными выражениями.

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

Хотя мне нравится содержание остальных ответов, они не ответили на вопрос прямо или правильно. Даже ответ Platinum был чрезмерно сложным и к тому же менее эффективным. Поэтому я был вынужден поставить это.

Я большой сторонник Regex при правильном использовании. Но из-за стигмы (и производительности) я всегда заявляю, что правильно сформированный XML или HTML должен использовать XML Parser. И даже лучшая производительность была бы при синтаксическом анализе строк, хотя есть грань между удобочитаемостью, если это становится слишком неуправляемым. Однако вопрос не в этом. Вопрос в том, как сопоставить тег ввода скрытого типа. Ответ:

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

В зависимости от вашего вкуса, единственный вариант регулярного выражения, который вам нужно включить, - это параметр ignorecase.

Суамере
источник
5
<input type='hidden' name='Oh, <really>?' value='Try a real HTML parser instead.'>
Ilmari Karonen
4
Ваш пример самозакрывающийся. Должен заканчиваться />. Кроме того, хотя шансы иметь >в поле имени почти нет, действительно возможно, что там будет >в дескрипторе действия. Например: встроенный вызов javascript для свойства OnClick. При этом у меня есть синтаксический анализатор XML для них, но также есть Regex для тех, где предоставленный мне документ слишком запутан для обработки синтаксическими анализаторами XML, но Regex может. Кроме того, вопрос был не в этом. Вы никогда не столкнетесь с подобными ситуациями со скрытым вводом данных, и мой ответ - лучший. Ya, <really>!.
Suamere
3
/>это XML-изм; он не требуется ни в одной версии HTML, за исключением XHTML (который никогда не пользовался особой популярностью и был почти заменен HTML5). И вы правы в том, что существует много беспорядочного не совсем корректного HTML, но хороший синтаксический анализатор HTML ( не XML) должен уметь справиться с большей его частью; если нет, скорее всего, и браузеры тоже.
Ilmari Karonen
1
Если единственный синтаксический анализ или поиск, который вам нужен, - это одно нажатие для возврата коллекции скрытых полей ввода, это регулярное выражение было бы идеальным. Использование классов .NET XML Document или ссылки на сторонний XML / HTML Parser только для вызова одного метода было бы излишним, когда Regex встроен. И вы правы, что веб-сайт настолько испорчен, что хороший HTML парсер не может справиться с этим, вероятно, разработчик даже не на что смотрит. Но моя компания передает миллионы страниц в месяц, которые объединяются и обрабатываются разными способами, так что иногда (не всегда) Regex является лучшим вариантом.
Suamere
1
Дело только в том, что мы не уверены в отношении всей компании, по которой этот разработчик хочет получить этот ответ. Но это то, о чем он просил.
Suamere
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 .

HTML5 разработчик
источник
0

предположим, что ваш html-контент хранится в строке html, тогда для получения каждого ввода, содержащего скрытый тип, вы можете использовать регулярное выражение

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

приведенное выше регулярное выражение find, <inputза которым следует любое количество символов, пока оно не получит, type="hidden"или введите = 'hidden', за которым следует любое количество символов, пока оно не получит>

/ g указывает регулярному выражению найти каждую подстроку, соответствующую заданному шаблону.

Нитин9791
источник