Regex и кириллица

273
20 февраля 2018, 07:00

Возникла та же проблема, что и в этой теме 2014 года - Регулярные выражения и кириллица Собстна, вопрос: за 4 года в плюсах не появился способ без доп. библиотек (типа boost) использовать регулярки без учета регистра в кириллице? Каким тогда образом решить эту проблему, не подключая буст?

Answer 1

Простые 8-битные локали (koi8-r, cp1251 ,ibm866)

Для любой простой 8-битной локали всё должно работать из-коробки, само собой, локаль должна быть корректно задана, терминал корректно настроен, а если в исходниках есть не-ASCII строки, то они должны быть правильно подготовлены.

// исходник в utf-8
#include <iostream>
#include <locale>
#include <regex>
#include <assert.h>
void printRxMatches (std::string line, std::regex rx) {
    auto words_begin = std::sregex_iterator(line.begin(), line.end(), rx);
    auto words_end = std::sregex_iterator();
    if (words_begin!=words_end) {
        auto wi = words_begin;
        std::cout << wi->str();
        for (++wi; wi != words_end; ++wi) {
            std::cout << ' ' << wi->str();
        }
    } else {
        std::cout << "<none>";
    }
}
int main(int argc, char **argv) {
    std::locale::global(std::locale(""));
    assert(argc==2);
    std::regex rxInput(argv[1],
            std::regex_constants::collate | std::regex_constants::icase);
    std::regex rxVowelsWords("\\b[аеёиоуыэюя]+\\b",
            std::regex_constants::collate | std::regex_constants::icase);
    for (std::string line; std::getline(std::cin, line); ) {
        std::cout << line << " : " ;
        std::cout << "InputMatches ("; 
        printRxMatches (line, rxInput);
        std::cout << ")    "; 
        std::cout << "VowelWordsMatches ("; 
        printRxMatches (line, rxVowelWords);
        std::cout << ")" << std::endl; 
    }
    return 0;
}

Сборка:

g++ -std=c++14 -fexec-charset=koi8-r -o reg-8bit reg-8bit.cpp

Запуск (множество iconv'ов симулируют изменение локали терминала на koi8):

$ echo "А я есьм строка. уууу! ЁЁ" | iconv -t KOI8-R | (LC_ALL=ru_RU.KOI8-R ./reg-8bit "$(echo "[есто]+" | iconv -t KOI8-R)" ) |iconv -f KOI8-R
А я есьм строка. уууу! ЁЁ : InputMatches (ес ст о)    VowelsWordsMatches (А я уууу ЁЁ)

Мультибайтовые локали

Для поддержки Мультибайтовых локалей (utf-8) единственным переносимым способом на чистом С++ будет использовать широкие символы. К сожалению в самом С++ нет адекватных методов для преобразования между обычной строкой в системной локали и широкой, поэтому придётся вручную доделывать костыли на основе функций из C99:

#include <iostream>
#include <locale>
#include <regex>
#include <wchar.h>
#include <assert.h>
void printRxMatches (std::wstring line, std::wregex rx) {
    auto words_begin = std::wsregex_iterator(line.begin(), line.end(), rx);
    auto words_end = std::wsregex_iterator();
    if (words_begin!=words_end) {
        auto wi = words_begin;
        std::wcout << wi->str();
        for (++wi; wi != words_end; ++wi) {
            std::wcout << ' ' << wi->str();
        }
    } else {
        std::wcout << "<none>";
    }
}

std::wstring toWString(const char *str) {
    std::wstring rv;
    mbstate_t ps;
    memset (&ps, 0, sizeof(ps));
    size_t len = strlen(str);
    rv.reserve(len);
    while (len>0) {
        wchar_t wch;
        size_t transLen = mbrtowc(&wch, str, len, &ps);
        if (transLen == (size_t)-1) {
            throw std::runtime_error ("Incorrect multibyte sequence");
        } else if (transLen == (size_t)-2) {
            throw std::runtime_error ("Incomplete string");
        } else {
            rv.push_back(wch);
            len -= transLen;
            str += transLen;
        }
    }
    rv.shrink_to_fit();
    return rv;
}
int main(int argc, char **argv) {
    std::locale::global(std::locale(""));
    assert(argc==2);
    std::wregex rxInput(toWString(argv[1]),
            std::regex_constants::collate | std::regex_constants::icase);
    std::wregex rxVowelWords(L"\\b[аеёиоуыэюя]+\\b",
            std::regex_constants::collate | std::regex_constants::icase);
    for (std::wstring line; std::getline(std::wcin, line); ) {
        std::wcout << line << " : " ;
        std::wcout << "InputMatches ("; 
        printRxMatches (line, rxInput);
        std::wcout << ")    "; 
        std::wcout << L"VowelWordsMatches ("; 
        printRxMatches (line, rxVowelWords);
        std::wcout << L")" << std::endl; 
    }
    return 0;
}

Из бонусов: * Это корректно работает для любой локали, и мультибайтовой и простой. * Потенциально переносима на любой компилятор поддерживающий C++11 и libc с поддержкой С99. * В UTF локалях работает даже эмодзи-безумие.

$ echo "А я есьм строка. уууу! ЁЁ" | iconv -t KOI8-R | (LC_ALL=ru_RU.KOI8-R ./reg-wide "$(echo "[а-ж]+" | iconv -t KOI8-R)" ) |iconv -f KOI8-R
А я есьм строка. уууу! ЁЁ : InputMatches (А е а ЁЁ)    VowelWordsMatches (А я уууу ЁЁ)
$ echo "я      
READ ALSO
Не находит библиотеки .h (C++, Visual Studio Community 2017)

Не находит библиотеки .h (C++, Visual Studio Community 2017)

VStudio не находит библиотеки, такие как conioh, time

221
Правильная остановка потока

Правильная остановка потока

Добрый деньИмеется нативный винапи поток, создаваемый функцией CreateThread(

192
Не собирается проект Veyon 4.0.4

Не собирается проект Veyon 4.0.4

При сборке проекта выдает следующую ошибку:

179
Пустой цикл в версии Release

Пустой цикл в версии Release

Использую пустой цикл

160