it-swarm.com.de

Regulärer Ausdruck zum Extrahieren von Tag-Attributen

Ich versuche, die Attribute eines Ankertags (<a>) zu extrahieren. Bisher habe ich diesen Ausdruck:

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

was funktioniert für strings wie

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

und (einfache Anführungszeichen)

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

aber nicht für einen String ohne Anführungszeichen:

<a href=test.html class=xyz>

Wie kann ich meinen regulären Ausdruck ändern, damit er mit Attributen ohne Anführungszeichen funktioniert? Oder gibt es einen besseren Weg, das zu tun?

Vielen Dank!

Update: Vielen Dank für all die guten Kommentare und Ratschläge. Eines habe ich nicht erwähnt: Ich muss leider Code korrigieren/modifizieren, der nicht von mir selbst geschrieben wurde. Und es gibt keine Zeit/Geld, um dieses Zeug von Grund auf neu zu schreiben.

45
splattne

Wenn Sie ein Element wie haben

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

dieser Regex kann verwendet werden, um nacheinander jeden Attributnamen und -wert zu finden

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

Aufgetragen auf:

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

es würde ergeben:

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

Hinweis: Dies funktioniert nicht mit numerischen Attributwerten, z. <div id="1"> funktioniert nicht.

83
VonC

Obwohl der Ratschlag, HTML nicht über regexp zu analysieren, gültig ist, haben wir hier einen Ausdruck, der so ziemlich das macht, was Sie gefragt haben:

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

"Aber warte", könnte man sagen. "Was ist mit * Kommentaren?!?!" Okay, dann können Sie den . im Nicht-Backtracking-Abschnitt durch Folgendes ersetzen: (Er behandelt auch CDATA-Abschnitte.)

(?:[^<]|<[^!]|<![^-\[]|<!\[(?!CDATA)|<!\[CDATA\[.*?\]\]>|<!--(?:[^-]|-[^-])*-->)
  • Wenn Sie unter Perl 5.10 eine Substitution durchführen wollten (und ich denke, PCRE), können Sie \K direkt vor den Attributnamen setzen und müssen sich nicht darum kümmern, all das aufzunehmen, was Sie überspringen möchten. 
22
Axeman

Antwort des Token-Mantras: Sie sollten nicht mit regulären Ausdrücken Tweak/Modify/Harvest/html/xml erstellen. 

es gibt zu viele Eckpunkte wie\'und\", die berücksichtigt werden müssen. Sie sind viel besser dran, wenn Sie einen richtigen DOM-Parser, XML-Parser oder eines der vielen anderen bewährten Tools für diesen Job verwenden eigene erfinden. 

Es ist mir egal, welche Sie verwenden, solange sie erkannt, getestet und verwendet werden. 

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

Sie können nicht denselben Namen für mehrere Captures verwenden. Daher können Sie keinen Quantifizierer für Ausdrücke mit benannten Captures verwenden.

Benutze also keine benannten Captures:

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

Oder verwenden Sie den Quantifizierer nicht für diesen Ausdruck:

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

Dies erlaubt auch Attributwerte wie bar=' baz='quux:

foo="bar=' baz='quux"

Nun, der Nachteil ist, dass Sie die führenden und nachgestellten Anführungszeichen anschließend entfernen müssen.

10
Gumbo

Nur um mit allen anderen übereinzustimmen: HTML nicht mit Regexp analysieren.

Es ist nicht möglich, einen Ausdruck zu erstellen, der Attribute für einen korrekten Teil von HTML auswählt, unabhängig von den möglichen fehlerhaften Varianten. Ihr Regex ist schon ziemlich unlesbar, auch wenn Sie versuchen, mit dem ungültigen Mangel an Anführungszeichen fertig zu werden. jagen Sie weiter in den Horror des realen HTML-Codes, und Sie werden mit einem unverwischbaren Klumpen unzuverlässiger Ausdrücke verrückt.

Es gibt bereits vorhandene Bibliotheken, um defektes HTML-Dokument zu lesen oder in gültiges XHTML zu korrigieren, das Sie dann leicht mit einem XML-Parser verschlingen können. Benutze sie.

9
bobince

PHP (PCRE) und Python

Einfache Attributextraktion ( Siehe es funktioniert ):

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

Oder mit der Tag-Eröffnungs-/Schließungsüberprüfung, dem Abrufen des Tag-Namens und dem Escape von Kommentaren. Dieser Ausdruck sieht nicht Anführungszeichen/Anführungszeichen, einfache/doppelte Anführungszeichen, Escape-Anführungszeichen in Attributen, Leerzeichen um Gleichheitszeichen, unterschiedliche Anzahl von Attributen, Prüfung nur auf Attribute in Tags und verschiedene Anführungszeichen innerhalb eines Attributwerts vor. ( Sieh es funktioniert ):

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

(Funktioniert besser mit den "Gisx" -Flaggen.)


Javascript

Da Javascript reguläre Ausdrücke keine Rückblicke unterstützen, werden die meisten Funktionen der vorherigen Ausdrücke, die ich vorschlage, nicht unterstützt. Für den Fall, dass dies zu den Bedürfnissen von jemandem passt, können Sie diese Version ausprobieren. ( Sieh es funktioniert ).

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

splattne

Die @VonC-Lösung funktioniert teilweise, es besteht jedoch ein Problem, wenn das Tag eine Mischung aus nicht zitierten und nicht zitierten Namen hatte

Dieser arbeitet mit gemischten Attributen

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

um es auszuprobieren

<?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( "@[email protected]", $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( "@[email protected]", $code, $ms);

var_dump( $ms );

$ ms würde dann Schlüssel und Werte für das 2. und 3. Element enthalten.

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

Dies ist mein bestes RegEx, um Eigenschaften im HTML-Tag zu extrahieren:

# Trimmen Sie die Übereinstimmung innerhalb der Anführungszeichen (einfach oder doppelt).

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

# Ohne Ordnung

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

Pros:

  • Sie können den Inhalt innerhalb von Anführungszeichen zuschneiden.
  • Stimmen Sie alle Sonderzeichen ASCII in den Anführungszeichen ab.
  • Wenn Sie title = "Du bist mein" hast, ist das RegEx nicht kaputt

Nachteile:

  • Es gibt 3 Gruppen zurück; zuerst die Eigenschaft, dann das Zitat ("| ') und am Ende die Eigenschaft innerhalb der Anführungszeichen, d. h .: <div title="You're"> das Ergebnis ist Gruppe 1: Titel, Gruppe 2:", Gruppe 3: Sie sind.

Dies ist das Online-RegEx-Beispiel: https://regex101.com/r/aVz4uG/13



Normalerweise verwende ich dieses RegEx, um die HTML-Tags zu extrahieren.

Ich empfehle das, wenn Sie keinen Tag-Typ wie <div, <span usw. verwenden.

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

Zum Beispiel:

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

Dies ist das Online-RegEx-Beispiel: https://regex101.com/r/aVz4uG/15

Der Fehler in diesem RegEx ist:

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

In diesem Tag:

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

Gibt <div '> zurück, sollte jedoch keine Übereinstimmung zurückgeben:

Match:  <div '>

Um dies zu "lösen", entfernen Sie das [^/]+?-Muster:

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


Die Antwort # 317081 ist gut, stimmt aber nicht mit den folgenden Fällen überein:

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

Dies ist die Verbesserung:

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

vs

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

Vermeiden Sie die Leerzeichen zwischen gleichem Signal: (\ S +) \ s * = \ s * ((?: ...

Ändern Sie das letzte + und. für: | [> "'])) ? [^"'] *) ​​["']?

Dies ist das Online-RegEx-Beispiel: https://regex101.com/r/aVz4uG/8

4

so etwas könnte hilfreich sein

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

Ich schlage vor, dass Sie HTML Tidy verwenden, um HTML in XHTML zu konvertieren, und dann einen geeigneten XPath-Ausdruck verwenden, um die Attribute zu extrahieren.

2
activout.se

Wenn Sie in .NET sind, empfehle ich das HTML-Agility-Paket, das selbst mit fehlerhaftem HTML sehr robust ist.

Dann können Sie XPath verwenden.

2
Andrew Bullock

Tags und Attribute in HTML haben die Form

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

Um Attribute abzugleichen, benötigen Sie einen regulären Ausdruck attr, der eines der vier Formulare findet. Dann müssen Sie sicherstellen, dass nur Übereinstimmungen in HTML-Tags gemeldet werden. Angenommen, Sie haben den richtigen regulären Ausdruck, dann wäre der gesamte reguläre Ausdruck:

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

Der Lookahead stellt sicher, dass nur andere Attribute und das schließende Tag dem Attribut folgen. Ich verwende den folgenden regulären Ausdruck für attr:

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

Unwichtige Gruppen werden nicht erfasst. Die erste passende Gruppe $1 gibt den Namen des Attributs an, der Wert ist einer von $2oder $3 oder $4. Ich benutze $2$3$4, um den Wert zu extrahieren. Der endgültige reguläre Ausdruck ist

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

Hinweis: Ich habe alle unnötigen Gruppen im Lookahead entfernt und alle verbleibenden Gruppen nicht erfasst.

1

Wenn Sie allgemein sein wollen, müssen Sie die genaue Angabe eines Tags wie hier betrachten. Aber selbst wenn, wenn Sie Ihre perfekte Regulierung durchführen, was ist, wenn Sie HTML falsch geschrieben haben?

Ich würde vorschlagen, eine Bibliothek zum Parsen von HTML zu verwenden, abhängig von der Sprache, mit der Sie arbeiten: z. wie Pythons schöne Suppe.

1
Piotr Lesnicki

Das funktioniert für mich. Es berücksichtigt auch einige Endfälle, die ich erlebt habe.

Ich verwende dieses Regex für XML-Parser

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

Ich würde die Strategie überdenken, nur einen einzelnen regulären Ausdruck zu verwenden. Sicher, es ist ein schönes Spiel, wenn man einen einzigen regulären Ausdruck findet, der alles macht. Aber in Bezug auf die Wartungsfähigkeit schießen Sie sich mit beiden Füßen.

0
innaM

Ich brauchte das auch und schrieb eine Funktion zum Parsen von Attributen, die Sie hier herunterladen können:

https://Gist.github.com/4153580

(Hinweis: Regex wird nicht verwendet.)

0
Furkan Mustafa

Ich habe eine PHP -Funktion erstellt , die Attribute von beliebigen HTML-Tags extrahieren kann. Es kann auch Attribute wie disabled behandeln, die keinen Wert haben, und es kann auch feststellen, ob das Tag ein eigenständiges Tag ist (kein schließendes Tag) oder nicht (ein schließendes Tag), indem das Ergebnis content geprüft wird:

/*! 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;
}

Testcode

$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