2011-07-25 12 views
6

Con expresiones regulares PCRE de PHP, el modo multi-línea (/m) permite ^ y $ para que coincida con el inicio y el final de las líneas (separadas por nuevas líneas) en el texto de origen, así como el inicio y el final de la fuente texto.¿Cómo cambiar lo que PCRE regexp piensa que son nuevas líneas en modo multilínea?

Esto parece funcionar muy bien en Linux con \n (LF) siendo el separador de nueva línea, pero falla en Windows con \r\n (CRLF).

¿Hay alguna manera de cambiar lo que PCRE cree que son nuevas líneas? ¿O quizás permitir que coincida con CRLF o LF de la misma manera que $ coincide con el final de la línea/cadena?

Ejemplo:

$EOL = "\n"; // Linux LF 
$SOURCE_TEXT = "one{$EOL}two{$EOL}three{$EOL}four"; 
if (preg_match('/^two$/m',$SOURCE_TEXT)) { 
    echo 'Found match.'; // <<< RESULT 
} else { 
    echo 'Did not find match!'; 
} 

RESULTADO: El éxito

$EOL = "\r\n"; // Windows CR+LF 
$SOURCE_TEXT = "one{$EOL}two{$EOL}three{$EOL}four"; 
if (preg_match('/^two$/m',$SOURCE_TEXT)) { 
    echo 'Found match.'; 
} else { 
    echo 'Did not find match!'; // <<< RESULT 
} 

RESULTADO: fallar

Respuesta

9

¿Has probado el (*CRLF) y modificadores relacionados? Se detallan en la Wikipedia here (en las opciones Newline/linebreak) y parecen hacer lo correcto en mis pruebas. es decir, '/(*CRLF)^two$/m' debe coincidir con las ventanas \r\n newlines. También (*ANYCRLF) debe coincidir con Linux y Windows, pero no he probado esto.

+1

+1. Funciona para mí: ** [demo] (http://ideone.com/O6xmV) ** –

+2

Sí, esto también funciona para mí (incluso '(* ANYCRLF)') cuando se especifica al comienzo del patrón. Tenga en cuenta que estos modificadores están disponibles desde PCRE 7.3, que [corresponde a PHP 5.2.5] (http://www.php.net/manual/en/pcre.installation.php). – MrWhite

5

Nota: La respuesta sólo es aplicable a las versiones anteriores de PHP, cuando yo lo escribí, no estaba al tanto de las secuencias y modificadores que están disponibles: \R, (*BSR_ANYCRLF) y (*BSR_UNICODE). Ver también la respuesta a: How to replace different newline styles in PHP the smartest way?

En PHP no es posible especificar el carácter de secuencia (s) de nueva línea de patrones de expresiones regulares PCRE. El modificador m solo busca \n, that's documented. Y no hay configuración de tiempo de ejecución disponible para realizar un cambio que sería posible en Perl, pero esa no es una opción con PHP.

que normalmente acaba de modificar la cadena antes de usarlo con preg_match y similares:

$subject = str_replace("\r\n", "\n", $subject); 

Esto podría no ser exactamente lo que está buscando, pero probablemente esto ayuda.

Editar: En cuanto al ejemplo ventanas EOL que ha añadido a su pregunta:

$EOL = "\r\n"; // Windows CR+LF 
$SOURCE_TEXT = "one{$EOL}two{$EOL}three{$EOL}four"; 
if (preg_match('/^two$/m',$SOURCE_TEXT)) { 
    echo 'Found match.'; 
} else { 
    echo 'Did not find match!'; // <<< RESULT 
} 

Esta falla porque en el texto, hay una \r después two. Entonces two no está al final de una línea, hay un carácter adicional, \r antes del final de la línea ($).

El manual de PHP explica claramente que solo \n se considera como el carácter que especifica un final de línea. $ considera \n solamente, así que si está buscando two\r al final de una línea, necesita cambiar su patrón. Esa es la otra opción (en lugar de convertir el texto como se sugirió anteriormente).

+0

La documentación no es particularmente clara al respecto, todo lo que indica es: "Si no hay caracteres" \ n "en una cadena de asunto, o no hay apariciones de^o $ en un patrón, configurar este modificador tiene sin efecto." – MrWhite

+0

Establece claramente que '\ n' es la (única) secuencia de caracteres de nueva línea reflejada por el modificador' m'. – hakre

+0

Agregué algunas explicaciones a la respuesta y el código que ha agregado. – hakre

3

Eso es extraño, no creo que $ (con el modificador m) se preocupe si hay una \n o \r\n como nueva línea.

Una idea para probar esto, agregue \s* antes del $. \s está haciendo coincidir también los caracteres de nueva línea y debe coincidir con el \r antes del \n si este fuera realmente el problema.
Siempre y cuando no haya problema si hay espacios en blanco adicionales al final de la línea, no debería doler.

+0

Bueno, no hubiera pensado que debería importar tampoco, pero mi expresión regular parece fallar coincidiendo con eol '\ r \ n'. El comienzo de la línea coincide con OK. Había asumido que esto era porque el carácter final era '\ r' y no' \ n'? He agregado un ejemplo a mi pregunta que parece mostrar esto. Gracias por la sugerencia '\ s *', eso parece resolver el problema. – MrWhite

0

Todo depende de dónde provienen sus datos: las fuentes externas e incontroladas pueden proporcionar datos bastante desordenados. Una sugerencia para aquellos de ustedes que están tratando de combatir (o al menos trabajar) el problema de emparejar un patrón correctamente al final ($) de cualquier línea en el modo de líneas múltiples (/ m).

<?php 
// Various OS-es have various end line (a.k.a line break) chars: 
// - Windows uses CR+LF (\r\n); 
// - Linux LF (\n); 
// - OSX CR (\r). 
// And that's why single dollar meta assertion ($) sometimes fails with multiline modifier (/m) mode - possible bug in PHP 5.3.8 or just a "feature"(?). 
$str="ABC ABC\n\n123 123\r\ndef def\rnop nop\r\n890 890\nQRS QRS\r\r~-_ ~-_"; 
//   C   3     p   0     _ 
$pat1='/\w$/mi'; // This works excellent in JavaScript (Firefox 7.0.1+) 
$pat2='/\w\r?$/mi'; // Slightly better 
$pat3='/\w\R?$/mi'; // Somehow disappointing according to php.net and pcre.org when used improperly 
$pat4='/\w(?=\R)/i'; // Much better with allowed lookahead assertion (just to detect without capture) without multiline (/m) mode; note that with alternative for end of string ((?=\R|$)) it would grab all 7 elements as expected 
$pat5='/\w\v?$/mi'; 
$pat6='/(*ANYCRLF)\w$/mi'; // Excellent but undocumented on php.net at the moment (described on pcre.org and en.wikipedia.org) 
$n=preg_match_all($pat1, $str, $m1); 
$o=preg_match_all($pat2, $str, $m2); 
$p=preg_match_all($pat3, $str, $m3); 
$r=preg_match_all($pat4, $str, $m4); 
$s=preg_match_all($pat5, $str, $m5); 
$t=preg_match_all($pat6, $str, $m6); 
echo $str."\n1 !!! $pat1 ($n): ".print_r($m1[0], true) 
    ."\n2 !!! $pat2 ($o): ".print_r($m2[0], true) 
    ."\n3 !!! $pat3 ($p): ".print_r($m3[0], true) 
    ."\n4 !!! $pat4 ($r): ".print_r($m4[0], true) 
    ."\n5 !!! $pat5 ($s): ".print_r($m5[0], true) 
    ."\n6 !!! $pat6 ($t): ".print_r($m6[0], true); 
// Note the difference among the three very helpful escape sequences in $pat2 (\r), $pat3 and $pat4 (\R), $pat5 (\v) and altered newline option in $pat6 ((*ANYCRLF)) - for some applications at least. 

/* The code above results in the following output: 
ABC ABC 

123 123 
def def 
nop nop 
890 890 
QRS QRS 

~-_ ~-_ 
1 !!! /\w$/mi (3): Array 
(
    [0] => C 
    [1] => 0 
    [2] => _ 
) 

2 !!! /\w\r?$/mi (5): Array 
(
    [0] => C 
    [1] => 3 
    [2] => p 
    [3] => 0 
    [4] => _ 
) 

3 !!! /\w\R?$/mi (5): Array 
(
    [0] => C 

    [1] => 3 
    [2] => p 
    [3] => 0 
    [4] => _ 
) 

4 !!! /\w(?=\R)/i (6): Array 
(
    [0] => C 
    [1] => 3 
    [2] => f 
    [3] => p 
    [4] => 0 
    [5] => S 
) 

5 !!! /\w\v?$/mi (5): Array 
(
    [0] => C 

    [1] => 3 
    [2] => p 
    [3] => 0 
    [4] => _ 
) 

6 !!! /(*ANYCRLF)\w$/mi (7): Array 
(
    [0] => C 
    [1] => 3 
    [2] => f 
    [3] => p 
    [4] => 0 
    [5] => S 
    [6] => _ 
) 
*/ 
?> 

Por desgracia, no tengo ningún acceso a un servidor con la última versión de PHP - PHP mi local es 5.3.8 y PHP de mi anfitrión pública es la versión 5.2.17.

Cuestiones relacionadas