C/C++UTF-8大小写转换

C / C++ UTF-8 upper/lower case conversions

本文关键字:转换 大小写 C++UTF-8      更新时间:2023-10-16

问题:有一种方法带有相应的测试用例,在一台机器上工作,在另一台机器中失败(详细信息如下)。我认为代码有问题,导致它在一台机器上偶然工作。不幸的是,我找不到问题。

请注意,std::string和utf-8编码的使用是我没有真正影响的要求。使用C++方法完全可以,但不幸的是,我没有找到任何东西。因此使用了C函数。

方法:

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's 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: "xE1xBAx9E" "foo"
Expected: "ßfoo"

两台机器都安装了区域设置en_US.utf8。然而,它们使用不同版本的libc。它在GLIBC_2.14的机器上工作,与编译位置无关,在其他机器上不工作,而只能在那里编译,因为否则它缺乏正确的libc版本。

无论哪种方式,都有一台机器编译这些代码,并在失败时运行它。代码肯定有问题,我想知道是什么。指向C++方法(特别是STL)也会很棒。由于其他外部需求,应该避免使用Boost和其他库。

也许有人会使用它(可能用于测试)

有了这个,你可以制作简单的转换器:)没有额外的库:)

http://pastebin.com/fuw4Uizk

1482个字母

示例

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

以下C++11代码对我有效(忽略现在,尖锐的s应该如何翻译的问题是保持不变。无论如何,它正在慢慢地从德语中淘汰)。

优化和仅大写第一个字母作为练习离开。

编辑:如前所述,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);
}

小写锐s:ß;大写锐s:ẞ.你在断言中使用了大写版本吗?下面的glibg2.14似乎实现了unicode5.1之前的sharp s的无大写版本,而在另一台机器上,libc使用unicode5.1ẞ=U1E9E。。。

对于该测试用例,您希望德语ß字符的大写版本是什么?

换句话说,你的基本假设是错误的。

请注意,维基百科在评论中写道:

Sharp s在拉丁字母中几乎是独一无二的,因为它没有传统的大写形式(为数不多的其他例子之一是kra,ĸ,它在格陵兰语中使用)。这是因为它最初从未出现在德语文本中,而传统的德语印刷术(使用黑色字母)也从未使用所有大写字母。当使用所有大写字母时,当前的拼写规则要求将ß替换为SS。[1] 然而,在2010年,官方文件中用大写字母书写地名时,必须使用它。[2]

因此,以尖锐的s开头的基本测试用例违反了德语的规则。我仍然认为我有一点,因为最初海报的前提是错误的,字符串通常不能在所有语言的大写和小写之间自由转换。

问题是不进行断言的区域设置是兼容的,而在其上进行断言的地方设置是不兼容的。

B.1.2[LC_CTYPE基本原理]:中要求的N897技术报告

由于LC_CTYPE字符类基于C标准字符类定义,因此该类别不支持多字符元素。例如,德语字符传统上被归类为小写字母。没有对应的大写字母;在德语文本的适当大写中,将用SS代替;即通过两个字符。这种转换不在touppertolower关键字的范围内。

本技术报告于2001年12月25日发布。但根据:https://en.wikipedia.org/wiki/Capital_%E1%BA%9E

2010年,资本的使用ẞ在德国的官方文件中,当用全大写书写地名时,成为强制性的

但标准委员会尚未重新讨论这个话题,因此在技术上独立于德国政府的说法,toupper的标准化行为应该是不改变ß字符。

这在机器上工作不一致的原因是setlocale:

安装指定的系统区域设置或其部分作为新的C区域设置

因此,指示toupper修改ß字符的是不兼容的系统区域设置en_US.utf8。不幸的是,专用ctype<char>::clasic_tablectype<wchar_t>上不可用,因此您无法修改行为。给您留下2个选项:

  1. 创建一个const map<wchar_t, wchar_t>,用于从所有可能的小写wchar_t转换为相应的大写wchar_t
  2. L'ß'添加一个检查,如下所示:

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

实时示例