Ignorar las tags html en preg_replace

¿Cómo ignoro las tags html en este preg_replace? Tengo una función foreach para una búsqueda, por lo que si alguien busca “apple span”, el preg_replace también aplica un span al span y el html se rompe:

preg_replace("/($keyword)/i","$1",$str); 

¡Gracias por adelantado!

Supongo que debe hacer su función basada en DOMDocument y DOMXPath en lugar de usar expresiones regulares. Incluso aquellos son bastante poderosos, te encuentras con problemas como el que describes que no son (siempre) fáciles y robustos de resolver con expresiones regulares.

El dicho general es: no analizar HTML con expresiones regulares.

Es una buena regla a tener en cuenta y, aunque como con cualquier regla, no siempre se aplica, vale la pena tomar una decisión al respecto.

XPath le permite encontrar todos los textos que contienen los términos de búsqueda dentro de los textos, ignorando todos los elementos XML.

Entonces solo necesitas envolver esos textos en el y listo.

Editar: finalmente un código;)

En primer lugar, utiliza xpath para localizar elementos que contienen el texto de búsqueda. Mi consulta se ve así, esto podría estar mejor escrito, no soy un super xpath pro:

 '//*[contains(., "'.$search.'")]/*[FALSE = contains(., "'.$search.'")]/..' 

$search contiene el texto para buscar, que no contiene ningún carácter " (comillas) (esto lo rompería, consulte Limpieza / desinfección de los atributos xpath para una solución si necesita presupuestos).

Esta consulta devolverá todos los padres que contengan los nodos de texto que juntos constituirán una cadena que contiene su término de búsqueda.

Como tal lista no es fácil de seguir tal como está, creé una clase TextRange que representa una lista de nodos DOMText . Es útil hacer operaciones de cadena en una lista de nodos de texto como si fueran una cadena.

Este es el esqueleto básico de la rutina:

 $str = '...'; # some XML $search = 'text that span'; printf("Searching for: (%d) '%s'\n", strlen($search), $search); $doc = new DOMDocument; $doc->loadXML($str); $xp = new DOMXPath($doc); $anchor = $doc->getElementsByTagName('body')->item(0); if (!$anchor) { throw new Exception('Anchor element not found.'); } // search elements that contain the search-text $r = $xp->query('//*[contains(., "'.$search.'")]/*[FALSE = contains(., "'.$search.'")]/..', $anchor); if (!$r) { throw new Exception('XPath failed.'); } // process search results foreach($r as $i => $node) { $textNodes = $xp->query('.//child::text()', $node); // extract $search textnode ranges, create fitting nodes if necessary $range = new TextRange($textNodes); $ranges = array(); while(FALSE !== $start = strpos($range, $search)) { $base = $range->split($start); $range = $base->split(strlen($search)); $ranges[] = $base; }; // wrap every each matching textnode foreach($ranges as $range) { foreach($range->getNodes() as $node) { $span = $doc->createElement('span'); $span->setAttribute('class', 'search_hightlight'); $node = $node->parentNode->replaceChild($span, $node); $span->appendChild($node); } } } 

Para mi ejemplo XML:

   This is some text that span across a page to search in. and more text that span  

Produce el siguiente resultado:

   This is some text that span across a page to search in. and more text that span  

Esto muestra que esto incluso permite encontrar texto que se distribuye entre varias tags. Eso no es tan fácil de hacer con expresiones regulares.

Aquí encontrarás el código completo: http://codepad.viper-7.com/U4bxbe (incluida la clase TextRange que he extraído del ejemplo de respuestas).

No funciona correctamente en el teclado de víbora debido a una versión LIBXML anterior que está usando el sitio. Funciona bien para mi versión LIBXML 20707. Creé una pregunta relacionada sobre este tema: orden de resultados de consultas XPath .

Una nota de advertencia: este ejemplo utiliza la búsqueda de cadenas binarias ( strpos ) y los desplazamientos relacionados para dividir los nodos de texto con la función DOMText::splitText . Eso puede provocar compensaciones incorrectas, ya que las funciones necesitan el desplazamiento de caracteres UTF-8. El método correcto es usar mb_strpos para obtener el valor basado en UTF-8 .

El ejemplo funciona de todos modos porque solo utiliza US-ASCII que tiene los mismos desplazamientos que UTF-8 para los datos de ejemplo.

Para una situación de la vida real, la cadena $search debe estar codificada en UTF-8 y debe usarse strpos lugar de strpos :

  while(FALSE !== $start = mb_strpos($range, $search, 0, 'UTF-8'))