Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.- как работать с регулярными выражениями в C++ и, особенно, C?
- какие там предоставляются возможности?
- как там в регулярных выражениях обстоят дела с локализованными (русскими) строками?
регулярные выражения в C/C++
Модератор: Olej
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
регулярные выражения в C/C++
Это тема переплыла вот отсюда - локализация строк в C-коде
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Вар.№1Olej писал(а): Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.
Код: Выделить всё
#include <stdio.h>
#define INIT char *sp = instring;
#define GETC() (*sp++)
#define PEEKC() (*sp)
#define UNGETC(c) (--sp)
#define RETURN(c) return c;
#define ERROR(c) printf( "error %d\n", c );
#include <regexp.h>
#define SIZE 80
int main( int argc, char *argv[] ) {
char pattern[ SIZE ] = "s",
buf[ SIZE ] = "test";
if( !compile( *argv, pattern, &pattern[ SIZE ], '\0' ) ) {
return 1;
}
if( !step( buf, pattern ) )
printf( "no match\n" );
else {
for( char* p = loc1; p < loc2; p++ )
printf( "%c", *p );
printf( "\n" );
}
return 0;
}
Вот уж действительно "неприятный"...У этого файла неприятный интерфейс. Программы, которые включают данный файл, перед оператором #include <regexp.h> должны содержать определение пяти макросов, перечисленных ниже. Макросы используются функцией compile.

Этот код компилируется без ошибок, но с такими вот характерными предупреждениями:
Код: Выделить всё
$ gcc -Wall regex0.c -o regex0
In file included from regex0.c:9:0:
/usr/include/regexp.h:31:2: предупреждение: #warning "<regexp.h> will be removed in the next release of the GNU C Library." [-Wcpp]
#warning "<regexp.h> will be removed in the next release of the GNU C Library."
^
/usr/include/regexp.h:32:2: предупреждение: #warning "Please update your code to use <regex.h> instead (no trailing 'p')." [-Wcpp]
#warning "Please update your code to use <regex.h> instead (no trailing 'p')."
^
Это устаревший механизм, пришедший из OS Solaris, и сохранившийся только из соображений синтаксической совместимости. Он представляет только исторический интерес, на случай если вы столкнётесь с очень старым кодом.The contents of this header file were standardized in the Single Unix Specification, Version 2 (1997) but marked as LEGACY; new applications were already being encouraged to use <regex.h> instead. POSIX.1-2001 removed this header.
Мне так и не удалось добиться работоспособности откомпилированного этого кода. Возможно, его поддержка уже действительно удалена из GNU библиотеки, как они и обещали. Возможно, я проявил недостаточно настойчивости.
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Вар.№2Olej писал(а): Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.
Код: Выделить всё
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#define __USE_GNU
#include <regex.h>
int main( int argc, char *argv[] ) {
#define SIZE 80
char buf[ SIZE ] = "123,4567,898709",
pattern[ SIZE ] = "^([^,]*),([^,]*),([^,]*)$";
if( argc > 1 ) strncpy( pattern, argv[ 1 ], SIZE - 1 );
struct re_pattern_buffer *weight;
weight = (struct re_pattern_buffer*)malloc( sizeof( struct re_pattern_buffer ) );
if( !weight ) {
printf( "allocate error %m\n" );
return 1;
}
re_set_syntax( RE_BACKSLASH_ESCAPE_IN_LISTS | RE_CHAR_CLASSES |
RE_NO_BK_BRACES | RE_NO_BK_PARENS | RE_NO_BK_VBAR | RE_INTERVALS );
const char *err = re_compile_pattern( pattern, strlen( pattern ), weight );
if( err ) {
printf( "compile error: %s\n", err );
free( weight );
return 2;
}
struct re_registers regs;
memset( ®s, 0, sizeof( regs ) );
while( fgets( buf, sizeof( buf ) - 1, stdin ) ) {
if( buf[ strlen( buf ) - 1 ] == '\n') buf[ strlen( buf ) - 1 ] = '\0';
if( 0 == strlen( buf ) ) continue;
printf( "'%s' ->\n", buf );
int p = re_match( weight, buf, strlen( buf ), 0, ®s );
if( p <= 0 ) {
printf( "no match\n" );
continue;
}
for( int c = 0; ( c < p ) && ( regs.start[ c ] >= 0 ); c++ ) {
printf( "%d/%d : ", regs.start[ c ], regs.end[ c ] );
for( int i = regs.start[ c ]; i < regs.end[ c ]; i++ )
printf( "%c", buf[ i ] );
printf( "\n" );
}
}
free( weight );
return 0;
}
Код: Выделить всё
$ ./regex1 "^([^,]*),([^,]*),([^,]*)$"
123,4567,898709
'123,4567,898709' ->
0/15 : 123,4567,898709
0/3 : 123
4/8 : 4567
9/15 : 898709
a,b,c
'a,b,c' ->
0/5 : a,b,c
0/1 : a
2/3 : b
4/5 : c
й,ё,Ё
'й,ё,Ё' ->
0/8 : й,ё,Ё
0/2 : й
3/5 : ё
6/8 : Ё
слово,ещё слово,снова слово
'слово,ещё слово,снова слово' ->
0/50 : слово,ещё слово,снова слово
0/10 : слово
11/28 : ещё слово
29/50 : снова слово
^C
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Вар.№3Olej писал(а): Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.
(это то же, что и №2 - <regex.h>, но в POSIX нотации)
Код: Выделить всё
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <regex.h>
int main( int argc, char *argv[] ) {
#define SIZE 80
char buf[ SIZE ], errbuf[ SIZE ] = "",
pattern[ SIZE ] = "[0-9]";
#define MATCHSIZE 20
regmatch_t regs[ MATCHSIZE ];
regex_t re, *pre = &re;
if( argc > 1 ) strncpy( pattern, argv[ 1 ], SIZE - 1 );
int err = regcomp( pre, pattern, REG_ICASE | REG_EXTENDED );
if( err ) {
regerror( err, pre, errbuf, SIZE );
printf( "%s\n", errbuf );
return 1;
}
while( fgets( buf, sizeof( buf ) - 1, stdin ) ) {
if( buf[ strlen( buf ) - 1 ] == '\n') buf[ strlen( buf ) - 1 ] = '\0';
if( 0 == strlen( buf ) ) continue;
if( argc > 1 ) printf( "'%s' ->\n", buf );
if( REG_NOMATCH == regexec( pre, buf, MATCHSIZE, regs, 0 ) ) {
printf( "no match\n" );
continue;
}
for( int c = 0; regs[ c ].rm_so != -1; c++ ) {
printf( "%d/%d : ", regs[ c ].rm_so, regs[ c ].rm_eo );
for( int i = regs[ c ].rm_so; i < regs[ c ].rm_eo; i++ )
printf( "%c", buf[ i ] );
printf( "\n" );
}
}
regfree( pre );
return 0;
}
Код: Выделить всё
setlocale( LC_ALL, "" );
Код: Выделить всё
$ ./regex2 "^([^,]*),([^,]*),([^,]*)$"
123,4567,898709
'123,4567,898709' ->
0/15 : 123,4567,898709
0/3 : 123
4/8 : 4567
9/15 : 898709
a,b,c
'a,b,c' ->
0/5 : a,b,c
0/1 : a
2/3 : b
4/5 : c
й,ё,Ё
'й,ё,Ё' ->
0/8 : й,ё,Ё
0/2 : й
3/5 : ё
6/8 : Ё
слово,ещё слово,снова слово
'слово,ещё слово,снова слово' ->
0/50 : слово,ещё слово,снова слово
0/10 : слово
11/28 : ещё слово
29/50 : снова слово
^C
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Результат сопоставления (в обоих вариантах и показанных далее) представляется как массив совпадений (пусть и выраженных по-разному), 1-й элемент которого описывает общее соответствие, а последующие — это совпадения последовательных отмеченных подвыражений (\1, \2, \3, … в терминологии регулярных выражений):
Но такое (побайтное) сопоставление имеет побочные эффекты и требует хорошего понимания происходящего: можно искать в строках совпадений многобайтных последовательностей подстрок, но нельзя корректно выполнять всё, что формулируется в терминологии символов, трактуемых как единичные байты:
Конец сопоставленной последовательности в данном случае должен был бы завершаться на 17-м байте, а не 16-м.
Код: Выделить всё
$ ./regex1 "([0-9]*).([0-9]*).([0-9]*).([0-9]*)"
192.168.1.3
'192.168.1.3' ->
0/11 : 192.168.1.3
0/3 : 192
4/7 : 168
8/9 : 1
10/11 : 3
^C
Код: Выделить всё
$ ./regex2 "Ё."
яываэё Ёйвап
'яываэё Ёйвап' ->
13/16 : Ё�
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Вар.№4Olej писал(а): Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.
Это широко используемый пакет (его библиотеки) — PCRE (Perl Compatible Regular Expressions). Для этого вы должны иметь установленным соответствующий пакет (или установить его из репозитория своего дистрибутива):
Код: Выделить всё
$ dnf list pcre
Последняя проверка окончания срока действия метаданных: 0:00:15 назад, Thu Sep 8 19:12:56 2016.
Установленные пакеты
pcre.i686 8.39-2.fc23 @updates
pcre.x86_64 8.39-2.fc23 @updates
Код: Выделить всё
$ apt show libpcre3
Пакет: libpcre3
...
Сопровождающий: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
...
Описание: поддержка регулярных выражений, совместимых с Perl5 (динамическая версия)
Библиотека предоставляет функции для работы с регулярными выражениями.
Синтаксис и семантика выражений сделаны максимально похожими на регулярные
выражения языка Perl 5.
Код: Выделить всё
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <pcre.h>
int main( int argc, char *argv[] ) {
#define SIZE 80
char buf[ SIZE ] = "test test test test ", // errbuf[ SIZE ] = "",
pattern[ SIZE ] = "s";
#define MATCHSIZE 20
const unsigned char *locale_tables = pcre_maketables();
if( argc > 1 ) strncpy( pattern, argv[ 1 ], SIZE - 1 );
int options = 0;
const char *error;
int erroffset;
pcre *re = pcre_compile( (char*)pattern, options, &error, &erroffset, locale_tables );
if( !re ) {
printf( "pattern error: %s\n", error );
return 1;
}
int count = 0;
int ovector[ MATCHSIZE * 2 ];
while( fgets( buf, sizeof( buf ) - 1, stdin ) ) {
if( buf[ strlen( buf ) - 1 ] == '\n') buf[ strlen( buf ) - 1 ] = '\0';
if( 0 == strlen( buf ) ) continue;
if( argc > 1 ) printf( "'%s' ->\n", buf );
count = pcre_exec( re, NULL, (char*)buf, strlen( buf ), 0, 0, ovector, MATCHSIZE );
if( count < 0 ) {
printf( "no match\n" );
continue;
}
for( int c = 0; ( c < 2 * count ) && ( ovector[ c ] >= 0 ); c += 2 ) {
printf( "%d/%d : ", ovector[ c ], ovector[ c + 1 ] );
for( int i = ovector[ c ]; i < ovector[ c + 1 ]; i++ )
printf( "%c", buf[ i ] );
printf( "\n" );
}
}
return 0;
}
Для сборки обязательна библиотека (и в ряде дистрибутивов её лучше указать в командной строке после файла исходного кода … на самом деле — после объектного файла):
Код: Выделить всё
$ gcc -Wall regex3.c -l pcre -o regex3
Код: Выделить всё
$ ./regex3 "Ё"
это Ёлка
'это Ёлка' ->
7/9 : Ё
а это ёлка
'а это ёлка' ->
no match
31.12.2016 NewYear - Ёлка
'31.12.2016 NewYear - Ёлка' ->
21/23 : Ё
^C
Код: Выделить всё
$ ./regex3 "^([^,]*),([^,]*),([^,]*)$"
123,4567,898709
'123,4567,898709' ->
0/15 : 123,4567,898709
0/3 : 123
4/8 : 4567
9/15 : 898709
a,b,c
'a,b,c' ->
0/5 : a,b,c
0/1 : a
2/3 : b
4/5 : c
й,ё,Ё
'й,ё,Ё' ->
0/8 : й,ё,Ё
0/2 : й
3/5 : ё
6/8 : Ё
слово,ещё слово,снова слово
'слово,ещё слово,снова слово' ->
0/50 : слово,ещё слово,снова слово
0/10 : слово
11/28 : ещё слово
29/50 : снова слово
слово , word, ещё слово
'слово , word, ещё слово' ->
0/36 : слово , word, ещё слово
0/11 : слово
12/17 : word
18/36 : ещё слово
^C
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Естественно, все перечисленные выше инструменты применимы и в коде на языке C++ (поскольку язык C++ является надмножеством языка C, а не каким-то совершенно новым языком). Хотя библиотека PCRE (см. выше) гораздо чаще обсуждается и применяется относительно C++, чем C (так было до стандарта C++11).
Тем не менее, C++ предлагает свои инструменты работы с регулярными выражениями — определения в файле <regex>, работающие по честному с строками локализованных (широких) символов wsttring. Но использовать эти возможности можно только с компилятором (или его режимами), поддерживающим, как минимум, стандарт C++11. В противном случае, вы сразу же получите сообщение об ошибке:
Приложение, сделанное во многом эквивалентным показанным раньше (в C):
Теперь всё стало гораздо короче. И это понитно — поддержка <regex> сделана через STL, в частности, результат сопоставлений wcmatch — это vector<wstring>. В этом и оборотная сторона этого инструмента: при ошибках в определениях типов в вашем коде создаются целые простыни сообщений об ошибках, которые почти невозможно истолковывать.
Как и следовало ожидать, такой код совершенно корректно работает с локализованными текстами:
Интересно заглянуть в заголовки <regex>. В частности, на вариации стиля сопоставления:
Это показывает и сколько различающихся диалектов синтаксиса регулярных выражений размножилось в природе, когда один и тот же образец будет восприниматься по-разному, давая различный результат. В коде своего сопоставления вы можете определить это флагами при создании шаблона, например так:
(Здесь, заодно, показана установка флага нечувствительности к регистру текста, по умолчанию шаблон различает регистр.)
P.S. В приложении - все сразу показанные до сих пор примеры.
Тем не менее, C++ предлагает свои инструменты работы с регулярными выражениями — определения в файле <regex>, работающие по честному с строками локализованных (широких) символов wsttring. Но использовать эти возможности можно только с компилятором (или его режимами), поддерживающим, как минимум, стандарт C++11. В противном случае, вы сразу же получите сообщение об ошибке:
Код: Выделить всё
$ g++ regex1++.cc -oregex1++
In file included from /usr/include/c++/5.3.1/regex:35:0,
from regex1++.cc:3:
/usr/include/c++/5.3.1/bits/c++0x_warning.h:32:2: ошибка: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
Код: Выделить всё
#include <iostream>
#include <locale>
#include <regex>
using namespace std;
int main( int argc, char *argv[] ) {
locale::global( locale ( "ru_RU.utf8" ) );
wstring buf = L"Строка строка строка";
wstring wp = L"строка";
if( argc > 1 ) {
wchar_t warg[ strlen( argv[ 1 ] ) + 1 ];
mbstowcs( warg, argv[ 1 ], strlen( argv[ 1 ] ) + 1 );
wp = wstring( warg );
}
basic_regex<wchar_t> pattern( wp ); // образец
wcmatch match; // результат сопоставления
while( wcin >> buf ) {
wcout << L'\'' << buf << L"' ->" << endl;
if( !regex_search( (wchar_t*)buf.c_str(), match, pattern ) ) {
wcout << "no match" << endl;
continue;
}
for( auto &m: match ) wcout << ": " <<m << endl;
}
}
Как и следовало ожидать, такой код совершенно корректно работает с локализованными текстами:
Код: Выделить всё
$ g++ -Wall -std=c++11 regex1++.cc -o regex1++
$ ./regex1++ "^([^,]*),([^,]*),([^,]*)"
слово1,слово2,слово3,слово4
'слово1,слово2,слово3,слово4' ->
: слово1,слово2,слово3
: слово1
: слово2
: слово3
^C
Код: Выделить всё
static constexpr flag_type icase = regex_constants::icase;
static constexpr flag_type nosubs = regex_constants::nosubs;
static constexpr flag_type optimize = regex_constants::optimize;
static constexpr flag_type collate = regex_constants::collate;
static constexpr flag_type ECMAScript = regex_constants::ECMAScript;
static constexpr flag_type basic = regex_constants::basic;
static constexpr flag_type extended = regex_constants::extended;
static constexpr flag_type awk = regex_constants::awk;
static constexpr flag_type grep = regex_constants::grep;
static constexpr flag_type egrep = regex_constants::egrep;
Код: Выделить всё
basic_regex<wchar_t> pattern( wp, regex_constants::icase | regex_constants::awk );
P.S. В приложении - все сразу показанные до сих пор примеры.
- Вложения
-
regex_04.odt
- (43.76 КБ) 130 скачиваний
-
Regex_04.tgz
- (6.26 КБ) 141 скачивание
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Дальнейшие редакции этого текста (дополнения, изменения, исправления, ...) см. здесь: Регулярные выражения C/C++Olej писал(а):P.S. В приложении - все сразу показанные до сих пор примеры.
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Книги (переводные), из числа самых приличных, по регулярным выражениям ... для читателей начиная с самых начинающих.Olej писал(а):- как работать с регулярными выражениями в C++ и, особенно, C?
- какие там предоставляются возможности?
Ссылки для свободного скачивания:

Скачать: Friedl J. / Фридл Дж. - Mastering Regular Expressions / Регулярные выражения (3-е издание), 2008г., СПб "Символ-Плюс", ISBN: 5-93286-121-5, 608 страниц

Скачать: Ян Гойвертс, Стивен Левитан, Регулярные выражения. Сборник рецептов, 2-е издание, 2015г., СПб "Символ-Плюс", ISBN: 978-5-93286-221-6, 704 страницы

Скачать: Майкл Фицджеральд, Регулярные выражения. Основы, 2015г., "Вильямс", ISBN: 978-5-8459-1953-3, 144 страниц
- Olej
- Писатель
- Сообщения: 20332
- Зарегистрирован: 24 сен 2011, 14:22
- Откуда: Харьков
- Контактная информация:
Re: регулярные выражения в C/C++
Здесь вот онлайновый тестер регулярных выражений, который позволяет проверить результат сопоставления, без написания какого-либо программного кода и не используя никакие GNU утилиты: Regex Pal.
Очень удобно при отработке.

Очень удобно при отработке.
Кто сейчас на конференции
Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость