一、前言
模板語言由HTML程式碼和邏輯控制程式碼組成,此處@PHP。通過模板語言可以快速的生成預想的HTML頁面。應該算是後端渲染不可缺少的組成部分。
二、功能介紹
通過使用學習tornado、bottle的模板語言,我也效仿著實現可以獨立使用的模板渲染的程式碼模組,模板語法來自tornado和bottle的語法。可以用來做一些簡單的網頁渲染,郵件內容生成等HTML顯示方面。以下就是簡單的語法使用介紹。
1. 變數。使用{{ }}包裹起來,裡面的變數為Python傳入。模板渲染時會將傳入的變數轉換成字串並填入對應位置。
# 模板檔案內容 <title>{{my_title}}</title> <label>{{ session.name }}</label> # py程式碼呼叫 t_html 為上面的內容 Template(t_html).render(my_title="標題", session = some_obj)
2. 轉義。預設傳入的資料都會進行HTML轉義,可以使用{% raw value %}來將value的內容按原始字串輸出。
# 模板檔案內容 <p>{% raw value %} </p> # Py呼叫內容 Template(t_html).render(my_title="<label>顯示標籤</label>")
3. 條件控制。支援Python的if,elif,else。條件程式碼需要放在{% %}內部,並且在條件結束後需要額外增加{% end %},用於標識條件控制語句塊範圍。
# 模板檔案內容 {% if a > 1%} <label>A大於1</label> {% else %} <label>A小於或等於1</label> {% end %} # py呼叫 Template(t_html).render(a=1)
4. 迴圈控制。支援Python的for和while。與條件控制一樣也需要放在{% %}內部,並且結束處需要額外增加{% end %},用於標識迴圈控制語句塊的範圍。
# 模板檔案內容 {% for i in range(10) %} <label>當前序號:{{i+1}}</label> {% end %} # py呼叫 Template(t_html).render()
5. 變數宣告。如果需要在模板檔案內宣告一個變數,方便使用時,可以通過set來實現。具體格式為{% set v = xx %}。通過set宣告的變數整個模板檔案中都可以使用,包括在條件控制和迴圈控制中作為條件判斷也可以。
# 模板檔案內容 {% set a = 1 %} <label>a的值:{{a}}</label>
三、原始碼
這個模板語言模組是在Python2.7上開發使用的,如果要在Python3+上使用需要對str和bytes進行一些處理即可,由於沒有引用任何其他模組,可以很好的獨立使用。
1 # -*- coding:utf-8 -*- 2 3 """ 模板語言""" 4 5 # TOKEN相關的定義 6 TOKEN_S_BRACE = "{" 7 TOKEN_S_BLOCK = "%" 8 TOKEN_EXPRESSION_L = "{{" 9 TOKEN_EXPRESSION_R = "}}" 10 TOKEN_BLOCK_L = "{%" 11 TOKEN_BLOCK_R = "%}" 12 TOKEN_KEY_SET = "set" 13 TOKEN_KEY_RAW = "raw" 14 TOKEN_KEY_IF = "if" 15 TOKEN_KEY_ELIF = "elif" 16 TOKEN_KEY_ELSE = "else" 17 TOKEN_KEY_FOR = "for" 18 TOKEN_KEY_WHILE = "while" 19 TOKEN_KEY_END = "end" 20 TOKEN_KEY_BREAK = "break" 21 TOKEN_KEY_CONTINUE = "continue" 22 TOKEN_SPACE = " " 23 TOKEN_COLON = ":" 24 # Token標記 {{}} {% %} 25 TOKEN_FLAG_SET = {TOKEN_S_BRACE, TOKEN_S_BLOCK} 26 # 簡單的語句 27 TOKEN_KEY_SET_SIMPLE_EXPRESSION = {TOKEN_KEY_SET, TOKEN_KEY_RAW} 28 # 前置條件 29 TOKEN_KEY_PRE_CONDITION = { 30 # end 必須在if/elif/else/for/while 後面 31 TOKEN_KEY_END: {TOKEN_KEY_IF, TOKEN_KEY_ELIF, TOKEN_KEY_ELSE, 32 TOKEN_KEY_FOR, TOKEN_KEY_WHILE}, 33 # elif 必須在if 後面 34 TOKEN_KEY_ELIF: {TOKEN_KEY_IF}, 35 # else 必須在if/elif 後面 36 TOKEN_KEY_ELSE: {TOKEN_KEY_IF, TOKEN_KEY_ELIF, TOKEN_KEY_FOR, TOKEN_KEY_WHILE}, 37 } 38 # 迴圈語句 39 TOKEN_KEY_LOOP = {TOKEN_KEY_WHILE, TOKEN_KEY_FOR} 40 # 迴圈的控制break continue 41 TOKEN_KEY_LOOP_CTRL = {TOKEN_KEY_BREAK, TOKEN_KEY_CONTINUE} 42 43 class ParseException(Exception): 44 pass 45 46 class TemplateCode(object): 47 def __init__(self): 48 self.codeTrees = {"parent": None, "nodes": []} 49 self.cursor = self.codeTrees 50 self.compiled_code = None 51 52 def create_code(self): 53 """建立一個程式碼子塊""" 54 child_codes = {"parent": self.cursor, "nodes": []} 55 self.cursor["nodes"].append(child_codes) 56 self.cursor = child_codes 57 58 def close_code(self): 59 """ 關閉一個程式碼子塊 """ 60 assert self.cursor["parent"] is not None, "overflow" 61 self.cursor = self.cursor["parent"] 62 63 def append_text(self, text): 64 """ 新增文字 """ 65 # 排除空行 66 self.cursor["nodes"].append("_add(%r)" % text) 67 68 def append_express(self, express, raw=False): 69 """ 表示式 """ 70 if raw: 71 temp_exp = "_t_exp = _str_(%s)" % express 72 else: 73 temp_exp = "_t_exp = _esc_(%s)" % express 74 self.cursor["nodes"].append(temp_exp) 75 self.cursor["nodes"].append("_add(_t_exp)") 76 77 def append_statement(self, statement): 78 """ 語句 """ 79 temp_statement = "%s" % statement 80 self.cursor["nodes"].append(temp_statement) 81 82 def reset(self): 83 self.codeTrees = {"parent": None, "nodes": []} 84 self.cursor = self.codeTrees 85 self.compiled_code = None 86 87 def build_code(self, filename): 88 temp_code_buff = [] 89 self.write_buff_with_indent(temp_code_buff, "def _template_render():", 0) 90 self.write_buff_with_indent(temp_code_buff, "_codes = []", 4) 91 self.write_buff_with_indent(temp_code_buff, "_add = _codes.append", 4) 92 self.write_codes(temp_code_buff, self.codeTrees, 4) 93 self.write_buff_with_indent(temp_code_buff, "return ''.join(_codes)", 4) 94 temp_code = "".join(temp_code_buff) 95 self.compiled_code = compile(temp_code,filename, "exec", dont_inherit=True) 96 97 def write_codes(self, code_buff, codes, indent): 98 for node in codes.get("nodes", []): 99 if isinstance(node, dict): 100 self.write_codes(code_buff, node, indent+4) 101 else: 102 self.write_buff_with_indent(code_buff, node, indent) 103 104 def generate(self, **kwargs): 105 temp_namespace = {} 106 temp_namespace['_str_'] = self.to_utf8 107 temp_namespace['_esc_'] = self.to_safe_utf8 108 temp_namespace.update(kwargs) 109 exec(self.compiled_code, temp_namespace) 110 return temp_namespace['_template_render']() 111 112 @staticmethod 113 def write_buff_with_indent(code_buff, raw_str, indent): 114 """""" 115 temp = (" " * indent) + raw_str + "\n" 116 code_buff.append(temp) 117 118 @staticmethod 119 def to_utf8(raw_str): 120 """ 轉換 """ 121 if isinstance(raw_str, str): 122 return raw_str 123 elif isinstance(raw_str, bytes): 124 return raw_str.decode() 125 return str(raw_str) 126 127 @staticmethod 128 def to_safe_utf8(raw_str): 129 """ 過濾html轉義 """ 130 text = TemplateCode.to_utf8(raw_str) 131 return text.replace("&", "&").replace("<", "<").replace(">", ">") 132 class Template(object): 133 """模板類""" 134 def __init__(self, input_obj,filename="<string>", **namespace): 135 """模板初始化""" 136 self.namespace = {} 137 self.namespace.update(namespace) 138 # 將資料丟進去解析生成編譯程式碼 139 self.lexer = TemplateLexer(input_obj, filename) 140 141 def render(self, **kwargs): 142 """渲染模板 """ 143 temp_name_space = {} 144 temp_name_space.update(self.namespace) 145 temp_name_space.update(kwargs) 146 # 執行渲染 147 return self.lexer.render(**kwargs) 148 149 class TemplateLexer(object): 150 """模板語法分析器 """ 151 def __init__(self, input_obb, filename="<string>"): 152 if hasattr(input_obb, "read"): 153 self.raw_string = input_obb.read() 154 else: 155 self.raw_string = input_obb 156 self.filename = filename 157 # 記錄當前的位置 158 self.pos = 0 159 # 記錄原始資料的總長度 160 self.raw_str_len = len(self.raw_string) 161 # 記錄解析的資料 162 self.code_data = TemplateCode() 163 # 開始解析 164 self.parse_template() 165 166 def match(self, keyword, pos=None): 167 return self.raw_string.find(keyword, pos if pos is not None else self.pos) 168 169 def cut(self, size=-1): 170 """剪取資料 size切割資料的大小,-1表示全部""" 171 if size == -1: 172 new_pos = self.raw_str_len 173 else: 174 new_pos = self.pos + size 175 s = self.raw_string[self.pos: new_pos] 176 self.pos = new_pos 177 return s 178 179 def remaining(self): 180 """獲取剩餘大小 """ 181 return self.raw_str_len - self.pos 182 183 def function_brace(self): 184 """ 獲取{{ / {% """ 185 skip_index = self.pos 186 while True: 187 index = self.match(TOKEN_S_BRACE, skip_index) # {% {{ 188 # 沒找到 189 if index == -1: 190 return None, -1 191 # 末尾 192 if index >= self.raw_str_len: 193 return None, -1 194 # 匹配型別 195 next_value = self.raw_string[index + 1:index + 2] 196 if next_value not in TOKEN_FLAG_SET: 197 skip_index = index + 1 198 # 說明不是關鍵型別 199 continue 200 brace = self.raw_string[index: index + 2] 201 return brace, index 202 return None, -1 203 204 def read_content_with_token(self, index, begin_token, end_token): 205 """ 206 讀取匹配token的內容 207 """ 208 end_index = self.match(end_token) 209 if end_index == -1: 210 return ParseException("{0} missing end token {1}".format(begin_token, end_token)) 211 # 過濾 begin_token 212 self.pos = index + len(begin_token) 213 content = self.cut(end_index - self.pos) 214 # 去除末尾 end_token 215 self.cut(len(end_token)) 216 return content 217 218 def add_simple_block_statement(self, operator, suffix): 219 if not suffix: 220 raise ParseException("{0} missing content".format(operator)) 221 if operator == TOKEN_KEY_SET: 222 self.code_data.append_statement(suffix) 223 elif operator == TOKEN_KEY_RAW: 224 self.code_data.append_express(suffix, True) 225 else: 226 raise ParseException("{0} is undefined".format(operator)) 227 228 def parse_template(self): 229 """解析模板 """ 230 # TODO 檢查模板檔案是否更改過,如果沒有則不需要重新解析 231 self.code_data.reset() 232 # 解析模板原檔案 233 self.__parse() 234 # 生成編譯code 235 self.__compiled_code() 236 237 def render(self, **kwargs): 238 return self.code_data.generate(**kwargs) 239 240 def __parse(self, control_operator=None, in_loop=False): 241 """開始解析""" 242 while True: 243 if self.remaining() <= 0: 244 if control_operator or in_loop: 245 raise ParseException("%s missing {%% end %%}" % control_operator) 246 break 247 # 讀取 {{ {% 248 brace, index = self.function_brace() 249 # 說明沒有找到 250 if not brace: 251 text = self.cut(index) 252 self.code_data.append_text(text) 253 continue 254 else: 255 text = self.cut(index - self.pos) 256 if text: 257 self.code_data.append_text(text) 258 259 if brace == TOKEN_EXPRESSION_L: 260 content = self.read_content_with_token(index, TOKEN_EXPRESSION_L, TOKEN_EXPRESSION_R).strip() 261 if not content: 262 raise ParseException("Empty Express") 263 self.code_data.append_express(content) 264 continue 265 elif brace == TOKEN_BLOCK_L: 266 content = self.read_content_with_token(index, TOKEN_BLOCK_L, TOKEN_BLOCK_R).strip() 267 if not content: 268 raise ParseException("Empty block") 269 270 # 得到表示式 for x in x ; if x ; elif x ; else ; end ; set ; while x ; 271 operator, _, suffix = content.partition(TOKEN_SPACE) 272 if not operator: 273 raise ParseException("block missing operator") 274 275 suffix = suffix.strip() 276 # 簡單語句,set / raw 277 if operator in TOKEN_KEY_SET_SIMPLE_EXPRESSION: 278 self.add_simple_block_statement(operator, suffix) 279 elif operator in TOKEN_KEY_LOOP_CTRL: 280 if not in_loop: 281 raise ParseException("{0} must in loop block".format(operator)) 282 self.code_data.append_statement(operator) 283 else: 284 # 控制語句 檢查匹配if 後面可以跟elif/else 285 pre_condition = TOKEN_KEY_PRE_CONDITION.get(operator, None) 286 if pre_condition: 287 # 裡面就是elif/else/end 288 if control_operator not in pre_condition: 289 raise ParseException("{0} must behind with {1}".format(operator, pre_condition)) 290 elif operator == TOKEN_KEY_END: 291 # 遇到{% end %}則結束 292 self.code_data.close_code() 293 return 294 else: 295 # 由於是依據if 進入 來計算elif ,因此elif與if是同級的 296 self.code_data.close_code() 297 self.code_data.append_statement(content + TOKEN_COLON) 298 self.code_data.create_code() 299 self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP)) 300 break 301 # 新增控制語句及內部語句體 if for while 302 self.code_data.append_statement(content + TOKEN_COLON) 303 self.code_data.create_code() 304 self.__parse(operator, in_loop or (operator in TOKEN_KEY_LOOP)) 305 else: 306 raise ParseException("Unkown brace") 307 return 308 309 def __compiled_code(self): 310 """生成 編譯code """ 311 self.code_data.build_code(self.filename) 312 if __name__ == "__main__": 313 t = Template("<html>{{hello}}</html>") 314 t.render(hello="你好"