ksnk/templater

Jinja2 php templater engine

100 2022-10-22 08:09 UTC

This package is auto-updated.

Last update: 2024-09-24 07:06:56 UTC


README

В качестве исходного языка для реализации шаблонов выбираем http://jinja.pocoo.org/2/documentation/templates - Jinja2 - развитие языка Django-template. Происходит выравниваиние языка с языком шаблонизатора Twig. Во всяком случае, все шаблоны twig проверяются на совместимость с шаблонизатором.

Фенечки

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

Основная функция шаблонизатора - сгенерировать PHP код (для версии шаблонов - генераторов PHP) или какой другой код. В дальнейшем, исполнение этого кода с нужными данными и приводит к генерации html (или чего там на самом деле шаблон должен генерить).

При необходимости, можно создать модель шаблонов для текущих надобносей. К примеру шаблоны для генерации html на стороне клиента из javascript. Шаблон js_internal именно для этой цели и делался. К сожалению, по отсутствии реальной надобности - не дописан.

Документация

Ядро и описание языка шаблонов читаем на сайте http://jinja.pocoo.org/2/documentation/templates. Остатки совести и лень не позволяют мне тупо скопировать описание с сайта, а лень и недостаток знаний английского - перевести описание достаточно понятно для изложения.

Сейчас проводится выравнивание реализованных фенечек с существующей версией Twig. Пока разница в языке - в реализации функции range. У меня она генерит массив от нуля до MAX с шагом STEP (как в Jinja), соотвестственно получая 2 параметра, а в twig - от MIN до MAX с шагом 1. Впрочем, я поьлзуюсь записью x..y с литеральными x и y, так что пока мин не обнаружено..

Ниже будет описание имеющихся отличий.

Изменением, по сравнению с Jinja синтаксисом, будет возможность указывать ключи данных в `` кавычках (обратная одинарная). Такой синтаксис позволит добавить возможность использования ключей с пробелами и странными символами в параметрах шаблона. Если в ключе параметра используется символ `, его нужно escape’ить(слешить) по обычным правилам языка С.

Изменением будет также дополнение тега if. Вместе с конструкцией elif можно будет указывать его синоним elseif, чтобы избежать ненужных ошибок у php программистов.

Все неизвестные системе идентификаторы автоматически считаются ключами передаваемых данных. Таким образом ошибка "неопределенное значение" отсутствует как класс, все неопределенные значения приводятся к пустой строке. Хорошо это или нет, не знаю, но пока думаю, что хорошо. Все неизвестные системе идентификаторы функций (за ними стоит оператор вызова) считаются функциями вызова макрокоманд. Макрокоманда обязана быть определена в шаблоне, наследуема из шаблона export или подключена оператором import. Никакого дополнительного поиска смысла таких операторов не производится. Надобность в операторе self представляется мне надуманной и лишней.

Такой подход позволяет генерировать независимый от окружения код. Любой шаблон преобразуется в один и тот-же код, независимо от того где он транслируется. Таким образом, можно генерировать php-шаблоны на девелоперской машине и распространять уже их без исходных шаблонов.

  • Variables - Так как не предполагается использовать объекты в качестве данных для рендеринга, то способы указания выборки элемента и выборки из массива полностью эквивалентны. Предполагается сделать "типизацию" при определении параметров макрокоманд, чтобы выборка из массива и получение метода объекта были оптимизированы. Также забавно сдалать "ретрансляцию" после нескольких прогонов. При прогоне шаблона собирается информация о типе переменных и при ретрансляции оптимизируется выборка в соответствии с полученными данными.

  • Filters - фильтры реализованы не полностью. Точнее, они дополняются по мере необходимости.

  • Тests - тесты описаны не полностью. С точки зрения реализации фильтры от тестов отличаются только необходимостью приведения результата теста к логическому. внешний вид и правило описания фильтров и тестов одинаковы.

  • Комментарии реализованы как блочные так и строчные.

  • Whitespace control - реализовано.

  • Escaping - тег raw реализован.

  • Line statesment - не реализовано

  • Наследование шаблонов реализовано. Функция super пока не реализована

  • Именованные теги окончания блоков не реализованы.

  • Block nesting and scoop - параметр scooped блока не реализован.

  • List on control structures

  • for - реализован без внутреннего if, но с else и внутренним loop. Атрибут recursive отсутствует (в twig его тоже нет). Рекурсивные конструкции следует делать с помощью макросов.

  • if реализован

  • макросы реализованы без импорта, с позиционными и именованными параметрами. внутренние переменные не реализованы. Рекурсивный вызов макроса из другого макроса не требует дополнительных функций.

  • Тег call не реализован

  • Тег filter не реализован

  • set реализован

  • Extends реализован

  • Include не реализован

  • Import реализован пока в простом виде, без переопределения имен макросов

Список реализованных фильтров

  • default
  • escape , e
  • join
  • length
  • replace

тестов нет

Список функций

  • lipsum

  • min

  • max

  • trim

  • parent - вызов шаблона-предка

Теги, описанные в Extension никак не реализованы.

Некоторые принципы работы

В результате работы транслятора получается некий php-класс. Имя класса зависит от имени шаблона. Из шаблона с именем XXX.jtpl - получается класс tpl_XXX При этом все точки в слове XXX заменяются на подчеркивания. При рендеринге данных достаточно проинициировать этот класс и вызвать либо нужную функцию, либо “корневую” - ‘_’. Никаких дополнительных действий и включения в код файлов компилятора не нужно. Дополнительные фильтры, функции и тесты, использующиеся при run-time рендеринге, описываются в классе tpl_base.

Каждый блок шаблона транслируется в функцию класса. Весь шаблон транслируются в функцию ‘_’. Макрокоманды являются такими же функциями класса, однако в конструкторе класса заводится массив macro со всеми описанными в шаблоне макросами для последующего импорта. Импорт макросов заключается в инициализации импортируемого класса шаблона и слиянии собственного массива macro с импортируемым. "Переопределение" блоков достаточно корректно эмулируется простым наследованием классов. При этом отсутствует возможность эффективно узнать структуру предка класса и наличие импортируемых макросов. Таким образом, вставка макроса {{ MACRONAME(PAR1,PAR2...) }} выглядит в коде так

if(!empty($this->macros[‘MACRONAME’])
    call_user_func($this->macros[‘MACRONAME’],PAR1,PAR2...)

Импорт макросов import ‘macros.tpl’ реализован так

function __construct (){
    // import ‘macros.tpl’
    require_once ‘macros.tpl.php’;
    $import= new macros();
    // конструкция import ‘macros.tpl’
    $this->macros=array_merge($this->macros,$import->macros);
    // конструкция from ‘macros.tpl’ import ‘yyy’ as’ ‘xxx’
    $this->macros[‘xxx’]=$import[‘yyy’]
}

API

унарные операции вводятся оператором newOp1

->newOp1('not','!(%s)','BB')

Первый параметр - имя операции, второй printf-шаблон для генерации кода, третий - указания по преобразованию типов. Первая литера - преобразование к логическому типу, вторая литера - преобразование операнда к логическому типу перед вставкой в шаблон. Бинарные операции вводятся оператором newOp2

->newOp2('== != > >= < <=',2,null,'B**')

Сокращенный способ описания операторов, которые есть в языке генерации, каждый из них будет вставлен в код по стандартному шаблону (%s) ОПЕРАЦИЯ (%s) , где ОПЕРАЦИЯ заменяется на описываемую операцию. изображения операций через пробел позволяет описывать несколько операций с одним приоритетом. Второй параметр - приоритет операции. Существенен только для бинарных операций. Третий параметр - printf-шаблон, если null, то используется стандартный printf-шалон. Четвертый параметр - преобразование типов при генерации шаблона.

->newOp2('and',3,'(%s) && (%s)','BBB')
->newOp2('|',11,array($this,'function_filter'))

Полный способ описания операции. первый параметр - имя операции, второй - приоритет операции , третьим параметром может быть printf-шаблон или callback функции, выдающей представление функции.

->newOpR('loop',array($this,'operand_loop'))
  • особый оператор loop. В зависимости от применяемым к loop атрибутам порождается та или иная конструкция. Вид и смысл конструкции определяется callback'ом.

новые функции, фильтры и тесты добавляются одинаковым образом.

  • В конструктор базового класса компилятора нужно вставить определение функции (для примера - функция escape) в виде:

    ->newFunc('e','htmlspecialchars(%s)','SS') ->newFunc('escape','htmlspecialchars(%s)','SS')

  • Первый параметр - имя функции. второй параметр - шаблон для printf’а с одним параметром. Все переданные функции параметры укладываются в эту строку через запятую. Тут описываются две функции- синонима. Таким образом можно описывать конструкции, непосредственно преобразуемые в php-код. при изготовлении конструкта результат будет иметь строковый вид (первая литера третьего параметра) и будет произведено преобразование типов операндов к строковому виду (второй и следующие литеры)

При трансляции конструкции {{user.username|e}} получится

htmlspecialchars((isset($user['username'])?$user['username']:""))

Функция lipsum ->newFunc('lipsum','$this->func_lipsum(%s)') При генерации функции lipsum предполагается вызов функции func_lipsum из базового шаблона tpl_base.

/**
 * функция lipsum
 */
function func_lipsum($n=5, $html=True, $min=20, $max=100){
  $result='';
...
  return $result;
}

Функция replace
->newFunc('replace',array($this,'function_replace')) Второй параметр - callback, который должен выдать представление функции в получившемся файле. Если нужно определить несколько синонимов функции - достаточно поменять имя функции. Callback может выглядеть так

  /**
   * фильтр - replace
   * @param operand $op1 - TYPE_ID - имя функции
   * @param operand $op2 - TYPE_LIST - параметры функции
   */
  function function_replace($op1,$op2){
    $op1->val= 'str_replace('.$this->to('S',$op2->value['keys'][1])->val
      .','.$this->to('S',$op2->value['keys'][2])->val
      .','.$this->to('S',$op2->value['keys'][0])->val
    .')';
    $op1->type="TYPE_OPERAND";
    return $op1;
  }

Выбор callback’а вместо явного представления в виде printf-шаблона сделан из за другого порядка следования операторов str_replace’а. Для пояснения производимых действий

  • функция получает 2 параметра - первый - оператор, содержащий имя функции, как правило - operand(val:’replace’,type:’TYPE_ID’) - имя функции, второй операнд содержит параметры, переданные при вызове функции.
  • функция to преобразует операнд к нужному виду

Типы данных и их скрытый смысл

При парсинге текста шаблона текст разбивается на лексемы с определенным типом. В процессе трансляции некоторые лексемы меняют свой тип.

  • TYPE_STRING - содержимое строки, изначально заключенное в двойные кавычки

  • TYPE_STRING1 - содержимое строки, изначально заключенное в одинарные кавычки

  • TYPE_STRING2 - содержимое строки, изначально заключенное в обратные кавычки

  • TYPE_ID - изображение идентификатора

  • TYPE_OPERATION - изображение предварительно зарегистрированных функций и операций (регистрируются операторами newOp1, newOp2)

  • TYPE_COMMA - нераспознанные символы

остальные типы появляются при дальнейшем анализе текста.

  • TYPE_OBJECT - внутренний тип операнда, который позволяет выполнять вызов и получение атрибута от одного и того же идентификатора.

  • TYPE_LIST - комплект операндов. Получается при анализе изображения массива, вызовов функций и описаний макросов. Содержит 2 внутренних массива операндов, именованных и позиционных. Их смысл меняется в зависимости от конструкции, которую анализировали.

  • TYPE_OPERAND - конструкция, выдающая строковое значение. Может использоваться в качестве операнда конкатенации, а также в качестве операнда php операторов. пример: ‘2’ (строковое изображение числа 2 с одинарными кавычками).

  • TYPE_SENTENSE - готовые для склеивания через точку с запятой куски текста. Пример $x=2

Шаблонизатор разбит на части

  • компилятор

  • внутренние шаблоны компилятора

  • оттранслированные внутренние шаблоны компилятора

  • система рендеринга

  • 'боевые' шаблоны

  • оттранслированные 'боевые' шаблоны

  • внутренние шаблоны системы рендеринга (наследуются от внутренних шаблонов компилятора)

Для поставки достаточно системы рендеринга и оттранслированных 'боевые' шаблоны

Для возможности перетрансляции шаблонов на месте достаточно добавить компилятор, 'основные' шаблоны и оттранслированные внутренние шаблоны компилятора

Структура файлов дистрибутива

  -|            readme.txt - файл с описанием
   |-lib        каталог с файлами, необходимыми для компиляции шаблонов
   |-render     откомпилированные "внутренние" шаблоны. Их необходимо
   |            поместить в каталог templates проекта.
   |-samples    каталог с примерами
     |- templates    каталог с примерами шаблонов.

Использование

Должна быть определена константа

  • TEMPLATE_PATH - расположение шаблонов и откомпилированных шаблонов.

В каталоге sample имеется пример простой функции рендеринга откомпилированных шаблонов. render.php

API

механика трансляции

Трансляция шаблона происходит с помощью парсера, переводящего текст шаблона в лексемы, и стекового автомата, "вычисляющего" их. Комментарии обрабатываются на этапе парсинга. Любой шаблон "дополняется" для трансляции скобками %} ... {% с начала и с конца файла. Такие скобки, а также }} ... {{, с разными вариантами начальных и конечных скобок, воспринимаются как операция "напечатай содержимое" и вызывают размещение на стеке операндов содержимого с типом СТРОКА, а на стеке операций - операции "печать". Таким образом, появляется возможность считать, что текст шаблона представляет из себя нормальный текст на языке программирования.

Парсер реализован в процедуре makelex.

В результате свертки лексем появляется массив классов operand с типами

  • TYPE_STRING - строка, изначально была взята в " кавычки
  • TYPE_STRING1 - строка, изначально была взята в ' кавычки
  • TYPE_STRING2 - строка, изначально была взята в ` кавычки
  • TYPE_DIGIT - изображение числа
  • TYPE_COMMA - неопознанный символ, смысл символа проясняется при анализе контента
  • TYPE_OPERATION - зарегистрированная операция
  • TYPE_ID - идентификатор, любая последовательность разрешенных символов, начинающаяся с литеры.

в результате свертки лексем добавляются операнды типа

  • TYPE_EOF - символ окончания текста

На этапе свертки лексем выкидываются комментарии и обрабатываются операторы очстки от пробельных символов -.

Теги - это начальные слова основных конструкций языка. К примеру, macro, if, for и т.д. Служебные слова - это слова, служащие для разделения тела тега на управляющие конструкции, else, endmacro и так далее. Все теги должны быть уникальны и переменные, совпадающие со служебными словами и с именами тегов недопустимы.

При трансляции тега мы ищем соответствующую тегу процедуру трансляции и вызываем ее.

Внутренность тега, до нужного нам служебного слова, можно получить функцией block_internal

$parcer->block_internal(array('else', 'endfor'));

типы данных и приведение их друг к другу в процессе трансляции

описание собственных тегов

В качестве примера рассмотрим реализацию тега loop. Каждый цикл for порождает связанный с ним объект loop, который хранит переменные цикла и выполняет сервисные функции.

{% for i in [1,2,3] %}
 <span class="{{loop.cycle('odd','even')}}">
 {{- loop.index -}} </span><br>
{% endfor %}

Оператор loop.cycle выдает последовательно по порядку свои параметры в цикле. loop.index - тукущая переменная цикла, и так далее. При трансляции в PHP подобной конструкции придется сгенерировать достаточно нетривиальный текст, к тому же непосредственного объекта при генерации нет.

Описание тега loop вводится в конструкторе сласса php_compiler

->newOpR('loop',array($this,'operand_loop'))

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

function operand_loop($op1=null,$attr=null,$reson='attr'){  ... }

attr - имя атрибута объекта loop, с которым идет работа. reason - повод, по которому нашу функцию дергает ядро транслятора.

reason==call - происходит вызов

reason==attr - вызывается, когда транслятор встречает в исходном тексте слово loop. Необходимое действие - вернуть объект типа operand (в данном случае - operation). Это значение будет помещено на стеке. Если параметр attr не пустой, происходит получение атрибута объекта loop в результате выполнения операции '.'.