пустая обратная ссылка вызывает сбой совпадения в 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)

К сожалению, если (:else)? не совпадает, обратная ссылка \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);
?>

person Ty W    schedule 11.08.2010    source источник


Ответы (3)


почему бы вам просто не использовать \1 вместо \2?

preg_match_all("/\{(something(:else)?)\}(.*?)\{\/\\1\}/is", $data, $matches);

что касается проблемы «вам нужен синтаксический анализатор», он вам понадобится/нужен для анализа вложенных конструкций.

person user187291    schedule 11.08.2010
comment
оба ответа решили проблему, этот мне показался чище. - person Ty W; 12.08.2010

Ну почему бы не заменить? с или?

Изменять

"/\{(something(:else)?)\}(.*?)\{\/something\\2\}/is"

To

"/\{(something(:else|))\}(.*?)\{\/something\\2\}/is"

Таким образом, ссылка всегда будет захвачена, но иногда она будет пустой (что нормально)...

person ircmaxell    schedule 11.08.2010
comment
Ага! Знал, что мне просто нужна еще одна пара глаз. Оказывается, это сработает. - person Ty W; 11.08.2010

для таких случаев создан следующий класс (например, {что-то}... {/что-то} или {что-то}... {что-то}... {/что-то} {/что-то} и т. д. пример с SL5_preg_contentFinder класс

https://gist.github.com/sl5net/7029093#file-sl5_preg_contentfinder-php

        $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'];
        }
person SL5net    schedule 17.10.2013