Denis Gladkikh
Russian   |  English

Регулярные выражения. Вспоминаем, пишем, тестируем.

Признаюсь, я фанат регулярных выражений. Всегда, когда я вижу задачу, которую можно решить при помощи RegEx, я загораюсь и бегу писать тест под новенькое Regex условие. Раньше даже специально держал установленный SharpDeveloper, так как там была удобная тулза для проверки RegEx выражений, сейчас же я немного поумнел и для каждого RegEx пишу просто отдельный тест и в нем же и тестирую. Вообще, нужно стараться находить те задачи, которые предназначены для решения их через регулярные выражения. Мне сложно помнить синтаксис регулярных выражений, точнее приходится их писать не так уж и часто, потому из головы постоянно вылетает: какой символ отвечает за начало строки и т.п. Для освежения я постоянно пользуюсь очень легкой статьей Регулярные выражения на RSDN.

Несколько примеров

Определение, что текст русский

На своем сайте я отображаю свои сообщения из твиттера, но раз я разделил весь контент на русский и английский, я решил так же поделить сообщения из твиттера. Сделать решил просто: если есть русская буква, то сообщение русское, если нет, то английское, итого Regex получился такой:

const string RegexIsRussian = @"[А-Яа-я]+"

Можно, конечно, данную задачу решить проходом по всей строке и просмотром вхождения кода символа в русский диапазон. Тут же проверка очень простая – хотя бы один русский символ. Использовать этот Regex теперь очень просто в коде:

Regex.IsMatch(text, RegexIsRussian)

Regex – класс из пространства имен System.Text.RegularExpressions.

Нахождение и замена ссылки

Предыдущий пример очень прост, давайте попробуем посмотреть пример посложнее, в строке необходимо найти http ссылку. Делать это в лоб, простым разборов строки, уже будет занятие неблагодарное, а в regex пишется достаточно красиво:

const string RegexUrl = @"(https?://(www.)?([\w\-]+)(\.([\w\-]+))+([\w\\\/\.?&%=\-+]*))"

И опять же, мы теперь можем искать ссылки при помощи конструкции Regex.IsMatch, но можно и поинтереснее. Ссылки я искал не просто так, а для того чтобы их заменять в обычном тексте на anchor – html ссылки, потому, при помощи такого выражения и следующей конструкции, сделать это очень просто

Regex.Replace(text, RegexUrl, "<a href='$1'>$1</a>");

Вместо $1 будут подставляться найденные ссылки (то что в первых скобках).

Таким же способом мы можем заменить пользователей и хештеги на ссылки:

const string RegexTwitterUser = @"(@([A-Za-z0-9_]+))";
Regex.Replace(text, RegexTwitterUser, "<a href='http://twitter.com/$2'>$1</a>")
const string RegexTwitterTag = @"(#([A-Za-z0-9_]+))";
Regex.Replace(text, RegexTwitterTag, "<a href='http://twitter.com/#search?q=%23$2'>$1</a>");

Во втором примере мы уже используем как $1 так и $2, где $2 – это то что во вторых скобках, а точнее имя пользователя без #.

Вообще, примеров еще может быть огромное количество, особенно часто Regex всплывают в валидации, при помощи них и встроенных средств ASP.NET или других технологий можно запросто написать валидатор на какое либо вводимое поле – будь то email, телефон, индекс, дата или еще что-нибудь. Благо готовых решений таких стандартных Regex для валидаторов огромное количество, главное не лениться искать в поисковиках.

Тестирование Regex

Я тестирую Regex при помощи комбинаторных тестов (тестов с параметрами, значения которых подставляются при помощи источников данных). Так в nunit (сейчас использую его для своего сайта, так как MbUnit c Gallio и R#5 не смог подружить в VS2010), например, чтобы тестировать замену ссылок я написал такой тест:

public string[][] Urls
{
    get
    {
        return new[]
                   {
                       new[] {"Hello http://google.com bye", "Hello <a href='http://google.com'>http://google.com</a> bye"},
                       new[]
                           {
                               "Hello http://mail.google.com bye",
                               "Hello <a href='http://mail.google.com'>http://mail.google.com</a> bye"
                           },
                       new[]
                           {
                               "Hello http://google.com/test/test.aspx",
                               "Hello <a href='http://google.com/test/test.aspx'>http://google.com/test/test.aspx</a>"
                           },
                       new[]
                           {
                               "Hello http://g0ogle.com/test/test.aspx?q=7&b=0",
                               "Hello <a href='http://g0ogle.com/test/test.aspx?q=7&b=0'>http://g0ogle.com/test/test.aspx?q=7&b=0</a>"
                           }
                   };
    }
}
 
[Test]
public void twitter_text_to_html_convert([ValueSource("Urls")] string[] url)
{
    string replace = HtmlParser.ReplaceHref(url[0]);
    Assert.AreEqual(url[1], replace);
}

Как вы видите тест на самом деле очень маленький – это всего лишь метод twitter_text_to_html_convert. А вот источник Urls я постоянно добавляю. Метод twitter_text_to_html_convert выполнится столько раз, сколько данных в источнике.

Недавно я узнал что, оказывается, у меня не учитывалось что в url может быть тире, потому я дописал свой regex и просто добавил еще одну пару в Urls:

new[]
    {
        "Hello http://ninja-assassin-movie.warnerbros.com",
        "Hello <a href='http://ninja-assassin-movie.warnerbros.com'>http://ninja-assassin-movie.warnerbros.com</a>"
    },

Так я был уверен, что моя добавленная работа в regex не порушило то что было, и новый regex так же отрабатывает url с новым условием.

P.S. На свой сайт положил тулзу для быстрой проверки regex в вебе http://outcoldman.ru/ru/tools/regex

Progg it


Вас также может заинтересовать

rss twitter

Комментарии (17)

hotach ( ) #
avatar
Спасибо за статью. Кстати есть хорошая программа-справочник для создания регулярных выражений Rad Software Regular Expression Designer(http://www.radsoftware.com.au/regexdesigner/)
Roma ( ) #
avatar
А мне нравится RegexBuddy
Denis Gladkikh ( ) #
avatar
За Rad Software Regular Expression Designer и RegexBuddy спасибо, попробую.
Algol36 ( ) #
avatar
const string RegexIsRussian = @"[А-Яа-я]+"
Такой паттерн не совсем корректен. Правильно:
const string RegexIsRussian = @"[А-Яа-яёЁ]+"
Символы ё и Ё - не входят в диапазон А-Я.
Denis Gladkikh ( ) #
avatar
Algol36, спасибо, не знал! Правда, я и не использую никогда в написании Ё
Clevelus ( ) #
avatar
Символ Ё и не очень нужно в данном контексте проверять, врядли будет твит из одной этой буквы.

А замечание Algol36 неплохо вставить в виде замечания (Note).

ЗЫ: а насколько корректно считать текст русским, если в нем только одна русская буква? (возможно, попавшая туда случайно)
Сергей ( ) #
avatar
На мой взгляд одна из лучших бесплатных программ для создания и тестирования RegEx это Expresso, которую можно скачать тут. http://www.ultrapico.com/
Юрий ( ) #
avatar
"Всегда, когда я вижу задачу, которую можно решить при помощи RegEx..."
На мой взгляд это надо делать когда без регулярок не обойтись.
Если это можно сделать используя незамысловатые string операции - то лучше использовать именно эти операции.
Выйгрыш по производительности будет в разы превышать регулярки
Denis Gladkikh ( ) #
avatar
Юрий, не могу тут с вами согласится. Так то всегда можно обойтись без регулярных выражений, но вот сопровождать имеющееся регулярное выражение и написанный код - разные вещи. Притом, что всегда вносятся всяческие дополнения, условия меняются и т.п. Переписать и протестировать regex это одно, а вот вспоминать как и что делает код - это другое.

Хотя, наверняка, можно найти задачу, в которой использовать regex можно, но излишне.
Дмитрий ( ) #
avatar
А я вот не фанат рег. выражений. Порой смотрю на решения где для трансформации делают Regex.Replace() двадцать раз подряд и ужасаюсь тому, что никого не волнует производительность.
Павел ( ) #
avatar
Хорошая статья.
Я в вебе пользуюсь http://gskinner.com/RegExr/
Пока лучшего ничего не нашел.
Еще главное чтоб при замене текста регулярными выражениями не наделать XSS уязвимостей при выводе.
Denis Gladkikh ( ) #
avatar
Дмитрий, ну все хорошо в меру, если писать свой генератор отчетов с внутренним языком, то делать все на regex - это конечно плохо, тут лучше обратить внимание на нисходящий разбор. А вот в случае, если у нас комментарии к статьям или в общем то текст около 4000 символов, то сделать к нему 10 Replace - это не удар по производительности.

В общем, я за такой подход, если проблемы с производительностью есть - то их нужно решать, если нет - то лучше думать о хорошо сопровождаемом коде.
Denis Gladkikh ( ) #
avatar
Павел, спасибо за ссылку - в избранное и думаю буду пользоваться.
Павел ( ) #
avatar
Кстати еще, тем кто хочет разобраться с регулярными выражениям, посоветую книжку:
"Регулярные выражения библиотека программиста" Автор - Дж Фридл.
Там в общем описывается синтаксис и потом, по каждому языку отдельно. Я с нее начинал разбираться.
7мб весит, на обменниках можно найти
paralainer ( ) #
avatar
Денис, статья полезная, но вот regex для url мне кажется излишне навороченным, по сути надо только убедиться, что вначале "https?//", а потом идет набор символов, т.е. проверять наличие "www" не обязательно, я бы сократил до такого
"(https?://(([\w\-]+)(\.)?)+([\w\\\/\.?&%=\-+]*))"

К тому же в примере из статьи url http://localhost является не валидным, хотя вполне имеет право на жизнь.
paralainer ( ) #
avatar
Забыл сказать спасибо за удобный инструмент для проверки регэкспов ;)
Denis Gladkikh ( ) #
avatar
paralainer, в моем случае мне нужно было менять ссылки в твиттер постах. Согласен про www, лишнее, но я просто так пишу (стараюсь писать) подробно regex и попорядку, так сказать можно теперь произвести рефакторинг regex. Ну и в твиттере вроде не появятся ссылки с доменами первого уровня :) Но замечание полезное, надо будет добавить, спасибо!

По поводу тулзы, я вот подумываю переделать это на Silverlight (как время будет, а вот как будет это сложный вопрос), сделать небольшую тулзу, которую можно поставить Out of Browser. Ну и эту само собой оставлю (не у всех же Silverlight есть, хотя не думаю что ей особо кто будет пользоваться кроме меня, в интернете полно аналогов получше).
Добавить комментарий

Если вы хотите получать уведомления о новых комментариях к данному топику, укажите, пожалуйста, email и отметьте соответствующий пункт в форме. Если вы хотите добавить код в тексте комментария, то заключите его внутри тега [code]...[/code], более того можно уточнить язык, на котором написан данный код при помощи [code cs]...[/code], где вместо cs могут быть cs, html, xml, java, js, php, sql, cpp, css.

 

busy