PHP RegEx для соответствия вложенных шаблонов (возможна рекурсия) - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь сопоставить шаблон, который может быть вложенным.
Вот некоторые примеры данных, где я хочу извлечь содержимое внутри элемента {{ 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

Пояснение:

  • /
  • {{ начало открытия петля элемент
    • \s+loop\s+ loop ключевое слово
    • #([a-zA-Z_][a-zA-Z0-9_]*) имя переменной ( например: #var)
    • ((?:\.[a-zA-Z0-9_]+)*) необязательный ключ переменной ( например: # var .key)
    • \s+as\s+ как ключевое слово
    • #([a-zA-Z_][a-zA-Z0-9_]*)\s+ псевдоним переменной ( например: #alias)
  • }} конец разомкнутой петли элемент
  • (.*) содержимое цикла
  • {{\s+endloop\s+}} закрыть цикл элемент
  • /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 (ungreedy), я не смогу использовать несколько циклов, так как они остановятся на последнем {{ endloop }}, даже если это разные циклы (строки 2-16 ).
У меня была предыдущая версия шаблона с использованием флага /m (многострочный), но он тоже не удался, поскольку он соответствовал только самому глубокому циклу уровня (строки 6-8 ).

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


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

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

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

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

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

Смотрите демо здесь

Распределение RegExp:

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

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

{{\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 для ., чтобы соответствовать любому символу.

См. Демоверсию regex

Основным отличием является замена .* на (?:(?!{{\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, следовательно, весь шаблон можно немного сократить.

...