Ответ 1
Прочитайте это первым!
Этот пост должен показать возможность, а не одобрить подход "все регулярное выражение" к проблеме. Автор написал 3-4 варианта, каждый из которых имеет тонкую ошибку, которые сложно обнаружить, до достижения текущего решения.
В вашем конкретном примере есть другое лучшее решение, которое более удобно обслуживать, например, сопоставление и разделение соответствия по разделителям.
Этот пост посвящен вашему конкретному примеру. Я действительно сомневаюсь, что возможно полное обобщение, но идея может использоваться повторно для подобных случаев.
Резюме
- .NET поддерживает захват повторяющегося шаблона с помощью
CaptureCollection
class. - Для языков, поддерживающих
\G
и look-behind, мы можем создать регулярное выражение, которое работает с глобальной функцией сопоставления. Нелегко написать это полностью правильно и легко написать тонкое багги-регулярное выражение. - Для языков без
\G
и поддержки позади: можно эмулировать\G
с помощью^
путем прерывания строки ввода после одного совпадения. (В этом ответе не указано).
Решение
Это решение предполагает, что механизм регулярных выражений поддерживает \G
совпадение границ, прогноз (?=pattern)
и look-behind (?<=pattern)
. Java, Perl, PCRE,.NET, Ruby regex поддерживает все перечисленные выше функции.
Однако вы можете пойти с вашим регулярным выражением в .NET. Поскольку .NET поддерживает захват всех экземпляров, которые сопоставляются группой захвата, которая повторяется с помощью класса CaptureCollection
.
В вашем случае это может быть сделано в одном регулярном выражении с использованием границы соответствия \G
, и смотреть вперед, чтобы ограничить количество повторений:
(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)
DEMO. Конструкция \w+-
повторена, затем \w+:end
.
(?:start:(?=\w+(?:-\w+){2,9}:end)|(?!^)\G-)(\w+)
DEMO. Конструкция \w+
для первого элемента, затем -\w+
повторяется. (Спасибо ka ᵠ за предложение). Эта конструкция проще рассуждать о ее правильности, поскольку есть меньше изменений.
\G
граница соответствия особенно полезна, когда вам нужно делать токенизацию, где вам нужно убедиться, что движок не проскакивает вперед и не соответствует материалам, которые должны были быть недействительными.
Описание
Разложим регулярное выражение:
(?:
start:(?=\w+(?:-\w+){2,9}:end)
|
(?<=-)\G
)
(\w+)
(?:-|:end)
Самая легкая часть для распознавания - это (\w+)
в строке до последней, которая является словом, которое вы хотите захватить.
Последняя строка также довольно легко распознается: за словом, которое должно быть сопоставлено, может следовать -
или :end
.
Я разрешаю регулярному выражению свободно начинать сопоставление в любом месте строки. Другими словами, start:...:end
может появляться в любом месте строки и любое количество раз; регулярное выражение будет просто соответствовать всем словам. Вам нужно обработать только возвращаемый массив, чтобы отделить его от соответствующих токенов.
Что касается объяснения, начало регулярного выражения проверяет наличие строки start:
, а следующие проверки проверяют, что количество слов находится в пределах заданного предела и заканчивается на :end
. Либо это, либо мы проверяем, что символ перед предыдущим совпадением равен -
, и продолжить с предыдущего соответствия.
Для другой конструкции:
(?:
start:(?=\w+(?:-\w+){2,9}:end)
|
(?!^)\G-
)
(\w+)
Все почти то же самое, за исключением того, что мы сначала сопоставляем start:\w+
перед сопоставлением повторения формы -\w+
. В отличие от первой конструкции, где мы сначала сопоставляем start:\w+-
и повторяющиеся экземпляры \w+-
(или \w+:end
для последнего повторения).
Весьма сложно заставить это регулярное выражение работать для совпадения в середине строки:
-
Нам нужно проверить количество слов между
start:
и:end
(как часть требования исходного регулярного выражения). -
\G
также соответствует началу строки!(?!^)
необходим для предотвращения такого поведения. Не заботясь об этом, регулярное выражение может вызвать совпадение, если нетstart:
.Для первой конструкции look-behind
(?<=-)
уже предотвращает этот случай ((?!^)
подразумевается(?<=-)
). -
Для первой конструкции
(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)
нам нужно убедиться, что после:end
мы ничего не смешаем. Внешний вид предназначен для этой цели: он предотвращает любой мусор после:end
от соответствия.Вторая конструкция не сталкивается с этой проблемой, так как мы будем застревать в
:
(of:end
) после того, как мы сравним все токены между ними.
Версия проверки
Если вы хотите сделать валидацию, чтобы строка ввода соответствовала формату (без лишних вещей спереди и сзади) и извлеките данные, вы можете добавить якоря как таковые:
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G-)(\w+)
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G)(\w+)(?:-|:end)
(Look-behind также не требуется, но нам все еще нужно (?!^)
, чтобы предотвратить \G
от совпадения начала строки).
Строительство
Для всех проблем, где вы хотите захватить все экземпляры повторения, я не думаю, что существует общий способ изменения регулярного выражения. Один из примеров "жесткого" (или невозможного?) Случая для конвертирования - это когда повторение должно возвращать один или несколько циклов для выполнения определенного условия для соответствия.
Когда исходное регулярное выражение описывает всю входную строку (тип проверки), ее обычно легче конвертировать по сравнению с регулярным выражением, которое пытается совместить с серединой строки (тип соответствия). Тем не менее, вы всегда можете выполнить совпадение с исходным регулярным выражением, и мы преобразуем проблему совпадающего типа обратно в проблему типа проверки.
Мы создаем такое регулярное выражение, пройдя следующие шаги:
- Напишите регулярное выражение, которое покрывает часть перед повторением (например,
start:
). Назовем это префиксом regex. - Совпадение и захват первого экземпляра. (например,
(\w+)
)
(В этот момент первый экземпляр и разделитель должны быть сопоставлены) - Добавьте
\G
как чередование. Обычно также необходимо предотвратить совпадение начала строки. - Добавьте разделитель (если есть). (например,
-
)
(После этого шага остальные маркеры должны быть также сопоставлены, за исключением последнего, возможно) - Добавьте часть, которая покрывает часть после повторения (если необходимо) (например,
:end
). Назовем эту часть после регулярного выражения суффикса повторения (добавим ли мы его в конструкцию неважно). - Теперь трудная часть. Вы должны проверить, что:
- Нет другого способа начать матч, кроме префикса regex. Обратите внимание на ветвь
\G
. - Невозможно начать любое совпадение после того, как будет найдено соответствие регулярного выражения суффикса. Обратите внимание, как ветка
\G
начинает совпадение. - Для первой конструкции, если вы смешаете регулярное выражение суффикса (например,
:end
) с разделителем (например,-
) в чередовании, убедитесь, что вы не разрешаете суффиксное выражение как разделитель.
- Нет другого способа начать матч, кроме префикса regex. Обратите внимание на ветвь