Шаблонизатор VMX::Template

Версия от 03:49, 4 января 2011; VitaliyFilippov (обсуждение | вклад)

Данный модуль представляет собой новую версию VMX::Template, построенную на некоторых новых идеях, ликвидировавшую безобразие и legacy-код, накопленный в старой версии, однако сохранившую высокую производительность и простоту.

Есть PHP-версия и Perl-версия шаблонизатора. Реализация, естественно, несколько отличается по причине различий языков — например, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь XCache или eAccelerator, ибо там сохранить coderef между запросами, по-видимому, невозможно.

Развивается то одна, то другая, в зависимости от проекта, над которым я работаю в моменте.

Также есть простенький (и кривоватенький) файл настроек синтаксиса шаблонов для Midnight Commander'а: tpl.syntax.

Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается». Это к тому, что идея вообще-то схожая, но реализация гораздо проще и быстрее.

Do you want to try some new features? By joining the beta, you will get access to experimental features, at the risk of encountering bugs and issues.

Ок Нет, спасибо

Идеи

Уйти от assign_vars(), assign_block_vars(). Передавать, как и в обычных движках, просто хеш с данными $vars. Как, например, в Template::Toolkit. При этом сохранить данные методы для совместимости.

Почистить синтаксис: ликвидировать «преобразования», «вложенный путь по переменной» (->key->index->key->и т. п.), специальный синтаксис для окончания SET, неочевидное обращение к счётчику block.#, tr_assign_* и т. п.

Переписать с нуля компилятор.

Добавить в употребление функции, но только самые необходимые.

Добавить обработку ошибок и диагностические сообщения.

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

require_once 'template.php';
 
# Конструктор
$template = new Template(array(
    'root'          => '.',        # директория с шаблонами
    'cache_dir'     => './cache',  # директория для кэширования компилированного кода шаблонов
    'print_error'   => true,       # если true, ошибки компиляции выводятся на STDOUT
    'raise_error'   => false,      # если true, при ошибке компиляции вызывается die()
    'reload'        => 1,          # если 0, шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет
    # 'use_utf8'      => undef,    # (нужно только в Perl) шаблоны в UTF-8 и с флагом UTF-8 = On
    'begin_code'    => '<!--',     # маркер начала директивы кода
    'end_code'      => '-->',      # маркер конца директивы кода
    'eat_code_line' => true,       # (похоже на TT CHOMP) съедать "лишний" перевод строки, если в строке только директива?
    'begin_subst'   => '{',        # маркер начала подстановки выражения
    'end_subst'     => '}',        # маркер конца подстановки выражения
    'compiletime_functions' =>     # дополнительные компилируемые функции
        array('func' => callback), # массив вида имя функции (в шаблонах) => callback,
                                   # которому передаются скомпилированные выражения всех аргументов
    # немного legacy, устаревшее:
    'wrapper'       => NULL,       # если равно чему-то, что можно вызвать, через это будет
                                   # пропущен вывод всех шаблонов ("глобальный фильтр")
    'strict_end'    => false,      # требовать <!-- END имя_блока --> после <!-- BEGIN имя_блока -->
));
 
# Присвоение одной переменной:
$template->vars("ключ", "значение");
 
# Присвоение кучи переменных:
$template->vars(array("ключ" => "значение", ...));
 
# Выполнение шаблона и получение результата:
# (возможно с передачей целого хеша данных)
$page = $template->parse('имя_файла' [, array("ключ" => "значение", ...)]);
 
# Аналогично выполнение именованного блока из файла:
$page = $template->parse('имя_файла', 'имя_блока' [, array("ключ" => "значение", ...)]);
 
# Аналогично выполнение кода из строки:
$page = $template->parse(NULL, 'код' [, 'имя_блока'] [, array("ключ" => "значение", ...)]);
 
# Очистка сохранённых данных для генерации ещё одной страницы:
$template->clear;

Реализация

Маркеры начала и конца кода <!-- --> и подстановки { } могут быть заменены любыми другими. Если, например, вы привыкли к TT, можно установить [% %]. Маркеры подстановки можно вообще убрать, ибо подстановка тоже является кодом.

Путь к переменной теперь может включать в себя числа. Это будут обращения к элементам массивов, в то время как всё остальное — обращения к элементам хешей.

Циклы

Вне блока {block} будет иметь значение ARRAY(0x…), то есть массив всех итераций блока block, а {block.0} будет иметь значение HASH(0x…), то есть первую итерацию блока block.

<!-- BEGIN block -->

Теперь, внутри блока {block} теперь будет иметь значение HASH(0x…), то есть уже значение текущей итерации блока block, а {block.#} будет иметь значением номер текущей итерации блока, отсчитываемый с 0, а не с 1, как в старой версии.

<!-- END block -->

На <!-- END другоеимя --> после <!-- BEGIN block --> шаблонизатор выдаст ошибку, «ибо нефиг» (c). Если block в хеше данных — не массив, а хеш — это значит, что итерация у блока только одна, и тогда <!-- BEGIN block --> работает как for($expression) {} в Perl.

BEGIN ... END — это циклы в «старом стиле». А можно использовать и TT-подобный:

<!-- FOR var = expression -->
...
<!-- END -->

Причём, если expression ::= block, то var может быть само block'ом. Это, по сути, и есть то, что делает BEGIN: эквивалентно . Предыдущее значение переменной цикла после выхода из цикла всегда восстанавливается.

К номеру итерации можно обратиться через {var#}.

Функции

Операторов нет, фильтров нет, есть функции. Пример:

<!-- IF OR(function(block.key1),AND(block.key2,block.key3)) -->

Почему? Тут всё просто — основываясь на предположении, что длинные выражения в шаблонах нужны очень редко, было лениво писать нормальную грамматику для разбора обычных выражений. Почему они нужны редко? Да просто минимум логики в шаблонах — признак хороших шаблонов. А функции покрывают сразу и выражения, и «фильтры», и методы объектов.

Синтаксис вызова функции нескольких аргументов:

<!-- function(block.key, 0, "abc") -->

Подстановка:

{function(block.key, 0, "abc")}

Синтаксис вызова функции одного аргумента:

<!-- function(block.key) -->
<!-- function block.key -->
{block.key/s}
{s block.key}

Синтаксис вызова метода объекта:

{object.method()}
{object.method(arg1, arg2)}
{call(object, "method")}
{call(object, "method", array(arg1, arg2))}

Последние два применения — как нетрудно заметить, обращение к функции call() и служат для вызова метода по вычисляемому имени.

Цепочки вызовов методов типа object.method().another_method() не поддерживаются, ибо к ним без сохранения звеньев нервно относится даже сам PHP.

IF

Условный вывод:

<!-- IF function(block.key) --><!-- ELSEIF ... --><!-- END -->
<!-- IF NOT block.key -->...<!-- END -->                      

ELSIF эквивалентно ELSE IF и ELSEIF.

SET

Запись значения переменной:

<!-- SET block.key -->...<!-- END --></nowiki>
<!-- SET block.key = выражение -->

Включения

Включение другого шаблона также осталось:

<!-- INCLUDE another-file.tpl -->
<!-- INCLUDE "another-file.tpl" -->

По «динамическому» имени шаблона включение производится функцией INCLUDE (она же PARSE). Как несложно заметить, вторая строка — как раз вызов функции.

Блоки

Блок — это часть шаблона, выделенная в отдельную «функцию», хорошо кэшируемая и предназначенная для повторного вызова из других мест. Покрывает сразу несколько вещей — «блоки», «макросы» и «обёртки» из TT. Да-да, TT славится бессмысленным дублированием функционала.

Но имеет несколько преимуществ:

  • блоки, определённые в одном шаблоне, можно смело вызывать из других по имени файла + имени блока!
  • блок можно определить просто как некоторое выражение.
  • блоки хорошо кэшируются — с VMX::Template вы не испытаете разочарования, если вызовете какой-нибудь блок 1000 раз. В отличие от TT.

Блоки в шаблоне не могут быть вложенными, а циклы, SET и прочие вещи, их оборачивающие, не имеют на них никакого влияния. После компиляции блоки просто вырезаются и преобразуются в отдельные функции PHP/Perl’а.

<!-- BLOCK имя_блока -->
...код...
<!-- END -->

или

<!-- BLOCK имя_блока = выражение -->

Вместо слова BLOCK можно также использовать слово FUNCTION или MACRO.

Вызывать блок из шаблона следует с помощью функции PROCESS. Вызывать блок из кода следует с помощью

Функции

Note.svg Первое, что обычно нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки:

  • S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.
  • H() — удаляет все HTML-теги, кроме «безопасных».
  • T() — удаляет все HTML-теги.
  • Q() — это addslashes(), экранирует символы для использования, например, в JS.
  • I() — преобразует значение к целому числу.

Расширяемость в области функций:

Run-time функции
В качестве функции можно использовать переданный в хеше данных coderef (замыкание, ссылку на функцию, или любое «is_callable» в случае PHP). Если же хочется вынести в «функцию» блок кода из шаблона — проще создать отдельный шаблон и вызывать его, предварительно делая <!-- SET --> именованных аргументов. Это действительно лучше, так как позволяет оптимально работать кэшированию.
Compile-time функции
При создании объекта шаблона можно передать параметр compiletime_functions, равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными коду для вычисления соответствующего аргумента, и должны возвращать код для вычисления результата. То есть, они выполняются на этапе компиляции.

OR, AND, NOT

Логические ИЛИ, И, НЕ, действующие аналогично Perl операторам ||, &&, !.

EVEN, ODD

Истина в случае, если аргумент чётный или нечётный соответственно.

INT=I, ADD, MUL, DIV, MOD, LOG

Преобразование к целому числу, арифметические операции и логарифм.

EQ, NE, SEQ, SNE, GT, LT, GE, LE, SGT, SLT, SGE, SLE

Действуют аналогично Perl операторам == eq > < >= <= gt lt ge le.

В PHP-версии на данный момент такого много-безобразия нет, есть просто EQ NE GT LT GE LE. Хотя это и имеет свои минусы - если хотя бы один аргумент принимается PHP как численный, сравнение становится численным.

COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD

Количество элементов массива, или 0, если аргумент — не массив — count(аргумент).

Аналог функции array_slice из PHP.

Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).

ARRAY, HASH

Создание массива или хэша из всех атрибутов.

Соответственно в хеше атрибуты идут парами КЛЮЧ, ЗНАЧЕНИЕ, КЛЮЧ, ЗНАЧЕНИЕ и т.п. (специального синтаксиса "=>" нет).

SORT

Сортировка массива по значениям.

ARRAY_MERGE

Слить массивы в один. Под Perl — только массивы (не хеши), под PHP — любые массивы.

GET, AGET, HGET

Получение элемента массива/хэша по «динамическому» ключу. По-моему, это лучше, чем зюки-хрюки Template Toolkit’а: hash.${hash2.$key} и т. п.

GET(откуда, что) автоматически решает, «откуда» — это массив или хеш, AGET служит только для массивов, а HGET только для хешей. В PHP-версии все три идентичны.

GET(что) — получение значения переменной верхнего уровня.

MAP

Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы).

LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE

Нижний и верхний регистр.

STRLIMIT

Ограничение длины строки s максимальной длиной l — strlimit(s, l). Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется «…» (троеточие).

S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR

Преобразование символов < > & " ' в HTML-сущности.

Удаление всех HTML/XML тегов.

Удаление только запрещённых тегов.

Преобразование переводов строк (\n) в HTML-тег <br />.

URI_QUOTE=URIQUOTE=URLENCODE

URL-кодирование строки (URI::Escape в Perl и urlencode() в PHP).

CONCAT, JOIN=IMPLODE

Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов.

Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.

SUBSTR=SUBSTRING, STRLEN

Стандартная (для всех кроме жавистов стандартная) функция подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки.

Ну и функция «длина строки».

Q=QUOTE=ADDSLASHES, REQUOTE=RE_QUOTE=PREG_QUOTE

Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).

Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. perldoc perlre).

REPLACE, SPLIT

Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).

Разделение строки по регулярному выражению и лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. perldoc -f split)

DUMP, JSON

Вывод всех данных из структуры — Dumper в Perl’е и var_dump в PHP.

Форматирование структуры данных в формат JSON.

INCLUDE=PROCESS=PARSE

Включение другого шаблона или выполнение блока.

process('имя файла')
process('имя файла', 'имя блока')
process('имя файла', 'имя блока', hash( аргументы ))
process('::имя блока в текущем шаблоне' [, hash(аргументы)])

Не рекомендуется, но возможно также и передавать код вместо имени файла:

process('', 'код шаблона' [, 'функция'] [, hash( аргументы )])

SUBST, SPRINTF, STRFTIME

Подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, …).

Sprintf — он и в Африке sprintf.

Форматирование даты и/или времени с помощью функции strftime — strftime(формат, дата [, часть_даты]). Формат strftime’овский (например, «%d %b %Y»). Дата может передаваться как один или два аргумента, если два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:

  • UNIX время.
  • Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из 4 или более цифр (год) и 2 (месяц, день) или 5 (месяц, день, часы, минуты, секунды) групп по 2 цифры, разделённые любыми нецифровыми символами и в конце — опционально временная зона — 2 цифры, предварённые пробелом, плюсом или минусом. Короче говоря,
    ^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$
  • Оракловский формат даты-времени: ДД-Мес-ГГ[ГГ] ЧЧ.ММ.СС.
  • RFC 822.