10

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:マクロ'js'はキーワード引数'caller'を取りません

または、次のような別の基本マクロを追加しようとすると

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

次の例外が発生します

jinja2.exceptions.TemplateAssertionError:ブロック'head_js'が2回定義されました

blockタグよりもタグの優先順位に関する設計上の問題が発生しているように感じmacroます(つまり、マクロは期待どおりにブロックタグをカプセル化していないようです)。


私の質問は非常に単純だと思います。

  1. Jinja2は私が試みていることを実行できますか?もしそうなら、どのように?

  2. そうでない場合は、この種のパターンをサポートする別のPythonベースのテンプレートエンジン(たとえば、mako、genshiなど)があります。これは、GoogleAppEngineで問題なく動作します。

読んでいただきありがとうございます-私はあなたの入力に感謝します。

ブライアン


編集:

この問題を解決するための拡張機能を作成しようとしています。私は途中です-次のコードを使用しています:

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>

もちろん、これは期待どおりにスクリプトを頭に入れることと同じではありませんが、少なくとも1つの場所に簡単に統合できます。

{% include "y.html" %}ただし、 「y.html」にステートメントが含まれている{% js %}場合{% js_content %}、インクルードのステートメントの前にが呼び出されるため、ソリューションは完全ではありません{% js %}(つまり、開始x.html前に完全に解析されy.htmlます)。

また、静的javascriptを持つ定数ノードを挿入する必要がありますが、まだ挿入していませんtry/catch。これは、そこに入れたいことを示しています。これは問題ではありません。

私は進歩を遂げることができてうれしく思います、そして私はインプットに感謝しています。

関連する質問を開きました:インクルード後のJinja2コンパイル拡張機能


編集

解決

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ます。


4

4 に答える 4

5

私のコメントから:

includeの代わりにextendを使用する場合は、それを行うことができます。ただし、解析ステップとレンダリングステップが完全に分離されているため、手遅れになるまで親スコープのコンテキストを変更することはできません。また、Jinjaコンテキストは不変であると想定されています。

例:

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 %}
于 2010-11-30T01:57:06.137 に答える
2

これを、マクロ内で機能する汎用キャプチャ拡張機能に一般化できます。これが私が書いたものです:

from jinja2 import nodes
from jinja2.ext import Extension

class CaptureExtension(Extension):
    """
    Generic HTML capture, inspired by Rails' capture helper

    In any template, you can capture an area of content and store it in a global
    variable:

    {% contentfor 'name_of_variable' %}
        blah blah blah 
    {% endcontentfor %}

    To display the result
    {{ name_of_variable }}

    Multiple contentfor blocks will append additional content to any previously 
    captured content.  

    The context is global, and works within macros as well, so it's useful for letting macros define
    javascript or <head> tag content that needs to go at a particular position
    on the base template.

    Inspired by http://stackoverflow.com/questions/4292630/insert-javascript-at-top-of-including-file-in-jinja-2
    and http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
    """
    tags = set(['contentfor'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)

    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.next()
        args = [parser.parse_expression()]
        body = parser.parse_statements(['name:endcontentfor'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args),[], [], body).set_lineno(tag.lineno)

    def _capture(self, name, caller):
        if name not in self.environment.globals:
            self.environment.globals[name] = ''
        self.environment.globals[name] += caller()
        return ""
于 2011-05-22T20:24:59.750 に答える
1

LeeSemelによる解決策は私にはうまくいきませんでした。現在、グローバルは実行時にこの種の変更から保護されていると思います。

from jinja2 import nodes
import jinja2
from jinja2.ext import Extension

class CaptureExtension(Extension):
    """
    Generic HTML capture, inspired by Rails' capture helper

    In any template, you can capture an area of content and store it in a global
    variable:

    {% capture 'name_of_variable' %}
        blah blah blah 
    {% endcapture %}
    {% capture 'a'  %}panorama{% endcapture %}

    To display the result
    {{ captured['name_of_variable'] }}
    {{ captured['a'] }}

    The context is global, and works within macros as well, so it's useful for letting macros define
    javascript or <head> tag content that needs to go at a particular position
    on the base template.

    Inspired by http://stackoverflow.com/questions/4292630/insert-javascript-at-top-of-including-file-in-jinja-2
    and http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
    """
    tags = set(['capture'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)
        assert isinstance(environment, jinja2.Environment)
        self._myScope = {}
        environment.globals['captured'] = self._myScope

    def parse(self, parser):
        """Parse tokens """
        assert isinstance(parser, jinja2.parser.Parser)
        tag = parser.stream.next()
        args = [parser.parse_expression()]
        body = parser.parse_statements(['name:endcapture'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args),[], [], body).set_lineno(tag.lineno)

    def _capture(self, name, caller):
        self._myScope[name] = caller()
        return ""
于 2013-02-25T12:46:39.040 に答える
1

上記の答えは私の質問にほぼ答えました(JavaScriptの異なるビットをすべて1つの場所(下部)に配置したかった)、キャプチャを相互に追加する「+ =」の種類を使用して、更新時に問題が発生することを受け入れます。キャプチャはすべての複数のコピーで終わり、更新がヒットされた回数に応じてあらゆる種類の問題を引き起こしました。

辞書のタグの行番号を使用してこれを回避し、キャプチャが1回だけ行われるようにしました。このアプローチの1つの小さな欠点は、キャプチャタグが検出されるたびにグローバルを再構築する必要があることです。

でも私にはうまくいきます。

from jinja2 import Markup, nodes
from jinja2.ext import Extension

class CaptureExtension(Extension):
    tags = set(['capture'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)
        environment.globals['captured'] = {}
        self._captured = {}

    def parse(self, parser):
        lineno = next(parser.stream).lineno
        args = [parser.parse_expression(), nodes.Const(lineno)]
        body = parser.parse_statements(['name:endcapture'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args), [], [], body).set_lineno(lineno)

    def _capture(self, name, lineno, caller):
        if name not in self._captured:
            self._captured[name] = {}
        self._captured[name][lineno] = caller()
        markup = Markup(''.join(s for s in self._captured[name].values()))
        self.environment.globals['captured'][name] = markup
        return ''
于 2016-02-26T12:24:27.040 に答える