Pregunta

Estoy tratando de extraer los atributos de una etiqueta de anclaje (<a>). Hasta ahora tengo esta expresión:

(?<name>\b\w+\b)\s*=\s*("(?<value>[^"]*)"|'(?<value>[^']*)'|(?<value>[^"'<> \s]+)\s*)+

que funciona para cadenas como

<a href="test.html" class="xyz">

y (comillas simples)

<a href='test.html' class="xyz">

pero no para una cadena sin comillas:

<a href=test.html class=xyz>

¿Cómo puedo modificar mi expresión regular para que funcione con atributos sin comillas? ¿O hay una mejor manera de hacerlo?

¡Gracias !

Actualización: Gracias por todos los buenos comentarios y consejos hasta ahora. Hay una cosa que no mencioné: lamentablemente tengo que parchear / modificar código no escrito por mí mismo. Y no hay tiempo / dinero para reescribir estas cosas de abajo hacia arriba.

¿Fue útil?

Solución

Si tiene un elemento como

<name attribute=value attribute="value" attribute='value'>

esta expresión regular podría usarse para encontrar sucesivamente cada nombre y valor de atributo

(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?

Aplicado en:

<a href=test.html class=xyz>
<a href="test.html" class="xyz">
<a href='test.html' class="xyz">

produciría:

'href' => 'test.html'
'class' => 'xyz'
  

Nota: esto no funciona con valores de atributos numéricos, p. <div id="1"> no funcionará.

Otros consejos

Aunque el consejo de no analizar HTML a través de regexp es válido, aquí hay una expresión que hace más o menos lo que pediste:

/
   \G                     # start where the last match left off
   (?>                    # begin non-backtracking expression
       .*?                # *anything* until...
       <[Aa]\b            # an anchor tag
    )??                   # but look ahead to see that the rest of the expression
                          #    does not match.
    \s+                   # at least one space
    ( \p{Alpha}           # Our first capture, starting with one alpha
      \p{Alnum}*          # followed by any number of alphanumeric characters
    )                     # end capture #1
    (?: \s* = \s*         # a group starting with a '=', possibly surrounded by spaces.
        (?: (['"])        # capture a single quote character
            (.*?)         # anything else
             \2           # which ever quote character we captured before
        |   ( [^>\s'"]+ ) # any number of non-( '>', space, quote ) chars
        )                 # end group
     )?                   # attribute value was optional
/msx;

" Pero espera, " tu podrias decir. & "; ¿Qué pasa con * comentarios?!?! &"; Bien, entonces puede reemplazar el . en la sección de no retroceso con: (También maneja las secciones CDATA.)

(?:[^<]|<[^!]|<![^-\[]|<!\[(?!CDATA)|<!\[CDATA\[.*?\]\]>|<!--(?:[^-]|-[^-])*-->)
  • Además, si desea ejecutar una sustitución en Perl 5.10 (y creo que PCRE), puede poner \K justo antes del nombre del atributo y no tener que preocuparse por capturar todas las cosas que desea omitir.

Respuesta de Token Mantra: no debe modificar / modificar / cosechar / o producir html / xml utilizando expresiones regulares.

también pueden existir condicionales de mayúsculas y minúsculas como \ 'y \ " que debe tenerse en cuenta. Es mucho mejor usar un analizador DOM adecuado, un analizador XML o una de las muchas docenas de herramientas probadas para este trabajo en lugar de inventar el suyo.

Realmente no me importa cuál uses, siempre y cuando sea reconocido, probado y uses uno.

my $foo  = Someclass->parse( $xmlstring ); 
my @links = $foo->getChildrenByTagName("a"); 
my @srcs = map { $_->getAttribute("src") } @links; 
# @srcs now contains an array of src attributes extracted from the page. 

Solo para estar de acuerdo con todos los demás: no analices HTML usando regexp.

No es posible crear una expresión que seleccione atributos incluso para una pieza correcta de HTML, sin importar todas las posibles variantes con formato incorrecto. Su expresión regular ya es casi ilegible incluso sin tratar de hacer frente a la falta de comillas inválidas; persigue aún más el horror del HTML del mundo real y te volverás loco con un blob inmanejable de expresiones poco confiables.

Hay bibliotecas existentes para leer HTML roto o corregirlo en XHTML válido que luego puede devorar fácilmente con un analizador XML. Úsalos.

No puede usar el mismo nombre para varias capturas. Por lo tanto, no puede usar un cuantificador en expresiones con capturas con nombre.

Por lo tanto, no use & # 8217; t use capturas con nombre:

(?:(\b\w+\b)\s*=\s*("[^"]*"|'[^']*'|[^"'<>\s]+)\s+)+

O don & # 8217; t use el cuantificador en esta expresión:

(?<name>\b\w+\b)\s*=\s*(?<value>"[^"]*"|'[^']*'|[^"'<>\s]+)

Esto también permite valores de atributos como bar=' baz='quux:

foo="bar=' baz='quux"

Bueno, el inconveniente será que tendrá que quitar las comillas iniciales y finales después.

PHP (PCRE) y Python

Extracción de atributos simple ( Véalo funcionando ):

((?:(?!\s|=).)*)\s*?=\s*?["']?((?:(?<=")(?:(?<=\\)"|[^"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!"|')(?:(?!\/>|>|\s).)+))

O con verificación de apertura / cierre de etiqueta, recuperación de nombre de etiqueta y escape de comentarios. Esta expresión prevé comillas simples / dobles, comillas simples / dobles, comillas escapadas dentro de los atributos, espacios alrededor de signos iguales, diferente número de atributos, verifique solo los atributos dentro de las etiquetas y administre diferentes comillas dentro de un valor de atributo. ( Véalo funcionando ):

(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)

(Funciona mejor con las " gisx " flags.)


Javascript

Como las expresiones regulares Javascript no son compatibles con las funciones de observación, no admitirá la mayoría de las características de las expresiones anteriores que propongo. Pero en caso de que pueda satisfacer las necesidades de alguien, puede probar esta versión. ( Véalo funcionando ).

(\S+)=[\'"]?((?:(?!\/>|>|"|\'|\s).)+)

splattne,

La solución @VonC funciona en parte, pero hay algún problema si la etiqueta tiene una combinación de entre comillas y sin comillas

Este funciona con atributos mixtos

$pat_attributes = "(\S+)=(\"|'| |)(.*)(\"|'| |>)"

para probarlo

<?php
$pat_attributes = "(\S+)=(\"|'| |)(.*)(\"|'| |>)"

$code = '    <IMG title=09.jpg alt=09.jpg src="http://example.com.jpg?v=185579" border=0 mce_src="example.com.jpg?v=185579"
    ';

preg_match_all( "@$pat_attributes@isU", $code, $ms);
var_dump( $ms );

$code = '
<a href=test.html class=xyz>
<a href="test.html" class="xyz">
<a href=\'test.html\' class="xyz">
<img src="http://"/>      ';

preg_match_all( "@$pat_attributes@isU", $code, $ms);

var_dump( $ms );

$ ms contendría claves y valores en el segundo y tercer elemento.

$keys = $ms[1];
$values = $ms[2];

Este es mi mejor RegEx para extraer propiedades en la etiqueta HTML:

# Recortar la coincidencia dentro de las comillas (simple o doble)

(\S+)\s*=\s*([']|["])\s*([\W\w]*?)\s*\2

# Sin recortar

(\S+)\s*=\s*([']|["])([\W\w]*?)\2

Pros:

  • Puede recortar el contenido dentro de las comillas.
  • Haga coincidir todos los caracteres ASCII especiales dentro de las comillas.
  • Si tienes title = " Eres mío " el RegEx no se rompe

Contras:

  • Devuelve 3 grupos; primero la propiedad, luego la comilla (" | ') y al final la propiedad dentro de las comillas, es decir: <div title="You're"> el resultado es Grupo 1: título, Grupo 2: " ;, Grupo 3: Eres

Este es el ejemplo de RegEx en línea: https://regex101.com/r/aVz4uG/13



Normalmente uso este RegEx para extraer las etiquetas HTML:

Recomiendo esto si no utiliza un tipo de etiqueta como <div, <span, etc.

<[^/]+?(?:\".*?\"|'.*?'|.*?)*?>

Por ejemplo:

<div title="a>b=c<d" data-type='a>b=c<d'>Hello</div>
<span style="color: >=<red">Nothing</span>
# Returns 
# <div title="a>b=c<d" data-type='a>b=c<d'>
# <span style="color: >=<red">

Este es el ejemplo de RegEx en línea: https://regex101.com/r/aVz4uG/15

El error en este RegEx es:

<div[^/]+?(?:\".*?\"|'.*?'|.*?)*?>

En esta etiqueta:

<article title="a>b=c<d" data-type='a>b=c<div '>Hello</article>

Devuelve <div '> pero no debería devolver ninguna coincidencia:

Match:  <div '>

Para " resolver " esto elimina el patrón [^/]+?:

<div(?:\".*?\"|'.*?'|.*?)*?>


La respuesta # 317081 es buena pero no coincide correctamente con estos casos:

<div id="a"> # It returns "a instead of a
<div style=""> # It doesn't match instead of return only an empty property
<div title = "c"> # It not recognize the space between the equal (=)

Esta es la mejora:

(\S+)\s*=\s*["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))?[^"']*)["']?

vs

(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?

Evite los espacios entre señal igual: (\ S +) \ s * = \ s * ((?: ...

Cambiar los últimos + y. para: | [> " '])) ? [^ "'] * ) [" ']?

Este es el ejemplo de RegEx en línea: https://regex101.com/r/aVz4uG/8

algo como esto podría ser útil

'(\S+)\s*?=\s*([\'"])(.*?|)\2

Le sugiero que use HTML Tidy para convertir el HTML a XHTML y luego usar un XPath adecuado expresión para extraer los atributos.

Si quiere ser general, debe mirar la especificación precisa de la etiqueta a, como aquí . Pero incluso con eso, si haces tu expresión regular perfecta, ¿qué pasa si tienes HTML malformado?

Sugeriría ir a una biblioteca para analizar html, dependiendo del idioma con el que trabaje: p. como la hermosa sopa de Python.

Si estás en .NET, te recomiendo el paquete de agilidad HTML, muy robusto incluso con HTML mal formado.

Entonces puedes usar XPath.

Reconsideraría la estrategia de usar solo una sola expresión regular. Claro que es un buen juego crear una sola expresión regular que lo haga todo. Pero en términos de mantenimiento, está a punto de dispararse con los dos pies.

Las etiquetas y atributos en HTML tienen la forma

<tag 
   attrnovalue 
   attrnoquote=bli 
   attrdoublequote="blah 'blah'"
   attrsinglequote='bloob "bloob"' >

Para hacer coincidir los atributos, necesita una expresión regular attr que encuentre uno de los cuatro formularios. Luego debe asegurarse de que solo las coincidencias se notifiquen dentro de las etiquetas HTML. Suponiendo que tiene la expresión regular correcta, la expresión regular total sería:

attr(?=(attr)*\s*/?\s*>)

La búsqueda anticipada garantiza que solo otros atributos y la etiqueta de cierre sigan el atributo. Utilizo la siguiente expresión regular para $1:

\s+(\w+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^><"'\s]+)))?

Los grupos sin importancia no se capturan. El primer grupo coincidente $2 le da el nombre del atributo, el valor es uno de $3 o $4 o $2$3$4. Yo uso <=> para extraer el valor. La expresión regular final es

\s+(\w+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^><"'\s]+)))?(?=(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^><"'\s]+))?)*\s*/?\s*>)

Nota: Eliminé todos los grupos innecesarios en la búsqueda anticipada e hice que todos los grupos restantes no se capturaran.

También necesitaba esto y escribí una función para analizar atributos, puede obtenerlo desde aquí:

https://gist.github.com/4153580

(Nota: no utiliza expresiones regulares)

He creado una función PHP que podría extraer atributos de cualquier etiqueta HTML. También puede manejar atributos como disabled que no tienen valor, y también puede determinar si la etiqueta es una etiqueta independiente (no tiene etiqueta de cierre) o no (tiene una etiqueta de cierre) al verificar el resultado content:

/*! Based on <https://github.com/mecha-cms/cms/blob/master/system/kernel/converter.php> */
function extract_html_attributes($input) {
    if( ! preg_match('#^(<)([a-z0-9\-._:]+)((\s)+(.*?))?((>)([\s\S]*?)((<)\/\2(>))|(\s)*\/?(>))$#im', $input, $matches)) return false;
    $matches[5] = preg_replace('#(^|(\s)+)([a-z0-9\-]+)(=)(")(")#i', '$1$2$3$4$5<attr:value>$6', $matches[5]);
    $results = array(
        'element' => $matches[2],
        'attributes' => null,
        'content' => isset($matches[8]) && $matches[9] == '</' . $matches[2] . '>' ? $matches[8] : null
    );
    if(preg_match_all('#([a-z0-9\-]+)((=)(")(.*?)("))?(?:(\s)|$)#i', $matches[5], $attrs)) {
        $results['attributes'] = array();
        foreach($attrs[1] as $i => $attr) {
            $results['attributes'][$attr] = isset($attrs[5][$i]) && ! empty($attrs[5][$i]) ? ($attrs[5][$i] != '<attr:value>' ? $attrs[5][$i] : "") : $attr;
        }
    }
    return $results;
}

Código de prueba

$test = array(
    '<div class="foo" id="bar" data-test="1000">',
    '<div>',
    '<div class="foo" id="bar" data-test="1000">test content</div>',
    '<div>test content</div>',
    '<div>test content</span>',
    '<div>test content',
    '<div></div>',
    '<div class="foo" id="bar" data-test="1000"/>',
    '<div class="foo" id="bar" data-test="1000" />',
    '< div  class="foo"     id="bar"   data-test="1000"       />',
    '<div class id data-test>',
    '<id="foo" data-test="1000">',
    '<id data-test>',
    '<select name="foo" id="bar" empty-value-test="" selected disabled><option value="1">Option 1</option></select>'
);

foreach($test as $t) {
    var_dump($t, extract_html_attributes($t));
    echo '<hr>';
}

Esto funciona para mí. También toma en consideración algunos casos finales que he encontrado.

Estoy usando este Regex para el analizador XML

(?<=\s)[^><:\s]*=*(?=[>,\s])

Extraer el elemento:

var buttonMatcherRegExp=/<a[\s\S]*?>[\s\S]*?<\/a>/;
htmlStr=string.match( buttonMatcherRegExp )[0]

Luego use jQuery para analizar y extraer el bit que desee:

$(htmlStr).attr('style') 

mira esto Regex & amp; PHP - aislar atributo src de la etiqueta img

tal vez pueda recorrer el DOM y obtener los atributos deseados. Funciona bien para mí, obteniendo atributos de la etiqueta del cuerpo

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top