Шаблонизатор VMX::Template — различия между версиями

Материал из YourcmcWiki
Перейти к: навигация, поиск
м
м
Строка 1: Строка 1:
'''VMX::Template''' - высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии (основная - в данный момент PHP).
+
'''VMX::Template''' — высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии (основная — в данный момент PHP).
  
 
* Лицензия: GNU GPL версии 3 или новее
 
* Лицензия: GNU GPL версии 3 или новее
* Исходники (PHP): {{SVN|vitalif/trunk/Template/}} - для работы нужны файлы template.php и template.parser.php. Исходник грамматики в [{{SVN|vitalif/trunk/Template/template.lime}} template.lime]
+
* Исходники (PHP): {{SVN|vitalif/trunk/Template/}} — для работы нужны файлы template.php и template.parser.php. Исходник грамматики в [{{SVN|vitalif/trunk/Template/template.lime}} template.lime]
 
* Исходники (Perl), несколько устаревшие: [{{SVN|vitaphoto/solstice/lib-sway/VMX/Template.pm}} Template.pm], [{{SVN|vitaphoto/solstice/lib-sway/VMX/Common.pm}} Common.pm]
 
* Исходники (Perl), несколько устаревшие: [{{SVN|vitaphoto/solstice/lib-sway/VMX/Template.pm}} Template.pm], [{{SVN|vitaphoto/solstice/lib-sway/VMX/Common.pm}} Common.pm]
 
* Простой файл настроек для подсветки синтаксиса шаблонов в [http://www.midnight-commander.org/ Midnight Commander]'а: [{{SVN|vitalif/trunk/scripts/tpl.syntax|markup}} tpl.syntax]
 
* Простой файл настроек для подсветки синтаксиса шаблонов в [http://www.midnight-commander.org/ Midnight Commander]'а: [{{SVN|vitalif/trunk/scripts/tpl.syntax|markup}} tpl.syntax]
Строка 14: Строка 14:
 
# И, наконец, на LALR(1) грамматику на основе генератора парсеров LIME
 
# И, наконец, на LALR(1) грамматику на основе генератора парсеров LIME
  
Есть PHP и Perl версии шаблонизатора, основная версия шаблонизатора - в данный момент PHP. Есть некоторые различия реализации - например, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь [http://xcache.lighttpd.net/ XCache] или [http://eaccelerator.net/ eAccelerator], ибо там сохранить coderef между запросами невозможно.
+
Есть PHP и Perl версии шаблонизатора, основная версия шаблонизатора — в данный момент PHP. Есть некоторые различия реализации — например, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь [http://xcache.lighttpd.net/ XCache] или [http://eaccelerator.net/ eAccelerator], ибо там сохранить coderef между запросами невозможно.
  
<span style="border: 2px #FF8000 dashed; padding: 4px">Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается».</span> Это к тому, что идея вообще-то схожая, но реализация гораздо проще и быстрее.
+
<span style="border: 2px #FF8000 dashed; padding: 4px">Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается».</span> Это к тому, что идея вообще-то схожая, но реализация гораздо проще и быстрее.
  
 
== Использование ==
 
== Использование ==
Строка 114: Строка 114:
 
== Синтаксис шаблонов ==
 
== Синтаксис шаблонов ==
  
Шаблон — любой текст (типично - HTML), в который ''местами'' включены директивы и/или подстановки. Пример:
+
Шаблон — любой текст (типично — HTML), в который ''местами'' включены директивы и/или подстановки.
 +
 
 +
=== Пример ===
  
 
<pre>
 
<pre>
Строка 140: Строка 142:
 
</pre>
 
</pre>
  
Маркеры:
+
=== Маркеры ===
* <tt><nowiki>'<!--', '-->'</nowiki></tt> - маркеры начала и конца директивы
+
 
* <tt><nowiki>'{', '}'</nowiki></tt> - маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т.п.)
+
* <tt><nowiki>'<!--', '-->'</nowiki></tt> — маркеры начала и конца директивы
 +
* <tt><nowiki>'{', '}'</nowiki></tt> — маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т. п.)
 
* Подстановки можно использовать и в директивах
 
* Подстановки можно использовать и в директивах
* Маркеры начала и конца директивы можно заменить другими - tсли, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>.
+
* Маркеры начала и конца директивы можно заменить другими — tсли, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>.
 
* Маркеры подстановок можно отключить, но заменить другими без правки грамматики парсера нельзя.
 
* Маркеры подстановок можно отключить, но заменить другими без правки грамматики парсера нельзя.
  
Выражения:
+
=== Выражения ===
 +
 
 
* Выражения состоят из переменных, операторов и вызовов функций и методов объектов
 
* Выражения состоят из переменных, операторов и вызовов функций и методов объектов
 
* Синтаксис обращений к переменным JS-подобный: <tt>hash.key</tt>, <tt>array[0]</tt>, <tt>hash['key']</tt>
 
* Синтаксис обращений к переменным JS-подобный: <tt>hash.key</tt>, <tt>array[0]</tt>, <tt>hash['key']</tt>
* Имена переменных '''регистрозависимы''', имена встроенных функций и названия директив (BEGIN, END и т. п.) '''регистронезависимы'''
+
* Имена переменных '''регистрозависимы''', имена встроенных функций и названия директив (BEGIN, END и т. п.) — '''регистронезависимы'''
 
* Встроенные функции: <tt>function(arg1, arg2, ...)</tt> или <tt>function single_arg</tt> (через пробел с одним аргументом)
 
* Встроенные функции: <tt>function(arg1, arg2, ...)</tt> или <tt>function single_arg</tt> (через пробел с одним аргументом)
 
* Функции, определённые через FUNCTION: <tt>fn_name('arg' => 'value', 'arg2' => 'value2', ...)</tt> или <tt>exec('fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })</tt>
 
* Функции, определённые через FUNCTION: <tt>fn_name('arg' => 'value', 'arg2' => 'value2', ...)</tt> или <tt>exec('fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })</tt>
Строка 157: Строка 161:
 
* Методы объектов: <tt>var.method(arg1, arg2, ...)</tt>
 
* Методы объектов: <tt>var.method(arg1, arg2, ...)</tt>
  
Операторы:
+
=== Операторы ===
  
 
<tab sep="bar" class="wikitable">
 
<tab sep="bar" class="wikitable">
 
a .. b | Конкатенация
 
a .. b | Конкатенация
a &#x7C;&#x7C; b, a OR b | Логическое ИЛИ
+
a || b, a OR b | Логическое ИЛИ
a XOR b | XOR - логическое исключающее ИЛИ
+
a XOR b | XOR — логическое исключающее ИЛИ
 
a && b, a AND b | Логическое И
 
a && b, a AND b | Логическое И
 
a & b | Побитовое И
 
a & b | Побитовое И
Строка 169: Строка 173:
 
!a, NOT a | Логическое НЕ
 
!a, NOT a | Логическое НЕ
 
(exp) | Выражение в скобках
 
(exp) | Выражение в скобках
{ 'a' => 'b', ... } | Создание хешрефа (Perl), ассоциативного массива (PHP)
+
{ 'a' => 'b', } | Создание хешрефа (Perl), ассоциативного массива (PHP)
 
</tab>
 
</tab>
  
Директивы:
+
=== Директивы ===
  
 
<tab sep="bar" class="wikitable">
 
<tab sep="bar" class="wikitable">
 
<tt>&lt;!--# Комментарий --&gt;</tt> | Комментарий
 
<tt>&lt;!--# Комментарий --&gt;</tt> | Комментарий
<tt>&lt;!-- FOR item = array --&gt;</tt><br />...код...<br /><tt>&lt;!-- END --&gt;</tt> | Цикл. Вместо FOR можно использовать слово FOREACH. Внутри цикла можно обратиться к счётчику через <tt>{item_index}</tt>
+
<tt>&lt;!-- FOR item = array --&gt;</tt><br />…код…<br /><tt>&lt;!-- END --&gt;</tt> | Цикл. Вместо FOR можно использовать слово FOREACH. Внутри цикла можно обратиться к счётчику через <tt>{item_index}</tt>
<tt>&lt;!-- IF выражение --&gt;</tt><br />...код... | Если (выражение)
+
<tt>&lt;!-- IF выражение --&gt;</tt><br />…код… | Если (выражение)
<tt>&lt;!-- ELSEIF выражение --&gt;</tt><br />...код... | Иначе если (выражение)
+
<tt>&lt;!-- ELSEIF выражение --&gt;</tt><br />…код… | Иначе если (выражение)
<tt>&lt;!-- ELSE --&gt;</tt><br />...код... | Иначе
+
<tt>&lt;!-- ELSE --&gt;</tt><br />…код… | Иначе
 
<tt>&lt;!-- END --&gt;</tt> | Конец если / цикла / присваивания
 
<tt>&lt;!-- END --&gt;</tt> | Конец если / цикла / присваивания
 
<tt>&lt;!-- SET var = выражение --&gt;</tt> | Присваивание переменной var результата выполнения выражения
 
<tt>&lt;!-- SET var = выражение --&gt;</tt> | Присваивание переменной var результата выполнения выражения
<tt>&lt;!-- SET var --&gt;</tt><br />...код...<br /><tt>&lt;!-- END --&gt;</tt> | Присваивание переменной var результата выполнения кода
+
<tt>&lt;!-- SET var --&gt;</tt><br />…код…<br /><tt>&lt;!-- END --&gt;</tt> | Присваивание переменной var результата выполнения кода
 
<tt>&lt;!-- FUNCTION name (arg1, arg2) = выражение --&gt;</tt> | Определение функции шаблона как результата выполнения выражения
 
<tt>&lt;!-- FUNCTION name (arg1, arg2) = выражение --&gt;</tt> | Определение функции шаблона как результата выполнения выражения
<tt>&lt;!-- FUNCTION name (arg1, arg2) --&gt;</tt><br />...код...<br /><tt>&lt;!-- END --&gt;</tt> | Определение функции / "блока" шаблона. Вместо FUNCTION можно использовать также слова BLOCK или MACRO
+
<tt>&lt;!-- FUNCTION name (arg1, arg2) --&gt;</tt><br />…код…<br /><tt>&lt;!-- END --&gt;</tt> | Определение функции / «блока» шаблона. Вместо FUNCTION можно использовать также слова BLOCK или MACRO
 
</tab>
 
</tab>
  
Строка 195: Строка 199:
 
Расширяемость в области функций:
 
Расширяемость в области функций:
  
;Run-time функции: В качестве функции можно использовать метод переданного в хеше данных объекта. В «функцию» можно вынести и блок кода из шаблона — см. [[#Блоки]]. Оно хорошо кэшируется.
+
;Run-time функции: В качестве функции можно использовать метод переданного в хеше данных объекта. В «функцию» можно вынести и блок кода из шаблона — см. [[#Блоки]]. Оно хорошо кэшируется.
;Compile-time функции: При создании объекта шаблона можно передать параметр <tt>compiletime_functions</tt>, равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными '''коду для вычисления соответствующего аргумента''', и должны возвращать '''код для вычисления результата'''. То есть, они выполняются на этапе компиляции.
+
;Compile-time функции: При создании объекта шаблона можно передать параметр <tt>compiletime_functions</tt>, равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными '''коду для вычисления соответствующего аргумента''', и должны возвращать '''код для вычисления результата'''. То есть, они выполняются на этапе компиляции.
  
{{note}} Первое, что обычно нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки:
+
{{note}} Первое, что обычно нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки:
  
* S() это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.
+
* S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.
* H() удаляет все HTML-теги, кроме «безопасных».
+
* H() — удаляет все HTML-теги, кроме «безопасных».
* T() удаляет все HTML-теги.
+
* T() — удаляет все HTML-теги.
* Q() это addslashes(), экранирует строки для использования, например, в JS.
+
* Q() — это addslashes(), экранирует строки для использования, например, в JS.
* I() преобразует значение к целому числу.
+
* I() — преобразует значение к целому числу.
  
 
=== Числа, логические операции ===
 
=== Числа, логические операции ===
Строка 230: Строка 234:
 
==== YESNO ====
 
==== YESNO ====
  
YESNO($1, $2, $3) - тернарный оператор ($1 ? $2 : $3).
+
YESNO($1, $2, $3) — тернарный оператор ($1 ? $2 : $3).
  
 
=== Строки ===
 
=== Строки ===
Строка 244: Строка 248:
 
==== Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE ====
 
==== Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE ====
  
* Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).
+
* Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).
* Экранирование символа " удвоением — sql_quote(строка). (актуально также для [[rupedia:CSV|CSV]])
+
* Экранирование символа " удвоением — sql_quote(строка). (актуально также для [[rupedia:CSV|CSV]])
* Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]).
+
* Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]).
  
 
==== URI_QUOTE=URIQUOTE=URLENCODE ====
 
==== URI_QUOTE=URIQUOTE=URLENCODE ====
Строка 254: Строка 258:
 
==== REPLACE, STR_REPLACE ====
 
==== REPLACE, STR_REPLACE ====
  
* Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).
+
* Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).
* Замена подстроки в строке — str_replace(искомое, замена, строка).
+
* Замена подстроки в строке — str_replace(искомое, замена, строка).
  
 
==== STRLEN ====
 
==== STRLEN ====
Строка 263: Строка 267:
 
==== SUBSTR=SUBSTRING ====
 
==== SUBSTR=SUBSTRING ====
  
Стандартная (для всех, кроме жавистов) функция подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки.
+
Стандартная (для всех, кроме жавистов) функция подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки.
  
 
==== TRIM ====
 
==== TRIM ====
Строка 271: Строка 275:
 
==== SPLIT ====
 
==== SPLIT ====
  
Разделение строки по регулярному выражению и лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. [http://perldoc.perl.org/functions/split.html perldoc -f split])
+
Разделение строки по регулярному выражению и лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. [http://perldoc.perl.org/functions/split.html perldoc -f split])
  
 
==== S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ====
 
==== S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ====
Строка 282: Строка 286:
 
==== CONCAT, JOIN=IMPLODE ====
 
==== CONCAT, JOIN=IMPLODE ====
  
* Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов.
+
* Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов.
* Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.
+
* Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.
  
 
==== SUBST, SPRINTF, STRFTIME ====
 
==== SUBST, SPRINTF, STRFTIME ====
  
Subst - подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, …).
+
Subst — подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, …).
  
Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf].
+
Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf].
  
Форматирование даты и/или времени с помощью функции [http://www.manpagez.com/man/3/strftime/ strftime] strftime(формат, дата [, часть_даты]). Формат strftime’овский (например, «%d %b %Y»). Дата может передаваться как один или два аргумента, если два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:
+
Форматирование даты и/или времени с помощью функции [http://www.manpagez.com/man/3/strftime/ strftime] — strftime(формат, дата [, часть_даты]). Формат strftime’овский (например, «%d %b %Y»). Дата может передаваться как один или два аргумента, если два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:
 
* UNIX время.
 
* UNIX время.
* Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из 4 или более цифр (год) и 2 (месяц, день) или 5 (месяц, день, часы, минуты, секунды) групп по 2 цифры, разделённые любыми нецифровыми символами и в конце — опционально временная зона — 2 цифры, предварённые пробелом, плюсом или минусом. Короче говоря, <pre>^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$</pre>
+
* Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из 4 или более цифр (год) и 2 (месяц, день) или 5 (месяц, день, часы, минуты, секунды) групп по 2 цифры, разделённые любыми нецифровыми символами и в конце — опционально временная зона — 2 цифры, предварённые пробелом, плюсом или минусом. Короче говоря, <pre>^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$</pre>
 
* Оракловский формат даты-времени: <nowiki>ДД-Мес-ГГ[ГГ] ЧЧ.ММ.СС</nowiki>.
 
* Оракловский формат даты-времени: <nowiki>ДД-Мес-ГГ[ГГ] ЧЧ.ММ.СС</nowiki>.
 
* RFC 822.
 
* RFC 822.
Строка 299: Строка 303:
 
==== STRLIMIT=TRUNCATE ====
 
==== STRLIMIT=TRUNCATE ====
  
Ограничение длины строки <tt>str</tt> максимальной длиной <tt>len</tt> <tt>strlimit(str, len, dots = "...")</tt>. Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется <tt>dots</tt> или по умолчанию <tt>"..."</tt>, если аргумент <tt>dots</tt> не передаётся.
+
Ограничение длины строки <tt>str</tt> максимальной длиной <tt>len</tt> — <tt>strlimit(str, len, dots = "...")</tt>. Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется <tt>dots</tt> или по умолчанию <tt>"..."</tt>, если аргумент <tt>dots</tt> не передаётся.
  
 
=== Массивы и хеши ===
 
=== Массивы и хеши ===
Строка 307: Строка 311:
 
Создание хэша из всех аргументов.
 
Создание хэша из всех аргументов.
  
Соответственно в хеше аргументы идут парами КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ и т. п.
+
Соответственно в хеше аргументы идут парами КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ и т. п.
  
 
==== KEYS, HASH_KEYS, ARRAY_KEYS ====
 
==== KEYS, HASH_KEYS, ARRAY_KEYS ====
  
Массив ключей хэша. Понятное дело, в PHP их порядок сохраняется, а в Perl — нет.
+
Массив ключей хэша. Понятное дело, в PHP их порядок сохраняется, а в Perl — нет.
  
 
==== SORT ====
 
==== SORT ====
Строка 324: Строка 328:
  
 
* Создание массива.
 
* Создание массива.
* Диапазон от A до B — range(A, B).
+
* Диапазон от A до B — range(A, B).
  
 
==== IS_ARRAY ====
 
==== IS_ARRAY ====
Строка 332: Строка 336:
 
==== COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ====
 
==== COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ====
  
* Количество элементов массива, или 0, если аргумент — не массив — count(аргумент).
+
* Количество элементов массива, или 0, если аргумент — не массив — count(аргумент).
 
* Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP.
 
* Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP.
* Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).
+
* Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).
  
 
==== GET ====
 
==== GET ====
  
<tt>GET(что)</tt> получение значения переменной верхнего уровня.
+
<tt>GET(что)</tt> — получение значения переменной верхнего уровня.
  
 
==== SET ====
 
==== SET ====
  
<tt>SET(куда, что)</tt> присваивание «куда» значения «что». Уравнения, понятное дело, не решает, то есть, как и обычно, присваивать можно только lvalue :)
+
<tt>SET(куда, что)</tt> — присваивание «куда» значения «что». Уравнения, понятное дело, не решает, то есть, как и обычно, присваивать можно только lvalue :)
  
 
==== ARRAY_MERGE ====
 
==== ARRAY_MERGE ====
  
Слить массивы в один. Под Perl — только массивы (не хеши), под PHP — любые массивы.
+
Слить массивы в один. Под Perl — только массивы (не хеши), под PHP — любые массивы.
  
 
==== SHIFT, POP, UNSHIFT, PUSH ====
 
==== SHIFT, POP, UNSHIFT, PUSH ====
  
* Вынуть элемент из начала массива — <tt>shift(array)</tt>
+
* Вынуть элемент из начала массива — <tt>shift(array)</tt>
* Вынуть из конца — <tt>pop(array)</tt>
+
* Вынуть из конца — <tt>pop(array)</tt>
* Добавить в начало — <tt>unshift(array, value)</tt>
+
* Добавить в начало — <tt>unshift(array, value)</tt>
* Добавить в конец — <tt>push(array, value)</tt>.
+
* Добавить в конец — <tt>push(array, value)</tt>.
  
 
=== Включения ===
 
=== Включения ===
Строка 371: Строка 375:
 
==== PARSE_INLINE=INCLUDE_INLINE=PROCESS_INLINE ====
 
==== PARSE_INLINE=INCLUDE_INLINE=PROCESS_INLINE ====
  
Включение кода не из файла, а просто из строки — <tt>parse_inline('код шаблона'[, аргументы])</tt>.
+
Включение кода не из файла, а просто из строки — <tt>parse_inline('код шаблона'[, аргументы])</tt>.
  
 
==== EXEC ====
 
==== EXEC ====
  
Включение блока из текущего шаблона — <tt>exec('имя блока'[, аргументы])</tt>.
+
Включение блока из текущего шаблона — <tt>exec('имя блока'[, аргументы])</tt>.
  
 
==== EXEC_FROM ====
 
==== EXEC_FROM ====
  
Включение блока из другого шаблона — <tt>exec_from('имя файла', 'имя блока'[, аргументы])</tt>.
+
Включение блока из другого шаблона — <tt>exec_from('имя файла', 'имя блока'[, аргументы])</tt>.
  
 
==== EXEC_FROM_INLINE ====
 
==== EXEC_FROM_INLINE ====
  
Ещё больше не рекомендуется, но можно вызывать и функции из кода из строки — <tt>exec_from_inline('код шаблона', 'имя блока'[, аргументы])</tt>.
+
Ещё больше не рекомендуется, но можно вызывать и функции из кода из строки — <tt>exec_from_inline('код шаблона', 'имя блока'[, аргументы])</tt>.
  
 
=== Прочее ===
 
=== Прочее ===
Строка 393: Строка 397:
 
==== DUMP=VAR_DUMP ====
 
==== DUMP=VAR_DUMP ====
  
Вывод всех данных из структуры — Dumper в Perl’е и var_dump в PHP.
+
Вывод всех данных из структуры — Dumper в Perl’е и var_dump в PHP.
  
 
==== JSON ====
 
==== JSON ====
Строка 401: Строка 405:
 
==== CALL ====
 
==== CALL ====
  
Вызов метода объекта по «динамическому» имени — <tt>call(varref, method_name, arg1, arg2, arg3, ...)</tt>.
+
Вызов метода объекта по «динамическому» имени — <tt>call(varref, method_name, arg1, arg2, arg3, ...)</tt>.
  
 
==== MAP ====
 
==== MAP ====
  
Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы).
+
Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы).
  
 
== Изменения относительно старых версий ==
 
== Изменения относительно старых версий ==
  
* Ликвидированы assign_vars(), assign_block_vars(), tr_assign_vars() - теперь, как и обычно, передаётся просто хеш с данными $vars
+
* Ликвидированы assign_vars(), assign_block_vars(), tr_assign_vars() — теперь, как и обычно, передаётся просто хеш с данными $vars
 
* Синтаксис типа {a->key} ликвидирован
 
* Синтаксис типа {a->key} ликвидирован
 
* SET теперь заканчивается обычным END, а не ENDSET
 
* SET теперь заканчивается обычным END, а не ENDSET
Строка 417: Строка 421:
 
* Добавлен встроенный фильтр для ликвидации пробелов из начал/концов каждой строки шаблона
 
* Добавлен встроенный фильтр для ликвидации пробелов из начал/концов каждой строки шаблона
  
=== Различия PHP и Perl версий ==
+
=== Различия PHP и Perl версий ===
  
 
==== Кэширование работает по-разному ====
 
==== Кэширование работает по-разному ====
  
В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая:
+
В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая:
  
* Perl: считается, что всё прогрессивное человечество уже давно использует <tt>mod_perl</tt> или [[Платформы для запуска Perl веб-приложений|другие способы запуска веб-приложений]], при которых частых переинициализаций интерпретатора не происходит. Иными словами, ''никто больше не использует CGI''. Таким образом, мы смело можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами.
+
* Perl: считается, что всё прогрессивное человечество уже давно использует <tt>mod_perl</tt> или [[Платформы для запуска Perl веб-приложений|другие способы запуска веб-приложений]], при которых частых переинициализаций интерпретатора не происходит. Иными словами, ''никто больше не использует CGI''. Таким образом, мы смело можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами.
 
* PHP: интерпретатор PHP инициализируется заново при обработке каждого HTTP-запроса. А живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно.
 
* PHP: интерпретатор PHP инициализируется заново при обработке каждого HTTP-запроса. А живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно.
  
В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они используют контекст класса Template.
+
В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они используют контекст класса Template.
  
Поэтому компилированный шаблон PHP-версии — это класс, производный от класса Template. Единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на <tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий блоку шаблона (см. [[#Блоки]]).
+
Поэтому компилированный шаблон PHP-версии — это класс, производный от класса Template. Единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на <tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий блоку шаблона (см. [[#Блоки]]).
  
 
Кроме кэширования классов в рамках запроса в PHP существует ещё две ступени:
 
Кроме кэширования классов в рамках запроса в PHP существует ещё две ступени:
Строка 434: Строка 438:
 
* Компилированный код шаблонов кэшируется в файлах на диске, и не компилируется лишний раз.
 
* Компилированный код шаблонов кэшируется в файлах на диске, и не компилируется лишний раз.
  
В Perl действие <tt>reload</tt> немного отличается — <tt>reload = 0</tt> работает так же, как <tt>reload = false</tt> в PHP, но если <tt>reload > 0</tt>, то тексты шаблонов перезагружаются с диска при изменении, но не чаще, чем раз в <tt>reload</tt> секунд. В остальном всё проще — компилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в my-переменной пакета VMX::Template и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода.
+
В Perl действие <tt>reload</tt> немного отличается — <tt>reload = 0</tt> работает так же, как <tt>reload = false</tt> в PHP, но если <tt>reload > 0</tt>, то тексты шаблонов перезагружаются с диска при изменении, но не чаще, чем раз в <tt>reload</tt> секунд. В остальном всё проще — компилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в my-переменной пакета VMX::Template и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода.
  
 
==== Несколько различается действие <tt>use_utf8 = true</tt> ====
 
==== Несколько различается действие <tt>use_utf8 = true</tt> ====
  
* Общий смысл — «мои шаблоны и страницы в кодировке UTF-8».
+
* Общий смысл — «мои шаблоны и страницы в кодировке UTF-8».
 
* PHP: «использовать mb_str* функции для работы со строками в выражениях».
 
* PHP: «использовать mb_str* функции для работы со строками в выражениях».
 
* Perl: «я передаю в шаблон все переменные с флагом UTF-8 = On, их можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, в Perl строки имеют на себе флаг UTF-8 = да или нет, и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, соответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, если строка на самом деле всё-таки в UTF-8, но просто на ней не установлен флаг.
 
* Perl: «я передаю в шаблон все переменные с флагом UTF-8 = On, их можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, в Perl строки имеют на себе флаг UTF-8 = да или нет, и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, соответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, если строка на самом деле всё-таки в UTF-8, но просто на ней не установлен флаг.
Строка 445: Строка 449:
 
==== Различается способ вывода ошибок при <tt>print_error = true</tt> ====
 
==== Различается способ вывода ошибок при <tt>print_error = true</tt> ====
  
* Общий смысл — при <tt>print_error = true</tt> ошибки и предупреждения должны попасть на экран.
+
* Общий смысл — при <tt>print_error = true</tt> ошибки и предупреждения должны попасть на экран.
 
* PHP: они просто выводятся print()'ами.
 
* PHP: они просто выводятся print()'ами.
 
* Perl: здесь так нельзя, потому что HTTP-заголовки сами могут и не отправиться, поэтому текст ошибок прицепляется к выводу шаблонизатора (возвращается вместе с результатом <tt>parse()</tt>).
 
* Perl: здесь так нельзя, потому что HTTP-заголовки сами могут и не отправиться, поэтому текст ошибок прицепляется к выводу шаблонизатора (возвращается вместе с результатом <tt>parse()</tt>).
Строка 456: Строка 460:
 
==== Различается поведение сравнений ====
 
==== Различается поведение сравнений ====
  
* PHP: Обычные сравнения - типозависимые.
+
* PHP: Обычные сравнения — типозависимые.
* Perl: EQ и т. п. без S/N эквивалентно строковому (Sxx).
+
* Perl: EQ и т. п. без S/N эквивалентно строковому (Sxx).
  
 
==== Различается поведение некоторых функций работы с массивами и хешами ====
 
==== Различается поведение некоторых функций работы с массивами и хешами ====
  
* KEYS — в PHP порядок ключей массива/хеша сохраняется, а в Perl — нет и принимаются только хеши. Обусловлено реализацией хешей в этих языках.
+
* KEYS — в PHP порядок ключей массива/хеша сохраняется, а в Perl — нет и принимаются только хеши. Обусловлено реализацией хешей в этих языках.
* PAIRS — в PHP порядок ключей сохраняется, в Perl-версии ключи будут отсортированы по имени.
+
* PAIRS — в PHP порядок ключей сохраняется, в Perl-версии ключи будут отсортированы по имени.
* RANGE — в Perl-версии принимает буквенные аргументы (A..Z = весь алфавит).
+
* RANGE — в Perl-версии принимает буквенные аргументы (A..Z = весь алфавит).
* IS_ARRAY — в PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко (надо проверить, численные ли все ключи).
+
* IS_ARRAY — в PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко (надо проверить, численные ли все ключи).
 
* AGET и HGET в PHP идентичны GET.
 
* AGET и HGET в PHP идентичны GET.
* ARRAY_MERGE: под Perl — только массивы (не хеши), под PHP — любые массивы.
+
* ARRAY_MERGE: под Perl — только массивы (не хеши), под PHP — любые массивы.
* DUMP — это Dumper в Perl’е и var_dump в PHP.
+
* DUMP — это Dumper в Perl’е и var_dump в PHP.
  
==== Строка исходного файла =====
+
==== Строка исходного файла ====
  
В PHP-версии в шаблоны не включаются C-подобные «прагмы» #line, а в текст ошибок не включается имя файла шаблона и строка. Ибо решил — раз уж #line не поддерживается, нечего на строки заморачиваться.
+
В PHP-версии в шаблоны не включаются C-подобные «прагмы» #line, а в текст ошибок не включается имя файла шаблона и строка. Ибо решил — раз уж #line не поддерживается, нечего на строки заморачиваться.
  
 
== А кстати, зачем вообще нужен шаблонизатор? ==
 
== А кстати, зачем вообще нужен шаблонизатор? ==
Строка 477: Строка 481:
 
Ответы:
 
Ответы:
 
* Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.
 
* Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.
* Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это сразу же ликвидирует:
+
* Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это сразу же ликвидирует:
 
** Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.
 
** Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.
 
** Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект.
 
** Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект.
  
Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:
+
Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:
 
* ''Руками'' писать в «шаблонном стиле»:
 
* ''Руками'' писать в «шаблонном стиле»:
 
*# Не смешивать сложные конструкции с HTML.
 
*# Не смешивать сложные конструкции с HTML.

Версия 20:35, 20 апреля 2013

VMX::Template — высокопроизводительный шаблонизатор, имеющий Perl- и PHP-версии (основная — в данный момент PHP).

Содержание

Что это за шаблонизатор?

VMX::Template изначально реализован по мотивам примитивного шаблонизатора, взятого из кода форума phpBB 2 (историческое описание старой версии), и с тех пор переписанного 4 раза:

  1. На регулярные выражения →
  2. На поиск подстроки →
  3. На метод рекурсивного спуска →
  4. И, наконец, на LALR(1) грамматику на основе генератора парсеров LIME

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

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

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

Здесь можно прочитать об: использовании в PHP, использовании в Perl, различиях реализаций.

PHP

require_once 'template.php';
 
# Конструктор
$template = new VMXTemplate(array(
    'root'          => '.',        # директория с шаблонами
    'cache_dir'     => './cache',  # директория для кэширования компилированного кода шаблонов
    'print_error'   => true,       # если true, ошибки компиляции выводятся на STDOUT
    'raise_error'   => false,      # если true, при ошибке компиляции вызывается die()
    'reload'        => true,       # если false, шаблоны будут считываться с диска только 1 раз, и вызовов stat() происходить не будет
    'use_utf8'      => true,       # если true, использовать кодировку UTF-8 для строковых операций
    '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('имя_файла.tpl' [, array("ключ" => "значение", ...)]);
 
# Выполнение именованного блока из файла:
$page = $template->exec_from('имя_файла.tpl', 'имя_блока' [, array("ключ" => "значение", ...)]);
 
# Выполнение кода из строки:
$page = $template->parse_inline('код' [, array("ключ" => "значение", ...)]);
 
# Выполнение именованного блока из кода (не рекомендуется, но возможно):
$page = $template->exec_from_inline('код', 'имя_блока' [, array("ключ" => "значение", ...)]);
 
# Очистка сохранённых данных для генерации ещё одной страницы:
$template->clear;

Perl

use VMX::Template;
 
# Конструктор
$template = new VMX::Template(
    'root'          => '.',        # директория с шаблонами
    'cache_dir'     => undef,      # директория для кэширования компилированного кода шаблонов
    'reload'        => 2,          # если 0, то шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет
                                   # если >0, то шаблоны будут перечитываться с диска не чаще чем раз в reload секунд
    'print_error'   => 1,          # если TRUE, ошибки компиляции попадают в вывод шаблона
    'raise_error'   => 0,          # если TRUE, при ошибке компиляции вызывается die()
    'use_utf8'      => undef,      # если TRUE, использовать "use utf8" на шаблонах
    'begin_code'    => '<!--',     # маркер начала директивы кода
    'end_code'      => '-->',      # маркер конца директивы кода
    'eat_code_line' => 1,          # (похоже на TT CHOMP) съедать "лишний" перевод строки, если в строке только директива?
    'begin_subst'   => '{',        # маркер начала подстановки выражения
    'end_subst'     => '}',        # маркер конца подстановки выражения
    'compiletime_functions' =>     # дополнительные компилируемые функции
        { 'func' => sub {} },      # хеш вида имя функции (в шаблонах) => coderef,
                                   # которому передаются скомпилированные выражения всех аргументов и первым - сам $template
    # немного legacy, устаревшее:
    'wrapper'       => undef,      # если coderef, через это будет пропущен вывод всех шаблонов ("глобальный фильтр")
    'strict_end'    => 0,          # требовать <!-- END имя_блока --> после <!-- BEGIN имя_блока -->
);
 
# Присвоение переменных:
$template->vars("ключ" => "значение", "ключ" => "значение", ...);
 
# Выполнения полностью аналогичны PHP:
$page = $template->parse('имя_файла.tpl' [, { "ключ" => "значение", ... }]);
$page = $template->exec_from('имя_файла.tpl', 'имя_блока' [, { "ключ" => "значение", ... }]);
$page = $template->parse_inline('код' [, { "ключ" => "значение", ... }]);
$page = $template->exec_from_inline('код', 'имя_блока' [, { "ключ" => "значение", ... }]);
 
# Очистка сохранённых данных для генерации ещё одной страницы:
$template->clear;

Синтаксис шаблонов

Шаблон — любой текст (типично — HTML), в который местами включены директивы и/или подстановки.

Пример

<!-- SET title -->
    Статистика
<!-- END -->
<!-- SET headscripts -->
    <script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/jquery.min.js"></script>
<!-- END -->
<!-- INCLUDE "admin_header.tpl" -->
<!-- IF NOT srcid -->
    <p>Добро пожаловать в простую OLAPообразную статистику. Выберите источник данных:</p>
    <form action="?" method="GET">
        <select style="width:100px" name="datasource">
            <!-- FOR s = sources -->
            <option value="{s s.id}">{s s.name}</option>
            <!-- END -->
        </select>
        <input type="submit" value=" Продолжить " />
    </form>
<!-- ELSEIF srcid == "test" || sources[srcid].mode == 'test' -->
    <p>Тестовый режим.</p>
<!-- END -->
<!-- INCLUDE "admin_footer.tpl" -->

Маркеры

  • '<!--', '-->' — маркеры начала и конца директивы
  • '{', '}' — маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т. п.)
  • Подстановки можно использовать и в директивах
  • Маркеры начала и конца директивы можно заменить другими — tсли, например, вы привыкли к TT, можно установить [% %].
  • Маркеры подстановок можно отключить, но заменить другими без правки грамматики парсера нельзя.

Выражения

  • Выражения состоят из переменных, операторов и вызовов функций и методов объектов
  • Синтаксис обращений к переменным JS-подобный: hash.key, array[0], hash['key']
  • Имена переменных регистрозависимы, имена встроенных функций и названия директив (BEGIN, END и т. п.) — регистронезависимы
  • Встроенные функции: function(arg1, arg2, ...) или function single_arg (через пробел с одним аргументом)
  • Функции, определённые через FUNCTION: fn_name('arg' => 'value', 'arg2' => 'value2', ...) или exec('fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })
  • Включение других шаблонов: INCLUDE 'template.tpl', INCLUDE('template.tpl', { 'arg' => 'value', ... })
  • Выполнение функции из другого шаблона: exec_from('template.tpl', 'fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })
  • Методы объектов: var.method(arg1, arg2, ...)

Операторы

a .. b Конкатенация
a b, a OR b Логическое ИЛИ
a XOR b XOR — логическое исключающее ИЛИ
a && b, a AND b Логическое И
a & b Побитовое И
a == b, a != b, a < b, a > b, a <= b, a >= b Операторы сравнения
a+b, a-b, a*b, a/b, a%b Сложение, вычитание, умножение, деление, взятие остатка от деления
!a, NOT a Логическое НЕ
(exp) Выражение в скобках
{ 'a' => 'b', … } Создание хешрефа (Perl), ассоциативного массива (PHP)

Директивы

<!--# Комментарий --> Комментарий
<!-- FOR item = array -->
…код…
<!-- END -->
Цикл. Вместо FOR можно использовать слово FOREACH. Внутри цикла можно обратиться к счётчику через {item_index}
<!-- IF выражение -->
…код…
Если (выражение)
<!-- ELSEIF выражение -->
…код…
Иначе если (выражение)
<!-- ELSE -->
…код…
Иначе
<!-- END --> Конец если / цикла / присваивания
<!-- SET var = выражение --> Присваивание переменной var результата выполнения выражения
<!-- SET var -->
…код…
<!-- END -->
Присваивание переменной var результата выполнения кода
<!-- FUNCTION name (arg1, arg2) = выражение --> Определение функции шаблона как результата выполнения выражения
<!-- FUNCTION name (arg1, arg2) -->
…код…
<!-- END -->
Определение функции / «блока» шаблона. Вместо FUNCTION можно использовать также слова BLOCK или MACRO

Функции

Функции используются в выражениях как ФУНКЦИЯ(АРГУМЕНТ, АРГУМЕНТ, ...) или как ФУНКЦИЯ (пробел) ОДИН_АРГУМЕНТ. Вместо запятой ", " можно также использовать «=>», например HASH(КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ). Синтаксической разницы между ", " и «=>» никакой нет.

Существующие функции перечислены ниже. Через «=» в подзаголовках указываются синонимы функций.

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

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

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

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

Числа, логические операции

LOG

Логарифм.

EVEN, ODD

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

INT=I=INTVAL

Преобразование к целому числу.

SEQ, SNE, SGT, SLT, SGE, SLE

Строковые сравнения.

NEQ, NNE, NGT, NLT, NGE, NLE

Численные сравнения.

YESNO

YESNO($1, $2, $3) — тернарный оператор ($1 ? $2 : $3).

Строки

LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE

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

LCFIRST, UCFIRST

Преобразование первого символа строки в нижний и верхний регистр соответственно.

Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE

  • Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).
  • Экранирование символа " удвоением — sql_quote(строка). (актуально также для CSV)
  • Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. perldoc perlre).

URI_QUOTE=URIQUOTE=URLENCODE

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

REPLACE, STR_REPLACE

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

STRLEN

Длина строки в символах.

SUBSTR=SUBSTRING

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

TRIM

Удаление пробелов из начала и конца строки.

SPLIT

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

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

  • Преобразование символов < > & " ' в HTML-сущности.
  • Удаление всех HTML/XML тегов.
  • Удаление только «небезопасных» HTML-тегов.
  • Преобразование переводов строк (\n) в HTML-тег <br />.

CONCAT, JOIN=IMPLODE

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

SUBST, SPRINTF, STRFTIME

Subst — подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — 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.

STRLIMIT=TRUNCATE

Ограничение длины строки str максимальной длиной len — strlimit(str, len, dots = "..."). Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется dots или по умолчанию "...", если аргумент dots не передаётся.

Массивы и хеши

HASH

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

Соответственно в хеше аргументы идут парами КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ и т. п.

KEYS, HASH_KEYS, ARRAY_KEYS

Массив ключей хэша. Понятное дело, в PHP их порядок сохраняется, а в Perl — нет.

SORT

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

PAIRS

Массив хэшей вида { key => ключ, value => значение } для хэша, в случае Perl ключи будут отсортированы по имени.

ARRAY, RANGE

  • Создание массива.
  • Диапазон от A до B — range(A, B).

IS_ARRAY

Проверка, является ли аргумент массивом. В PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко.

COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD

  • Количество элементов массива, или 0, если аргумент — не массив — count(аргумент).
  • Аналог функции array_slice из PHP.
  • Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).

GET

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

SET

SET(куда, что) — присваивание «куда» значения «что». Уравнения, понятное дело, не решает, то есть, как и обычно, присваивать можно только lvalue :)

ARRAY_MERGE

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

SHIFT, POP, UNSHIFT, PUSH

  • Вынуть элемент из начала массива — shift(array)
  • Вынуть из конца — pop(array)
  • Добавить в начало — unshift(array, value)
  • Добавить в конец — push(array, value).

Включения

Сюда относятся функции выполнения других шаблонов и/или их блоков. Во все эти функции можно передавать «данные» (tpldata) либо с помощью создания хеша функцией hash(), либо просто передачей аргументов как КЛЮЧ => ЗНАЧЕНИЕ, ....

PARSE=INCLUDE=PROCESS

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

parse('имя файла')
parse('имя файла', hash( ключ => значение, ... ))
parse('имя файла', ключ => значение, ...)

PARSE_INLINE=INCLUDE_INLINE=PROCESS_INLINE

Включение кода не из файла, а просто из строки — parse_inline('код шаблона'[, аргументы]).

EXEC

Включение блока из текущего шаблона — exec('имя блока'[, аргументы]).

EXEC_FROM

Включение блока из другого шаблона — exec_from('имя файла', 'имя блока'[, аргументы]).

EXEC_FROM_INLINE

Ещё больше не рекомендуется, но можно вызывать и функции из кода из строки — exec_from_inline('код шаблона', 'имя блока'[, аргументы]).

Прочее

VOID

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

DUMP=VAR_DUMP

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

JSON

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

CALL

Вызов метода объекта по «динамическому» имени — call(varref, method_name, arg1, arg2, arg3, ...).

MAP

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

Изменения относительно старых версий

  • Ликвидированы assign_vars(), assign_block_vars(), tr_assign_vars() — теперь, как и обычно, передаётся просто хеш с данными $vars
  • Синтаксис типа {a->key} ликвидирован
  • SET теперь заканчивается обычным END, а не ENDSET
  • Обращение к счётчику цикла теперь {block_index}, а не {block.#}
  • Добавлены функции, операторы
  • Добавлены детальные сообщения об ошибках
  • Добавлен встроенный фильтр для ликвидации пробелов из начал/концов каждой строки шаблона

Различия PHP и Perl версий

Кэширование работает по-разному

В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая:

  • Perl: считается, что всё прогрессивное человечество уже давно использует mod_perl или другие способы запуска веб-приложений, при которых частых переинициализаций интерпретатора не происходит. Иными словами, никто больше не использует CGI. Таким образом, мы смело можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами.
  • PHP: интерпретатор PHP инициализируется заново при обработке каждого HTTP-запроса. А живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно.

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

Поэтому компилированный шаблон PHP-версии — это класс, производный от класса Template. Единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на tpldata и поле parent, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий блоку шаблона (см. #Блоки).

Кроме кэширования классов в рамках запроса в PHP существует ещё две ступени:

  • Текст шаблонов кэшируется в XCache или eAccelerator, если таковые присутствуют, и не перезагружается с диска лишний раз. Если reload = false, лишними считаются все разы, кроме первого, даже если файл шаблона менялся.
  • Компилированный код шаблонов кэшируется в файлах на диске, и не компилируется лишний раз.

В Perl действие reload немного отличается — reload = 0 работает так же, как reload = false в PHP, но если reload > 0, то тексты шаблонов перезагружаются с диска при изменении, но не чаще, чем раз в reload секунд. В остальном всё проще — компилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в my-переменной пакета VMX::Template и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода.

Несколько различается действие use_utf8 = true

  • Общий смысл — «мои шаблоны и страницы в кодировке UTF-8».
  • PHP: «использовать mb_str* функции для работы со строками в выражениях».
  • Perl: «я передаю в шаблон все переменные с флагом UTF-8 = On, их можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, в Perl строки имеют на себе флаг UTF-8 = да или нет, и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, соответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, если строка на самом деле всё-таки в UTF-8, но просто на ней не установлен флаг.
    Для приведения всех переменных шаблона к UTF-8 можно использовать функцию utf8on() из VMX::Common (рекурсивный Encode::_utf8_on()).

Различается способ вывода ошибок при print_error = true

  • Общий смысл — при print_error = true ошибки и предупреждения должны попасть на экран.
  • PHP: они просто выводятся print()'ами.
  • Perl: здесь так нельзя, потому что HTTP-заголовки сами могут и не отправиться, поэтому текст ошибок прицепляется к выводу шаблонизатора (возвращается вместе с результатом parse()).

Различаются аргументы, передаваемые в compiletime_functions

  • PHP: просто список кода выражений всех аргументов вызова. Функция-компилятор вызывается вне контекста объекта.
  • Perl: тот же список + $self (объект VMX::Template) в качестве первого элемента.

Различается поведение сравнений

  • PHP: Обычные сравнения — типозависимые.
  • Perl: EQ и т. п. без S/N эквивалентно строковому (Sxx).

Различается поведение некоторых функций работы с массивами и хешами

  • KEYS — в PHP порядок ключей массива/хеша сохраняется, а в Perl — нет и принимаются только хеши. Обусловлено реализацией хешей в этих языках.
  • PAIRS — в PHP порядок ключей сохраняется, в Perl-версии ключи будут отсортированы по имени.
  • RANGE — в Perl-версии принимает буквенные аргументы (A..Z = весь алфавит).
  • IS_ARRAY — в PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко (надо проверить, численные ли все ключи).
  • AGET и HGET в PHP идентичны GET.
  • ARRAY_MERGE: под Perl — только массивы (не хеши), под PHP — любые массивы.
  • DUMP — это Dumper в Perl’е и var_dump в PHP.

Строка исходного файла

В PHP-версии в шаблоны не включаются C-подобные «прагмы» #line, а в текст ошибок не включается имя файла шаблона и строка. Ибо решил — раз уж #line не поддерживается, нечего на строки заморачиваться.

А кстати, зачем вообще нужен шаблонизатор?

Ответы:

  • Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.
  • Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это сразу же ликвидирует:
    • Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.
    • Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект.

Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:

  • Руками писать в «шаблонном стиле»:
    1. Не смешивать сложные конструкции с HTML.
    2. Шаблоны выносить в отдельные функции и общаться с ними через 1 ассоциативный массив с данными.
    3. Вывод не печатать, а буферизовать (ob_start() → ob_get_contents() → ob_end_clean()) и возвращать.
  • Использовать читаемый стиль кода (?> и <?php только в конце строк, отступы насквозь через PHP и HTML).

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