Диапазон квантификатора не работает в просмотре назад

Итак, я работаю над проектом, в котором мне нужно регулярное выражение, которое может соответствовать *, за которым следуют 1-4 пробела или табуляции, а затем строка текста. Прямо сейчас я использую .* после просмотра для целей тестирования. Однако я могу заставить его явно соответствовать 1, 2 или 4 пробелам/вкладкам, но не 1-4. Я тестирую следующий блок

*    test line here
*   Second test
*  Third test
* Another test

И это два шаблона, которые я тестирую (?<=(\*[ \t]{3})).*, которые работают так, как ожидалось, и соответствуют 2-й строке, то же самое, если я заменю 3 на 1, 2 или 4, однако, если я заменю его на 1,4, образуя следующий шаблон (?<=(\*[ \t]{1,4})).*, он больше не соответствует любой из строк, и я, честно говоря, не могу понять, почему. Я пробовал гуглить без успеха. Я использую флаг g(lobal).


person Hultner    schedule 10.02.2011    source источник


Ответы (1)


PHP, как и многие разновидности, не поддерживает просмотр назад переменной длины. Единственная поддержка — это чередование (|) на верхнем уровне ретроспективного просмотра. Даже ? может сломать шаблон. Альтернативой является использование:

(?<=\*[ \t]|\*[ \t]{2}|\*[ \t]{3}|\*[ \t]{4}).*

Или, лучше, прервите просмотр назад для группы:

\*[ \t]{1,4}(.*)

Это должно хорошо сработать для вас, так как в любом случае не похоже, что ваши совпадения перекрываются.

Из руководства:

Содержимое утверждения просмотра назад ограничено таким образом, что все строки, которым оно соответствует, должны иметь фиксированную длину. Однако, если есть несколько альтернатив, они не обязательно должны иметь одинаковую фиксированную длину. Таким образом, (?‹=волк|осел) разрешено, но (?‹!собаки?|кошки?) вызывает ошибку во время компиляции. Ветви, которые соответствуют строкам разной длины, разрешены только на верхнем уровне ретроспективного утверждения.

Источник: http://www.php.net/manual/en/regexp.reference.assertions.php

person Kobi    schedule 10.02.2011
comment
Также стоит упомянуть, что регулярное выражение по-прежнему не будет делать то, что, вероятно, хочет OP - оно с радостью будет соответствовать более чем 4 пробелам, потому что .* будет отлично соответствовать пробелам. - person Tim Pietzcker; 10.02.2011
comment
@Tim - Это хороший момент, но я думаю, что .* - это просто упрощенный пример того, что ОП считает странным поведением - интересная часть - это взгляд позади. - person Kobi; 10.02.2011
comment
Спасибо, я проглядел это. Кстати, RegexBuddy не жалуется на {1,4} (он отказывается от бесконечных кванторов, но не от этого конечного квантора). - person Tim Pietzcker; 10.02.2011
comment
После некоторого тестирования с чередованием кажется, что он всегда будет соответствовать только *, за которым следует один пробел, в результате чего совпадающая область начинается с табуляций или пробелов, но я думаю, что я мог бы просто прервать группу просмотра назад, как вы сказали, а затем с помощью манипуляции со строкой удалить ненужное пространство. Думаю, я мог бы использовать подстроку, чтобы удалить первый символ, а затем ltrim. И да, .* были просто упрощением, потому что мне нужна была помощь с просмотром назад. - person Hultner; 10.02.2011
comment
@Tim - я полагаю, это зависит от реализации: {1,4} можно расширить до допустимого чередования, но PHP этого не делает (что, конечно, лучше, это может создать чудовище). Я проверяю свои шаблоны PHP на pagecolumn.com/tool/pregtest.htm , а иногда на идеоне, которые, я думаю, ближе к реальности :) - person Kobi; 10.02.2011
comment
@Hultner - обратите внимание на группу захвата, которую я добавил во второе регулярное выражение - вы все равно можете получить строку без префикса. Попробуйте использовать preg_match_all("/\*[ \t]{1,4}(.*)/", $str, $matches, PREG_SET_ORDER); , и $matches будет содержать массив массивов. Второй элемент в каждом — это строка без звездочки и начальных пробелов. - person Kobi; 10.02.2011
comment
Хм, теперь я думаю, что я что-то пропустил. Откуда php знает, что он должен удалить звездочку и начальные пробелы, а не что-то еще? Сейчас я использую preg_match_all("/^[*][ \t]{1,4}[ \w]{3,50}$/m") - person Hultner; 10.02.2011
comment
Неважно, теперь я понимаю, установив PREG_SET_ORDER, он помещает все группы в их собственный элемент в массиве. Это на самом деле очень умно, таким образом, все, что находится за пределами группы, работает как просмотр назад и просмотр вперед. - person Hultner; 10.02.2011
comment
@Hultner - Приятно видеть, что ты понял это еще до того, как я успел прокомментировать. Удачи! :) - person Kobi; 10.02.2011