之前在看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 <b>World</b>!'
>>> 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)