1. Объявляется набор Модераторов, свои заявки можно оставлять в этом разделе, перед оставлением заявки рекомендуется ознакомиться с Правилами для модераторов.
    Скрыть объявление
  2. Приобрети VIP доступ вовсе разделы форума (временно автоматическая оплата не работает и, если Вы хотите приобрести "VIP" доступ, то пишите об этом мне в профиле или в переписке).
    Purchase VIP access to all sections of the forum (temporarily automatic payment does not work and if you want to buy "VIP" access, write to me about it in the profile or in the correspondence).).
    Скрыть объявление
  3. Внимание! Форум продается, по вопросам покупки писать в ЛС

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

Тема в разделе "Perl, Python, Ruby", создана пользователем letun, 26 май 2013.

  1. letun

    letun Капитан-Узурпатор

    Регистрация:
    7 май 2013
    Сообщения:
    125
    Симпатии:
    36
    Пол:
    Мужской
    Наверняка многие из вас задумывались о том, как устроены шаблонизаторы, какого его внутреннее устройство и каким образом происходит преобразование в фрагменты 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. xakc

    xakc Новичок

    Регистрация:
    17 июн 2013
    Сообщения:
    14
    Симпатии:
    1
    Пол:
    Мужской
    а есть ли программа для этоого дела :(
     
  3. genesis2

    genesis2 Новичок

    Регистрация:
    31 окт 2013
    Сообщения:
    4
    Симпатии:
    1
    Пол:
    Мужской
    Форматирование кода просто жесть :) В качестве альтернативы можно посмотреть реализацию шаблонов в Django - там вполне богатый функционал, ну, или Jinja2 - тоже не уступает.
     
  4. yrewq

    yrewq Новичок

    Регистрация:
    7 фев 2014
    Сообщения:
    0
    Симпатии:
    2
    Какая программа? Нотепад!
     
  5. yrewq

    yrewq Новичок

    Регистрация:
    7 фев 2014
    Сообщения:
    0
    Симпатии:
    2
    Кстати, подскажите хороший хостинг для Питона
     
  6. provirtuos

    provirtuos Новичок

    Регистрация:
    27 фев 2014
    Сообщения:
    4
    Симпатии:
    1
    Пол:
    Мужской
    Pyton сложный язык?
     
  7. letun

    letun Капитан-Узурпатор

    Регистрация:
    7 май 2013
    Сообщения:
    125
    Симпатии:
    36
    Пол:
    Мужской
    Трудно сказать однозначно...Для тех кто знает языки программирование - ничего сложного
     
  8. витас

    витас Новичок

    Регистрация:
    19 мар 2014
    Сообщения:
    3
    Симпатии:
    0
    так как отказался от окон и перешёл на линукс, питон - логичный выбор. Только с хостингом проблема. Может у кого есть предложения, идеи ?
     
  9. nikkk

    nikkk Новичок

    Регистрация:
    19 мар 2014
    Сообщения:
    20
    Симпатии:
    1
    Пол:
    Мужской
    Нет его иногда называют псевдоязыком) он очень простой, динамически тепизированный иногда его считают идеальным для начала программирования)

    Дж
    Джанго.ру от 117 р за м, ну или локум тоже дешево

    А зачем вообще нужен шаблонизатор свой из и так с десяток) притом профессиональных, поддерживаемых сообществом)
     
    Последнее редактирование модератором: 19 мар 2014
  10. trovka

    trovka Новичок

    Регистрация:
    26 мар 2014
    Сообщения:
    1
    Симпатии:
    0
    Пол:
    Мужской
    может быть ты имел ввиду джино?
     
  11. Kaktak

    Kaktak Новичок

    Регистрация:
    2 апр 2014
    Сообщения:
    6
    Симпатии:
    0
    Пол:
    Женский
    а какую-нибудь литературу порекомендуете?
     
  12. RomaZveR

    RomaZveR Новичок

    Регистрация:
    1 май 2014
    Сообщения:
    2
    Симпатии:
    0
    Пол:
    Мужской
    относительно лёгкий.
     
  13. bbcustom

    bbcustom Новичок

    Регистрация:
    12 май 2014
    Сообщения:
    19
    Симпатии:
    0
    Пол:
    Мужской
    а может кто-нибудь может подсказать хорошие видео уроки ? всё, что смотрел, было как то обо всём и не о чём
     
  14. neon001

    neon001 Новичок

    Регистрация:
    29 май 2014
    Сообщения:
    7
    Симпатии:
    2
    Пол:
    Мужской
    Лучше книгу тогда почитай, начни с классики, больше пользы будет.
     
  15. benolder

    benolder Новичок

    Регистрация:
    17 авг 2014
    Сообщения:
    3
    Симпатии:
    1
    Пол:
    Мужской
  16. letun

    letun Капитан-Узурпатор

    Регистрация:
    7 май 2013
    Сообщения:
    125
    Симпатии:
    36
    Пол:
    Мужской
    Снова твой высер...Давать ссыль на первоисточник лично мое дело...Ты пока по сути просто показал, что являешься фанатом Харбара и не более...Я рад что ты там знакомишься со статьями, но судя по ответам IQ на получения знаний и ответа по существу - как у пятилетнего ребенка
     
  17. pikaper

    pikaper Новичок

    Регистрация:
    16 окт 2017
    Сообщения:
    1
    Симпатии:
    0
    Пол:
    Мужской
    помогите, ошибка какая то
     
  18. циник

    циник Creator Команда форума Администратор

    Регистрация:
    17 окт 2012
    Сообщения:
    1.339
    Симпатии:
    629
    Пол:
    Мужской
    а более подробно, или хотя бы скрин ошибки
     
  19. ๖ۣۣۜAnton ๖ۣۣۜBoing

    ๖ۣۣۜAnton ๖ۣۣۜBoing Новичок

    Регистрация:
    22 июл 2016
    Сообщения:
    2
    Симпатии:
    0
    Пол:
    Мужской
    Спасибо за урок.
     

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