LINUX.ORG.RU

Приведение к верхнему регистру

 , ,


0

1

Имеется программа на C++, где в ряде мест проводится сравнение вводимого пользователем текста с имеющимся набором строк. При этом сравниваемые строки приводятся к верхнему регистру вызовом std::towupper. Для 26 английских символов это работает, для остальных — нет.

Пример приведения.

Как я понял, проблема в том, что в соответствующих местах программы текущей локалью является «C». Если перед вызовом towupper переключиться на локаль с UTF-8, например вызвать

std::setlocale(LC_ALL, "ru_RU.utf8");
то всё приводится правильно.

В программе уже имеются 8 вызовов setlocale: перед каждым вызовом функций, преобразующих wide string в multibyte и multibyte в wide вызывается

setlocale(LC_ALL, "");
а после преобразования вызывается
setlocale(LC_ALL, "C");

Вопросы:

1. Подозреваю, что локаль «ru_RU.utf8» для большинства пользователей недоступна. Какая локаль с UTF-8 гарантированно есть у всех? «en_US.utf8»? Или вызвать для "", считая, что сейчас у всех неангличан системная локаль юникодная? Или это сработает для соответствующего языка и в неюникодной локали? — Единогласно решили вызывать "", а у кого ещё нет Юникода — их проблема.

2. Когда и где вызывать setlocale? Перед каждым блоком вызовов towupper? — Да. А потом сбрасывать в «C».

3. Как сохранить текущее состояние локали, чтобы к нему потом вернуться? Или не заморачиваться и считать что всюду «C»? — Да, всюду «C».

4. Какая категория отвечает за правила смены регистра? LC_COLLATE? LC_CTYPE? Или тоже не заморачиваться и писать всюду LC_ALL? — Проще конвертировать функцией toupper, у которой 2-й параметр — имя локали. Если в итераторе требуется функция с 1 параметром, писать лямбду либо приватную функцию, которая внутри вызывает toupper с нужным параметром.

Итог: https://github.com/MeridianOXC/OpenXcom/commit/31594f7c7e45bf0c1e0868975ef6da...

Поиск работает, но функция, которая использует std::transform(...), иногда находит одиночные русские буквы в строках, где их нет. Так, буква «ц» нашлась в:
«Heavy plasma»
«Heavy plasma clip»
«Малая пусковая установка»
«Парализующая бомба»
«й» нашлась в:
«Glock 18»
«Магазин Glock 18»
«w» нашлась в:
«Bone Club»
«Shogg Staff»
«Tonfa»
«Baseball Bat»
и ещё более 100 совпадений.

Для более длинных поисковых запросов ложноположительные ответы не обнаружены.

Так как проблема с «w» проявляется и в непатченой программе, можно считать патч рабочим.

Дополнение: Как объяснил вернувшийся автор, поиск ведётся по набору стрингов, которые видны не на всех экранах или через подменю — не только название предмета, но и категории: «подводное оружие», «холодное оружие», «боеприпасы» и т.п.. В частности, по «w» находит предметы категории «Underwater», по «ц» — «Технологии пришельцев», по «й» — «Технологии людей».

★★★★★

Последнее исправление: question4 (всего исправлений: 5)

В Юникодных локалях вроде все языки работают (на практике воде так, но в общем случая я могу и ошибаться). Поэтому «en_US.utf8» должно подойти.

setlocale() переключает глобальное состояние, которое влияет на towupper(c), которая функция из C. В плюсах можно делать так и не заморачиваться с переключением глобального состояния:

std::towupper(c, std::locale("en_US.utf8"));
(Только в теле цикла наверное создавать локаль не стоит на каждый символ.)

xaizek ★★★★★
()
Последнее исправление: xaizek (всего исправлений: 2)
Ответ на: комментарий от xaizek

В Юникодных локалях вроде все языки работают (на практике воде так, но в общем случая я могу и ошибаться). Поэтому «en_US.utf8» должно подойти.

На моей машине работают en_US.utf8 и ru_RU.utf8, но не работает fr_FR.utf8. Поэтому и спросил. А эта программа должна работать и под Windows, и под Андроидом.

towupper(c, std::locale(«en_US.utf8»));

Спасибо.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Доступные языки зависят от того, что установлено в систему. Но если локаль utf8, то другие языки (по крайней мере установленные) работают (у меня en_US.utf8 и с русским проблем нет).

И только сейчас заметил:

+[~]$ locale -a | egrep ^C
C
C.utf8
Оказывается, «C.utf8» тоже существует.

Кстати за setlocale(LC_ALL, ""); может сойти std::locale("") (я не до конца уверен, что они гарантированно эквивалентны по стандарту).

xaizek ★★★★★
()
Ответ на: комментарий от xaizek

Тогда другой вопрос. В 3 местах есть вызовы вида:

std::transform(itemLocalName.begin(), itemLocalName.end(), itemLocalName.begin(), towupper);
https://github.com/MeridianOXC/OpenXcom/blob/oxce3.5-plus-proto/src/Battlesca...

Я ещё не разобрался, что они делают. Как подставить локаль в их параметры?

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Лямбд в коде не вижу, поэтому написать так:

static std::wint_t upCase(std::wint_t c)
{
    static std::locale userLocale("");
    // оно без w: http://en.cppreference.com/w/cpp/locale/toupper
    return std::toupper(c, userLocale);
}

...
std::transform(itemLocalName.begin(), itemLocalName.end(), itemLocalName.begin(), &upCase);
...
Кстати есть там C++11, но функцией всегда можно.

std::transform это практически тот же самый цикл эквивалентный for (auto & c : projectName) c = towupper(c);.

xaizek ★★★★★
()
Последнее исправление: xaizek (всего исправлений: 1)
Ответ на: комментарий от xaizek

оно без w: http://en.cppreference.com/w/cpp/locale/toupper

То-то у меня ничего не компилируется :)

Кстати есть там C++11, но функцией всегда можно.

В смысле? При сборке используется ключ "-std=gnu++11". Я так понимаю, это C++11? Как можно поступить, если там C++11?

question4 ★★★★★
() автор топика
Ответ на: комментарий от xaizek

Оказывается, «C.utf8» тоже существует.

Проверил. В моей системе отсутствует.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Да, gnu++11 является разновидностью С++11. Тогда можно лямбду передать:

std::locale userLocale("");
std::transform(itemLocalName.begin(), itemLocalName.end(), itemLocalName.begin(),
               [&userLocale](std::wint_t c) {
                   return std::toupper(c, userLocale);
               });

xaizek ★★★★★
()
Последнее исправление: xaizek (всего исправлений: 1)
Ответ на: комментарий от xaizek

itemLocalName это судя по коду,

std::wstring
и в лямбде нужно тогда писать
wchar_t

std::locale userLocale("");
std::transform(itemLocalName.begin(), itemLocalName.end(), itemLocalName.begin(),
               [&userLocale](wchar_t c) {
                   return std::toupper(c, userLocale);
               });
fsb4000 ★★★★★
()

ICU пользуй. Плюсцы и их строки и обработка текста малосовместимы. Скажем дружно спасибо разработчикам стандартов C и C++

dzidzitop ★★
()

Используй дефолтную локаль и всё. Если пользователь не хочет, чтобы у него работал юникод - его проблемы.

KivApple ★★★★★
()
Ответ на: комментарий от xaizek

Лямбд в коде не вижу, поэтому написать так:

Не «олдскульно», правильный до-C++11 код будет с использованием std::bind2nd.

DELIRIUM ☆☆☆☆☆
()
Ответ на: комментарий от fsb4000

Да, согласен, wint_t это с сигнатуры towupper.

xaizek ★★★★★
()
Ответ на: комментарий от KivApple

Используй дефолтную локаль и всё. Если пользователь не хочет, чтобы у него работал юникод - его проблемы.

Так и решил.

Хотя человек может иметь, скажем, только чешскую локаль, и пытаться что-то делать в ней.

question4 ★★★★★
() автор топика
Ответ на: комментарий от dzidzitop

ICU пользуй. Плюсцы и их строки и обработка текста малосовместимы. Скажем дружно спасибо разработчикам стандартов C и C++

Стандартные компоненты умеют делать нормализацию Юникода? Там ещё буквы «ё» кое-где мелькают :)

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Чтобы функция не была доступна за пределами этого файла. Можно убрать, если будет использоваться из нескольких мест.

xaizek ★★★★★
()
Ответ на: комментарий от question4

Ну если пишем а старом стандарте, то оно не депрекейтед. И лучше использовать готовый код, чем самому его генерить.

DELIRIUM ☆☆☆☆☆
()
Ответ на: комментарий от xaizek

Чтобы функция не была доступна за пределами этого файла. Можно убрать, если будет использоваться из нескольких мест.

Она вызывается из 3 мест в одной функции. Я её объявил как private в namespace Inventory. Но без static в хедере отказывалось компилироваться.

Вариант без лямбды собрался и работает. Спасибо. Попробую ещё с лямбдой, но боюсь, выйдет длиннее.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

в стандартных средствах и буквы «ё» нет.

и нормализации тоже нет.

но можно из wchar_t получить char32_t * и обратно. это практически всё что там есть.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 1)
Ответ на: комментарий от dzidzitop

в стандартных средствах и буквы «ё» нет.

Есть. И без проблем меняет регистр. Насколько я понимаю, всё это сидит в glibc.

Отсюда вывод: ты со стандартными средствами не знаком, и спрашивать тебя бесполезно :)

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Да-да. В стандарте C есть табличка, где букве ё сопоставлено значение какого-то целочисленного типа и символьный литерал. Так?

Или ты свой рантайм выдаёшь за языковые средства?

Ответ - конечно же нет. Так что хватит нести хрень.

Но, вообще, пользуйся своими стандартными средствами. Колхоз - это дело добровольное.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 3)
Ответ на: комментарий от DELIRIUM

правильный до-C++11 код будет с использованием std::bind2nd.

Это непередаваемое чувство, когда хрень уже закопали, а ты так и не выучил что она делала.

d_a ★★★★★
()
Ответ на: комментарий от question4

Всё понятно. Пользуйся «стандартным рантаймом» и «средствами ОС» и радуйся своей розовой беспечной жизни.

dzidzitop ★★
()
Ответ на: комментарий от dzidzitop

Когда надо объяснять «чайнику» как устанавливать каждую стороннюю библиотеку под оффтопик, отказ от лишней библиотеки очень облегчает жизнь :)

question4 ★★★★★
() автор топика

xaizek, fsb4000, функция с

std::transform(itemLocalName.begin(), itemLocalName.end(), itemLocalName.begin(), upCase);
иногда находит отдельные буквы даже там, где их нет. Например, «й» в «Glock 18» и «Магазин Glock 18». При записи через лямбду — тот же результат.

Завтра поэкспериментирую.

question4 ★★★★★
() автор топика
Последнее исправление: question4 (всего исправлений: 1)
Ответ на: комментарий от question4

Не должно быть так.

#include <algorithm>
#include <iostream>
#include <locale>
#include <string>

int main() {
    // std::setlocale(LC_ALL, "");
    std::wstring itemLocalName = L"Glock 18";
    std::locale userLocale("");
    std::transform(itemLocalName.begin(), itemLocalName.end(), itemLocalName.begin(),
                   [&userLocale](wchar_t c) {
                       return std::toupper(c, userLocale);
                   });
    std::wcout << itemLocalName << L'\n';
}
$ g++ -std=gnu++11 test.cpp
$ ./a.out
GLOCK 18

Может что-то не то с временем жизни объектов.

xaizek ★★★★★
()
Ответ на: комментарий от question4

Если стандартные классы умеют делать нормализацию, их хватит.

Раньше не умели, сейчас как ХЗ. Может что-то и умеет, но за Visual Studio и компилятор от M$ не ручаюсь. Они скорее всего не умеют.

peregrine ★★★★★
()
Ответ на: комментарий от question4

Когда надо объяснять «чайнику» как устанавливать каждую стороннюю библиотеку под оффтопик, отказ от лишней библиотеки очень облегчает жизнь :)

Что ж ты делаешь-то? Под оффтопик нужно библиотеки вместе с программой запаковывать в инсталлятор.

proud_anon ★★★★★
()
Ответ на: комментарий от question4

Я не имел в виду кодогенерацию, имел в виду, что нахрен надо руками писать функтор, если в данном случае он легко делается с помощью std::bind2nd.

DELIRIUM ☆☆☆☆☆
()
Ответ на: комментарий от DELIRIUM

Чтобы использовать std::bind2nd надо сделать класс и отнаследовать его от std::binary_function, с указателями на функции оно не работает само по себе.

xaizek ★★★★★
()
Ответ на: комментарий от proud_anon

У меня Slackware, так что уже две системы.

xaizek ★★★★★
()

Во-первых, сравнивать надо не «приведением к верхнему регистру», а т. н. casefolding-ом.

Во-вторых, для этого нужно взять что-то вроде ICU.

anonymous
()
Ответ на: комментарий от peregrine

C++ преобразовать русскую строку в нижний регистр
Это уже смотрел? Правда там к нижнему регистру приводили, а у тебя к верхнему надо.

Посмотрел. Всё то же. Избежал кучи проблем — всё с самого начало было во wstring-ах :)

question4 ★★★★★
() автор топика
Ответ на: комментарий от xaizek

Может что-то не то с временем жизни объектов.

В смысле? Оба стринга убиваются до начала сравнения?

Попробовал заменять файлы русификации, в результате прекратило воспроизводиться. Теперь не воспроизводится даже с той русификацией, с которой начинал.

question4 ★★★★★
() автор топика
Ответ на: комментарий от xaizek

Смог воспроизвести баг с немодифицированным файлом. Нашёл «w» в «Bone Club». Баг не наш :)

question4 ★★★★★
() автор топика
Ответ на: комментарий от xaizek

Твоя правда, давно я не писал на «старых» крестах =)

DELIRIUM ☆☆☆☆☆
()
Ответ на: комментарий от anonymous

Во-первых, сравнивать надо не «приведением к верхнему регистру», а т. н. casefolding-ом.

Во-вторых, для этого нужно взять что-то вроде ICU.

Выше уже объяснил. Основные разработчики не хотят включать дополнительные библиотеки. Поэтому пока нужно обойтись средствами стандартных библиотек.

question4 ★★★★★
() автор топика
Ответ на: комментарий от xaizek

&upCase);

Заметил у себя опечатку. И с «&upCase», и с «upCase» без «&» компилируется и работает одинаково. Есть какое-то правило, как положено писать?

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

В смысле? Оба стринга убиваются до начала сравнения?

Или одна из строк. В любом случае может быть ошибка.

Заметил у себя опечатку. И с «&upCase», и с «upCase» без «&» компилируется и работает одинаково. Есть какое-то правило, как положено писать?

Амперсанд для функций опционален, они автоматически разлагаются до указателей. Я пишу явно, общего правила нет, работает оно одинаково.

xaizek ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.