如何检查 UTF-8 字符串是否以 'a' 开头

How to check if a UTF-8 string starts with an 'a'

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

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

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

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

所以我的问题是:

根本没关系。

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

"̀"

(也就是字节序列22 cc 80 22。)

您似乎在争论它不是:因为 JSON 字符串应该以 "(引号)开头,但它以 (引号 + 组合)开头重音)。

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

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

>>> json.loads(bytes.fromhex('22cc8022'))
'̀'

How to write a code that checks if a string starts with the letter a?

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

C当然没有API这样做

How do parsers (such as YAML parsers) manage to parse files without being able to answer this kind of question.

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

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

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

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

这是一个检查 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;
}

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

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

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 */
}

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

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