Шаблонизатор VMX::Template
Данный модуль представляет собой новую версию VMX::Template, построенную на некоторых новых идеях, ликвидировавшую безобразие и legacy-код, накопленный в старой версии, однако сохранившую высокую производительность и простоту.
Есть PHP-версия и Perl-версия шаблонизатора. Реализация, естественно, несколько отличается по причине различий языков — например, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь XCache или eAccelerator, ибо там сохранить coderef между запросами, по-видимому, невозможно.
Развивается то одна, то другая, в зависимости от проекта, над которым я работаю в моменте.
Также есть простенький (и кривоватенький) файл настроек синтаксиса шаблонов для Midnight Commander'а: tpl.syntax.
Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается». Это к тому, что идея вообще-то схожая, но реализация гораздо проще и быстрее.
Содержание
- 1 Идеи
- 2 Использование (PHP)
- 3 Реализация
- 4 Функции
- 4.1 OR, AND, NOT
- 4.2 EVEN, ODD
- 4.3 INT=I, ADD, MUL, DIV, MOD, LOG
- 4.4 EQ, NE, SEQ, SNE, GT, LT, GE, LE, SGT, SLT, SGE, SLE
- 4.5 COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD
- 4.6 ARRAY, HASH
- 4.7 SORT
- 4.8 ARRAY_MERGE
- 4.9 GET, AGET, HGET
- 4.10 MAP
- 4.11 LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE
- 4.12 STRLIMIT
- 4.13 S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR
- 4.14 URI_QUOTE=URIQUOTE=URLENCODE
- 4.15 CONCAT, JOIN=IMPLODE
- 4.16 SUBSTR=SUBSTRING, STRLEN
- 4.17 Q=QUOTE=ADDSLASHES, REQUOTE=RE_QUOTE=PREG_QUOTE
- 4.18 REPLACE, SPLIT
- 4.19 DUMP, JSON
- 4.20 INCLUDE=PROCESS=PARSE
- 4.21 SUBST, SPRINTF, STRFTIME
Идеи
Уйти от 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. Вызывать блок из кода следует с помощью
Функции
Первое, что обычно нужно — это 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-сущности.
Удаление только запрещённых тегов.
Преобразование переводов строк (\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.