Пустая обратная ссылка приводит к сбою совпадений в PHP... есть ли обходной путь?
У меня проблемы с регулярным выражением в PHP, которое использует потенциально пустую обратную ссылку. Я надеялся, что это будет работать, как объяснено в http://www.regular-expressions.info/brackets.html:
Если обратная ссылка не использовалась в конкретной попытке сопоставления (например, в первом примере, где вопросительный знак сделал первую обратную ссылку необязательной), она просто пуста. Использование пустой обратной ссылки в регулярном выражении прекрасно. Это будет просто заменено ничем.
Однако кажется, что PHP немного отличается от http://php.net/manual/en/regexp.reference.back-references.php:
Если подшаблон фактически не использовался в конкретном совпадении, то любые обратные ссылки на него всегда терпят неудачу.
В качестве упрощенного примера я хочу сопоставить следующие две вещи с этим регулярным выражением:
- {что-то что-то}
- {что-то: еще} ... {/ что-то: еще}
Где "что-то" известно заранее, а "еще" может быть чем угодно (или ничем).
поэтому я попробовал следующее регулярное выражение ("просто" для простоты):
preg_match("/\{(something(:else)?)\}(.*?)\{\/something\\2\}/is", $data, $matches)
К сожалению, если (: еще)? не совпадает, обратная ссылка \2 завершается неудачно. Если я сделаю \2 необязательным (\2?), То я могу сопоставить {что-то}... {что-то: еще}, что не годится.
Сталкивался ли я с ограничением регулярных выражений (печально известное "вам нужен анализатор, а не регулярное выражение") или это можно исправить?
Тестовая программа:
<?php
$data = "{something} ... {/something}
{something:else} ... {/something:else}
{something:else} ... {/something}";
// won't match {something} ... {/something}
preg_match_all("/\{(something(:else)?)\}(.*?)\{\/something\\2\}/is", $data, $matches);
print_r($matches);
// change \\2 to \\2? and it matches too much
preg_match_all("/\{(something(:else)?)\}(.*?)\{\/something\\2?\}/is", $data, $matches);
print_r($matches);
?>
3 ответа
Почему бы вам просто не использовать \1 вместо \2?
preg_match_all("/\{(something(:else)?)\}(.*?)\{\/\\1\}/is", $data, $matches);
Что касается проблемы "вам нужен синтаксический анализатор", она вам понадобится для анализа вложенных конструкций.
Ну а почему бы не заменить? с или?
+ Изменить
"/\{(something(:else)?)\}(.*?)\{\/something\\2\}/is"
к
"/\{(something(:else|))\}(.*?)\{\/something\\2\}/is"
Таким образом, ссылка всегда будет захвачена, но иногда она будет пустой (что нормально)...
Следующий класс создан для таких случаев (например, {что-то}... {/ что-то} или {что-то}... {что-то}... {/ что-то} {/ что-то} и т. д. пример с классомSL5_ preg_contentFinder
https://gist.github.com/sl5net/7029093
$content1 = $content = '`ha <!--[01.o0]-->1<!--[/01.o0]-->
hi [02.o0]2 ho 3 `';
$pos_of_next_search = 0;
$begin = '(<!--)?\[([^\]>]*\.o0)\](-->)?';
$end = '<!--\[\/($2)\]-->';
$cf = new SL5_preg_contentFinder($content);
$cf->setBeginEnd_RegEx($begin, $end);
$cf->setSearchMode('use_BackReference_IfExists_()$1${1}');
$loopCount = 0;
while ($loopCount++ < 5) {
$cf->setPosOfNextSearch($pos_of_next_search);
list($findPos['begin_begin'], $findPos['end_begin'],
$findPos['begin_end'], $findPos['end_next'], $matchesReturn) = $cf->get_borders_left(__LINE__);
$content = $cf->getContent();
$expectedContent = $maxLoopCount;
if ($maxLoopCount>3)$expectedContent = '';
if ($content != $expectedContent)
die(__LINE__ . 'ERROR : $content != $expectedContent :' . " '$content'!= '$expectedContent ");
if (is_null($findPos['begin_begin'])) {
break;
}
echo(__LINE__ . ': '.$content1.' ==> "' . $content . '"');
$pos_of_next_search = $findPos['end_next'];
}