python - 在 Jinja 2 的包含文件顶部插入 javascript

标签 python templates mako jinja2 genshi

在 Jinja2 中,我希望通过运行下面的代码来运行:

from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('x.html')
print template.render()

本质上,目标是将所有 javascript 合并到 <head> 中使用 {% call js() %} /* some js */ {% endcall %} 标记宏。


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: macro 'js' takes no keyword argument 'caller'

或者,当我尝试添加另一个基础宏时,例如

{% macro js2() -%}
{%- block head_js -%}
//     ... something
{%- endblock -%}
{%- endmacro %}

我得到以下异常

jinja2.exceptions.TemplateAssertionError: block 'head_js' defined twice

我觉得我遇到了关于 block 优先级的设计问题macro 上的标签标签(即宏似乎没有按照我期望的方式封装 block 标签)。


我想我的问题很简单:

  1. Jinja2 可以做我正在尝试的事情吗?如果是,怎么办?

  2. 如果没有,是否有另一个基于 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.htmly.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 插入它.


最佳答案

来 self 的评论:

If you would use extend instead of include you could do it. But because of the full separation between the parse and render step you won't be able to change the context of the parent scope till after it's too late. Also, the Jinja context is supposed to be immutable.

例子:

base.html

<html>
   <head>
      {% block head %}

      <title>{% block title %}This is the main template{% endblock %}</title>

      <script type="text/javascript">
      {% block head_js %}
      $(function () {
        $("#abc").css("color", "red");
      });
      {% endblock %}
      </script>

      {% endblock head_js %}
   </head>
   <body>
      {% block body %}
      <h1>{% block body_title %}This is the main template{% endblock body_title %}</h1>

      {% endblock body %}
   </body>
 </html>

some_page.html

{% block title %}This is some page{% endblock title %}

{% block head_js %}
{{ super() }}
try { {{ caller() }} } catch (e) {
   my.log.error(e.name + ": " + e.message);
}        // jquery parlance:
{% endblock head_js %}

关于python - 在 Jinja 2 的包含文件顶部插入 javascript,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4292630/

相关文章:

python - 更好的错误报告 mako

python - 如何检查 python mako 模板中是否存在列表变量?

python - 如何在一系列列上拆分列表?

python - 在 YAML 中执行算术运算?

python - 为来自 Pandas 数据框的轨迹数据绘制热图

c++ - 实例化多个模板

c++ - 嵌套模板类和全局命名空间中的函数

python - 运算符或函数的操作数类型不正确;运算符 : ADD, 操作数类型 : STRING, typeSet:ALLOWED_FOR_ADD_OPERAND

html - 如何使用django在所有浏览器中播放wav文件?

python - Pylons/路线 url_for() 在模板内改变了吗?