Я пытаюсь сопоставить шаблон, который может быть вложенным.
Вот несколько примеров данных, из которых я хочу извлечь содержимое внутри элемента {{ 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), но я не видел никакого прогресса. Я искал решение для "рекурсивные шаблоны", лучшее, что я нашел, было этот вопрос, но после многих попыток я не смог адаптировать его к своему проекту.
(?R)
, но не смог ее использовать, будет ли это полезно в моем случае?Я не только ищу решение, я был бы очень признателен за понимание, как я могу это решить. Ссылка на текущее RegexR: regexr.com/426fd
@ WiktorStribiżew на самом деле ваш шаблон делает именно то, что я ищу (возможно, я плохо объяснил), я хотел поймать 4-ю группу вашего шаблона. Вы можете опубликовать шаблон в качестве ответа, если хотите, с небольшим объяснением этой последней группы, может быть?
Решения у меня и у Revo разные (совпадают с разными текстами), поэтому я бы воздержался от сравнения их эффективности. Мой подход состоит в том, чтобы сопоставлять строки только между соответствующими loop
/ {{ endloop }}
(обратите внимание на эта "испорченная" демонстрация ввода, и решение revo будет жадно захватывать от первого loop
до последнего {{ endloop }}
при таком вводе (демонстрация).
Спасибо за дополнительную информацию, @ WiktorStribiżew. Я использую ваше решение, я зачислил вас в код, вам подходит?
Да, без проблем. :)
Я верю эта версия моего регулярного выражения достаточно быстрая.
Большое спасибо, @ WiktorStribiżew! Я сохраняю его для следующего обновления EKF (занят на модульных тестах фреймворка RN), не мог вас отблагодарить! :)
Вот быстрое исправление вашего текущего шаблона:
{{\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, поэтому весь шаблон можно немного сократить.
Большое спасибо за объяснение, все еще трудно понять, но я буду работать над своими навыками. Спасибо и за последние советы, я сокращу выкройку, так как она будет очень длинной!
Вот решение вашей проблемы с точки зрения производительности (для этого требуется несколько сотен шагов вместо тысячи шагов с возвратом):
{{\s+loop\s+#(\w+)[^#]*#(\w+)\s*}}(?:[^{]*+|(?R)|{+)*{{\s+endloop\s+}}
Разбивка RegExp:
{{\s+loop\s+#(\w+)[^#]*#(\w+)\s*}}
Сопоставление структуры начального цикла и захват хешированных слов(?:
Начало группы без захвата
[^{]*+
Совпадает с чем угодно, кроме {
, собственнически|
Или(?R)
Повторяет весь паттерн|
Или{+
Соответствует любому количеству открывающих фигурных скобок)*
Максимальное соответствие{{\s+endloop\s+}}
Соответствует конечной структуреРаботает тоже хорошо, за исключением того, что напрямую не захватывает контент в группу. Было бы неплохо использовать несколько синтаксисов цикла. Возможно, мне нужно изменить правила именования переменных в моем движке шаблонов, которые на самом деле являются строгими. Спасибо за Ваш ответ !
Чтобы захватить внутреннее содержимое, вам нужно заключить не захватывающую группу в захватывающую группу. Смотрите здесь. И, пожалуйста, о каком синтаксисе множественного цикла вы говорите?
Я попытался удалить ?:
вместо того, чтобы вложить .. (тупой я). На самом деле разрешен только синтаксис loop ... as
, но с вашим шаблоном мы могли бы заменить as
на что-нибудь еще.
Да, это еще одно небольшое исправление это можно было сделать. Основная причина, по которой кто-то должен пойти с этим ответом, - это соображение о том, как быстро найти совпадение или потерпеть неудачу. Это огромная разница.
Если вы хотите сохранить захват, подпрограммы (рекурсия) не помогут. См. эта демонстрация.