Как изменить то, что PCRE regexp считает новыми строками в многострочном режиме? - PullRequest
6 голосов
/ 25 июля 2011

При использовании регулярных выражений PCRE в PHP многострочный режим (/m) позволяет ^ и $ соответствовать началу и концу строк (разделенных новыми строками ) в исходном тексте. , а также начало и конец исходного текста.

Похоже, что это прекрасно работает в Linux с \n (LF) в качестве разделителя новой строки, но не работает в Windows с \r\n (CRLF).

Есть ли способ изменить то, что PCRE считает переводом строки? Или, возможно, позволить ему соответствовать либо CRLF, либо LF таким же образом, как $ соответствует концу строки / строки?

Пример:

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

РЕЗУЛЬТАТ: Успех

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

РЕЗУЛЬТАТ: Fail

Ответы [ 4 ]

9 голосов
/ 27 сентября 2011

Вы пробовали (*CRLF) и связанные с ним модификаторы? Они подробно описаны в Википедии здесь (в разделе Newline / linebreak options) и, похоже, в моих тестах делают все правильно. то есть '/(*CRLF)^two$/m' должно соответствовать символам новой строки \r\n. Также (*ANYCRLF) должно соответствовать как Linux, так и Windows, но я не проверял это.

5 голосов
/ 25 июля 2011

Примечание: Ответ применим только к более старым версиям PHP, когда я писал его, я не знал о доступных последовательностях и модификаторах: \R, (*BSR_ANYCRLF) и (*BSR_UNICODE).См. Также ответ на вопрос: Как наиболее разумно заменить различные стили новой строки в PHP?

В PHP невозможно указать символ новой строки-последовательность (и) для шаблонов регулярных выражений PCRE.Модификатор m ищет только \n, , что задокументировано .И нет доступных настроек времени выполнения для внесения изменений, которые были бы возможны в perl, но это не вариант с PHP.

Обычно я просто изменяю строку перед использованием ее с preg_match и т.п.:

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

Возможно, это не совсем то, что вы ищете, но, вероятно, это поможет.

Редактировать: Что касается примера Windows EOL, который вы добавили в свой вопрос:

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

Это невозможно, потому что в тексте есть \r после two.Так что two не в конце строки, есть дополнительный символ, \r перед концом строки ($).

В руководстве по PHP четко объясняется, что только \nсчитается символом, указывающим окончание строки.$ учитывает только \n, поэтому, если вы ищете two\r в конце строки, вам нужно изменить ваш шаблон.Это другой вариант (вместо преобразования текста, как предложено выше).

3 голосов
/ 25 июля 2011

Странно, я не думаю, что $ (с модификатором m) заботится, есть ли \n или \r\n в качестве новой строки.

Идея проверить это, добавить \s* перед $. \s соответствует также символам новой строки и должно соответствовать \r перед \n, если это действительно будет проблемой.
Пока это не проблема, если в конце строки есть дополнительные пробелы, это не должно повредить.

0 голосов
/ 20 февраля 2018

Все зависит от того, откуда берутся ваши данные - внешние и неконтролируемые источники могут предоставлять довольно грязные данные.Подсказка для тех из вас, кто пытается решить (или хотя бы обойти) проблему правильного сопоставления шаблона в конце ($) любой строки в режиме нескольких строк (/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] => _
)
 */
?>

К сожалению, у меня нет доступа к серверу с последней версией PHP - мой локальный PHP - 5.3.8, а PHP моего публичного хоста - версия 5.2.17.

...