本身实现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

相关文章
相关标签/搜索