Раздел 3. Регулярные выражения в .NETАннотация: Данный материал писался исключительно для того, чтобы разобраться с особенностями использования регулярных выражений в Net. Когда он разросся до данного объема, было принято решение поделиться им с народом. Материал статьи написан исключительно на основе систематизации материалов, собранных в интернете и не претендует на новизну, но может быть полезен как попытка еще одного подхода к изложению материала для быстрого освоения регулярных выражений.
Параграф 1. Введение в использование регулярных выражений в .NETРегулярное выражение (regular expression, regexp) - это не новый язык, а стандарт для поиска и замены текста в строках. Существует два стандарта: основные регулярные выражения (BRE - basic regular expressions) и расширенные регулярные выражения (ERE - extended regular expressions). ERE включает все функциональные возможности BRE. Наиболее развита поддержка регулярных выражений, до появления .Net, была в Perl (выполнялась на уровне интерпретатора). В настоящее время регулярные выражения поддерживаются практически всеми новыми языками программирования. Обработка регулярных выражений в .Net, хотя и не является встроенной, но сами регулярные выражения почти полностью аналогичны регулярным выражениям в Perl и имеют ряд дополнительных возможностей. Существует два типа механизмов выполнения регулярных выражений: механизм детерминированных конечных автоматов и механизм не детерминированных конечных автоматов. Первый работает быстрее, но не поддерживает сохранение, позиционные проверки и минимальные квантификаторы. Net полностью поддерживает традиционный механизм недетерминированных конечных автоматов(не Posix совместимый). Его принцип действия заключается в последовательном сравнении каждого элемента регулярного выражения с входной строкой и запоминании найденных позиций совпадений. При неудачном дальнейшем поиске выполняется возврат к сохраненным позициям. В заключении, перебираются альтернативные варианты совпадений и выбираются ближайшие к левой границе точки начала поиска (в отличие от Posix совместимого, выбирающего максимально возможное). Поддержка регулярных выражений в .Net выполняется классами пространства имен: using System.Text.RegularExpressions; Основные классы:
Более подробно на использовании классов мы остановимся ниже, после рассмотрения синтаксиса регулярных выражений. Параграф 2. Основные элементы синтаксиса регулярных выраженийЭлементарные примеры в таблице даны для подстановки строки и регулярного выражения в две функции обработки нажатия кнопок 1 и 2, в которых одновременно демонстрируется и использование классов Match и MatchCollection. Для упрощения кода примеров, вывод результатов выполняется в заголовок формы: Код 1. Функция для подстановки значений regex и строки s из таблицы 1: private void button1_Click(object sender, EventArgs e) { string s = Строка подстановки из таблицы 1 код 1; Regex regex = new Regex(Регулярное выражение из таблицы 1 код 1); Match match = regex.Match(s); Text = ""; if (match.Success) { for (int i = 0; i < match.Groups.Count; i++) { Text += match.Groups[0].Value.ToString() + " "; } } } Код 2. Функция для подстановки значений regex и строки s из таблицы 1: private void button2_Click(object sender, EventArgs e) { string s = Строка подстановки из таблицы 1 код 2; Regex regex = new Regex(Регулярное выражение из таблицы 1 код 2); MatchCollection matchcollection = regex.Matches(s); Text = ""; for (int i = 0; i < matchcollection.Count; i++) { Text += matchcollection[i].Value+ " "; } } Таблица 1.
В регулярных выражениях допускаются метасимволы: \t - горизонтальная табуляция, \r - возврат курсора, \n - перевод строки, \v - вертикальная табуляция, \e - Escape, \f - подача страница, \0AA - символ представленный двумя цифрами (AA) восьмеричного кода, \xAA - символ представленный двумя цифрами (AA) шестнадцатеричного кода, \xAAAA- символ представленный четырьмя цифрами (AAAA) шестнадцатеричного кода, \a - звуковой сигнал. Параграф 3. Приоритет групповых регулярных выражений:
Параграф 4. Примеры использования групповых регулярных выраженийВ примерах используется код button2_Click, приведенный вначали параграфа 2. Извлечь адрес электронной почты из строкиstring s = "Почта: wladm@narod.ru Сайт: http://wladm.narod.ru "; Regex regex = new Regex(@"\b\w+([\.\w]+)*\w@\w((\.\w)*\w+)*\.\w{2,3}\b"); В регулярном выражении \b определяет начало слова, далее \w+ слово (цифра или буква) повторенные 1 или более раз. Следующую часть строки ([\.\w]+)*\w@ для данного примера можно было заменить на @, но адрес может иметь еще слова через точку до собаки. Поэтому точка или слово (в квадратных скобках) повторенные 1 или более раз и все это может быть повторено 0 или более раз. Следующий символ \w@ тоже можно сократить \w, но символ @ обязателен. Он якорный для распознавания почтового адреса. Далее идет практически повторение того, что было до символа @ - повторение слов с точкой и в конце слово от двух до трех символов (ru, com....). Извлечь адрес сайта из строкиДля примера взяты 2 адреса, короткий и длинный - реальный адрес MSDN с описанием класса Match. Первый адрес также показывает, что классы успешно работают с национальными кодировками. string s = "http://msdn.microsoft.com/иванов.html Сайт: "; s+="http://msdn.microsoft.com/library/rus/default.asp?url="; s+="/library/RUS/cpref/html/frlrfSystemTextRegularExpressionsMatchClassTopic.asp"; Regex regex = new Regex(@"(\b\w+:\/\/\w+((\.\w)*\w+)*\.\w{2,3}(\/\w*|\.\w*|\?\w*\=\w*)*)"); Пример похож на предыдущий, отличие в начале: Результат: http://msdn.microsoft.com/иванов.html http://msdn.microsoft.com/library/rus/default.asp?url= /library/RUS/cpref/html/frlrfSystemTextRegularExpressionsMatchClassTopic.asp Выделение цифр из строкиРезультат: string s = "Приход = +225 Расход: 211 Остаток: 335 Выручка 2222 Долги -357.8 "; Regex regex = new Regex(@"[-+]?\d*\.?\d*"); Здесь [-+]? - знак перед цифрой повторенный 0 или 1 раз, \. точка повторенная 0 или 1 раз и цифры, которые могут быть повторены 0 и более раз. Результат: 225 211 335 2222 -357.8 Удаление пробелов из текстаstring s = " Это мой текст - был с пробелами: в начале, в конце и в середине? "; Regex regex = new Regex(@"\b(\w+)\,?\??\!?(\ \-\ )?\:?\ ?"); ....... //При формировании строки из слов, к каждому слову не надо //добавлять пробел (его находит \ ?). Text += matchcollection[i].Value; Результат: Это мой текст - был с пробелами: в начале, в конце и в середине? string s = "Дядя: 812-555-55-55 Тетя: 555-55-55 Петя: 848-222-22-22"; Regex regex = new Regex (@"\b(\d{3}){0,1}(\-){0,1}(\d{3}\-)(\d{2}\-)(\d{2})\b"); В примере все знакомые конструкции. В начале слова код города с черточкой, которого может и не быть. Результат: 812-555-55-55 555-55-55 848-222-22-22 Более усложненный пример, понимающий код города в скобках и номер телефона с (и) без: string s = "Дядя: (812)5555555 Тетя: 555-55-55 Петя: (848)222-22-22"; Regex regex = new Regex (@"((\(\b\d{3}\))|(\b(\d{3}){0,1}(\-){0,1}))(\d{7}|((\d{3}\-)(\d{2}\-)(\d{2})\b))"); Особенность примера в том, что начало слова не может быть отнесено к скобке и, поэтому, скобка фиксируется как отдельный элемент, а начало слова относится к тому, что в слове. Результат: (812)5555555 555-55-55 (848)222-22-22 Разбор сложного текстового файла содержащего символы табуляцииВ данном примере используется реальный файл ведомости платежей, рассылаемый Мегафон в организации, сотрудники которой пользуются в служебных целях данной связью. Фрагмент данного файла содержит семизначный номер телефона, четыре колонки начислений за пользование различными видами связи и колонку суммарных значений платежа. 1111111 412.86 288.67 701.53 2222222 626.63 626.63 3333333 4444444 732.96 285.34 54.40 1072.70 5555555 288.23 135.00 156.74 579.97 Пример кажется простым до того момента, пока мы не посмотрим его строковое представление. Символы табуляции в файле обязательно идут только до номера телефона. Далее они "почти непредсказуемы" и их количество зависит от того, есть ли цифра в следующей колонке. При наличии цифры это два tab, при отсутствии один. Задача - превратить эту "мешанину" tab в нули там, где это необходимо. \t1111111\t412.86\t\t288.67\t\t\t701.53 \t2222222\t626.63\t\t\t\t\t626.63 \t3333333\t\t\t\t\t\t \t4444444\t732.96\t\t285.34\t54.40\t1072.70 \t5555555\t288.23\t135.00\t156.74\t\t\t579.97 Выполнить задачу можно только поэтапно:
Далее мы можем спокойно использовать извлеченные цифры: MatchCollection matchcollection = r3.Matches(sS); Параграф 5. Жадность квантификаторовКвантификаторы или повторители шаблонов (+ и *) обладают т.н. жадностью - т.е. возвращают самый длинный фрагмент текста, соответствующий шаблону. string s = "Это мой текст был с пробелами в начале, в конце и в середине?"; Regex regex = new Regex(@"\bЭ(.*)o"); //или ..Regex regex = new Regex(@"\bЭ(.+)o"); Результат: Это мой текст был с пробелами в начале, в ко Параграф 6. Более подробно о Regex6.1. Конструктор класса и его основные методыКонструктор класса Regex имеет два перегружаемых метода, для второго из которых options - поразрядная комбинация OR значений перечисления RegexOption. public Regex ( string pattern, RegexOptions options ); Основные опции определяются, как логическое "И" RegexOptions опций.
Основные методы класса - это:
Эти методы мы уже многократно использовали при рассмотрении синтаксиса регулярных выражений. Далее остановимся на других методах класса. 6.2. Использование класса Regex для замены в строках по шаблонамДля замещения вхождений образца символов, определенного регулярным выражением в указанной строке используется метод Replace. Метод имеет 10 перегружаемых реализаций, в которых используются следующие параметры:
Метод возвращает либо строку c произведенными заменами, либо строку с результатами выполнения кода вызванного делегата и части строки, не подвергшейся обработке. Примеры: 1. Заменяем в HTML документе содержимое тэга Title: StringBuilder mystringbuilder = new StringBuilder(); mystringbuilder.Append("<html><head><title>"); mystringbuilder.Append("Регулярные выражения "); mystringbuilder.Append("в .NET</TITLE></head>"); mystringbuilder.Append("<body><p>Абзац текста</p>"); mystringbuilder.Append("</body></html>"); Regex regex = new Regex(@"(?<=<title>).*(?=</title>)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Singleline); string s = regex.Replace(mystringbuilder.ToString(), "Регулярные выражения и их использование в Net"); textBox1.Text = s; Результат Регулярные выражения и их использование в Net 2. Вызов делегата при каждом совпадении: private int viI=0; private void button4_Click(object sender, EventArgs e) { string s = "abcdeabrtfabqwe"; Regex regex = new Regex("ab"); //Вывод в заголовок и в TextBox Text = ""; textBox1.Text=""; //Replace с вызовом делегата myMatchReplace string d = regex.Replace(s,myMatchReplace); textBox1.Text = d; } public string myMatchReplace(Match match) { if(match.Success) { for (int i = 0; i < match.Groups.Count; i++) { Text += "Вызов: "+Convert.ToString(viI)+" " +match.Groups[0].Value.ToString() + " "; } } viI++; return "Вызов: "+Convert.ToString(viI-1)+" "; } В результате в Text формы отобразятся все результаты удачных сравнений, обработанные делегатом: Вызов: 0 ab Вызов: 1 ab Вызов: 2 ab В TextBox формы видим, то, что возвращает regex.Replace(s,MatchReplace). В этом случае метод возвращает как результаты удачных сравнений, обработанные делегатом, так и остаток строки до следующего совпадения: Вызов: 0 cde Вызов: 1 rtf Вызов: 3 qwe 6.3. Проверка наличия совпаденийДля проверки наличия совпадений используется метод IsMatch: Метод имеет 2 перегружаемых реализации, в которых используются следующие параметры:
Метод возвращает true, если в строке есть совпадения с шаблоном и false в противном случае. Пример: string s = "abcdeabrtfabqwe"; Regex regex = new Regex("ab"); bool f = regex.IsMatch(s); if(f) Text = "Совпадение есть"; else Text = "Совпадений нет"; 6.4. Разделение строки по шаблонуДля разделение строки по шаблону используется метод Split: Метод имеет 2 перегружаемых реализации, в которых используются следующие параметры:
Метод разбивает строку, используя разделитель, определяемый регулярным выражением, а не набором знаков. Если указан count, строка разбивается максимум на count строк (последняя строка содержит остаток строки, от предыдущих разбиений). При Count=1 строка выводится полностью, при 2 - разбивается на 2 элементы - начало до совпадения и остаток строки и т.д. Примеры: string s = "cdabcdeabrtfabqwe"; Regex regex = new Regex("ab"); string[] s1 = regex.Split(s,2); for (int i = 0; i < s1.Length; i++) { textBox1.Text += s1[i] + " "; } Результат: cd cdeabrtfabqwe Параграф 7. Некоторые особенности классов для работы с регулярными выражениямиВыше мы перечисляли основные классы и говорили об их предназначении. В этом параграфе мы более подробно остановимся на некоторых особенностях использования их свойств и методов. 7.1 Match и CapturesМы уже отмечали, что класс Match - предоставляет результаты одного сопоставления регулярного выражения. Класс Match имеет свойство Captures, унаследованное от класса родитетеля Group. Captures должно хранить массив результатов сопоставлений по группировкам. Но, поскольку, класс при очередном применении регулярного выражения к строке предоставляет только одно сопоставление, то содержание, хранящееся в CaptureCollection (коллекция объектов Capture) представляет собой один элемент Capture. Следующий пример наглядно демонстрирует данное утверждение (кстати, он наглядно демонстрирует и жадность квантификаторов - здесь {1,2}): private void button4_Click(object sender, EventArgs e) { string s = "1 слон 2 слон 3 слон 4 слон 5 слон "; string sregx = @"((\d+)\s+(слон)\s+){1,2}"; Regex regex = new Regex(sregx, RegexOptions.IgnoreCase); Match match = regex.Match(s); int viI0 = 1; while (match.Success) { System.Console.WriteLine ("Применение регулярного выражения к строке " + Convert.ToString(viI0)+"й раз"); viI0++; System.Console.WriteLine("Match содержит: " + match.ToString()); CaptureCollection capturecollection = match.Captures; int viI = 1; foreach (Capture capture in capturecollection) { System.Console.WriteLine("Класс Capture номер " + Convert.ToString(viI) + " содержит: " + capture); viI++; } //Или так for (int i = 0; i < match.Captures.Count; i++) { System.Console.WriteLine("Класс Capture номер " + Convert.ToString(i + 1) + " содержит: " + match.Captures[i]); } match = match.NextMatch(); } } Результат мы вывели в Output Form (Меню View\Output). Заметим, что Captures.Count всегда равен 1. Применение регулярного выражения к строке 1й раз Match содержит: 1 слон 2 слон Класс Capture номер 1 содержит: 1 слон 2 слон Класс Capture номер 1 содержит: 1 слон 2 слон Применение регулярного выражения к строке 2й раз Match содержит: 3 слон 4 слон Класс Capture номер 1 содержит: 3 слон 4 слон Класс Capture номер 1 содержит: 3 слон 4 слон Применение регулярного выражения к строке 3й раз Match содержит: 5 слон Класс Capture номер 1 содержит: 5 слон Класс Capture номер 1 содержит: 5 слон 7.2 Match и GroupВ регулярное выражение, как правило, входят группировки и кванторы. Как результат, регулярное выражение может иметь несколько отдельных захватов каждым подвыражением регулярного выражения, из которых, как отмечали выше, в качестве значения Match выбирается наибольшее. Результаты промежуточных захватов хранятся в свойстве Groups как классы Group. private void button1_Click(object sender, EventArgs e) { string s = "1 слон 2 слон 3 слон 4 слон 5 слон "; string sregx = @"((\d+)\s+(слон)\s+){1,2}"; Regex regex = new Regex(sregx, RegexOptions.IgnoreCase); Match match = regex.Match(s); int viI0 = 1; while(match.Success) { System.Console.WriteLine ("Применение регулярного выражения к строке " + Convert.ToString(viI0) + "й раз"); viI0++; System.Console.WriteLine("Match содержит: " + match.ToString()); for(int i = 0; i < match.Groups.Count; i++) { Group group = match.Groups[i]; System.Console.WriteLine("Group № "+Convert.ToString(i)+ " Значение: " + group); } match = match.NextMatch(); } } Результат мы видим в Output Form (Меню View\Output): Применение регулярного выражения к строке 1й раз Match содержит: 1 слон 2 слон Group № 0 Значение: 1 слон 2 слон Group № 1 Значение: 2 слон Group № 2 Значение: 2 Group № 3 Значение: слон Применение регулярного выражения к строке 2й раз Match содержит: 3 слон 4 слон Group № 0 Значение: 3 слон 4 слон Group № 1 Значение: 4 слон Group № 2 Значение: 4 Group № 3 Значение: слон Применение регулярного выражения к строке 3й раз Match содержит: 5 слон Group № 0 Значение: 5 слон Group № 1 Значение: 5 слон Group № 2 Значение: 5 Group № 3 Значение: слон 7.3. Match, Group, Capture,Группа захвата может захватить ноль, одну или более строк в отдельном сопоставлении из-за кванторов, поэтому Group их хранит в коллекции объектов Capture. private void button1_Click(object sender, EventArgs e) { string s = "1 слон 2 слон 3 слон 4 слон 5 слон "; string sregx = @"((\d+)\s+(слон)\s+){1,2}"; Regex regex = new Regex(sregx, RegexOptions.IgnoreCase); Match match = regex.Match(s); int viI0 = 1; while(match.Success) { System.Console.WriteLine ("Применение регулярного выражения к строке " + Convert.ToString(viI0) + "й раз"); viI0++; System.Console.WriteLine("Match содержит: " + match.ToString()); for(int i = 0; i < match.Groups.Count; i++) { Group group = match.Groups[i]; System.Console.WriteLine("Group № "+Convert.ToString(i)+ " Значение: " + group); int viI = 0; foreach (Capture capture in group.Captures) { System.Console.WriteLine("Capture № " + Convert.ToString(viI) +" Значение: "+capture); viI++; } } match = match.NextMatch(); } } Результат мы видим в Output Form (Меню View\Output): Применение регулярного выражения к строке 1й раз Match содержит: 1 слон 2 слон Group № 0 Значение: 1 слон 2 слон Capture № 0 Значение: 1 слон 2 слон Group № 1 Значение: 2 слон Capture № 0 Значение: 1 слон Capture № 1 Значение: 2 слон Group № 2 Значение: 2 Capture № 0 Значение: 1 Capture № 1 Значение: 2 Group № 3 Значение: слон Capture № 0 Значение: слон Capture № 1 Значение: слон Применение регулярного выражения к строке 2й раз Match содержит: 3 слон 4 слон Group № 0 Значение: 3 слон 4 слон Capture № 0 Значение: 3 слон 4 слон Group № 1 Значение: 4 слон Capture № 0 Значение: 3 слон Capture № 1 Значение: 4 слон Group № 2 Значение: 4 Capture № 0 Значение: 3 Capture № 1 Значение: 4 Group № 3 Значение: слон Capture № 0 Значение: слон Capture № 1 Значение: слон Применение регулярного выражения к строке 3й раз Match содержит: 5 слон Group № 0 Значение: 5 слон Capture № 0 Значение: 5 слон Group № 1 Значение: 5 слон Capture № 0 Значение: 5 слон Group № 2 Значение: 5 Capture № 0 Значение: 5 Group № 3 Значение: слон Capture № 0 Значение: слон И последнее замечание: Match в свойстве Groups хранит объекты класса GroupCollection и, поэтому, можно пользоваться следующим кодом для доступа к значениям Group: GroupCollection groupcollection = match.Groups; int viI = 0; foreach (Group group in groupcollection) { System.Console.WriteLine("Group № " + Convert.ToString(viI) + " Значение: " + group); viI++; } ЛитератураМолчанов Владислав 21.09.2006г. Еcли Вы пришли с поискового сервера - посетите мою главную страничкуНа главной странице Вы найдете программы комплекса Veles - программы для автолюбителей, программы из раздела графика - программы для работы с фото, сделанными цифровым фотоаппаратом, программу Bricks - игрушку для детей и взрослых, программу записную книжку, программу TellMe - говорящий Русско-Английский разговорник - программу для тех, кто собирается погостить за бугром или повысить свои знания в английском, теоретический материал по программированию в среде Borland C++ Builder, C# (Windows приложения и ASP.Net Web сайты). |