dk lab
Homepage Карта сайта Версия для печати Scriptor&Pager:
. .
Использование системы Scriptor
Pager v1.0 (техническое описание)
dkLab | В процессе | Scriptor&Pager | Pager v1.0 (техническое описание)
Система Orphus

Шабн... шаблн... шаблонз... ша-бло-ни-за-тор!..
Автор пожелал остаться неизвестным

Pager & Scriptor - развитой шаблонизатор страниц

Дмитрий Котеров


Опухоль, мрт позвоночника позволять полностью оно экран получить. | Уборка помещений с материалами Tork | Лучшая подарочная упаковка коробки недорого

В этом документе кратко описаны основные возможности системы 
Pager. Система пока еще находится в стадии разработки, но конец уже 
близок.
Результаты тестирования системы на машине Pentium100/Windows98/32M
при обработке активной (по большей части) блочной страницы с несколькими
рисунками и другими макротэгами:

- компиляция во внутреннее представление (исключая запуск Perl):
1.2 с
время сокращено за счет отказа от HTML::Parser
- исполнение откомпилированной страницы (исключая время компиляции модулей)
0.0125 с (80 стр/c)
без кэширования вывода - 0.03 с (33 стр/с)

Таким образом, получаем, что открытие страницы, поддерживаемой 
системой Pager, очень незначительно (около 30% при использовании mod_perl,
что теоретически исключает время компиляции модулей) отличается от 
открытия статической страницы! Конечно, результаты несколько хуже на 
обычном Perl - сказывается замедление, вызванное запуском Perl и 
компиляцией немногочисленных модулей... 



Возможности системы
-------------------
Здесь кратко перечислены особенности и возможности системы Scriptor&Pager.

- работает в качестве CGI на Perl 5.002 и старше
- предельно простая установка и настройка
- низкая загрузка сервера
- иерархическая схема построения страницы из блоков, наследование 
  блоков от родительских директорий
- использование предварительной компиляции страниц и трехуровневого кэша 
  страниц и блоков
- автоматическое отслеживание изменений в файлах и перекомпиляция
  в случае необходимости
- кэширование заданных участков страниц; различные алгоритмы 
  обновления кэша;
  поддержка множества кэшей для одного и того же блока в зависимости 
  от данных, на которых он базируется (незаменим при проектировании 
  новостных и др. систем с высокой нагрузкой и частым обновлением)
- поддержка автоматического генерирования фреймов для страницы - 
  "вставка страницы во фреймы"; поддержка "умного" кэша фреймов, 
  серьезно снижающего нагрузку на браузер пользователя и трафик
- автоматическая генерация названия страницы на основе имен наддиректорий
- возможность ссылаться на другие блоки - как прямые, так и "поздние";
  возможность ссылаться на блоки и других файлов и использовать 
  относительные URL; исключение "зацикливаний" при обращении блока к 
  себе - удобные средства для включения одних файлов из других
- мощная макроподстановка с использованием регулярных выражений; как 
  пример - автоматическое построение карты (содержания) страницы
- возможность внедрения Perl-кода в страницы, подстановка $-переменных 
  непосредственно, прямо в страницу
- различные дополнительные server-side "псевдотэги", такие как <if>..</if>,
  <foreach>...</foreach> и т.д.
- "прозрачные" и "умные" form-тэги (такие как все input-ы, select и 
  textarea) - между вызовами одной и той же формы СОХРАНЯЮТ свои 
  значения. У тэга <select> введен параметр buttonize (и tail), 
  позволяющий представлять элемент не в виде списка, а в виде 
  кнопок - radios при не-multiple и checkboxes при multiple-select-е. 
  Можно также задать не набор options-ов, а имя массива-списка значений. 
  Дополнительный параметр confirm у submit-кнопки - позволяет задавать 
  вопрос о подтверждении при нажатии. ВСЕ остальные функции сохранены 
  без изменения, и их можно также использовать 
- возможность надстраивания существующих моделей страниц
- поддержка идеологии трехэшелонного проектирования обработчиков форм 
  (дизайн и html-кодирование <-> интерфейс <-> реализация); тэг <datasrc> 
- гарантированное избавление от 500-й Ошибки - вывод ошибок в браузер
- в случае ошибки при выполнении любого блока показывается имя файла и строка, 
  где она произошла, даже если блок получился в результате макроподстановки
- простая расширяемость в будущем; простой код
- API разработчика, в том числе поддержка GET-POST-Cookies-Upload


Этот список не полон, кроме того, в будущем новые возможности будут 
постоянно добавляться без ущерба совместимости.


Несколько слов об организации файлов Pager
-------------------------------------------------------------------
Все файлы имеют ОДИН И ТОТ ЖЕ формат. Расширение различно 
только для удобства. Для примера, это распространяется на следующие 
файлы:
- файлы *.blk
- файлы *.htm
- файлы *.html (документы)
- файлы .htaccess (да-да, те самые!)
Далее все такие файлы мы будем называть "файлами блоков".
Файлы, содержащие блок Output (косвенно или непосредственно), назовем
"моделями страницы". Можно сказать, что модель страницы задает ее 
внешний вид.



Формат файлов
-------------
Каждый файл состоит из одного или нескольких именованных блоков.
Новый блок объявляется в виде конструкции:

 ##ИмяБлока = Содержимое_Блока_На_Одной_Строке

или

 ##ИмяБлока
 Содержимое
 блока
 на нескольких
 строках	

Первое объявление задает однострочный блок, последнее - 
многострочный. Содержимое однострочного блока может быть заключено в
кавычки - в этом случае из него не удаляются начальные и концевые 
пробелы. Иначе - удаляются, ровно как и из многострочного. Имени блока
обязательно должны предшествовать символы "##", идущие с начала строки.
Имена блоков ЧУВСТВИТЕЛЬНЫ к регистру символов!
Использование "##" мотивировано тем, что это позволяет легко
задавать блоки в файле .htaccess, которые сервер воспримет просто как 
комментарии.
Концом содержимого блока считается либо конец файла, либо 
начало определения следующего блока. Исключение - однострочный блок:
его конец определяется концом строки. Начальные и концевые пробельные 
символы отрезаются. Этого можно избежать, заключив ВСЕ тело блока в 
двойные кавычки.
Любой текст, расположенный вне тел блоков, рассматривается как 
комментарий и игнорируется. Часто для большей читабельности мы предваряем 
такие комментарии символом "#", который не играет никакой роли.
При обработке какого-либо файла сначала собираются вместе все 
блоки, которые ему соответствуют (все внешние .htaccess-ы плюс блоки
в самом файле и блоки, на которые он ссылается).


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

 ##@INCLUDE = URL_файла
включает В ДАННОЕ МЕСТО (!) все блоки, содержащиеся в
файле URL_файла

 ##@USE = URL_файла
почти то же самое, что и @INCLUDE, за одним очень важным исключением:
добавляемые блоки из файла помещаются В САМЫЙ КОНЕЦ "стопки" блоков,
а не в то место, где встретилось @USE. Обычно эта директива используется
для загрузки файлов-моделей в главном .htaccess-файле, чтобы можно
было делать надстройки одной модели над другой - например, создать
модель A, полностью копирующую модель B, но вставляющую в блок Текст
локальную карту страницы.

 ##@INC = URL_с_файлами
задает очередной URL, в котором будут искаться все файлы,
включаемые посредством @INCLUDE или @USE

 ##@AUTORUN = код_для_автообработки
как только управление доходит до этого блока, текст,
указанный как "код_для_автообработки", обрабатывается,
и то, что получилось, выводится прямо в браузер, минуя
другие блоки. Этот блок имеет смысл, если в нем присутствуют 
участки	кода на Perl, заключенные в <? и ?>, либо же расширенные тэги
от WiseTags. Например, так мы загружаем код, реализующий интерфейс с 
системой новостей:
##@AUTORUN = <DATASRC src=News::List>
где News::List есть указание использовать модуль News/List.pm
из стандартной директории модулей. См. описание тэга <DATASRC>.

 ##@DEFAULTGLUE = разделитель_по_умолчанию
задает разделитель по умолчанию, который будет использован для 
склейки названий, если "клеевой" блок не указан. Чтобы следующее
название "приклеивалось" к предыдущему, нужно в начале его тела 
поставить символы [имя_блока_клея], например:
##Название = [Glue]Документ
В этом примере блок "Название" будет составлен как содержимое
предыдущего блока "Название" и текста "Документ", склеенное
содержимым блока "Glue". Если указаны пустые скобки, 
подразумевается блок @DEFAULTGLUE.

 ##@CACHE = имя_блока; выражение [; кэш_код_зависимостей]
говорит системе о том, что результат работы блока имя_блока
должен кэшироваться. Как часто обновлять кэш - говорит выражение.
Если оно представляет собой десятичное число - например, 20, -
то это означает, что кэш блока должен обновляться КАЖДЫЕ 20 секунд 
- то есть, по истечение этого промежутка времени блок будет исполнен 
при запросе к нему. Если выражение - любая непустая строка 
(рекомендуем применять строку "!" или "update"), то кэш считается
недействительным сейчас и обновляется. Если же выражение равно 
пустой строке (""), то кэш используется всегда, и его перевычисление
не производится. Тело прагмы @CACHE, как и любой другой прагмы или 
блока, вычисляется при КАЖДОМ открытии страницы - в этом она
похожа на прагму @AUTORUN. Это означает, что мы можем использовать в 
качестве тела не статическую строку, а результат работы некоторого
кода, который как раз и будет генерировать "!" в случае 
недействительности кэша (например, когда получено сообщение о том, что 
база данных обновлена) и "" в случае его "истинности". Отдельно 
рассмотрим, что такое необязательный параметр кэш_код_зависимостей.
Дело в том, что тело блока может кэшироваться не в "единственном" 
экземпляре, а, например, в зависимости от того, с какими параматрами была
вызвана страница. Например, совершенно очевидно, что для страницы
новостной системы, которая принимает в параметре дату, за которую 
выводятся новости, будет недостаточно одного-единственного кэша - 
нужно, чтобы для каждой даты был свой собственный кэш. Добиться этого
можно, указав в третьем параметре дату, для которой затребована страница
- вообще говоря, для любых двух разных строк, стоящих на месте третьего
параметра, используются два разных и независимых кэша. Итак, для нашей
гипотетической новостной системы прагма @CACHE будет выглядеть так:
##@CACHE = НовостнойБлок; 300; $IN{date}
что говорит системе использовать разные кэши бля блока НовостнойБлок,
каждый из которых соответствует разным датам $IN{date}.


 ##@CACHEDIE = время
задает время (в секундах), спустя которое будут удаляться давно не
используемые кэши блоков. Если эта прагма нигде не задана, по умолчанию
используется 3 часа.		

 ##@FILIZATOR = URL_директории; тайм-аут
задает параметры настройки Filizator-а (см. ниже). 
URL_директории - это URL той директории, где будет храниться
кэш фреймов, а тайм-аут - время, через которое этот кэш очищается
(то есть, фреймы, к которым давно не обращались, автоматически
удаляются из кэша после любого запроса). Если эта прагма нигде не указана,
используется директория /frames и время 10 минут.

 ##/выражение/
макротэг, задаваемый регулярным выражением. Макротеги -
это специальные блоки, имя которых является регулярным 
выражением. После того, как страница полностью обработана,
но до того, как она будет откомпилирована (!), эти выражения в 
ней заменяются на содержимое соответствующих блоков (при этом, если
эти блоки содержат Perl-код, то он выполняется ТОЛЬКО ОДИН РАЗ,
а именно, перед компиляцией, но не при каждом открытии страницы.
Например, так мы можем сделать, чтобы во всей странице
тэги <h3>...</h3> превратились в <b><h3>...</h3></b>:

##/<h3>(.*?)</h3>/ = <b><h3>$1</h3></b>

Нужно заметить, что во избежание проблем с администрированием
лучше использовать только макротэги, заключенные в "<" и ">".
Причина - в элементах формы <textarea> и др. символы "<" 
и ">" заменяются на "<" и ">" соотв., и впоследствии
уже не обрабатываются процессором макротэгов, что нам и нужно
(в противном случае мы бы получили расширения этих тэгов
прямо во время администрирования в форме, что, конечно,
нежелательно).



Ссылка на блоки
---------------
В любом блоке можно сослаться на содержимое другого блока.
То есть, мы можем вставить в блок A содержимое блока B при помощи
конструкции:

 ##A
 текст блока A
 {{B}}
 кажется, вставили то, что было в B...

Те блоки, на которые в документе нет ссылок (за исключением 
прагм), НЕ ОБРАБАТЫВАЮТСЯ, а значит, не тормозят процесс.
Существует и другой способ ссылаться на блок. Делается это при помощи 
предопределенного хэша %Blocks (или %Blk, что то же самое). Например:

 ##A
 текст блока A
 $Blk{B}
 кажется, вставили то, что было в B...

Отличие от {{B}} заключается в том, что в первом случае макропроцессор
пытается подставить тело затребованного блока на место {{B}} как можно 
раньше - а именно, ДО компиляции, тогда как в последнем случае тело блока 
B вставляется в нужное место ВО ВРЕМЯ ИСПОЛНЕНИЯ кода страницы. Кроме того,
допустима такая конструкция:

 ##A
 <? 
 $a=$Blk{B};       # так предпочтительнее!
 $a=RunBlock("B"); # ...но можно и так
 ?>

но не допустима следующая:

 ##A
 <? 
 $a={{B}} 
 ?>

Возможно, в будущих версиях будут работать оба способа, но пока - 
только первый (это связано с трудностью определения, находится ли некоторая
позиция в строке внутри <? и ?> или вне их).
С точки зрения быстродействия обе конструкции работают почти 
одинаково. Функциональные их различия можно понять и на примере макротэга, 
строящего локальную карту страницы, включая все блоки, которые ей используются
- в этом случае нужно обязательно будет использовать синтаксис {{B}}, но не
$Blk{B}, потому что первая разворачивается до того, как будут обработаны 
макротэги, а вторая - после.
При ссылке на блок можно также задавать, из какого файла он будет 
браться (естественно, этот файл должен быть сначала подгружен страницей с
помощью @INCLUDE или @USE). Делается это путем задания префикса перед 
именем блока - абсолютного либо относительного URL. Например:

{{B}}                - вставляет "ближайший" блок B 
{{../B}}             - B из файла .htaccess, находящегося на уровень выше
{{/B}}               - вставляет из главного .htaccess-файла
{{/dir/file.html/B}} - вставляет блок из файла /dir/file.html

Все ссылки представляют собой URL, а не абсолютный пути файловой системы.
Имена ссылок могут также формироваться динамически, во время исполнения 
соответствующего блока. То есть, если имя в {{...}} не содержит
Perl-переменные или код, то ссылка расширяется в момент препроцессирования, 
иначе - в момент исполнения. Можно форсировать позднее расширение ссылки 
(например, для уменьшения размера кэш-файла) путем использования синтаксиса
{{late:имя_блока}}.
Рассмотренные только что ссылки назовем "прямыми". Существуют также и 
"косвенные" ссылки, связанные с понятием Filizator (см. ниже). Их синтаксис 
почти такой же, как и у прямых ссылок, и выглядит так: 
{{filize:имя_блока}}. Косвенные ссылки - всегда "поздние", в отличие 
от прямых.



Блок Output
-----------
Этот блок несет специальный смысл. А именно, его содержимое 
выводится в браузер. Обычно блок Output содержат файлы-модели страниц.
Вообще, выбор имени Output совершенно произволен - просто Scriptor 
настроен именно на него. 



Встраивание Perl-кода в блоки
-----------------------------
В текст любого блока можно встроить код на Perl. В этом случае
он выполняется, и то, что он вывел, и вставляется в текст блока.
Код должен обрамляться специальными тэгами <? и ?>.
Рекомендуется КАК МОЖНО МЕНЬШЕ использовать эту возможность.
В качестве альтернативы предлагаются расширенные тэги управления от
WiseTags, которые действительно способны удовлетворить все потребности, 
которые могут возникнуть при использовании Pager.



Расширенные html-тэги ветвления и циклов (WiseTags)
---------------------------------------------------
<input type=...> 
преобразуется в эквивалент из Tags.pm

<textarea...>...</textarea>
преобразуется в эквивалент из Tags.pm

<select...>...</select>
преобразуется в эквивалент из Tags.pm. Кроме того, существуют дополнительные 
параметры к тэгу <select>:
buttonize - использует не список, а набор кнопок
tail=...  - текст, которым будет завершена каждая кнопка (см. buttonize)
multiple  - формирует список с множественным выбором
Если вместо ВСЕХ <option> задана ОДНА СТРОКА из букв и цифр, то она
трактуется как имя переменной, содержащей массив или хэш элементов.

<foreach var=имя_счетчика src=имя_переменной_списка>...</foreach>
заключает блок текста между тэгами в цикл foreach

<if expr=логическое_выражение>...</if> или <elsif...> или <else>
заключает  блок в оператор if

<elsif expr=логическое_выражение>
то же, только в оператор elsif

<else>
то же, только в оператор else

<ifarray var=имя_переменной>
то же, что <if>, только проверяется, является ли переменная массивом

<ifhash var=имя_переменной>
то же, но проверяется на хэш

<ifscalar var=имя_переменной>
то же, но проверяется, что переменная - не массив и не хэш

<dump[src=имя_объекта]>
печатает полное содержимое объекта независимо от его структуры. Если 
параметр не задан, то печатается содержимое %IN.

<require src=имя_модуля_скрипта>
выполняет команду use для модуля, а затем делает доступными в пакете
вызвавшего контекста переменные, взятые из хэша %IN.

<datasrc src=имя_модуля_скрипта>
то же самое

В параметре src у этих тэгов должна быть указана переменная-список
(за исключением <dump> - у него там может быть любой объект, и <datasrc> с 
<require>, где должна быть указана константа), причем БЕЗ предваряющего 
символа "@". Вообще, единственное место, где иногда нужно употреблять $, % 
или @ - это в параметрах expr и некоторых других местах. Везде, где требуется
лишь имя переменной, предварять ее $, % или % НЕЛЬЗЯ!



Функции API
-----------
При запуске конкретного блока ему доступны некоторые переменные и 
функции API. Все блоки запускаются внутри пакета Pager, поэтому все 
глобальные переменные, которые они создают, расположены именно в этом 
пакете. Кроме того, доступны следующие предопределенные переменные:

$CurPagerFile
содержит указатель на объект текущего файла.

$CurBlock
содержит указатель на данные текущего блока

$CurKey
содержит ключ (название) текущего исполняемого блока. При обработке 
макротэга - ключ того блока, который обрабатывается в данный момент.

$CurBody
только во время расширения макротэгов - содержит еще не откомпилированное
тело блока, который обрабатывается в данный момент.

%Blocks или %Blk
псевдохэш, при обращении к ключам которого на самом деле происходит
исполнение указанного блока.

%Filize
псевдохэш, при обращении к ключам которого вызывается функция 
FilizeBlock() (см. ниже). 

Доступны также следующие функции:

string RunBlock(string $BlkName)
исполняет и возвращает результат для блока $BlkName. Исполнение блока 
происходит только при первом обращении к нему. Если такого блока не 
существует, возвращает undef. Вместо этой функции предпочтительнее
использовать оператор {{...}} или псевдохэш %Blocks.

string FilizeBlock(string $BlkName)
запускает указанный блок, а результат работы записывает в специальный 
фрейм-файл filizator-а (см. описание filizator-а). Возвращает полный
URL полученного файла. Вместо вызова этой функции предпочтительнее
использовать оператор {{filize:...}} или псевдохэш %Filize.

void Depends(string $url)
рекомендуется применять только в макротэгах, то есть в коде: выполняемом в 
момент расширения и компиляции блока. Сигнализирует кэшу о том, что файл 
зависит от указанного $url, и в случае обновления этого $url необходимо
перекомпилировать весь файл. Пример такого макротэга - тэг вставки 
csv-таблицы.

Блок может в любой момент установить Cookie или сделать Redirect при помощи 
соответствующих функций. Гарантируется, что все то, что он печатает с 
помощью print или echo, не попадет напрямую в браузер, а будет вставлено
в шаблон.



Возможности Filizator-а
-----------------------
Filizator - набор специальных функций, позволяющих строить шаблоны на 
основе фреймовой структуры. Главная из них - FilizeBlock() (вызывается 
оператором {{filize:...}}, рассматриваемым ниже), которая делает 
следующее: запускает блок и сохраняет его вывод в специальной директории, 
"видимой" снаружи - например, в директории /frames. Файлу 
дается уникальное имя, основанное на его контрольной сумме, поэтому, 
если блок не меняется, не меняется и имя файла. Функция возвращает 
полный URL к этому файлу, который может быть вставлен на место параметра 
src тэга <frame>, например:

 ##Output
 <html>
 <head><title>{{Название}}</title></head>
 <frameset  rows="29%,*">
     <frame name=FTop  src={{filize:Верх}} frameborder=1>
     <frame name=FBody src={{filize:Текст}} frameborder=1>
 </frameset>
 </html>

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



Возможные направления модернизации
----------------------------------
1) Не расширять макротэги и {{...}} внутри блоков <? ... ?>, потому что 
   это, вообще говоря, делать действительно нельзя.
2) Сделать защиту для Filize-блоков, подобную авторизации, чтобы можно было 
   безопасно использовать Filizator даже и в скриптах администрирования,
   не опасаясь, что кто-то что-то подсмотрит. Вообще говоря, как это сделать 
   - неясно.
3) Ввести поддержку mod_perl.

Дмитрий Котеров
dkLab, ©1999-2016