【发布时间】:2011-05-16 14:40:21
【问题描述】:
在 Jinja2 中,我希望通过运行以下命令来正常工作:
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('x.html')
print template.render()
本质上,目标是使用 {% call js() %} /* some js */ {% endcall %} 宏将所有 javascript 合并到 <head> 标记中。
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>
实际结果
实际结果并不令人鼓舞。我得到了几种可能具有启发性的错误,例如:
TypeError: 宏 'js' 没有关键字参数 'caller'
或者,当我尝试添加另一个基础宏,例如
{% 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() 将产生一些有启发性的 cmets 和预期的输出:
<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 %} 语句之前被调用(即@ 987654339@ 在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 插入它。
【问题讨论】:
-
为什么要把所有的JS都放在头上?
-
@Cameron:一致性、易于调试以及代码和内容的分离。我不想在我的 HTML 中乱扔数百个
<script>标签。 -
@Brian:很公平,但我担心没有简单的方法可以做到这一点......我也不确定你是否可以在宏中包含块。
-
@Cameron:我也有同样的怀疑,但它确实让我觉得它不是完全不受欢迎的模式,其他人可能已经解决了。
-
如果您使用
extend而不是include,您可以这样做。但是由于parse和render步骤之间的完全分离,您将无法更改父范围的上下文,直到为时已晚。此外,Jinja 上下文应该是不可变的。
标签: python templates mako jinja2 genshi