17

问题:有一种方法具有相应的测试用例,可以在一台机器上运行而在另一台机器上失败(详情如下)。我认为代码有问题,导致它偶然在一台机器上工作。不幸的是我找不到问题。

请注意,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: "\xE1\xBA\x9E" "foo"
Expected: "ßfoo"

两台机器都安装了语言环境 en_US.utf8。然而,他们使用不同版本的 libc。它可以在带有 GLIBC_2.14 的机器上工作,与编译位置无关,并且不能在另一台机器上工作,而它只能在那里编译,否则它缺少正确的 libc 版本。

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

4

5 回答 5

10

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

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

http://pastebin.com/fuw4Uizk

1482 个字母

例子

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

下面的 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);
}
于 2015-10-10T21:23:14.947 回答
1

小写尖 s : ß; 大写锐利 s:ẞ. 您是否在断言中使用了大写版本?似乎 glibg 2.14 遵循 pre unicode5.1 没有大写版本的 sharp s,而在另一台机器上 libc 使用 unicode 5.1 ẞ=U1E9E ...

于 2012-09-19T12:10:20.493 回答
1

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

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

请注意,评论中的维基百科指出:

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

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

于 2012-09-19T11:09:28.163 回答
0

问题是您不断言的语言环境是合规的,而您的断言确实触发的语言环境是不合规的。

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

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

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

2010 年,在德国的官方文件中,当以全大写形式书写地名时,必须使用大写字母 ẞ

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

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

将指定的系统语言环境或其部分安装为新的 C 语言环境

因此,它是不符合系统语言环境的,en_US.utf8即指示toupper修改 ß 字符。不幸的是,专业化ctype<char>::clasic_table, 不可用,ctype<wchar_t>因此您无法修改行为。留给您两个选择:

  1. 创建一个const map<wchar_t, wchar_t>从每个可能的小写字母wchar_t到相应大写字母的转换wchar_t
  2. 添加这样的检查L'ß'

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

Live Example

于 2016-06-01T14:20:25.490 回答