摘要:在上篇文章中我們的模板引擎實現(xiàn)了對和對支持,同時在文章的最后我給大家留了一個問題如何實現(xiàn)支持和的標(biāo)簽功能。在本篇文章中我們將一起來動手實現(xiàn)這兩個功能。
在 上篇文章 中我們的模板引擎實現(xiàn)了對 if 和 for 對支持,同時在文章的最后我給大家留了一個 問題:如何實現(xiàn)支持 include 和 extends 的標(biāo)簽功能。
在本篇文章中我們將一起來動手實現(xiàn)這兩個功能。
includeinclude 標(biāo)簽對語法是這樣的:假設(shè)有一個 item.html 模板文件,它的內(nèi)容如下:
還有一個我們要渲染的模板 list.html 內(nèi)容如下:
渲染 list.html 后的結(jié)果類似:
從上面可以看出來 include 標(biāo)簽的作用類似使用 include 所在位置的名字空間 渲染另一個模板然后再使用渲染后的結(jié)果。所以我們可以將 include 的模板文件 當(dāng)作普通的模板文件來處理,用解析那個模板生成后的代碼替換 include 所在的位置, 再將結(jié)果追加到 result_var 。 生成的代碼類似:
def func_name(): result = [] # 解析 include 的模板 def func_name_include(): result_include = [] return "".join(result_include) # 調(diào)用生成的 func_name_include 函數(shù)獲取渲染結(jié)果 result.append(func_name_include()) return "".join(result)
生成類似上面的代碼就是 include 的關(guān)鍵點,下面看一下實現(xiàn) include 功能 都做了哪些改動 (可以從 Github 上下載 template3a.py):
class Template: def __init__(self, ..., template_dir="", encoding="utf-8"): # ... self.template_dir = template_dir self.encoding = encoding # ... def _handle_tag(self, token): """處理標(biāo)簽""" # ... tag_name = tag.split()[0] if tag_name == "include": self._handle_include(tag) else: self._handle_statement(tag) def _handle_include(self, tag): filename = tag.split()[1].strip("""") included_template = self._parse_another_template_file(filename) # 把解析 include 模板后得到的代碼加入當(dāng)前代碼中 # def __func_name(): # __result = [] # ... # def __func_name_hash(): # __result_hash = [] # return "".join(__result_hash) self.code_builder.add(included_template.code_builder) # 把上面生成的代碼中函數(shù)的執(zhí)行結(jié)果添加到原有的結(jié)果中 # __result.append(__func_name_hash()) self.code_builder.add_line( "{0}.append({1}())".format( self.result_var, included_template.func_name ) ) def _parse_another_template_file(self, filename): template_path = os.path.realpath( os.path.join(self.template_dir, filename) ) name_suffix = str(hash(template_path)).replace("-", "_") func_name = "{}_{}".format(self.func_name, name_suffix) result_var = "{}_{}".format(self.result_var, name_suffix) with open(template_path, encoding=self.encoding) as fp: template = self.__class__( fp.read(), indent=self.code_builder.indent, default_context=self.default_context, func_name=func_name, result_var=result_var, template_dir=self.template_dir ) return template
首先是 __init__ 增加了兩個參數(shù) template_dir 和 encoding:
template_dir: 指定模板文件夾路徑,因為 include 的模板是相對路徑所以需要這個 選項來獲取模板的絕對路徑
encoding: 指定模板文件的編碼,默認(rèn)是 utf-8
然后就是 _parse_another_template_file 了,這個方法是用來解析 include 中 指定的模板文件的,其中的 func_name 和 result_var 之所以加了個 hash 值 作為后綴是不想跟其他函數(shù)變量重名。
_handle_include 實現(xiàn)的是解析 include 的模板, 然后將生成的代碼和代碼中函數(shù)的執(zhí)行結(jié)果添加到當(dāng)前代碼中。
下面來看一下實現(xiàn)的效果。還是用上面的模板文件:
item.html:
list.html:
先來看一下生成的代碼:
>>> from template3a import Template >>> text = open("list.html").read() >>> t = Template(text) >>> t.code_builder def __func_name(): __result = [] __result.extend(["
然后是渲染效果:
>>> print(t.render({"items": ["item1", "item2", "item3"]}))
include 已經(jīng)實現(xiàn)了,下面讓我們一起來實現(xiàn) extends 功能。
extendsextends 標(biāo)簽實現(xiàn)的是模板繼承的功能,并且只能在第一行出現(xiàn),語法如下:
假設(shè)有一個 parent.html 文件它的內(nèi)容是:
{% block header %} parent_header {% endblock header %}
還有一個 child.html 文件:
{% extends "parent.html" %} {% block header %} child_header {{ block.super }} {% endblock header %}
child.html 渲染后的結(jié)果:
child_header parent_header
可以看到 extends 的效果類似用子模板里的 block 替換父模板中定義的同名 block, 同時又可以使用 {{ block.super }} 引用父模板中定義的內(nèi)容,有點類似 class 的繼承效果。
注意我剛才說的是: 類似用子模板里的 block 替換父模板中定義的同名 block 。
這個就是 extends 的關(guān)鍵點,我們可以先找出子模板里定義的 block , 然后用子模板里的 block 替換父模板里的同名 block , 最后只處理替換后的父模板就可以了。
暫時先不管 block.super ,支持 extends 的代碼改動如下(可以從 Github 下載 template3b.py ):
class Template: def __init__(self, ...): # extends self.re_extends = re.compile(r"{% extends (?P.*?) %}") # blocks self.re_blocks = re.compile( r"{% block (?P w+) %}" r"(?P .*?)" r"{% endblock 1 %}", re.DOTALL) def _parse_text(self): # extends self._handle_extends() tokens = self.re_tokens.split(self.raw_text) # ... def _handle_extends(self): match_extends = self.re_extends.match(self.raw_text) if match_extends is None: return parent_template_name = match_extends.group("name").strip(""" ") parent_template_path = os.path.join( self.template_dir, parent_template_name ) # 獲取當(dāng)前模板里的所有 blocks child_blocks = self._get_all_blocks(self.raw_text) # 用這些 blocks 替換掉父模板里的同名 blocks with open(parent_template_path, encoding=self.encoding) as fp: parent_text = fp.read() new_parent_text = self._replace_parent_blocks( parent_text, child_blocks ) # 改為解析替換后的父模板內(nèi)容 self.raw_text = new_parent_text def _replace_parent_blocks(self, parent_text, child_blocks): """用子模板的 blocks 替換掉父模板里的同名 blocks""" def replace(match): name = match.group("name") parent_code = match.group("code") child_code = child_blocks.get(name) return child_code or parent_code return self.re_blocks.sub(replace, parent_text) def _get_all_blocks(self, text): """獲取模板內(nèi)定義的 blocks""" return { name: code for name, code in self.re_blocks.findall(text) }
從上面的代碼可以看出來我們遵循的是使用子模板 block 替換父模板同名 block 然后改為解析替換后的父模板的思路. 即,雖然我們要渲染的是:
{% extends "parent.html" %} {% block header %} child_header {% endblock header %}
實際上我們最終渲染的是替換后的父模板:
child_header
依舊是來看一下實際效果:
parent1.html:
{% block header %} parent_header {% endblock header %}
child1.html:
{% extends "parent1.html" %} {% block header %} {{ header }} {% endblock header %}
看看最后要渲染的模板字符串:
>>> from template3b import Template >>> text = open("child1.html").read() >>> t = Template(text) >>> print(t.raw_text){{ header }}
可以看到確實是替換后的內(nèi)容,再來看一下生成的代碼和渲染后的效果:
>>> t.code_builder def __func_name(): __result = [] __result.extend(["",str(header),""]) return "".join(__result) >>> print(t.render({"header": "child_header"}))child_header
extends 的基本功能就這樣實現(xiàn)了。下面再實現(xiàn)一下 {{ block.super }} 功能。
block.super{{ block.super }} 類似 Python class 里的 super 用來實現(xiàn)對父 block 的引用,讓子模板可以重用父 block 中定義的內(nèi)容。 只要改一下 _replace_parent_blocks 中的 replace 函數(shù)讓它支持 {{ block.super }} 就可以了(可以從 Github 下載 template3c.py):
class Template: def __init__(self, ....): # blocks self.re_blocks = ... # block.super self.re_block_super = re.compile(r"{{ block.super }}") def _replace_parent_blocks(self, parent_text, child_blocks): def replace(match): ... parent_code = match.group("code") child_code = child_blocks.get(name, "") child_code = self.re_block_super.sub(parent_code, child_code) new_code = child_code or parent_code return new_code
效果:
parent2.html:
{% block header %} parent_header {% endblock header %}
child2.html:
{% extends "parent2.html" %} {% block header %} child_header {{ block.super }} {% endblock header %}
>>> from template3c import Template >>> text = open("child2.html").read() >>> t = Template(text) >>> t.raw_text "child_header parent_header" >>> t.render() "child_header parent_header"
到目前為主我們已經(jīng)實現(xiàn)了現(xiàn)代 python 模板引擎應(yīng)有的大部分功能了:
變量
if
for
include
extends, block, block.super
后面需要做的工作就是完善我們代碼了。
不知道大家有沒有注意到,我之前都是用生成 html 來試驗?zāi)0逡娴墓δ艿模?這是因為模板引擎確實是在 web 開發(fā)中用的比較多,既然是生成 html 源碼那就需要考慮 針對 html 做一點優(yōu)化,比如去掉多余的空格,轉(zhuǎn)義之類的,還有就是一些 Web 安全方面的考慮。
至于怎么實現(xiàn)這些優(yōu)化項,我將在 第四篇文章 中向你詳細(xì)的講解。敬請期待。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/37852.html
摘要:在本文中我們將解決一些用于生成的模板引擎需要面對的一些安全問題。整個系列的所有文章地址讓我們一起來構(gòu)建一個模板引擎一讓我們一起來構(gòu)建一個模板引擎二讓我們一起來構(gòu)建一個模板引擎三讓我們一起來構(gòu)建一個模板引擎四文章中涉及的代碼已經(jīng)放到上了 在 上篇文章 中我們的模板引擎實現(xiàn)了對 include 和 extends 的支持, 到此為止我們已經(jīng)實現(xiàn)了模板引擎所需的大部分功能。 在本文中我們將解...
摘要:原作者唐斌騰訊什么原名是一個簡單易用的前端模板預(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)了變量和注釋功能,同時在文章的最后我給大家留了一個 問題:如何實現(xiàn)支持 if 和 for 的標(biāo)簽功能: {% if user.is_admin %} admin, {{ user...
摘要:使用技術(shù)我們將使用將模板編譯為代碼的方式來解析和渲染模板。下面我們就一起來實現(xiàn)這個方法。 假設(shè)我們要生成下面這樣的 html 字符串: welcome, Tom age: 20 weight: 100 height: 170 要求姓名以及 中的內(nèi)容是根據(jù)變量動態(tài)生成的,也就是這樣的: welco...
摘要:回到純靜態(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...
閱讀 1010·2023-04-25 19:35
閱讀 2672·2021-11-22 09:34
閱讀 3703·2021-10-09 09:44
閱讀 1730·2021-09-22 15:25
閱讀 2944·2019-08-29 14:00
閱讀 3378·2019-08-29 11:01
閱讀 2606·2019-08-26 13:26
閱讀 1741·2019-08-23 18:08