2010-10-28 11 views
12

ho bisogno di trovare e sostituire tutto il testo corrisponde in modo insensibile caso, a meno che il testo sia all'interno di un tag di ancoraggio - per esempio:Regex/DOMDocument - partita e sostituire il testo non in un link

<p>Match this text and replace it</p> 
<p>Don't <a href="/">match this text</a></p> 
<p>We still need to match this text and replace it</p> 

Ricerca per "abbina questo testo" sostituisce solo la prima istanza e l'ultima istanza.

[Modifica] Come per il commento di Gordon, può essere preferibile utilizzare DOMDocument in questa istanza. Non ho alcuna familiarità con l'estensione DOMDocument e apprezzerei davvero alcuni esempi di base per questa funzionalità.

+1

Usa DOM [come mostrato] (http://stackoverflow.com/questions/4003031/how-to-replace-text-urls-and-exclude-urls-in-html-tags/4037753#4037753) qui e adattare – Gordon

+0

Qual è il tuo comportamento preferito con i tag nidificati all'interno dell'ancora, ad esempio '

Questo è a link with don't match this text content

'? –

risposta

14

Ecco una soluzione sicura UTF-8, che funziona non solo con documenti formattati correttamente, ma anche con frammenti di documenti.

Il mb_convert_encoding è necessario, perché loadHtml() sembra avere un bug con la codifica UTF-8 (vedere here e here).

mb_substr sta tagliando il tag body dall'output, in questo modo si ottiene il contenuto originale senza alcun markup aggiuntivo.

<?php 
$html = '<p>Match this text and replace it</p> 
<p>Don\'t <a href="/">match this text</a></p> 
<p>We still need to match this text and replace itŐŰ</p> 
<p>This is <a href="#">a link <span>with <strong>don\'t match this text</strong> content</span></a></p>'; 

$dom = new DOMDocument(); 
// loadXml needs properly formatted documents, so it's better to use loadHtml, but it needs a hack to properly handle UTF-8 encoding 
$dom->loadHtml(mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8")); 

$xpath = new DOMXPath($dom); 

foreach($xpath->query('//text()[not(ancestor::a)]') as $node) 
{ 
    $replaced = str_ireplace('match this text', 'MATCH', $node->wholeText); 
    $newNode = $dom->createDocumentFragment(); 
    $newNode->appendXML($replaced); 
    $node->parentNode->replaceChild($newNode, $node); 
} 

// get only the body tag with its contents, then trim the body tag itself to get only the original content 
echo mb_substr($dom->saveXML($xpath->query('//body')->item(0)), 6, -7, "UTF-8"); 

Riferimenti:
1. find and replace keywords by hyperlinks in an html fragment, via php dom
2. Regex/DOMDocument - match and replace text not in a link
3. php problem with russian language
4. Why Does DOM Change Encoding?

ho letto decine di risposte nel soggetto, quindi mi dispiace se ho dimenticato qualcuno (si prega di commentare e lo farò aggiungi il tuo anche in questo caso).

Grazie per Gordon e fermo per aver commentato my other answer.

+0

@Gordon Potresti fornire una stringa di testo per questo caso? –

+1

@styu '

Questo è a link with inline content

' - Quando si esegue l'iterazione sul risultato di // testo, si otterranno tutti i nodi di testo nel documento. Si individuano solo quelli con un elemento padre diretto '', ma non quelli con un elemento '' al di sopra di quello. – Gordon

0
<?php 
$a = '<p>Match this text and replace it</p> 
<p>Don\'t <a href="/">match this text</a></p> 
<p>We still need to match this text and replace it</p> 
'; 
$res = preg_replace("#[^<a.*>]match this text#",'replacement',$a); 
echo $res; 
?> 

In questo modo funziona. Spero che tu voglia davvero la distinzione tra maiuscole e minuscole, quindi abbinalo a una lettera minuscola

+0

Mi dispiace, ma in molti casi non funzionerà. In questo momento, stai cercando "abbina questo testo", preceduto da qualsiasi carattere tranne "<', '.',' * 'o'> '... –

+0

questo codice non ha intenzione di fare il lavoro. Ci sono una dozzina di senatori in cui questo non riuscirebbe a fare il suo lavoro. – Caleb

0

L'analisi HTML con regex è una grande sfida, e possono facilmente finire per diventare troppo complessi e occupare un sacco di memoria. Direi che il modo migliore è quello di fare questo:

preg_replace('/match this text/i','replacement text'); 
preg_replace('/(<a[^>]*>[^(<\/a)]*)replacement text(.*?<\/a)/is',"$1match this text$3"); 

Se il replacement text è qualcosa che potrebbe verificarsi in caso contrario, si potrebbe desiderare di aggiungere un passaggio intermedio con un po 'identificatore univoco.

+0

Una sfida enorme è un bel modo di metterlo :) –

+0

Un po 'di eufemismo, eh? :) Per alcune cose, è praticamente impossibile. Questo piccolo compito è comunque gestibile. –

+0

Bel tentativo, il "rimpiazzo" evita diverse potenziali insidie ​​di questa operazione, ma penso che la tua soluzione non riuscirà ancora su tag nidificati, tag che si estendono su più righe e molti altri scenari. L'unico modo per farlo correttamente sarà l'utilizzo di qualcosa che effettivamente analizza il DOM. – Caleb

5

provare questo:

$dom = new DOMDocument; 
$dom->loadHTML($html_content); 

function preg_replace_dom($regex, $replacement, DOMNode $dom, array $excludeParents = array()) { 
    if (!empty($dom->childNodes)) { 
    foreach ($dom->childNodes as $node) { 
     if ($node instanceof DOMText && 
      !in_array($node->parentNode->nodeName, $excludeParents)) 
     { 
     $node->nodeValue = preg_replace($regex, $replacement, $node->nodeValue); 
     } 
     else 
     { 
     preg_replace_dom($regex, $replacement, $node, $excludeParents); 
     } 
    } 
    } 
} 

preg_replace_dom('/match this text/i', 'IT WORKS', $dom->documentElement, array('a')); 
3

Questo è l'approccio non ricorsivo stackless utilizzando pre-ordine di attraversamento della struttura DOM.

libxml_use_internal_errors(TRUE); 
    $dom=new DOMDocument('1.0','UTF-8'); 

    $dom->substituteEntities=FALSE; 
    $dom->recover=TRUE; 
    $dom->strictErrorChecking=FALSE; 

    $dom->loadHTMLFile($file); 
    $root=$dom->documentElement; 
    $node=$root; 
    $flag=FALSE; 
    for (;;) { 
     if (!$flag) { 
      if ($node->nodeType==XML_TEXT_NODE && 
       $node->parentNode->tagName!='a') { 
       $node->nodeValue=preg_replace(
        '/match this text/is', 
        $replacement, $node->nodeValue 
      ); 
      } 
      if ($node->firstChild) { 
       $node=$node->firstChild; 
       continue; 
      } 
    } 
    if ($node->isSameNode($root)) break; 
    if ($flag=$node->nextSibling) 
      $node=$node->nextSibling; 
    else 
      $node=$node->parentNode; 
} 
echo $dom->saveHTML(); 

libxml_use_internal_errors(TRUE); e 3 righe di codice dopo $dom=new DOMDocument; dovrebbero essere in grado di gestire qualsiasi HTML errato.

2
$a='<p>Match this text and replace it</p> 
<p>Don\'t <a href="/">match this text</a></p> 
<p>We still need to match this text and replace it</p>'; 

echo preg_replace('~match this text(?![^<]*</a>)~i','replacement',$a); 

Il lookahead negativo garantisce che la sostituzione avvenga solo se il tag successivo non è un collegamento di chiusura. Funziona bene con il tuo esempio, anche se non funzionerà se ti capita di usare altri tag all'interno dei tuoi link.

1

È possibile utilizzare PHP Simple HTML DOM Parser. È simile a DOMDocument, ma a mio parere è più semplice da usare. Ecco l'alternativa in parallelo con Netcoder's DomDocument solution:

function replaceWithSimpleHtmlDom($html_content, $search, $replace, $excludedParents = array()) { 
    require_once('simple_html_dom.php'); 
    $html = str_get_html($html_content); 
    foreach ($html->find('text') as $element) { 
     if (!in_array($element->parent()->tag, $excludedParents)) 
      $element->innertext = str_ireplace($search, $replace, $element->innertext); 
    } 
    return (string)$html; 
} 

ho appena profilato questo codice contro la mia soluzione DomDocument (strega stampa lo stesso uscita esatta), e il DomDocument è (non sorprendentemente) il modo più veloce (~ 4ms contro ~ 77ms).

+0

Alternative di terze parti suggerite a [SimpleHtmlDom] (http://simplehtmldom.sourceforge.net/) che in realtà utilizzano [DOM] (http://php.net/manual/en/book.dom.php) anziché Parsing delle stringhe : [phpQuery] (http://code.google.com/p/phpquery/), [Zend_Dom] (http://framework.zend.com/manual/en/zend.dom.html), [QueryPath] (http://querypath.org/) e [FluentDom] (http://www.fluentdom.org). – Gordon

+0

@Gordon: Penso che tutti stiano costruendo il DOM analizzando le stringhe (incluso DOMDocument). La domanda è: come stanno facendo questo (stanno confondendo il documento con entità indesiderate per esempio, o stanno semplicemente facendo il loro lavoro). E la velocità non è un problema reale qui, perché si desidera elaborare il documento solo quando viene modificato. Comunque, grazie per i suggerimenti, li esaminerò ulteriormente. –

+0

@styu tutti questi sono basati su DOM e DOM utilizza libxml. – Gordon

Problemi correlati