自己实现JSON、XML的解析 没那么难
本文的目的,不是針對現(xiàn)有的可用于生產(chǎn)環(huán)境的JSON、XML解析器源碼進(jìn)行剖析,而是介紹文本掃描的基礎(chǔ)方法next(char),并以此為核心武器,根據(jù)目標(biāo)語言的詞法和語法特點(diǎn),一步步地組織出條例清晰、易維護(hù)的解析器代碼。希望這會是一篇實(shí)踐性強(qiáng),讓您有所收獲的文章。
另外,這里需要提前說明的是,本文所實(shí)現(xiàn)的解析器僅作為coding練習(xí)使用。一些目標(biāo)語言的規(guī)范中提到的語法,可能無法正常解析。另外,本文所實(shí)現(xiàn)的解析器也缺少大量的實(shí)例進(jìn)行測試。請不要用于生產(chǎn)用途。
前言
作為一個非科班前端程序員,我最近特別癡迷于自學(xué)《編譯原理》這門課。原因在于,自己大學(xué)時代的專業(yè)是語言學(xué),其中的理論有頗多相似之處;再加上前端工作中,模版編譯成render function,webpack通過loader加載文件等都方面涉及到了編譯。我也希望自己能多了解一些編譯知識,說不定能在日后的前端工作中能夠發(fā)揮奇效。
看了一些youtube上的公開課資源,啃了一點(diǎn)龍書這樣的編譯原理經(jīng)典作品后,我感覺自己只了解了一堆關(guān)于詞法法解析、語法解析的理論總結(jié),很難從中獲得“學(xué)會了”、“會用了”這樣的成就感。于是在稍稍有了一點(diǎn)知識基礎(chǔ)后,我開始尋找github上關(guān)于解析器的源碼。
JSON的解析
這里想給大家推薦的是JSON之父,Douglas Crockford的repo: JSON-js中的這個源代碼,它也是本文的靈感源泉:
JSON-js/json_parse.js at master · douglascrockford/JSON-js
據(jù)代碼注釋,這個文件實(shí)現(xiàn)了JSON.parse方法,使用的解析手段是recursive decending(遞歸下降分析)。
在同一個repo里還有一個json_parse_state.js文件,也是JSON.parse方法的實(shí)現(xiàn),使用的解析手段是state machine(狀態(tài)機(jī))。
其實(shí)我個人認(rèn)為上文鏈接中的源代碼使用的解析手段也是state machine,因?yàn)閞ecursive decending應(yīng)該是語法分析使用的方法來著= =。
但從代碼的清晰度上來看,json_parse.js要好不少,所以更推薦閱讀。
快速地過一遍源碼,我們可以發(fā)現(xiàn)一個核心函數(shù):
var next = function (c) {// If a c parameter is provided, verify that it matches the current character.if (c && c !== ch) {error("Expected '" + c + "' instead of '" + ch + "'");}// Get the next character. When there are no more characters, // return the empty string.ch = text.charAt(at);at += 1;return ch; }; 復(fù)制代碼這個方法相當(dāng)于一個字符掃描器,其中使用的全局變量at是當(dāng)前掃描光標(biāo)所處位置的索引,ch是當(dāng)前掃描光標(biāo)所處位置的字符。調(diào)用next方法時,如果傳入了參數(shù)c(也是一個字符),則會比較此字符與當(dāng)前掃描器所在的字符,如果不相同就會報(bào)錯,并且掃描光標(biāo)不會向前移動;如果未傳參數(shù),掃描光標(biāo)的位置和所指的字符都會向前更新一個位置。
這份代碼中的其他函數(shù),充斥著對next的調(diào)用,讓我們來看幾個例子感受一下next的用法。
var word = function () {// true, false, or null.switch (ch) {case "t":next("t");next("r");next("u");next("e");return true;case "f":next("f");next("a");next("l");next("s");next("e");return false;case "n":next("n");next("u");next("l");next("l");return null;}error("Unexpected '" + ch + "'"); }; 復(fù)制代碼word函數(shù)用來處理JSON中的三個常量token,即true, false和null。整個函數(shù)根據(jù)首字母,分別接收t->r->u->e,f->a->l->s->e,n->u->l->l這樣的字符輸入。如果其中出現(xiàn)了其他順序的字符輸入,都會拋出Error。word方法還會在匹配token的同時,返回所匹配到的token的值。3個return語句所出現(xiàn)的位置,表示word函數(shù)已經(jīng)接受了這段字符輸入,并成功解析出了一個值。
再來看另一個不傳參調(diào)用next()的例子:
var white = function () {// Skip whitespace.while (ch && ch <= " ") {next();} }; 復(fù)制代碼white函數(shù)的作用僅在于跳過空白,只要當(dāng)前字符是屬于空白的,就不停地調(diào)用next()作無條件后跳。
源碼中還有number和string函數(shù),其用途和上面的word, white一樣,只不過邏輯更為復(fù)雜,可以解析出不定長度、不定字符組合的數(shù)字和字符串。
一步一步地寫出這些“零件”的解析函數(shù)后,我們就可以進(jìn)一步寫出一些復(fù)合結(jié)構(gòu)的解析函數(shù)了,也就是源碼中的array和object函數(shù)。
最后,源碼中實(shí)現(xiàn)了可以解析任意一個JSON元素的value函數(shù)。從語法的角度講,這里所定義的value,可以是任何一個string, number, array或object,至此,我們就完成了解析所有JSON元素需要的函數(shù)。
以上就是解析的核心代碼了,個人認(rèn)為十分地易于理解并且有明確的分層,易于維護(hù)以及以后增加功能。我也在這里用同樣的next函數(shù)的手法,嘗試重寫了這個JSON解析器源代碼。作為練習(xí),我沒有實(shí)現(xiàn)escape或revive等功能,但把各個解析函數(shù)拆分得更加精細(xì)(例如為每個單字符token都寫了解析函數(shù),將array拆解為[ + elements + ]等),使得代碼更易讀。地址是:
18 JSON parser
XML的解析
有了上面的JSON解析器實(shí)現(xiàn)的“手感”,我又嘗試著用同樣的next函數(shù)手法,部分地實(shí)現(xiàn)了XML的解析。和JSON相比,個人在實(shí)現(xiàn)過程中發(fā)現(xiàn)的坑點(diǎn)主要在于:
- JSON對象基本上就是JavaScript中Object對象的字面化表示,所以每次解析出來一小段之后,直接以JavaScript數(shù)列或?qū)ο蟮男问奖4婕纯伞ML節(jié)點(diǎn)需要為其定義類似下面的數(shù)據(jù)結(jié)構(gòu),所以代碼的復(fù)雜度略有增加:
- XML對象必須作語法分析,也就是close tag有沒有匹配的問題。諸如<a><b></a></b>這樣的XML需要提示解析錯誤。不過實(shí)現(xiàn)這個也很簡單,使用一個nodeStack棧,在opentag時推入節(jié)點(diǎn);在closetag時檢查當(dāng)前節(jié)點(diǎn)是否和棧尾的tag相匹配,匹配則推出末尾的節(jié)點(diǎn);在comment節(jié)點(diǎn)或text節(jié)點(diǎn)時不作處理即可。
- comment節(jié)點(diǎn)的結(jié)束判斷。comment節(jié)點(diǎn)的格式是<!--content-->,因此在解析content部分時,每輸入一個字符,需要作3個字符的提前判斷。即,如果當(dāng)前所讀到的字符的接下來三個字符分別是-->時,停止解析。
我所實(shí)現(xiàn)的XML解析器的代碼如下(沒有實(shí)現(xiàn)self-closing tag的解析功能,例如<br>, <input>等。所有tag必須成對出現(xiàn)):
20 XML parser
轉(zhuǎn)載于:https://juejin.im/post/5a46e174518825698e726486
總結(jié)
以上是生活随笔為你收集整理的自己实现JSON、XML的解析 没那么难的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 细细品味C#——文件操作
- 下一篇: ASP.NET提取多层嵌套json数据的