Организация "шаблонного" вывода в CGI-скриптах на Perl


Прислал: Андрей Черный [ 17.03.2004 @ 16:21 ]
Раздел:: [ Статьи по Perl ]


Организация "шаблонного" вывода в CGI-скриптах на Perl

Введение

Написать эту статью меня подвигли попытки изменить один бесплатный CGI-скрипт гостевой книги под требования конкретного сайта. Все, что нужно было с ним сделать - это везде изменить выводимые слова "гостевая книга" на "книга отзывов", и выровнять некоторые строки по центру. Как оказалось, в скрипте было четыре (!) блока вывода - один для штатной ситуации ("Ваша запись была успешно добавлена...") и три - для вывода различных ошибок (отсутствует E-Mail, отсутствует имя и отсутствует текст). Причем текст этих блоков был "жестко" прописан в операторах print. Понятное дело, что повозиться пришлось.

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

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

1. Шаблоны вывода.

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

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

1. настроить скрипт может любой человек, который не писал этот скрипт и не знает его "тонкостей", да и вообще не разбирается в Perl-программировании; :-)

2. при настройке невозможно внести в код скрипта синтаксические ошибки, которые потом придется искать;

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

4. один скрипт можно использовать с разными вариантами вывода, "подставляя" ему разные шаблоны.

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

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

Для того, чтобы скрипт "знал", в какие места шаблона какаие данные надо вставлять, шаблон содержит специальные метки - зарезервированные последовательности символов. Перед выдачей страницы клиенту скрипт заменяет эти последовательности конкретными данными. В качестве меток обычно применяются последовательности букв, "обрамленные" слева и справа определенными последовательностями символов, которые заведомо не могут встречаться в тексте HTML-шаблона. Часто метки делают на манер HTML-тэгов, например:

<--MESSAGE-->, <-NAME-> и т.п.

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

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

2. Создаем блок вывода

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

Простейший блок вывода этого скрипта может выглядеть так:


#Читаем файл шаблона
open TF,"<template.txt";
sysread TF,$t,-s TF;
close TF;
$t=~s/<-NAME->/$name/g;     #Подставляем значение <-NAME->
$t=~s/<-EMAIL->/$eml/g;     #Подставляем значение <-EMAIL->
$t=~s/<-COUNTRY->/$ctry/g;  #Подставляем значение <-COUNTRY->
$t=~s/<-MESSAGE->/$msg/g;   #Подставляем значение <-MESSAGE->
print "Content-Type: text/html\n\n";
print $t;

Здесь предполагается, что в файле template.txt хранится шаблон вывода, а в переменных $name, $eml, $ctry, $msg хранятся соответствующие значения полей. Четыре оператора замены заменяют метки на соответствующие им значения. Параметр g означает "глобальная замена", т.е. каждая метка будет заменена соответствующим значением по всему шаблону, сколько бы раз она не встретилась.

Таким образом, шаблон может выглядеть, например, так:


<HTML>
<HEAD>
<TITLE><-NAME->, Ваша запись была успешно добавлена</TITLE>
</HEAD>
<BODY>
<B><-NAME->, Ваша запись была успешно добавлена в Гостевую книгу.</B>
<HR>
<P>Вы ввели следующие данные:
<P><B>Имя:</B> <-NAME->
<p><B>E-Mail:</B> <A href="mailto:<-EMAIL->"><-EMAIL-></A>
<P><B>Страна:</B> <-COUNTRY->
<P><B>Текст сообщения:</B><I><-TEXT-></I>
</BODY></HTML>

В этом шаблоне метка <-NAME-> встречается три раза, и везде при выводе будет заменена именем человека, оставившего запись.

Несколько необычно выглядит метка <-EMAIL-> внутри тэга <A href=...>. Однако метки можно вставлять в ЛЮБОМ МЕСТЕ HTML-документа, т.к. они будут заменены значениями ДО того, как документ будет выдан клиентской программе.

3. Метки "высокого" и "низкого" уровня

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

Допустим, мы хотим, чтобы значение <-NAME-> было ссылкой на E-Mail человека - чтобы, щелкнув по имени, можно было написать ему письмо.

Для этого можно изменить одну строчку рассмотренного выше шаблона следующим образом:

<P><B>Имя:</B><A href="mailto:<-EMAIL->"><-NAME-></A>.

Но в этом случае, если человек не ввел свой E-Mail, ссылка будет "пустая" и это будет выглядеть несколько некрасиво. Если мы хотим, чтобы имя человека служило ссылкой только в том случае, если он ввел адрес E-Mail, а в противном случае было просто текстом, то это надо сделать в самом Perl-скрипте - чтобы переменная $name уже содержала ссылку, если это нужно.


#"Художественно обрабатываем" имя для вывода
if ($eml ne ""){$name="<A HREF=\"mailto:$eml\">$name</A>"};

Однако, в двух других случаях использования метки <-NAME-> (в тэге <TITLE> и сообщении о добавлении записи) нам в любом случае нужен текст, без ссылки. Для этого надо для разных потребностей "завести" две разные метки, скажем, <-NAME-> для "текстового" имени и <-A_NAME-> для ссылки.

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

4. Вложенные шаблоны

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

Здесь нам понадобятся уже два шаблона - отдельно шаблон страницы и шаблон записи.


open TM,"<msg.txt"; # Загружаем шаблон сообщения
sysread TM,$tm,-s TM;
close TM;

open TP,"<page.txt"; # Загружаем шаблон страницы
sysread TP,$tp,-s TP;
close TP;


#В массивах $name[], $eml[], $text[] хранятся имя, электронный адрес
#и собственно оставленное сообщение. В переменной $num-кол-во
#сообщений.

$m=""; #Инициализируем переменную для сообщений
for ($i=$num;$i<=0;$i--){
$m1=$tm;
$m1=~s/<-NAME->/$name[$i]/g;
$m1=~s/<-EMAIL->/$eml[$i]/g;
$m1=~s/<-TEXT->/$text[$i]/g;
$m=$m.$m1; #Формируем список сообщений
};
#
$p=$tp;
$p=~s/<-MESSAGES->/$m/g;
#
# ... Подстановка других меток, если нужно
#
print "Content-Type: text/html\n\n";
print $p;

В шаблоне page.txt хранится общий вид выдаваемой страницы. В том месте, где скриптом должны быть вставлены сообщения, стоит метка <-MESSAGES-> На это место будет вставлено нужное количество сообщений, сформированных по шаблону msg.txt.

5. Обработка шаблонов для случаев, если имена меток заранее неизвестны

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

Соответственно, замена меток с "жесткими" именами, как мы делали до этого, в таких случаях невозможна, однако выход есть. Предположим, что набор параметров вывода лежит в хэше %PARAM; тогда блок вывода можно сделать так:


open TF,"<template.txt";
sysread TF,$t,-s TF;
close TF;
#
# ... Замена "жестких" меток, если необходимо
#
# А теперь меняем динамические метки
$t=~s/<-(.*)->/$PARAM{$1}/gie;
print "Content-Type: text/html\n\n";
print $t;

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

6. Поддержка единых элементов дизайна для страниц и скриптов

Многие сайты используют SSI для вставки единых элементов оформления на все страницы. Это дает возможность быстрой полной смены дизайна при необходимости.

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

Для этого можно предусмотреть метки специального вида, вместо которых скрипт будет вставлять содержимое файла с сервера. Возможна даже обработка меток стандартного для SSI-директив вида!

В большинстве случаев достаточно обработки наиболее часто используемой директивы <!--#include virtual="..."-->

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


$base="/usr/home/site"; #"Базовый" путь к сайту.

#Загружаем файл шаблона
open TF,"<template.txt";
sysread TF,$t,-s TF;
close TF;

#Обрабатываем "инклюды"
$t=~s/<!--#include virtual="(.*)"-->/getfile($1)/ge;
#
# ... Здесь, если нужно, обрабатываем другие метки
#

#выводим страницу клиенту
print "Content-Type: text/html\n\n";
print $t;

sub getfile{
if (substr($_[0],0,1) eq "/"){$fn=$base.$_[0];}
else {$fn=$_[0];};
open F,"<$fn";
sysread F,$t,-s F; close F; return $t;}

В этом фрагменте оператор ~s заменяет соответствующую вышеуказанной SSI-директиве конструкцию на значение функции getfile, которая возвращает содержимое нужного файла. Аргументом для функции служит путь к файлу, "извлекаемый" из "SSI-директивы". Если этот путь начинается со слэша '/' ("корневая папка сайта"), то в его начало добавляется базовый путь к сайту из переменной $base. Параметр e оператора ~s указывает на то, что заменяемая строка является функцией, значение которой надо использовать для замены.

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

Автор: Андрей Черный
//angel07.webservis.ru
программирование CGI-скриптов на Perl;
авторская коллекция скриптов;
статьи о хостинге;
интернет-пользователю.