Django/テンプレートシステムを読む(テンプレートのパース)
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[Djangoを読む]]
#contents
*はじめに [#n7eb059c]
前回のおさらい。django.template.loaderのget_templateを呼...
#code(Python){{
def get_template(self, template_name, template_dirs=N...
"""
Calls self.get_template_sources() and returns a T...
the first template matching template_name. If ski...
template origins in skip are ignored. This is use...
during template extending.
"""
tried = []
args = [template_name]
# RemovedInDjango20Warning: Add template_dirs for...
# old loaders
if func_supports_parameter(self.get_template_sour...
args.append(template_dirs)
for origin in self.get_template_sources(*args):
if skip is not None and origin in skip:
tried.append((origin, 'Skipped'))
continue
try:
contents = self.get_contents(origin)
except TemplateDoesNotExist:
tried.append((origin, 'Source does not ex...
continue
else:
return Template(
contents, origin, origin.template_nam...
)
raise TemplateDoesNotExist(template_name, tried=t...
}}
今回はこの中の、Templateインスタンス生成の中に入っていき...
*django/template/base.py [#f18eaeb3]
Templateとは何者なのか確認。
#code(Python){{
from django.template import Origin, Template, TemplateDoe...
}}
django/template/__init__.py
#code(Python){{
# Template parts
from .base import ( ...
Context, Node, NodeList, Origin, RequestContext, Stri...
Variable,
)
}}
うーん、ややこしい。というわけでTemplateクラスはdjango/te...
#code(Python){{
class Template(object):
def __init__(self, template_string, origin=None, name...
try:
template_string = force_text(template_string)
except UnicodeDecodeError:
raise TemplateEncodingError("Templates can on...
"from unicode or ...
# If Template is instantiated directly rather tha...
# exactly one Django template engine is configure...
# This is required to preserve backwards-compatib...
# e.g. Template('...').render(Context({...}))
if engine is None:
from .engine import Engine
engine = Engine.get_default()
if origin is None:
origin = Origin(UNKNOWN_SOURCE)
self.name = name
self.origin = origin
self.engine = engine
self.source = template_string
self.nodelist = self.compile_nodelist()
}}
compile_nodelistメソッドによりコンパイルが行われるようで...
#code(Python){{
def compile_nodelist(self):
"""
Parse and compile the template source into a node...
is True and an exception occurs during parsing, t...
is annotated with contextual line information whe...
template source.
"""
if self.engine.debug:
lexer = DebugLexer(self.source)
else:
lexer = Lexer(self.source)
tokens = lexer.tokenize()
parser = Parser(
tokens, self.engine.template_libraries, self....
self.origin,
)
try:
return parser.parse()
except Exception as e:
if self.engine.debug:
e.template_debug = self.get_exception_inf...
raise
}}
デバッグ用の処理が少し追加されていますが、典型的なコンパ...
+Lexerでトークン化
+Parserでトークンからノードに変換
それぞれ見ていきましょう。
**Lexer [#fd23b4d3]
Lexerは同じbase.py内に書かれています。
#code(Python){{
def tokenize(self):
"""
Return a list of tokens from a given template_str...
"""
in_tag = False
lineno = 1
result = []
for bit in tag_re.split(self.template_string):
if bit:
result.append(self.create_token(bit, None...
in_tag = not in_tag
lineno += bit.count('\n')
return result
}}
tag_reはbase.pyの先頭の方に書かれています。{%, %}で囲むこ...
※}}を含むのでシンタックスハイライトなしで貼ってます
BLOCK_TAG_START = '{%'
BLOCK_TAG_END = '%}'
VARIABLE_TAG_START = '{{'
VARIABLE_TAG_END = '}}'
COMMENT_TAG_START = '{#'
COMMENT_TAG_END = '#}'
# match a variable or block tag and capture the entire t...
# delimiters
tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
(re.escape(BLOCK_TAG_START), re.escape(BLOCK_T...
re.escape(VARIABLE_TAG_START), re.escape(VARI...
re.escape(COMMENT_TAG_START), re.escape(COMME...
[[正規表現オブジェクトのsplitの挙動>https://docs.python.j...
キャプチャグループの丸括弧が pattern で使われていれば、
パターン内のすべてのグループのテキストも結果のリストの一...
実際、splitを実行してみると以下のようにテンプレートのタグ...
['', '{% if latest_question_list %}',
'\n <ul>\n ', '{% for question in latest_question...
'\n <li><a href="/polls/', '{{ question.id }}',
'/">', '{{ question.question_text }}',
'</a></li>\n ', '{% endfor %}',
'\n </ul>\n', '{% else %}',
'\n <p>No polls are available.</p>\n', '{% endif %}']
create_tokenではタグの種類によってTokenオブジェクトを作成...
#code(Python){{
def create_token(self, token_string, position, lineno...
"""
Convert the given token string into a new Token o...
If in_tag is True, we are processing something th...
otherwise it should be treated as a literal string.
"""
if in_tag and token_string.startswith(BLOCK_TAG_S...
# The [2:-2] ranges below strip off *_TAG_STA...
# We could do len(BLOCK_TAG_START) to be more...
# hard-coded the 2s here for performance. And...
# the TAG_START values are going to change an...
block_content = token_string[2:-2].strip()
if self.verbatim and block_content == self.ve...
self.verbatim = False
if in_tag and not self.verbatim:
if token_string.startswith(VARIABLE_TAG_START):
token = Token(TOKEN_VAR, token_string[2:-...
elif token_string.startswith(BLOCK_TAG_START):
if block_content[:9] in ('verbatim', 'ver...
self.verbatim = 'end%s' % block_content
token = Token(TOKEN_BLOCK, block_content,...
elif token_string.startswith(COMMENT_TAG_STAR...
content = ''
if token_string.find(TRANSLATOR_COMMENT_M...
content = token_string[2:-2].strip()
token = Token(TOKEN_COMMENT, content, pos...
else:
token = Token(TOKEN_TEXT, token_string, posit...
return token
}}
verbatimはテンプレートの処理を無効化する(つまり、タグが...
さて、対象のtoken_stringがタグなのかそうじゃないのかを表...
**Parser [#k6de54b7]
Parserもbase.py内に書かれています。parseメソッドは長いの...
***変数 [#z5262705]
#code(Python){{
elif token.token_type == 1: # TOKEN_VAR
if not token.contents:
raise self.error(token, 'Empty variab...
try:
filter_expression = self.compile_filt...
except TemplateSyntaxError as e:
raise self.error(token, e)
var_node = VariableNode(filter_expression)
self.extend_nodelist(nodelist, var_node, ...
}}
compile_filterはFilterExpressionオブジェクト生成している...
#code(Python){{
def __init__(self, token, parser):
self.token = token
matches = filter_re.finditer(token)
var_obj = None
filters = []
for match in matches:
if var_obj is None:
var, constant = match.group("var", "const...
if constant:
# 省略
elif var is None:
# 省略
else:
var_obj = Variable(var)
else:
# 省略
self.filters = filters
self.var = var_obj
}}
filter_reはFilterExpressionクラス定義のすぐ上にあります。...
ともかく変数は、
VariableNode
FilterExpression
Variable
というオブジェクトの構造になるようです。
***ブロック [#acaad311]
#code(Python){{
elif token.token_type == 2: # TOKEN_BLOCK
try:
command = token.contents.split()[0]
except IndexError:
raise self.error(token, 'Empty block ...
if command in parse_until:
# A matching token has been reached. ...
# the caller. Put the token back on t...
# caller knows where it terminated.
self.prepend_token(token)
return nodelist
# Add the token to the command stack. Thi...
# messages if further parsing fails due t...
# tag.
self.command_stack.append((command, token))
# Get the tag callback function from the ...
# the parser.
try:
compile_func = self.tags[command]
except KeyError:
self.invalid_block_tag(token, command...
# Compile the callback into a node object...
# the node list.
try:
compiled_result = compile_func(self, ...
except Exception as e:
raise self.error(token, e)
self.extend_nodelist(nodelist, compiled_r...
# Compile success. Remove the token from ...
self.command_stack.pop()
}}
二つのことが行われています。
+コマンドに対するタグオブジェクトを取得して呼び出す。結果...
+終わりのコマンドに来たら処理を終了してreturnする。見た感...
というわけでここからは各論です。
*タグライブラリ [#t6672795]
**タグの読み込み [#ree379ba]
チュートリアルのテンプレート例ではifやforが使われていまし...
#code(Python){{
def __init__(self, tokens, libraries=None, builtins=N...
self.tokens = tokens
self.tags = {}
self.filters = {}
self.command_stack = []
if libraries is None:
libraries = {}
if builtins is None:
builtins = []
self.libraries = libraries
for builtin in builtins:
self.add_library(builtin)
self.origin = origin
}}
ややこしいですがadd_libraryメソッドでtagsへの追加が行われ...
#code(Python){{
def add_library(self, lib):
self.tags.update(lib.tags)
self.filters.update(lib.filters)
}}
次にParserを作っている部分。初めの方に見たdjango.template...
#code(Python){{
parser = Parser(
tokens, self.engine.template_libraries, self....
self.origin,
)
}}
engineは、django.template.loaders.base.Loaderから渡されて...
#code(Python){{
return Template(
contents, origin, origin.template_nam...
)
}}
今度はLoaderにengineを渡した相手を探します。前回飛ばした...
#code(Python){{
def find_template_loader(self, loader):
if isinstance(loader, (tuple, list)):
args = list(loader[1:])
loader = loader[0]
else:
args = []
if isinstance(loader, six.string_types):
loader_class = import_string(loader)
return loader_class(self, *args)
else:
raise ImproperlyConfigured(
"Invalid value in template loaders config...
}}
というわけで、engineとはdjango.template.engine.Engineです。
template_builtinsに設定されているものを確認。
#code(Python){{
class Engine(object):
default_builtins = [
'django.template.defaulttags',
'django.template.defaultfilters',
'django.template.loader_tags',
]
def __init__(self, dirs=None, app_dirs=False, context...
debug=False, loaders=None, string_if_inv...
file_charset='utf-8', libraries=None, bu...
# 省略
if builtins is None:
builtins = []
# 省略
self.builtins = self.default_builtins + builtins
self.template_builtins = self.get_template_builti...
}}
get_template_builtinsはimportしてるだけなので、django.tem...
**タグライブラリの仕組み [#z8f614f7]
defaulttags.py内の前半はタグに対応していると思われるノー...
#code(Python){{
@register.tag('if')
def do_if(parser, token):
# {% if ... %}
bits = token.split_contents()[1:]
condition = TemplateIfParser(parser, bits).parse()
nodelist = parser.parse(('elif', 'else', 'endif'))
conditions_nodelists = [(condition, nodelist)]
token = parser.next_token()
# {% elif ... %} (repeatable)
# 省略
# {% else %} (optional)
if token.contents == 'else':
nodelist = parser.parse(('endif',))
conditions_nodelists.append((None, nodelist))
token = parser.next_token()
# {% endif %}
assert token.contents == 'endif'
return IfNode(conditions_nodelists)
}}
TemplateIfParserの中に立ち入ると長くなるので省略します。...
[
(ifに書かれている式を表すオブジェクト, ifがTrueの場合...
(None, elseの場合に使われるNodeList)
]
なお、@registerのregisterはdjango.template.libraryのLibra...
#code(Python){{
register = Library()
}}
Libraryクラスのtagメソッド
#code(Python){{
def tag(self, name=None, compile_function=None):
if name is None and compile_function is None:
# @register.tag()
return self.tag_function
elif name is not None and compile_function is None:
if callable(name):
# @register.tag
return self.tag_function(name)
else:
# @register.tag('somename') or @register....
def dec(func):
return self.tag(name, func)
return dec
elif name is not None and compile_function is not...
# register.tag('somename', somefunc)
self.tags[name] = compile_function
return compile_function
else:
# 省略
}}
今回の場合、nameがnot Noneでstr、compile_functionがNoneで...
+elifの1つ目、さらにelseに進み関数内関数のdecが返される。...
+デコレータの機能としてdecが呼び出される。tagメソッドが2...
+elifの2つ目の部分が実行されtagsに登録される
という動作をします。
*おわりに [#l3167c17]
さて、テンプレートファイルの読み込み、解析まで見てきまし...
というわけで次回に続く。
終了行:
[[Djangoを読む]]
#contents
*はじめに [#n7eb059c]
前回のおさらい。django.template.loaderのget_templateを呼...
#code(Python){{
def get_template(self, template_name, template_dirs=N...
"""
Calls self.get_template_sources() and returns a T...
the first template matching template_name. If ski...
template origins in skip are ignored. This is use...
during template extending.
"""
tried = []
args = [template_name]
# RemovedInDjango20Warning: Add template_dirs for...
# old loaders
if func_supports_parameter(self.get_template_sour...
args.append(template_dirs)
for origin in self.get_template_sources(*args):
if skip is not None and origin in skip:
tried.append((origin, 'Skipped'))
continue
try:
contents = self.get_contents(origin)
except TemplateDoesNotExist:
tried.append((origin, 'Source does not ex...
continue
else:
return Template(
contents, origin, origin.template_nam...
)
raise TemplateDoesNotExist(template_name, tried=t...
}}
今回はこの中の、Templateインスタンス生成の中に入っていき...
*django/template/base.py [#f18eaeb3]
Templateとは何者なのか確認。
#code(Python){{
from django.template import Origin, Template, TemplateDoe...
}}
django/template/__init__.py
#code(Python){{
# Template parts
from .base import ( ...
Context, Node, NodeList, Origin, RequestContext, Stri...
Variable,
)
}}
うーん、ややこしい。というわけでTemplateクラスはdjango/te...
#code(Python){{
class Template(object):
def __init__(self, template_string, origin=None, name...
try:
template_string = force_text(template_string)
except UnicodeDecodeError:
raise TemplateEncodingError("Templates can on...
"from unicode or ...
# If Template is instantiated directly rather tha...
# exactly one Django template engine is configure...
# This is required to preserve backwards-compatib...
# e.g. Template('...').render(Context({...}))
if engine is None:
from .engine import Engine
engine = Engine.get_default()
if origin is None:
origin = Origin(UNKNOWN_SOURCE)
self.name = name
self.origin = origin
self.engine = engine
self.source = template_string
self.nodelist = self.compile_nodelist()
}}
compile_nodelistメソッドによりコンパイルが行われるようで...
#code(Python){{
def compile_nodelist(self):
"""
Parse and compile the template source into a node...
is True and an exception occurs during parsing, t...
is annotated with contextual line information whe...
template source.
"""
if self.engine.debug:
lexer = DebugLexer(self.source)
else:
lexer = Lexer(self.source)
tokens = lexer.tokenize()
parser = Parser(
tokens, self.engine.template_libraries, self....
self.origin,
)
try:
return parser.parse()
except Exception as e:
if self.engine.debug:
e.template_debug = self.get_exception_inf...
raise
}}
デバッグ用の処理が少し追加されていますが、典型的なコンパ...
+Lexerでトークン化
+Parserでトークンからノードに変換
それぞれ見ていきましょう。
**Lexer [#fd23b4d3]
Lexerは同じbase.py内に書かれています。
#code(Python){{
def tokenize(self):
"""
Return a list of tokens from a given template_str...
"""
in_tag = False
lineno = 1
result = []
for bit in tag_re.split(self.template_string):
if bit:
result.append(self.create_token(bit, None...
in_tag = not in_tag
lineno += bit.count('\n')
return result
}}
tag_reはbase.pyの先頭の方に書かれています。{%, %}で囲むこ...
※}}を含むのでシンタックスハイライトなしで貼ってます
BLOCK_TAG_START = '{%'
BLOCK_TAG_END = '%}'
VARIABLE_TAG_START = '{{'
VARIABLE_TAG_END = '}}'
COMMENT_TAG_START = '{#'
COMMENT_TAG_END = '#}'
# match a variable or block tag and capture the entire t...
# delimiters
tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' %
(re.escape(BLOCK_TAG_START), re.escape(BLOCK_T...
re.escape(VARIABLE_TAG_START), re.escape(VARI...
re.escape(COMMENT_TAG_START), re.escape(COMME...
[[正規表現オブジェクトのsplitの挙動>https://docs.python.j...
キャプチャグループの丸括弧が pattern で使われていれば、
パターン内のすべてのグループのテキストも結果のリストの一...
実際、splitを実行してみると以下のようにテンプレートのタグ...
['', '{% if latest_question_list %}',
'\n <ul>\n ', '{% for question in latest_question...
'\n <li><a href="/polls/', '{{ question.id }}',
'/">', '{{ question.question_text }}',
'</a></li>\n ', '{% endfor %}',
'\n </ul>\n', '{% else %}',
'\n <p>No polls are available.</p>\n', '{% endif %}']
create_tokenではタグの種類によってTokenオブジェクトを作成...
#code(Python){{
def create_token(self, token_string, position, lineno...
"""
Convert the given token string into a new Token o...
If in_tag is True, we are processing something th...
otherwise it should be treated as a literal string.
"""
if in_tag and token_string.startswith(BLOCK_TAG_S...
# The [2:-2] ranges below strip off *_TAG_STA...
# We could do len(BLOCK_TAG_START) to be more...
# hard-coded the 2s here for performance. And...
# the TAG_START values are going to change an...
block_content = token_string[2:-2].strip()
if self.verbatim and block_content == self.ve...
self.verbatim = False
if in_tag and not self.verbatim:
if token_string.startswith(VARIABLE_TAG_START):
token = Token(TOKEN_VAR, token_string[2:-...
elif token_string.startswith(BLOCK_TAG_START):
if block_content[:9] in ('verbatim', 'ver...
self.verbatim = 'end%s' % block_content
token = Token(TOKEN_BLOCK, block_content,...
elif token_string.startswith(COMMENT_TAG_STAR...
content = ''
if token_string.find(TRANSLATOR_COMMENT_M...
content = token_string[2:-2].strip()
token = Token(TOKEN_COMMENT, content, pos...
else:
token = Token(TOKEN_TEXT, token_string, posit...
return token
}}
verbatimはテンプレートの処理を無効化する(つまり、タグが...
さて、対象のtoken_stringがタグなのかそうじゃないのかを表...
**Parser [#k6de54b7]
Parserもbase.py内に書かれています。parseメソッドは長いの...
***変数 [#z5262705]
#code(Python){{
elif token.token_type == 1: # TOKEN_VAR
if not token.contents:
raise self.error(token, 'Empty variab...
try:
filter_expression = self.compile_filt...
except TemplateSyntaxError as e:
raise self.error(token, e)
var_node = VariableNode(filter_expression)
self.extend_nodelist(nodelist, var_node, ...
}}
compile_filterはFilterExpressionオブジェクト生成している...
#code(Python){{
def __init__(self, token, parser):
self.token = token
matches = filter_re.finditer(token)
var_obj = None
filters = []
for match in matches:
if var_obj is None:
var, constant = match.group("var", "const...
if constant:
# 省略
elif var is None:
# 省略
else:
var_obj = Variable(var)
else:
# 省略
self.filters = filters
self.var = var_obj
}}
filter_reはFilterExpressionクラス定義のすぐ上にあります。...
ともかく変数は、
VariableNode
FilterExpression
Variable
というオブジェクトの構造になるようです。
***ブロック [#acaad311]
#code(Python){{
elif token.token_type == 2: # TOKEN_BLOCK
try:
command = token.contents.split()[0]
except IndexError:
raise self.error(token, 'Empty block ...
if command in parse_until:
# A matching token has been reached. ...
# the caller. Put the token back on t...
# caller knows where it terminated.
self.prepend_token(token)
return nodelist
# Add the token to the command stack. Thi...
# messages if further parsing fails due t...
# tag.
self.command_stack.append((command, token))
# Get the tag callback function from the ...
# the parser.
try:
compile_func = self.tags[command]
except KeyError:
self.invalid_block_tag(token, command...
# Compile the callback into a node object...
# the node list.
try:
compiled_result = compile_func(self, ...
except Exception as e:
raise self.error(token, e)
self.extend_nodelist(nodelist, compiled_r...
# Compile success. Remove the token from ...
self.command_stack.pop()
}}
二つのことが行われています。
+コマンドに対するタグオブジェクトを取得して呼び出す。結果...
+終わりのコマンドに来たら処理を終了してreturnする。見た感...
というわけでここからは各論です。
*タグライブラリ [#t6672795]
**タグの読み込み [#ree379ba]
チュートリアルのテンプレート例ではifやforが使われていまし...
#code(Python){{
def __init__(self, tokens, libraries=None, builtins=N...
self.tokens = tokens
self.tags = {}
self.filters = {}
self.command_stack = []
if libraries is None:
libraries = {}
if builtins is None:
builtins = []
self.libraries = libraries
for builtin in builtins:
self.add_library(builtin)
self.origin = origin
}}
ややこしいですがadd_libraryメソッドでtagsへの追加が行われ...
#code(Python){{
def add_library(self, lib):
self.tags.update(lib.tags)
self.filters.update(lib.filters)
}}
次にParserを作っている部分。初めの方に見たdjango.template...
#code(Python){{
parser = Parser(
tokens, self.engine.template_libraries, self....
self.origin,
)
}}
engineは、django.template.loaders.base.Loaderから渡されて...
#code(Python){{
return Template(
contents, origin, origin.template_nam...
)
}}
今度はLoaderにengineを渡した相手を探します。前回飛ばした...
#code(Python){{
def find_template_loader(self, loader):
if isinstance(loader, (tuple, list)):
args = list(loader[1:])
loader = loader[0]
else:
args = []
if isinstance(loader, six.string_types):
loader_class = import_string(loader)
return loader_class(self, *args)
else:
raise ImproperlyConfigured(
"Invalid value in template loaders config...
}}
というわけで、engineとはdjango.template.engine.Engineです。
template_builtinsに設定されているものを確認。
#code(Python){{
class Engine(object):
default_builtins = [
'django.template.defaulttags',
'django.template.defaultfilters',
'django.template.loader_tags',
]
def __init__(self, dirs=None, app_dirs=False, context...
debug=False, loaders=None, string_if_inv...
file_charset='utf-8', libraries=None, bu...
# 省略
if builtins is None:
builtins = []
# 省略
self.builtins = self.default_builtins + builtins
self.template_builtins = self.get_template_builti...
}}
get_template_builtinsはimportしてるだけなので、django.tem...
**タグライブラリの仕組み [#z8f614f7]
defaulttags.py内の前半はタグに対応していると思われるノー...
#code(Python){{
@register.tag('if')
def do_if(parser, token):
# {% if ... %}
bits = token.split_contents()[1:]
condition = TemplateIfParser(parser, bits).parse()
nodelist = parser.parse(('elif', 'else', 'endif'))
conditions_nodelists = [(condition, nodelist)]
token = parser.next_token()
# {% elif ... %} (repeatable)
# 省略
# {% else %} (optional)
if token.contents == 'else':
nodelist = parser.parse(('endif',))
conditions_nodelists.append((None, nodelist))
token = parser.next_token()
# {% endif %}
assert token.contents == 'endif'
return IfNode(conditions_nodelists)
}}
TemplateIfParserの中に立ち入ると長くなるので省略します。...
[
(ifに書かれている式を表すオブジェクト, ifがTrueの場合...
(None, elseの場合に使われるNodeList)
]
なお、@registerのregisterはdjango.template.libraryのLibra...
#code(Python){{
register = Library()
}}
Libraryクラスのtagメソッド
#code(Python){{
def tag(self, name=None, compile_function=None):
if name is None and compile_function is None:
# @register.tag()
return self.tag_function
elif name is not None and compile_function is None:
if callable(name):
# @register.tag
return self.tag_function(name)
else:
# @register.tag('somename') or @register....
def dec(func):
return self.tag(name, func)
return dec
elif name is not None and compile_function is not...
# register.tag('somename', somefunc)
self.tags[name] = compile_function
return compile_function
else:
# 省略
}}
今回の場合、nameがnot Noneでstr、compile_functionがNoneで...
+elifの1つ目、さらにelseに進み関数内関数のdecが返される。...
+デコレータの機能としてdecが呼び出される。tagメソッドが2...
+elifの2つ目の部分が実行されtagsに登録される
という動作をします。
*おわりに [#l3167c17]
さて、テンプレートファイルの読み込み、解析まで見てきまし...
というわけで次回に続く。
ページ名: