当前位置: 首页 > 工具软件 > bottle-cork > 使用案例 >

Bottle模板引擎分析

麹高义
2023-12-01

之前在看bottle模板系统源码的时候,遇到了一些困难,模板的解析和编译原理中的词法和语法分析有些关联,所以前段时间看了些编译方面的内容,理解了差不多百分之四五十。但是像中间代码、目标代码生成的部分和模板的关系也不大。
家里电脑暂时装不上python,现在回过头读读模板部分的文档。讲一些理解。
模板总共有两个接口,compile和render部分

>>> from bottle import SimpleTemplate
>>> tpl = SimpleTemplate('Hello {{name}}!')
>>> tpl.render(name='World')
u'Hello World!'

编译只进行一次,Bottle会通过compile函数把模板翻译成python字节码存在缓存中,缓存是通过只含__get__方法的描述符实现的,编译过程中会做词法分析把整个模板转换成token流。
这样在之后进行渲染(render)的时候,不必再重新编译模板,只需要进行填空(把python代码或变量转换成对应的字符串加入模板)就可,这一步转换是通过exec函数进行的
比如

>>>parser = StplParser('<ul>% for item in basket:\n    <li>{{item}}</li>\n% end\n</ul>')
>>>code = parser.translate()
>>>code
>>>"_printlist(('<ul>\\n',))\nfor item in basket:\n  _printlist(('    <li>', _escape(item), '</li>\\n',))\n  \n_printlist(('</ul>',))\n"

将模板块中的python代码(如for item in basket)提取出来,用exec直接执行。就省去了语法分析的步骤。至于变量,用_escape函数进行转换。普通文本使用_printlist函数直接输出。

any python expression is allowed within the curly brackets as long as it evaluates to a string or something that has a string representation

为了防止XSS攻击,其中HTML特殊字符会进行自动转义

>>> template('Hello {{name}}!', name='<b>World</b>')
u'Hello &lt;b&gt;World&lt;/b&gt;!'
>>> template('Hello {{!name}}!', name='<b>World</b>') #也可以这样阻止转义
u'Hello <b>World</b>!'

内嵌python代码分成三种形式,
一种是单行的inline形式,以{{ }}内联
单行的code,以%开头
另一种是多行的代码块,以<%开始,%>结束。(不常用)
在代码块中也可能存在单行的inline代码

>>> template('Hello {{name.title() if name else "stranger"}}!', name=None)
u'Hello stranger!'
% name = "Bob"  # a line of python code
<p>Some plain text in between</p>
<%
  # A block of python code
  name = name.title().strip()
%>
<p>More plain text</p>

忽略缩进,正常缩进的代码现在需要一个end关键字结束块

<ul>
  % for item in basket:
    <li>{{item}}</li>
  % end
</ul>
<div>
 % if True:
  <span>content</span>
 % end
</div>

在解析中,以re_split以%为界将文本进行分割,
flush_text函数在%之前的text中搜索inline code
read_code函数在%所在行搜索code

    def translate(self): #以%分隔代码块
        if self.offset: raise RuntimeError('Parser is a one time instance.')
        while True:
            m = self.re_split.search(self.source, pos=self.offset)
            if m:
                text = self.source[self.offset:m.start()]   #取得普通文本
                self.text_buffer.append(text) #将普通html代码存入text_buffer
                self.offset = m.end()
                if m.group(1):  # Escape syntax
                    line, sep, _ = self.source[self.offset:].partition('\n')
                    self.text_buffer.append(self.source[m.start():m.start(1)] +
                                            m.group(2) + line + sep)
                    self.offset += len(line + sep)
                    continue
                self.flush_text() #在%之前的text中搜索inline code并存入code_buffer
                self.offset += self.read_code(self.source[self.offset:],
                                              multiline=bool(m.group(4)))  
                                              # 解析%当行的code并存入code_buffer
            else:
                break
        self.text_buffer.append(self.source[self.offset:])
        self.flush_text()
        return ''.join(self.code_buffer)
 类似资料: