本身實現JSON、XML的解析 沒那麼難

本文的目的,不是針對現有的可用於生產環境的JSON、XML解析器源碼進行剖析,而是介紹文本掃描的基礎方法next(char),並以此爲核心武器,根據目標語言的詞法和語法特色,一步步地組織出條例清晰、易維護的解析器代碼。但願這會是一篇實踐性強,讓您有所收穫的文章。javascript

另外,這裏須要提早說明的是,本文所實現的解析器僅做爲coding練習使用。一些目標語言的規範中提到的語法,可能沒法正常解析。另外,本文所實現的解析器也缺乏大量的實例進行測試。請不要用於生產用途。前端

前言

做爲一個非科班前端程序員,我最近特別癡迷於自學《編譯原理》這門課。緣由在於,本身大學時代的專業是語言學,其中的理論有頗多類似之處;再加上前端工做中,模版編譯成render function,webpack經過loader加載文件等都方面涉及到了編譯。我也但願本身能多瞭解一些編譯知識,說不定能在往後的前端工做中可以發揮奇效。java

看了一些youtube上的公開課資源,啃了一點龍書這樣的編譯原理經典做品後,我感受本身只瞭解了一堆關於詞法法解析、語法解析的理論總結,很難從中得到「學會了」、「會用了」這樣的成就感。因而在稍稍有了一點知識基礎後,我開始尋找github上關於解析器的源碼。node

JSON的解析

這裏想給你們推薦的是JSON之父,Douglas Crockford的repo: JSON-js中的這個源代碼,它也是本文的靈感源泉:webpack

JSON-js/json_parse.js at master · douglascrockford/JSON-jsgit

據代碼註釋,這個文件實現了JSON.parse方法,使用的解析手段是recursive decending(遞歸降低分析)。程序員

在同一個repo裏還有一個json_parse_state.js文件,也是JSON.parse方法的實現,使用的解析手段是state machine(狀態機)。github

其實我我的認爲上文連接中的源代碼使用的解析手段也是state machine,由於recursive decending應該是語法分析使用的方法來着= =。web

但從代碼的清晰度上來看,json_parse.js要好很多,因此更推薦閱讀。json

快速地過一遍源碼,咱們能夠發現一個核心函數:

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;
};
複製代碼

這個方法至關於一個字符掃描器,其中使用的全局變量at是當前掃描光標所處位置的索引,ch是當前掃描光標所處位置的字符。調用next方法時,若是傳入了參數c(也是一個字符),則會比較此字符與當前掃描器所在的字符,若是不相同就會報錯,而且掃描光標不會向前移動;若是未傳參數,掃描光標的位置和所指的字符都會向前更新一個位置。

這份代碼中的其餘函數,充斥着對next的調用,讓咱們來看幾個例子感覺一下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 + "'");
};
複製代碼

word函數用來處理JSON中的三個常量token,即true, falsenull。整個函數根據首字母,分別接收t->r->u->e,f->a->l->s->e,n->u->l->l這樣的字符輸入。若是其中出現了其餘順序的字符輸入,都會拋出Error。word方法還會在匹配token的同時,返回所匹配到的token的值。3個return語句所出現的位置,表示word函數已經接受了這段字符輸入,併成功解析出了一個值。

再來看另外一個不傳參調用next()的例子:

var white = function () {

// Skip whitespace.

    while (ch && ch <= " ") {
        next();
    }
};
複製代碼

white函數的做用僅在於跳過空白,只要當前字符是屬於空白的,就不停地調用next()做無條件後跳。

源碼中還有number和string函數,其用途和上面的word, white同樣,只不過邏輯更爲複雜,能夠解析出不定長度、不定字符組合的數字和字符串。

一步一步地寫出這些「零件」的解析函數後,咱們就能夠進一步寫出一些複合結構的解析函數了,也就是源碼中的array和object函數。

最後,源碼中實現了能夠解析任意一個JSON元素的value函數。從語法的角度講,這裏所定義的value,能夠是任何一個string, number, array或object,至此,咱們就完成了解析全部JSON元素須要的函數。

以上就是解析的核心代碼了,我的認爲十分地易於理解而且有明確的分層,易於維護以及之後增長功能。我也在這裏用一樣的next函數的手法,嘗試重寫了這個JSON解析器源代碼。做爲練習,我沒有實現escape或revive等功能,但把各個解析函數拆分得更加精細(例如爲每一個單字符token都寫了解析函數,將array拆解爲[ + elements + ]等),使得代碼更易讀。地址是:

18 JSON parser

XML的解析

有了上面的JSON解析器實現的「手感」,我又嘗試着用一樣的next函數手法,部分地實現了XML的解析。和JSON相比,我的在實現過程當中發現的坑點主要在於:

  • JSON對象基本上就是JavaScript中Object對象的字面化表示,因此每次解析出來一小段以後,直接以JavaScript數列或對象的形式保存便可。XML節點須要爲其定義相似下面的數據結構,因此代碼的複雜度略有增長:
Node {
  tagName //節點標籤名
  attrs //節點上的屬性,爲key/value的數組
  children //節點的子節點,爲Node的數組
}
複製代碼
  • XML對象必須做語法分析,也就是close tag有沒有匹配的問題。諸如<a><b></a></b>這樣的XML須要提示解析錯誤。不過實現這個也很簡單,使用一個nodeStack棧,在opentag時推入節點;在closetag時檢查當前節點是否和棧尾的tag相匹配,匹配則推出末尾的節點;在comment節點或text節點時不做處理便可。
  • comment節點的結束判斷。comment節點的格式是<!--content-->,所以在解析content部分時,每輸入一個字符,須要做3個字符的提早判斷。即,若是當前所讀到的字符的接下來三個字符分別是-->時,中止解析。

我所實現的XML解析器的代碼以下(沒有實現self-closing tag的解析功能,例如<br>, <input>等。全部tag必須成對出現):

20 XML parser

相關文章
相關標籤/搜索