سؤال

في Jinja2 ، أود أن يعمل ما يلي كما يبدو ، من خلال الجري:

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

والهدف من ذلك هو تجميع كل JavaScript في <head> العلامات باستخدام AA {% 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 %}

نتيجة متوقعة

عندما أقوم بتشغيل X.HTML عبر Jinja2 ، أتوقع أن تكون النتيجة:

<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' لا يأخذ وسيطة للكلمة الرئيسية 'المتصل'

أو عندما أحاول إضافة ماكرو على أساس آخر مثل

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

أحصل على الاستثناء التالي

jinja2.exceptions.templateassertionerror: كتلة 'head_js' محددة مرتين

أشعر وكأنني أواجه مشكلة في التصميم فيما يتعلق بأسبقية block العلامات على macro العلامات (أي وحدات الماكرو لا يبدو أنها تغلف علامات الكتلة بالطريقة التي أتوقعها).


أفترض أن أسئلتي بسيطة للغاية:

  1. هل يمكن لـ Jinja2 فعل ما أحاول؟ إذا كان الأمر كذلك ، كيف؟

  2. إذا لم يكن الأمر كذلك ، فهل هناك محرك آخر يعتمد على Python يدعم هذا النوع من النمط (على سبيل المثال Mako ، Genshi ، إلخ) ، والذي سيعمل بدون مشكلة في محرك تطبيق Google

شكرا لك على القراءة - أقدر مدخلاتك.

براين


يحرر:

أحاول كتابة تمديد لحل هذه المشكلة. أنا في منتصف الطريق - باستخدام الكود التالي:

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 يبدأ).

أحتاج أيضًا إلى ذلك ، لكن لم يتم إدراج عقد ثابت 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 هذا لديه كل جافا سكريبت. يمكنني إدراج هذا عبر ، على سبيل المثال ، string.Template.


هل كانت مفيدة؟

المحلول

من تعليقي:

إذا كنت تستخدم تمديدًا بدلاً من تضمين ، فيمكنك القيام بذلك. ولكن بسبب الفصل الكامل بين التحليل والخطوة ، فلن تتمكن من تغيير سياق نطاق الوالد حتى بعد فوات الأوان. أيضا ، من المفترض أن يكون سياق جينجا غير قابل للتغيير.

مثال:

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 %}

نصائح أخرى

يمكنك تعميم هذا في امتداد لالتقاط عام يعمل داخل وحدات الماكرو. هذا واحد كتبت:

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 ""

لم ينجح حل لي سيميل لي. أعتقد أن Globals محمية من هذا النوع من التعديل في وقت التشغيل الآن.

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 ""

الإجابات أعلاه تقريبا أجبت على استعلامي (أردت أن أضع أجزاء متباينة من JavaScript في مكان واحد - القاع) ، قبول استخدام "+=" الذي يلتقط لبعضها البعض تسبب مشاكل في التحديث. ستنتهي الالتقاط بنسخ متعددة من كل شيء وتسبب في جميع أنواع المشكلات اعتمادًا على عدد المرات التي تم فيها تحقيق تحديث.

عملت حول هذا باستخدام رقم سطر العلامة في القاموس لضمان الانتهاء من التقاط مرة واحدة فقط. إن العيب البسيط الواحد لهذا النهج هو الاحتياجات العالمية لإعادة بناءها في كل مرة يتم فيها مواجهة التقاط.

يعمل بشكل جيد بالنسبة لي رغم ذلك.

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 ''
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top