网络寻租

Programmer, Gamer, Hacker

Shpaml源码分析

| Comments

在上次已经 介绍过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采用函数嵌套调用的方法来解析和处理树状结构, 这个也是通常用的解析树状结构的方法(如果树状结构嵌套不多的话), 对于编程语法的解析, 也可以采用类似这样的方式, 对于每一个语法规则都有一个函数, 然后嵌套调用解析, 直到解析完毕.

Comments