web-dev-qa-db-fra.com

Expression régulière pour extraire les attributs de balises

J'essaie d'extraire les attributs d'une balise d'ancrage (<a>). Jusqu'à présent, j'ai cette expression:

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

qui fonctionne pour les chaînes comme

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

et (guillemets simples)

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

mais pas pour une chaîne sans guillemets:

<a href=test.html class=xyz>

Comment puis-je modifier mon regex en le faisant fonctionner avec des attributs sans guillemets? Ou y a-t-il une meilleure façon de le faire?

Merci!

Mise à jour: Merci pour tous les bons commentaires et conseils reçus jusqu'à présent. Il y a une chose que je n'ai pas mentionnée: je dois malheureusement corriger/modifier le code non écrit par moi-même. Et il n’ya pas de temps/d’argent pour réécrire ces choses de bas en haut.

45
splattne

Si vous avez un élément comme

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

cette expression rationnelle pourrait être utilisée pour trouver successivement chaque nom et valeur d'attribut

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

Appliqué sur:

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

cela donnerait:

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

Remarque: Ceci ne fonctionne pas avec les valeurs d'attribut numériques, par exemple. <div id="1"> ne fonctionnera pas.

83
VonC

Bien que le conseil de ne pas analyser le code HTML via regexp soit valide, voici une expression qui correspond à peu près à ce que vous avez demandé:

/
   \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;

"Mais attendez", pourriez-vous dire. "Et les * commentaires?!?!" OK, vous pouvez alors remplacer le . dans la section non-backtracking par: (Il gère également les sections CDATA.)

(?:[^<]|<[^!]|<![^-\[]|<!\[(?!CDATA)|<!\[CDATA\[.*?\]\]>|<!--(?:[^-]|-[^-])*-->)
  • De plus, si vous souhaitez exécuter une substitution sous Perl 5.10 (et je pense PCRE), vous pouvez placer \K juste avant le nom de l'attribut sans avoir à vous soucier de la capture de tout ce que vous souhaitez ignorer. 
22
Axeman

Réponse Token Mantra: vous ne devez pas modifier/récolter/ou produire autrement html/xml en utilisant une expression régulière. 

il existe également des conditionnalités majuscules, telles que\'et\", qui doivent être prises en compte. Il vaut mieux utiliser un analyseur DOM, un analyseur XML ou l'un des nombreux autres outils éprouvés pour ce travail. d'inventer le vôtre. 

Je ne me soucie pas vraiment de celui que vous utilisez, tant qu'il est reconnu, testé et que vous en utilisez un. 

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. 
13
Kent Fredric

Vous ne pouvez pas utiliser le même nom pour plusieurs captures. Ainsi, vous ne pouvez pas utiliser de quantificateur pour les expressions avec des captures nommées.

Donc, n’utilisez pas de captures nommées:

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

Ou n'utilisez pas le quantificateur sur cette expression:

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

Cela autorise également des valeurs d'attributs telles que bar=' baz='quux:

foo="bar=' baz='quux"

L’inconvénient sera que vous devrez par la suite enlever les guillemets en début et en fin.

10
Gumbo

Juste pour être d’accord avec tout le monde: ne pas analyser HTML en utilisant regexp.

Il n'est pas possible de créer une expression qui sélectionne des attributs pour un code HTML correct, sans parler de toutes les variantes malformées possibles. Votre expression rationnelle est déjà à peu près illisible, même sans essayer de gérer le manque de guillemets invalide; Chasser plus loin dans l'horreur de HTML réel et vous vous rendrez fou avec un blob impossible à maintenir d'expressions non fiables.

Il existe des bibliothèques pour lire le HTML cassé ou le corriger en XHTML valide que vous pouvez ensuite facilement dévorer avec un analyseur XML. Utilise les.

9
bobince

PHP (PCRE) et Python

Extraction d'attribut simple ( le voir fonctionner ):

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

Ou avec la vérification d'ouverture/fermeture de balise, la récupération du nom de balise et l'échappement de commentaires. Cette expression prévoit les guillemets simples/doubles, les guillemets échappés à l'intérieur d'attributs, les espaces autour des signes d'égalité, un nombre différent d'attributs, la recherche des attributs dans les balises et la gestion de guillemets différents dans une valeur d'attribut. ( le voir fonctionner ):

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

(Fonctionne mieux avec les drapeaux "gisx".)


Javascript

Comme les expressions régulières Javascript ne prennent pas en charge les repères, elle ne prend pas en charge la plupart des fonctionnalités des expressions précédentes que je propose. Mais si cela convient aux besoins de quelqu'un, vous pouvez essayer cette version. ( le voir fonctionner ).

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

splattne,

La solution @VonC fonctionne en partie, mais il y a un problème si la balise comportait un mélange de non cités et de citations.

Celui-ci fonctionne avec des attributs mixtes

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

pour le tester

<?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 contiendrait alors des clés et des valeurs sur les 2ème et 3ème éléments.

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

C'est mon meilleur RegEx pour extraire les propriétés dans les balises HTML:

# Couper la correspondance à l'intérieur des guillemets (simples ou doubles)

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

# Sans trim

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

Avantages:

  • Vous êtes en mesure de couper le contenu à l'intérieur des guillemets.
  • Correspond à tous les caractères spéciaux ASCII à l'intérieur des guillemets.
  • Si vous avez title = "Vous êtes à moi" le RegEx ne casse pas

Les inconvénients:

  • Il retourne 3 groupes; d'abord la propriété, puis la citation ("| ') et à la fin, la propriété à l'intérieur des guillemets i.e .: <div title="You're"> le résultat est Groupe 1: titre, Groupe 2:", Groupe 3: Vous.

Voici l'exemple de RegEx en ligne: https://regex101.com/r/aVz4uG/13



J'utilise normalement ce RegEx pour extraire les balises HTML:

Je le recommande si vous n'utilisez pas un type de balise tel que <div, <span, etc.

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

Par exemple:

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

Voici l'exemple de RegEx en ligne: https://regex101.com/r/aVz4uG/15

Le bogue dans ce RegEx est:

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

Dans cette balise:

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

Retourne <div '> mais ne devrait pas renvoyer de correspondance:

Match:  <div '>

Pour "résoudre" ceci supprimer le modèle [^/]+?:

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


La réponse # 317081 est bonne mais elle ne correspond pas correctement à ces cas:

<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 (=)

C'est l'amélioration:

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

contre

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

Évitez les espaces entre les signaux égaux: (\ S +) \ s * = \ s * ((?: ...

Changer le dernier + et. pour: | [> "'])) ? [^"'] *) ​​["']?

Voici l'exemple de RegEx en ligne: https://regex101.com/r/aVz4uG/8

4

quelque chose comme ça pourrait être utile

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

Je vous suggère d'utiliser HTML Tidy pour convertir le code HTML en XHTML, puis d'utiliser une expression XPath appropriée pour extraire les attributs.

2
activout.se

Si vous êtes dans .NET, je vous recommande le pack d'agilité HTML, très robuste, même avec du HTML mal formé.

Ensuite, vous pouvez utiliser XPath.

2
Andrew Bullock

Si vous voulez être général, vous devez regarder la spécification précise de la balise, comme ici . Mais même avec cela, si vous faites votre regexp parfaite, que se passe-t-il si vous avez du HTML malformé?

Je suggérerais d'aller chercher une bibliothèque pour analyser le langage HTML, en fonction de la langue avec laquelle vous travaillez: par exemple. comme belle soupe de python.

1
Piotr Lesnicki

Cela fonctionne pour moi. Il prend également en compte certains cas finaux que j'ai rencontrés.

J'utilise cet analyseur syntaxique Regex for XML

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

Je reconsidérerais la stratégie consistant à n'utiliser qu'une seule expression régulière. Bien sûr, c’est un jeu sympa de proposer une seule expression régulière qui fasse tout. Mais en termes de maintenance, vous êtes sur le point de vous tirer dans les deux pieds.

0
innaM

J'avais aussi besoin de ça et j'ai écrit une fonction pour analyser les attributs, vous pouvez l'obtenir à partir d'ici:

https://Gist.github.com/4153580

(Remarque: il n'utilise pas de regex)

0
Furkan Mustafa

J'ai créé une fonction PHP qui pourrait extraire les attributs de toutes les balises HTML. Il peut également gérer des attributs tels que disabled qui n'a pas de valeur et déterminer si la balise est une balise autonome (n'a pas de balise de fermeture) ou non (a une balise de fermeture) en vérifiant le résultat 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;
}

Code de test

$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>';
}
0
Taufik Nurrohman