標籤:

從零開始的 JSON 庫教程(四):Unicode 解答篇

本文是《從零開始的 JSON 庫教程》的第四個單元解答篇。解答代碼位於 json-tutorial/tutorial04_answer。

(題圖 Photo by Hieu Vu Minh)

1. 實現 lept_parse_hex4()

這個函數只是讀 4 位 16 進位數字,可以簡單地自行實現:

static const char* lept_parse_hex4(const char* p, unsigned* u) { int i; *u = 0; for (i = 0; i < 4; i++) { char ch = *p++; *u <<= 4; if (ch >= 0 && ch <= 9) *u |= ch - 0; else if (ch >= A && ch <= F) *u |= ch - (A - 10); else if (ch >= a && ch <= f) *u |= ch - (a - 10); else return NULL; } return p;}

可能有同學想到用標準庫的 strtol(),因為它也能解析 16 進位數字,那麼可以簡短的寫成:

static const char* lept_parse_hex4(const char* p, unsigned* u) { char* end; *u = (unsigned)strtol(p, &end, 16); return end == p + 4 ? end : NULL;}

但這個實現會錯誤地接受 "u 123" 這種不合法的 JSON,因為 strtol() 會跳過開始的空白。要解決的話,還需要檢測第一個字元是否 [0-9A-Fa-f],或者 !isspace(*p)。但為了 strtol() 做多餘的檢測,而且自行實現也很簡單,我個人會選擇首個方案。(前兩個單元用 strtod() 就沒辨法,因為它的實現要複雜得多。)

2. 實現 lept_encode_utf8()

這個函數只需要根據那個 UTF-8 編碼表就可以實現:

static void lept_encode_utf8(lept_context* c, unsigned u) { if (u <= 0x7F) PUTC(c, u & 0xFF); else if (u <= 0x7FF) { PUTC(c, 0xC0 | ((u >> 6) & 0xFF)); PUTC(c, 0x80 | ( u & 0x3F)); } else if (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 這種操作呢?這是因為 u 是 unsigned 類型,一些編譯器可能會警告這個轉型可能會截斷數據。但實際上,配合了範圍的檢測然後右移之後,可以保證寫入的是 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 中提出,讓所有人一起討論。

推薦閱讀:

C++重載運算符的調用者是誰?
如何用C++實現一個視頻聊天伺服器,要用到那些協議和庫?
學習C++,關於小項目練手的問題總是感覺不知道從哪入手?
malloc是從系統堆裡面動態申請空間,那與char *申請的空間有什麼區別?
c++程序哪些應該放在頭文件裡面,哪些應該放在源文件裡面?

TAG:JSON | CC | 教程 |