3

我有一个以 null 结尾的 UTF-8 字符串const char*。我想知道这个字符串的第一个字母是否是一个单独的字母a。以下代码

bool f(const char* s) {
  return s[0] == 'a';
}

是错误的,因为字符串的第一个字母(字素簇)可能是à- 由 2 个 unicode 标量值组成:a`. 所以这个非常简单的问题似乎很难回答,除非你知道字素簇是如何制作的。

尽管如此,许多库解析 UTF-8 文件(例如 YAML 文件),因此应该能够回答这类问题。但是这些库似乎并不依赖于 Unicode 库。

所以我的问题是:

  • 如何编写检查字符串是否以字母开头的代码a

  • 假设第一个问题没有简单的答案,解析器(如 YAML 解析器)如何在无法回答此类问题的情况下设法解析文件?

4

4 回答 4

5

根本没关系。

考虑:这个字符串是有效的 JSON 吗?

"̀"

(这是字节序列22 cc 80 22。)

您似乎认为它不是:因为 JSON 字符串应该以"(QUOTATION MARK) 开头,而是以(QUOTATION MARK + COMBINING GRAVE ACCENT) 开头。

唯一合理的回答是您的想法是错误的:文本序列化是根据代码点定义的。仅考虑用于处理自然语言和编辑文本的字素簇。

这当然被认为是有效的 JSON。

>>> json.loads(bytes.fromhex('22cc8022'))
'̀'
于 2017-06-19T19:42:09.520 回答
2

如何编写检查字符串是否以字母 a 开头的代码?

对此没有简单的答案。要回答这个问题,您需要测试代码点的 Unicode CCC 属性。如果它不为零,那么它是一个组合字符。

当然,C 没有这样做的 API。

解析器(例如 YAML 解析器)如何在无法回答此类问题的情况下设法解析文件。

这不是他们需要回答的问题。为什么?因为他们从不问。

如果 YAML 正在读取一个键,那么它会一直读取到名称终止字符(如:)。Unicode 组合字符不能通过这样的字符组合,YAML 规范不关心:. 如果它看到 a :,那么它就知道它已经到达了名称的末尾,而在那之前的所有内容都是一个键。

如果它正在读取一个文本字符串,那么它同样会继续读取,直到它读取一个终止字符或字符序列。

使用大多数文本格式解析文本是基于针对某些终止条件的正则表达式匹配(或类似的东西)。也就是说,字符串可以是任何字符集(或者,除了一些字符集之外的所有字符),直到终止字符。

于 2017-06-19T19:44:16.913 回答
1

s[0] == 'a'是第一个字符是否是的正确测试a。如果字符串包含 的分解版本à,那将是两个字符,a以及组合的坟墓。直到 Apple 决定在所有地方强制执行 NFD 之前,这基本上都不是问题,因为想要à单独被视为一个字符/字母的人会将其作为一个输入,而想要将其作为一个a附加标记的人会输入它作为两个。是的,这违背了规范等效的 Unicode 意图,但是规范等效的 Unicode 意图在很大程度上违背了用户的期望和意图(更不用说现有的文本和文本处理模型)。

如果你真的想检查第一个字符是 ana并且后面没有任何组合标记,这应该有效:

wchar_t tmp = WEOF;
mbrtowc(&tmp, s+1, MB_LEN_MAX, &(mbstate_t){0});
if (tmp && wcwidth(tmp)==0) {
    /* character following 'a' is a combining mark */
}

这取决于 POSIXwcwidth函数,但您可以找到它的可移植版本或根据 Unicode 表编写自己的版本(实际上,您可以编写一个更简单的函数,只检查组合状态,而不是东亚宽度属性)。

要回答您关于解析器的第二个问题,他们没有任何理由知道或关心您关心的问题。yaml、json 等文件格式不受规范等效性的约束(至少在解析级别不受约束;应用程序将解释的文件中存储的内容可能受其约束)。一个不同的 Unicode 字符序列的字符串,即使它在规范上是等效的,也是一个比较不相等的不同字符串

于 2017-06-19T20:10:09.247 回答
-1

这是一个检查 utf8 字符串是否以字母“a”开头的代码?

bool f(const char* s) {

        if (s[0] == 'a') return true;

        if (strlen(s) >= 2 && s[0] == '\xc3') {
                char s1 = s[1];
                if (s1 == '\x80') return true; // LATIN CAPITAL LETTER A WITH GRAVE
                if (s1 == '\x81') return true; // LATIN CAPITAL LETTER A WITH ACUTE
                if (s1 == '\x82') return true; // LATIN CAPITAL LETTER A WITH CIRCUMFLEX
                if (s1 == '\x83') return true; // LATIN CAPITAL LETTER A WITH TILDE
                if (s1 == '\x84') return true; // LATIN CAPITAL LETTER A WITH DIAERESIS
                if (s1 == '\x85') return true; // LATIN CAPITAL LETTER A WITH RING ABOVE

                if (s1 == '\xa0') return true; // LATIN SMALL LETTER A WITH GRAVE
                if (s1 == '\xa1') return true; // LATIN SMALL LETTER A WITH ACUTE
                if (s1 == '\xa2') return true; // LATIN SMALL LETTER A WITH CIRCUMFLEX
                if (s1 == '\xa3') return true; // LATIN SMALL LETTER A WITH TILDE
                if (s1 == '\xa4') return true; // LATIN SMALL LETTER A WITH DIAERESIS
                if (s1 == '\xa5') return true; // LATIN SMALL LETTER A WITH RING ABOVE
        }
        return false;
}
于 2017-06-19T20:00:46.917 回答