PHP RegEx для соответствия вложенным шаблонам (возможная рекурсия)

Я пытаюсь сопоставить шаблон, который может быть вложенным. Вот несколько примеров данных, из которых я хочу извлечь содержимое внутри элемента {{ loop ... }:

<ul>
    {{ loop #users as #u }}
        <li>{{ #u.first_name }} {{ #u.last_name }}</li>
    {{ endloop }}
</ul>

Я правильно понимаю это с помощью этого RegEx:

/{{\s+loop\s+#([a-zA-Z_][a-zA-Z0-9_]*)((?:\.[a-zA-Z0-9_]+)*)\s+as\s+#([a-zA-Z_][a-zA-Z0-9_]*)\s+}}(.*){{\s+endloop\s+}}/sU

Explanation:

  • /
  • {{ start of open loop element
    • \s+loop\s+loop keyword
    • #([a-zA-Z_][a-zA-Z0-9_]*) a variable name (ex: #var)
    • ((?:\.[a-zA-Z0-9_]+)*) optional variable key (ex: #var.key)
    • \s+as\s+as keyword
    • #([a-zA-Z_][a-zA-Z0-9_]*)\s+ alias variable name (ex: #alias)
  • }} end of open loop element
  • (.*) the loop content
  • {{\s+endloop\s+}} close loop element
  • /sU

Где это не удается

С вложенными циклами мне нужно получить содержимое цикла первого уровня (потому что содержимое затем анализируется рекурсивно в моем проекте). Вот несколько примеров данных:

 1| <ul>
 2|     {{ loop #users as #u }}
 3|         <li>
 4|             {{ #u.first_name }} {{ #u.last_name }}
 5|             <ul>
 6|                 {{ loop #u.friends as #f }}
 7|                     <li>{{ #f.first_name }} {{ #f.last_name }}</li>
 8|                 {{ endloop }}
 9|             </ul>
10|         </li>
11|     {{ endloop }}
12| </ul>
13| 
14| {{ loop #foo as #bar }}
15|     <a href = "#">{{ #bar }}</a>
16| {{ endloop }}

С этим содержимым шаблон остановится на первом встреченном {{ endloop }} (строки 2-8). Если я удалю флаг U (нежелательный), я не смогу использовать несколько циклов, так как он остановится до последнего {{ endloop }}, даже если это разные циклы (строки 2–16) .
У меня была предыдущая версия паттерна с использованием флага /m (многострочная), но она тоже не удалась, поскольку соответствовала только петле самого глубокого уровня (строки 6-8).

У меня было много попыток (в основном, на regexr.com), но я не видел никакого прогресса. Я искал решение для "рекурсивные шаблоны", лучшее, что я нашел, было этот вопрос, но после многих попыток я не смог адаптировать его к своему проекту.


  • Есть ли комбинация флаг / флаги, чтобы дать приоритет этому типу шаблона?
  • Я немного читал о рекурсии в RegEx с (?R), но не смог ее использовать, будет ли это полезно в моем случае?
  • очевидный последний вопрос: как я могу сопоставить все содержимое циклов первого уровня?

Я не только ищу решение, я был бы очень признателен за понимание, как я могу это решить. Ссылка на текущее RegexR: regexr.com/426fd

Если вы хотите сохранить захват, подпрограммы (рекурсия) не помогут. См. эта демонстрация.

Wiktor Stribiżew 31.10.2018 09:46

@ WiktorStribiżew на самом деле ваш шаблон делает именно то, что я ищу (возможно, я плохо объяснил), я хотел поймать 4-ю группу вашего шаблона. Вы можете опубликовать шаблон в качестве ответа, если хотите, с небольшим объяснением этой последней группы, может быть?

AymDev 31.10.2018 10:06

Решения у меня и у Revo разные (совпадают с разными текстами), поэтому я бы воздержался от сравнения их эффективности. Мой подход состоит в том, чтобы сопоставлять строки только между соответствующими loop / {{ endloop }} (обратите внимание на эта "испорченная" демонстрация ввода, и решение revo будет жадно захватывать от первого loop до последнего {{ endloop }} при таком вводе (демонстрация).

Wiktor Stribiżew 02.11.2018 09:10

Спасибо за дополнительную информацию, @ WiktorStribiżew. Я использую ваше решение, я зачислил вас в код, вам подходит?

AymDev 02.11.2018 09:25

Да, без проблем. :)

Wiktor Stribiżew 02.11.2018 09:30

Большое спасибо, @ WiktorStribiżew! Я сохраняю его для следующего обновления EKF (занят на модульных тестах фреймворка RN), не мог вас отблагодарить! :)

AymDev 02.11.2018 09:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
7
221
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вот быстрое исправление вашего текущего шаблона:

{{\s+loop\s+#([a-zA-Z_]\w*)((?:\.\w+)*)\s+as\s+#([a-zA-Z_]\w*)\s*}}((?:(?!{{\s+(?:end)?loop\s).|(?R))*){{\s+endloop\s+}}

Обратите внимание, что вам не нужен модификатор U для того, чтобы этот шаблон работал должным образом, но вам все равно нужен модификатор s для ., чтобы он соответствовал любому символу.

См. демонстрация регулярного выражения

Основное отличие - замена .* на (?:(?!{{\s+(?:end)?loop\s).|(?R))*. Соответствует 0 или более повторениям:

  • (?!{{\s+(?:end)?loop\s). - любой символ (.), который не запускает последовательность, соответствующую следующему шаблону:
    • {{ - подстрока {{
    • \s+ - 1+ пробелов
    • (?:end)? - необязательная подстрока end
    • loop - подстрока loop
    • \s - пробел
  • | - или
  • (?R) - весь шаблон регулярного выражения

Кроме того, [a-zA-Z0-9_] равен \w, если вы не используете модификатор u или глагол (*UCP) PCRE, поэтому весь шаблон можно немного сократить.

Большое спасибо за объяснение, все еще трудно понять, но я буду работать над своими навыками. Спасибо и за последние советы, я сокращу выкройку, так как она будет очень длинной!

AymDev 31.10.2018 10:14

Вот решение вашей проблемы с точки зрения производительности (для этого требуется несколько сотен шагов вместо тысячи шагов с возвратом):

{{\s+loop\s+#(\w+)[^#]*#(\w+)\s*}}(?:[^{]*+|(?R)|{+)*{{\s+endloop\s+}}

См. живая демонстрация здесь

Разбивка RegExp:

  • {{\s+loop\s+#(\w+)[^#]*#(\w+)\s*}} Сопоставление структуры начального цикла и захват хешированных слов
  • (?: Начало группы без захвата
    • [^{]*+ Совпадает с чем угодно, кроме {, собственнически
    • | Или
    • (?R) Повторяет весь паттерн
    • | Или
    • {+ Соответствует любому количеству открывающих фигурных скобок
  • )* Максимальное соответствие
  • {{\s+endloop\s+}} Соответствует конечной структуре

Работает тоже хорошо, за исключением того, что напрямую не захватывает контент в группу. Было бы неплохо использовать несколько синтаксисов цикла. Возможно, мне нужно изменить правила именования переменных в моем движке шаблонов, которые на самом деле являются строгими. Спасибо за Ваш ответ !

AymDev 31.10.2018 10:35

Чтобы захватить внутреннее содержимое, вам нужно заключить не захватывающую группу в захватывающую группу. Смотрите здесь. И, пожалуйста, о каком синтаксисе множественного цикла вы говорите?

revo 31.10.2018 10:37

Я попытался удалить ?: вместо того, чтобы вложить .. (тупой я). На самом деле разрешен только синтаксис loop ... as, но с вашим шаблоном мы могли бы заменить as на что-нибудь еще.

AymDev 31.10.2018 10:45

Да, это еще одно небольшое исправление это можно было сделать. Основная причина, по которой кто-то должен пойти с этим ответом, - это соображение о том, как быстро найти совпадение или потерпеть неудачу. Это огромная разница.

revo 31.10.2018 10:50

Другие вопросы по теме