在 上篇文章中咱們的模板引擎實現了變量和註釋功能,同時在文章的最後我給你們留了一個 問題:如何實現支持 if
和 for
的標籤功能:html
{% if user.is_admin %} admin, {{ user.name }} {% elif user.is_staff %} staff {% else %} others {% endif %} {% for name in names %} {{ name }} {% endfor %}
在本篇文章中咱們將一塊兒來實現這個功能。python
首先咱們來實現對 if
語句的支持。 if
語句的語法以下:git
{% if True %} ... {% elif True %} ... {% else %} ... {% endif %}
咱們首先要作的跟以前同樣,那就是肯定匹配標籤語法的正則表達式。這裏咱們用的是下面 的正則來匹配標籤語法:github
re_tag = re.compile(r'\{% .*? %\}') >>> re_tag.findall('{% if True %}...{% elif True %}...{% else %}...{% endif %}') ['{% if True %}', '{% elif True %}', '{% else %}', '{% endif %}']
而後就是生成代碼了, if
語句跟以前的變量不同那就是:須要進行縮進切換,這一點須要注意一下。正則表達式
下面咱們來看一下爲了支持 if
標籤增長了哪些代碼吧(完整代碼能夠從 Github 上下載 template2a.py ):app
class Template: def __init__(self, ...): # ... # 註釋 self.re_comment = re.compile(r'\{# .*? #\}') # 標籤 self.re_tag = re.compile(r'\{% .*? %\}') # 用於按變量,註釋,標籤分割模板字符串 self.re_tokens = re.compile(r'''( (?:\{\{ .*? \}\}) |(?:\{\# .*? \#\}) |(?:\{% .*? %\}) )''', re.X) # 生成 def __func_name(): # ... def _parse_text(self): # ... for token in tokens: # ... if self.re_variable.match(token): # ... elif self.re_comment.match(token): continue # {% tag %} elif self.re_tag.match(token): # 將前面解析的字符串,變量寫入到 code_builder 中 # 由於標籤生成的代碼須要新起一行 self.flush_buffer() tag = token.strip('{%} ') tag_name = tag.split()[0] if tag_name in ('if', 'elif', 'else'): # elif 和 else 以前須要向後縮進一步 if tag_name in ('elif', 'else'): self.code_builder.backward() self.code_builder.add_line('{}:'.format(tag)) # if 語句條件部分結束,向前縮進一步,爲下一行作準備 self.code_builder.forward() elif tag_name in ('endif',): # if 語句結束,向後縮進一步 self.code_builder.backward() else: # ...
上面代碼的關鍵點是生成代碼時的縮進控制:優化
在遇到 if
的時候, 須要在 if
這一行以後將縮進往前移一步ui
在遇到 elif
和 else
的時候, 須要將縮進先日後移一步,待 elif
/ else
那一行完成後還須要把縮進再移回來code
在遇到 endif
的時候, 咱們知道此時 if
語句已經結束了,須要把縮進日後移一步, 離開 if
語句的主體部分orm
咱們來看一下生成的代碼:
>>> from template2a import Template >>> t = Template(''' ... {% if score >= 80 %} ... A ... {% elif score >= 60 %} ... B ... {% else %} ... C ... {% endif %} ... ''') >>> t.code_builder def __func_name(): __result = [] __result.extend(['\n']) if score >= 80: __result.extend(['\nA\n']) elif score >= 60: __result.extend(['\nB\n']) else: __result.extend(['\nC\n']) __result.extend(['\n']) return "".join(__result)
代碼中的 if
語句和縮進沒有問題。下面再看一下 render
的結果:
>>> t.render({'score': 90}) '\n\nA\n\n' >>> t.render({'score': 70}) '\n\nB\n\n' >>> t.render({'score': 50}) '\n\nC\n\n'
對 if
語句的支持就這樣實現了。有了此次經驗下面讓咱們一塊兒來實現對 for
循環的支持吧。
模板中的 for
循環的語法以下:
{% for name in names %} ... {% endfor %}
從語法上能夠看出來跟 if
語句是很類似了,甚至比 if
語句還要簡單。只需在原有 if
語句代碼 的基礎上稍做修改就能夠(完整版能夠從 Github 上下載 template2b.py ):
class Template: # ... def _parse_text(self): # ... elif self.re_tag.match(token): # ... if tag_name in ('if', 'elif', 'else', 'for'): # ... elif tag_name in ('endif', 'endfor'): # ...
能夠看到其實就是修改了兩行代碼。按照慣例咱們先來看一下生成的代碼:
>>> from template2b import Template >>> t = Template(''' ... {% for number in numbers %} ... {{ number }} ... {% endfor %} ... ''') >>> t.code_builder def __func_name(): __result = [] __result.extend(['\n']) for number in numbers: __result.extend(['\n',str(number),'\n']) __result.extend(['\n']) return "".join(__result)
render
效果:
>>> t.render({'numbers': range(3)}) '\n\n0\n\n1\n\n2\n\n'
for ... endfor
語法就這樣實現了。是否是很簡單??可是還沒完?
相信你們都知道在 python 中 for
循環其實還支持 break
和 else
。 下面咱們就一塊兒來讓咱們的模板引擎的 for
語法支持 break
和 else
(能夠從 Github 上下載: template2c.py )
class Template: # ... def _parse_text(self): # ... elif self.re_tag.match(token): # ... if tag_name in ('if', 'elif', 'else', 'for'): # ... elif tag_name in ('break',): self.code_builder.add_line(tag) elif tag_name in ('endif', 'endfor'): # ...
能夠看到,其實也是隻增長了兩行代碼。效果:
from template2c import Template >>> t = Template(''' ... {% for number in numbers %} ... {% if number > 2 %} ... {% break %} ... {% else %} ... {{ number }} ... {% endif %} ... {% else %} ... no break ... {% endfor %} ... ''') >>> t.code_builder def __func_name(): __result = [] __result.extend(['\n']) for number in numbers: __result.extend(['\n ']) if number > 2: __result.extend(['\n ']) break __result.extend(['\n ']) else: __result.extend(['\n ',str(number),'\n ']) __result.extend(['\n']) else: __result.extend(['\n no break\n']) __result.extend(['\n']) return "".join(__result) >>> t.render({'numbers': range(3)}).replace('\n', '') ' 0 1 2 no break' >>> t.render({'numbers': range(4)}).replace('\n', '') ' 0 1 2 '
就這樣咱們的模板引擎對 for
的支持算是比較完善了。 至於生成的代碼裏的換行和空格暫時先無論,留待以後優化代碼的時候再處理。
咱們的 Template._parse_text
方法代碼隨着功能的增長已經變成下面這樣了:
def _parse_text(self): """解析模板""" tokens = self.re_tokens.split(self.raw_text) for token in tokens: if self.re_variable.match(token): variable = token.strip('{} ') self.buffered.append('str({})'.format(variable)) elif self.re_comment.match(token): continue elif self.re_tag.match(token): self.flush_buffer() tag = token.strip('{%} ') tag_name = tag.split()[0] if tag_name in ('if', 'elif', 'else', 'for'): if tag_name in ('elif', 'else'): self.code_builder.backward() self.code_builder.add_line('{}:'.format(tag)) self.code_builder.forward() elif tag_name in ('break',): self.code_builder.add_line(tag) elif tag_name in ('endif', 'endfor'): self.code_builder.backward() else: self.buffered.append('{}'.format(repr(token)))
有什麼問題呢?問題就是 for
循環裏的代碼太長了,咱們須要分割 for
循環裏的 代碼。好比把對變量,if/for
的處理封裝到單獨的方法裏。
下面展現了一種方法(能夠從 Github 下載 template2d.py ):
def _parse_text(self): """解析模板""" tokens = self.re_tokens.split(self.raw_text) handlers = ( (self.re_variable.match, self._handle_variable), # {{ variable }} (self.re_tag.match, self._handle_tag), # {% tag %} (self.re_comment.match, self._handle_comment), # {# comment #} ) default_handler = self._handle_string # 普通字符串 for token in tokens: for match, handler in handlers: if match(token): handler(token) break else: default_handler(token) def _handle_variable(self, token): """處理變量""" variable = token.strip('{} ') self.buffered.append('str({})'.format(variable)) def _handle_comment(self, token): """處理註釋""" pass def _handle_string(self, token): """處理字符串""" self.buffered.append('{}'.format(repr(token))) def _handle_tag(self, token): """處理標籤""" # 將前面解析的字符串,變量寫入到 code_builder 中 # 由於標籤生成的代碼須要新起一行 self.flush_buffer() tag = token.strip('{%} ') tag_name = tag.split()[0] self._handle_statement(tag, tag_name) def _handle_statement(self, tag, tag_name): """處理 if/for""" if tag_name in ('if', 'elif', 'else', 'for'): # elif 和 else 以前須要向後縮進一步 if tag_name in ('elif', 'else'): self.code_builder.backward() # if True:, elif True:, else:, for xx in yy: self.code_builder.add_line('{}:'.format(tag)) # if/for 表達式部分結束,向前縮進一步,爲下一行作準備 self.code_builder.forward() elif tag_name in ('break',): self.code_builder.add_line(tag) elif tag_name in ('endif', 'endfor'): # if/for 結束,向後縮進一步 self.code_builder.backward()
這樣處理後是否是比以前那個都放在 _parse_text
方法裏要好不少?
至此,咱們的模板引擎已經支持了以下語法:
變量: {{ variable }}
註釋: {# comment #}
if
語句: {% if ... %} ... {% elif ... %} ... {% else %} ... {% endif %}
for
循環: {% for ... in ... %} ... {% break %} ... {% else %} ... {% endfor %}
以後的文章還將實現其餘實用的模板語法,好比 include
, extends
模板繼承等。
include
的語法(item.html 是個獨立的模板文件, list.html 中 include item.html):
{# item.html #} <li>{{ item }}</li> {# list.html #} <ul> {% for name in names %} {% include "item.html" %} {% endfor %} </ul>
list.html 渲染後將生成相似下面這樣的字符串:
<ul> <li>Tom</li> <li>Jim<li> </ul>
extends
的語法(base.html 是基礎模板, child.html 繼承 base.html 而後從新定義 base.html 中定義過的 block):
{# base.html #} <div id="content"> {% block content %} parent_content {% endblock content %} </div> <footer id="footer"> {% block footer %} (c) 2016 example.com {% endblock footer %} </footer>
child.html:
{% extends "base.html" %} {% block content %} child_content {{ block.super }} {% endblock content %}
child.html 渲染後將生成相似下面這樣的字符串:
<div id="content"> child_content parent_content </div> <footer id="footer"> (c) 2016 example.com </footer>
那麼,該如何實現 include
和 extends
功能呢? 我將在 第三篇文章 中向你詳細的講解。敬請期待。