Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Реализация электронного справочника (словаря, энциклопедии) с помощью простого CGI-скрипта на Perl

Радиомир. Ваш компьютер» 12/2005)

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

Создание и справочника, и словаря, и энциклопедии происходит одинаково, разница только в содержимом, поэтому далее я буду для краткости писать просто «справочник», имея в виду и то, и другое, и третье.

Как выглядит работа с таким справочником? Пользователь набирает в адресной строке браузера адрес скрипта, после чего появляется форма для ввода запроса (рис. 1). Введя нужный запрос, пользователь нажимает кнопку «Найти» (либо клавишу Enter, когда курсор стоит в поле ввода). Если для введённого запроса найдено соответствие в справочнике, скрипт выдаёт соответствующую информацию (рис. 2, 3), иначе — выдаёт сообщение о безуспешности поиска (рис. 4).

Рис. 1

Рис. 2

Рис. 3

Рис. 4

Теперь рассмотрим всё это, так сказать, изнутри. Ниже приведён текст скрипта.

#!usr/bin/perl -w

use strict;
use CGI qw(:standard);

my $script_name='ref.pl'; # Имя файла скрипта.
my $data_name='data.txt'; # Имя файла с содержимым справочника.
my $q=param('q');         # Запрос пользователя.
my $separator='~';        # Разделитель ключа и значения записи.
my $record;               # Содержимое текущей записи.

# Если переменная $q не определена (то есть запроса не было),
# приравниваем её пустой строке (вообще-то она и без того
# интерпретировалась бы как пустая строка; явное присваивание
# нужно, чтобы Perl не выдавал предупреждений).

unless (defined ($q)) {$q=''}

# Заменяем в тексте запроса символы "&", "<", ">", '"' на их
# HTML-эквиваленты. Именно в таком виде текст запроса должен
# быть помещён в формируемый HTML-документ, и именно в таком
# виде представлено содержимое файла с записями.

$q=~s/&/&amp;/g;  # Именно эта замена должна быть первой!
$q=~s/</&lt;/g;
$q=~s/>/&gt;/g;
$q=~s/"/&quot;/g;

# Выводим HTML-документ, который будет возвращён пользователю.
# Начальное содержимое поля ввода берётся из переменной $q: если
# запроса не было - там пустая строка, иначе - текст запроса.

print <<END_OF_HTML;
Content-Type: text/html; charset=windows-1251
Expires: Thu Jan  1 00:00:00 1970

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="ru">
<head>
<meta http-equiv="Content-Type" content="text/html;
 charset=windows-1251">
<title>Справочник</title>
<style type="text/css">
<!--
body {background: white; color: black; padding: 1em}
p {text-align: justify; text-indent: 2em; margin-top: 0;
 margin-bottom: 0}
-->
</style>
</head>
<body>
<div align="center">
<h1>Справочник</h1>
<form action="$script_name" method="get">
<input type="text" name="q" size="50" value="$q">
<input type="submit" value="Найти">
</form>
</div>
END_OF_HTML

# Если запрос был, ищем соответствующую запись в файле
# с содержимым справочника.

if (defined (param ('q')))
{
 open (DATA,'<',$data_name) ||
  die ("Can't open file '$data_name'! $!\n");

SEARCH_AND_OUT:
 {
  while ($record=<DATA>) # Пока файл не кончится.
  {
   if ($record eq "\n") {next} # Пустые строки пропускаем.

   # Прочитана первая строка записи. Если эта строка
   # заканчивается на "\", то удаляем этот "\" и добавляем к ней
   # следующую строку. Так повторяем, пока очередная прочитанная
   # строка не окажется без "\" в конце - это будет последняя
   # строка записи.

   while ($record=~s/\\\n//)
   {
    $record.=<DATA>;
   }

   # Итак, вся текущая запись прочитана в $record. Ищем в ней
   # разделитель - он отделяет ключ от значения (например, если
   # это символ "~", то в записи "file~file - файл" ключом будет
   # "file", а значением - "file - файл"). Если разделитель
   # не найден - значит, в файле с записями - синтаксическая
   # ошибка, тогда выводим сообщение об этом и прерываем
   # выполнение скрипта.

   ($record=~m/$separator/o) ||
    die ("Syntax error in file '$data_name', line $.:".
    " separator not found in record!\n");

   # Теперь во встроенной переменной $` - то, что было в записи
   # до разделителя (то есть ключ), а во встроенной переменной
   # $' - то, что было после разделителя (то есть значение).
   # Проверяем, совпадает ли ключ с запросом.

   if ($` eq $q)
   {
    # Совпадение обнаружено.
    # Выводим значение и выходим из блока SEARCH_AND_OUT.
    print ($');
    last SEARCH_AND_OUT;
   }
  }

  # Все записи просмотрены, но совпадения не было. Выдаём
  # сообщение о безуспешности поиска.

  print <<END_OF_HTML;
<div align="center">
К&nbsp;сожалению, по&nbsp;запросу &laquo;$q&raquo; ничего
не&nbsp;найдено.
</div>
END_OF_HTML
 } # Конец блока SEARCH_AND_OUT.

 close (DATA) || die ("Can't close file '$data_name'! $!\n");
}

# Завершаем формирование HTML-документа.

print <<END_OF_HTML;
</body>
</html>
END_OF_HTML
Скачать скрипт (2 КБ ZIP)

Для работы со скриптом на своём компьютере (локально) вам понадобится Perl (я использовал ActivePerl 5.8.6.811 — http://www.ActiveState.com/ActivePerl/), программа — web-сервер (я использовал Small HTTP Server 3.05.23 — http://home.lanck.net/mf/srv/index.html) и браузер (я использовал Opera 5.12 (http://www.opera.com) — да, это не последняя версия Opera, но зато она работает с приемлемой скоростью на моём 486 DX2/66 с 8 МБ RAM).

По умолчанию предполагается, что имя файла со скриптом — «ref.pl» (от «reference book» — «справочник»). Это имя хранится в переменной $script_name (см. начало скрипта). Именно значение этой переменной подставляется в качестве адреса обработчика формы в HTML-документе, создаваемом скриптом. Если вы решили назвать файл со скриптом по-другому, значение $script_name также следует изменить. Замечу, что скрипт должен находиться в каталоге, предназначенном для размещения CGI-скриптов (для Small HTTP Server 3.05.23 по умолчанию это каталог «cgi-bin» или какой-либо созданный в нём подкаталог).

Непосредственно содержимое справочника хранится в отдельном файле, формат которого описан ниже. Имя этого файла по умолчанию — «data.txt», оно хранится в переменной $data_name. Вы, конечно, можете назвать этот файл по-другому, не забыв изменить $data_name. По умолчанию предполагается, что этот файл расположен в одном каталоге со скриптом; если это не так, то в $data_name перед именем файла надо указать путь к нему.

Файл с содержимым справочника — текстовый, он состоит из записей, каждая из которых начинается с новой строки. Количество записей не ограничивается. Между записями может быть любое число пустых строк. Каждая запись может занимать одну или несколько строк, в последнем случае все строки записи, кроме последней, должны заканчиваться символом «\» (скрипт при чтении такой многострочной записи уберёт эти «\» и склеит все её строки в одну).

Пример расположения записей в файле:

запись 1

начало записи 2\
продолжение записи 2\
окончание записи 2
запись 3

Если запись сама должна заканчиваться на «\», то, чтобы предотвратить неправильное понимание этого «\» как признака «продолжение на следующей строке», надо поставить после него ещё один «\» и после этой записи добавить пустую строку. Тогда при чтении этой записи просто произойдёт её склеивание с добавленной пустой строкой, что не изменит запись.

Каждая запись состоит из трёх частей, следующих друг за другом: ключа, разделителя (по умолчанию это символ «~», но можно использовать для этого и какой-нибудь другой символ или даже строку из нескольких символов, изменив переменную $separator) и значения. Например, в записи «file~file — файл» ключом будет «file», а значением — «file — файл». Само собой, разделитель не должен встречаться в ключе записи (но может встречаться в значении). Как вы уже, наверно, догадались, ключ — это то, что сравнивается с запросом пользователя, а значение — это то, что выводится в случае их совпадения.

Значение найденной записи становится частью HTML-документа, формируемого скриптом. Поэтому в файле с записями вместо символов «&», «<», «>» и «"» в значениях записей должны быть HTML-эквиваленты этих символов (соответственно «&amp;», «&lt;», «&gt;» и «&quot;»). В ключах записей также должна быть выполнена эта замена, потому что перед сравнением текста запроса с ключами скрипт выполняет эту замену в тексте запроса (чтобы затем корректно вывести текст запроса в качестве начального значения запроса в формируемом HTML-документе).

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

Ниже приведён пример файла с содержимым справочника. Именно с использованием этого файла получены рис. 2—4. Файл содержит всего две записи, информацию для которых я взял из своей таблицы расширений файлов TR-DOS. В этом примере как раз хорошо видно использование различных элементов HTML-форматирования.

sna~<p><b>Расширение:</b> sna</p>\
<p><b>Комментарий:</b> snapshot (файл, в&nbsp;котором \
запоминается состояние эмулируемого &laquo;Спектрума&raquo;: \
содержимое памяти, значения регистров процессора и&nbsp;\
некоторая другая информация).</p>\
<p><b>Программа для&nbsp;обработки:</b> File Extractor 1.1b.\
</p>\
<p><b>Происхождение расширения:</b> от&nbsp;&laquo;snapshot\
&raquo;.</p>\
<p><b>Информация о&nbsp;формате или&nbsp;ссылка на&nbsp;неё:\
</b> описание формата SNA&nbsp;48K&nbsp;&#151; см.&nbsp;\
документацию к&nbsp;эмулятору JPP; описание отличий формата \
SNA&nbsp;128K от&nbsp;48K&nbsp;&#151; см.&nbsp;документацию \
к&nbsp;эмулятору UKV (его можно найти, например, на&nbsp;сайте \
Virtual <nobr>TR-DOS&nbsp;&#151;</nobr> <a href=\
"http://zx.da.ru">http://zx.da.ru</a>).</p>

trd~<p><b>Расширение:</b> trd</p>\
<p><b>Комментарий:</b> образ диска <nobr>TR-DOS.</nobr></p>\
<p><b>Программа для&nbsp;обработки:</b> File Extractor 1.1b.\
</p>\
<p><b>Происхождение расширения:</b> первые три буквы \
аббревиатуры <nobr>&laquo;TR-DOS&raquo;.</nobr></p>\
<p><b>Информация о&nbsp;формате или&nbsp;ссылка на&nbsp;неё:\
</b> в&nbsp;файле хранится содержимое всех секторов диска \
<nobr>TR-DOS,</nobr> <nobr>без&nbsp;какой-либо</nobr> \
дополнительной служебной информации.</p>
Скачать вышеприведённый пример файла с содержимым справочника (1 КБ ZIP)

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

Такое сообщение об ошибке может появиться и тогда, когда в многострочной записи вы забыли поставить символ «\» в конце какой-либо строки, где он должен быть. Тогда следующая строка будет сочтена началом новой записи, и если в этой «записи» не окажется случайно разделителя, то как раз и появится сообщение об ошибке.

При необходимости можно изменить дизайн HTML-документов, формируемых скриптом, изменив для этого фрагменты HTML-кода, находящиеся в скрипте между строками «print <<END_OF_HTML;» и «END_OF_HTML». Но учтите, что при выводе этих фрагментов Perl производит так называемую интерполяцию переменных (например, вместо «$q» подставляется значение этой переменной). Поэтому, если в добавляемом HTML-коде содержатся символы «$» и «@», то перед ними надо поставить «\», чтобы Perl не считал, что с этих символов начинаются имена переменных. Как видим, символ «\» имеет специальное назначение, поэтому, если в добавляемом HTML-коде этот символ встречается сам по себе, то перед ним также надо поставить «\». Замечу, что сказанное в этом абзаце не относится к тем фрагментам HTML-кода, которые содержатся в текстовом файле с записями.

Строки «Content-Type: text/html; charset=windows-1251» и «Expires: Thu Jan 1 00:00:00 1970» — это HTTP-заголовок, возвращаемый скриптом. Пустая строка, отделяющая их от собственно тела HTML-документа, должна быть обязательно. Строка «Expires…» добавлена в отладочных целях, чтобы не позволить браузеру кэшировать HTML-документы, формируемые скриптом. Иначе при отладке справочника, когда вы как-либо изменяете либо скрипт, либо текстовый файл с базой, может случиться так, что после ввода запроса, совпадающего с одним из предыдущих запросов, браузер выдаст старый результат из кэша — в то время как необходим именно новый результат.

Приведённый вариант скрипта рассчитан на то, что кодировка и его самого, и файла с записями — Windows-1251. Именно поэтому и в HTTP-заголовке формируемого HTML-документа, и в самом этом документе указано: «charset=windows-1251». Если в вашем случае кодировка другая (но, само собой, одна и та же для скрипта и файла с записями), то нужно указать именно её. А если скрипт вызывается web-сервером, выполняющим автоматическое перекодирование в зависимости от того, кто обратился к серверу (Small HTTP Server 3.05.23 это не делает), то упоминания о кодировке надо исключить из скрипта (заменить «text/html; charset=windows-1251» на «text/html») во избежание ситуации, когда фактическая кодировка HTML-документа не совпадает с указанной в самом этом документе, в результате чего этот документ неправильно отображается в браузере.

И ещё одно замечание. При большом размере файла с записями поиск в нём может занять длительное время, ведь в худшем случае (если искомой записи в этом файле нет или если эта запись последняя) будет просмотрен весь файл. Для ускорения поиска можно, например, разбить файл с записями на отдельные файлы, в каждом из которых будут записи с ключами, начинающимися на «свою» букву, и производить поиск лишь в одном из этих файлов, в зависимости от первой буквы запроса. Или можно использовать какую-либо базу данных.

Источники

  1. В.Маслов. «Введение в Perl». http://lib.ru/PERL/russperl5/perl.txt
  2. Спецификация HTML 4.01, раздел «Формы». http://pyramidin.narod.ru/html401/forms.html
  3. П.Аммосов, А.Пенттинен, М.Поляков, Г.Строкин. «FAQ по perl и программированию для web». http://pavel.ammosov.ru/ruperl/
Страница Ивана Рощина > Статьи >