Преобразования верхнего и нижнего регистров C/С++ UTF-8

Проблема: Существует метод с соответствующим тестовым сценарием, который работает на одной машине и не работает с другой (подробнее см. Ниже). Я предполагаю, что что-то не так с кодом, заставляя его работать случайно на одной машине. К сожалению, я не могу найти проблему.

Обратите внимание, что использование кодировок std::string и utf-8 - это требования, на которые я не влияю. Использование С++-методов было бы вполне нормально, но, к сожалению, я ничего не нашел. Следовательно, использование С-функций.

Метод:

std::string firstCharToUpperUtf8(const string& orig) {
  std::string retVal;
  retVal.reserve(orig.size());
  std::mbstate_t state = std::mbstate_t();
  char buf[MB_CUR_MAX + 1];
  size_t i = 0;
  if (orig.size() > 0) {
    if (orig[i] > 0) {
      retVal += toupper(orig[i]);
      ++i;
    } else {
      wchar_t wChar;
      int len = mbrtowc(&wChar, &orig[i], MB_CUR_MAX, &state);
      // If this assertion fails, there is an invalid multi-byte character.
      // However, this usually means that the locale is not utf8.
      // Note that the default locale is always C. Main classes need to set them
      // To utf8, even if the system default is utf8 already.
      assert(len > 0 && len <= static_cast<int>(MB_CUR_MAX));
      i += len;
      int ret = wcrtomb(buf, towupper(wChar), &state);
      assert(ret > 0 && ret <= static_cast<int>(MB_CUR_MAX));
      buf[ret] = 0;
      retVal += buf;
    }
  }
  for (; i < orig.size(); ++i) {
    retVal += orig[i];
  }
  return retVal;
}

Тест:

TEST(StringUtilsTest, firstCharToUpperUtf8) {
  setlocale(LC_CTYPE, "en_US.utf8");
  ASSERT_EQ("Foo", firstCharToUpperUtf8("foo"));
  ASSERT_EQ("Foo", firstCharToUpperUtf8("Foo"));
  ASSERT_EQ("#foo", firstCharToUpperUtf8("#foo"));
  ASSERT_EQ("ßfoo", firstCharToUpperUtf8("ßfoo"));
  ASSERT_EQ("Éfoo", firstCharToUpperUtf8("éfoo"));
  ASSERT_EQ("Éfoo", firstCharToUpperUtf8("Éfoo"));
}

Неудавшийся тест (происходит только на одной из двух машин):

Failure
Value of: firstCharToUpperUtf8("ßfoo")
  Actual: "\xE1\xBA\x9E" "foo"
Expected: "ßfoo"

На обеих машинах установлена ​​локаль en_US.utf8. Однако они используют разные версии libc. Он работает на машине с GLIBC_2.14 независимо от того, где она была скомпилирована и не работает на другой машине, тогда как ее можно скомпилировать только там, потому что в противном случае она не имеет надлежащей версии libc.

В любом случае, есть машина, которая компилирует этот код и запускает его, пока он терпит неудачу. Что-то не так с кодом, и мне интересно, что. Указание на методы С++ (в частности, STL) также будет отличным. Boost и другие библиотеки следует избегать из-за других внешних требований.

Ответ 1

мелкий случай sharp s: ß; верхний регистр резкий s: ẞ. Использовали ли вы прописную букву в своем утверждении? Похоже, glibg 2.14 следует за инструментами pre unicode5.1 без версии верхнего острия s, а на другой машине libc использует unicode 5.1 ẞ = U1E9E...

Ответ 2

Может быть, кто-то будет использовать его (возможно, для тестов)

С этим вы можете сделать простой конвертер:) Нет дополнительных libs:)

http://pastebin.com/fuw4Uizk

1482 буквы

Пример

Ь <> ь
Э <> э
Ю <> ю
Я <> я
Ѡ <> ѡ
Ѣ <> ѣ
Ѥ <> ѥ
Ѧ <> ѧ
Ѩ <> ѩ
Ѫ <> ѫ
Ѭ <> ѭ
Ѯ <> ѯ
Ѱ <> ѱ
Ѳ <> ѳ
Ѵ <> ѵ
Ѷ <> ѷ
Ѹ <> ѹ
Ѻ <> ѻ
Ѽ <> ѽ
Ѿ <> ѿ
Ҁ <> ҁ
Ҋ <> ҋ
Ҍ <> ҍ
Ҏ <> ҏ
Ґ <> ґ
Ғ <> ғ
Ҕ <> ҕ
Җ <> җ
Ҙ <> ҙ
Қ <> қ
Ҝ <> ҝ
Ҟ <> ҟ
Ҡ <> ҡ
Ң <> ң

Ответ 3

Для меня работает следующий код С++ 11 (без учета вопрос о том, как следует перевести острые вопросы, - это оставлен без изменений. В любом случае, он постепенно прекращается с немецкого языка).

Оптимизация и верхняя буква только первая буква оставленный как упражнение.

Изменить: Как указано, codecvt, похоже, устарел. Однако он должен оставаться в стандарте до тех пор, пока не будет определена соответствующая замена. См. Устаревший заголовок <codecvt> замена

#include <codecvt>
#include <iostream>
#include <locale>

std::locale const utf8("en_US.UTF-8");

// Convert UTF-8 byte string to wstring
std::wstring to_wstring(std::string const& s) {
  std::wstring_convert<std::codecvt_utf8<wchar_t> > conv;
  return conv.from_bytes(s);
}

// Convert wstring to UTF-8 byte string
std::string to_string(std::wstring const& s) {
  std::wstring_convert<std::codecvt_utf8<wchar_t> > conv;
  return conv.to_bytes(s);
}

// Converts a UTF-8 encoded string to upper case
std::string tou(std::string const& s) {
  auto ss = to_wstring(s);
  for (auto& c : ss) {
    c = std::toupper(c, utf8);
  }
  return to_string(ss);
}

void test_utf8(std::ostream& os) {
  os << tou("foo" ) << std::endl;
  os << tou("#foo") << std::endl;
  os << tou("ßfoo") << std::endl;
  os << tou("Éfoo") << std::endl;
}    

int main() {
  test_utf8(std::cout);
}

Ответ 4

Что вы ожидаете от версии немецкого ß-символа в верхнем регистре, для этого тестового примера?

Другими словами, ваши основные допущения ошибочны.

Обратите внимание, что в Википедии в комментарии указано:

Sharp s почти уникален среди букв латинского алфавита тем, что у него нет традиционной формы в верхнем регистре (одним из немногих других примеров является kra, ĸ, который использовался в Гренландии). Это связано с тем, что он никогда не встречается первоначально в немецком тексте, и традиционная немецкая печать (которая использовала черный текст) никогда не использовала все шапки. При использовании all-caps правила текущей орфографии требуют замены ß на SS. [1] Однако в 2010 году его использование стало обязательным в официальной документации при написании географических названий во всех шапках. [2]

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

Ответ 5

Проблема заключается в том, что ваши локали, которые не утверждают, являются совместимыми, ваши локали, на которых действует assert, не соответствуют требованиям.

Технический отчет N897, требуемый в B.1.2 [ LC_CTYPE Обоснование]:

Поскольку классы символов LC_CTYPE основаны на определении символьного класса C Standard, категория не поддерживает многохарактерные элементы. Например, немецкий характер традиционно классифицируется как строчная буква. Нет соответствующей прописной буквы; при правильной капитализации немецкого текста будет заменена СС; то есть двумя символами. Этот вид преобразования выходит за рамки ключевых слов toupper и tolower.

Настоящий технический отчет был опубликован в 25 декабря 2010 года. Но согласно: https://en.wikipedia.org/wiki/Capital_%E1%BA%9E

В 2010 году использование капитала ẞ стало обязательным в официальной документации в Германии при написании географических названий во всех шапках

Но эта тема не была пересмотрена стандартным комитетом, настолько технически независимым от того, что говорит немецкое правительство, стандартизованное поведение toupper должно состоять в том, чтобы не вносить изменения в символ ß.

Причина, по которой это работает непоследовательно над машинами, setlocale:

Устанавливает указанный языковой стандарт системы или ее часть в качестве новой локали языка

Таким образом, это не совместимый системный язык, en_US.utf8, который инструктирует toupper изменять символ ß. К сожалению, специализация ctype<char>::clasic_table не доступна на ctype<wchar_t>, поэтому вы не можете изменять поведение. Оставляя вас с двумя вариантами:

  • Создайте const map<wchar_t, wchar_t> для преобразования из всех возможных строчных wchar_t в соответствующий заглавный wchar_t
  • Добавьте проверку для L'ß' следующим образом:

    int ret = wcrtomb(buf, wChar == L'ß' ? L'ẞ' : towupper(wChar), &state);
    

Live Example