在 Jinja2 中,我希望通过运行以下命令按其应有的方式工作:
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('x.html')
print template.render()
<head>
本质上,目标是通过使用{% call js() %} /* some js */ {% endcall %}
宏将所有 javascript 合并到标签中。
x.html
<html>
<head>
<script type="text/javascript>
{% block head_js %}{% endblock %}
</script>
</head>
<body>
{% include "y.html" %}
</body>
</html>
y.html
{% macro js() -%}
// extend head_js
{%- block head_js -%}
{{ super() }}
try { {{ caller() }} } catch (e) {
my.log.error(e.name + ": " + e.message);
}
{%- endblock -%}
{%- endmacro %}
Some ... <div id="abc">text</div> ...
{% call js() %}
// jquery parlance:
$(function () {
$("#abc").css("color", "red");
});
{% endcall %}
预期结果
当我通过 jinja2 运行 X.html 时,我希望结果是:
<html>
<head>
<script type="text/javascript>
try { {{ $("#abc").css("color", "red"); }} } catch (e) {
usf.log.error(e.name + ": " + e.message);
}
</script>
</head>
<body>
Some ... <div id="abc">text</div> ...
</body>
</html>
实际结果
实际结果并不令人鼓舞。我得到了几种可能具有启发性的错误,例如:
类型错误:宏“js”没有关键字参数“调用者”
或者,当我尝试添加另一个基础宏时,例如
{% macro js2() -%}
{%- block head_js -%}
// ... something
{%- endblock -%}
{%- endmacro %}
我得到以下异常
jinja2.exceptions.TemplateAssertionError: block 'head_js' 定义了两次
我觉得好像我遇到了关于block
标签优先于标签的设计问题macro
(即宏似乎没有以我期望的方式封装块标签)。
我想我的问题很简单:
Jinja2 可以做我正在尝试的事情吗?如果是这样,怎么做?
如果没有,是否有另一个基于 Python 的模板引擎支持这种模式(例如 mako、genshi 等),它可以在 Google App Engine 中正常工作
感谢您阅读 - 我感谢您的意见。
布赖恩
编辑:
我正在尝试编写一个扩展来解决这个问题。我已经完成了一半——使用以下代码:
from jinja2 import nodes, Environment, FileSystemLoader
from jinja2.ext import Extension
class JavascriptBuilderExtension(Extension):
tags = set(['js', 'js_content'])
def __init__(self, environment):
super(JavascriptBuilderExtension, self).__init__(environment)
environment.extend(
javascript_builder_content = [],
)
def parse(self, parser):
"""Parse tokens """
tag = parser.stream.next()
return getattr(self, "_%s" % str(tag))(parser, tag)
def _js_content(self, parser, tag):
""" Return the output """
content_list = self.environment.javascript_builder_content
node = nodes.Output(lineno=tag.lineno)
node.nodes = []
for o in content_list:
print "\nAppending node: %s" % str(o)
node.nodes.extend(o[0].nodes)
print "Returning node: %s \n" % node
return node
def _js(self, parser, tag):
body = parser.parse_statements(['name:endjs'], drop_needle=True)
print "Adding: %s" % str(body)
self.environment.javascript_builder_content.append(body)
return nodes.Const('<!-- Slurped Javascript -->')
env = Environment(
loader = FileSystemLoader('.'),
extensions = [JavascriptBuilderExtension],
)
这使得将 Javascript 添加到模板末尾变得很简单......例如
<html>
<head></head>
<body>
{% js %}
some javascript {{ 3 + 5 }}
{% endjs %}
{% js %}
more {{ 2 }}
{% endjs %}
<script type="text/javascript">
{% js_content %}
</script>
</body>
</html>
运行env.get_template('x.html').render()
将产生一些启发性的评论和预期的输出:
<html>
<head>
<script type="text/javascript>
</script>
</head>
<body>
<!-- Slurped Javascript -->
<!-- Slurped Javascript -->
<script type="text/javascript">
some javascript 8
more 2
</script>
</body>
</html>
当然,这与希望的那样将脚本放在头脑中并不相同,但至少它可以方便地合并到一个地方。
但是,解决方案并不完整,因为当您{% include "y.html" %}
在其中有一个“y.html”包含{% js %}
语句时,{% js_content %}
会在包含语句之前调用该{% js %}
语句(即在开始x.html
之前完全解析y.html
)。
我还需要(但还没有)插入具有静态 javascript 的常量节点,try/catch
我表示我想在其中拥有它。这不是问题。
我很高兴能取得进展,我很感谢您的意见。
我已经打开了相关问题:Jinja2 compile extension after includes
编辑
解决方案
class JavascriptBuilderExtension(Extension):
tags = set(['js'])
def __init__(self, environment):
super(JavascriptBuilderExtension, self).__init__(environment)
environment.extend(jbc = "",)
def parse(self, parser):
"""Parse tokens """
tag = parser.stream.next()
body = parser.parse_statements(['name:endjs'], drop_needle=True)
return nodes.CallBlock(
self.call_method('_jbc', [], [], None, None),
[], [], body
).set_lineno(tag.lineno)
def _jbc(self, caller=None):
self.environment.jbc += "\ntry { %s } catch (e) { ; };" % caller()
return "<!-- Slurped -->"
完成后,环境将包含一个包含jbc
所有 Javascript 的变量。例如,我可以通过string.Template
.