Изменения

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

26 441 байт добавлено, 20:43, 27 июля 2018
Нет описания правки
Данный модуль представляет собой новую версию '''VMX::Template''' — простой и высокопроизводительный шаблонизатор, построенную на некоторых новых идеях, ликвидировавшую безобразие имеющий Perl- и legacyPHP-код, накопленный в [[Шаблонизатор VMX::Template/Старая версия|старой версии]], однако сохранившую высокую производительность и простоту.
Есть * Лицензия: GNU GPL версии 3 или новее* Полный набор исходников здесь: http://yourcmc.ru/git/vitalif/VMXTemplate** PHP-версия (PHP >= 5.4): [{{SVN|vitaphotohttp:/branch/yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.phptemplate.php] и [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.parser.php}} PHPtemplate.parser.php].** Новая Perl-версия: [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/VMXTemplate.pm VMXTemplate.pm] и все его [{{SVN|vitaphotohttp:/branch/solsticeyourcmc.ru/lib-swaygit/VMXtree/TemplateVMXTemplate.git/master/VMXTemplate подмодули VMXTemplate/*.pm}} ].** Старая Perl-версия: [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/VMX%2FTemplate.pm VMX/Template.pm] шаблонизатораи [http://yourcmc. Реализация, естественно, несколько отличается по причине различий языков — например, в Perl’е для кэширования кода используются coderef’ы, а в ru/git/vitalif/VMXTemplate/raw/master/VMX%2FCommon.pm VMX/Common.pm].** Исходный код грамматики PHP предполагается, что кэшированием занимается какой-нибудь версии (LALR(1) [http://xcacheyourcmc.lighttpdru/git/vitalif/lime LIME]): [http://yourcmc.netru/ XCachegit/vitalif/VMXTemplate/raw/master/template.lime template.lime] или .** Исходный код грамматики Perl-версии (LALR(1) {{CPAN|Parse::Yapp}}): [http://eacceleratoryourcmc.netru/ eAcceleratorgit/vitalif/VMXTemplate/raw/master/template.yp template.yp]и [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.skel.pm template.skel.pm].** Исходный код голой грамматики (LALR(1) yacc): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.y template.y].** В Git-репозитории можно найти полную историю разработки, ибо там сохранить coderef между запросами, поначиная с самых старых phpbb-видимомуподобный версий.* Простые настройки для подсветки синтаксиса шаблонов в [http://www.midnight-commander.org/ Midnight Commander]'а: [{{SVN|vitalif/trunk/scripts/tpl.syntax|markup}} tpl.syntax]. Чтобы подсветка нормально выглядела, невозможнок tpl.syntax в начало надо дописать html.syntax из стандартного комплекта поставки mc.
Развивается то одна, то другая, в зависимости от проекта, над которым я работаю в моменте.== Что это за шаблонизатор? ==
Также есть простенький (и кривоватенький) файл настроек синтаксиса шаблонов для [http://www.midnight-commander.org/ Midnight Commander]'а''VMX: :Template''' изначально реализован по мотивам примитивного шаблонизатора, взятого из кода форума phpBB 2 ([[{{SVN|vitalif/trunk/scripts/tpl.syntaxСтарая версия|markup}} tpl.syntaxисторическое описание старой версии].]), и с тех пор (с 2006 года) переписанного 4 раза:# На регулярные выражения →# На поиск подстроки →# На метод рекурсивного спуска →# И, наконец, на LALR(1) грамматику на основе генератора парсеров LIME
<span style="border: 2px #FF8000 dashed; padding: 4px">Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишутЕсть PHP и Perl версии шаблонизатора, всё Templateосновная версия шаблонизатора — в данный момент PHP. Есть некоторые различия реализации — например, в Perl’е для кэширования кода используются coderef’ы, а в PHP предполагается, что кэшированием занимается какой-нибудь [http://xcache.lighttpd.net/ XCache] или [http:Toolkit получается»//eaccelerator.<net/span> Это к томуeAccelerator], что идея вообще-то схожая, но реализация гораздо проще и быстрееибо там сохранить coderef между запросами невозможно.
<span style== Идеи =="border: 2px #FF8000 dashed; padding: 4px">Про VMX::Template можно сказать «ох уж эти перлисты — что ни пишут, всё Template::Toolkit получается».</span>
Уйти от assign_vars()Это к тому, assign_block_vars(). Передаватьчто и идея, как и в обычных движкахсинтаксис шаблонов вообще-то схожи, просто хеш с данными $vars. Как, например, в {{CPAN|Template::Toolkit}}. При этом сохранить данные методы для совместимостино сама реализация гораздо проще и быстрее.
Почистить синтаксис: ликвидировать «преобразования», «вложенный путь по переменной» (->key->index->key->и т. п.), специальный синтаксис для окончания SET, неочевидное обращение к счётчику block.#, tr_assign_* и т. п.== Использование ==
Переписать с нуля компилятор.=== PHP ===
Добавить в употребление функции, но только самые необходимыеТребуется PHP версии не ниже 5.4.
Добавить обработку ошибок и диагностические сообщения. == Использование (PHP) == <source lang="code-php">
require_once 'template.php';
# Конструктор(значения опций - по умолчанию)$template = new TemplateVMXTemplate(array(
'root' => '.', # директория с шаблонами
'cache_dir' => './cache', # директория для кэширования компилированного кода шаблонов
'print_error' => truefalse, # если true, ошибки компиляции выводятся на STDOUT
'raise_error' => false, # если true, при ошибке компиляции вызывается die()
'log_error' => false, # если true, ошибки компиляции логгируются через error_log() 'reload' => 1true, # если 0false, шаблоны не будут перечитываться считываться с дискатолько 1 раз, и вызовов stat() происходить не будет # 'use_utf8' => undeftrue, # (только Perl) шаблоны в если true, использовать кодировку UTF-8 и с флагом UTF-8 = Onдля строковых операций
'begin_code' => '<!--', # маркер начала директивы кода
'end_code' => '-->', # маркер конца директивы кода
'eat_code_line' => true, # (похоже на TT CHOMP) съедать "лишний" перевод строки, если в строке только директива?
'begin_subst' => '{', # маркер начала подстановки выражения(либо {, либо false) 'end_subst' => '}', # маркер конца подстановки выражения(либо }, либо false)
'compiletime_functions' => # дополнительные компилируемые функции
array('func' => callback), # массив хеш вида имя функции (в шаблонах) => callback($template, array $args),
# которому передаются скомпилированные выражения всех аргументов
'strip_space' => true, # немного legacy, устаревшеевстроенный фильтр:срезание пробелов из начал и концов строк 'wrapperfilters' => NULL, # если равно чемуфильтры -то, что можно вызвать, через это будет # пропущен вывод выполняются над выводом всех шаблонов array("глобальный фильтр"callback1, ...), 'strict_endauto_escape' => false, # требовать <!функция авто-экранирования, например "s" (для HTML- END имя_блока --> после <!-- BEGIN имя_блока -->безопасного режима)
));
# Присвоение одной переменной:
$template->vars("ключ", "значение");
# Присвоение кучи переменных:
# Выполнение шаблона и получение результата:
# (возможно с передачей целого хеша данныхв шаблон)$page = $template->parse('имя_файла.tpl' [, array("ключ" => "значение", ...)]);
# Аналогично выполнение Выполнение именованного блока из файла:$page = $template->parseexec_from('имя_файла.tpl', 'имя_блока' [, array("ключ" => "значение", ...)]);
# Аналогично выполнение Выполнение кода из строки:$page = $template->parseparse_inline(NULL, 'код' [, array("ключ" => "значение", ...)]); # Выполнение именованного блока из кода (не рекомендуется, но возможно):$page = $template->exec_from_inline('код', 'имя_блока'] [, array("ключ" => "значение", ...)]);
# Очистка сохранённых данных для генерации ещё одной страницы:
$template->clear;
</sourcecode-php>
== Реализация = Perl ===
Маркеры начала Perl версия обновлена и конца кода <tt><nowiki><!теперь в точности соответствует PHP-- --></nowiki></tt> и подстановки <tt><nowiki>{ }</nowiki></tt> могут быть заменены любыми другими. Если, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>. Маркеры подстановки можно вообще убрать, ибо подстановка тоже является кодомверсии.
Путь к переменной теперь может включать в себя числа. Это будут обращения к элементам массивов, в то время как всё остальное — обращения к элементам хешей.<source lang="perl">use VMXTemplate;
# Конструктор$template =new VMXTemplate( 'root' => '.', # директория с шаблонами 'cache_dir' = Блоки > undef, # директория для кэширования компилированного кода шаблонов 'reload' => 2, # если 0, то шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет # если >0, то шаблоны будут перечитываться с диска не чаще чем раз в reload секунд 'print_error' => 1, # если TRUE, ошибки компиляции попадают в вывод шаблона 'log_error' => 1, # если TRUE, ошибки компиляции печатаются на STDERR 'raise_error' => 0, # если TRUE, при ошибке компиляции вызывается die() 'use_utf8' => undef, # если TRUE, использовать "use utf8" на шаблонах 'begin_code' => '<!--', # маркер начала директивы кода 'end_code' => '-->', # маркер конца директивы кода 'begin_subst' => '{', # маркер начала подстановки выражения 'end_subst' => '}', # маркер конца подстановки выражения 'eat_code_line' => 1, # (похоже на TT CHOMP) если TRUE, съедать "лишний" перевод строки, если в строке только директива кода (begin_code..end_code) 'no_code_subst' => 1, # если TRUE, выполнять директивы кода (begin_code..end_code), но игнорировать их результат 'compiletime_functions' => # дополнительные компилируемые функции { 'func' => sub {} }, # хеш вида имя функции (в шаблонах) => coderef, # которому передаются скомпилированные выражения всех аргументов и первым - парсер (объект VMXTemplate::Parser) 'filters' => [ sub {}, .. ], # фильтры для запуска на выводе каждого внешнего шаблона (фильтр - функция, модифицирующая $_[0]) 'strip_space' => 0, # если TRUE, удалять пробелы и табы из начала и конца всех строк вывода 'auto_escape' => '', # функция авто-экранирования, например "s" (для HTML-безопасного режима));
Вне блока $template->vars({block} будет иметь значение ARRAY(0x…) var => 'value', то есть массив всех итераций блока block, а {block .0..} будет иметь значение HASH(0x…), то есть первую итерацию блока block.;
<pre# Выполнения полностью аналогичны PHP:$page = $template-><!parse('имя_файла.tpl' [, { "ключ" => "значение", ... }]);$page = $template->exec_from('имя_файла.tpl', 'имя_блока' [, { "ключ" => "значение", ... }]);$page = $template- BEGIN block ->parse_inline('код' [, { "ключ" => "значение", ... }]);$page = $template-></preexec_from_inline('код', 'имя_блока' [, { "ключ" =>"значение", ... }]);
Теперь, внутри блока {block} теперь будет иметь значение HASH(0x…), то есть уже значение текущей итерации блока block, а {block.#} будет иметь значением номер текущей итерации блока, отсчитываемый с 0, а не с 1, как в старой версии.Очистка сохранённых данных для генерации ещё одной страницы:$template->clear;</source>
<pre><!-- END block --></pre>== Синтаксис шаблонов ==
На <nowiki><!-- END другоеимя --></nowiki> после <nowiki><!-- BEGIN block --></nowiki> шаблонизатор выдаст ошибку, «ибо нефиг» Шаблон — любой текст (cтипично — HTML). Если block , в хеше данных — не массив, а хеш — это значит, что итерация у блока только одна, который ''местами'' включены директивы и тогда <nowiki><!-- BEGIN block --></nowiki> работает как for($expression) {} в Perlили подстановки.
<tt>BEGIN Если вы знаете о порождающих грамматиках, то вот контекстно-свободная грамматика для LALR(1) алгоритма разбора: * Голая Bison-грамматика, для получения представления о синтаксисе: http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.y* Рабочая [http://github. END<com/tt> — это циклы в «старом стиле»vitalif/lime LIME]-грамматика: http://yourcmc. А можно использовать и TTru/git/vitalif/VMXTemplate/raw/master/template.lime (для её работы нужен патченый LIME — см. https://github.com/vitalif/lime)* Рабочая {{CPAN|Parse::Yapp}}-подобныйграмматика:http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.yp === Пример ===
<pre>
<!-- 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 var s = expression 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" -->
</pre>
Причём, если <tt>expression ::= block</tt>, то <tt>var</tt> может быть само <tt>block</tt>'ом. Это, по сути, и есть то, что делает BEGIN: <!-- BEGIN block --> эквивалентно <!-- FOR block = block -->. Предыдущее значение переменной цикла после выхода из цикла всегда восстанавливается.= Маркеры ===
К номеру итерации * <tt><nowiki>'<!--', '-->'</nowiki></tt> — маркеры начала и конца директивы* <tt><nowiki>'{', '}'</nowiki></tt> — маркеры начала и конца подстановки выражения (между скобками не может быть инструкций типа IF/ELSE и т. п.)* Подстановки можно обратиться использовать и в директивах* Маркеры начала и конца директивы можно заменить другими — если, например, вы привыкли к TT, можно установить <tt><nowiki>[% %]</nowiki></tt>. Главное, чтобы маркер начала не был равен маркеру конца.* Маркеры подстановок можно тоже заменить на другие, либо отключить вовсе. === Выражения === * Выражения состоят из переменных, операторов и вызовов функций и методов объектов* Синтаксис обращений к переменным JS-подобный: <tt>hash.key</tt>, <tt>array[0]</tt>, <tt>hash['key']</tt>* Имена переменных '''регистрозависимы''', имена встроенных функций и названия директив (BEGIN, END и т. п.) — '''регистронезависимы'''* Никаких ошибок при обращениях к необъявленным переменным не происходит* Компилируемые функции: <tt>function(arg1, arg2, ...)</tt> или <tt>function single_arg</tt> (через пробел с одним аргументом)* Функции, определённые через FUNCTION: <tt>fn_name('arg'=> 'value', 'arg2' => 'value2', ...)</tt>или <tt>exec('fn_name', {var#'arg' => 'value', 'arg2' => 'value2', ... })</tt>* Включение других шаблонов: <tt>INCLUDE 'template.tpl'</tt>, <tt>INCLUDE('template.tpl', { 'arg' => 'value', ... })</tt>* Выполнение функции из другого шаблона: <tt>exec_from('template.tpl', 'fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })</tt>* Методы объектов: <tt>var.method(arg1, arg2, ...)</tt> === Операторы === Все операторы, кроме сравнений, левоассоциативны. Сравнения — не ассоциативны. <tab sep="bar" class="wikitable">a .. b | Конкатенацияa <html>&#x7C;&#x7C;</html> b, a OR b | Логическое ИЛИ. JS- или Perl-подобное — возвращает первое истинное значение.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)</tab> === Директивы === <tab sep="bar" class="wikitable"><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;!-- IF выражение --&gt;</tt><br />…код… | Если (выражение)<tt>&lt;!-- ELSEIF выражение --&gt;</tt><br />…код… | Иначе если (выражение)<tt>&lt;!-- ELSE --&gt;</tt><br />…код… | Иначе<tt>&lt;!-- END --&gt;</tt> | Конец если / цикла / присваивания<tt>&lt;!-- SET var = выражение --&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><br />…код…<br /><tt>&lt;!-- END --&gt;</tt> | Определение функции / «блока» шаблона. Вместо FUNCTION можно использовать также слова BLOCK или MACRO</tab>
=== Функции ===
'''Операторов нет'''Синтаксис вызова функций:* <tt>ФУНКЦИЯ(АРГУМЕНТ, фильтров нетАРГУМЕНТ, есть функции. Пример:..)</tt>* <tt>ФУНКЦИЯ <пробел> ОДИН_АРГУМЕНТ</tt>
<pre><!-- IF OR(function(blockСуществующие функции перечислены ниже.key1),AND(blockЧерез «=» в подзаголовках указываются синонимы функций.key2,block.key3)) --></pre>
Почему? Тут всё просто — основываясь на предположении, что длинные выражения Расширяемость в шаблонах нужны очень редко, было лениво писать нормальную грамматику для разбора обычных выражений. Почему они нужны редко? Да просто минимум логики в шаблонах — признак хороших шаблонов. А функции покрывают сразу и выражения, и «фильтры», и методы объектов.области функций:
Синтаксис вызова ;Run-time функции нескольких аргументов:В качестве функции можно использовать метод переданного в хеше данных объекта. В «функцию» можно вынести и блок кода из шаблона — см. [[#Блоки]]. Оно хорошо кэшируется.;Compile-time функции: При создании объекта шаблона можно передать параметр <tt>compiletime_functions</tt>, равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными '''коду для вычисления соответствующего аргумента''', и должны возвращать '''код для вычисления результата'''. То есть, они выполняются на этапе компиляции.
<pre><!-- function{{note}} Первое, что обычно нужно — это S(block.key), 0H(), "abc"T() --></pre>, Q(), I(), то есть «фильтры» для различных преобразований строки:
Подстановка:* S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.* H() — удаляет все HTML-теги, кроме «безопасных».* T() — удаляет все HTML-теги.* Q() — это addslashes(), экранирует строки для использования, например, в JS.* I() — преобразует значение к целому числу.
<pre>{function(block.key, 0, "abc")}</pre>==== HTML-безопасный режим ====
Синтаксис вызова функции одного аргументаЕсли вы хотите использовать «HTML-безопасный» режим с автоматическим экранированием — установите опцию '''auto_escape''' равной, например, как раз «s». Смысл режима в том, чтобы:* Не экранировать все значения руками* Случайно не забыть что-то экранировать, экранируя это руками
<pre><!Работает так: если какое-- functionто подставляемое значение не экранировано вами явно через одну из функций вроде перечисленных выше (blocks/t/h/i и т. п.key) , то оно будет экранировано функцией, заданной в auto_escape. Получается «авто--><!-- function blockзащита» от атак типа XSS.key -->Значения, которые надо подставить «как есть», нужно предварить вызовом функции RAW: {block.key/sraw value}{s block.key}</pre>Тогда значение auto_escape’ом экранировано не будет.
Синтаксис вызова метода объектаНа заметку:для удобства функции JSON, QUOTE, SQL_QUOTE и REQUOTE считаются «безопасными», хотя таковыми, строго говоря, не являются. Однако используются они обычно внутри JS-кода, поэтому лучше их вывод не трогать.
<pre>{object.method()}{object.method(arg1=== Числа, arg2)}{call(object, "method")}{call(object, "method", array(arg1, arg2))}</pre>логические операции ===
Последние два применения — как нетрудно заметить, обращение к функции call() и служат для вызова метода по вычисляемому имени.==== LOG ====
Цепочки вызовов методов типа <tt>object.method().another_method()</tt> не поддерживаются, ибо к ним без сохранения звеньев нервно относится даже сам PHPЛогарифм.
=== IF = EVEN, ODD ====
Условный вывод:Истина в случае, если аргумент чётный или нечётный соответственно.
<pre><!-- IF function(block.key) --><!-- ELSEIF ... --><!-- END --><!-- IF NOT block.key -->...<!-- END --> </pre>==== INT=I=INTVAL ====
ELSIF эквивалентно ELSE IF и ELSEIFПреобразование к целому числу.
=== SET = SEQ, SNE, SGT, SLT, SGE, SLE ====
Запись значения переменной:Строковые сравнения.
<pre><!-- SET block.key -->...<!-- END --></nowiki><!-- SET block.key = выражение --></pre>=== NEQ, NNE, NGT, NLT, NGE, NLE ====
=== Включения ===Численные сравнения.
Включение другого шаблона также осталось:==== YESNO ====
<pre><!-- INCLUDE another-fileYESNO($1, $2, $3) — тернарный оператор ($1 ? $2 : $3).tpl --><!-- INCLUDE "another-file.tpl" --></pre>
По «динамическому» имени шаблона включение производится функцией <tt>INCLUDE</tt> (она же <tt>PARSE</tt>). Как несложно заметить, вторая строка — как раз вызов функции.=== Строки ===
=== Блоки = LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE ====
Блок — это часть шаблона, выделенная в отдельную «функцию», хорошо кэшируемая Нижний и предназначенная для повторного вызова из других мест. Покрывает сразу несколько вещей — «блоки», «макросы» и «обёртки» из TT. Да-да, TT славится бессмысленным дублированием функционалаверхний регистр.
Но имеет несколько преимуществ:* блоки==== LCFIRST, определённые в одном шаблоне, можно смело вызывать из других по имени файла + имени блока!* блок можно определить просто как некоторое выражение.* блоки хорошо кэшируются — с VMX::Template вы не испытаете разочарования, если вызовете какой-нибудь блок 1000 раз. В отличие от TT.UCFIRST ====
Блоки Преобразование первого символа строки в шаблоне не могут быть вложенными, а циклы, SET нижний и прочие вещи, их оборачивающие, не имеют на них никакого влияния. После компиляции блоки просто вырезаются и преобразуются в отдельные функции PHP/Perl’аверхний регистр соответственно.
<pre><!-- BLOCK имя_блока -->...код...<!-- END --></pre>==== Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE ====
или* Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).* Экранирование символа " удвоением — sql_quote(строка). (актуально также для [[rupedia:CSV|CSV]])* Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]).
<pre><!-- BLOCK имя_блока = выражение --></pre>=== URI_QUOTE=URIQUOTE=URLENCODE ====
Вместо слова <tt>BLOCK<URL-кодирование строки ({{CPAN|URI::Escape}} в Perl и [http:/tt> можно также использовать слово <tt>FUNCTION</tt> или <tt>MACRO<php.net/tt>manual/en/function.urlencode.php urlencode()] в PHP).
Вызывать блок из шаблона следует с помощью функции [[#INCLUDE = PROCESS = PARSE|PROCESS]]. Вызывать блок из кода следует с помощью== REPLACE, STR_REPLACE ====
== Функции ==* Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).* Замена подстроки в строке — str_replace(искомое, замена, строка).
{{note}} Первое, что обычно нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки:==== STRLEN ====
* S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы Длина строки ''в строках.* H() — удаляет все HTML-теги, кроме «безопасных».* T() — удаляет все HTML-теги.* Q() — это addslashes(), экранирует символы для использования, например, в JS.* I() — преобразует значение к целому числусимволах''.
Расширяемость в области функций:==== SUBSTR=SUBSTRING ====
;Run-time функции: В качестве функции можно использовать переданный в хеше данных coderef Стандартная (замыканиедля всех, ссылку на функцию, или любое «[http://php.net/manual/en/function.is-callable.php is_callable]» в случае PHPкроме жавистов). Если же хочется вынести в «функцию» блок кода из шаблона — проще создать отдельный шаблон и вызывать егофункция подстроки — substr(строка, предварительно делая <tt>&lt;!-- SET --></tt> именованных аргументов. Это действительно лучшеначало, так как позволяет оптимально работать кэшированию.;Compile-time функции: При создании объекта шаблона можно передать параметр <tt>compiletime_functions</tt>длина), равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable substr(PHPстрока, начало). Эти функции вызываются в контексте объекта шаблона с параметрами, равными '''коду для вычисления соответствующего аргумента''', Причём начало и должны возвращать '''код для вычисления результата'''. То естьдлина могут быть отрицательными, тогда они выполняются на этапе компиляциисчитаются относительно длины строки. (пока только PHP-версия)
=== OR, AND, NOT = TRIM ====
Логические ИЛИ, И, НЕ, действующие аналогично Perl операторам ||, &&, !Удаление пробелов из начала и конца строки.
=== EVEN, ODD = SPLIT ====
Истина в случаеРазделение строки по регулярному выражению и лимиту — split(RegExp, если аргумент чётный или нечётный соответственно, лимит). Лимит необязателен. (см. [http://perldoc.perl.org/functions/split.html perldoc -f split])
=== INT=IS=HTML=HTMLSPECIALCHARS, ADDT=STRIP, MULH=STRIP_UNSAFE, DIV, MOD, LOG NL2BR ====
* Преобразование к целому числу, арифметические операции и логарифмсимволов < > & " ' в HTML-сущности.* Удаление всех [[lib:HTML|HTML]]/[[lib:XML|XML]] тегов.* Удаление только «небезопасных» HTML-тегов.* Преобразование переводов строк (\n) в HTML-тег <tt><nowiki><br /></nowiki></tt>.
=== EQ= CONCAT, NE, SEQ, SNE, GT, LT, GE, LE, SGT, SLT, SGE, SLE JOIN=IMPLODE ====
Действуют аналогично Perl операторам == eq > < >= <= gt lt ge le* Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов.* Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.
В PHP-версии на данный момент такого много-безобразия нет==== SUBST, есть просто EQ NE GT LT GE LE. Хотя это и имеет свои минусы - если хотя бы один аргумент принимается PHP как численныйSPRINTF, сравнение становится численным.STRFTIME ====
=== COUNTSubst — подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, SUBARRAY=ARRAY_SLICE$1, SUBARRAY_DIVMOD ===$2, …).
Количество элементов массива, или 0, если аргумент — не массив — count(аргумент)Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf].
Аналог Форматирование даты и/или времени с помощью функции [http://phpwww.netmanpagez.com/manualman/en3/functionstrftime/ strftime] — strftime(формат, дата [, часть_даты]).array-sliceФормат strftime’овский (например, «%d %b %Y»).php array_slice] Дата может передаваться как один или два аргумента, если два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:* UNIX время.* Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из PHP4 или более цифр (год) и 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>.* RFC 822.
Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod).==== STRLIMIT=TRUNCATE ====
Ограничение длины строки <tt>str</tt> максимальной длиной <tt>len</tt> — <tt>strlimit(str, len, dots === ARRAY"...")</tt>. Если строка превышает заданную длину, HASH ===она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется <tt>dots</tt> или по умолчанию <tt>"..."</tt>, если аргумент <tt>dots</tt> не передаётся.
Создание массива или хэша из всех атрибутов.==== PLURAL_RU ====
Соответственно Выбор правильного окончания в хеше атрибуты идут парами КЛЮЧрусском языке в зависимости от количества: <tt>plural_ru(число, ЗНАЧЕНИЕодин, КЛЮЧнесколько, ЗНАЧЕНИЕ и т.пмного)</tt>. Например (специального синтаксиса "=>" нет1 шаблон, 2-3-4-102 шаблонА, 5-6-15-… шаблонОВ).:
<tt><nowiki>{num} шаблон{plural_ru(num, '', 'а', 'ов')}</nowiki></tt> === Массивы и хеши === ==== HASH ==== Создание хэша из всех аргументов. Соответственно в хеше аргументы идут парами КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ и т. п. ==== KEYS, HASH_KEYS, ARRAY_KEYS ==== Массив ключей хэша. Понятное дело, в PHP их порядок сохраняется, а в Perl — нет. ==== SORT ====
Сортировка массива по значениям.
=== ARRAY_MERGE = PAIRS ====
Слить массивы в один. Под Perl — только массивы (не хеши)Массив хэшей вида <tt>{ key => ключ, под PHP — любые массивыvalue => значение }</tt> для хэша, в случае Perl ключи будут отсортированы по имени.
=== GET= ARRAY, AGET, HGET RANGE ====
Получение элемента * Создание массива/хэша по «динамическому» ключу. По-моему* Диапазон от A до B — range(A, это лучше, чем зюки-хрюки Template Toolkit’а: <tt>hash.${hash2.$key}</tt> и т. пB).
<tt>GET(откуда, что)</tt> автоматически решает, «откуда» — это массив или хеш, AGET служит только для массивов, а HGET только для хешей. В PHP-версии все три идентичны.==== IS_ARRAY ====
<tt>GET(что)</tt> — получение значения переменной верхнего уровняПроверка, является ли аргумент массивом. В PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко.
=== MAP = COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ====
Применение функции* Количество элементов массива, имя которой передано как первый или 0, если аргумент — не массив — count(аргумент).* Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP.* Выбор из массива каждого div’того элемента, ко всем переданным аргументам и элементам всех переданных массивов — mapначиная с номера mod или нуля по умолчанию — subarray_divmod(«имя_функции»массив, div, аргументыmod).
=== LC=LOWERGET =LOWERCASE, UC=UPPER=UPPERCASE ===
Нижний и верхний регистр<tt>GET(что)</tt> — получение значения переменной верхнего уровня.
=== STRLIMIT = SET ====
Ограничение длины строки s максимальной длиной l — strlimit<tt>SET(sкуда, lчто)</tt> — присваивание «куда» значения «что». Если строка превышает заданную длинуУравнения, она обрезается предпочтительно по пробелу или Tab’упонятное дело, а в конец добавляется «…» (троеточиене решает, то есть, как и обычно, присваивать можно только lvalue :).
=== S=HTMLARRAY_MERGE =HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ===
Преобразование символов < > & " ' Слить массивы в HTML-сущностиодин. Под Perl — только массивы (не хеши), под PHP — любые массивы.
Удаление всех [[lib:HTML|HTML]]/[[lib:XML|XML]] тегов.==== SHIFT, POP, UNSHIFT, PUSH ====
Удаление только запрещённых тегов* Вынуть элемент из начала массива — <tt>shift(array)</tt>* Вынуть из конца — <tt>pop(array)</tt>* Добавить в начало — <tt>unshift(array, value)</tt>* Добавить в конец — <tt>push(array, value)</tt>.
Преобразование переводов строк (\n) в HTML-тег <tt><nowiki><br /></nowiki></tt>.=== Включения ===
Сюда относятся функции выполнения других шаблонов и/или их блоков. Во все эти функции можно передавать «данные» (tpldata) либо с помощью создания хеша функцией hash(), либо просто передачей аргументов как <tt>КЛЮЧ === URI_QUOTE=URIQUOTE=URLENCODE ===> ЗНАЧЕНИЕ, ...</tt>.
URL-кодирование строки ({{CPAN|URI::Escape}} в Perl и [http://php.net/manual/en/function.urlencode.php urlencode()] в PHP).==== PARSE=INCLUDE=PROCESS ====
=== CONCAT, JOIN=IMPLODE ===Включение другого шаблона.
Конкатенация всех своих аргументов — concat<pre>parse(аргументы'имя файла')parse('имя файла', hash( ключ => значение, . Конкатенирует также все элементы всех переданных массивов.. ))parse('имя файла', ключ => значение, ...)</pre>
Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов.==== PARSE_INLINE=INCLUDE_INLINE=PROCESS_INLINE ====
=== SUBSTR=SUBSTRINGВключение кода не из файла, STRLEN ===а просто из строки — <tt>parse_inline('код шаблона'[, аргументы])</tt>.
Стандартная (для всех кроме жавистов стандартная) функция подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки.==== EXEC ====
Ну и функция «длина строки»Включение блока из текущего шаблона — <tt>exec('имя блока'[, аргументы])</tt>.
=== Q=QUOTEEXEC_FROM =ADDSLASHES, REQUOTE=RE_QUOTE=PREG_QUOTE ===
Экранирование символов " ' \ и перевода строки бэкслэшем — quoteВключение блока из другого шаблона — <tt>exec_from(строка'имя файла', 'имя блока'[, аргументы])</tt>.
Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]).==== EXEC_FROM_INLINE ====
=== REPLACEЕщё больше не рекомендуется, SPLIT ===но можно вызывать и функции из кода из строки — <tt>exec_from_inline('код шаблона', 'имя блока'[, аргументы])</tt>.
Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).=== Прочее ===
Разделение строки по регулярному выражению и лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. [http://perldoc.perl.org/functions/split.html perldoc -f split])==== VOID ====
Вычислить аргумент и вернуть пустую строку. Потенциально нужно для игнорирования результата, ибо все возвращаемые значения радостно подставляются в выходной поток. ==== RAW ==== Пустое преобразование первого аргумента со снятием флага «небезопасности». Нужно для подстановки значений «как есть» в HTML-безопасном режиме авто-экранирования. ==== DUMP, JSON =VAR_DUMP ====
Вывод всех данных из структуры — Dumper в Perl’е и var_dump в PHP.
Форматирование структуры данных в формат ==== JSON. (пока только PHP)====
=== INCLUDE=PARSE ===Форматирование любой структуры данных в формат JSON.
Включение файла с именем, переданным аргументом.==== CALL ====
=== SUBSTВызов метода объекта по «динамическому» имени — <tt>call(varref, SPRINTFmethod_name, STRFTIME ===arg1, arg2, arg3, ...)</tt>.
Подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, …).==== MAP ====
Sprintf — он Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf]элементам всех переданных массивов — map(«имя_функции», аргументы).
Форматирование даты == Изменения относительно старых версий == * Ликвидированы assign_vars(), assign_block_vars(), tr_assign_vars() — теперь, как и/или времени обычно, передаётся просто хеш с помощью данными $vars* Синтаксис типа {a->key} ликвидирован* Авто-переводы, которые были в перловой версии — тоже тю-тю (хотя, может, и будут возрождены)* SET теперь заканчивается обычным END, а не ENDSET* Обращение к счётчику цикла теперь {block_index}, а не {block.#}* Добавлены функции [http, операторы* Добавлены детальные сообщения об ошибках* Добавлен встроенный фильтр для ликвидации пробелов из начал/концов каждой строки шаблона == Различия PHP и Perl версий == ==== Кэширование работает по-разному ==== В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая* Perl: считается, что всё прогрессивное человечество уже давно использует <tt>mod_perl<//wwwtt> или [[Платформы для запуска Perl веб-приложений|другие способы запуска веб-приложений]], при которых частых переинициализаций интерпретатора не происходит.manpagezИными словами, ''никто больше не использует CGI''.comТаким образом, мы легко можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами. Так и живём — скомпилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в ''экземпляре объекта VMXTemplate'' и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода. '''Важное следствие:''' объект VMXTemplate между запросами нужно оставлять живым. Если его убить — кэш полностью очищается.* PHP: интерпретатор PHP всегда инициализируется заново при обработке каждого HTTP-запроса, а живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно. Однако предполагается, что всё прогрессивное человечество давно использует APC/manXCache/3ZendOpCache/strftimeeAccelerator, и поэтому, когда текст шаблонов компилируется в файлы, а файлы подгружаются путём require, на самом деле они загружаются не с диска, а из памяти кэшера, причём — в уже скомпилированном виде. Кроме того, так как скомпилированный шаблон представляет собой класс — в рамках одного запроса он загружается максимум 1 раз, последующие вызовы происходят уже очень быстро. Ну и на всякий пожарный — хотя это, возможно, уже особого выигрыша и не даёт — нескомпилированный текст шаблонов тоже кэшируется в кэше переменных APC/ strftime] — strftime(форматXCache/eAccelerator, дата [если таковой присутствует, часть_даты])и не перезагружается с диска лишний раз. Формат strftime’овский (напримерЕсли <tt>reload = false</tt>, «%d %b %Y»)лишними считаются все разы, кроме первого, даже если файл шаблона менялся. Дата может передаваться  В Perl действие <tt>reload</tt> немного отличается — <tt>reload = 0</tt> работает так же, как один или два аргумента<tt>reload = false</tt> в PHP, но если два — <tt>reload > 0</tt>, то тексты шаблонов всё-таки перезагружаются с диска при изменении, но не чаще, чем раз в <tt>reload</tt> секунд. В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они конкатенируются через пробелдёргают разные функции от $this, подразумевая, что это объект класса VMXTemplate. Далее дата разбирается способом Поэтому компилированный шаблон PHP-версии — это класс, похожим производный от класса VMXTemplate. Как уже сказано выше, единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на wfTimestamp<tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий функции шаблона (&lt;!-- FUNCTION … --&gt;) . ==== Несколько различается действие <tt>use_utf8 = true</tt> ==== * Общий смысл — «мои шаблоны и страницы в MediaWikiкодировке UTF-8». Принимается следующее:* UNIX времяPHP: «использовать mb_str* функции для работы со строками в выражениях».* Времена типа MySQL DATEPerl: «я передаю в шаблон все переменные с флагом UTF-8 = On, MySQL DATETIMEих можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, EXIFв Perl строки имеют на себе флаг UTF-8 = да или нет, ISO 8601и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, MediaWikiсоответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, и любые другиеесли строка на самом деле всё-таки в UTF-8, подпадающие под следующий форматно просто на ней не установлен флаг.*: 1 группа Для приведения всех переменных шаблона к UTF-8 можно использовать функцию <tt>utf8on()</tt> из 4 или более цифр <tt>VMXTemplate::Utils</tt> (рекурсивный <tt>Encode::_utf8_on(год) </tt>). ==== Различается способ вывода ошибок при <tt>print_error = true</tt> ==== * Общий смысл — при <tt>print_error = true</tt> ошибки и 2 (месяцпредупреждения должны попасть на экран.* PHP: ошибки группируются и выводятся в отдельном div’е в конце страницы, день) или 5 либо просто print’ами по месту возникновения при вызове из консоли.* Perl: текст ошибок прицепляется к выводу шаблонизатора (месяцвозвращается вместе с результатом <tt>parse()</tt>). ==== Различается поведение сравнений ==== * PHP: Тип обычных операторов сравнения определяется во время выполнения. То есть, деньесли во время выполнения одно из сравниваемых значений — число, часыони сравниваются как числа, минутыиначе — как строки.* Perl: Тип обычных операторов сравнения определяется ''во время компиляции''. То есть, секундыесли из контекста понятно, что одно из сравниваемых значений — число (если это константа или результат, например, функции count) групп по 2 цифры, разделённые любыми нецифровыми символами сравнение будет численным, иначе — строковым.* Пустые массивы и хеши ложны в конце — опционально временная зона — 2 цифрыPHP и истинны в Perl. То есть простая проверка «IF array» (приведение к булеву типу), предварённые пробеломесли array пуст, плюсом или минусомв PHP вернёт false, а в Perl — true. Короче говоря ==== Различается поведение некоторых функций работы с массивами и хешами ==== * KEYS — в PHP порядок ключей массива/хеша сохраняется, <pre>^\Dа в Perl — нет и принимаются только хеши. Обусловлено реализацией хешей в этих языках.*(\d{4PAIRS — в PHP порядок ключей сохраняется,})\Dв Perl-версии ключи будут отсортированы по имени.*RANGE — в Perl-версии принимает буквенные аргументы (\d{2}A..Z = весь алфавит)\D.*IS_ARRAY — в PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко (\d{2}надо проверить, численные ли все ключи)\D.*AGET и HGET в PHP идентичны GET.* ARRAY_MERGE: под Perl — только массивы (не хеши), под PHP — любые массивы.* DUMP — это Dumper в Perl’е и var_dump в PHP. ==== Строка исходного файла ==== В PHP-версии в шаблоны не включаются C-подобные «прагмы» #line, а в текст ошибок не включается имя файла шаблона и строка. Ибо решил — раз уж #line не поддерживается, нечего на строки заморачиваться. == А кстати, зачем вообще нужен шаблонизатор?== Ответы:* Чтобы структурировать код, осознанно используя для генерации HTML-ек язык с ограниченными возможностями. Так как возможности ограничены, сложные вычисления писать на нём автоматически не хочется, соответственно, они перемещаются в логику, разделение становится более явным.* Чтобы структурировать выполнение — сначала логика, потом HTML. В идеале «обратной связи» из шаблонов в логику быть не должно, то есть шаблону должно передаваться ровно столько данных, сколько ему нужно, чтобы в процессе выполнения он ничего не дочитывал. Это приблизительно называется MVP (\d{2}Model-View-Presenter; View имеет связь с моделью только через Presenter)\Dи сразу же ликвидирует:** Трудноуловимые проблемы производительности, происходящие по вине ленивых вычислений и вызовов методов модели, дочитывающих данные из БД, из view.** Проблемы с преждевременной отправкой HTTP-заголовков, после которой внезапно обнаруживается, что, оказывается, нужно было сделать редирект.* Второй вариант — классический MVC, шаблон — это View (\d{2}представление)\D, во View передаётся модель, и View отображает состояние модели так, как ему хочется. То есть, шаблон общается напрямую с живыми объектами модели, которые ему передают, «обратная связь» присутствует.*(\d: {2{Warning}} Важное ИМХО! Частая проблема классического подхода — по 100 запросов для чтения одного и того же свойства при отображении 100 объектов выборки, вместо того, чтобы прочитать это свойство за 1 запрос сразу для всех 100 объектов. Чтобы такого не было — нужно, чтобы объекты помнили, частью какой коллекции они являются, и при чтении свойства читали его сразу для всех объектов «своей» коллекции. Идея основана на предположении, что если у объекта, прочитанного из БД как часть большой выборки, запрашивается какое-то свойство — велика вероятность того, что это же свойство будет запрошено и у всех остальных объектов той же самой выборки. При такой реализации — и писать удобно (не нужно заморачиваться, что передавать в шаблон, а что нет)\D, и производительность не страдает. Примечание: так как PHP — «язык наизнанку», сам немножко являющийся шаблонизатором, то при выполнении следующих требований можно писать и без шаблонизатора:*([\+\- ]\d{2}\D''Руками'' писать в «шаблонном стиле»:*# Не смешивать сложные конструкции с HTML.*# Шаблоны выносить в отдельные функции и общаться с ними через 1 ассоциативный массив с данными.*# Вывод не печатать, а буферизовать (ob_start()?&rarr; ob_get_contents()?$</pre>&rarr; ob_end_clean()) и возвращать.* Оракловский формат даты-времени: <nowikiИспользовать читаемый стиль кода (?>ДД-Мес-ГГ[ГГ] ЧЧ.ММ.ССи </nowiki>?php только в конце строк, отступы насквозь через PHP и HTML).* RFC 822Это бывает весьма полезно, если нужно написать модуль к системе, которая сама написана без шаблонизатора или с каким-нибудь полу-кривым собственным, и не хочется вводить дополнительную зависимость.
[[Категория:Sway]][[Категория:РазработкаТехактивы]]
[[Категория:Perl]]
[[Категория:PHP]]