Регулярные выражения. Вспоминаем, пишем, тестируем.
- modified:
- reading: 4 minutes
Признаюсь, я фанат регулярных выражений. Всегда, когда я вижу задачу, которую можно решить при помощи 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 с новым условием.