PHP Regex, игнорировать первую группировку в переменном выражении

Я пытаюсь понять, как захватить один оператор, если другой не существует с помощью preg_match.

Пример текста:

<!-- InstanceBeginEditable name="doctitle" -->

<title>BU Libraries | Research Guides | Citing Your Sources</title>

<!-- InstanceEndEditable -->

<div id="standardpgt"><h1><!-- InstanceBeginEditable name="pagetitle" --><strong>Citing Your Sources</strong><!-- InstanceEndEditable --></h1></div>

Поскольку существует список pagetitle, я хочу вытащить его вместо тега doctitle. Конечно, между ними есть много других персонажей, но я хотел показать вам небольшой образец.

Если pagetitle не существует, я хотел бы захватить содержимое doctitle.

Твист заключается в том, что я не использую PHP-код напрямую, я передаю инструкцию regex через конфигурационный файл, затем script берет его и вытаскивает 1-ю группу из инструкции.

Вот что я придумал:

((?!.*?<!--\s*?InstanceBeginEditable\s*?name=\x22pagetitle\x22\s*?-->.*?<!--\s*?InstanceEndEditable\s*?-->)<!--\s*?InstanceBeginEditable\s*?name=\x22doctitle\x22\s*?-->\s*?<title>(.*?)<\/title>\s*?<!--\s*?InstanceEndEditable\s*?-->|<!-- InstanceBeginEditable\s*?name=\x22pagetitle\x22\s*?-->(.*?)<!--\s*?InstanceEndEditable\s*?-->)

По какой-то причине php всегда читает первую пустую группу как группу 1, если она не работает.

Например, в приведенном выше примере текста он вернет

0 -> <!-- InstanceBeginEditable name="pagetitle" --><strong>Citing Your Sources</strong><!-- InstanceEndEditable -->
1 -> 
2 -> <strong>Citing Your Sources</strong>

Я не могу понять, как это сделать. Я также написал это регулярное выражение:

(?(?=.*?<!--\s*?InstanceBeginEditable\s*?name=\x22pagetitle\x22\s*?-->.*?<!--\s*?InstanceEndEditable\s*?-->).*?<!-- InstanceBeginEditable\s*?name=\x22pagetitle\x22\s*?-->(.*?)<!--\s*?InstanceEndEditable\s*?-->|.*?<!--\s*?InstanceBeginEditable\s*?name=\x22doctitle\x22\s*?-->\s*?<title>(.*?)<\/title>\s*?<!--\s*?InstanceEndEditable\s*?-->)

Но это тоже не сработало. Большое спасибо за помощь.

Крис

Ответы

Ответ 1

user178551 абсолютно прав, рекомендуя использовать конструкцию ветки reset. В вашем исходном регулярном выражении принципиально ничего плохого (кроме того, что он имеет длину более 300 символов и ВСЕ НА ОДИН ЛИНИИ!), И что он не может поставить одну из двух альтернатив в одну группу захвата). Нетривиальное (мягко говоря) выражение, подобное этому, должно быть записано в режиме свободного пробела с отступом, чтобы вы могли его прочитать. Вот ваше исходное регулярное выражение с добавленным разумным пробелом:

$re_OP1 = '%
    (                                             # $1:
      (?!
        .*?<!--\s*?InstanceBeginEditable\s*?name=\x22pagetitle\x22\s*?-->
        .*?<!--\s*?InstanceEndEditable\s*?-->
      )
           <!--\s*?InstanceBeginEditable\s*?name=\x22doctitle\x22\s*?-->\s*?
           <title>(.*?)<\/title>\s*?              # $2: 
           <!--\s*?InstanceEndEditable\s*?-->
    |      <!-- InstanceBeginEditable\s*?name=\x22pagetitle\x22\s*?-->
           (.*?)                                  # $3;
           <!--\s*?InstanceEndEditable\s*?-->
    )
    %six';

Теперь, посмотрев на это регулярное выражение, вы можете увидеть, где у вас есть жестко закодированное пространство на строке с оператором OR (т.е. |<!-- InstanceBegin...). Это приведет к тому, что regex не сможет соответствовать модификатору 'x'. Итак, заменив это пространство на \s* и запустив его в тестовых данных, вот результат, который я получаю (php-5.2.14):

Array
(
    [0] => <!-- InstanceBeginEditable name="pagetitle" --><strong>Citing Your Sources</strong><!-- InstanceEndEditable -->
    [1] => <!-- InstanceBeginEditable name="pagetitle" --><strong>Citing Your Sources</strong><!-- InstanceEndEditable -->
    [2] =>
    [3] => <strong>Citing Your Sources</strong>
)

Эти результаты аналогичны тем, которые вы опубликовали (но почему-то ваши результаты показывают только 2 группы захвата???). Теперь нам нужно применить предложение user178551 branch reset, а решение regex будет выглядеть следующим образом:

$re_jmr = '%
    (?|  # Branch reset construct. (restart counting for each alternative)
      (?!
        .*?<!--\s*InstanceBeginEditable\s*name="pagetitle"\s*-->
        .*?<!--\s*InstanceEndEditable\s*-->
      )
           <!--\s*InstanceBeginEditable\s*name="doctitle"\s*-->\s*
           <title>(.*?)<\/title>\s*              # $1: Group 1A
           <!--\s*InstanceEndEditable\s*-->
    |      <!--\s*InstanceBeginEditable\s*name="pagetitle"\s*-->
           (.*?)                                  # $1: Group 1B
           <!--\s*InstanceEndEditable\s*-->
    )
    %six';

Я пошел вперед и изменил все ленивые \s*? на жадные (потому что жадный - это то, что вы хотите здесь). Я также изменил все \x22 на " - более короткое и читаемое IMHO. И вот результаты работы с этим новым ветвь reset regex:

Array
(
    [0] => <!-- InstanceBeginEditable name="pagetitle" --><strong>Citing Your Sources</strong><!-- InstanceEndEditable -->
    [1] => <strong>Citing Your Sources</strong>
)

Что, (если я не ошибаюсь), именно то, что вы ищете. (Вы не представили тестовый пример для другой альтернативы, так что еще не были протестированы.) Кроме этого, ваше исходное регулярное выражение было довольно близко.

Ответ 2

Просто используйте ветвь reset pattern: (? |...) вокруг всего вашего выражения, например:

((?|(?!.*?<!--\s*?InstanceBeginEditable\s*?name=\x22pagetitle\x22\s*?-->.*?<!--\s*?InstanceEndEditable\s*?-->)<!--\s*?InstanceBeginEditable\s*?name=\x22doctitle\x22\s*?-->\s*?<title>(.*?)<\/title>\s*?<!--\s*?InstanceEndEditable\s*?-->|<!-- InstanceBeginEditable\s*?name=\x22pagetitle\x22\s*?-->(.*?)<!--\s*?InstanceEndEditable\s*?-->))s

От "man perlre":

"(|? Шаблон)" Это шаблон "branch reset", который имеет специальный свойство, которое буферы захвата пронумерована с той же начальной точки в каждой ветки чередования. Он доступен начиная с perl 5.10.0.

Буферы захвата нумеруются слева направо, но внутри этой конструкции нумерация перезапускается для каждой ветки.

Нумерация в каждой ветки будет такой же нормальной, и любая буферы после этой конструкции будут пронумерованы так, как будто конструкция содержала только одну ветвь, которая была большинство захватных буферов в нем.

Эта конструкция будет полезна, когда вы хотите ее захватить из нескольких альтернативных совпадений.

Рассмотрим следующий шаблон. Цифры под дисплеем, в котором буфер записанное содержимое будет сохранено.

         # before  ---------------branch-reset----------- after
         / ( a )  (?| x ( y ) z | (p (q) r) | (t) u (v) ) ( z ) /x
         # 1            2         2  3        2     3     4