Регулярное выражение: точка с исключением включает исключенный символ

#php #regex

Вопрос:

Следующий пример:

     $text = <<<END
some text

q 1
some next line

q 2 test
exptra line
END;

    $text   = trim($text);
    if (preg_match_all("/nQs d .[^n]*n/isU", $text, $match)) {
        print_r($match);
        echo "OK";
    } else {
        echo "FAIL";
    }
 

Выход:

 Array
(
    [0] => Array
        (
            [0] => 
q 1
some next line

            [1] => 
q 2 test

        )

)
OK
 

Пожалуйста, обратите внимание, что новая строка в примере для $text состоит только из одной n .

По какой-то причине .[^n]* включает символ новой строки, даже если он указан как исключающий. Это происходит только в том случае, если новая строка является единственным символом, соответствующим этой части выражения ( n идет сразу после 1), и звездочка ( * ), которая должна означать, что любое количество символов, включая none/необязательно, не воспроизводится здесь правильно.

Поскольку мне нужно сопоставить оба случая, что можно сделать в этом случае?

Ожидаемый результат, чтобы соответствовать обеим строкам, начинающимся со q N следующей строки, но без нее:

 Array
(
    [0] => Array
        (
            [0] => 
q 1 

            [1] => 
q 2 test

        )

)
 

Приведенный выше пример упрощен. Сопоставленная строка может содержать символы новой строки, но не в указанном месте. На самом деле я тестировал . с другими символами, и это работает так же. Если для точки остается только один символ, который указан как исключающий — он все равно включает его:

     $text = <<<END
some text

q 1e
some next line

q 2 test
exptra line
END;

    $text   = trim($text);
    if (preg_match_all("/Qs d .[^e]*/iU", $text, $match)) {
        print_r($match);
        echo "OK";
    } else {
        echo "FAIL";
    }
 

Выход:

 Array
(
    [0] => Array
        (
            [0] => q 1e
            [1] => q 2 
        )

)
 

Комментарии:

1. . совпадения n , потому что у вас есть s флаг. [^n]* соответствует нулю или более не-LFS, поэтому это не ограничивает . здесь. Что вы ожидаете здесь получить?

2. Попробуй preg_match_all('~^Qh d.*~im', $text, $matches) . Смотрите демонстрацию регулярных выражений.

3. Добавлен ожидаемый результат. Из s документации по модификаторам A negative class such as [^a] always matches a newline character , поэтому на самом деле не должно иметь значения, s установлен модификатор или нет для этого случая. n указано как «исключить» символ и не должно быть включено, как во втором случае.

4. s флаг изменяет поведение . , а не [^a] то , что вы перепутали с документами здесь.

5. Я имею в виду, что .[^a] в соответствии с документацией символ новой строки все равно включается, независимо от того, есть ли s место или нет.

Ответ №1:

Ваше /Qs d .[^e]*/iU регулярное выражение содержит U флаг (PCRE_UNGREEDY), который меняет местами жадность кванторов и равен /Qs ?d ?.[^e]*?/i шаблону. /Qs ?d ?.[^e]*?/i играм q или Q , кто либо еще, но как можно меньше пробелов, символов, тогда один или более, но при этом как можно меньше цифр, а затем любой символ (кроме разрыва строки char, если s флаг опущен) и потом — посмотри — любое ноль или больше символов, чем другие e как можно меньше (т. е. он не соответствует ни одному тексту, как это не обязательно).

Вы можете использовать

 <?php

$text = <<<END
some text

q 1
some next line

q 2 test
exptra line
END;

if (preg_match_all('~^Qh d.*~im', $text, $match)) {
    print_r($match);
    echo "OK";
} else {
    echo "FAIL";
}
 

Смотрите демонстрационную версию PHP. Выход:

 Array
(
    [0] => Array
        (
            [0] => q 1
            [1] => q 2 test
        )

)
OK
 

Детали узора:

  • ^ — начало строки (из-за m флага)
  • Q Q или q (из-за i флага)
  • h — один или несколько горизонтальных пробелов
  • d — цифра
  • .* — любые нулевые или более символов, кроме символов разрыва строки (поскольку я не использую s флаг), как можно больше (т. Е. Остальная часть строки).

Комментарии:

1. Извините, не могу принять ответ. Приведенный выше пример упрощен. Сопоставленная строка может содержать символы новой строки, но не в указанном месте. На самом деле я тестировал . с другими символами, и это работает так же. Если для точки остается только символ, указанный как исключающий, — он все равно включает его. См.добавленный пример.

2. @Index Если вы объясните свои требования к шаблону на простом английском языке, было бы намного проще предоставить вам 100% рабочее решение. Ваши примеры не проясняют, чего вы на самом деле хотите достичь. Пожалуйста, ознакомьтесь с деталями моего шаблона и составьте набор требований, используя аналогичный стиль.

3. @Index Я добавлю объяснение, почему вы получаете эти совпадения, возможно, это даст вам подсказку.

Ответ №2:

Модификатор s переключает поведение . . Потому что вы включили его в . соответствие n с завещанием .

Небольшая демонстрация:

 $patterns = [
  '(.*)', '(.*)s', '([^\n]*)'
];

foreach ($patterns as $pattern) {
    preg_match($pattern, "foonbar", $match);
    echo $pattern, ': "', $match[0], ""n";
}
 

Выход:

 (.*): "foo"
(.*)s: "foo
bar"
([^n]*): "foo"
 

Модификатор m был бы более подходящим в вашем случае. Это изменяет поведение ^ и $ соответствует началу/концу строки (а не только строки).

 $pattern = '(
  ^q   # letter q or Q at line start (with modifier m)
  \s   # at least one whitespace 
  (?<number>\d ) # string of digits - named group "number"
  (?<label>[^\n]*) # any character except "n" (without modifier s) - named group "label"
)xmi';

if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
    print_r($match);
}
 

Выход:

 Array
(
    [0] => Array
        (
            [0] => q 1
            [number] => 1
            [1] => 1
            [label] => 
            [2] => 
        )

    [1] => Array
        (
            [0] => q 2 test
            [number] => 2
            [1] => 2
            [label] =>  test
            [2] =>  test
        )

)
 

Модификатор x включает «расширенный синтаксис». Это позволяет форматировать и документировать шаблон.

Я предлагаю использовать именованные группы для получения результатов. Это делает код более читабельным и надежным. PREG_SET_ORDER помещает данные для каждого совпадения в набор.