2010-10-28 15 views
12

necesito para buscar y reemplazar todos los partidos de texto de una manera insensible caso, a menos que el texto se encuentra dentro de una etiqueta de anclaje - por ejemplo:expresión regular/DOMDocument - Partido y reemplazar texto no en un enlace

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

Búsqueda para 'emparejar este texto' solo reemplazaría la primera instancia y la última instancia.

[Editar] Según el comentario de Gordon, es preferible utilizar DOMDocument en esta instancia. No estoy familiarizado con la extensión DOMDocument, y realmente apreciaría algunos ejemplos básicos para esta funcionalidad.

+1

Utilice DOM [como se muestra] (http://stackoverflow.com/questions/4003031/how-to-replace-text-urls-and-exclude-urls-in-html-tags/4037753#4037753) aquí y adaptar – Gordon

+0

¿Cuál es su comportamiento preferido con etiquetas anidadas dentro del ancla, como '

Esto es a link with don't match this text content

'? –

Respuesta

14

Aquí hay una solución segura UTF-8, que no solo funciona con documentos formateados correctamente, sino también con fragmentos de documentos.

El mb_convert_encoding es necesario, porque loadHtml() parece tener un error con la codificación UTF-8 (ver here y here).

El mb_substr está recortando la etiqueta del cuerpo de la salida, de esta manera recupera el contenido original sin ningún marcado adicional.

<?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"); 

Referencias:
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?

leí docenas de respuestas en el tema, así que lo siento si he olvidado a alguien (por favor comente y lo haré agregue el suyo también en este caso).

Gracias por Gordon y todavía para comentar sobre my other answer.

+0

+1 para dar un intento de DOM :) Sin embargo, esto no considera elementos en línea dentro del nodo de texto del elemento ''. Una XPath de '// text() [not (ancestor :: a)]' solo devolverá nodos 'DOMText' fuera de un árbol' '. En realidad, creo que ninguna de las respuestas lo tiene en cuenta. – Gordon

+0

@Gordon ¿Podría proporcionar una cadena de texto para este caso? –

+1

@styu '

Esto es a link with inline content

' - Cuando repite el resultado del // texto, obtendrá todos los nodos de texto en el documento. Solo selecciona aquellos con un elemento directo padre '', pero no aquellos con un elemento '' encima. – 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; 
?> 

De esta manera funciona. Espero que realmente sea sensible a las mayúsculas y minúsculas, así que combine con una letra pequeña.

+0

Lo siento, pero esto no va a funcionar en muchos casos. En este momento, usted está buscando "hacer coincidir este texto", precedido por cualquier carácter excepto '<', '.',' * 'o'> '... –

+0

este código realmente no va a hacer el trabajo. Hay una docena de senarios donde esto dejaría de hacer su trabajo. – Caleb

0

El análisis HTML con expresiones regulares es un gran desafío, y pueden terminar siendo muy complejas y ocupar mucha memoria. Yo diría que la mejor manera es hacer esto:

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

Si su replacement text es algo que podría ocurrir lo contrario, es posible que desee añadir un paso intermedio con algún identificador único.

+0

Enorme desafío es una buena manera de ponerlo :) –

+0

Poco una subestimación, ¿eh? :) Para algunas cosas, es casi imposible. Sin embargo, esta pequeña tarea es casi manejable. –

+0

Buen intento, el "reemplazo de nuevo" evita varias trampas potenciales de esta operación, pero creo que su solución aún fallará en las etiquetas anidadas, las etiquetas que abarcan varias líneas y muchos otros escenarios. La única manera de hacer esto bien va a ser utilizar algo que realmente analiza el DOM. – Caleb

5

prueba este:

$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

Este es el enfoque no recursiva utilizando sin pérdida de velocidad pre-orden de recorrido del árbol 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); y 3 líneas de código después de $dom=new DOMDocument; deben ser capaces de manejar cualquier HTML con formato incorrecto.

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); 

El aspecto negativo asegura que la sustitución solo se produce si la siguiente etiqueta no es un enlace de cierre. Funciona bien con su ejemplo, aunque no funcionará si usa otras etiquetas dentro de sus enlaces.

1

Puede usar PHP Simple HTML DOM Parser. Es similar a DOMDocument, pero en mi opinión es más fácil de usar. Aquí es la alternativa en paralelo 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; 
} 

acabo de perfilado este código contra mi solución DomDocument (bruja imprime la misma salida exacta), y la DomDocument es (como es lógico) de forma más rápida (~ 4 ms contra ~ 77ms).

+0

Alternativas de terceros sugeridas a [SimpleHtmlDom] (http://simplehtmldom.sourceforge.net/) que realmente usan [DOM] (http://php.net/manual/en/book.dom.php) en lugar de String Parsing : [phpQuery] (http://code.google.com/p/phpquery/), [Zend_Dom] (http://framework.zend.com/manual/en/zend.dom.html), [QueryPath] (http://querypath.org/) y [FluentDom] (http://www.fluentdom.org). – Gordon

+0

@Gordon: Creo que todos ellos construyen el DOM mediante el análisis de cadenas (incluyendo DOMDocument). La pregunta es cómo hacen esto (¿arruinan el documento con entidades no deseadas, por ejemplo, o simplemente están haciendo su trabajo). Y la velocidad no es un problema real aquí, porque solo quiere procesar el documento cuando se modifique. De todos modos, gracias por las sugerencias, las investigaré más a fondo. –

+0

@styu todos estos están basados ​​en DOM y DOM usa libxml. – Gordon

Cuestiones relacionadas