Кратко съдържание


Регулярните изрази в .NET Framework



страница72/73
Дата21.07.2018
Размер9.03 Mb.
#76887
1   ...   65   66   67   68   69   70   71   72   73

Регулярните изрази в .NET Framework


И така, след като разгледахме основните моменти от синтаксиса на регулярните изрази, е време да видим как можем да използваме техния апарат със средствата на .NET Framework. Класовете за работа с регулярни изрази се намират в пространството от имена System.Text. RegularExpressions. Както ще се убедим, с помощта на методите на тези класове работата с шаблони никак не е сложна. Обикновено от нас се изисква само да инициализи­раме един обект с даден регулярен израз. След това с последователни извиквания в цикъл на метода за търсене на съвпадение лесно получа­ваме всички необходими резултати.

Разбира се, .NET Framework предлага и достатъчно възможности за по-задълбочено боравене с регулярните изрази, например за работа с групи, за заместване по даден шаблон, за разделяне на низ по шаблон и т.н. Има и възможности за настройка на различни опции при търсенето на съвпадения. Ще направим кратък преглед на класовете за работа с регу­лярни изрази, а след това ще разгледаме по-подробно методите и свойст­вата им, както и тяхното приложение.


Пространството System.Text.RegularExpressions


Класът Regex е основният клас за работа с регулярни изрази. Той пред­ставя един неизменим (константен) шаблон. Чрез неговите методи се извършват операциите с регулярни изрази – търсене, заместване, разде­ляне на низ по регулярен израз и др. Класът Regex може да се използва както чрез свой обект, инициализиран с даден шаблон, така и чрез ста­тичните си методи.

Класът Match представя едно съвпадение при търсене с регулярен израз. Чрез свойствата си той ни дава цялата необходима информация за съв­паде­нието – текста му, дължината му и началната му позиция в низа и. Методите на класа Match позволяват да се преминава към следващото намерено съвпадение.

Класът MatchCollection съдържа списък от всички съвпадения, получени при търсенето с шаблона върху израза. Чрез методите за търсене на класа Regex можем да получим като резултат именно такъв обект, който после да обходим и да обработим всяко съвпадение.

Класовете Group и Capture, както и съответните класове за колекции GroupCollection и CaptureCollection, са свързани с механизма на гру­пите в регулярните изрази, с който ще се запознаем след малко. Техните методи ни дават редица възможности за работа с тези групи.

Делегатът MatchEvaluator се използва при операциите за заместване, като имаме възможност да прилагаме дефинираната в него потребителска функционалност върху всяко намерено съвпадение и да заместваме с получения резултат.

Изброеният тип RegexOptions съдържа различни константи, които се използват за указване на опции при търсенето с регулярни изрази.

В пространството System.Text.RegularExpressions се намира и класът RegexCompilationInfo. Той се използва в процеса на компилиране на регулярните изрази в самостоя­телни асемблита, за което ще стане дума по-нататък в темата. Няма да го разглеждаме подробно.

Представяне на шаблони


Както вече споменахме, за представяне на регулярен израз в .NET служи класът Regex. Ето защо той е задължителен при работа с регулярни изра­зи. Можем да го използваме по два начина.

Единият вариант е да инстан­цираме обект от този клас и да подадем на конструктора му като пара­метри низ за шаблон и евентуално някакви опции. След това можем да извикваме методите на обекта, които реализи­рат операциите с регулярни изрази.

Другият вариант е да използваме статичните методи, които Regex предлага, като всеки път им подаваме необходимия шаблон за параметър.

Тези две възможности ще демонстрираме със следния кратък пример:



string text = "0887-654-364";

string patter = @"088\d-\d{3}-\d{3}";

// Instance Regex

Regex regex = new Regex(pattern);

Match match = regex.Match(text);
// Static Regex

Match match2 = Regex.Match(text, pattern);



Двата начина за работа с класа са приблизително еквивалентни и е въпрос на личен избор кой да се използва. Статичните методи предлагат пълноценен достъп до основната функционалност за работа с регулярни изрази, тъй като повечето методи имат статичен аналог. В повечето случаи можем спокойно да използваме тях, защото така не създаваме излишни обекти. Ако обаче се нуждаем от по-специалните вариации на методите, е по-добре да създадем инстанция на Regex. Вариантът с обект е по-добрият избор и ако се налага да използваме един и същи шаблон няколкократно върху различни низове, тъй като така шаблонът се компи­лира за машината на регулярните изрази само веднъж и това подобрява ефективността.

Ако изберем работата с обект и по някаква причина искаме да извлечем регулярния израз, с който е инстанциран този обект, можем да използва­ме метода ToString(). Той, както и простото преобразуване към string, ни връщат точно желания резултат.


Търсене с регулярни изрази


Основната процедура, свързана с регулярните изрази – търсенето на съвпадения с даден шаблон в даден низ – извършваме с помощта на класовете Regex и Match и техните методи и свойства. Търсенето за съвпадения можем също да извършим по два начина. Единият е да търсим последователно няколко пъти за всяко следващо успешно съвпадение. Това можем да постигнем чрез метода Match(…) на класа Regex. Методът Matches(…) пък ни дава възможност да извършим всички търсения до изчерпването на низа наведнъж и да получим съвпаденията в колекция от тип MatchCollection.

Двата метода на пръв поглед действат различно, но машината за регуляр­ните изрази се държи по познатия начин и при двата – всяко следващо търсене започва от края на предното. Разликата е, че в единият случай трябва да предизвикваме всяко търсене (докато стигнем до неуспешно съвпадение, т.е. низът е свършил) с извикване на метод, а в другия това става автоматично. Понеже често не се нуждаем от всички съвпадения, и двата метода намират своето приложение.


Няколко основни правила при търсенето


Хубаво е да запомним следните няколко практически съвета, които обик­новено са свързани с търсенето по шаблон:

  • Търсенето с регулярни изрази в повечето случаи е свързано с извличане на информация от текст. Хубаво е да подберем шаблона така, че след като открием съвпадение, лесно да можем да го обра­ботим, за да получим информацията, която практически ни върши работа. Оптималният вариант, разбира се, е самото съвпадение да е това, което ни трябва, но не винаги ситуацията е толкова проста.

  • Когато не извличаме информация, ние обикновено проверяваме дали изобщо съществува съвпадение. Този подход се прилага с цел вали­дация на данни или при класифициране на различни текстове въз основа на това какви низове съдържат, и др.

  • В повечето случаи не се налага да допълваме шаблона със символи, които трябва да "поемат" частта от текста, която не ни интересува. Не е нужно шаблонът ни да покрива целия текст. Понеже обикно­вено просто търсим някакви поднизове, то естествено е той да отра­зява само тях.

  • Трябва да се обърне специално внимание на "лакомите" символи при съставянето на шаблона. Те често могат да ни лишат по невнимание от желаната информация. Подобен ефект може да се получи, ако не оценим правилно принципа на действие на машината на регулярните изрази. С придобиването на опит ще правим все по-малко грешки от такъв тип.

  • В някои практически проблеми се налага работа с по-необичайни символи (букви с ударения, нестандартни знаци и т.н.). Има специ­ални начини за действие с Unicode, които ще разгледаме към края на темата.

Класът Match


Класът Match, както вече споменахме, представя всяко съвпадение на шаблон с подниз. Информацията за съвпадението е достъпна чрез свой­ствата на Match:

  • Свойството Success връща булева стойност, която показва дали съвпадението е успешно. Неуспешното съвпадение също е обект от класа Match, който може да бъде върнат от метода Match(…) на класа Regex, когато при търсене се стигне до края на низа и не е намерено нито едно съвпадение с шаблона.

  • Свойството Value съдържа стойността на съвпадението, тоест под­низ, който отговаря на нашия шаблон. Същата стойност се връща и от метода ToString(), както и от преобразуването към string.

  • Свойството Index представя позицията в низа, от която започва съвпадението (броенето e от 0).

  • Свойството Length ни дава дължината на съвпадението.

Класът Match няма public конструктор и съвпадението, което пази, не може да се изменя. Обекти от този тип можем да получим единствено чрез методите на класа Regex и те пазят вътрешна информация за регулярния израз, при търсенето с който са получени.

Статичното свойство Match.Empty представлява обект – неуспешно съвпа­дение. Той може да се използва за сравнение (дали нашето съвпадение е успешно), но по-добре е да се използва свойството Success. Свойството Empty има нулеви стойности за Value, Index и Length.

Типът MatchCollection ни предоставя колекция от обекти на класа Match. Такива колекции можем да получим като резултат от изпълнение на мето­да Matches(…), когато търсим всички съвпадения наведнъж. Колекцията се итерира по стандартните начини (например с foreach), а свойството Count ни дава броя успешни съвпадения.

Последователно еднократно търсене с Match(…) и NextMatch()


Методът Match(text) на класа Regex е основният метод, който се използва при работата с регулярни изрази. Той извършва едно търсене за съвпадения с шаблона в низа. Действието му се прекратява при първото намерено съвпадение и се връща обект от класа Match, който описва съвпадението. Такъв обект се връща и ако не е намерено нито едно съв­падение, така че за да проверим дали все пак имаме валиден резултат, трябва да използваме свойството Success на класа Match.

Статичният вариант на този метод е Match(text, pattern). Други варианти (но не статични) позволяват да се указва определена част от низа, в която да се търси за съвпадение.

Чрез обекта от клас Match, който сме получили като резултат, можем да продължим търсенето до намирането на ново съвпадение. Това става чрез метода NextMatch(). Той връща нов Match обект, който представя следва­щото съвпадение в низа. Това е възможно, тъй като в класа Match се пази информация за шаблона, с който търсим, както и за това къде започва и колко е дълго текущото съвпадение. Търсе­нето продължава от позицията, на която то завършва. Ако извикаме NextMatch() за обект, който е неус­пешно съвпадение, ще получим просто още едно неуспешно съвпадение.

Обикновено използваме един обект от тип Match (резултата от метода Match(…)), на който в цикъл присвояваме последователно резултата от поредното извикване на NextMatch(), след което го обработваме. За условие на цикъла обикновено използваме проверка на свойството Success, но в тялото може да правим и други проверки, въз основа на които евентуално да прекратим цикъла.


Тагове за хипервръзки в HTML код – пример


Казаното дотук ще илюстрираме с един практически пример. В редица ситуации се налага да се извлекат хипервръзките от даден HTML доку­мент. Например по подобен начин действат т.нар. web-spiders, които се използват от Интернет търсачките за обхождане на голямо количество страници за кратко време.

Можем да използваме следния регулярен израз за извличане на хипер­връзките:



<\s*a\s[^>]*?\bhref\s*=\s*('[^']*'|""[^""]*""|\S*)[^>]*>(.|\s)*?<\s*/a\s*

Чрез следните стъпки ще създадем програма, която да открива таговете от вида "", които представляват хипервръзки:

  1. Отваряме VS.NET и създаваме нов конзолен проект.

  2. Въвеждаме кода на програмата:

    static void Main()

    {

    string text = @"



    This is a hyperlink:

    close the window


    ... and one more link:
    target=""_blank"" href=/main.aspx >

    main page < a href = 'http://www.nakov.com'

    > Nakov's home site < /a >";
    string hrefPattern = @"<\s*a\s[^>]*?\bhref\s*=\s*" +

    @"('[^']*'|""[^""]*""|\S*)[^>]*>" +

    @"(.|\s)*?<\s*/a\s*>";

    Match match = Regex.Match(text, hrefPattern);

    while (match.Success)

    {

    Console.WriteLine("{0}\n\n", match);



    match = match.NextMatch();

    }

    }



  3. Сега можем да стартираме програмата. Резултатът от нея е следният:

Как работи нашата програма? Търсенето с регулярния израз е по стан­дартната схема с метода NextMatch() в цикъл, която вече обяснихме. Стойностите на съвпаденията извеждаме чрез преобразуване до string. Бихме могли да използваме и свойството Value. Ще обясним действието на самия шаблон, подобно на вече разгледания по-горе пример:



Шаблон: <\s*

Коментар: Започва със символа "<" и преминава през празното прост­ранство след него (ако има).


Шаблон: a\s

Коментар: Търси символ "a", следван задължително от празно прост­ранство.


Шаблон: [^>]*?\bhref

Коментар: Преминава през неопределен брой символи (но различни от затварящия таг ">", докато намери дума "href" (ако тагът има други атрибути преди "href", ги пропуска). Метасимволът * е "мързелив", защото конструкцията е последвана от подниз, който не искаме да изпуснем (въпреки това програмата ще работи и с "лаком" плюс – прегледайте целия шаблон внимателно и помислете защо!).


Шаблон: \s*=\s*

Коментар: Търси символа "=", евентуално предшестван и следван от празно пространство.


Шаблон: '[^']*'|""[^""]*""

Коментар: Ако следват двойни кавички или апостроф, преминава през 0 или повече символа до намиране на съответни затварящи двойни кавич­ки или апостроф.


Шаблон: |\S*

Коментар: Ако не следват двойни кавички или апостроф, преминава през 0 или повече символа, различни от празно пространство. Цялата конструкция за алтернативен избор е затворена в групиращи скоби, за да ограничи видимостта на метасимвола |.


Шаблон: [^>]*>

Коментар: Пропуска всички символи до намиране на символ ">" и пре­минава през него (включително други атрибути на тага). Тук вече * спокойно може да е лаком, защото е следван от символ, който самият той не може да "поеме" (защото е забранен в класа от символи, към който се отнася звездичката).


Шаблон: (.|\s)*?

Коментар: Преминава през 0 или повече произволни символи, които представляват текста на хипервръзката. Звездичката отново е "мързе­лива", защото иначе ще приеме целия низ до края за съвпадение. Конструк­цията (.|\s) отговаря на абсолютно всеки възможен символ (точката е за всички без нов ред, който пък е съвпадение за \s).


Шаблон: <\s*/a\s*>

Коментар: Търси затварящ таг "", евентуално съдържащ на места разделящи символи празно пространство (whitespace).


Още нещо за позицията на следващото търсене


Има един случай, когато правилото "новото започва от края на старото" не е в сила. Когато съвпадението е било празен низ (т. е. свойството Length съдържа 0), машината на регулярните изрази премества текущата позиция с едно напред. Ще покажем това с един кратък пример:

string text = @"Testing this and that";

Match match = Regex.Match(text, @".*?");

if (match.Success)

{

Console.WriteLine("Съвпадение: <>{0}<> на позиция {1}",



match.Value,match.Index);

match = match.NextMatch();

if (match.Success)

{

Console.WriteLine("Съвпадение: <>{0}<> на позиция {1}",



match.Value, match.Index);

}

}



// Output:

// Съвпадение: <><> на позиция 0

// Съвпадение: <><> на позиция 1


Тук "мързеливото" .*? открива най-късото съвпадение, което е винаги празният низ, който се среща на всяка позиция. Макар че първото съвпадение от позиция 0 има дължина 0, следващото търсене започва вече от позиция 1, защото в противен случай машината просто би търсила до безкрай. Така ако извикаме достатъчен брой пъти NextMatch(), ще се стигне до края на низа и търсенето ще завърши при проверката за успешно съвпадение, която ще се провали.

Търсене за съвпадения наведнъж с Matches(…) и MatchCollection


Другият вариант за търсене, който най-често се използва по подразбира­не в някои езици за програмиране, например Perl, е да използваме метода Matches(…). Той извършва цялостно търсене по шаблона, т.е. машината на регулярните изрази спира последователните търсения едва при дости­гане на края на низа. Както казахме, получените съвпадения при търсе­нето се запазват под формата на обект от класа MatchCollection. Тези съвпадения не могат да са част едно от друго поради факта, че всяко следващо започва от края на предното. Ще разгледаме един кратък при­мер, който демонстрира и този начин на работа. Колекцията обикновено обхождаме с цикъл foreach, вътре в който ще изведем съвпаденията:

static void Main()

{

// A pattern for cyrillic words



Regex regex = new Regex(@"\b[А-Яа-я]+\b");
String text =

"The Bulgarian word 'бира' (beer) often" +

" comes with the word 'скара' (grill).";
MatchCollection matches = regex.Matches(text);

foreach (Match match in matches)

{

Console.Write("{0}:{1} ", match);



}
// Output: бира скара

}


Методът Matches(…) се използва, когато се нуждаем от всички съвпаде­ния. Ако имаме причина да прекратим търсенето, преди да сме обходили целия низ, по-добре е да приложим схемата с Match(…) и NextMatch(), за да не губим производителност.



Сподели с приятели:
1   ...   65   66   67   68   69   70   71   72   73




©obuch.info 2024
отнасят до администрацията

    Начална страница