2011-07-25 11 views
6

wyrażeń regularnych PCRE w PHP, tryb multi-line (/m) umożliwia ^ i $ dopasować początek i koniec linii (oddzielone nowej linii) w tekście źródłowym, jak również początek i koniec źródła tekst.Jak zmienić wyrażenie regexp w PCRE są nowe linie w trybie wieloliniowym?

Wygląda na to, że działa świetnie na systemie Linux, gdzie \n (LF) jest separatorem nowej linii, ale kończy się niepowodzeniem w systemie Windows z \r\n (CRLF).

Czy istnieje sposób na zmianę tego, co PCRE uważa za nowe linie? A może pozwolić mu dopasować CRLF lub LF w taki sam sposób, jak $ pasuje do końca linii/łańcucha?

PRZYKŁAD:

$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!'; 
} 

WYNIK: Sukces

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

WYNIK: Fail

Odpowiedz

9

Czy spróbować (*CRLF) i pokrewnych modyfikatory? Są one szczegółowo opisane na Wikipedii here (w dziale Newline/opcje linii łamania) i wydają się postępować właściwie w moich testach. tj. '/(*CRLF)^two$/m' powinien pasować do nowych linii w systemie Windows \r\n. Również (*ANYCRLF) powinien pasować zarówno do Linuksa, jak i Windowsa, ale tego jeszcze nie przetestowałem.

+1

+1. Działa to dla mnie: ** [demo] (http://ideone.com/O6xmV) ** –

+2

Tak, to działa również dla mnie (włączając '(* ANYCRLF)'), gdy jest określone na początku wzorca. Zauważ, że modyfikatory te są dostępne od wersji PCRE 7.3, która [odpowiada PHP 5.2.5] (http://www.php.net/manual/en/pcre.installation.php). – MrWhite

5

Uwaga: Odpowiedź ma zastosowanie tylko do starszych wersji PHP, kiedy ja napisałem to, nie byłem świadom sekwencji i modyfikatorów, które są dostępne: \R, (*BSR_ANYCRLF) i (*BSR_UNICODE). Zobacz także odpowiedź na: How to replace different newline styles in PHP the smartest way?

W PHP nie jest możliwe określenie nowej sekwencji znaków dla szablonów regex PCRE. Modyfikator m szuka tylko \n, that's documented. I nie ma żadnego ustawienia środowiska wykonawczego, które umożliwiłoby zmianę, która byłaby możliwa w perlu, ale nie jest to opcja w PHP.

Zwykle wystarczy zmodyfikować ciąg przed użyciem go z preg_match i tym podobne:

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

To może nie być dokładnie to, czego szukasz, ale prawdopodobnie to pomaga.

Edit: Jeśli chodzi o przykład okna EOL dodanych do twojego pytania:

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

To nie działa, ponieważ w tekście, istnieje \r po two. Tak więc two nie jest na końcu linii, jest dodatkowy znak, \r przed końcem linii ($).

Instrukcja PHP wyraźnie wyjaśnia, że ​​tylko \n jest uważany za znak określający koniec linii. $ rozważa tylko \n, więc jeśli szukasz two\r na końcu linii, musisz zmienić swój wzór. To druga opcja (zamiast konwertowania tekstu, jak sugerowano powyżej).

+0

ja nie dokumentacja jest szczególnie wyraźnie na to, wszystko to wskazuje, to: „Jeśli nie ma żadnych«znaków \ ​​n»w napisu lub brak wystąpienia^lub $ w postaci wzoru, ustawienie to ma modyfikator bez efektu." – MrWhite

+0

Wyraźnie stwierdza, że ​​'\ n' jest (tylko) sekwencją znaków nowej linii odzwierciedloną przez modyfikator' m'. – hakre

+0

Dodałem trochę wyjaśnienia do odpowiedzi również dla kodu, który dodałeś. – hakre

3

Ów dziwny, nie sądzę, że $ (z m modyfikatora) obchodzi jeśli jest \n lub \r\n jako nowej linii.

Pomysł na przetestowanie tego, dodaj \s* przed $. \s dopasowuje również znaki nowego wiersza i powinien być zgodny z \r przed \n, jeśli byłby to naprawdę problem.
Dopóki nie ma problemu, jeśli na końcu linii znajdują się dodatkowe białe znaki, nie powinno to być bolesne.

+0

Cóż, nie sądziłem, że powinno to mieć znaczenie, ale moje wyrażenie regularne nie spełnia wymagań eol '\ r \ n'. Początek linii odpowiada OK. Przyjąłem, że to dlatego, że końcowy znak to '\ r', a nie' \ n'? Dodałem przykład do mojego pytania, które wydaje się to pokazywać. Dzięki za sugestię '\ s *' - rzeczywiście wydaje się, że rozwiązuje problem. – MrWhite

0

Wszystko zależy od tego, gdzie dane pochodzą z - zewnętrznych źródeł niekontrolowanych i może dostarczać dane dość niechlujnie. Wskazówka dla tych z was, którzy próbują odeprzeć (lub przynajmniej obejść) problem dopasowania wzorca poprawnie na końcu ($) dowolnej linii w trybie wielu linii (/ 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] => _ 
) 
*/ 
?> 

Niestety, nie mam żadnego dostępu do serwera z najnowszą wersją PHP - mój lokalny PHP 5.3.8 i mojego publicznego hosta PHP jest w wersji 5.2.17.