摘要:首先我們來實現(xiàn)對語句的支持。下面我們就一起來讓我們的模板引擎的語法支持和可以從上下載可以看到,其實也是只增加了兩行代碼。效果就這樣我們的模板引擎對的支持算是比較完善了。
在 上篇文章中我們的模板引擎實現(xiàn)了變量和注釋功能,同時在文章的最后我給大家留了一個 問題:如何實現(xiàn)支持 if 和 for 的標(biāo)簽功能:
{% if user.is_admin %} admin, {{ user.name }} {% elif user.is_staff %} staff {% else %} others {% endif %} {% for name in names %} {{ name }} {% endfor %}
在本篇文章中我們將一起來實現(xiàn)這個功能。
if ... elif ... else ... endif首先我們來實現(xiàn)對 if 語句的支持。 if 語句的語法如下:
{% if True %} ... {% elif True %} ... {% else %} ... {% endif %}
我們首先要做的跟之前一樣,那就是確定匹配標(biāo)簽語法的正則表達式。這里我們用的是下面 的正則來匹配標(biāo)簽語法:
re_tag = re.compile(r"{% .*? %}") >>> re_tag.findall("{% if True %}...{% elif True %}...{% else %}...{% endif %}") ["{% if True %}", "{% elif True %}", "{% else %}", "{% endif %}"]
然后就是生成代碼了, if 語句跟之前的變量不一樣那就是:需要進行縮進切換,這一點需要注意一下。
下面我們來看一下為了支持 if 標(biāo)簽增加了哪些代碼吧(完整代碼可以從 Github 上下載 template2a.py ):
class Template: def __init__(self, ...): # ... # 注釋 self.re_comment = re.compile(r"{# .*? #}") # 標(biāo)簽 self.re_tag = re.compile(r"{% .*? %}") # 用于按變量,注釋,標(biāo)簽分割模板字符串 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 中 # 因為標(biāo)簽生成的代碼需要新起一行 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 語句條件部分結(jié)束,向前縮進一步,為下一行做準(zhǔn)備 self.code_builder.forward() elif tag_name in ("endif",): # if 語句結(jié)束,向后縮進一步 self.code_builder.backward() else: # ...
上面代碼的關(guān)鍵點是生成代碼時的縮進控制:
在遇到 if 的時候, 需要在 if 這一行之后將縮進往前移一步
在遇到 elif 和 else 的時候, 需要將縮進先往后移一步,待 elif/ else 那一行完成后還需要把縮進再移回來
在遇到 endif 的時候, 我們知道此時 if 語句已經(jīng)結(jié)束了,需要把縮進往后移一步, 離開 if 語句的主體部分
我們來看一下生成的代碼:
>>> 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([" "]) if score >= 80: __result.extend([" A "]) elif score >= 60: __result.extend([" B "]) else: __result.extend([" C "]) __result.extend([" "]) return "".join(__result)
代碼中的 if 語句和縮進沒有問題。下面再看一下 render 的結(jié)果:
>>> t.render({"score": 90}) " A " >>> t.render({"score": 70}) " B " >>> t.render({"score": 50}) " C "
對 if 語句的支持就這樣實現(xiàn)了。有了這次經(jīng)驗下面讓我們一起來實現(xiàn)對 for 循環(huán)的支持吧。
for ... endfor模板中的 for 循環(huán)的語法如下:
{% for name in names %} ... {% endfor %}
從語法上可以看出來跟 if 語句是很相似了,甚至比 if 語句還要簡單。只需在原有 if 語句代碼 的基礎(chǔ)上稍作修改就可以(完整版可以從 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([" "]) for number in numbers: __result.extend([" ",str(number)," "]) __result.extend([" "]) return "".join(__result)
render 效果:
>>> t.render({"numbers": range(3)}) " 0 1 2 "
for ... endfor 語法就這樣實現(xiàn)了。是不是很簡單??但是還沒完?
相信大家都知道在 python 中 for 循環(huán)其實還支持 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([" "]) for number in numbers: __result.extend([" "]) if number > 2: __result.extend([" "]) break __result.extend([" "]) else: __result.extend([" ",str(number)," "]) __result.extend([" "]) else: __result.extend([" no break "]) __result.extend([" "]) return "".join(__result) >>> t.render({"numbers": range(3)}).replace(" ", "") " 0 1 2 no break" >>> t.render({"numbers": range(4)}).replace(" ", "") " 0 1 2 "
就這樣我們的模板引擎對 for 的支持算是比較完善了。 至于生成的代碼里的換行和空格暫時先不管,留待之后優(yōu)化代碼的時候再處理。
重構(gòu)我們的 Template._parse_text 方法代碼隨著功能的增加已經(jīng)變成下面這樣了:
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 循環(huán)里的代碼太長了,我們需要分割 for 循環(huán)里的 代碼。比如把對變量,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): """處理標(biāo)簽""" # 將前面解析的字符串,變量寫入到 code_builder 中 # 因為標(biāo)簽生成的代碼需要新起一行 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 表達式部分結(jié)束,向前縮進一步,為下一行做準(zhǔn)備 self.code_builder.forward() elif tag_name in ("break",): self.code_builder.add_line(tag) elif tag_name in ("endif", "endfor"): # if/for 結(jié)束,向后縮進一步 self.code_builder.backward()
這樣處理后是不是比之前那個都放在 _parse_text 方法里要好很多?
至此,我們的模板引擎已經(jīng)支持了如下語法:
變量: {{ variable }}
注釋: {# comment #}
if 語句: {% if ... %} ... {% elif ... %} ... {% else %} ... {% endif %}
for 循環(huán): {% for ... in ... %} ... {% break %} ... {% else %} ... {% endfor %}
之后的文章還將實現(xiàn)其他實用的模板語法,比如 include, extends 模板繼承等。
include 的語法(item.html 是個獨立的模板文件, list.html 中 include item.html):
{# item.html #}
list.html 渲染后將生成類似下面這樣的字符串:
extends 的語法(base.html 是基礎(chǔ)模板, child.html 繼承 base.html 然后重新定義 base.html 中定義過的 block):
{# base.html #}{% block content %} parent_content {% endblock content %}
child.html:
{% extends "base.html" %} {% block content %} child_content {{ block.super }} {% endblock content %}
child.html 渲染后將生成類似下面這樣的字符串:
child_content parent_content
那么,該如何實現(xiàn) include 和 extends 功能呢? 我將在 第三篇文章 中向你詳細(xì)的講解。敬請期待。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/37840.html
摘要:在本文中我們將解決一些用于生成的模板引擎需要面對的一些安全問題。整個系列的所有文章地址讓我們一起來構(gòu)建一個模板引擎一讓我們一起來構(gòu)建一個模板引擎二讓我們一起來構(gòu)建一個模板引擎三讓我們一起來構(gòu)建一個模板引擎四文章中涉及的代碼已經(jīng)放到上了 在 上篇文章 中我們的模板引擎實現(xiàn)了對 include 和 extends 的支持, 到此為止我們已經(jīng)實現(xiàn)了模板引擎所需的大部分功能。 在本文中我們將解...
摘要:使用技術(shù)我們將使用將模板編譯為代碼的方式來解析和渲染模板。下面我們就一起來實現(xiàn)這個方法。 假設(shè)我們要生成下面這樣的 html 字符串: welcome, Tom age: 20 weight: 100 height: 170 要求姓名以及 中的內(nèi)容是根據(jù)變量動態(tài)生成的,也就是這樣的: welco...
摘要:原作者唐斌騰訊什么原名是一個簡單易用的前端模板預(yù)編譯工具。本文作者為來自騰訊團隊的唐斌,他在本文中為我們分析了傳統(tǒng)前端模板內(nèi)嵌的弊端,如開發(fā)調(diào)試效率低下自動化構(gòu)建復(fù)雜度比較高等特點,并針對目前現(xiàn)狀給出了較好的解決方案。 原作者: 唐斌(騰訊)| TmodJS什么 TmodJS(原名atc)是一個簡單易用的前端模板預(yù)編譯工具。它通過預(yù)編譯技術(shù)讓前端模板突破瀏覽器限制,實現(xiàn)后端模板一樣的同...
摘要:在上篇文章中我們的模板引擎實現(xiàn)了對和對支持,同時在文章的最后我給大家留了一個問題如何實現(xiàn)支持和的標(biāo)簽功能。在本篇文章中我們將一起來動手實現(xiàn)這兩個功能。 在 上篇文章 中我們的模板引擎實現(xiàn)了對 if 和 for 對支持,同時在文章的最后我給大家留了一個 問題:如何實現(xiàn)支持 include 和 extends 的標(biāo)簽功能。 在本篇文章中我們將一起來動手實現(xiàn)這兩個功能。 include in...
摘要:回到純靜態(tài)頁面開發(fā)階段,讓頁面不需要后端渲染也能跑起來。改造開始本文著重介紹如何將靜態(tài)頁面改造成后端渲染需要的模板。總結(jié)在后端渲染的項目里使用多頁應(yīng)用架構(gòu)是絕對可行的,可不要給老頑固們嚇唬得又回到傳統(tǒng)前端架構(gòu)了。 本文首發(fā)于Array_Huang的技術(shù)博客——實用至上,非經(jīng)作者同意,請勿轉(zhuǎn)載。原文地址:https://segmentfault.com/a/119000000820338...
閱讀 2676·2021-11-16 11:53
閱讀 2737·2021-07-26 23:38
閱讀 2073·2019-08-30 15:55
閱讀 1751·2019-08-30 13:21
閱讀 3650·2019-08-29 17:26
閱讀 3306·2019-08-29 13:20
閱讀 875·2019-08-29 12:20
閱讀 3192·2019-08-26 10:21