Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Примеры работы с регулярными выражениями






Полагаю, что примеры дополнят краткое описание возможностей регулярных выражений и позволят лучше понять, как с ними работать. Начну с функции FindMatch, которая производит поиск первого вхождения подстроки, соответствующей образцу:

string FindMatch(string str, string strpat){ Regex pat = new Regex(strpat); Match match =pat.Match(str); string found = " "; if (match.Success) { found =match.Value; Console.WriteLine(" Строка ={0}\tОбразец={1}\ tНайдено={2}", str, strpat, found); } return(found); }//FindMatch

В качестве входных аргументов функции передается строка str, в которой ищется вхождение, и строка patstr, задающая образец - регулярное выражение. Функция возвращает найденную в результате поиска подстроку. Если соответствия нет, то возвращается пустая строка. Функция начинает свою работу с создания объекта pat класса Regex, конструктору которого передается образец поиска. Затем вызывается метод Match этого объекта, создающий объект match класса Match. Далее анализируются свойства этого объекта. Если соответствие обнаружено, то найденная подстрока возвращается в качестве результата, а соответствующая информация выводится на печать. (Чтобы спокойно работать с классами регулярных выражений, я не забыл добавить в начало проекта предложение: using System.Text.RegularExpressions.)

Поскольку запись регулярных выражений - вещь, привычная не для всех программистов, я приведу достаточно много примеров:

public void TestSinglePat(){ //поиск по образцу первого вхождения string str, strpat, found; Console.WriteLine(" Поиск по образцу"); //образец задает подстроку, начинающуюся с символа a, //далее идут буквы или цифры. str =" start"; strpat =@" a\w+"; found = FindMatch(str, strpat); str =" fab77cd efg"; found = FindMatch(str, strpat); //образец задает подстроку, начинающуюся с символа a, //заканчивающуюся f с возможными символами b и d в середине strpat = " a(b|d)*f"; str = " fabadddbdf"; found = FindMatch(str, strpat); //диапазоны и escape-символы strpat = " [X-Z]+"; str = " aXYb"; found = FindMatch(str, strpat); strpat = @" \u0058Y\x5A"; str = " aXYZb"; found = FindMatch(str, strpat); }//TestSinglePat

Некоторые комментарии к этой процедуре.

Регулярные выражения задаются @-константами, описанными в лекции 14. Здесь они как нельзя кстати.

В первом образце используется последовательность символов \w+, обозначающая, как следует из таблицы 15.1, непустую последовательность латиницы и цифр. В совокупности образец задает подстроку, начинающуюся символом a, за которым следуют буквы или цифры (хотя бы одна). Этот образец применяется к двум различным строкам.

В следующем образце используется символ * для обозначения итерации. В целом регулярное выражение задает строки, начинающиеся с символа a и заканчивающиеся символом f, между которыми находится возможно пустая последовательность символов из b и d.

Последующие два образца демонстрируют использование диапазонов и escape-последовательностей для представления символов, заданных кодами (в Unicode и шестнадцатиричной кодировке).

Взгляните на результаты, полученные при работе этой процедуры.


Рис. 15.1. Регулярные выражения. Поиск по образцу

Пример " чет и нечет"

Не всякий класс языков можно описать с помощью регулярных выражений. И даже тогда, когда такая возможность есть, могут потребоваться определенные усилия для корректной записи соответствующего регулярного выражения. Рассмотрим, например, язык L1 в алфавите T={0, 1}, которому принадлежат пустое слово и слова, содержащие четное число нулей и четное число единиц. В качестве другого примера рассмотрим язык L2, отличающийся от первого тем, что в нем число единиц нечетно. Оба языка можно задать регулярными выражениями, но корректная запись непроста и требует определенного навыка. Давайте запишем регулярные выражения, определяющие эти языки, и покажем, что C# справляется с проблемой их распознавания. Вот регулярное выражение, описывающее первый язык:

(00|11)*((01|10)(00|11)*(01|10)(00|11)*)*

Дадим содержательное описание этого языка. Слова языка представляют возможно пустую последовательность из пар одинаковых символов. Далее может идти последовательность, начинающаяся и заканчивающаяся парами различающихся символов, между которыми может стоять произвольное число пар одинаковых символов. Такая группа может повторяться многократно. Регулярное выражение короче и точнее передает описываемую структуру слов языка L1.

Язык L2 описать теперь совсем просто. Его слова представляют собой единицу, окаймленную словами языка L1.

Прежде чем перейти к примеру распознавания слов языков L1 и L2, приведу процедуру FindMatches, позволяющую найти все вхождения образца в заданный текст:

void FindMatches(string str, string strpat){ Regex pat = new Regex(strpat); MatchCollection matchcol =pat.Matches(str); Console.WriteLine(" Строка ={0}\tОбразец={1}", str, strpat); Console.WriteLine(" Число совпадений ={0}", matchcol.Count); foreach(Match match in matchcol) Console.WriteLine(" Index = {0} Value = {1}, Length ={2}", match.Index, match.Value, match.Length); }//FindMatches

Входные аргументы у процедуры те же, что и у функции FindMatch, ищущей первое вхождение. Я не стал задавать выходных аргументов процедуры, ограничившись тем, что все результаты непосредственно выводятся на печать в самой процедуре. Выполнение процедуры, так же, как и в FindMatch, начинается с создания объекта pat класса Regex, конструктору которого передается регулярное выражение. Замечу, что класс Regex, так же, как и класс String, относится к неизменяемым (immutable) классам, поэтому для каждого нового образца нужно создавать новый объект pat.

В отличие от FindMatch, объект pat вызывает метод Matches, который определяет все вхождения подстрок, удовлетворяющих образцу, в заданный текст. Результатом выполнения метода Matches является автоматически создаваемый объект класса MatchCollection, хранящий коллекцию объектов уже известного нам класса Match, каждый из которых задает очередное вхождение. В процедуре используются свойства коллекции и ее элементов для получения в цикле по элементам коллекции нужных свойств - индекса очередного вхождения подстроки в строку, ее длины и значения.

Вот процедура, в которой многократно вызывается FindMatches для различных строк и образцов поиска:

public void TestMultiPat(){ //поиск по образцу всех вхождений string str, strpat, found; Console.WriteLine(" Распознавание языков: чет и нечет"); //четное число нулей и единиц strpat =" ((00|11)*((01|10)(00|11)*(01|10)(00|11)*)*)"; str = " 0110111101101"; FindMatches(str, strpat); //четное число нулей и нечетное единиц string strodd = strpat + " 1" + strpat; FindMatches(str, strodd); }//TestMultiPat

Коротко прокомментирую работу этой процедуры. Первые два примера связаны с распознаванием языков L1 и L2 (чет и нечет) - языков с четным числом единиц и нулей в первом случае и нечетным числом единиц во втором. Регулярные выражения, описывающие эти языки, подробно рассматривались. В полном соответствии с теорией, константы задают эти выражения. На вход для распознавания подается строка из нулей и единиц. Для языка L1 метод находит три соответствия. Первое из них задает максимально длинную подстроку, содержащую четное число нулей и единиц, и две пустые подстроки, по определению принадлежащие языку L1. Для языка L2 находится одно соответствие - это сама входная строка. Взгляните на результаты распознавания.


Рис. 15.2. Регулярные выражения. Пример " чет и нечет"

Пример " око и рококо"

Следующий образец в нашем примере позволяет прояснить некоторые особенности работы метода Matches. Сколько раз строка " око" входит в строку " рококо" - один или два? Все зависит от того, как считать. С точки зрения метода Matches, - один раз, поскольку он разыскивает непересекающиеся вхождения, начиная очередной поиск вхождения подстроки с того места, где закончилось предыдущее вхождение. Еще один пример на эту же тему работает с числовыми строками.

Console.WriteLine(" око и рококо"); strpat=" око"; str = " рококо"; FindMatches(str, strpat); strpat=" 123"; str= " 0123451236123781239"; FindMatches(str, strpat);

На рис. 15.3 показаны результаты поисков.


Рис. 15.3. Регулярные выражения. Пример " око и рококо"

Пример " кок и кук"

Этот пример на поиск множественных соответствий навеян словами песни Высоцкого, где говорится, что дикари не смогли распознать, где кок, а где Кук. Наше регулярное выражение также не распознает эти слова. Обратите внимание на точку в регулярном выражении, которая соответствует любому символу, за исключением символа конца строки. Все слова в строке поиска - кок, кук, кот и другие - будут удовлетворять шаблону, так что в результате поиска найдется множество соответствий.

Console.WriteLine(" кок и кук"); strpat=" (т|к).(т|к)"; str=" кок тот кук тут как кот"; FindMatches(str, strpat);

Вот результаты работы этого фрагмента кода.


Рис. 15.4. Регулярные выражения. Пример " кок и кук"

Пример " обратные ссылки"

В этом примере рассматривается ранее упоминавшаяся, но не описанная возможность задания в регулярном выражении обратных ссылок. Можно ли описать с помощью регулярных выражений язык, в котором встречаются две подряд идущие одинаковые подстроки? Ответ на это вопрос отрицательный, поскольку грамматика такого языка должна быть контекстно-зависимой, и нужна память, чтобы хранить уже распознанные части строки. Аппарат регулярных выражений, предоставляемый классами пространства RegularExpression, тем не менее, позволяет решить эту задачу. Причина в том, что расширение стандартных регулярных выражений в Net Framework является не только синтаксическим. Содержательные расширения связаны с введением понятия группы, которой отводится память и дается имя. Это и дает возможность ссылаться на уже созданные группы, что и делает грамматику языка контекстно-зависимой. Ссылка на ранее полученную группу называется обратной ссылкой. Признаком обратной ссылки является пара символов " \k", после которой идет имя группы. Приведу пример:

Console.WriteLine(" Ссылка назад - второе вхождение слова"); strpat = @" \s(? < word> \w+)\s\k'word'"; str = " I know know that, You know that! "; FindMatches(str, strpat);

Рассмотрим более подробно регулярное выражение, заданное строкой strpat. В группе, заданной скобочным выражением, после знака вопроса идет имя группы " word", взятое в угловые скобки. После имени группы идет шаблон, описывающий данную группу, в нашем примере шаблон задается произвольным идентификатором " \w+" (? так? кто кого задает?). В дальнейшем описании шаблона задается ссылка на группу с именем " word". Здесь имя группы заключено в одинарные кавычки. Поиск успешно справился с поставленной задачей, подтверждением чему являются результаты работы этого фрагмента кода.


Рис. 15.5. Регулярные выражения. Пример " обратные ссылки"

Пример " Дом Джека"

Давайте вернемся к задаче разбора предложения на элементы. В классе string для этого имеется метод Split, который и решает поставленную задачу. Однако у этого метода есть существенный недостаток, - он не справляется с идущими подряд разделителями и создает для таких пар пустые слова. Метод Split класса Regex лишен этих недостатков, в качестве разделителей можно задавать любую пару символов, произвольное число пробелов и другие комбинации символов. Повторим наш прежний пример:

public void TestParsing(){ string str, strpat; //разбор предложения - создание массива слов str = " А это пшеница, которая в темном чулане хранится, " +" в доме, который построил Джек! "; strpat =" +|, "; Regex pat = new Regex(strpat); string[] words; words = pat.Split(str); int i=1; foreach(string word in words) Console.WriteLine(" {0}: {1}", i++, word); }//TestParsing

Регулярное выражение, заданное строкой strpat, определяет множество разделителей. Заметьте, в качестве разделителя задан пробел, повторенный сколь угодно много раз, либо пара символов - запятая и пробел. Разделители задаются регулярными выражениями. Метод Split применяется к объекту pat класса Regex. В качестве аргумента методу передается текст, подлежащий расщеплению. Вот как выглядит массив слов после применения метода Split.


Рис. 15.6. Регулярные выражения. Пример " Дом Джека"

Пример " Атрибуты"

Как уже говорилось, регулярные выражения особенно хороши при разборе сложных текстов. Примерами таковых могут быть различные справочники, различные текстовые базы данных, весьма популярные теперь XML-документы, разбором которых приходится заниматься. В качестве заключительного примера рассмотрим структурированный документ, строки которого содержат некоторые атрибуты, например, телефон, адрес и e-mail. Структуру документа можно задавать по-разному; будем предполагать, что каждый атрибут задается парой " имя: Значение" Наша задача состоит в том, чтобы выделить из строки соответствующие атрибуты. В таких ситуациях регулярное выражение удобно задавать в виде групп, где каждая группа соответствует одному атрибуту. Приведу начальный фрагмент кода очередной тестирующей процедуры, в котором описываются строки текста и образцы поиска:

public void TestAttributes(){ string s1 = " tel: (831-2) 94-20-55 "; string s2 = " Адрес: 117926, Москва, 5-й Донской проезд, стр.10, кв.7"; string s3 = " e-mail: Valentin.Berestov@tverorg.ru "; string s4 = s1+ s2 + s3; string s5 = s2 + s1 + s3; string pat1 = @" tel: \s(? < tel> \((\d|-)*\)\s(\d|-)+)\s"; string pat2= @" Адрес: \s(? < addr> [0-9А-Яа-я \-\, \.]+)\s"; string pat3 =@" e-mail: \s(? < em> [a-zA-Z.@]+)\s"; string compat = pat1+pat2+pat3; string tel=" ", addr = " ", em = " ";

Строки s4 и s5 представляют строку разбираемого документа. Их две, для того чтобы можно было проводить эксперименты, когда атрибуты в документе представлены в произвольном порядке. Каждая из строк pat1, pat2, pat3 задает одну именованную группу в регулярном выражении, имена групп - tel, Адрес, e-mail - даются в соответствии со смыслом атрибутов. Сами шаблоны подробно описывать не буду - сделаю лишь одно замечание. Например, шаблон телефона исходит из того, что номеру предшествует код, заключенный в круглые скобки. Поскольку сами скобки играют особую роль, то для задания скобки как символа используется пара - " \(". Это же касается и многих других символов, используемых в шаблонах, - точки, дефиса и т.п. Строка compat представляет составное регулярное выражение, содержащее все три группы. Строки tel, addr и em нам понадобятся для размещения в них результатов разбора. Применим вначале к строкам s4 и s5 каждый из шаблонов pat1, pat2, pat3 в отдельности и выделим соответствующий атрибут из строки. Вот код, выполняющий эти операции:

Regex reg1 = new Regex(pat1); Match match1= reg1.Match(s4); Console.WriteLine(" Value =" + match1.Value); tel= match1.Groups[" tel" ].Value; Console.WriteLine(tel); Regex reg2 = new Regex(pat2); Match match2= reg2.Match(s5); Console.WriteLine(" Value =" + match2.Value); addr= match2.Groups[" addr" ].Value; Console.WriteLine(addr); Regex reg3 = new Regex(pat3); Match match3= reg3.Match(s5); Console.WriteLine(" Value =" + match3.Value); em= match3.Groups[" em" ].Value; Console.WriteLine(em);

Все выполняется нужным образом - создаются именованные группы, к ним можно получить доступ и извлечь найденный значения атрибутов. А теперь попробуем решить ту же задачу одним махом, используя составной шаблон compat:

Regex comreg = new Regex(compat); Match commatch= comreg.Match(s4); tel= commatch.Groups[" tel" ].Value; Console.WriteLine(tel); addr= commatch.Groups[" addr" ].Value; Console.WriteLine(addr); em= commatch.Groups[" em" ].Value; Console.WriteLine(em); }// TestAttributes

И эта задача успешно решается. Взгляните на результаты разбора текста.


Рис. 15.7. Регулярные выражения. Пример " Атрибуты"

На этом и завершим рассмотрение регулярных выражений а также лекции, посвященные работе с текстами в C#.






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.