Работа с регулярными выражениями в C# (класс Regex)

Что такое регулярные выражения? Есть несколько определений, но вся их суть сводится к следующему: регулярные выражения — это текстовые шаблоны с помощью которых производится поиск и замена текста. Регулярные выражения, сами по себе — это набор специальных символов, который обрабатывается механизмом работы с регулярными выражениями. В 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) по каждому совпадению нам вывели подробную информацию. Итак, что скрывалось за всеми этими непонятными символами в нашем регулярном выражении:

  1. Первые круглый скобки (...)в регулярных выражениях так описывается группа (Group). Группа может состоять из набора символов. В нашем случае, мы указываем шаблону, что в эту группу попадает всё, что будет соответствовать шаблону, т.е. html-тег целиком, например, <div> или </a> и т.д.
  2. Последовательность \<в нашем случае это означает, что следующий за косой чертой символ будет читаться буквально — означать ровно то, что он и означает, то есть начало HTML-тега <
  3. Вторые круглые скобки (...) — вторая группа, в которую попадут все символы, исключая треугольные скобки <>
  4. Последовательность /? говорит о том, что символ / должен встретиться 0 или 1 раз. Символ ? в регулярных выражениях ещё называют квантификатором или квантором. Квантификатор указывает сколько раз должен встретиться стоящий перед ним символ или группа символов (токен).
  5. Последовательность в квадратных скобках [^\>] представляет собой диапазон. В нашем случае дословно обозначает, что в проверяемом диапазоне не должно быть (^) символов \ и >все остальные символы могут встречаться.
  6. Символ + говорит, что диапазон может встретиться 1 или более раз.
  7. Последовательность \> указывает на то, что сравниваемый текст должен закончиться символом >

Собственно, следуя этому описанию, мы и получили свои результаты, а именно — 2979 совпадений (Matches) где каждое совпадение содержит по две группы (Group) текста% в первой группе — весь тег целиком, во втором внутренность тега, включая символ /. Попробуем изменить регулярное выражение следующим образом:

(\</.?\>)

Вместо диапазона символов мы указали, что после последовательности символов </ может встретиться любой символ (за это отвечает символ точки — .) 0 или 1 раз (?) и, затем, мы должны встретить закрывающую скобку тега > В результате обработки текста с использованием этого шаблона мы получим все закрывающие html-теги, состоящие из одного символа, например, </a>, </p> и т.д.

Рассмотрим другой пример. Что означает вот такое регулярное выражение:

\s*(\w*н{2}\w*)\s*

Опять, сверяясь со шпаргалкой выше, читаем слева-направо:

  1. Последовательность \s говорит о том, что ищем пробел.
  2. символ * говорит о том, что пробел (предыдущий токен) должен встретиться один или более раз
  3. далее начинается группа (...)
  4. в группе должны содержаться любые алфавитно-цифровые символы (\w) один или более раз (*).
  5. последовательность н{2} читается как «символ н должен встретиться строго два раза» (квантификатор {2})
  6. затем в группе должны встретиться снова любые символы, кроме пробела любое количество раз (w*)
  7. и шаблон заканчивается одним или несколькими пробелами \s*

Таким образом, наше регулярное выражение должно «выловить» из текста все слова в которых встречается двойная н. При этом, у регулярного выражения есть недостаток — в совпадение могу попасть лишние пробелы. Например, если перед словом поставить 100 пробелов, то все они попадут в совпадение (Match), но, при этом, группа будет содержать только слово. Можете, используя шпаргалку, попытаться оптимизировать это регулярное выражение, а пока разберемся с тем, какие инструменты нам предлагает C# для работы с регулярными выражениями.

System.Text.RegularExpressions

Инструменты для работы с регулярными выражениями в C# содержатся в пространстве имен System.Text.RegularExpressions. Основным классом для работы с регулярными выражениями является класс Regex. Этот класс содержит как статические методы, так и методы, которые мы можем использовать только после создания объекта типа Regex.

Для примера, используем один из ключевых методов класса RegexIsMatch, который возвращает 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. Каждый из этих методов также имеет ряд перегруженных версий.

IsMatch Указывает, обнаружено ли в указанной входной строке соответствие заданному регулярному выражению
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.

Подписаться
Уведомить о
guest
1 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии