¿Qué hacer? El patrón de expresión regular no coincide en ningún lugar de la cadena?

182

Estoy tratando de hacer coincidir <input>los campos tipo "ocultos" con este patrón:

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

Estos son datos de formulario de muestra:

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

Pero no estoy seguro de que el type, namey valueatributos serán siempre aparecen en el mismo orden. Si el typeatributo es el último, la coincidencia fallará porque en mi patrón está al comienzo.

Pregunta:
¿Cómo puedo cambiar mi patrón para que coincida independientemente de las posiciones de los atributos en la <input>etiqueta?

PD: Por cierto, estoy usando la herramienta de escritorio RegEx basada en Adobe Air para probar expresiones regulares.

Salman
fuente
44
regex es una excelente solución si tienes el control sobre el html generado porque es un debate regular versus no regular, pero en mi caso, no sé cómo va a cambiar el html en el futuro, así que lo mejor es usar un analizador en lugar de regex, he usado regex en mi proyecto en partes sobre las que tengo control
Salman

Respuestas:

108

Contrariamente a todas las respuestas aquí, porque lo que estás tratando de hacer regex es una solución perfectamente válida. Esto se debe a que NO está tratando de hacer coincidir las etiquetas equilibradas, ¡eso sería imposible con regex! Pero solo está haciendo coincidir lo que hay en una etiqueta, y eso es perfectamente regular.

Aquí está el problema, sin embargo. No puede hacerlo con una sola expresión regular ... necesita hacer una coincidencia para capturar una <input>etiqueta, luego hacer un procesamiento adicional sobre eso. Tenga en cuenta que esto solo funcionará si ninguno de los valores de atributo tiene un >carácter, por lo que no es perfecto, pero debería ser suficiente para entradas sensatas.

Aquí hay un código Perl (pseudo) para mostrarle lo que quiero decir:

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
}

El principio básico aquí es, no intentes hacer demasiado con una expresión regular. Como notó, las expresiones regulares imponen una cierta cantidad de orden. Entonces, lo que debe hacer en su lugar es hacer coincidir primero el CONTEXTO de lo que está tratando de extraer, luego hacer una subcordancia con los datos que desea.

EDITAR: Sin embargo, estaré de acuerdo en que, en general, usar un analizador HTML es probablemente más fácil y mejor y realmente debería considerar rediseñar su código o volver a examinar sus objetivos. :-) Pero tuve que publicar esta respuesta como respuesta a la reacción instintiva de que es imposible analizar cualquier subconjunto de HTML: HTML y XML son irregulares cuando se considera la especificación completa, pero la especificación de una etiqueta es bastante regular , ciertamente dentro del poder de PCRE.

Azul platino
fuente
14
No es contrario a todas las respuestas aquí. :)
tchrist
66
@tchrist: Tu respuesta no estaba aquí cuando publiqué la mía. ;-)
Platinum Azure
77
sí, bueno, por alguna razón me tomó más tiempo escribir que el tuyo. Creo que mi teclado debe engrasarse. :)
tchrist
66
Eso es HTML no válido: debe ser value = "& lt; ¿Está realmente seguro de esto? & Gt;" Si el lugar que está limpiando hace un mal trabajo para escapar de cosas como esta, entonces necesitará una solución más sofisticada, pero si lo hacen bien (y si tiene control sobre él, debería asegurarse de que sea correcto), entonces está bien.
Ross Snyder
14
Enlace obligatorio a la mejor respuesta SO sobre el tema (posiblemente el mejor período de respuesta SO): stackoverflow.com/questions/1732348/…
Daniel Ribeiro
682

¡Oh, sí, puedes usar expresiones regulares para analizar HTML!

Para la tarea que está intentando, ¡las expresiones regulares están perfectamente bien!

Que es cierto que la mayoría de la gente subestima la dificultad de análisis de HTML con expresiones regulares y por lo tanto hacen tan mal.

Pero este no es un defecto fundamental relacionado con la teoría computacional. Esa estupidez se repite mucho por aquí , pero no les creas.

Entonces, aunque ciertamente se puede hacer (esta publicación sirve como una prueba de existencia de este hecho incontrovertible), eso no significa que  deba  serlo.

Debes decidir por ti mismo si estás a la altura de la tarea de escribir lo que equivale a un analizador HTML dedicado y de propósito especial a partir de expresiones regulares. La mayoría de las personas no lo son.

Pero yo si . ☻


Soluciones generales de análisis HTML basadas en expresiones regulares

Primero, mostraré lo fácil que es analizar HTML arbitrario con expresiones regulares. El programa completo está al final de esta publicación, pero el corazón del analizador es:

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

¿Ves lo fácil que es leer?

Tal como está escrito, identifica cada pieza de HTML y le dice dónde encontró esa pieza. Puede modificarlo fácilmente para hacer lo que quiera con cualquier tipo de pieza, o para tipos más particulares que estos.

No tengo fallas en los casos de prueba (izquierda :): he ejecutado con éxito este código en más de 100,000 archivos HTML, cada uno de los cuales pude tener en mis manos rápida y fácilmente. Más allá de eso, también lo ejecuté en archivos construidos específicamente para romper analizadores ingenuos.

Este no es un analizador ingenuo.

Oh, estoy seguro de que no es perfecto, pero aún no he logrado romperlo. Me imagino que incluso si algo sucediera, la solución sería fácil de encajar debido a la estructura clara del programa. Incluso los programas con expresiones regulares deberían tener estructura.

Ahora que está fuera del camino, permítanme abordar la pregunta del OP.

Demostración de cómo resolver la tarea del OP utilizando expresiones regulares

El pequeño html_input_rxprograma que incluyo a continuación produce el siguiente resultado, para que pueda ver que analizar HTML con expresiones regulares funciona bien para lo que desea hacer:

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

Analizar etiquetas de entrada, ver No hay entrada malvada

Aquí está la fuente del programa que produjo el resultado anterior.

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

Ahí tienes! Nada de eso! :)

Solo usted puede juzgar si su habilidad con expresiones regulares depende de cualquier tarea de análisis particular. El nivel de habilidad de cada persona es diferente, y cada nueva tarea es diferente. Para los trabajos en los que tiene un conjunto de entrada bien definido, las expresiones regulares son obviamente la opción correcta, porque es trivial juntarlas cuando tiene un subconjunto restringido de HTML con el que lidiar. Incluso los principiantes de expresiones regulares deben manejar esos trabajos con expresiones regulares. Cualquier otra cosa es exagerada.

Sin embargo , una vez que el HTML comienza a ser menos claro, una vez que comienza a ramificarse de maneras que no puede predecir pero que son perfectamente legales, una vez que tiene que hacer coincidir más tipos diferentes de cosas o con dependencias más complejas, eventualmente llegará a un punto donde tiene que trabajar más para lograr una solución que use expresiones regulares de lo que tendría que usar una clase de análisis. Donde cae ese punto de equilibrio depende nuevamente de su propio nivel de comodidad con expresiones regulares.

¿Entonces qué debo hacer?

No voy a decirte lo que debes hacer o lo que no puedes hacer. Creo que eso está mal. Solo quiero presentarte posibilidades, abre los ojos un poco. Puedes elegir lo que quieres hacer y cómo quieres hacerlo. No hay absolutos, y nadie más conoce tu propia situación tan bien como tú. Si algo parece que es demasiado trabajo, bueno, tal vez lo sea. La programación debería ser divertida , ya sabes. Si no es así, puede estar haciéndolo mal.

Uno puede mirar mi html_input_rxprograma de muchas maneras válidas. Uno de ellos es que de hecho puede analizar HTML con expresiones regulares. Pero otra es que es mucho, mucho, mucho más difícil de lo que casi todos piensan. Esto puede llevar fácilmente a la conclusión de que mi programa es un testimonio de lo que no debe hacer, porque realmente es demasiado difícil.

No estaré en desacuerdo con eso. Ciertamente, si todo lo que hago en mi programa no tiene sentido para usted después de algún estudio, entonces no debería intentar usar expresiones regulares para este tipo de tarea. Para HTML específico, las expresiones regulares son geniales, pero para HTML genérico, equivalen a locura. Uso clases de análisis todo el tiempo, especialmente si es HTML que no he generado yo mismo.

Regexes óptimos para problemas de análisis de HTML pequeños , pesimales para problemas grandes

Incluso si mi programa se toma como ilustrativo de por qué usted debe no utiliza expresiones regulares para analizar general de HTML - lo cual está bien, porque un poco decir para que sea de esa ☺ - que todavía debe ser una revelación para que más gente a romper el terriblemente común y desagradable, desagradable hábito de escribir patrones ilegibles, no estructurados e imposibles de mantener.

Los patrones no tienen que ser feos, y no tienen que ser difíciles. Si creas patrones feos, es un reflejo en ti, no en ellos.

Lenguaje fenomenalmente exquisito de expresiones regulares

Me han pedido que señale que mi solución proferida a su problema ha sido escrita en Perl. ¿Estás sorprendido? ¿No te diste cuenta? ¿Es esta revelación una bomba?

Es cierto que no todas las otras herramientas y lenguajes de programación son tan convenientes, expresivos y poderosos cuando se trata de expresiones regulares como Perl. Hay un gran espectro, algunos son más adecuados que otros. En general, es más fácil trabajar con los idiomas que han expresado expresiones regulares como parte del lenguaje central en lugar de como una biblioteca. No he hecho nada con expresiones regulares que no pudieras hacer, por ejemplo, PCRE, aunque estructurarías el programa de manera diferente si usaras C.

Eventualmente, otros idiomas se pondrán al día con Perl en términos de expresiones regulares. Digo esto porque cuando comenzó Perl, nadie más tenía nada como las expresiones regulares de Perl. Di lo que quieras, pero aquí es donde Perl claramente ganó: todos copiaron las expresiones regulares de Perl, aunque en diferentes etapas de su desarrollo. Perl fue pionero en casi (no del todo, pero casi) todo lo que usted ha llegado a confiar en los patrones modernos de hoy, sin importar qué herramienta o lenguaje use. Entonces, eventualmente los demás se pondrán al día.

Pero solo se pondrán al día con Perl en el pasado, tal como es ahora. Todo avanza. En expresiones regulares, si nada más, donde Perl conduce, otros lo siguen. ¿Dónde estará Perl una vez que todos los demás finalmente se pongan al día donde está Perl ahora? No tengo idea, pero sé que nosotros también nos habremos mudado. Probablemente estaremos más cerca del estilo de patrones de elaboración de Perl₆ .

Si te gusta ese tipo de cosas pero te gustaría usarlo en Perl₅, quizás te interese el maravilloso módulo Regexp :: Grammars de Damian Conway . Es completamente increíble, y hace que lo que he hecho aquí en mi programa parezca tan primitivo como el mío hace que los patrones que las personas agrupan sin espacios en blanco o identificadores alfabéticos. ¡Echale un vistazo!


HTML simple Chunker

Aquí está la fuente completa del analizador desde el que mostré la pieza central al comienzo de esta publicación.

Estoy no sugiriendo que usted debe utilizar esta clase de análisis a través de una rigurosa prueba. Pero estoy cansado de que la gente finja que nadie puede analizar HTML con expresiones regulares solo porque no pueden. Claramente puede, y este programa es prueba de esa afirmación.

Está claro que no es fácil, pero que es posible!

Y tratar de hacerlo es una pérdida de tiempo terrible, porque existen buenas clases de análisis que debe utilizar para esta tarea. La respuesta correcta para las personas que intentan analizar HTML arbitrario no es que sea imposible. Esa es una respuesta fácil y falsa. La respuesta correcta y honesta es que no deberían intentarlo porque es demasiado molesto descubrirlo desde cero; no deben romperse la espalda tratando de reinventar una rueda que funcione perfectamente bien.

Por otro lado, el HTML que se encuentra dentro de un subconjunto predecible es muy fácil de analizar con expresiones regulares. No es de extrañar que la gente intente usarlos, porque para problemas pequeños, problemas con los juguetes, tal vez, nada podría ser más fácil. Es por eso que es tan importante distinguir las dos tareas, específicas versus genéricas, ya que estas no requieren necesariamente el mismo enfoque.

Espero en el futuro ver un tratamiento más justo y honesto de las preguntas sobre HTML y expresiones regulares.

Aquí está mi lexer HTML. No intenta hacer un análisis de validación; solo identifica los elementos léxicos. Puede pensarlo más como un fragmentador de HTML que como un analizador de HTML. No es muy indulgente con HTML roto, aunque hace algunas concesiones muy pequeñas en esa dirección.

Incluso si nunca analiza HTML completo usted mismo (¿y por qué debería hacerlo? ¡Es un problema resuelto!), Este programa tiene muchos bits de expresiones regulares geniales de los que creo que mucha gente puede aprender mucho. ¡Disfrutar!

#!/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
fuente
23
dos puntos destacados de su comentario "Uso clases de análisis todo el tiempo, especialmente si es HTML que no he generado yo mismo". y "Los patrones no tienen que ser feos, y no tienen que ser difíciles. Si crea patrones feos, es un reflejo en usted, no en ellos". Estoy totalmente de acuerdo con lo que ha dicho, así que estoy volviendo a evaluar el problema. muchas gracias por una respuesta tan detallada
Salman
168
Para aquellos que no lo saben, pensé en mencionar que Tom es coautor de "Programming Perl" (también conocido como el libro Camel) y una de las principales autoridades de Perl. Si duda de que este sea el verdadero Tom Christiansen, regrese y lea la publicación.
Bill Ruppert
20
En resumen: los RegEx están mal nombrados. Creo que es una pena, pero no cambiará. Los motores 'RegEx' compatibles no pueden rechazar idiomas no regulares. Por lo tanto, no se pueden implementar correctamente solo con Finte State Machines. Los poderosos conceptos en torno a las clases computacionales no se aplican. El uso de RegEx no garantiza el tiempo de ejecución de O (n). Las ventajas de RegEx son la sintaxis concisa y el dominio implícito del reconocimiento de caracteres. Para mí, este es un choque de trenes en movimiento lento, imposible de mirar hacia otro lado, pero con terribles consecuencias.
Steve Steiner
27
@tchrist, esto nunca responde la pregunta original de los OP. ¿Y está analizando el término apropiado aquí? Afaics, la expresión regular está haciendo tokenización / análisis léxico, pero el análisis final se realiza con el código Perl, no la expresión regular en sí.
Qtax
65
@tchrist Muy impresionante. Obviamente, usted es un programador de Perl altamente calificado y talentoso, y extremadamente conocedor de las expresiones regulares modernas. Sin embargo, quisiera señalar que lo que ha escrito no es realmente una expresión regular (moderna, regular o de otro tipo), sino más bien un programa Perl que utiliza mucho las expresiones regulares. ¿Su publicación realmente admite la afirmación de que las expresiones regulares pueden analizar HTML correctamente? ¿O es más como evidencia de que Perl puede analizar HTML correctamente? De cualquier manera, ¡buen trabajo!
Mike Clark el
126
  1. Puedes escribir una novela como lo hizo tchrist
  2. Puede usar una biblioteca DOM, cargar el HTML y usar xpath y simplemente usar //input[@type="hidden"]. O si no desea usar xpath, solo obtenga todas las entradas y filtre con cuáles están ocultas getAttribute.

Prefiero # 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
fuente
72
Ese era mi punto, en realidad. Quería mostrar lo difícil que es.
tchrist
19
Muy buenas cosas allí. Realmente esperaba que la gente mostrara lo fácil que es usar una clase de análisis, ¡así que gracias! Solo quería un ejemplo práctico del problema extremo que tienes que pasar para hacerlo desde cero usando expresiones regulares. Espero que la mayoría de la gente llegue a la conclusión de usar analizadores prefabricados en HTML genérico en lugar de usar el suyo. Sin embargo, las expresiones regulares siguen siendo excelentes para HTML simple que crearon, porque eso elimina el 99.98% de la complejidad.
tchrist
55
Lo que sería bueno después de leer esos 2 enfoques muy interesantes sería comparar la velocidad / uso de memoria / CPU de un enfoque contra otro (es decir, la clase de análisis VS basada en expresiones regulares).
the_yellow_logo
1
@ Avt'W Sí, no es que debas escribir una 'novela' si las expresiones regulares son más rápidas, pero de hecho sería realmente interesante saberlo. :) Pero supongo que un analizador también
consume
¡Esta es la razón por la cual XPath fue inventado en primer lugar!
Thorbjørn Ravn Andersen
21

En el espíritu de la solución léxica de Tom Christiansen, aquí hay un enlace al artículo de 1998 aparentemente olvidado de Robert Cameron, REX: XML Shallow Parsing with Regular Expressions.

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

Resumen

La sintaxis de XML es lo suficientemente simple como para que sea posible analizar un documento XML en una lista de sus elementos de marcado y texto utilizando una sola expresión regular. Tal análisis superficial de un documento XML puede ser muy útil para la construcción de una variedad de herramientas ligeras de procesamiento XML. Sin embargo, las expresiones regulares complejas pueden ser difíciles de construir e incluso más difíciles de leer. Utilizando una forma de programación alfabetizada para expresiones regulares, este documento documenta un conjunto de expresiones de análisis superficial XML que pueden usarse como base para un análisis superficial XML simple, correcto, eficiente, robusto e independiente del lenguaje. También se proporcionan implementaciones completas de analizador superficial de menos de 50 líneas en Perl, JavaScript y Lex / Flex.

Si te gusta leer sobre expresiones regulares, el artículo de Cameron es fascinante. Su escritura es concisa, minuciosa y muy detallada. No solo le muestra cómo construir la expresión regular REX, sino también un enfoque para construir expresiones regulares complejas a partir de partes más pequeñas.

He estado usando la expresión regular REX por intervalos durante 10 años para resolver el tipo de problema sobre el que preguntó el póster inicial (¿cómo hago coincidir esta etiqueta en particular pero no alguna otra etiqueta muy similar? He descubierto que la expresión regular que desarrolló es completamente confiable.

REX es particularmente útil cuando se concentra en los detalles léxicos de un documento, por ejemplo, al transformar un tipo de documento de texto (por ejemplo, texto plano, XML, SGML, HTML) en otro, donde el documento puede no ser válido, bien formado, o incluso analizable para la mayor parte de la transformación. Le permite apuntar a islas de marcado en cualquier lugar dentro de un documento sin alterar el resto del documento.

David
fuente
7

Si bien me encantan los contenidos del resto de estas respuestas, en realidad no respondieron la pregunta directamente o tan correctamente. Incluso la respuesta de Platinum fue demasiado complicada y también menos eficiente. Así que me vi obligado a poner esto.

Soy un gran defensor de Regex, cuando se usa correctamente. Pero debido al estigma (y el rendimiento), siempre afirmo que un XML o HTML bien formado debe usar un analizador XML. E incluso un mejor rendimiento sería el análisis de cadenas, aunque hay una línea entre la legibilidad si eso se sale de control. Sin embargo, esa no es la pregunta. La pregunta es cómo hacer coincidir una etiqueta de entrada de tipo oculto. La respuesta es:

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

Dependiendo de su sabor, la única opción de expresiones regulares que necesitaría incluir es la opción ignorar.

Suamere
fuente
55
<input type='hidden' name='Oh, <really>?' value='Try a real HTML parser instead.'>
Ilmari Karonen
44
Su ejemplo es de cierre automático. Debería terminar con />. Además, aunque las posibilidades de tener un >campo de nombre son casi nulas, es posible que haya >un identificador de acción. Por ejemplo: una llamada de JavaScript en línea en la propiedad OnClick. Dicho esto, tengo un analizador XML para esos, pero también tengo un Regex para aquellos en los que el documento que recibo está demasiado desordenado para que los analizadores XML puedan manejarlo, pero un Regex puede. Además, esta no es la pregunta. Nunca se encontrará con estas situaciones con una entrada oculta, y mi respuesta es la mejor. Ya, <really>!.
Suamere
3
/>es un ismo XML; no es obligatorio en ninguna versión de HTML, excepto XHTML (que nunca ganó mucha tracción y ha sido reemplazado por HTML5). Y tiene razón en que hay un montón de HTML desordenado no realmente válido, pero un buen analizador de HTML ( no XML) debería ser capaz de hacer frente a la mayoría; si no lo hacen, muy probablemente tampoco lo harán los navegadores.
Ilmari Karonen
1
Si el único análisis o búsqueda que necesita es un solo golpe para devolver una colección de campos de entrada ocultos, esta expresión regular sería perfecta. Usar las clases de documentos XML de .NET o hacer referencia a un analizador XML / HTML de terceros solo para llamar a un método sería excesivo cuando Regex está incorporado. Y tiene razón en que un sitio web está tan mal que un buen HTML el analizador no podía manejarlo, probablemente ni siquiera es algo que un desarrollador estaría mirando. Pero mi empresa recibe millones de páginas al mes que se concatenan y se conectan de muchas maneras, de modo que a veces (no siempre), Regex es la mejor opción.
Suamere
1
El único punto es que no estamos seguros de la razón completa de la compañía por la que este desarrollador quiere esta respuesta. Pero es lo que pidió.
Suamere
3

puedes probar esto:

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

y para un resultado más cercano puedes probar esto:

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

puedes probar tu patrón de expresiones regulares aquí http://regexpal.com/

estos patrones son buenos para esto:

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

y para un orden aleatorio de type, namey valuepuedes usar esto:

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

o

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

en este :

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

``

por cierto creo que quieres algo como esto:

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

No es bueno, pero funciona de cualquier manera.

pruébalo en: http://regexpal.com/

Shamshirsaz.Navid
fuente
1

Me gustaría usar **DOMDocument**para extraer el 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') );
}

Por cierto, puedes probarlo aquí: regex101.com. Muestra el resultado en tiempo real. Algunas reglas sobre Regexp: http://www.eclipse.org/tptp/home/downloads/installguide/gla_42/ref/rregexp.html Reader .

Desarrollador HTML5
fuente
0

suponga que su contenido html está almacenado en una cadena html, para obtener cada entrada que contenga el tipo oculto, puede usar expresiones regulares

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

el regex anterior se encuentra <inputseguido de cualquier número de caracteres hasta que se obtiene type="hidden"o escriba = 'hidden' seguido de cualquier número de caracteres hasta que se obtiene>

/ g le dice a la expresión regular que busque cada subcadena que coincida con el patrón dado.

Nitin9791
fuente