O que fazer O padrão de expressão regular não corresponde a nenhum lugar da string?

181

Estou tentando corresponder <input>os campos do tipo "oculto" usando este padrão:

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

Estes são dados de exemplo de formulário:

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

Mas não tenho a certeza que o type, name, e valueatributos sempre aparecem na mesma ordem. Se o typeatributo vier por último, a correspondência falhará, porque no meu padrão está no início.

Pergunta:
Como posso alterar meu padrão para que ele corresponda, independentemente das posições dos atributos na <input>tag?

PS: A propósito, estou usando a RegEx Desktop Tool baseada em Adobe Air para testar expressões regulares.

Salman
fonte
4
regex é uma solução excelente se você tem o controle sobre o html gerado porque é um debate regular versus não regular, mas, no meu caso, não sei como o html mudará no futuro, o melhor é usar o analisador em vez do regex, usei regex no meu projeto em partes que eu sou tem controle sobre
Salman

Respostas:

108

Ao contrário de todas as respostas aqui, o que você está tentando fazer regex é uma solução perfeitamente válida. Isso ocorre porque você NÃO está tentando combinar tags equilibradas - isso seria impossível com a expressão regular! Mas você está apenas combinando o que está em uma tag, e isso é perfeitamente regular.

Aqui está o problema, no entanto. Você não pode fazer isso com apenas um regex ... você precisa fazer uma correspondência para capturar uma <input>tag e, posteriormente, fazer um processamento adicional. Observe que isso só funcionará se nenhum dos valores de atributo tiver um >caractere, portanto, não é perfeito, mas deve ser suficiente para entradas sãs.

Aqui está algum código Perl (pseudo) para mostrar o que eu quero dizer:

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
}

O princípio básico aqui é: não tente fazer muito com uma expressão regular. Como você notou, expressões regulares impõem uma certa quantidade de ordem. Portanto, o que você precisa fazer é primeiro corresponder ao CONTEXTO do que você está tentando extrair e, em seguida, fazer a sub correspondência dos dados desejados.

EDIT: No entanto, eu concordo que, em geral, o uso de um analisador de HTML é provavelmente mais fácil e melhor e você deve considerar redesenhar seu código ou reexaminar seus objetivos. :-) Mas eu tive que postar esta resposta como uma reação à reação instintiva de que analisar qualquer subconjunto de HTML é impossível: HTML e XML são irregulares quando você considera toda a especificação, mas a especificação de uma tag é decentemente regular , certamente dentro do poder do PCRE.

Platinum Azure
fonte
14
Não é contrário a todas as respostas aqui. :)
tchrist
6
@ tchrist: Sua resposta não estava aqui quando publiquei a minha. ;-)
Platinum Azure
7
sim - por algum motivo, demorei mais para digitar do que o seu. Acho que meu teclado precisa ser lubrificado. :)
tchrist
6
Isso é HTML inválido - deve ser value = "& lt; Você tem certeza disso? & Gt;" Se o local que ele está raspando faz um mau trabalho para escapar de coisas como essa, então ele precisará de uma solução mais sofisticada - mas se eles fizerem o certo (e se ele tiver controle sobre isso, ele deve se certificar de que está certo), então ele estará bem.
Ross Snyder
14
Ligação obrigatória para a melhor resposta SO sobre o assunto (possivelmente melhor período resposta SO): stackoverflow.com/questions/1732348/...
Daniel Ribeiro
682

Ah, sim, você pode usar expressões regulares para analisar HTML!

Para a tarefa que você está tentando, as expressões regulares estão perfeitamente corretas!

Ele é verdade que a maioria das pessoas subestimam a dificuldade de análise de HTML com expressões regulares e, portanto, fazê-lo mal.

Mas essa não é uma falha fundamental relacionada à teoria computacional. Essa bobagem é muito repetida por aqui , mas você não acredita nelas.

Portanto, embora certamente possa ser feito (essa publicação serve como prova de existência desse fato incontestável), isso não significa que  deva  ser.

Você deve decidir por si próprio se está preparado para a tarefa de escrever o que equivale a um analisador HTML específico para fins especiais a partir de expressões regulares. A maioria das pessoas não é.

Mas eu sou ☻


Soluções gerais de análise de HTML baseadas em Regex

Primeiro, mostrarei como é fácil analisar HTML arbitrário com expressões regulares. O programa completo está no final desta postagem, mas o coração do analisador é:

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

Veja como é fácil ler isso?

Como está escrito, ele identifica cada pedaço de HTML e informa onde ele o encontrou. Você pode modificá-lo facilmente para fazer o que quiser com qualquer tipo de peça ou para tipos mais específicos que estes.

Não tenho casos de teste com falha (à esquerda :): executei esse código com êxito em mais de 100.000 arquivos HTML - todos os que consegui com rapidez e facilidade. Além disso, eu também o executei em arquivos construídos especificamente para quebrar analisadores ingênuos.

Este não é um analisador ingênuo.

Ah, tenho certeza de que não é perfeito, mas ainda não consegui quebrá-lo. Eu acho que, mesmo que algo acontecesse, a correção seria fácil de se encaixar por causa da estrutura clara do programa. Mesmo programas pesados ​​em regex devem ter estrutura.

Agora que isso está fora do caminho, deixe-me abordar a questão do OP.

Demonstração de solução de tarefas do OP usando expressões regulares

O pequeno html_input_rxprograma que incluo abaixo produz a seguinte saída, para que você possa ver que a análise de HTML com regexes funciona bem para o que você deseja fazer:

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

Analisar tags de entrada, consulte Nenhuma entrada incorreta

Aqui está a fonte do programa que produziu a saída acima.

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

Ai está! Nada disso! :)

Somente você pode julgar se sua habilidade com expressões regulares depende de uma tarefa de análise específica. O nível de habilidade de todos é diferente e cada nova tarefa é diferente. Para trabalhos em que você tem um conjunto de entradas bem definido, as expressões regulares são obviamente a escolha certa, porque é trivial reunir algumas quando você tem um subconjunto restrito de HTML para lidar. Mesmo iniciantes em regex devem lidar com esses trabalhos com regexes. Qualquer outra coisa é um exagero.

No entanto , uma vez que o HTML comece a ficar menos complicado, uma vez que começa a se ramificar de maneiras que você não pode prever, mas que são perfeitamente legais, uma vez que você tenha que combinar tipos diferentes de coisas ou dependências mais complexas, você chegará a um ponto em que você precisa trabalhar mais para efetuar uma solução que use expressões regulares do que para uma classe de análise. O ponto em que esse ponto de equilíbrio cai depende novamente do seu próprio nível de conforto com as expressões regulares.

Então, o que eu deveria fazer?

Não vou lhe dizer o que você deve fazer ou o que não pode fazer. Eu acho que está errado. Eu só quero lhe apresentar possibilidades, abra seus olhos um pouco. Você escolhe o que deseja fazer e como deseja fazê-lo. Não há absolutos - e ninguém mais conhece sua própria situação, assim como você. Se algo parece dar muito trabalho, bem, talvez seja. A programação deve ser divertida , você sabe. Se não estiver, você pode estar fazendo errado.

Pode-se olhar para o meu html_input_rxprograma de várias maneiras válidas. Uma delas é que você realmente pode analisar HTML com expressões regulares. Mas outra é que é muito, muito, muito mais difícil do que quase todo mundo pensa que é. Isso pode facilmente levar à conclusão de que meu programa é uma prova do que você não deve fazer, porque é realmente muito difícil.

Não vou discordar disso. Certamente, se tudo o que faço no meu programa não faz sentido para você depois de algum estudo, você não deve tentar usar expressões regulares para esse tipo de tarefa. Para HTML específico, as expressões regulares são ótimas, mas para HTML genérico, é o mesmo que loucura. Eu uso classes de análise o tempo todo, especialmente se for HTML que eu não tenha gerado.

Regexa ideal para pequenos problemas de análise de HTML, pessimal para grandes

Mesmo que meu programa seja tomado como ilustrativo do motivo pelo qual você não deve usar expressões regulares para analisar HTML geral - o que é bom, porque eu meio que pretendia que fosse isso -, ainda assim deveria ser algo que abre os olhos para que mais pessoas quebrem o padrão comum. e o hábito desagradável de escrever padrões ilegíveis, não estruturados e inatingíveis.

Os padrões não precisam ser feios e não precisam ser difíceis. Se você criar padrões feios, é um reflexo sobre você, não sobre eles.

Linguagem Regex fenomenalmente requintada

Me pediram para salientar que minha solução proferida para o seu problema foi escrita em Perl. Você está surpreso? Você não percebeu? Esta revelação é uma bomba?

É verdade que nem todas as outras ferramentas e linguagens de programação são tão convenientes, expressivas e poderosas quando se trata de expressões regulares quanto o Perl. Há um grande espectro por aí, com alguns sendo mais adequados que outros. Em geral, os idiomas que expressaram expressões regulares como parte do idioma principal e não como uma biblioteca são mais fáceis de trabalhar. Não fiz nada com expressões regulares que você não poderia fazer, por exemplo, no PCRE, embora você estruture o programa de maneira diferente se estiver usando C.

Eventualmente, outros idiomas serão informados sobre o local em que o Perl está agora em termos de expressões regulares. Digo isso porque, quando o Perl começou, ninguém mais tinha nada parecido com as expressões regulares do Perl. Diga o que quiser, mas foi aqui que Perl claramente venceu: todos copiaram as expressões regulares de Perl, embora em estágios variados de seu desenvolvimento. O Perl foi pioneiro em quase (não quase tudo, mas quase) tudo em que você confia nos padrões modernos de hoje, independentemente da ferramenta ou linguagem usada. Então, eventualmente, os outros vão alcançá-lo.

Mas eles só alcançam onde Perl estava em algum momento no passado, exatamente como está agora. Tudo avança. Em regexes, se nada mais, onde Perl leva, outros seguem. Onde estará o Perl depois que todo mundo finalmente alcançar onde está agora? Não faço ideia, mas sei que nós também teremos mudado. Provavelmente estaremos mais próximos do estilo de criação de padrões de Perl₆ .

Se você gosta desse tipo de coisa, mas gostaria de usá-lo em Perl₅, pode estar interessado no maravilhoso módulo Regexp :: Grammars de Damian Conway . É totalmente incrível e faz com que o que eu fiz aqui no meu programa pareça tão primitivo quanto o meu, que faz com que os padrões que as pessoas criam juntos sem espaço em branco ou identificadores alfabéticos. Confira!


Chunker HTML Simples

Aqui está a fonte completa do analisador de onde mostrei a peça central no início desta postagem.

Estou não sugerindo que você deve usar este sobre uma classe analisar rigorosamente testados. Mas estou cansado de pessoas que fingem que ninguém pode analisar HTML com expressões regulares apenas porque não podem. Você pode, claramente, e este programa é a prova dessa afirmação.

Claro, não é fácil, mas isso é possível!

E tentar fazer isso é uma terrível perda de tempo, porque existem boas classes de análise que você deve usar para esta tarefa. A resposta certa para as pessoas que tentam analisar HTML arbitrário não é que seja impossível. Essa é uma resposta fácil e falsa. A resposta correta e honesta é que eles não devem tentar, porque é um incômodo demais descobrir do zero; eles não devem quebrar as costas se esforçando para reativar uma roda que funcione perfeitamente bem.

Por outro lado, o HTML que se enquadra em um subconjunto previsível é extremamente fácil de analisar com expressões regulares. Não é de admirar que as pessoas tentem usá-las, porque, para pequenos problemas, talvez problemas com brinquedos, nada poderia ser mais fácil. É por isso que é tão importante distinguir as duas tarefas - específica e genérica -, pois elas não exigem necessariamente a mesma abordagem.

Espero no futuro aqui ver um tratamento mais justo e honesto das perguntas sobre HTML e expressões regulares.

Aqui está o meu lexer HTML. Ele não tenta fazer uma análise de validação; apenas identifica os elementos lexicais. Você pode pensar nisso mais como um chunker HTML do que como um analisador HTML. Ele não perdoa muito o HTML quebrado, embora faça algumas permissões muito pequenas nessa direção.

Mesmo se você nunca analisar o HTML completo (e por que deveria? É um problema resolvido!), Este programa possui muitos bits legais de regex que acredito que muitas pessoas podem aprender muito. Aproveitar!

#!/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
fonte
23
dois destaques do seu comentário "Eu uso classes de análise o tempo todo, especialmente se for HTML que eu não tenha gerado." e "Os padrões não precisam ser feios e não precisam ser difíceis. Se você criar padrões feios, é um reflexo para você, não para eles". concordo totalmente com o que você disse, por isso estou reavaliando o problema. muito obrigado por essa resposta detalhada
Salman
168
Para quem não sabe, pensei em mencionar que Tom é o co-autor de "Programming Perl" (também conhecido como livro Camel) e uma das principais autoridades de Perl. Se você duvida que este é o verdadeiro Tom Christiansen, volte e leia o post.
Bill Ruppert
20
Resumindo: os RegEx estão com o nome errado. Eu acho uma pena, mas não vai mudar. Os mecanismos 'RegEx' compatíveis não têm permissão para rejeitar idiomas não regulares. Portanto, eles não podem ser implementados corretamente apenas com máquinas de estado Finte. Os poderosos conceitos em torno das classes computacionais não se aplicam. O uso de RegEx não garante tempo de execução de O (n). As vantagens dos RegEx são a sintaxe concisa e o domínio implícito do reconhecimento de caracteres. Para mim, isso é um acidente de trem lento, impossível de desviar o olhar, mas com conseqüências horríveis se desdobrando.
9788 Steve Jobs #
27
@tchrist, isso nunca responde à pergunta original do OP. E está analisando o termo apropriado aqui? Afaics, o regex está fazendo uma análise tokenizing / lexical, mas a análise final feita com o código Perl, não o próprio regex.
Qtax
65
@tchrist Muito impressionante. Você é obviamente um programador Perl altamente qualificado e talentoso, e extremamente conhecedor das expressões regulares modernas. Eu apontaria, no entanto, que o que você escreveu não é realmente uma expressão regular (moderna, regular ou não), mas sim um programa Perl que usa expressões regulares pesadamente. Sua postagem realmente suporta a alegação de que expressões regulares podem analisar HTML corretamente? Ou é mais uma evidência de que o Perl pode analisar o HTML corretamente? De qualquer maneira, bom trabalho!
Mike Clark
126
  1. Você pode escrever um romance como tchrist fez
  2. Você pode usar uma biblioteca DOM, carregar o HTML e usar xpath e apenas usar //input[@type="hidden"]. Ou se você não quiser usar o xpath, obtenha todas as entradas e filtre quais estão ocultas getAttribute.

Eu prefiro o 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>';
}

Resultado:

hide yo kids<br>hide yo wife<br>
meder omuraliev
fonte
72
Esse foi o meu ponto, na verdade. Eu queria mostrar o quão difícil é.
tchrist
19
Coisas muito boas lá. Eu realmente esperava que as pessoas mostrassem como é mais fácil usar uma aula de análise, então obrigada! Eu só queria um exemplo prático dos problemas extremos que você precisa enfrentar para fazer isso do zero usando expressões regulares. Espero, com certeza, que a maioria das pessoas conclua o uso de analisadores pré-fabricados em HTML genérico, em vez de usar seus próprios. Regexes ainda são ótimos para HTML simples que eles mesmos criaram, porque isso elimina 99,98% da complexidade.
tchrist
5
O que seria bom depois de ler essas duas abordagens muito interessantes seria comparar a velocidade / uso de memória / CPU de uma abordagem com outra (ou seja, classe de análise VS baseada em regex).
the_yellow_logo 31/08
1
@ Avt'W Sim, não que você deva escrever um 'romance' se o Regexes for mais rápido, mas na verdade seria realmente interessante saber. :) Mas o meu palpite é já, que um analisador leva menos recursos, também ..
Dennis98
É por isso que o XPath foi inventado em primeiro lugar!
Thorbjørn Ravn Andersen
21

No espírito da solução lexer de Tom Christiansen, aqui está um link para o aparentemente esquecido artigo de Robert Cameron, de 1998, REX: XML Shallow Parsing with Regular Expressions.

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

Resumo

A sintaxe do XML é simples o suficiente para que seja possível analisar um documento XML em uma lista de sua marcação e itens de texto usando uma única expressão regular. Uma análise superficial de um documento XML pode ser muito útil para a construção de uma variedade de ferramentas de processamento XML leves. No entanto, expressões regulares complexas podem ser difíceis de construir e ainda mais difíceis de ler. Usando uma forma de programação alfabética para expressões regulares, este documento documenta um conjunto de expressões de análise superficial XML que podem ser usadas como base para análise superficial XML simples, correta, eficiente, robusta e independente de idioma. Também são fornecidas implementações completas de analisador superficial com menos de 50 linhas, cada uma em Perl, JavaScript e Lex / Flex.

Se você gosta de ler sobre expressões regulares, o artigo de Cameron é fascinante. Sua escrita é concisa, completa e muito detalhada. Ele não está simplesmente mostrando como construir a expressão regular REX, mas também uma abordagem para criar qualquer regex complexo a partir de partes menores.

Eu uso a expressão regular REX há 10 anos para resolver o tipo de problema sobre o qual o pôster inicial perguntou (como faço para corresponder a essa tag específica, mas não a uma tag muito semelhante?). Eu encontrei o regex que ele desenvolveu para ser completamente confiável.

O REX é particularmente útil quando você se concentra nos detalhes lexicais de um documento - por exemplo, ao transformar um tipo de documento de texto (por exemplo, texto sem formatação, XML, SGML, HTML) em outro, onde o documento pode não ser válido, bem formado, ou mesmo analisável durante a maior parte da transformação. Permite segmentar ilhas de marcação em qualquer lugar de um documento sem perturbar o restante do documento.

David
fonte
7

Embora eu adore o conteúdo das demais respostas, elas não responderam à pergunta direta ou corretamente. Até a resposta da Platinum foi excessivamente complicada e também menos eficiente. Então eu fui forçado a colocar isso.

Sou um grande defensor do Regex, quando usado corretamente. Mas, devido ao estigma (e desempenho), eu sempre declaro que XML ou HTML bem formado deve usar um Analisador de XML. E um desempenho ainda melhor seria a análise de cadeias, embora exista uma linha entre a legibilidade, se isso ficar fora de controle. No entanto, essa não é a questão. A questão é como combinar uma tag de entrada do tipo oculto. A resposta é:

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

Dependendo do seu sabor, a única opção regex que você precisa incluir é a opção ignecase.

Suamere
fonte
5
<input type='hidden' name='Oh, <really>?' value='Try a real HTML parser instead.'>
Ilmari Karonen
4
Seu exemplo é de fechamento automático. Deve terminar com />. Além disso, embora as chances de ter um >no campo nome sejam quase nenhuma, é realmente possível que exista >um identificador de ação. EG: Uma chamada javascript embutida na propriedade OnClick. Dito isto, eu tenho um analisador XML para eles, mas também tenho um Regex para aqueles em que o documento que recebo está muito bagunçado para os analisadores XML manipularem, mas um Regex pode. Além disso, não era essa a pergunta. Você nunca encontrará essas situações com uma entrada oculta, e minha resposta é a melhor. Ya, <really>!.
Suamere 30/01
3
/>é um XML-ism; não é necessário em nenhuma versão do HTML, exceto no XHTML (que nunca ganhou muita força e foi praticamente substituído pelo HTML5). E você está certo de que existe um monte de HTML confuso e não muito válido por aí, mas um bom analisador de HTML ( não XML) deve ser capaz de lidar com a maior parte; se não o fizerem, provavelmente os navegadores também não.
Ilmari Karonen
1
Se a única análise ou pesquisa necessária for um único hit para retornar uma coleção de campos de entrada ocultos, esse regex seria perfeito. Usar a (s) classe (s) .NET XML Document ou referenciar um Analisador XML / HTML de terceiros apenas para chamar um método seria um exagero quando o Regex estiver incorporado. E você está certo que um site tão bagunçado que um bom HTML o analisador não conseguiu lidar com isso, provavelmente nem é algo que um desenvolvedor esteja olhando. Mas minha empresa recebe milhões de páginas por mês que são concatenadas e levantadas de várias maneiras, de modo que, às vezes (nem sempre), o Regex é a melhor opção.
Suamere 30/01
1
A única questão é que não temos certeza de toda a empresa pela qual esse desenvolvedor deseja esta resposta. Mas é o que ele pediu.
Suamere 30/01
3

você pode tentar isso:

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

e para obter resultados mais próximos, você pode tentar o seguinte:

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

você pode testar seu padrão regex aqui http://regexpal.com/

estes pattens são bons para isso:

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

e por ordem aleatória de type, namee valuevocê pode usar isto:

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

ou

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

nisto :

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

`

a propósito, eu acho que você quer algo como isto:

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

não é bom, mas funciona de qualquer maneira.

teste-o em: http://regexpal.com/

Shamshirsaz.Navid
fonte
1

Eu gostaria de usar **DOMDocument**para extrair o código 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, você pode testá-lo aqui - regex101.com. Mostra o resultado em tempo real. Algumas regras sobre o Regexp: http://www.eclipse.org/tptp/home/downloads/installguide/gla_42/ref/rregexp.html Reader .

Desenvolvedor HTML5
fonte
0

suponha que seu conteúdo html seja armazenado na string html e, a fim de obter todas as entradas que contêm o tipo oculto, você pode usar expressões regulares

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

o regex acima encontra <inputseguido por qualquer número de caracteres até que obtenha type="hidden"ou digite = 'oculto' seguido por qualquer número de caracteres até que obtenha>

/ g informa a expressão regular para encontrar todas as subseqüências que correspondem ao padrão especificado.

Nitin9791
fonte