Skip to content

Latest commit

History

History
100 lines (80 loc) · 4.35 KB

File metadata and controls

100 lines (80 loc) · 4.35 KB

从零开始的 JSON 库教程(四):Unicode 解答篇

  • Milo Yip
  • 2016/10/6

本文是《从零开始的 JSON 库教程》的第四个单元解答篇。解答代码位于 json-tutorial/tutorial04_answer

1. 实现 lept_parse_hex4()

这个函数只是读 4 位 16 进制数字,可以简单地自行实现:

staticconstchar*lept_parse_hex4(constchar*p, unsigned*u) {
inti;
*u=0;
for (i=0; i<4; i++) {
charch=*p++;
*u <<= 4;
if (ch >= '0'&&ch <= '9') *u |= ch-'0';
elseif (ch >= 'A'&&ch <= 'F') *u |= ch- ('A'-10);
elseif (ch >= 'a'&&ch <= 'f') *u |= ch- ('a'-10);
elsereturnNULL;
}
returnp;
}

可能有同学想到用标准库的 strtol(),因为它也能解析 16 进制数字,那么可以简短的写成:

staticconstchar*lept_parse_hex4(constchar*p, unsigned*u) {
char*end;
*u= (unsigned)strtol(p, &end, 16);
returnend==p+4 ? end : NULL;
}

但这个实现会错误地接受 "\u 123" 这种不合法的 JSON,因为 strtol() 会跳过开始的空白。要解决的话,还需要检测第一个字符是否 [0-9A-Fa-f],或者 !isspace(*p)。但为了 strtol() 做多余的检测,而且自行实现也很简单,我个人会选择首个方案。(前两个单元用 strtod() 就没办法,因为它的实现要复杂得多。)

2. 实现 lept_encode_utf8()

这个函数只需要根据那个 UTF-8 编码表就可以实现:

staticvoidlept_encode_utf8(lept_context*c, unsignedu) {
if (u <= 0x7F) PUTC(c, u&0xFF);
elseif (u <= 0x7FF) {
PUTC(c, 0xC0 | ((u >> 6) &0xFF));
PUTC(c, 0x80 | ( u&0x3F));
}
elseif (u <= 0xFFFF) {
PUTC(c, 0xE0 | ((u >> 12) &0xFF));
PUTC(c, 0x80 | ((u >> 6) &0x3F));
PUTC(c, 0x80 | ( u&0x3F));
}
else {
assert(u <= 0x10FFFF);
PUTC(c, 0xF0 | ((u >> 18) &0xFF));
PUTC(c, 0x80 | ((u >> 12) &0x3F));
PUTC(c, 0x80 | ((u >> 6) &0x3F));
PUTC(c, 0x80 | ( u&0x3F));
}
}

有同学可能觉得奇怪,最终也是写进一个 char,为什么要做 x & 0xFF 这种操作呢?这是因为 uunsigned 类型,一些编译器可能会警告这个转型可能会截断数据。但实际上,配合了范围的检测然后右移之后,可以保证写入的是 0~255 内的值。为了避免一些编译器的警告误判,我们加上 x & 0xFF。一般来说,编译器在优化之后,这与操作是会被消去的,不会影响性能。

其实超过 1 个字符输出时,可以只调用 1 次 lept_context_push()。这里全用 PUTC() 只是为了代码看上去简单一点。

3. 代理对的处理

遇到高代理项,就需要把低代理项 \uxxxx 也解析进来,然后用这两个项去计算出码点:

case'u':
if (!(p=lept_parse_hex4(p, &u)))
STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX);
if (u >= 0xD800&&u <= 0xDBFF) { /* surrogate pair */if (*p++!='\\')
STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
if (*p++!='u')
STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
if (!(p=lept_parse_hex4(p, &u2)))
STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_HEX);
if (u2<0xDC00||u2>0xDFFF)
STRING_ERROR(LEPT_PARSE_INVALID_UNICODE_SURROGATE);
u= (((u-0xD800) << 10) | (u2-0xDC00)) +0x10000;
}
lept_encode_utf8(c, u);
break;

4. 总结

JSON 的字符串解析终于完成了。但更重要的是,同学通过教程和练习后,应该对于 Unicode 和 UTF-8 编码有基本了解。使用 Unicode 标准去处理文本数据已是世界潮流。虽然 C11/C++11 引入了 Unicode 字符串字面量及少量函数,但仍然有很多不足,一般需要借助第三方库。

我们在稍后的单元还要处理生成时的 Unicode 问题,接下来我们要继续讨论数组和对象的解析。

如果你遇到问题,有不理解的地方,或是有建议,都欢迎在评论或 issue 中提出,让所有人一起讨论。