ВыходВход

Меню сайта

Категории статей
Nested Sets [10]
Работа с деревьями Nested Sets
Программирование Perl [5]
Статьи по теоретическому и практическому применению Perl, при проектировании интернет приложений
HTML + JavaScript [1]
Некоторые примеры HTML верстки с использованием JavaScript
Устаревшие статьи [4]
Данные статьи были написаны довольно давно, поэтому могут быть не актуальны в данное время

Меню пользователя

Поиск по статьям

Друзья сайта

Шаблонизатор II (часть 1)
» Каталог статей » Программирование Perl
Шаблонизатор II (часть 1)

Шаблонизатор II (часть 1)

1. Введение


В одной из своих первых статей я описал принцип работы своего собственного шаблонизатора, со временем, код модернизировался и изменялся, но принцип "нарезки" и сборки остался тот же. В итоге собран небольшой модуль в 120 строк + дополнительный модуль раширения о котором поговорим позже. Итак, посмотрим на пример шаблона и пример вывода, что бы в общих чертах понять принцип работы:


Схема 1


Как видно из шаблона, в нем определены четыре блока (<!-- [1, 3, 5, 6] -->) и две метки вставки (<!-- [1, 3]_insert -->). Блоки 5 и 6 являются сотавными частями для блока 1, поэтому для них меток вставок нет. Сразу может возникнуть вопрос, по поводу того, почему каждая строка блока имеет свою метку, а не просто определены границы блока, поясняю, в некоторых случаях блоки в шаблоне могут вкладываться друг в друга, а визуальный вид шаблона терять не хочется, разместив блоки по порядку, а не тех местах HTML кода где им положено быть. Так же в шаблоне включены 3 метки вставки (<[inc_...]>) которыые обрабатываюстя отдельным модулем и являются динамическими частями страницы не зависящими от основного кода скрипта, чаще всего это динамические меню, которые присутсвуют на нескольких страницах и зависят только от глобальных установок с небольшими оговорками.


2. Принцип модуля шаблонизатора


Определим, какие данные будут хранится в нашем объекте и какие методы мы будем использовать:



  • Данные объекта:

    • HTML код шаблона - ссылка на массив;

    • HTML код блоков шаблона - ссылка на хеш ([номер блока] => [HTML код блока]);

    • Пользователькие переменные - ссылка на хеш ([параметр ([$...$])] => [значение]);

    • Папка, в которой хранятся шаблоны;

    • "Флаг" обработки SSI - если обработка SSI не нужна, то и не за чем обрабатывать в пустую;



  • Методы объекта:

    • Объявление объекта;

    • Забор шаблона из файла;

    • "Нарезка" блоков;

    • "Склеивание" блоков;

    • Обработка шаблона;

    • Вывод шаблона пользователю;

    • Внутренняя процедура обработки SSI - метод не обязательный, но весьма полезный;




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

2.
Во время забора шаблона из файла ничего особенного не происходит, просто быбираем файл в массив и кидаем ссылку на него в объект.

3.
Во время "нарезки" блоков шаблон построчно обрабатывается и строки с метками переносятся в хеш с соответсвующим ключем с последующим удалением этих строк из общего шаблона;

4.
Во время "склейки" блоков шаблон так же построчно обрабатывается на предмет поиска меток вставки с последующей их заменой на соответсвующий блок;

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

6.
Во время вывода шаблона пользователю отсылается заголовок (если надо) и HTML код шаблона.

7.
Во время обработки SSI, просто находится соотвествующий файл и внедряется в основной код шаблона.


3. Код


3.1. Объявление объекта


package WM5::Template;
# Без этого - "ни шагу со двора"
use strict; use warnings;
# Локаль, тоже пригодится
use locale;
use POSIX qw(locale_h);
setlocale(LC_CTYPE, 'ru_RU.CP1251');
setlocale(LC_ALL, "ru_RU.CP1251");
# Определим версию
our $VERSION = '2.0.1';

sub new {
# Получаем переменные
my ($self, %common) = @_;
# Определяем хеш объекта
$self = {
template => undef,
block => undef,
user_vars => undef,
# Папка шаблонов определяется сама как [папка месторасположения скрипта]/template/
# хотя такой подход не обязателен, достаточно по умолчанию сделать ./template/
# или определить какую-либо свою

folder => ($ENV{'SCRIPT_FILENAME'} =~ /^(.*?)[^\\\/]*$/)[0].'template/',
SSI => 'N',
};
# "Прицепляем" ссылку на хеш(!) пользовательских переменных если мы её получаем
$$self{'user_vars'} = $common{'user_vars'} if $common{'user_vars'};
# "Прицепляем" папку шаблонов, если мы определяем её вручную
$$self{'forder'} = $common{'folder'} if $common{'folder'};
# Определяем "флаг" использования SSI. Несколько сложное определение обусловлено
# использованием use warnings; (-w), а так же для того что бы ограничить возможность
# использования других значений переменной кроме как Y или N

$$self{'SSI'} = {'Y' => 'Y', 'N' => 'N'}->{($common{'SSI'} || 'N')} || 'N';
# Если мы передаем file при объявлении объекта, то можно сразу получить шаблон
if ($common{'file'}) {$self = &GetTemplateFromFile($self, $common{'file'})}
# Теперь можно "благословить" объект...
bless $self;
# ... и вернуть основному скрипту
return $self
}

3.2. Выборка шаблона из файла


sub GetTemplateFromFile {
# Получаем объект и имя файла
my ($self, $file) = @_;
# Определяем путь к файлу
$file = $self->{'folder'}.$file;
# Открываем файл и выбираем весь HTML код в массив
open (TMP, $file);
my @template = <TMP>;
close TMP;
# Чистим концы строк, не обязательно, но шаблон будет легче
chomp @template;
# "Цепляем" ссылку на массив объекту
$self->{'template'} = \@template;
# Возвращаем объект, обязательно, только тогда когда мы обращаемся к данной
# процедуре не как к методу, это мы производим из процедуры объявления

return $self;
}

3.3. "Нарезка" блоков


sub SplitTemplate {
# Получаем объект
my $self = shift;
# Незаметно отключаем предупреждения, а то не хочется проверять лишний раз
# определен ли у нас элемент хеша блоков ($self->{'block'})

no warnings;
# Объявляем счетчик
my $i;
# "Прогоняем" шаблон...
while (${$self->{'template'}}[$i]) {
# ... ищем метки ...
if (${$self->{'template'}}[$i] =~ s/<!--\s(\d+)\s-->//g) {
# ... прицепляем строку к соответсвующему элементу хеша блоков...
$self->{'block'}->{$1} .= ${$self->{'template'}}[$i];
# ... удаляем елемент массива ...
splice (@{$self->{'template'}}, $i ,1);
# ... уменьшаем счетчик на 1 ...
$i--
}
# ... увеличиваем счетчик на 1
$i++
}
}

Я не использую цикл foreach или for из-за того, что не просто обнуляю элементы массива шаблона, а удаляю их, при этом количество циклов динамически изменяется.


3.4. "Склейка" блоков


sub MergeTemplate {
# Получаем объект
my $self = shift;
# Прогоняем шаблон в цикле
foreach (@{$self -> {'template'}}) {
# Заменяем метки вставки соответствующими блоками
$_ =~s /<!-- (\d+)_insert -->/$self->{'block'}->{$1} || ''/eg
}
# Удаляем хеш блоков
$self->{'block'} = undef
}

3.5. Обработка шаблона


sub DefaultParce {
# Получаем объект
my $self = shift;
# Формируем регулярное выражение стандартных кодов
my %codes = ('url' => $ENV{'SERVER_NAME'},
'nbsp' => '&nbsp;',
'br' => "\n",
);
my $regex = '\[((?:'.join(')|(?:', keys %codes).'))\]';
# Прогоняем шаблон в цикле
foreach (@{$self->{'template'}}) {
# Обрабатываем SSI по требованию
$_ =~s /\<!--#include virtual\=\"([\?\&\=\-\w\.\/]+)\"\s*\-+\>/
&_include_ssi($self, $1)/eg
if $self->{'SSI'} eq 'Y';
# Замена стандартных кодов
$_ =~s /$regex/$codes{lc($1)}/gi;
$_ =~s /\[nbsp\:\s*(0-9)\s*\]/'&nbsp' x $1/egi;
# Замена пользовательских пременных
$_ =~s /\[\$([\w\s\-]+)\$\]/$self->{'user_vars'}->{$1} || ''/eg;
}
}

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


~s /\[((?:url)|(?:br)|(?:nbsp))\]/$array{lc($1)}/gi 

3.6. Вывод шаблона


sub ShowTemplate {
# Получаем объект
my $self = shift;
# Выводим заголовок браузеру, если нужно
print "Content-type: text/html; charset=windows-1251\n$self->{'user_vars'}->{'cookies'}\n"
if !$ENV{'MOD_PERL'};
# Выводим шаблон
print @{$self->{'template'}};
}

Собственно, 3 строки, единственно, в заголовке возможна передача Cookies, а так - ничего нового


3.7. Обработка SSI


sub _include_ssi {
# Получаем объект и ссылку на файл
my ($self, $ssi) = @_;
# Чистим все после знака ?
$ssi =~s /\?.*//i;
# Определяем путь к файлу
my $file;
$file = $ssi !~ /^\/\// ?
# Если относительная ссылка
'./template/'.$ssi :
# Если абсолютная ссылка eq.: [//ssi/include_file.html]
$ENV{'DOCUMENT_ROOT'}.$ssi;
my $html;
# Открываем файл и сохраняем данные файла в переменной
if (-e $file) {
open (FILE, $file);
$html = join('',<FILE>);
close FILE
} else {$html = 'Error including file!!!'}
# Возвращаем содержимое файла, или текстовую ошибку
return $html
}

Без прикрас и лишних обработок, хотя, при желании можно и поэкспериментировать.


4. Пример использования


Требуется вывести список товаров, разбитый постранично, товары хранятся в базе данных в одной таблице. Итак, код шаблона:


<html>
<head>
<title>
Список товаров</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
</head>
<body>

<!-- 10 --> <table width="100%" border="0" cellspacing="0" cellpadding="0">
<!-- 10 --> <tr><td>Страницы:</td><td>
<!-- 10 --> [$list$]
<!-- 11 --> <a href="?page=[$page$]">[$page$]</a>[nbsp]
<!-- 12 --> <b>[$page$]</b>[nbsp]
<!-- 10 --> </td></tr></table><br>
<!-- 10_insert -->
<table width="100%" border="1" cellPadding="3" cellSpacing="1">
<!-- 1 --> <tr>
<!-- 1 --> <td>[$name$]</td>
<!-- 1 --> <td>[$description$]</td>
<!-- 1 --> </tr>
<!-- 1_insert -->
</table><br>
<!-- 10_insert -->
</body>
</html>

Код скрипта:


#!/usr/bin/perl
use strict;
use CGI;
use DBI;
use lib './../lib/';
use PM::Template;
# Подключаемся к базе
my $dbh = 'DBI'->connect('DBI:mysql:database=test:host=localhost:port=3306','user', 'pass')
|| die "Cann't connect DataBase!!! $DBI::errstr";
# Создаем объект CGI
my $query = new CGI;
# Объявляем пользовательские переменные и определяем текущую страницу
# и количество строк на странице

my %uv;
$uv{'page'} = $query->param('page') || 1;
$uv{'page'} = int($uv{'page'}) || 1;
$uv{'goods_per_page'} = 3;
# Создаем объект шаблона и сразу забираем из файла
my $template = new PM::Template (user_vars => \%uv, file => 'list.html');
# Нарезаем шаблон
$template->SplitTemplate;
# Формируем список страниц
my $sql = 'SELECT COUNT(*) FROM table2';
my $sth = $dbh->prepare($sql); $sth->execute();
my $nums = $sth->fetchrow_arrayref()->[0] || 1;
$sth->finish();
my $pages = int(($nums - 1)/$uv{'goods_per_page'}) + 1;
my $lines; # Временная переменная списка
for my $i (1..$pages) {
my $line = $i == $uv{'page'} ?
$template->{'block'}->{'12'} : $template->{'block'}->{'11'};
$line =~s /\[\$page\$\]/$i/gi;
$lines .= $line;
}
$template->{'block'}->{'10'} =~s /\[\$list\$\]/$lines/gi; undef $lines;
# Формируем список товаров
$sql = 'SELECT name, description
FROM table2
ORDER BY name
LIMIT '
.(($uv{'page'} - 1) * $uv{'goods_per_page'}).','.$uv{'goods_per_page'};
$sth = $dbh->prepare($sql); $sth->execute();
while (my $row = $sth->fetchrow_hashref()) {
my $line = $template->{'block'}->{'1'};
$line =~s /\[\$(\w+)\$\]/$$row{$1}/gi;
$lines .= $line;
}
$template->{'block'}->{'1'} = $lines; undef $lines;
# Склеиваем шаблон
$template->MergeTemplate;
# Окончательная обработка шаблона
$template->DefaultParce;
# Вывод пользователю
$template->ShowTemplate;
exit;

И результат:


Скриншот 1


Вот в общем-то и все, просто и, по идее, понятно. Теперь можно рассмотретьь внедрение динамических блоков в шаблон...

Категория: Программирование Perl | Добавил: phoinix (2006-02-09) | Автор: Сергей Томулевич aka Phoinix
Просмотров: 4333 | Рейтинг: 0.0 |

Комментарии

 

Бесплатный конструктор сайтов - uCoz