在上次已经
介绍过shpaml ,
它的代码总共只有365行, 既然这么一点代码, 没有理由不分析分析它的具体实现.
首先, 代码可以在这里看到:
http://shpaml.webfactional.com/source_code
首先是入口出的代码:
1
2
3
4
5
6
7
8
if __name__ == "__main__" :
# if file name is given convert file, else convert stdin
import sys
if len ( sys . argv ) == 2 :
shpaml_text = open ( sys . argv [ 1 ]) . read ()
else :
shpaml_text = sys . stdin . read ()
sys . stdout . write ( convert_text ( shpaml_text ))
这里面是实现以下3种语法的功能, 太简单就不说了:
python -c “import shpaml; shpaml.convert_text()” echo ‘b | foo’ |
python shpaml.py touch test.shpaml; python shpaml.py test.shpaml
然后, 从convert_text函数,
一直深入调用到了indent_lines函数(外面的几层都是包装), 这个才是重心.
我们知道, html是树状的, 输入的shpaml格式的文档本质上也是树状的.
我们需要把shpaml按照树状的方式解析出来,
同时对分析出来的数据做处理(加<>以及结尾加</tag>).
简单地介绍下代码里面的处理方式. 程序内嵌一个recurse函数,
这个函数的输入是字符串列表(就是需要转换的文本啦),
处理文本的时候如果发现有新的子树, 就会嵌套调用recurse,
用函数调用栈的方式来遍历tag树. 每次处理文本,
都会把生成的文本放到output这个字符串列表里面去. 下面是具体的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def indent_lines ( lines ,
output ,
branch_method ,
leaf_method ,
pass_syntax ,
flush_left_syntax ,
flush_left_empty_line ,
indentation_method ,
get_block ,
):
"""Returns None.
一堆注释不管它
"""
append = output . append
# 递归调用函数
def recurse ( prefix_lines ):
# 循环解析传进来的字符串列表
while prefix_lines :
# 列表已经处理过了, 分割成空格的前缀和后面的字符
prefix , line = prefix_lines [ 0 ]
if line == '' :
prefix_lines . pop ( 0 )
append ( '' )
continue
# 我们看看这一行是否有缩进
block_size = get_block ( prefix_lines )
if block_size == 1 :
# 如果没有缩进, 就根据状况来处理
prefix_lines . pop ( 0 )
if line == pass_syntax :
pass
elif line . startswith ( flush_left_syntax ):
append ( line [ len ( flush_left_syntax ):])
elif line . startswith ( flush_left_empty_line ):
append ( '' )
else :
append ( prefix + leaf_method ( line ))
else :
# 如果是一个新的缩进, 我们需要找到缩进的结尾, 然后把这一块数据取出来,
# 让branch_method处理下来
block = prefix_lines [: block_size ]
prefix_lines = prefix_lines [ block_size :]
branch_method ( output , block , recurse )
# 循环消耗prefix_lines, 直到消耗完毕, 任务就完成了.
return
prefix_lines = list ( map ( indentation_method , lines ))
recurse ( prefix_lines )
recurse 最后会进入到2个函数里面去, 一个是leaft_method,
它处理单行的一些语法, 比如: tag > tag2 > tag3 | text,
也是采用上面那种循环消耗字符串的方法. 这里略过不表.
另一个就是branch_method, 这里面的值是函数html_block_tag.
它里面是处理缩进后的一些语法. 处理完头部之后,
会把缩进里面的内容传给recurse函数, 就这样一步步解析玩子树.
里面的append函数就把解析玩的内容传给output, 最后打印成html代码.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def html_block_tag ( output , block , recurse ):
append = output . append
prefix , tag = block [ 0 ]
if RAW_HTML . regex . match ( tag ):
# 如果是html代码(<开头)就不解析头部
append ( prefix + tag )
# 解析子树
recurse ( block [ 1 :])
elif COMMENT_SYNTAX . match ( tag ):
# 注释..
pass
elif VERBATIM_SYNTAX . match ( tag ):
# 子树不解析, 直接打印出来
m = VERBATIM_SYNTAX . match ( tag )
tag = m . group ( 1 ) . rstrip ()
start_tag , end_tag = apply_jquery_sugar ( tag )
append ( prefix + start_tag )
stream ( append , block [ 1 :])
append ( prefix + end_tag )
else :
# 普通的状况, 解析出tag
start_tag , end_tag = apply_jquery_sugar ( tag )
# 输出tag头
append ( prefix + start_tag )
# 解析子树
recurse ( block [ 1 :])
# 输出tag尾
append ( prefix + end_tag )
结论
shpaml采用函数嵌套调用的方法来解析和处理树状结构,
这个也是通常用的解析树状结构的方法(如果树状结构嵌套不多的话),
对于编程语法的解析, 也可以采用类似这样的方式,
对于每一个语法规则都有一个函数, 然后嵌套调用解析, 直到解析完毕.