- •Министерство образования и науки российской федерации
- •Обработка текстов с использованием регулярных выражений
- •305040, Г.Курск, ул. 50 лет Октября, 94. Содержание
- •Введение
- •1. Цель работы
- •2. Необходимое программное обеспечение
- •3. Теоретическая часть
- •3.1. Зачем нужны регулярные выражения
- •3.2. Регулярные выражения: что это такое?
- •3.3. Синтаксис регулярных выражений
- •3.3.1. Метасимволы
- •Значения метасимволов
- •Последовательности метасимволов
- •3.3.2. Символьные классы
- •3.3.3. Квантификаторы
- •Квантификаторы
- •3.3.4. О квантификаторах: жадных и ленивых
- •«Жадные» и «ленивые» варианты квантификаторов
- •3.3.6. Обратные ссылки
- •3.3.7. Модификаторы
- •Модификаторы
- •3.4. Как оно всё работает
- •3.5. Чего не умеют регулярные выражения
- •4. Использование библиотеки RegExpr
- •4.1. Как делается поиск подстрок
- •4.2. Как делается поиск и замена
- •5. Порядок выполнения работы
- •6. Содержание отчета
- •7. Контрольные вопросы
- •Библиографический список
- •Варианты заданий
3.5. Чего не умеют регулярные выражения
Проверять сбалансированность симметричных конструкций (begin–end, открывающих/закрывающих скобок и т.п.). Например, для случая
begin … begin … begin … end … end … end
проблематично составить регулярное выражение, которое заберет текст от второго «begin» до предпоследнего «end». Регулярные выражения в принципе не приспособлены для этого. Возможные пути решения см. в [1], стр.217-218, и [4].
4. Использование библиотеки RegExpr
Библиотека RegExpr написана на Delphi, но ее можно подключать к программам на C++ Builder 6.0. Работа с регулярными выражениями реализована в классе TRegExpr. Далее описываются свойства и методы этого класса, но не все, а самые необходимые для лабораторной работы. За остальными обращайтесь в HELP-файл или дистрибутив библиотеки. Все примеры даются на С++ (аналоги на Delphi нетрудно посмотреть в документации).
Класс TRegExpr работает со строками формата AnsiString: каждый символ занимает 1 байт, сама строка хранится в динамической памяти, и ограничена размером 2 Гб. Символы нумеруются начиная с 1.
Для подключения библиотеки RegExpr в С++ Builder нужно: а) из дистрибутива библиотеки RegExpr в папку с вашим проектом скопировать файлы regexpr.pas, regexpr.hpp; б) включить в проект файл regexpr.pas, используя команду Project/Add to project; в) в заголовок программы включить строку:
#include "regexpr.hpp"
Объект класса TRegExpr создается конструктором new, например так:
TRegExpr *MyRegExpr = new TRegExpr;
4.1. Как делается поиск подстрок
Регулярное выражение присваивается свойству
__property AnsiString Expression =
{read=GetExpression, write=SetExpression};
Для поиска подстрок, соответствующих регулярному выражению, используются методы:
bool __fastcall Exec(const AnsiString AInputString);
bool __fastcall ExecPos(int AOffset = 1);
bool __fastcall ExecNext(void);
Для первого поиска вызывается метод Exec, ему передается строка с текстом, в котором следует искать образец. Если поиск был успешным, метод вернет true, если нет – false. Для последующих поисков используется метод ExecNext (он продолжает поиск с позиции, на которой завершился прошлый поиск) и метод ExecPos (ищет с указанной позиции). Оба метода возвращают true, если нашли подстроку, и false при неудаче.
Все эти методы после каждого поиска помещают характеристики найденной подстроки в свойства класса TRegExpr:
__property AnsiString Match[int Idx] = {read=GetMatch};
В элементе Match[0] хранится подстрока, соответствующая регулярному выражению целиком. В элементах Match[ i ], i=1,2,3,… возвращаются подстроки, соответствующие 1й, 2й и т.д. группе (что такое «группа» см. п.3.3.5 и примеры на стр.13). Если регулярное выражение или группа не найдены, соответствующий элемент Match[ i ]=”” (пустой строке).
__property int MatchLen[int Idx] = {read=GetMatchLen};
Массив содержит длины найденных подстрок. В элементе MatchLen[0] содержится длина подстроки, отвечающей регулярному выражению целиком. В элементах MatchLen[ i ], соответственно, длины подстрок 1й, 2й и т.д. групп. Если регулярное выражение или группа отсутствуют, соответствующий MatchLen[ i ] = –1.
__property int MatchPos[int Idx] = {read=GetMatchPos};
Массив содержит позицию (смещение) подстроки и ее групп относительно начала входной строки. Позиции отсчитываются от 1. Аналогично предыдущему свойству, MatchPos[0] хранит позицию подстроки, соответствующей всему регулярному выражению, MatchPos[ i ] – позицию i й группы и т.д. Если регулярное выражение или группа не найдены, соответствующий MatchPos[ i ]=–1.
__property int SubExprMatchCount =
{read=GetSubExprMatchCount, nodefault};
Свойство содержит количество групп (подвыражений), найденных при последнем поиске. Если выражение не найдено (Exec… вернул false), или выражение не содержит групп нет, то SubExprMatchCount = 0.
Имейте в виду, что некоторые группы могут быть не найдены. Например, шаблон ‘(\d*)(\d)’ для поиска чисел содержит две группы: правая (2я) группа отделяет младшую цифру, левая (1я) группа захватывает все старшие цифры. Так, в тексте “Васе шел 104-й год” будет найдено Match[0]=”104”, Match[1]=”10”, Match[2]=”4”. А при поиске в тексте “Жене шел 3-й год” будет найдено Match[0]=”3”, Match[1]=”” (пустая группа: старших цифр нет), Match[2]=”3”.
Пример: Ниже приведен листинг консольного приложения на C++Builder, которое ищет в текстовом файле строки, удовлетворяющие регулярному выражению.
В строках С++ символ «\» применяется в ESC-последовательностях, поэтому при записи шаблонов регулярных выражений символ «\» заменяется на «\\».
#pragma hdrstop
#include <iostream.h> // ввод-вывод на консоль
#include <stdio.h> // работа с файлами
#include <conio.h> // для вызова getch()
#include "regexpr.hpp" // регулярные выражения
#pragma argsused
int main(int argc, char* argv[])
{
char file_name[] = "TEST.TXT";
FILE *file;
if (( file = fopen( file_name, "r")) != NULL ) {
// определяем размер файла
fseek( file, 0, SEEK_END );
size_t file_size = ftell( file );
fseek( file, 0, SEEK_SET );
// резервируем буфер
AnsiString buf;
buf.SetLength( file_size ); // длина буф.= размеру файла
// считываем файл в буфер целиком
if ( fread( buf.c_str(), 1, file_size , file )) {
TRegExpr *MyRegExpr = new TRegExpr; // создаем РегЭсп
MyRegExpr->Expression = "\\d+"; // задаем шаблон '\d+'
// поиск регулярными выражениями
if ( MyRegExpr->Exec(buf)){ //если 1-й поиск удачный..
do { // ...продолжаем поиск, пока не найдем всё
// с найденной строкой что-то делаем,
// например, выводим на экран
cout << MyRegExpr->Match[0] << endl;
} while ( MyRegExpr->ExecNext());
}
delete MyRegExpr; // удаляем объект РегЭксп
}
fclose( file ); // закрываем файл, он больше не нужен
}
getch();
return 0;
}
Специально для задач распознавания, соответствует ли строка регулярному выражению, в модуле RegExpr есть глобальная функция (это не метод класса TRegExpr !!!):
bool __fastcall ExecRegExpr(
const AnsiString ARegExpr, // шаблон регулярного выражения
const AnsiString AInputStr); // текст для проверки
Функция возвращает true, если строка AInputStr (вся целиком) покрывается регулярным выражением ARegExpr, иначе – false. Если регулярное выражение содержит ошибки, функция вызывает исключение, поэтому ее рекомендуется вызывать внутри try…catch.
В п.3.3.7 описывались модификаторы, управляющие работой регулярных выражений. Класс TRegExpr позволяет задать глобальное влияние модификаторов на всё регулярное выражение в целом. Ниже перечислены свойства класса TRgExpr, управляющие работой глобальных модификаторов, и их значения по умолчанию (расшифровка действия каждого модификатора дана в таблице 5):
__property bool ModifierI // (?i) = false
__property bool ModifierR // (?r) = true
__property bool ModifierM // (?m) = false
__property bool ModifierS // (?s) = true
__property bool ModifierG // (?g) = true
__property bool ModifierX // (?x) = false