1. Приветствую гостей и пользователей нашего форума! Первый раз вы у нас или же давно участвуете в жизни ресурса, хочу напомнить несколько моментов.

    1) Пользуемся поиском по форуму! Зачастую информация может находиться не по разделу!

    2) Раздел ИНФО-продуктов относительно новый, но имеем уже более 3000 высококлассных материалов (пользуемся сортировкой по прификсам).

    3) И самое важное, КАК КАЧАТЬ БЕЗ ОГРАНИЧЕНИЙ, вся информация находится по этой ссылке КУПИТЬ VIP

    4) Временная акция, получи +5 постов за вступление в нашу группу "Вконтакте" Более подробно ТУТ

    5) Веди активную жизнь на форуме и получай рубли на личный счёт!

    Скрыть объявление
  2. На нашем форуме Null-Prog действует серьёзное правило касательно размещения материалов!

    ДЛЯ РЕЛИЗЁРОВ: категорически запрещается выкладка материалов на файлообменники типа Deposit, letitbit и другие, требующие просмотров рекламы, обрезающие скорость и тд. Нарушителям, первые 2 раза предупреждения, далее БАН. Тему по этому поводу можно посмотреть ТУТ

    Скрыть объявление
  3. В тестовом режиме на нашем форуме открыт онлайн конструктор сайтов. Вы можете попробовать создать свой сайт у НАС, интуитивно понятный интерфейс, переведёт на 95%, быстрый экспорт проекта, от вас только перетаскивать элементы и вставить в них необходимый текст!

    Все вопросы ТУТ

    Скрыть объявление

  4. Скрыть объявление
  5. Уважаемые форумчане, открывается новый раздел форума, посвящённый ремонту и эксплуатации автомобилей. Просмотреть его можно ТУТ

    Так как раздел новый, информация будет пополнять каждый день. Если есть какие либо замечания по этому разделу, отписываемся в соответствующий раздел форума, либо в личку.

    Напоминаю, сообщения в разделе АВТО не учитываются, общение не ограничено.

    Скрыть объявление
  6. Объявляется набор Модераторов на различные раздел форума, свои заявки можно оставлять в ЭТОМ разделе, перед оставлением заявки рекомендуется ознакомиться с ПРАВИЛАМИ для модераторов.

Пишем свой шаблонизатор на Python

Тема в разделе "Perl, Python, Ruby", создана пользователем Sam Jack, 18 май 2015.

  1. Sam Jack

    Sam Jack Капитан-Узурпатор
    Команда форума Созидатель

    Регистрация:
    5 май 2015
    Сообщения:
    13.762
    Симпатии:
    4.589
    Наверняка многие из вас задумывались о том, как устроены шаблонизаторы, какого его внутреннее устройство и каким образом происходит преобразование в фрагменты HTML-кода, однако не догадывались о каких-то особенностях его реализации. Поэтому давайте реализуем упрощенную версию движка шаблонов и продемонстрируем как это работает «под капотом».

    Для начала необходимо определится с синтаксисом, с которым будет работать наш движок. Другими словами — конструкции языка, которые будет понимать шаблонизатор и обрабатывать их соответствующим образом.

    Синтаксис языка

    Хоть в нашем случае язык будет сильно упрощен, но для примера реализуем работу с переменными и блоками, которые будут выглядеть примерно следующим образом:
    <!—- переменные будет расположены внутри `{{` и `}}` --><div>{{my_var}}</div><!—- блоки же окружены `{%` и `%}` -->{% each items %}<div>{{it}}</div>{% end %}

    Большинство блоков должно быть закрыто, как в примере, приведенном выше и оканчиваться тегом end.
    Наш шаблонизатор будет также поддерживать работу с циклами и условиями. Не забудем добавить поддержку вызовов внутри блоков – это будет достаточно удобной вещью, которая может нам пригодится.

    Циклы

    С их помощью мы сможем обходить коллекции и получать элемент, с которым будет совершать нужные операции:

    {% each people %}
    <div>{{it.name}}</div>
    {% end %}

    {% each [1, 2, 3] %}
    <div>{{it}}</div>
    {% end %}

    {% each records %}
    <div>{{..name}}</div>
    {% end %}



    В этом примере, people это коллекция и it ссылается на элемент из нее. Точка, как разделитель, позволяет обратится к полям объекта, чтобы извлечь необходимую информацию. Использование ".." предоставит доступ к именам, расположенных в контексте родителя.

    Условия

    Не нуждаются в представлении. Наш движок будет их поддерживать конструкции if…else, а также операторы: ==, <=,> =, =, is, >, <!.

    {% if num > 5 %}
    <div>больше 5</div>
    {% else %}
    <div>меньше или равно 5</div>
    {% end %}

    Вызовы функций

    Вызовы должны быть указаны внутри шаблона. Не забудем, конечно же, поддержку именованных и позиционных параметров. Блоки, вызывающие функции, не должны быть закрытыми.

    <!—- поддержка позиционных аргументов... -->
    <div class='date'>{% call prettify date_created %}</div>
    <!-- ...и именованных аргументов -->
    <div>{% call log 'here' verbosity='debug' %}</div>

    Теоретическая часть

    Прежде чем углубляться в детали движка, который будет заниматься рендерингом шаблонов необходимо иметь представление о том, каким образом представлять шаблоны в памяти.

    В нашем случае будут использоваться абстрактные синтаксические деревья (далее АСД), столь необходимые для представления данных. АСД – это результат лексического анализа исходного кода. Эта структура имеет много достоинств по сравнению с исходным кодом, одним из которых является исключение ненужных текстовых элементов (например, разделителей).

    Мы будет производить парсинг данных и анализировать шаблон, строя соответствующее дерево, которое будет представлять некий скомпилированный шаблон. Рендеринг шаблона будет представлять собой простой обход по дереву, при котором будут возвращаться элементы дерева, сформированные в фрагменты HTML кода.

    Определение синтаксиса

    Первым шагом в нашем нелегком деле будет разделение контента на фрагменты. Каждый фрагмент – это тег HTML. Для разделение контента будут использоваться регулярные выражения, а также функция split().

    VAR_TOKEN_START = '{{'
    VAR_TOKEN_END = '}}'
    BLOCK_TOKEN_START = '{%'
    BLOCK_TOKEN_END = '%}'
    TOK_REGEX = re.compile(r"(%s.*?%s|%s.*?%s)" % (
    VAR_TOKEN_START,
    VAR_TOKEN_END,
    BLOCK_TOKEN_START,
    BLOCK_TOKEN_END
    ))

    Итак, давайте проанализируем TOK_REGEX. В этом регулярном выражении у нас есть выбор между переменной или блоком. В этом есть определенный смысл – мы же хотим разделить содержимое по переменным или блокам. Обертка в виде тегов, которые были оговорены заранее, помогут нам определить фрагменты, которые нужно обработать. Знак ?, указанный внутри регулярного выражения – это не жадное повторение. Это необходимо для того, чтобы регулярное выражение было «ленивым» и останавливалось на первом совпадении, например, когда нужно извлечь переменные, указанные внутри блока. Кстатиздесь можно почитать о том, как контролировать жадность регулярных выражений.

    Вот простой пример, демонстрирующий работу данной регулярки:
    >>> TOK_REGEX.split('{% each vars %}<i>{{it}}</i>{% endeach %}')
    ['{% each vars %}', '<i>', '{{it}}', '</i>', '{% endeach %}']

    Кроме того, каждому обработанному фрагменту будет соответствовать свой тип, которые будут учитываться при обработке и компиляции. Фрагменты будут разделены на четыре типа:

    VAR_FRAGMENT = 0
    OPEN_BLOCK_FRAGMENT = 1
    CLOSE_BLOCK_FRAGMENT = 2
    TEXT_FRAGMENT = 3

    Формирование АСД

    После анализа регулярным выражением исходного текста HTML-страницы, содержащей фрагменты, относящиеся к нашему шаблонизатору, необходимо построить дерево на основе элементов, которые относятся к нашему «языку». У нас будет класс Node, являющегося корнем дерева и содержащего дочерние узлы, которые являются подклассами для каждого типа узла. Подклассы должны содержать методы process_fragment() и render():
    process_fragment() используется для дальнейшего анализа содержимого и хранения необходимых атрибутов объекта Node.
    render() нужен для преобразования соответствующего фрагмента в HTML –код

    Опционально реализовать методы enter_scope() и exit_scope(), которые вызываются в процессе работы компилятора. Первая функция, enter_scope(), вызывается когда узел создает новую область (об этом позже), и exit_scope() чтобы покинуть текущую область, обрабатываемой при завершении обработки области.

    Базовый класс Node:
    class _Node(object):
    def __init__(self, fragment=None):
    self.children = []
    self.creates_scope = False
    self.process_fragment(fragment)

    def process_fragment(self, fragment):
    pass

    def enter_scope(self):
    pass

    def render(self, context):
    pass

    def exit_scope(self):
    pass

    def render_children(self, context, children=None):
    if children is None:
    children = self.children
    def render_child(child):
    child_html = child.render(context)
    return '' if not child_html else str(child_html)
    return ''.join(map(render_child, children))



    А вот пример подкласса Variable:
    class _Variable(_Node):
    def process_fragment(self, fragment):
    self.name = fragment

    def render(self, context):
    return resolve_in_context(self.name, context)

    При определения узла будет анализироваться фрагмент текста, который нам подскажет тип этого фрагмента (т.е. это переменная, скобка, и т.п.)
    Текст и переменные будут преобразованы в соответствующие им подклассы.
    Если же это циклы, то их обработка будет происходить немного дольше, ведь это означает целый ряд команд, которые нужно выполнить. Узнать что это блок команд достаточно просто: необходимо лишь проанализировать фрагмент текста, заключенного в «{%» и « %}». Вот простой пример:
    {% each items %}

    Где each – это предполагаемый блок команд

    Важным моментом является то, что каждый узел создает область. В процессе компиляции мы отслеживаем текущую область и узлы, добавляемые в рамках этой области. Если в процессе анализа встречается закрывающая скобка, то завершается формирование текущей области, и происходит переход к следующей.

    def compile(self):
    root = _Root()
    scope_stack = [root]
    for fragment in self.each_fragment():
    if not scope_stack:
    raise TemplateError('nesting issues')
    parent_scope = scope_stack[-1]
    if fragment.type == CLOSE_BLOCK_FRAGMENT:
    parent_scope.exit_scope()
    scope_stack.pop()
    continue
    new_node = self.create_node(fragment)
    if new_node:
    parent_scope.children.append(new_node)
    if new_node.creates_scope:
    scope_stack.append(new_node)
    new_node.enter_scope()
    return root

    Рендеринг

    Последним шагом является преобразование АСД к HTML. Для этого мы посещаем все узлы дерева и вызываем метод render(). В процессе рендеринга необходимо учесть, с чем в данный момент происходит работа: с литералами или контекстом имени переменной. Для этого используем ast.literal_eval(), который безопасно позволяет проанализировать строку:

    def eval_expression(expr):
    try:
    return 'literal', ast.literal_eval(expr)
    except ValueError, SyntaxError:
    return 'name', expr

    Если же имеем дело с контекстом имени переменной, то анализируем, что указано с ним: «.» или «..»:
    def resolve(name, context):
    if name.startswith('..'):
    context = context.get('..', {})
    name = name[2:]
    try:
    for tok in name.split('.'):
    context = context[tok]
    return context
    except KeyError:
    raise TemplateContextError(name)

    Заключение

    Данная статья является переводом, которая позволяет дать общее представление о том, как работают шаблонизаторы. Хоть это и является простейшим примером реализации, но его можно использовать как базу для построения более сложных шаблонизаторов.
     
    Метки:
  2. amz66

    amz66 Новичок

    Регистрация:
    29 янв 2020
    Сообщения:
    17
    Симпатии:
    1
    Наверное сегодня это уже не работает.
     
    hgjghjghj нравится это.
  3. hgjghjghj

    hgjghjghj Новичок

    Регистрация:
    29 янв 2020
    Сообщения:
    4
    Симпатии:
    0
    Шаблонизаторы действительно устарели, но думаю кто-то и на них сейчас сидит)
     

Поделиться этой страницей

iHax Community
Рейтинг@Mail.ru Яндекс.Метрика мониторинг сайтов
Форум программного обеспечения/
Загрузка...