В этой статье я хотел бы рассказать о применении шаблонов в программировании для WEB.
Материалов об этом довольно много в сети, но почему-то просматривая всевозможные бесплатные или демо скрипты, написанные как профессионалами, так и любителями, я практически не встречаю среди этих текстов использование шаблонов. В связи с этим обстоятельством я решил написать данную статью. Может быть прочитав этот материал вы откроете для себя, пока еще не понятное слово template (шаблон), или расширите свои знания и начнете более активно использовать их на практике. В любом случае думаю материал полезный для всех кто интересуется разработкой WEB-приложений. Для программирования я буду использовать язык Perl, гибкий и мощный, он идеально подходит для работы с текстом (а шаблоны и есть обычный текст), а также адаптированный под огромное количество платформ, мне он кажеться наиболее удобным, хотя поклонники PHP, тоже могут для себя что-то открыть из этой статьи.

[INTRO]

Прежде чем начать писать эту статью, я долго пытался представиь себе форму изложения материала. И как мне показалось нашел ее - написание мощного приложения с использованием шаблонов, а конкретней - гостевой книги, с разграничением доступа на пользователей, модераторов и администраторов, с использованием SQL для хранения данных и все в этом роде. Я активно взялся за статью, но "чем дальше в лес, тем больше дров" наломал :-), в общем, когда статья близилась к завершению понял что читать ее будет абсолютно невозможно человеку, чей уровень знаний perl средний и ниже. А читать профессионалу это было бы неинтересно, по причине того что все это ему должно быть известно. Я отказался от разработки громоздкого интерактивного приложения и решил значительно упростить задачу, и более наглядно показать работу с шаблонами.

[PART I]

А для чего?

Первый и наверно вплоне резонный вопрос - а зачем мне это надо? Я ведь до этого свободно обходился безо всяких замут и писал себе свои:

[CODE]

#!/usr/bin/perl -w

print "Content-Type: text/html\n\n";

print qq(<B><CENTER>HEllo world!</CENTER></B>);

[/CODE]

И не парился...
Но если у вас более сложный скрипт :-), например какой-нибудь интерфейс работы с базами данных, или система электронного документооборота, или даже просто гостевая книга для которой вы бы хотели сделать поддержку скинов? Как бы вы решили эту задачу? В случае со скинами - в зависимости от выбора пользователя грузили бы разные страницы HTML? Или все бы это вписали бы в свой скрипт, как вложенный документ (конструкция print <<HTML; ...)? Не думаю что это красивое решение. Красивое решение в данном случае будет - использование шаблонов.
Работать с шаблонами на Perl, можно как всегда - "более чем одним способом". Можно использовать модуль HTML::Template (о его использовании я напишу в [PART II]), а можно написать свою маа-а-аленькую функцию, которая будет отвечать нашим скромным потребностям. Модуль HTML::Template предоставляет разработчику широкий выбор методов, это и вложенные шаблоны, и конструкции IF,ELSE, и циклы, но о нем - позже.

Я напишу скрипт, который будет формировать html-страницу, в зависимости от выбора пользователем понравившегося ему скина (в скрипте я не буду использовать работу с cookie-файлами, для запоминания выбора пользователя, так как считаю этот вопрос опять же выходящим за рамки темы).
Наберите следующий HTML-код, и сохраните файл под именем skin.htm (имя может быть любым, как и расширение файла)

[HTML]
<html>
<head>
<title>TMPL_TITLE</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<link href="TMPL_CSS" rel="stylesheet" type="text/css">
</head>

<body>
<table width="100%" height="287" border="0">
<tr>
<td colspan="2">
<div align="center">
<img src="TMPL_IMG" border="0">
</div>
</td>
</tr>
<tr>
<td width="14%" align="center" valign="top"><p><a href="http://localhost/some.cgi?param=drink">1. Пойти упиться</a></p>
<p><a href="http://localhost/some.cgi?param=marry">2. Пойти жениться</a></p>
<p><a href="http://localhost/some.cgi?param=love">3. Пойти влюбиться</a></p>
<img src="TMPL_BANNER" width="75" height="75" border="0">
<p>&nbsp;</p></td>
<td width="86%" align="center">
Выберите skin из ниспадающего меню :-)
<form method="post" >
<select name="select" size="1">
<option value="1">Horror</option>
<option value="2">Lucky</option>
<option value="3">Crazy</option>
</select>
<input type="submit" value="Выбрать">
</form></td>
</tr>
</table>
</body>
</html>
[/HTML]

Только что мы создали шаблон, или template, на основе которого скрипт будет формировать нашу страничку. Дизайн конечно ужасен, это я и сам(!) понимаю :-)). Некоторые тэги в этом файле вам покажуться странными (я их выделил жирным шрифтом), но читайте дальше и вы поймете их предназначение. Шаблон есть, теперь напишем сам скрипт, из-за которого мы замутили этот skin.htm.
Наберите следующий код и сохраните под именем skin.cgi

[CODE]

#!/usr/bin/perl -w

use strict;# включаем строгий контроль синтаксиса
use CGI qw(param header);# подключаем модуль (импортируем только используемые нами методы), 
для уменьшения объема кода, вы можете создать стандартые функции обработки POST и GET запросов, 
чтобы обойтись без него. 
Хотя, его применение, меня в 8 случаях из 10 устраивает.

my (%Horror,%Lucky,%Crazy);# объявляем хэши в которых будем хранить параметры соответствующих скинов.
my $path="http://localhost/files";# путь к включаемым файлам и картинкам.
my $rand=int(rand(4));# выбираем рандомное число, совпадающее с именем баннера (это для баннерокрутилки :-)
# Заполняем хэши
%Horror=('TITLE' => "HORROR-SKIN",
         'CSS' => "$path/horror.css",
         'IMG' => "$path/horror.jpg",
         'BANNER' =>"$path/$rand.jpg");# отображаем рандомный баннер
%Lucky=('TITLE' => "LUCKY-SKIN",
         'CSS' => "$path/lucky.css",
         'IMG' => "$path/lucky.jpg",
         'BANNER' =>"$path/$rand.jpg");
%Crazy=('TITLE' => "CRAZY-SKIN",
         'CSS' => "$path/crazy.css",
         'IMG' => "$path/crazy.jpg",
         'BANNER' =>"$path/$rand.jpg");

# Функция обработки шаблона
sub template {
my $options;#объявляем переменную в которой будем хранить ссылку на хэш скина
$options=shift;# получаем ссылку на нужный хэш (shift - возвращает первый элемент массива @_)
open(MAIN,"<","../../htdocs/files/skin.htm"or return "Не могу открыть файл шаблона - проверьте пути к нему";
# открываем наш шаблон либо возвращаемся с сообщением об ошибке

local $/$_=<MAIN> }# в этой строке используем локализованный разделитель записей внутри нового блока
# для импортирования целого файла в пространство переменной $_
close(MAIN);# закрываем файл шаблона
for my $key(keys %$options)
# обрабатываем переменную $_, делая замены для соответствующих полей, выделенных нами в шаблоне skin.htm.
  # теперь я думаю вам стало понятен синтаксис этих записей в шаблоне.
        s/TMPL_$key/$options->{$key}/;# для получения значения ключа хэша используем "->" оператор, 
так как $options - является ссылкой на хэш, и требует разыменовывания, если бы мы использовали действительный хэш, 
то использовали бы запись $options{$key}

}
return $_;# возвращаем обработанный HTML
}

########## Тело скрипта ######################

if(param('select'eq '2') {
print header(-charset=>'windows-1251');
print template(\%Lucky);# скин Lucky
exit 0;
}

if(param('select'eq '3') {
print header(-charset=>'windows-1251');
print template(\%Crazy);# скин Crazy
exit 0;

else {# по умолчанию выводим скин Horror
print header(-charset=>'windows-1251');# выводим заголовок
print template(\%Horror);# по умолчанию выводим скин Horror (передаем ссылку на хэш в функцию)
}

__END__

[/CODE]

Я специально отделил комментариями тело скрипта, чтобы показать, как удобно работать (а главное компактно и прозрачно), с шаблонами. Все что было описано до # Тела скрипта #, можно было вынести с отдельный файл + наполнение хэшей автоматизировать, забирая их параметры из dbm-файла, или парся обычный txt.
А теперь представьте себе код скрипта, реализующий тоже самое, но без использования шаблонов. Он бы был гораааздо более объемистым :-). Плюс мы не навязчиво прикрепили баннерокрутилку к нашей страничке :-). И помимо компактности кода, мы получили куда более гибкий скрипт, нежели скрипт содержащий включенные документы HTML. Чем же он гибок? Во-первых, мне никогда не нравилось мешать программный код и дизайнерскую часть. За программой должна оставаться только логика, но не вывод дизайна. Например, над этой страничкой со скинами работали вы и ваш друг - вэб-дизайнер. Он ничего не понимает в Perl, но круто рубит в хтмл, CSS и т.п. Для того чтобы изменить дизайн он должен создать новую страничку, отдать ее вам, вы ее втисните в ваш скрипт (в случае использования включенных документов). Если вы используете шаблон, то вы можете дать ему досутп к фтп, он будет только перезаливать файлы шаблонов и тут же просматривать результат. Скрипт будет по прежнему работать так же как и в начале. Ему абсолютно все равно какой HTML-код вы в него запихиваете. Или рассмотрим пример когда вам необходимы SSI включения, но ваш хостер запретил доступ к SSI, шаблоны являются прекрасной альтернативой технологии SSI.
Как правило шаблоны представляют собой небольшое кол-во информации, поэтому их вывод не занимает много процессорного времени и памяти.
В первом примере есть один недостаток, довольно существенный. В случае если в вашем шаблоне имеется параметр повторяющийся несколько раз, то замена будет произведена только при первом вхождении этого параметра. Например, TMPL_URL - представляет собой url вашего сайта, и в шаблоне эта метка встречается более 1 раза, замена будет произведена только в первом случае, так как функция template подставляет поочередно ключи хэша, и не возвращается к уже пройденным ключам. Можно усовершенствовать нашу функцию, но лучше давайте перейдем к рассмотрению такого замечательного модуля для работы с шаблонами, как HTML::Template.

[PART II]

HTML::Template - модуль доступен для скачивания на сайте http://html-template.sourceforge.net или в CPAN. Также он приаттачен к этой статье. Его подключение к скрипту элементарно, достаточно поместить его в одну директорию (или поддиректорию) с вашим скриптом, и указать в скрипте директиву use lib qw(.) # в случае если модуль лежит в той же директории что и скрипт, и модуль будет доступен для использования в скрипте. Если вам нехватает возможностей функции рассмотренной в предыдущем примере, читайте дальше - возможно этот модуль решит ваши проблемы. Мы же рассмотрим кое-какие полезные методы этого модуля на том же примере страницы с разными скинами. Для этого перепишем наш предыдущий пример с использованием HTML::Template и немного расширим его функциональность.
Создадим новый шаблон, лишь немного отличающийся от шаблона из предыдущего примера.
Назовем файл skin-tmpl.htm и напишем в него следующий html-код

[HTML]
<html>
<head>
<title><TMPL_VAR NAME=TITLE></title>
<meta http-equiv="Content-Type" content="text/html; charset=<TMPL_VAR NAME=CHARSET>">
<link href="<TMPL_VAR NAME=CSS>" rel="stylesheet" type="text/css">
</head>

<body>
<table width="100%" height="287" border="0">
<tr>
<td colspan="2">
<div align="center">
<img src="<TMPL_VAR NAME=IMG>" border="0">
</div>
</td>
</tr>
<tr>
<td width="14%" align="center" valign="top"><p><a href="<TMPL_VAR NAME=URL>/param=drink">1. Пойти упиться</a></p>
<p><a href="<TMPL_VAR NAME=URL>/param=marry">2. Пойти жениться</a></p>
<p><a href="<TMPL_VAR NAME=URL>/param=love">3. Пойти влюбиться</a></p>
<img src="<TMPL_VAR NAME=BANNER>" width="75" height="75" border="0">
<p>&nbsp;</p></td>
<td width="86%" align="center">
Выберите skin из ниспадающего меню :-)
<form method="post" >
<select name="select" size="1">
<TMPL_LOOP NAME=CHOOSE>
<option value="<TMPL_VAR NUM>"><TMPL_VAR NAME></option>
</TMPL_LOOP>
</select>
<input type="submit" value="Выбрать">
</form></td>
</tr>
</table>
</body>
</html>
[/HTML]


Как вы должны были заметить, объявление меток в шаблоне изменилось с TMPL_SOMEKEY на <TMPL_VAR NAME="SOMENAME">, а также появились конструкции типа <TMPL_LOOP NAME=""></TMPL_LOOP>. Если о первых более менее понятно, то о последних скажу отдельно. Эта конструкция сообщает скрипту о начале и завершении цикла, в процессе исполнения которого, скрипт будет заполнять html-код, содержащийся между этими конструкциями, элементами массива ассоциированного с этой конструкцией. Пока все это еще не совсем понятно, думаю пример скрипта внесет ясность. Назовем скрипт skin-tmpl.cgi

[CODE]

#!/usr/bin/perl -w

use strict;
use lib qw('modules');# ищем модуль HTML::Template в папке modules (которая должна быть расположена в текущей)
use CGI qw(param header);
use HTML::Template;# подключаем модуль

#####################################
my $skin=param('select') || 1;# принимаем параметры из формы или присваиваем дефолтное значение 1
$skin=~ s/[^1-3]//;# фильтрация переменных (возможны значения от 1 до 3, все остальное удаляется)
$skin=1 unless($skin=~/[1-3]/);# если после фильтрации переменная не содержит ничего полезного :-) - присваиваем дефолт
#####################################
my $options=get_skin($skin); # инициализируем переменную ссылкой на нужный хэш
my $choose=get_list();# инициализируем переменную ссылкой на массив хэшей
########## формируем шаблон ################
my $template=HTML::Template->new(
             filename => "skin-tmpl.htm",# имя файла шаблона
             path     => "../../htdocs/files" # путь к файлу шаблона
             );

$template->param(TITLE    => $options->{'TITLE'}); # делаем подстановки в шаблоне
$template->param(CHARSET  => $options->{'CHARSET'});
$template->param(CSS      => $options->{'CSS'});
$template->param(URL      => $options->{'URL'});
$template->param(IMG      => $options->{'IMG'});
$template->param(BANNER   => $options->{'BANNER'});
$template->param(CHOOSE   => $choose);# соотносим блок TMPL_LOOP со ссылкой на массив хэшей
#######################################
###### выводим на печать шаблон #######
print header(-charset=>$options->{'CHARSET'});
print $template->output();
####################################### функции ##################################
sub get_skin {
my $param=shift;# принимаем параметр для определения запрошенного хэша
### далее код почти целиком заимствован из предыдущего примера
my (%Horror,%Lucky,%Crazy);# объявляем хэши в которых будем хранить параметры соответствующих скинов.
my $path="http://localhost/files";# путь к включаемым файлам и картинкам.
my $rand=int(rand(4));# выбираем рандомное число, совпадающее с именем баннера (это для баннерокрутилки :-)
# Заполняем хэши
%Horror=('TITLE'     => "HORROR-SKIN",
         'CSS'       => "$path/horror.css",
         'IMG'       => "$path/horror.jpg",
         'CHARSET'   => "windows-1251",
         'URL'       => "http://mazafaka.ru",
         'BANNER'    => "$path/$rand.jpg");# отображаем рандомный баннер
%Lucky=('TITLE'      => "LUCKY-SKIN",
         'CSS'       => "$path/lucky.css",
         'IMG'       => "$path/lucky.jpg",
         'CHARSET'   => "windows-1251",
         'URL'       => "http://akafazam.ru",
         'BANNER'    => "$path/$rand.jpg");
%Crazy=('TITLE'      => "CRAZY-SKIN",
         'CSS'       => "$path/crazy.css",
         'IMG'       => "$path/crazy.jpg",
         'CHARSET'   => "windows-1251",
         'URL'       => "http:/fakamaza.ru",
         'BANNER'    => "$path/$rand.jpg");

# возвращаем ссылку на хэш, в зависимости от номера запрошенноо скина
return \%Horror if $param==1;
return \%Lucky  if $param==2;
return \%Crazy  if $param==3;
}
# функция заполнения массива хэшей значениями
sub get_list {
my $menu=();# объявляем переменную (которую в дальнейшем будем использовать как ссылку на массив
# вносим в массив 3 анонимных хэша.
push (@{$menu},({"NUM" =>1,"NAME"=>"Horror"},{"NUM" =>2,"NAME"=>"Lucky"},{"NUM" =>3,"NAME"=>"Crazy"}));
return $menu;# возвращаем ссылку на массив
}

__END__

[/CODE]

В коде даны подробные комментарии. Работать с модулем как вы видите довольно просто. Он производит рекурсивные замены меток, что не делала наша функция в первом примере. Здесь я специально ввел метку <TMPL_VAR NAME=URL> - чтобы продемонстрировать это. В примере представлены конечно же не все возможности этого модуля, но наиболее важные и сложные (в случае с TMPL_LOOP).
В модуле также имеются методы:
- Условия
<TMPL_IF>
...
<TMPL_ELSE>
...
</TMPL_IF>

<TMPL_UNLESS>
...
<TMPL_ELSE>
...
</TMPL_UNLESS>

- Подключения более мелких шаблонов
<TMPL_INCLUDE NAME="filename.txt">

И прочее. Все вы их можете изучить через perldoc HTML::Template. Там же имеются и примеры их использования. Документация по модулю более менее ясная и подробная. Модуль имеет также некоторые расширенные вариации, которые имеют больше методов, включая использование регулярных выражений в шаблонах. Но мне кажеться это лишним, ведь логику я предпочитаю оставлять за скриптом, чего и вам советую. И еще хотел бы посоветовать - используйте этот модуль если он вам действительно необходим, в противном случае используйте приведенную мной функцию для работы с шаблонами. Не забывайте модель DWIM ("Делать то что задумано") и не перегружать свои скрипты вызовами излишних модулей.

[OUTRO]

Надеюсь эта статья была для вас полезной, и у вас появилось желание самому что-нибудь написать. Что ж не буду вас больше задерживать, упомяну лишь еще об одном модуле для работы с шаблонами - Template::Toolkit (или в среде perl-программеров просто TT2). Это чрезвычайно мощное средство для работы с шаблонами, но и довольно сложное, поэтому в этой статье я не касался его. Возможно в следующий раз мы поговорим о нем.

Автор: PoizOn /15.01.05/ ©

Mazafaka.Ru - E-Zine - 2005 ©