2012-08-07 18 views
10

Tengo una expresión regular que coincide para un sistema de plantillas, que desafortunadamente parece apagarse apache (se está ejecutando en Windows) en algunas búsquedas modestamente triviales. Investigué el problema y hay algunas sugerencias para aumentar el tamaño de la pila, etc., pero ninguna de ellas parece funcionar, y realmente no me gusta tratar con tales problemas aumentando los límites, ya que generalmente solo empujan el error hacia el futuro.PHP regex estrellarse apache

¿De todos modos alguna idea sobre cómo modificar la expresión regular para que sea menos probable que se estropee?

La idea es atrapar el bloque más interno (en este caso {block:test}This should be caught first!{/block:test}) que luego voy a volver a colocar las etiquetas de inicio/finalización y volver a ejecutar todo a través de la expresión regular hasta que no queden bloques.

Regex:

~(?P<opening>{(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)})(?P<contents>(?:(?!{/?block:[0-9a-z-_]+}).)*)(?P<closing>{/block:\3})~ism 

plantilla de la muestra:

<div class="f_sponsors s_banners"> 
    <div class="s_previous">&laquo;</div> 
    <div class="s_sponsors"> 
     <ul> 
      {block:sponsors} 
      <li> 
       <a href="{var:url}" target="_blank"> 
        <img src="image/160x126/{var:image}" alt="{var:name}" title="{var:name}" /> 
       </a> 
      {block:test}This should be caught first!{/block:test} 
      </li> 
      {/block:sponsors} 
     </ul> 
    </div> 
    <div class="s_next">&raquo;</div> 
</div> 

Es una posibilidad remota, supongo. :(

+4

extraño como suena, una vez tuve una expresión regular que mantuvo comiendo una instancia de Apache en particular y la bandera 'S' (* mayúscula! *) lo arregló. Supuse que era una pérdida de memoria no reportada o algo así y el proceso de estudio hizo que se evitara. Posesión remota, pero vale la pena intentarlo, diría ... – DaveRandom

+1

@DaveRandom, tuve el mismo problema una vez, con la misma solución! Veamos si eso lo hace para el OP –

+2

Otro pensamiento que ocurre es que escapando de los caracteres '{}' literales en la expresión regular podría ayudar. Técnicamente, son metacaracteres y, aunque PCRE parece ser bastante indulgente con las llaves, no le queda mucho trabajo por hacer si se le escapa correctamente. ¿Por qué usar grupos de captura con nombre y no usar los nombres en las referencias? '/ block: \ 3' =>'/block :(? P = name) '. Esto es especialmente cierto para su expresión regular ya que '' es opcional, en cuyo caso '' será '\ 2', no' \ 3' – DaveRandom

Respuesta

4

prueba este:

'~(?P<opening>\{(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)\})(?P<contents>[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*)(?P<closing>\{/block:(?P=name)\})~i' 

O, en forma legible:

'~(?P<opening> 
    \{ 
    (?P<inverse>[!])? 
    block: 
    (?P<name>[a-z0-9\s_-]+) 
    \} 
) 
(?P<contents> 
    [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* 
) 
(?P<closing> 
    \{ 
    /block:(?P=name) 
    \} 
)~ix' 

La parte más importante está en el (?P<contents>..) grupo:

[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* 

Para empezar, el único personaje que nos interesa es el corsé de apertura, por lo que podemos sorber cualquier otro personaje con [^{]*. Solo después de ver un { comprobamos si es el comienzo de una etiqueta {/block}. Si no es así, seguimos consumiéndolo y comenzamos a buscar el siguiente, y lo repetimos según sea necesario.

Usando RegexBuddy, probé cada expresión regular colocando el cursor al comienzo de la etiqueta {block:sponsors} y la depuración. Luego eliminé la llave de cierre de la etiqueta de cierre {/block:sponsors} para forzar una coincidencia fallida y la depuré de nuevo. Su expresión regular tomó 940 pasos para tener éxito y 2265 pasos para fallar. El mío dio 57 pasos para tener éxito y 83 pasos para fallar.

En una nota lateral, eliminé el modificador s porque porque no estoy usando el punto (.), y el modificador m porque nunca fue necesario. También utilicé la referencia inversa nombrada (?P=name) en lugar de \3 según la excelente sugerencia de @ DaveRandom. Y escapé de todas las llaves ({ y }) porque me resulta más fácil leer de esa manera.


EDIT: Si desea hacer coincidir la más interna bloque de llamada, cambie la porción media de la expresión regular de esto:

(?P<contents> 
    [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* 
) 

... a esto (según lo sugerido por @ Kobi en su comentario):

(?P<contents> 
    [^{]*(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*)* 
) 

Originalmente, el grupo (?P<opening>...) agarraba la primera etiqueta de apertura se saw, entonces el grupo (?P<contents>..) consumiría cualquier cosa, incluidas otras etiquetas, siempre que no fuera la etiqueta de cierre para coincidir con la encontrada por el grupo (?P<opening>...). (A continuación, el grupo (?P<closing>...) seguirá adelante y consumir eso.)

Ahora, el grupo (?P<contents>...) se niega a igualar cualquier etiqueta, abrir o cerrar (nótese el /? al principio), no importa cuál es el nombre. Por lo tanto, la expresión regular inicialmente comienza a coincidir con la etiqueta {block:sponsors}, pero cuando encuentra la etiqueta {block:test}, abandona esa coincidencia y vuelve a buscar una etiqueta de apertura. Comienza de nuevo en la etiqueta {block:test}, esta vez completando con éxito la coincidencia cuando encuentra la etiqueta de cierre {/block:test}.

Parece ineficaz describirlo así, pero en realidad no es así. El truco que describí antes, sorber los no brackets, ahoga el efecto de estos inicios en falso. Donde hacía una búsqueda negativa en casi todas las posiciones, ahora solo está haciendo una cuando encuentra un {.Incluso se puede utilizar cuantificadores posesivos, como se sugirió como @godspeedlee:

(?P<contents> 
    [^{]*+(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*+)*+ 
) 

... porque usted sabe que nunca va a consumir todo lo que se tendrá que dar de nuevo más tarde. Eso aceleraría un poco las cosas, pero no es realmente necesario.

+0

He actualizado la pregunta un poco ya que solo atrapa el bloque más externo. sin embargo, el suyo todavía no requiere la porción ~ sm, ¡así que creo que está en el camino correcto! – Meep3D

+2

¡Muy bonito! Yo * creo * Entiendo lo que quiere el OP ... Creo que por "bloque anidado" quieren decir bloques anidados de cualquier nombre, no solo del mismo nombre, que se reemplazan iterativamente. entonces '{1} {2} {/ 2} {/ 1}' debería capturar '2' antes de' 1'. Si ese es el caso, puede cambiar fácilmente la parte central, de '[^ {] * (?: \ {(?!/Block :(? P = name) \}) [^ {] *) *' to '[^ {] * (?: \ {(?! /? bloque: [a-z0-9 \ s _-] + \}) [^ {] *) *' - http://regexr.com?31qsf – Kobi

+0

Problema similar que coincide con HTML ] *)?> (? :(?!).) * # s' se colgó mientras' # # s' trabajado. Aunque agregar 'U' modificador ungreedy también evitó crash; No estoy seguro de por qué, ya que pensé que causaba más retroceso e ineficiencia. Si no, también podría haber escrito '# # Us' y terminar con eso... – Jake

4

usted podría utilizar atomic group: (?>...) o possessive quantifiers: ?+ *+ ++.. para suprimir/limitar el retroceso y la velocidad de juego por unrolling loop técnica Mi solución:..

\{block:(\w++)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}

He probado de http://regexr.com?31p03

partido {block:sponsors}...{/block:sponsors}:
\{block:(sponsors)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb3

partido {block:test}...{/block:test}:
\{block:(test)\}([^<{]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb6

la otra solución:
en el código fuente PCRE, puede eliminar el comentario de config.h:
/* #undef NO_RECURSE */

siguiente texto de la copia de config.h :
PCRE utiliza llamadas de funciones recursivas para manejar el seguimiento de retroceso durante la coincidencia. Esto a veces puede ser un problema en sistemas que tienen pilas de tamaño limitado. Defina NO_RECURSE para obtener una versión que no use recursividad en la función match(); en su lugar crea su propia pila mediante steam utilizando pcre_recurse_malloc() para obtener memoria del montón.

o puede cambiar pcre.backtrack_limit y pcre.recursion_limit de php.ini (http://www.php.net/manual/en/pcre.configuration.php)

+0

Sin embargo, cuando coloca un bloque dentro de un bloque, solo atrapa el bloque más externo, en lugar del bloque más interno. ¡He actualizado la pregunta de alguna manera! – Meep3D

4

¿La solución tiene que ser una sola expresión regular? Un enfoque más eficiente podría ser simplemente buscar la primera aparición de {/block: (que podría ser una simple búsqueda de cadenas o una expresión regular) y luego buscar hacia atrás desde ese punto para encontrar su etiqueta de apertura correspondiente, reemplazar el lapso de manera apropiada y repetir hasta que no más bloques. Si cada vez que busca la primera etiqueta de cierre comenzando desde la parte superior de la plantilla, eso le dará el bloque más anidado.

El espejo algoritmo de imagen funcionaría igual de bien - buscar la última etiqueta de apertura y luego buscar hacia adelante desde allí por la correspondiente etiqueta de cierre:

<?php 

$template = //... 

while(true) { 
    $last_open_tag = strrpos($template, '{block:'); 
    $last_inverted_tag = strrpos($template, '{!block:'); 
    // $block_start is the index of the '{' of the last opening block tag in the 
    // template, or false if there are no more block tags left 
    $block_start = max($last_open_tag, $last_inverted_tag); 
    if($block_start === false) { 
    // all done 
    break; 
    } else { 
    // extract the block name (the foo in {block:foo}) - from the character 
    // after the next : to the character before the next }, inclusive 
    $block_name_start = strpos($template, ':', $block_start) + 1; 
    $block_name = substr($template, $block_name_start, 
     strcspn($template, '}', $block_name_start)); 

    // we now have the start tag and the block name, next find the end tag. 
    // $block_end is the index of the '{' of the next closing block tag after 
    // $block_start. If this doesn't match the opening tag something is wrong. 
    $block_end = strpos($template, '{/block:', $block_start); 
    if(strpos($template, $block_name.'}', $block_end + 8) !== $block_end + 8) { 
     // non-matching tag 
     print("Non-matching tag found\n"); 
     break; 
    } else { 
     // now we have found the innermost block 
     // - its start tag begins at $block_start 
     // - its content begins at 
     // (strpos($template, '}', $block_start) + 1) 
     // - its content ends at $block_end 
     // - its end tag ends at ($block_end + strlen($block_name) + 9) 
     // [9 being the length of '{/block:' plus '}'] 
     // - the start tag was inverted iff $block_start === $last_inverted_tag 
     $template = // do whatever you need to do to replace the template 
    } 
    } 
} 

echo $template;