在分析一段字符串的时候,可以借助字符串的indexOf等方法,或者是正则表达式,可是,如果需要解析的是下面这段字符串:

<group>
    <arc c-bind:cx='_width*0.5' c-bind:cy='_height*0.5' c-bind:radius1='_min*0.5' radius2='100' deg='120deg'>
    </arc>
    <group c-for='value in dataList'>
        <circle c-bind:cx='value' c-on:click='doit0' c-bind:cy='value' radius='10'></circle>
        <text c-bind:x='value+20' c-bind:y='value' c-bind:content='value' fill-color='red'></text>
    </group>
    <path>
        <move-to x='10' y='10'></move-to>
        <line-to x='100' y='100'></line-to>
    </path>
    <text c-bind:x='_width*0.5' c-bind:y='_height*0.5'>文字</text>
</group>

什么时候可能会需要解析类似这样的字符串?比如你可能希望的nodejs环境开发一个爬虫,分析爬到的页面内容,或者是像上面的设计,用html来表达希望绘制什么样的图形后通过js在canvas画布上绘制出用户的意图等。

下面,我们来一起看看,具体的怎么一步步分析处理上面的字符串包含的信息的。

我们把分析分为这几个步骤: 分析出符号 → 分析出单词 → 单词信息分析 → 获取整体信息

## 分析出符号

我们把一个最小的类似"语句"的称为单词(和编译原理中的单词加以区分),比如这里的一个标签,而为了得到单词,首先需要分析的称为符号。比如对 "<div>你好</div>"而言,就存在三个符号: "<div>"、"你好"和"</div>"。

因此,符号就是容易分析出且在此基础上很容易分析出单词的存在,具体什么是符号,取决于分析的内容和目标。

上面的内容,分析出符号的最终结果就是:

比如第二个符号,原始代码是"<arc c-bind:cx='_width*0.5' c-bind:cy='_height*0.5' c-bind:radius1='_min*0.5' radius2='100' deg='120deg'>",经过分析得到,他是一个标签的开始部分,名称叫arc,有一些属性等。

那么,这样的符号是如何分析出来的?很简单,通过while循环即可。

在分析一个符号开始前,如果遇到的第一个非空白字符是"<",说明这是一个标签(可能是开始、结束或自闭合的),直到遇到">"的时候,分析结束,也就是获取了一个符号。

而如果在分析一个符号开始前,遇到的第一个非空白字符不是"<",说明这是一段文本,等遇到"<"的时候,回退一步即可获得一段文本符号。

而对于标签符号,只要在分析的时候额外加些判断,就可以获取更丰富的信息并获取属性值等。从而,就得到了上面的符号列表。

## 分析出单词

在上面符号列表的基础上,我们接下来将分析出下面的单词列表:

很明显,单词明显比符号要少。那么,如何通过符号列表获取单词列表?这会比上一步容易的多。

首先,你读取了符号列表的第一个符号,如果是文本或者自闭合标签,就已经获取了第一个单词,否则,一定是开始标签。

现在,你把这个开始标签存放到栈(先进后出)中去,并占据单词列表的一个位置,接着观察下一个符号,如果是文本或者自闭合标签,就是一个新的单词,否则查看是否是开始标签,如果是,同样的处理,否则,判断是否和当前栈顶的元素匹配,如果匹配,出栈并完成了一个单词的匹配,不然就抛出错误。

如此反复下去,直到符号列表遍历完毕,观察此时的栈,如果还有元素,可以默认自闭合即可。

## 单词信息分析

在上一步,除了分析出单词外,还需要额外给每个单词标记层次(例如:根group是第一层,根的孩子是第二层,以此类推)。

层次是如何获取的?比如,你当前的层次是deep,如果遇到的下一个符号是开始符号,那么,下一个单词(或文本,下同)的层次就一个是deep+1,如果遇到的是结束符号,下一个单词就应该是deep-1,否则就依旧是deep。

获取整体信息

对于一个字符串表达式而言,就是求值,对于json字符串而言,就是获取json对象,而在此处,就是要获取一个带有关系的DOM树。

先看看最终的结果:

我们拿节点"<circle c-bind:cx='value' c-on:click='doit0' c-bind:cy='value' radius='10'></circle>"举例子。

通过parentNode和childNodes指明了它的父节点是第二个节点group,没有孩子,前一个兄弟preNode为null没有,后一个兄弟nextNode为4,也就是text标签。

同样的,我们来说说思路。

在单词列表中,拿出一个单词(其实也就是节点或文本节点),如果下一个单词的deep和当前的一样,就是兄弟关系,如果小一级,就是当前结点的孩子,这都比较容易。

如果比当前结点的deep大,怎么办?说明当前这个节点是叶子,需要回溯,回溯到deep和新的节点deep一样的即可,那么新的节点就是此节点的下一个兄弟,以此类推即可。

小结

上述关于解析字符串html的算法,已经封装并对外开源: 解析xhtml为json对象

03-05 21:27