RegEx - повторное использование подвыражений
Скажем, что у меня есть регулярное выражение, соответствующее шестнадцатеричному 32-битовому номеру:
([0-9a-fA-F]{1,8})
Когда я создаю регулярное выражение, где мне нужно совместить это несколько раз, например
(?<from>[0-9a-fA-F]{1,8})\s*:\s*(?<to>[0-9a-fA-F]{1,8})
Должен ли я каждый раз повторять определение подвыражения, или есть способ "назвать и повторно использовать" его?
Я бы предположил что-то вроде (предупреждение, изобретенный синтаксис!)
(?<from>{hexnum=[0-9a-fA-F]{1,8}})\s*:\s*(?<to>{=hexnum})
где hexnum=
будет определять подвыражение "hexnum", а {= hexnum} будет повторно использовать его.
Поскольку я уже понял, это важно: я использую .NET System.Text.RegularExpressions.Regex
, но общий ответ тоже был бы интересным.
Ответы
Ответ 1
Подпрограммы RegEx
Если вы хотите использовать подвыражение несколько раз, не переписывая его, вы можете сгруппировать его, а затем вызвать его как подпрограмму. Подпрограммы могут вызываться по имени, индексу или относительной позиции.
Подпрограммы поддерживаются PCRE, Perl, Ruby, PHP, Delphi, R и другими. К сожалению,.NET Framework не хватает, но есть некоторые библиотеки PCRE для .NET, которые вы можете использовать вместо них (например, https://github.com/ltrzesniewski/pcre-net).
Синтаксис
Вот как работают подпрограммы: скажем, у вас есть суб-выражение [abc]
, которое вы хотите повторять три раза подряд.
Стандартный RegEx
Любое: [abc][abc][abc]
Подпрограмма, по имени
Perl: (?'name'[abc])(?&name)(?&name)
PCRE: (?P<name>[abc])(?P>name)(?P>name)
Ruby: (?<name>[abc])\g<name>\g<name>
Подпрограмма по индексу
Perl/PCRE: ([abc])(?1)(?1)
Ruby: ([abc])\g<1>\g<1>
Подпрограмма, по относительной позиции
Perl: ([abc])(?-1)(?-1)
PCRE: ([abc])(?-1)(?-1)
Ruby: ([abc])\g<-1>\g<-1>
Подпрограмма, предопределенная
Это определяет подпрограмму без ее выполнения.
Perl/PCRE: (?(DEFINE)(?'name'[abc]))(?P>name)(?P>name)(?P>name)
Примеры
Соответствует допустимой строке адреса IPv4, от 0.0.0.0 до 255.255.255.255:
((?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.(?1)\.(?1)\.(?1)
Без подпрограмм:
((?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.((?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.((?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.((?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))
И для решения исходной проблемы:
(?<from>(?P<hexnum>[0-9a-fA-F]{1,8}))\s*:\s*(?<to>(?P>hexnum))
Подробнее
http://regular-expressions.info/subroutine.html
http://regex101.com/
Ответ 2
Почему бы не сделать что-то вроде этого, не совсем короче, но немного более ремонтопригодным.
String.Format("(?<from>{0})\s*:\s*(?<to>{0})", "[0-9a-zA-Z]{1,8}");
Если вы хотите больше кода для самостоятельного документирования, я бы присвоил строку с регулярным выражением числа с правильно названной константной переменной.
Ответ 3
Если я правильно понимаю ваш вопрос, вы хотите повторно использовать некоторые шаблоны для построения более крупного шаблона?
string f = @"fc\d+/";
string e = @"\d+";
Regex regexObj = new Regex(f+e);
Кроме этого, использование обратных ссылок поможет только в том случае, если вы пытаетесь сопоставить ту же самую строку, которую вы ранее сопоставили где-то в своем регулярном выражении.
например.
/\b([a-z])\w+\1\b/
Будет только соответствовать: text
, spaces
в приведенном выше тексте:
Это образец текста, который не является заголовком, так как он не заканчивается двумя пробелами.
Ответ 4
.NET regex не поддерживает рекурсию шаблонов, и если вы можете использовать (?<from>(?<hex>[0-9a-fA-F]{1,8}))\s*:\s*(?<to>(\g<hex>))
в Ruby и PHP/PCRE (где hex
- это "техническая" группа захвата с именем, имя которой не должно встречаться в основном шаблоне), в .NET вы можете просто определить блок (-ы) как отдельные переменные, а затем использовать их для построения динамического шаблона.
Начиная с С# 6, вы можете использовать интерполированный строковый литерал, который очень похож на рекурсию подпрограммы PCRE/Onigmo, но на самом деле чище и не имеет потенциального узкого места, когда группа называется идентично "технической" группе захвата:
демонстрация С#:
using System;
using System.Text.RegularExpressions;
public class Test
{
public static void Main()
{
var block = "[0-9a-fA-F]{1,8}";
var pattern = [email protected]"(?<from>{block})\s*:\s*(?<to>{block})";
Console.WriteLine(Regex.IsMatch("12345678 :87654321", pattern));
}
}
[email protected]"..."
- это строковый литерал, содержащий строковый интерполированный строковый литерал, где escape-последовательности обрабатываются как комбинация буквенного обратного слэша и char после него. Обязательно определите литерал {
с помощью {{
и }
с помощью }}
(например, [email protected]"(?:{block}){{5}}"
, чтобы повторить block
5 раз).
Для более старых версий С# используйте string.Format
:
var pattern = string.Format(@"(?<from>{0})\s*:\s*(?<to>{0})", block);
как предлагается в Маттиасе.
Ответ 5
Нет такого предопределенного класса. Я думаю, вы можете упростить его, используя опцию "игнорировать", например:
(?i)(?<from>[0-9a-z]{1,8})\s*:\s*(?<to>[0-9a-z]{1,8})
Ответ 6
Для повторного использования regex named capture group используйте этот синтаксис:\k <name> или \k'name '
Итак, ответ:
(?<from>[0-9a-fA-F]{1,8})\s*:\s*\k<from>
Дополнительная информация: http://www.regular-expressions.info/named.html