Chesher Cat - версия 1.0

Вашему вниманию предлагается статья, посвященная оригинальной библиотеке PHP для работы с шаблонами

[Александр Пащенко (admin@lllit.ru)]

Содержание

    Вместо предисловия

      Метатэги

      Программа вывода

      Как все это работает?

      Библиотечные функции

      Простые приемчики

 

Вместо предисловия

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

  • Хотелось действительно разделить код php и html. То есть, хотелось, чтобы шаблон был полноценным html-файлом, который нормально отображается в браузере и верстать его дизайнер мог бы, видя, что он делает, а не догадываясь о половине.

  • Хотелось получить быстрый и компактный код.

  • Хотелось обойтись минимальным количеством метатэгов и в то же время сделать библиотеку универсальной.

  • Хотелось, чтобы библиотека не требовала никаких внешних пакетов и уживалась с PHP, начиная с версии PHP 4.1

  • Хотелось сделать такой пакет, на освоение которого уходило бы полчаса, а не полмесяца.

  • Хотелось и продолжает хотется, чтобы те, кто его попробует, написали о своих впечатлениях на мыло автора :) Библиотека freeware, - и эта версия, и остальные будут бесплатными и распространяются на условиях лицензии GPL (оригинальный текст этой лицензии Вы можете найти здесь ), - и если она Вам пригодилась, то потратьте, пожалуйста, десять минут на то, чтобы написать о багах и затыках в программах, и о непонятках и ошибках в этом тексте, договорились?

И удачи всем! :)

Метатэги

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

   Метатэги, которые включаются в html-файл, могут иметь два равноправных синтаксиса.
Первый вариант - предпочтительный: они могут оформляться как комментарии html, то есть:

<!--%метатэг%-->

   Предпочтительный он потому, что в этом случае браузер просто их пропускает и html-файл отображается по-человечески. Но если нам, например, надо в метатэге передать атрибут тэга html, вроде адреса картинки, то мы не можем написать: 

<IMG src="<!--%адрес%-->"> 

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


{%метатэг%}

Соответственно, предыдущий пример может выглядеть так: 

<IMG src="{%адрес%}">


   Видно, что в обоих вариантах метатэги начинаются и заканчиваются знаком процента.
Обе формы могут применяться вперемешку, но их, конечно, нельзя комбинировать в пределах одного метатэга, то есть, если Вы напишите что-то вроде "<!--%метатэг%}", то функции библиотеки (да и браузер), Вас не поймут и обидятся.

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

Вот перечень допустимых метатэгов (буду писать только в первой форме). Их всего четыре:

1. Условный оператор:

<!--%if имя%-->...кусок 1...<!--%endif имя%--> 

или

<!--%if имя%-->...кусок 1...<!--%else имя%-->...кусок 2...<!--%endif имя%-->

Здесь и дальше: операторы (if, else и т.д.) - регистронезависимы. То есть можно написать if или IF - кому как нравится. Имена - зависят от регистра!  
   Теперь вернемся к условному оператору. Во всех трех частях имя должно быть одним и тем же. Повторение имени не только упрощает жизнь ленивому автору этой библиотеки :) - оно еще позволяет избежать неоднозначности при вложенных if-ах.
   Разделители между операторами и именами - один или несколько пробелов. Наличие хотя бы одного пробела перед именем обязательно. Пробелы между знаком процента и оператором, а также между именем и знаком процента недопустимы.
Имя здесь - это имя флажка, который может быть установлен или сброшен программой вывода  (о программе вывода - см. ниже). По умолчанию все флажки сброшены, то есть будет выполнена else-часть, если она есть..

   Кусок 1 и кусок 2 - это куски шаблона, которые кроме html-кода могут включать в себя и произвольное количество метатэгов, в том числе и условных операторов. Число уровней вложения не ограничено.

2. Узел:

<!--%node имя%--> ...кусок шаблона... <!--%endnode имя%-->

   Кусок шаблона между этими метатэгами по умолчанию НЕ выводится! Для того, чтобы его включить, используются специальные команды  программы вывода. Подробно об этом ниже.
Кусок шаблона, соответствующий узлу, может содержать кроме html-кода и произвольное
количество метатэгов, в том числе и узлов. Число уровней вложения узлов не ограничено.
   Разделители между оператором ("node" или "endnode") и именем - один или несколько пробелов. Наличие хотя бы одного пробела перед именем обязательно. Пробелы между знаком процента и "node", а также между именем и знаком процента недопустимы.

3. Точечная переменная (оператор "точка"):

<!--%.имя%--> 

   Если программа вывода устанавливает переменную с указанным именем (без точки!), то в то место шаблона, где записан этот метатэг, подставляется значение этой переменной. Если переменная не устанавливается - подставляется пустая строка (строка нулевой длины).
   Между оператором (символом точки) и именами - могут быть, но не обязательно, один или несколько пробелов. Пробелы между знаком процента и точкой, а также между именем и знаком процента недопустимы.

4. Протяженная переменная:

<!--%+имя%--> ...кусок шаблона... <!--%-имя%-->

   Если программа вывода устанавливает переменную с таким именем (без знаков "+" и "-"!), то вместо всей этой конструкции подставляется значение переданной переменной. Если не устанавливает - в вывод включается кусок шаблона между этими метатэгами. Зачем нужны "протяженные" переменные? Для удобства верстки шаблона. Пусть, например, нам нужно подставлять значение в некоторую ячейку таблицы. Если мы воспользуемся "точечной" переменной и напишем ее метатэг в этой ячейке:  

<TD><!--.имя_переменной--></TD> ,

  то, поскольку браузер пропустит наш метатэг (он ведь для него комментарий), то браузер  "схлопнет" эту пустую ячейку. А вот если мы используем "протяженную" переменную:


<TD><!--%+имя_переменной%-->данные ламера Васи<!--%-имя_переменной%--></TD> ,


тогда дизайнер увидит эту ячейку на своем месте с надписью "данные ламера Васи" в ней и верстать, соответственно, уже будет не наощупь.
   Между оператором (символом "+" или "-") и именами - могут быть, но не обязательно, один или несколько пробелов. Пробелы между знаком процента и оператором, а также между именем и знаком процента недопустимы.
  
Протяженная переменная не может содержать вложенных метатэгов. Если мы попытаемся между ее открывающим и закрывающим тетатэгом вставить произвольный метатэг - это не вызовет ошибки, но метатэг не будет обработан. Это позволяет, при необходимости, выводить в html-страницу конструкции вида {%что угодно%}, окружив их метатэгами протяженной переменной.

Программа вывода

   Кроме шаблона (или его имени файла), функциям этой библиотеки передается еще один параметр: массив, в котором записана программа вывода.
   Это неассоциативный массив, каждый элемент которого - строка с одной из команд вывода. Никто Вам, конечно, не мешает использовать и ассоциативный массив :) Просто имена элементов никак не применяются - функция обработки шаблона читает этот массив обычным циклом foreach
   Команд ветвления нет, все команды выполняются строго последовательно, одна за другой. В одной строке записывается одна команда. Пробелы между частями команд недопустимы.
Вот перечень допустимых команд:

1. Присваивание гнезду  переданного значения (оператор "=") :

"имя_гнезда=значение"

     Соответствующему гнезду переменной или гнезду узла присваивается в качестве текущего значения остаток строки команды после знака равенства.  Значение может содержать любые символы, в том числе и "=" - присваивается все, что стоит правее первого встреченного знака равенства. Запись вида "имя_гнезда=" - без правой части равенства - присвоит текущему значению гнезда пустую строку. О том, что такое гнезда, я расскажу ниже и думаю, что Вы согласитесь: все просто и логично. А сейчас закончим перечислять команды.

2. Приписывание к текущему значению гнезда переданной строки справа (оператор ".=") :

"имя_гнезда.=значение"

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

3. Приписывание к текущему значению гнезда переданной строки слева (оператор ":=")  :

"имя_гнезда:=значение"

    Отличается от ".=" только тем, что приписывает не в хвост текущего значения, а перед ним. Этот вариант удобен, если нам нужно формировать какую-то часть html-страницы "снизу вверх".

4. Вывод узла в гнездо (оператор "->" - стрелка)  :

"имя_узла->имя_гнезда"

     Формирует экземпляр узла с текущими установками гнезд и флажков в этом узле, и выводит его в указанное гнездо. Если имя гнезда опущено (команда вида "имя_узла->"), то экземпляр узла выводится в "родное" гнездо, то есть, гнездо с тем же именем. 
     После того, как узел выведен, все гнезда в нем (если они есть) устанавливаются в значения по умолчанию. Установленные флажки условных операторов при этом не сбрасываются! То есть, если флажок однажды установлен или сброшен, все экземпляры узла будут выводиться с этим значением флажка, пока он не будет явно изменен оператором "@" программы вывода.

5. Приписывание к текущему значению гнезда экземпляра узла справа (оператор ".->"):

"имя_узла.->имя_гнезда"

   Действует, как оператор "->", но не заменяет текущее значение гнезда экземпляром узла, а приписывает к его хвосту.

6. Приписывание к текущему значению гнезда экземпляра узла слева:

"имя узла:->имя_гнезда"

    То же, что ".->", но экземпляр узла приписывается перед текущим значением гнезда, а не за ним.

7.Включение и сброс флажка  метатэга условного оператора (оператор "@") :

"имя_флажка@строка" 

   В зависимости от параметра "строка" эта команда включает  (устанавливает) или выключает (сбрасывает) переменную-флажок условного оператора (метатэга {%if имя%}). Если "строка" - это пустая строка (то есть, команда имеет вид "имя_флажка@"), "0", а также "FALSE" или "OFF" в верхнем или нижнем регистрах, то флажок сбрасывается. Любое другое значение параметра "строка" включает флажок.
   По умолчанию все флажки выключены (сброшены), что соответствует выполнению части else условного оператора. 
   Конечно, можно было включить функциональность этого оператора  в оператор присваивания. Но отдельная команда для управления флажком позволяют снизить число ошибок и описок в шаблоне и в программе вывода.

 

Как все это работает?

   Теперь, как обещал, расскажу про гнезда, узлы и команды. Правда, для этого придется коротко объяснить, как работают функции библиотеки.

    Итак, мы передали функции обработки шаблона два параметра: сам шаблон и программу вывода. 
    Функция работает в три этапа:

  • разбирает шаблон (эту операцию принято называть парсингом, а функцию php, которая этим занимается - парсером);

  • полностью разобрав шаблон, она затем последовательно, команда за командой, выполняет программу вывода;

  • после того, как вся программа вывода выполнена, выводит полученный результат в выходную строку.

    Значит, сначала фунция вывода шаблона откладывает программу вывода в сторону и начинает разбирать этот шаблон. Каждый раз, встретив точечную переменную, она отмечает это место, вставляя вместо метатэга точечной переменной штуку, которую я назвал гнездом.  Гнезда - это и есть "улыбка Чеширского кота", которая остается тогда, когда исчезает сам кот. :)

   Пока понятно? Тогда идем дальше... Значит, точечные переменные парсер заменяет гнездами с соответствующими именами, и присваивает этим гнездам пустую строку. То есть, если мы не присвоим точечной переменной никакого значения, при выводе шаблона все будет так же, как если бы этого метатэга вообще не было.

    А что делает парсер, встретив метатэги протяженной переменной? Да то же самое! Разница только в том, что этому гнезду будет присвоено не пустое значение (и по умолчанию, и текущее), а то, что было написано между открывающим и закрывающим метатэгами. И это текущее значение будет использовано при выводе шаблона, если только мы не изменим его в программе вывода.

   Проще всего нам будет считать, что гнездо - это просто некоторый объект, которой имеет имя, значение по умолчанию и текущее значение. Значение по умолчанию - это то, что мы написали в шаблоне. И это же значение устанавливается текущим значением. То есть, если в шаблоне мы напишем:

...какой-то кусок html...{%+v%}Тут был Вася{%-v%}...еще какой-то кусок кода html... ,

то парсер создаст гнездо с именем "v", припишет этому гнезду значение по умолчанию "Тут был Вася" и ту же строку - "Тут был Вася"  сделает текущим значением этого гнезда. В будущем в качестве текущего командами программы вывода можно присвоить гнезду произвольное значение (ведь именно для этого нам и нужны шаблоны, правда? Чтобы можно было в конкретные места html-страницы выводить какие-то переменные).  Вот парсер и втыкает в шаблон гнезда. 

   Заметили? Функции обработки шаблона не нужно запоминать, была там точечная или протяженная переменная. И там, и там она просто воткнет гнездо с тем же именем, которое мы написали в метатэге, и присвоит гнезду  значение по умолчанию и текущее значение. Если потом при выполнении программы вывода встретится команда
 "имя_гнезда=новое_значение",
то она найдет это гнездо и присвоит ему новое текущее значение. Вот и все. :)

   А вот что делает парсер, встретив метатэг узла (возможно, не просто узла, а с вложенными в него подузлами, подподузлами и кучей переменных и условных операторов)? Вы не поверите, но он делает то же самое :)

   Когда ему на глаза в шаблоне попадается узел, парсер целиком выковыривает его оттуда со всем, что в него вложено и откладывает в сторону. А вместо него втыкает в шаблон все то же гнездо!
   Это гнездо ничем не отличается от гнезда точечной переменной. Это просто гнездо - с именем, которым мы назвали наш узел, и присвоенным значением пустой строки - и по умолчанию, и текущим. 
   Парсер никак не помечает, что было на этом месте - переменная или узел. И вместо того, и вместо другого в шаблон вставляется просто гнездо - с каким-то именем и  какой-то парой значений. И ведут себя эти гнезда совершенно одинаково.

   Метатэги условных операторов пока остаются на своих местах и никак не обрабатываются.

   Разобрав шаблон, парсер берется за отложенные узлы и обрабатывает их точно так же, заменяя в них метатэги переменных и подузлов гнездами. Потом он обрабатывает подузлы и т.д.

    В результате разбора  парсер строит массив, в котором каждый элемент имеет имя, соответствующее имени узла, а значение - это результат разбора этого узла. Сам шаблон (то есть, "самый верхний" его уровень) тоже хранится в этом массиве и имеет недоступное нам имя.

    Разберем простой пример.

Давайте представим, что узел с именем a у нас вложен в узел с именем b, то есть, что шаблон у нас выглядит так: 

к1...{%node b%}...к2...{%node a%}...к3...{%endnode a%}..к4..{%endnode b%}...к5 

   Здесь к1 ... к5 - это какие-то куски шаблона. Итак, парсер разбирает этот шаблон.
   Как мы помним, весь шаблон в целом - это тоже узел, но с "неопределенным" именем. Дойдя до метатэга {%node b%}, парсер вырежет из этого "главного" узла  шаблона  весь узел b вместе с вложенным в него узлом a, а вместо вырезанного узла b подставит в шаблон гнездо с тем же именем b. Потом он сохранит разобранный шаблон верхнего уровня в элементе массива узлов,  вызовет себя для разбора узла b, вырежет из него вложенный узел a и вместо узла a подставит в b гнездо a. В результате работы функция построила массив из трех элементов: "главного" узла, подузла b и a:

nodes[]:      к1 ... [гнездо b] ... к5      - "главный" узел

nodes['b']:   к2 ... [гнездо a] ... k4      - узел b

nodes['a']:   к3                                    - узел a

   Итак, в результате разбора мы получаем не дерево(!), а просто массив узлов, включая весь шаблон как "главный" узел :) Там, где в них были вложенные узлы, находятся гнезда. Еще раз обратите внимание: никакой иерархии нет! Это - не "матрешка". Это - просто массив. Просто набор никак не связанных между собой именованных элементов!

   И вот теперь функция обработки шаблона берет в одну руку этот массив, а в другую - ту программу вывода, которую мы ей передали :) и начинает последовательно, команда за командой, эту самую программу выполнять.

   Нам сейчас нужно понять, как работают всего две  команды: "=" и  "->". Остальные команды - это просто "вариации на тему..." :)

   Если Вы поняли все сказанное до сих пор, то Вы уже знаете почти все.
Встретив команду "имя_гнезда=значение", функция библиотеки начинает искать во всех элементах нашего массива гнездо с переданным в команде именем (на самом деле все делается эффективней, но нам ведь главное понять принцип, правда?)
   Так вот, она просматривает элементы нашего массива, пока в каком-нибудь из них не найдет гнездо с таким именем. Это место может быть только одно - потому мы и потребовали уникальности имен в метатэгах!
   Когда гнездо найдено - ему присваивается переданное значение в качестве текущего
   Обратите внимание еще раз: неважно, что было в исходном шаблоне на этом месте - переменная или узел. Команда "имя=значение" просто найдет гнездо и присвоит ему значение.
   А что, если гнезда с таким именем нет? Тогда функция обработки вернет нам информацию об ошибке в шаблоне или программе вывода.

    Согласитесь, до сих пор нет ничего сложного, верно? И вот теперь мы, наконец, подошли к самому главному: сейчас я расскажу, как работает вторая команда, то есть,
"имя_узла->имя_гнезда". Поняв это, Вы поймете действительно все :)

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

    Гнездо функция нашла, теперь ищет узел. Это совсем просто: в массиве должен быть элемент с таким именем. 
    Итак, функция берет этот узел с теми значениями гнезд в нем, которые мы на данный момент установили (вы ведь помните, что команды программы вывода выполняются строго последовательно, одна за другой. В нашей программе вывода до этого оператора "->" мы могли присвоить командами "=", "->" или другими текущие значения каким-то гнездам этого узла). Вот функция и берет наш узел (кусок шаблона между метатэгами {%node имя%} и {%endnode имя%}), в котором на тех местах, где в шаблоне были записаны метатэги переменных и подузлов, сейчас стоят текущие значения соответствующих гнезд. 

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

   И вот теперь, когда функция выполнила условные операторы в этом узле, она присваивает то, что получила в результате, тому гнезду, которое она первым делом нашла. То есть, именно эта команда - "->" - выводит подузел в узел.
   И после этого выполняется еще одно действие: все гнезда, которые есть в этом подузле (в том, который выводится, а не в том, куда мы выводим!), итак, все гнезда в выведенном подузле сбрасываются в значения по умолчанию. Это значит, что текущим значениям всех гнезд на местах точечных переменных и узлов присваивается пустая строка, а текущим значениям гнезд протяженных переменных присваивается то значение, которое записано в исходном шаблоне.
   Это заключительное действие - сброс всех гнезд в их значения по умолчанию - иначе все равно нам пришлось бы делать вручную отдельными командами.

   Последнее, на что еще нужно обратить внимание в команде "имя_узла->имя_гнезда", это вот что: команда позволяет вывести узел в любое гнездо. Однако в 99% случаев нам нужно вывести его в "родное" гнездо, то есть, в то самое место шаблона, где он был записан. Это и делает сокращенная форма команды - строка "имя узла->".

   Мы уже говорили, что функция вывода шаблона работает в три этапа: сначала полностью разбирает шаблон, затем выполняет программу вывода, и уже после всех этих действий выводит шаблон в целом в выходную строку.
   Для того, чтобы запомнить, при каких параметрах выводится шаблон в целом, мы можем представить, будто функция просто нахально приписывает в конец переданной ей программы вывода еще одну команду: "главный_узел->", которая выводит шаблон как "главный" узел со всеми текущими на этот момент значениями гнезд и флажков в нем в выходную строку.

   Запутались немного? Не страшно, давайте повторим на простых примерах идею работы с библиотекой.

   Пусть у нас есть такой шаблон:

<html><body>Нello <!--%node a%-->, Vasya <!--%.f%--><!--%endnode a%--> ! <body></html>

   Сначала обработаем его с пустой программой вывода, то есть, вообще не будем передавать функции обработки шаблона массив с программой вывода.
   Что при этом выведется? Мы сказали, что в конец любой программы вывода функция приписывает еще одну команду - вывод "главного" узла в выходную строку. Значит, на самом деле наша программа вывода будет не пустой, а состоящей из этой единственной команды.

   Теперь давайте посмотрим на шаблон. Он состоит из "главного" узла такого вида:

<html><body>Hello [гнездо вложенного узла "a"] ! <body></html>,

   и вложенного узла по имени a, который содержит точечную переменную f:

, Vasya [гнездо точечной переменной "f"] 

   Что выведет наша пустая программа? Итак, на самом деле она будет содержать единственную команду "главный_узел->". Эта команда возьмет наш главный узел с текущими на момент ее выполнения установками (а парсер ведь присвоил гнезду и значением по умолчанию, и текущим значением пустую строку, то есть строку нулевой длины!), и выведет этот главный узел в выходную строку. Наша выходная строка в результате будет иметь вид:

<html><body>Hello  ! <body></html>

   Вот то, о чем я рассказывал с самого начала: узлы вырезаются парсером из шаблона и не выводятся "сами собой" при выводе "главного" узла. Вместо узлов выводятся значения соответствующим им гнезд в "главном" узле; в данном случае - пустая строка. Чтобы вывелся узел - нужно сделать это явным образом вручную, добавив в программу вывода команду "->".
   Сейчас Вам это наверняка кажется ненужным усложнением. Но когда Вы начнете пользоваться библиотекой, то убедитесь, что такой подход позволяет без всякой путаницы работать с очень сложными шаблонами, как угодно перекраивать выводимую страницу "на лету", не только вырезая или вставляя в нее куски, но выводя их командой "имя_узла->имя_гнезда" в разные места страницы в зависимости от исходных данных.

   Итак, продолжим разбирать наш пример. Как же нам поздороваться конкретно с Васей? Уже понятно: для этого нужно включить в нашу программу вывода команду, выводящую наш узел по имени "a" в его "родное" гнездо. То есть, если массив с нашей программой вывода мы назовем, например, coms, то до того, как вызвать функцию библиотеки, мы должны включить в него один-единственный элемент:

$coms[]="a->";

   Эта команда заставит библиотечную функцию взять вложенный узел a с его текущими установками, - то есть, с пустой строкой вместо точечной переменной f - и воткнуть его в качестве текущего значения элемента [гнездо вложенного узла "a"] нашего "главного" узла.
   После этого "невидимая" команда вывода, которая (мы помним!) добавляется в конец каждой программы, возьмет "главный" узел с текущими установками (то есть, с текущим значением элемента [гнездо вложенного узла "a"], равным ", Vasya " ) и выведет этот главный узел в выходную строку:

<html><body>Нello , Vasya  ! <body></html>

   Когда мы наслаждаемся результатом, к нам прибегает перепуганный заказчик и ставит нам бутылку пива. "Простите," - упрашивает он, - "вышло так, что на нашем сайте есть один крутой юзер по фамилии Пупкин, которого обязательно надо поприветствовать торжественно и полностью!"

   "Раз плюнуть!" - говорим мы ему и добавляем в нашу программу вывода еще одну команду, которая воткнет фамилию крутого Васи вместо мудро и прозорливо предусмотренной нами в шаблоне точечной переменной "f".
   Мы помним, что команда "->" выводит узел с его текущими установками, поэтому мы до нее записываем команду присваивания:

$comm[]="f=Pupkin";
$coms[]="a->";

   Проверяем: первая команда присвоит переменной f в узле a значение "Pupkin". Узел примет вид:

, Vasya [Pupkin]

   Квадратных скобок на самом деле в узле нет - мы здесь просто отметили ими для наглядности гнездо точечной переменной.

   Вторая команда присвоит гнезду  [гнездо вложенного узла "a"] нашего "главного" узла текущее значение узла a, и (что неважно сейчас, но станет очень важным в других случаях) после этого всем гнездам в узле "а" (в нашем случае - гнезду точечной переменной f) присвоит значение по умолчанию! То есть, в результате работы этой команды главный узел станет выглядеть так:

<html><body>Нello [, Vasya Pupkin] ! <body></html>

   Тут мы опять выделили квадратными скобками гнездо вложенного узла a, реальный вывод никаких квадратных скобок содержать не будет. А узел a после выполнения этой команды начнет опять выглядеть:

, Vasya []

так как точечной переменной f в качестве текущего значения будет назначено ее значение по умолчанию - пустая строка.

   И, наконец, третья, "невидимая" команда программы вывода выведет подготовленный главный узел в выходную строку:

<html><body>Нello , Vasya Pupkin ! <body></html>

   Открываем пиво, сидим и кайфуем... В это время открывается дверь и вваливается заказчик весь в слезах и с пятью бутылками пива. "Спасите, Христа ради!" - Умоляет он. - "Я раскрутил сайт и теперь крутых Вась у нас ну просто завались! Я могу передавать Вашему php-скрипту массивчик по имени names с их фамилиями и мне надо, чтобы после Hello, Vasya они перечислялись через пробел."

   Поскольку мы настоящие программисты, сначала берем пиво и обещаем все сделать, а потом уже думаем как.
   Конечно, этот список можно сформировать в нашем php-скрипте и передать шаблону вместо фамилии "Pupkin". Но нам ведь интересно разобраться с библиотекой. Соображаем, нельзя ли чего-то сделать ее средствами. Посмотрев на перечень команд, мы видим даже два способа: можно или в цикле присваивать командой "=" точечной переменной f из нашего примера очередную фамилию и потом каждый раз выводить узел a в соответствующее ему гнездо командой не "->", а ".->", которая будет при каждом применении приписывать гнезду узла a в нашем "главном" узле очередного Васю из переданного нам заказчиком массива names :

   foreach ($names as $n) {
        $coms[]="f=".$n;
        $coms[]="a.->";
   }

   Если в массиве names у нас три фамилии - Pupkin, Popkin и Papkin, результат будет выглядеть:

<html><body>Нello , Vasya Pupkin , Vasya Popkin , Vasya Papkin ! <body></html>

   Либо мы можем сделать по-другому: командами ".=" загнать в точечную переменную f весь список и после этого один раз вывести узел a в его гнездо. При этом между фамилиями нам надо не забыть поставить пробелы, а для пущей красоты заключим перечень в круглые скобки:

$coms[]="f.=(";
foreach ($names as $n)  {
     $coms[]="f.=".$n." ";
}
$coms[]="f.=)";
$coms[]="a->";

В результате получаем:

<html><body>Нello , Vasya (Pupkin Popkin Papkin) ! <body></html>

   Сидим. Балдеем. Пьем пиво.
   Учебный пример на этом можно и закончить. А если это жизнь? Значит, в этот момент опять открывается дверь и в нее вползает рыдающий заказчик, который волочет за собой три ящика пива. "Помогите чем могите!" - вопит он. - "Я продал сайт новому русскому Феде и здороваться надо теперь только с ним. А шаблон править некому, потому как дизайнеру я поставил пиво еще вчера и он теперь под столом в глухом офф-лайне. Выручайте! Можно что-то сделать, не меняя шаблона?"

   Ну, что ж... Смотрим на шаблон. То есть, конечно, сначала берем пиво, а потом смотрим на шаблон. Вот наш главный узел:

<html><body>Hello [гнездо вложенного узла "a"] ! <body></html>

   В нем есть гнездо узла a. Что это такое? Обычное гнездо. Кто сказал, что в него можно выводить только узел a? В него, как и в любое другое гнездо, можно вывести все, что угодно. А угодно нам, понятно, вывести в него: "Fedya".

   Нашего заказчика осчастливит программа вывода, состоящая из одной-единственной команды:

$comm[]="a=Fedya";

   Проверяем результат:

<html><body>Hello Fedya ! <body></html>

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

    Мы не будем разбирать отдельно применение метатэгов условных операторов. Просто запомним: команды вывода узла ("->", ".->" или ":->") выводят узел с текущими установками. Это значит, что, во-первых, используются все текущие значения всех гнезд в этом узле, а во-вторых, что если в узле есть условные операторы, то выводятся if- или else- части, в зависимости от установки флажков на момент выполнения этой команды. Иначе говоря, порядок работы всегда один и тот же: сначала мы подготавливаем узел, то есть задаем значения всех его нужных гнезд (в том числе, возможно, выведя в них какие-то вложенные узлы) и устанавливаем нужные флажки для порезки этого узла условными операторами, а потом уже полностью подготовленный узел выводим в гнездо.
   Если при этом не забывать еще, что все команды программы вывода выполняются строго последовательно, одна за другой, и что весь шаблон в целом выводится в выходную строку в самую последнюю очередь, после выполнения всех остальных команд, то никакой путаницы не возникнет.

Библиотечные функции

   Итак, все, что есть в пакете Chesheer Cat 1.0 - это четыре метатэга и семь команд. Согласитесь, что это немного. Последнее, что еще осталось узнать - в какие функции надо засунуть эти метатэги и команды, чтобы получить на выходе html-страницу.

   Тут еще проще: на самом деле все делает одна функция chesheer. В нее передается шаблон и массив с программой вывода, и она возвращает или обработанный шаблон, или сообщение об ошибке.
   Вся наша солидная библиотека - это всего-навсего одна функция :) Поэтому больше запоминать ничего не придется, кроме разве что ее имени и параметров. Приятный сюрприз под конец, правда? ;)

   Итак, вот как выглядит эта функция:

mixed chesheer ($pattern string, $programm mixed)

   Для ее использования необходимо включить в программу php файл chesheer.php:

require_once ('chesheer.php');

mixed означает, что функция chesheer может возвращать значения различных типов. Если она отработала нормально, то возвращается строка, которая содержит результирующую html-страницу. Если функция обнаружила ошибки в шаблоне или программе вывода - она вернет массив из двух элементов: ['err_num'] содержит числовой номер ошибки;  ['err_add'] - дополнительную информацию об ошибке (как правило, часть шаблона с ошибочным метатэгом или ошибочную команду программы вывода). Обратите внимание: словесное описание ошибок содержится в отдельных файлах в зависимости от используемой на сервере кодировки. Если Вы хотите выводить словесную расшифровку ошибок - влючите в свою программу один из прилагаемых файлов ch_error_xxx.php: 

ch_error_koi8r.php содержит массив $error c описанием ошибок в кодировке koi8-r.
ch_error_win.php - то же самое в формате cp-1251.

   Если Вы работаете с какой-либо иной кодировкой - вот, на всякий случай, расшифровка возвращаемых ошибок (содержимое файлов ch_error_xxx.php):

$error[4001]='Незакрытый метатэг в шаблоне.';
$error[4002]='Неправильный метатэг в шаблоне.';
$error[4003]='Метатэг с таким именем уже определен в шаблоне.';
$error[4004]='ELSE без IF в шаблоне.';
$error[4005]='Повторное задание ELSE-части в шаблоне.';
$error[4006]='ENDIF без IF (попытка закрыть неоткрытый условный оператор в шаблоне).';
$error[4007]='Повторное задание ENDIF (условный оператор с таким именем уже закрыт).';
$error[4008]='Ошибка в шаблоне: узел не закрыт.';
$error[4009]='Пустой метатэг в шаблоне.';
$error[4010]='Отсутствует имя метатэга в шаблоне.';
$error[4011]='Не закрыта протяженная переменная (не найден метатэг {%- имя%} в шаблоне)';
$error[4012]='Неправильный метатэг или попытка закрыть неоткрытый метатэг в шаблоне.';
$error[4013]='Незакрытый условный оператор (не найден тэг ENDIF для указанного имени).';
$error[4101]='Пустое имя в команде программы вывода.';
$error[4102]='Отсутствует оператор в команде программы вывода.';
$error[4103]='Гнездо с указанным в программе вывода именем в шаблоне отсутствует.';
$error[4104]='Условного оператора с указанным в программе вывода именем в шаблоне нет.';
$error[4105]='Узел с указанным в программе вывода именем в шаблоне отстутствует.';
$error[4106]='Неизвестный оператор в программе вывода.';

   То есть, чтобы узнать, была ли ошибка, нам нужно проверить тип переменной, которую вернула функция chesheer. Например, это можно сделать так (здесь же показан вывод расшифровки ошибки. Подразумевается, что мы включили соответствующий файл ch_error.php):

$out=chesheer($pat, $prog);
if (is_array($out))
{     echo 'шаблон накрылся медным тазом! <br>';
       echo "Ошибка: '.$out['err_num'].' - '.$error[$out['err_num']].'<br>';
       echo htmlspecialchars($out['err_add']);
       exit;
}

     Обратите внимание - это важно: в переменной $out["err_add"] у нас находится кусок шаблона. Более чем вероятно, что он содержит спецсимволы html, например, "<". Поэтому, если мы выводим $out["err_add"] в браузер, применение htmlspecialchars() или htmlentities() обязательно всегда, иначе мы просто ничего не увидим!

     Функция принимает два параметра. Первый - это строка с шаблоном. Так как наши шаблоны вполне "смотрибельны" в браузере (в отличие от шаблонов многих других библиотек), то логично держать их в файлах *.htm и загружать оттуда, например, функцией file_get_contents() (она доступна начиная с версии php 4.3.0) или стандартной последовательностью функций. Конечно, никто не мешает держать шаблоны в базе или еще где-нибудь.

     Второй параметр - массив с программой вывода. Но почему тогда я в описании функции тогда написал не $programm array, а $programm mixed? Только чтобы подчеркнуть, что этот массив может быть и пустым, то есть не содержать ни одного элемента. Это будет нечасто (иначе зачем нам вообще шаблон и php? Можно ведь просто положить на сервер htm-файл.) Но на реальном сайте наверняка будут нештатные ситуации, когда юзер каким-то образом попадает, скажем, на страницу с пустым телом - ну, например, на отсутствующую форумную тему. Так или иначе,  имейте в виду: функция не обижается на пустую программу вывода, и даже на отсутствие второго параметра вообще.

Простые приемчики

  •    Самый распространенный случай: сайт состоит из страниц, оформленных одинаковым образом, то есть, у них одна и та же шапка, подвал, левая панель с меню, крутилка баннеров и т.д., а разное только тело страницы. Как быть в таком случае? Решение очевидно: делаем "главный" шаблон со всем оформлением, а на месте тела страницы ставим метатэг переменной.
       Для каждой страницы рисуем шаблоны только с телом.
       Скрипт php, который должен вывести конкретную страницу, сначала подгружает шаблон этой страницы, формирует нужную программу вывода и вызывает библиотечную функцию, которая обработает этот шаблон и вернет в строке результат. После этого грузим "главный" шаблон и пишем программу вывода, которая, может быть, как-то его попутно модифицирует, но кроме того присваивает метатэгу на месте тела наш уже обработанный шаблон. Вызываем еще раз библиотечную функцию и получаем результат. Всё :)
        Альтернативный путь решения этой проблемы так же прост: делаем один-единственный шаблон со всей стандартной частью страниц, и в нем на месте тела записываем ряд узлов, в каждом узле - тело конкретной страницы. Дизайнер при отладке шаблона увидит полностью оформленную страницу с длинным телом - все тела страниц друг над другом. А мы потом в скрипте работы с конкретной страницей просто будем готовить нужный нам узел и выводить его затем в "родное" гнездо. Так как "сами собой" узлы не выводятся, то никакого мусора от ненужных нам в данный момент узлов в готовой html-странице не будет.

  •     Обратите внимание на то, какое значение параметра включает, а какое выключает флажок в команде "@" программы вывода. Очень частый случай: передан массив и нам надо или вывести в таблице его элементы, или вместо таблицы написать, например, "элементов нет", если массив пустой. Соответствующий кусок шаблона может выглядеть примерно так:
    {%if z%}<TABLE>{%node r%}<TR><TD>{%.d%}</TD></TR>{%endnode r%}</TABLE>{%else z%}элементов нет{%endif z%}
      
    Здесь в if-части у нас таблица, в которую вложен узел r, соответствующий одной строке. Мы можем в нашем скрипте в цикле foreach по элементам массива (пусть имя массива у нас arr) присваивать очередной элемент гнезду точечной переменной d и выводить сформированный узел в его "родное" гнездо. А "включающие" и "выключающие" флажок значения определены таким образом, что мы можем управлять им, просто передавая число элементов массива. То есть, программа вывода будет выглядеть примерно так:

    foreach ($arr as $element) {
         $prog[]="d=".$element;
         $prog[]="r->";
    }
    $prog[]="z@".count($arr);

    Ненулевое значение элементов включит флажок z, заставляя при последующем выводе всего этого узла сработать if-часть условного оператора. При нулевом значении флажок будет сброшен и при последующем выводе будет использована else-часть - строка "нет элементов".

Исходные тексты шаблонов можно найти по адресу: