Domanda

Sto cercando di estrarre gli attributi di un tag di ancoraggio (<a>).Finora ho questa espressione:

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

che funziona per stringhe come

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

e (virgolette singole)

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

ma non per una stringa senza virgolette:

<a href=test.html class=xyz>

Come posso modificare la mia espressione regolare facendola funzionare con attributi senza virgolette?O c'è un modo migliore per farlo?

Grazie!

Aggiornamento: Grazie per tutti i buoni commenti e consigli finora.C'è una cosa che non ho menzionato:Purtroppo devo patchare/modificare il codice non scritto da me.E non c’è né tempo né denaro per riscrivere queste cose dal basso verso l’alto.

È stato utile?

Soluzione

Se hai un elemento come

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

questa regex potrebbe essere usata per trovare successivamente ogni nome e valore di ogni attributo

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

Applicato su:

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

produrrebbe:

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

Nota: non funziona con valori di attributo numerici, ad es. <div id="1"> non funzionerà.

Altri suggerimenti

Sebbene il consiglio di non analizzare l'HTML tramite regexp sia valido, ecco un'espressione che fa praticamente quello che hai chiesto:

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

" Ma aspetta, " potresti dire. " Che dire di * commenti?!?! " Bene, allora puoi sostituire . nella sezione non backtracking con: (Gestisce anche sezioni CDATA.)

(?:[^<]|<[^!]|<![^-\[]|<!\[(?!CDATA)|<!\[CDATA\[.*?\]\]>|<!--(?:[^-]|-[^-])*-->)
  • Inoltre, se si desidera eseguire una sostituzione in Perl 5.10 (e penso che PCRE), è possibile mettere \K subito prima del nome dell'attributo e non doversi preoccupare di acquisire tutto ciò che si desidera ignorare.

Risposta al Mantra simbolico:non dovresti modificare/modificare/raccogliere/o produrre in altro modo html/xml utilizzando l'espressione regolare.

ci sono anche condizionali minimi come \' e \" di cui bisogna tenere conto.Faresti molto meglio usando un parser DOM, un parser XML o una delle tante altre dozzine di strumenti provati e testati per questo lavoro invece di inventarne uno tuo.

Non mi interessa davvero quale usi, purché sia ​​​​riconosciuto, testato e tu ne usi 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. 

Giusto per concordare con tutti gli altri: non analizzare HTML usando regexp.

Non è possibile creare un'espressione che selezionerà gli attributi anche per un corretto pezzo di HTML, non importa tutte le possibili varianti malformate. Il tuo regexp è già praticamente illeggibile anche senza cercare di far fronte alla mancanza di virgolette non valida; insegui ulteriormente l'orrore dell'HTML del mondo reale e ti farà impazzire con una massa non sostenibile di espressioni inaffidabili.

Esistono librerie esistenti per leggere codice HTML non funzionante o correggerlo in XHTML valido che puoi facilmente divorare con un parser XML. Usali.

Non è possibile utilizzare lo stesso nome per più acquisizioni. Pertanto non è possibile utilizzare un quantificatore sulle espressioni con acquisizioni denominate.

Quindi, o don & # 8217; t usa acquisizioni con nome:

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

O don & # 8217; t usa il quantificatore su questa espressione:

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

Ciò consente anche valori di attributo come bar=' baz='quux:

foo="bar=' baz='quux"

Bene, lo svantaggio sarà che dovrai rimuovere le virgolette iniziali e finali in seguito.

PHP (PCRE) e Python

Semplice estrazione degli attributi ( Guardalo funzionare ):

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

O con verifica apertura / chiusura tag, recupero nome tag e escape commenti. Questa espressione prevede virgolette singole / doppie non quotate / tra virgolette, virgolette di escape all'interno degli attributi, spazi attorno ai segni di uguale, numero diverso di attributi, controllo solo degli attributi all'interno dei tag e gestione di virgolette diverse all'interno di un valore di attributo. ( Guardalo funzionare ):

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

(Funziona meglio con le " gisx " flags.)


JavaScript

Dato che Javascript le espressioni regolari non supportano i look-behind, non supporteranno la maggior parte delle funzioni delle espressioni precedenti che propongo. Ma nel caso in cui potesse soddisfare le esigenze di qualcuno, potresti provare questa versione. ( Guardalo funzionare ).

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

splattne,

La soluzione @VonC funziona in parte ma c'è qualche problema se il tag aveva un misto di non quotato e quotato

Questo funziona con attributi misti

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

per provarlo

<?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 conterrà quindi chiavi e valori sul 2o e 3o elemento.

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

Questo è il mio RegEx migliore per estrarre le proprietà nel tag HTML:

# Taglia la corrispondenza all'interno delle virgolette (singole o doppie)

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

# Senza rifinitura

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

Pro:

  • Puoi tagliare il contenuto tra virgolette.
  • Corrisponde a tutti i caratteri speciali ASCII all'interno delle virgolette.
  • Se hai title = " Sei mio " il RegEx non si rompe

Contro:

  • Restituisce 3 gruppi; prima la proprietà, quindi la citazione (" | ') e alla fine la proprietà all'interno delle virgolette ovvero: <div title="You're"> il risultato è Gruppo 1: titolo, Gruppo 2: " ;, Gruppo 3: Sei.

Questo è l'esempio RegEx online: https://regex101.com/r/aVz4uG/13



Normalmente utilizzo questo RegEx per estrarre i tag HTML:

Lo consiglio se non si utilizza un tipo di tag come <div, <span, ecc.

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

Ad esempio:

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

Questo è l'esempio RegEx online: https://regex101.com/r/aVz4uG/15

Il bug in questo RegEx è:

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

In questo tag:

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

Restituisce <div '> ma non dovrebbe restituire alcuna corrispondenza:

Match:  <div '>

A " risolvere " questo rimuove il [^/]+? modello:

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


La risposta # 317081 è valida ma non corrisponde correttamente a questi casi:

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

Questo è il miglioramento:

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

vs

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

Evita gli spazi tra segnale uguale: (\ S +) \ s * = \ s * ((:? ...

Cambia l'ultimo + e. per: | [Gt &; & Quot; '])) [^ quot &;?'] * ) [quot; '&]

?

Questo è l'esempio RegEx online: https://regex101.com/r/aVz4uG/8

qualcosa del genere potrebbe essere utile

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

Ti suggerisco di utilizzare HTML Tidy per convertire l'HTML in XHTML e quindi utilizzare un XPath adatto espressione per estrarre gli attributi.

Se vuoi essere generale, devi guardare la specifica precisa del tag a, come qui . Ma anche con quello, se fai la tua regexp perfetta, cosa succede se hai html malformato?

Suggerirei di consultare una libreria per analizzare html, a seconda della lingua con cui lavori: ad es. come Beautiful Soup di Python.

Se sei in .NET, ti consiglio il pacchetto di agilità HTML, molto robusto anche con HTML non valido.

Quindi puoi usare XPath.

Riconsidererei la strategia per usare solo una singola espressione regolare. Sicuramente è un bel gioco trovare una singola espressione regolare che faccia tutto. Ma in termini di manutenibilità stai per spararti in entrambi i piedi.

I tag e gli attributi in HTML hanno la forma

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

Per abbinare gli attributi, è necessario un regex attr che trova uno dei quattro moduli. Quindi devi assicurarti che solo le corrispondenze siano riportate nei tag HTML. Supponendo che tu abbia la regex corretta, la regex totale sarebbe:

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

Il lookahead assicura che solo altri attributi e il tag di chiusura seguano l'attributo. Uso la seguente espressione regolare per $1:

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

I gruppi non importanti non vengono acquisiti. Il primo gruppo corrispondente $2 ti dà il nome dell'attributo, il valore è uno di $3 o $4 o $2$3$4. Uso <=> per estrarre il valore. La regex finale è

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

Nota: ho rimosso tutti i gruppi non necessari nel lookahead e reso tutti i gruppi rimanenti non acquisiti.

Ne avevo anche bisogno e ho scritto una funzione per l'analisi degli attributi, puoi ottenerlo da qui:

https://gist.github.com/4153580

(Nota: non usa regex)

Ho creato una funzione PHP che potrebbe estrarre gli attributi di qualsiasi tag HTML. Può anche gestire attributi come disabled che non ha alcun valore e può anche determinare se il tag è un tag autonomo (non ha tag di chiusura) oppure no (ha un tag di chiusura) controllando il content risultato:

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

Codice 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>';
}

Questo funziona per me. Prende anche in considerazione alcuni casi finali che ho riscontrato.

Sto usando questo parser Regex per XML

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

Estrai l'elemento:

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

Quindi utilizzare jQuery per analizzare ed estrarre il bit desiderato:

$(htmlStr).attr('style') 

dai un'occhiata a questo Regex & amp; PHP - isola l'attributo src dal tag img

forse puoi attraversare il DOM e ottenere gli attributi desiderati. Funziona bene per me, ottenendo gli attributi dal body-tag

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top