Содержание
Что такое регулярные выражения? Есть несколько определений, но вся их суть сводится к следующему: регулярные выражения — это текстовые шаблоны с помощью которых производится поиск и замена текста. Регулярные выражения, сами по себе — это набор специальных символов, который обрабатывается механизмом работы с регулярными выражениями. В C# используется свой механизм работы с регулярными выражениями, в Delphi — свой, в Python — третий и т.д. Но все они достаточно похожи друг на друга и, научившись работать с регулярными выражениями один раз — вы сможете их использовать в любом другом языке.
Зачем нужны регулярные выражения?
В C# есть достаточно много инструментов для работы с текстом (строками) — тот же класс StringBuilder
или методы класса string
. Однако представим такую задачу: нам передается большой фрагмент текста, например, web-страничка со статьей и нам необходимо перечислить все html-теги, которые встречаются в коде этой страницы. Или, скажем, вытащить с этой странички содержимое определенного тега. Можно пойти длинным путем — перечислить в программе все возможные теги, потом искать их позиции в тексте, запоминать, что нашли и т.д. Но и это не решит полностью нашу задачу, а вот, используя регулярные выражения, мы можем решить эту задачу буквально в несколько строк кода. Регулярные выражения, при должном навыке их использования, превращаются в по-настоящему мощнейший механизм обработки текста. И как этим механизмом пользоваться мы сегодня и разберемся.
Основы построения регулярных выражений
Прежде, чем переходить непосредственно к класса C# для работы с регулярными выражениями, нам необходимо эти самые выражения «пощупать» — понять суть и принцип их работы. Поэтому, для начала, попробуем поработать в онлайн-сервисе для работы с регулярными выражениями https://regex101.com/. Здесь же, кстати, можно будет, при желании, сравнить различные версии регулярных выражений (для Python, C# и т.д.).
Итак, любое регулярное выражения — это специальный шаблон по которому будет происходить поиск текста. В C# выделяют несколько групп символов и их последовательностей из которых может состоять регулярное выражение: escape-символы, привязки и т.д. Со всеми ними вы всегда можете ознакомиться в справочнике на сайте Microsoft. Я же, чаще всего, использую старую добрую шпаргалку по регулярным выражениям:
Итак, попробуем воспользоваться этой шпаргалкой, написав свое первое регулярное выражение. Кстати, в шпаргалке оно есть — найдем все HTML-теги на странице. Вот как будет выглядеть это регулярное выражение:
(\<(/?[^\>]+)\>)
Переходим на сайт https://regex101.com/, выбираем слева в меню .NET (C#) и вставляем наше регулярное выражение в строку сверху:
Преимущество сайта https://regex101.com/ в том, что помимо непосредственно теста регулярного выражения, мы также можем, при необходимости, изучить из чего состоит регулярное выражение. Теперь возьмем любой html-код и вставим его в поле текста. В итоге получим следующий результат:
за 62 миллисекунды мы получили 2979 совпадений (matches) по каждому совпадению нам вывели подробную информацию. Итак, что скрывалось за всеми этими непонятными символами в нашем регулярном выражении:
- Первые круглый скобки
(...)
в регулярных выражениях так описывается группа (Group
). Группа может состоять из набора символов. В нашем случае, мы указываем шаблону, что в эту группу попадает всё, что будет соответствовать шаблону, т.е. html-тег целиком, например,<div>
или</a>
и т.д. - Последовательность
\<
в нашем случае это означает, что следующий за косой чертой символ будет читаться буквально — означать ровно то, что он и означает, то есть начало HTML-тега<
- Вторые круглые скобки
(...)
— вторая группа, в которую попадут все символы, исключая треугольные скобки<>
- Последовательность
/?
говорит о том, что символ/
должен встретиться 0 или 1 раз. Символ?
в регулярных выражениях ещё называют квантификатором или квантором. Квантификатор указывает сколько раз должен встретиться стоящий перед ним символ или группа символов (токен). - Последовательность в квадратных скобках
[^\>]
представляет собой диапазон. В нашем случае дословно обозначает, что в проверяемом диапазоне не должно быть (^
) символов\
и>
все остальные символы могут встречаться. - Символ
+
говорит, что диапазон может встретиться 1 или более раз. - Последовательность
\>
указывает на то, что сравниваемый текст должен закончиться символом>
Собственно, следуя этому описанию, мы и получили свои результаты, а именно — 2979 совпадений (Matches) где каждое совпадение содержит по две группы (Group) текста% в первой группе — весь тег целиком, во втором внутренность тега, включая символ /
. Попробуем изменить регулярное выражение следующим образом:
(\</.?\>)
Вместо диапазона символов мы указали, что после последовательности символов </
может встретиться любой символ (за это отвечает символ точки — .
) 0 или 1 раз (?
) и, затем, мы должны встретить закрывающую скобку тега >
В результате обработки текста с использованием этого шаблона мы получим все закрывающие html-теги, состоящие из одного символа, например, </a>
, </p>
и т.д.
Рассмотрим другой пример. Что означает вот такое регулярное выражение:
\s*(\w*н{2}\w*)\s*
Опять, сверяясь со шпаргалкой выше, читаем слева-направо:
- Последовательность
\s
говорит о том, что ищем пробел. - символ
*
говорит о том, что пробел (предыдущий токен) должен встретиться один или более раз - далее начинается группа
(...)
- в группе должны содержаться любые алфавитно-цифровые символы (
\w
) один или более раз (*
). - последовательность
н{2}
читается как «символн
должен встретиться строго два раза» (квантификатор{2}
) - затем в группе должны встретиться снова любые символы, кроме пробела любое количество раз (
w*
) - и шаблон заканчивается одним или несколькими пробелами
\s*
Таким образом, наше регулярное выражение должно «выловить» из текста все слова в которых встречается двойная н
. При этом, у регулярного выражения есть недостаток — в совпадение могу попасть лишние пробелы. Например, если перед словом поставить 100 пробелов, то все они попадут в совпадение (Match), но, при этом, группа будет содержать только слово. Можете, используя шпаргалку, попытаться оптимизировать это регулярное выражение, а пока разберемся с тем, какие инструменты нам предлагает C# для работы с регулярными выражениями.
System.Text.RegularExpressions
Инструменты для работы с регулярными выражениями в C# содержатся в пространстве имен System.Text.RegularExpressions
. Основным классом для работы с регулярными выражениями является класс Regex
. Этот класс содержит как статические методы, так и методы, которые мы можем использовать только после создания объекта типа Regex
.
Для примера, используем один из ключевых методов класса Regex
— IsMatch
, который возвращает true
/false
в зависимости от того, найдены ли совпадения в тексте или нет:
string pattern = @"\s*(\w*н{2}\w*)\s*"; //задаем регулярное выражение if (Regex.IsMatch("Анна наполнила ванну", pattern)) Console.WriteLine("Найдены совпадения"); //совпадения будут найдены 100% else Console.WriteLine("Совпадения не найдены");
Здесь мы используем статический метод IsMatch
в который первым параметром передаем обрабатываемый текст, а вторым — регулярное выражение. Здесь стоит обратить внимание на то, как мы записываем регулярное выражение (pattern
) в C#. Здесь я использовал символ @
, указав тем самым, что строку надо читать буквально, чтобы символы после \
не читались как escape-последовательности и не получить в итоге ошибки. Второй вариант записи менее читабельный — использовать двойную косую черту \\
. То есть записывать регулярное выражение вот так:
"\\s*(\\w*н{2}\\w*)\\s*"
Итак, с первым примером работы Regex
познакомились. Посмотрим мз чего состоит это класс.
Настройки работы Regex (перечисление RegexOptions)
У Regex
определено несколько конструкторов, позволяющих провести начальную инициализацию свойств объекта. В качестве параметров, мы можем передать в конструктор регулярное выражение, а также настройки в виде перечисления RegexOptions
, которое состоит из следующих основных значений:
Compiled
— компилировать регулярное выражение в сборку. Эта настройка позволяет ускорить работу регулярного выражения, особенно, если предполагается обработка больших объемов текста.CultureInvariant
— игнорировать настройки культуры.IgnoreCase
— игнорировать регистр символов в текстеIgnorePatternWhitespace
— удаляет из регулярного выражения все пробелы и разрешает использовать комментарии, начинающиеся с символа#
Multiline
— текст необходимо рассматривать в многострочном режиме. При таком режиме символы^
и$
обозначают, соответственно, начало и конец строки, а не начало и конец всего текстаRightToLeft
— текст необходимо считывать справа налевоSingleline
— текст необходимо рассматривать как одну строку. В этом случае символ.
соответствует любому символу, в том числе последовательности\n
(переход на новую строку)
Методы Regex и поиск последовательностей
Ниже представлены основные методы класса Regex
. Каждый из этих методов также имеет ряд перегруженных версий.
Is |
Указывает, обнаружено ли в указанной входной строке соответствие заданному регулярному выражению |
Match |
Ищет в указанной входной строке первое вхождение регулярного выражения |
Matches |
Ищет в указанной входной строке все вхождения регулярного выражения. |
Replace |
В указанной входной строке заменяет все строки, соответствующие указанному регулярному выражению, строкой, возвращенной делегатом MatchEvaluator . |
Split |
Разделяет входную строку в массив подстрок в позициях, определенных шаблоном регулярного выражения |
Вернемся к нашему примеру с регулярным выражением и рассмотрим работу этих методов на его примере. С методом IsMatch
мы уже познакомились.
Поиск первого вхождения регулярного выражения в строку (метод Match
)
string pattern = @"\s*(\w*н{2}\w*)\s*"; string input = "Анна наполнила ванну"; Regex regex = new Regex(pattern); Match match = regex.Match(input); if (match.Success) //нашли совпадение { Console.WriteLine($"Первое совпадение {match.Value}"); match = match.NextMatch(); while (match.Success) { Console.WriteLine($"Следующее совпадение {match.Value}"); match = match.NextMatch(); } }
здесь мы создали объект типа Regex
, передав в конструкторе регулярное выражение. Затем нашли первое совпадение, используя метод Match
. Этот метод вернул нам одноименный класс Match, содержащий информацию о найденном совпадении. Далее, мы воспользовались уже методом класса Match.NextMatch
и прошлись дальше по тексту, получив все совпадения с регулярным выражением. Но устраивать цикл не обязательно, так как все совпадения мы можем получить с помощью другого метода Regex
.
Получение всех вхождений регулярного выражения в текст (метод Matches
)
string pattern = @"\s*(\w*н{2}\w*)\s*"; string input = "Анна наполнила ванну"; Regex regex = new Regex(pattern); MatchCollection collection = regex.Matches(input); foreach (Match match in collection) { Console.WriteLine($"Совпадение {match.Value}"); GroupCollection groupCollection = match.Groups; foreach (Group group in groupCollection) { Console.WriteLine($"\tЗначения группы: Индекс {group.Index} \tИмя {group.Name} \tЗначение {group.Value} \tРазмер {group.Length}"); } }
В этом примере мы получаем сразу все вхождения регулярного выражения в текст, используя метод Matches
. Затем проходимся по всей коллекции совпадений и считываем информацию о группах (GroupCollection
) в каждом совпадении. Обратите внимание, что в регулярном выражении у нас обозначена одна группа, в то время как Regex
будет создавать две группы:
По полученным данным нетрудно понять, какие свойства за что отвечают в группе:
Group.Index
— индекс символа в строке с которого начинается значение группыGroup.Name
— порядковый номер группы в коллекцииGroup.Value
— значение группыGroup.Length
— длина значение в символах
В первую группу (с индексом 0) попадает строка, содержащая всё совпадение, то есть, в нашем случае строка вместе со всеми пробелами.
Замена подстрок в строке с использованием Regex
(метод Replace
)
Первый способ — замена строки на другую строку:
string pattern = @"\s*(\w*н{2}\w*)\s*"; string input = "Анна наполнила ванну"; Regex regex = new Regex(pattern); string result = regex.Replace(input, "замена"); Console.WriteLine(result);//заменанаполнилазамена
Как видите, заменились все совпадения (не значения групп) и строка превратилась в один сплошной набор символов. Второй способ замены более гибкий и позволяет перед заменой произвести анализ того, что мы планируем заменить:
static string GetText(Match m) { string x = m.ToString();//получаем значение группы if (char.IsUpper(x.Trim()[0])) //слово начинается с большой буквы - это имя { return "Маша "; } else return " ведро"; } static void Main(string[] args) { string pattern = @"\s*(\w*н{2}\w*)\s*"; string input = "Анна наполнила ванну"; Regex regex = new Regex(pattern); string result = regex.Replace(input, new MatchEvaluator(GetText)); Console.WriteLine(result); } }
здесь мы воспользовались одной из перегруженных версий метода Replace
и передали во втором параметре метод, который анализирует очередное совпадение с регулярным выражением и предлагает результат замены. Так, если в совпадении первый символ представлен заглавной буквой, то мы считаем, что перед нами имя и мы его меняем на другое имя, иначе — меняем слово «ванна» на «ведро». В результате получим строку: «Маша наполнила ведро».
Разделение строки на подстроки по регулярному выражению (метод Split
)
string pattern = @"\s*(\w*н{2}\w*)\s*"; string input = "Анна наполнила ванну"; Regex regex = new Regex(pattern); string[] strings = regex.Split(input); foreach (string s in strings) { Console.WriteLine(s); }
В результате получим следующий массив строк:
наполнила
ванну
Причем массив будет состоять не из трех, а из пяти элементов где первый и последний элементы — пустые строки.
Итого
Регулярные выражения позволяют максимально эффективно обрабатывать большие массивы текста, при необходимости, заменяя найденные подстроки. В C# для работы с регулярными выражениями используется класс Regex
, расположенный в пространстве имен System.Text.RegularExpressions
.