Глава 19. CGI-программирование с  использованием cgi-lib.pl

Коротко

В предыдущей главе рассказывалось о CGI-программировании (Common Gateway Interface) на базе методов стандартного модуля CGI.pm. Не менее популярен среди программистов пакет cgi-lib.pl. Поскольку многие CGI-сценарии на Perl написаны с его помощью, в этой главе рассказывается именно о нем.

Авторские права на этот пакет принадлежат его создателю Стивену Е. Бреннеру (Steven E. Brenner), на домашней странице которого (http://cgi-lib.stanford.edu/cgi-lib) можно получить копию cgi-lib.pl. Вам разрешается работать с cgi-lib.pl и даже изменять его до тех пор, пока ваши действия не будут ущемлять описанные в начале файла авторские права. Особая процедура установки не требуется — файл cgi-lib.pl копируется в каталог, где хранятся CGI-сценарии, и с помощью команды require подключается к ним:

require 'cgi-lib.pl';

В этой главе создаются два сценария, генерирующие те же страницы, что и в предыдущей главе, но вместо CGI.pm на сей раз будет использован пакет cgilib.pl. В предыдущей главе сценарии назывались cgi1.cgi и cgi2.cgi, а в этой речь пойдет о lib1.cgi и lib2.cgi, с тем чтобы избежать путаницы. Когда пользователь открывает страницу в броузере (требуется указать адрес сценария, например http://www.yourserver.com/user/cgi/lib1.cgi), lib1.cgi возвращает страницу, содержащую элементы управления HTML и текст. В данном случае это страничка  с анкетой. На рис. 19.1_19.3 она показана в окне программы Netscape Navigator.

Как видно из рис. 19.1, страница содержит приветствие и сообщение о том, что посетители, не желающие заполнять анкету, могут перейти по ссылке на сервер CPAN (Comprehensive Perl Archive Network — всеобъемлющий архив, посвященный языку Perl).

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

Рис. 19.1. Текст, маркированный список и гиперссылка

Рис. 19.2. Текстовое поле и текстовая область

Просматривая анкету дальше, вы увидите еще несколько элементов управления, показанных на рисунке 19.3, — кнопки с зависимой и независимой фиксацией, списки, а также кнопка подтверждения и очистки анкеты. Все эти элементы предназначены для ввода дополнительной информации.

Когда пользователь нажимает на кнопку Submit, расположенную в конце анкеты, броузер собирает данные, введенные на странице, и передает их другому CGI-сценарию, cgi2.cgi. В справочных целях он приведен в листинге 19.2, а результаты его выполнения — на рис. 19.4, где вы можете наблюдать сводку введенной пользователем информации.

Рис. 19.3. Элементы управления HTML получают данные от пользователя

Рис. 19.4. Сценарий lib2.cgi показывает результаты анкетирования

Завершив обзор сценариев, которые нам предстоит создать, перейдем к рассмотрению файла, с которым мы будем работать в течение главы, — cgi-lib.pl.

Использование cgi-lib.pl

Пакет cgi-lib.pl подключается к сценарию с помощью команды require. (Не запрещено использовать также use, но вряд ли вам встретится такой вариант.) В отличие от модуля CGI.pm, количество функций, генерирующих теги HTML в cgilib.pl, крайне ограничено. Обычно эти теги приходится выводить вручную. (Пакет cgi-lib.pl предназначался в первую очередь для разбора посланных сценарию данных.) Впрочем, некоторые теги HTML все же генерируются автоматически: подпрограмма PrintHeader создает шапку HTML, необходимую для страницы, раздел HtmlTop (метки <HEAD> и <BODY>). Также с ее помощью можно создать заголовок страницы, как показано в следующем примере, задающем страницу с заголовком "My Web Page":

#!/usr/local/bin/perl

require 'cgi-lib.pl';

print &PrintHeader;

print &HtmlTop ("My Web Page");

После описания начала страницы остальная разметка HTML (в том числе формы) создается путем непосредственного вывода тегов. Например, вот так задается заголовок <H1>:

print "<CENTER><H1>Hello!</H1></CENTER>";

Чтобы прочитать данные, переданные CGI-сценарию, используется подпрограм ма ReadParse. Она создает хэш (обычно называемый %in) и записывает в него значения элементов данных, переданных сценарию. Элементы данных адресуются по именам, присвоенным соответствующим элементам HTML. Например, следующий код создает хэш %in и, читая данные текстового поля 'text', выводит их:

&ReadParse(*in);

print "Here is the text: EM>", $in('text'), "<EM>.";

Метка HTML <EM> просто выделяет текст (в большинстве броузеров этому тегу соответствует курсив). Чтобы завершить Web-страницу метками HTML </BODY> и </HTML>, можно использовать подпрограмму HtmlBot (она просто возвращает строку "</BODY>\n</HTML>\n"):

print &HtmlBot;

Вот так, вкратце, и работает cgi-lib.pl. Более подробный обзор вы найдете в разделе «Непосредственные решения».

Листинг 19.1. lib1.cgi

#!/usr/local/bin/perl

require 'cgi-lib.pl';

print &PrintHeader;

print &HtmlTop ("CGI Example Using cgi-lib.pl");

print

 

Использование cgi-lib.pl

"<BODY BGCOLOR=\"white\" LINK=\"red\"><p>

<CENTER><H1>Here is the Survey!</H1></CENTER>

<H2>Please fill out our survey</H2>

Reasons for filling out our survey:

<P><UL>

<LI>Fame</LI>

<LI>Fortune</LI>

<LI>Fun</LI>

</UL>

If you would rather not fill out our survey? you might

be interested in

<A HREF=\"http://www.cpan.org/\">CPAN</A>

<HR>

<FORM METHOD=\"POST\"

ACTION=\"http://www.yourserver.com/user/cgi/lib2.cgi\"

ENCTYPE=\"application/x-www-form-urlencoded\">

Please  enter  your  name:

<INPUT TYPE=\"text\" NAME=\"text\"  VALUE=\"\"><P>

Please enter your opinion:<P><TEXTAREA NAME=\"textarea\"

ROWS=10 COLS=60>No opinion</TEXTAREA><P>

Please indicate what products you use:  <P>

<INPUT TYPE=\"checkbox\" NAME=\"checkboxes\"

VALUE=\"Shampoo\">

Shampoo

<INPUT TYPE=\"checkbox\" NAME=\"checkboxes\"

VALUE=\"Toophpaste\">

Toothpaste

<INPUT TYPE=\"checkbox\" NAME=\"checkboxes\" VALUE=\"Bread\"

CHECKED>Bread

<INPUT TYPE=\"checkbox\" NAME=\"checkboxes\" VALUE=

\"Cruise  missiles\" CHECKED>Cruise missiles  </P>

Please indicate your income level:  <P><SELECT

NAME=\"list\" SIZE=4>

<OPTION VALUE=\"Highest\">Highest

<OPTION SELECTED  VALUE=\"High\">High

<OPTION VALUE=\"Medium\">Medium

<OPTION VALUE=\"Low\">Low

</SELECT><P>

Please indicate your day of the week:  <P>

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"1\" CHECKED>

Sunday

продолжение И

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"2\">Monday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"3\">Tuesday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"4\">Wensday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"5\">Thursday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"6\">Friday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"7\">Saturday

<P>

Thank you for filling out our Survey/ Please indicate how

much unsolicited mail you like to get:

<SELECT NAME=\"popupmenu\">

<OPTION VALUE=\"Very much\">Very much

<OPTION VALUE=\"A lot\">A lot

<OPTION VALUE=\"Not so much\">Not so much

<OPTION VALUE=\"None\">None

</SELECT><P>

<INPUT TYPE=\"hidden\" NAME=\"hiddendata\" VALUE=\"Rosebud\">

<CENTER><INPUT TYPE=\"submit\" NAME=\"submit\">

<INPUT TYPE=\"reset\">

</CENTER><HR></FORM>";

print  &HtmlBot;

Листинг 19.2. lib2.cgi

#!/usr/local/bin/perl

require 'cgi-lib.pl';

print &PrintHeader;

print &HtmlTop ("CGI Example Using cgi-lib.pl");

print "<BODY BGCOLOR=\"white\" LINK=\"red\"><p>

<CENTER><H1>Thank you for filling out our survey.</H1></CENTER>

<H3>Here are your responses...</H3>

if (&ReadParse(*in))  {

print

"Your name is: <EM>", $in{'text'}, "</EM>.", "<p>",

"Your opinions are: <EM>" , $in{'textarea'}, "</EM>.",

"<p>", "You use these products: <EM>",

join(", ", &SplitParam($in{'checkboxes'})), "</EM>,

"<p>", "Your income level is: <EM>",$in{'list'},

"<EM>.", <p>,"Today is day <EM>", $in{'radios'}, "</EM> of the week." "<p>",

"How much uncolicited mail you like: <EM>",

$in{'popupmenu'}, "</EM>.", "<p>",

"The hidden data is <EM>", $in{'hiddendata'}, "</EM>.";

}

print  &HtmlBot;

Непосредственные решения

Какие подпрограммы входят в состав cgi-lib.pl?

Вот список подпрограмм с описанием их действий:

· CgiDie — как и CgiError, печатает сообщение об ошибке и, кроме того, останавливает программу.

· CgiError — печатает сообщение об ошибке, используя стандартные заголовки и HTML-код.

· HtmlBot — возвращает строку "</BODY >\n</HTML>\n".

· HtmlTop — возвращает раздел <HEAD> документа HTML и открывает раздел <BODY>. Необязательный строковый параметр используется в качестве названия Web-страницы: добавляется тег HTML <H1> с этим названием.

· MethGet — возвращает значение истина, если текущий вызов CGI сделан при помощи метода GET. В противном случае возвращается значение ложь.

· MethPost — возвращает значение истина, если текущий вызов CGI сделан при помощи метода POST. В противном случае возвращается значение ложь.

· MyBaseUrl — возвращает базовый адрес (base URL) CGI-сценария, без дополнительного пути или строк запроса.

· MyFullUrl — возвращает базовый адрес (base URL) CGI-сценария, включая дополнительный путь и строки запроса.

· PrintEnv — форматирует и печатает переменные среды, доступные сценарию.

· PrintHeader — возвращает строку "Content-type: text/html\n\n". С нее должны начинаться все Web-страницы, создаваемые cgi-lib.pl.

· PrintVariables — форматирует и печатает значения данных. Ей передается хэш или запись таблицы символов (для вывода элементов соответствующего массива). Без аргументов PrintVariables выводит содержимое хэша %in.

· ReadParse — основная подпрограмма библиотеки cgi-lib.pl. Она читает и разбирает данные, переданные CGI-сценарию методами GET или POST. Обычно она используется для создания хэша %in: ей передается запись таблицы символов (typeglob) *in. Хэш содержит данные, переданные сценарию, упорядоченные по именам соответствующих элементов управления. Необязатель ные второй, третий и четвертый параметры указывают на то, что надо заполнить соответствующие хэши данными из принятых файлов.

· SplitParam — разбивает параметр, содержащий несколько значений, на список из единичных параметров. Эта подпрограмма предназначена для работы с элементами HTML, способными хранить несколько значений, — например, группой кнопок.

Начинаем документ HTML

Прежде всего необходимо подключить пакет cgi-lib.pl. HTTP-заголовок ("Content-type: text/html\n\n") обычно генерируется функцией PrintHeader. Секции <HEAD> и <BODY> создает подпрограмма HtmlTop. Ее необязательный строковый аргумент используется в качестве названия Web-страницы и вставляется в начало страницы как заголовок первого уровня. Пример (lib1.cgi):

#!/usr/local/bin/perl

require 'cgi-lib.pl';

print &PrintHeader;

print &HtmlTop  ("CGI  Example  Using  cgi-lib.pl");

Результат работы этого кода показан на рис. 19.1. Если для тега <BODY> требуется задать дополнительные атрибуты, придется вывести его вручную:

print  "<BODY  BGCOLOR=\"white\"  LINK=\"red\"><p>;

Подсказка. Конечно же, можно пропустить HtmlTop и создать собственные <HEAD> и <BODY>, выводя теги HTML со всеми необходимыми атрибутами.

Создаем заголовки HTML

Файл cgi-lib.pl не имеет специальных подпрограмм для создания разметки HTML, поэтому почти все, что требуется, приходится выводить вручную. Вот так создаются <H1> и <H2>, представляющие анкету:

print

"<H1>Here  is  the  Survey!</H1>

<H2>Please fill out our survey</H2>";

Результат работы этого кода показан на рис. 19.1.

Центрируем элементы HTML

Чтобы центрировать элементы HTML, добавьте тег <CENTER>:

print

"<CENTER><H1>Here is the Survey!</H1></CENTER>

<H2>Please  fill  out  our  survey</H2>";

Результат работы этого кода показан на рис. 19.1.

Создаем маркированный список

Для создания маркированного списка, указывающего пользователю на преимущества, которые он получит, заполнив анкету, выведите код HTML:

 

Создаем форму HTML

print

"Reasons  for  filling  out  our  survey:<P>

<UL>

<LI>Fame</LI>

<LI>Fortune</LI>

<LI>Fun</LI>

</UL>";

Результат работы этого кода показан на на рис. 19.1.

Создаем гиперссылку

Для создания гиперссылки вы просто печатаете ее разметку. Вот как создается ссылка на сайт CPAN в анкете, созданной при помощи lib1.cgi:

print

"If you would rather not fill out our survey, you might

be interested in

<A HREF=\"http://www.cpan.org/\">CPAN</A>";

Результат работы этого кода показан на рис. 19.1.

Создаем горизонтальную линию

Горизонтальную линию, равно как и другие элементы HTML, вы создаете, непосредственно печатая код. Вот пример создания горизонтальной полосы, такой же, как и в lib1.cgi:

print  "<HR>";

Результат работы этого кода показан на рис. 19.1.

Создаем форму HTML

Входящий в состав Perl пакет CGI.pm включает метод startform, но в cgi-lib.pl нет аналогичной подпрограммы, startform, то есть опять приходится выводить разметку самостоятельно. В метке <FORM> требуется установить атрибуты METHOD (метод передачи данных: POST или GET, мы используем POST) и ACTION (URL CGI-сценария, получающего данные). Также можно задать кодировку формы, но обычно это не нужно:

print

"<FORM  METHOD=\"POST\"

ACTION=\"http://www.yourserver.com/user/cgi/lib2.cgi\"

ENCTYPE=\"application/x-www-form-urlencoded\">";

После появления тега <FORM> все последующие элементы управления до </FORM> (см. раздел «Закрываем форму HTML») будут принадлежать текущей форме.

Работаем с текстовыми полями

Для создания текстового поля используется тег <INPUT> с атрибутом TYPE, установленным в text. Вот как это сделано в lib1.cgi  — текстовое поле получает имя text, и, поскольку изначально оно должно быть пустым, в качестве значения указана пустая строка:

print

"Please  enter  your  name:

<INPUT  TYPE=\"text\"  NAME=\"text\"  VALUE=\"\"><P>";

Вновь созданное текстовое поле показано на рис. 19.1. Как прочитать данные из него? Читайте следующий раздел.

Читаем данные из элементов управления HTML

Для чтения данных из различных элементов управления в cgi-lib.pl предназначе на подпрограмма ReadParse. Обычно создается хэш %in, с данными, полученны ми от элементов управления. Он адресуется по именам элементов. Например, в предыдущем параграфе мы задавали текстовое поле с именем text. Вот как создается хэш %in и выводятся данные, полученные от элемента управления:

require  'cgi-lib.pl';

if  (&ReadParse(*in))  {

print

"Your name is: <EM>, $in{'text'}, "</EM>.", "<p>"; }

Обратите внимание: значение ReadParse проверяется до обращения к хэшу %in. Если ReadParse возвращает значение ложь, значит, получить данные не удалось.

Результат работы этого кода показан на рис. 19.4.

Работаем с текстовыми областями

Для создания текстовой области используется тег <TEXTAREA>. Рассмотрим как lib1.cgi создает текстовую область, предназначенную для ввода мнения пользователя в анкете. В нашем примере текстовая область названа textarea, имеет значение по умолчанию "No opinion" и хранит 10 строк по 60 символов:

print

"Please  enter  your  opinion:<P><TEXTAREA  NAME=\"textarea\"

ROWS=10  COLS=60>No  opinion</TEXTAREA><P>";

Полученную текстовую область вы можете видеть на рис.  19.2. После нажатия кнопки Submit Query данные отправляются сценарию lib2.cgi. Содержимое текстовой области (см. рис. 19.4) выводится следующим образом:

if  (&ReadParse(*in))  {

print

"Your  opinions  are:  <EM>,  $in{'textarea'},  "</EM>.";

}

 

Работаем со списками

Работаем с кнопками с независимой фиксацией

Группы кнопок с независимой фиксацией (checkbuttons) создаются с помощью тега <INPUT>. Его атрибут TYPE устанавливается в значение checkbox, а всем переключателям одной группы присваивается одинаковое имя. Вот как это делается в lib1.cgi:

print

"Please  indicate  what  products  you  use:  <P>

<INPUT  TYPE=\"checkbox\"  NAME=\"checkboxes\"

VALUE=\"Shampoo\">

Shampoo

<INPUT  TYPE=\"checkbox\"  NAME=\"checkboxes\"

VALUE=\"Toophpaste\">

Toothpaste

<INPUT  TYPE=\"checkbox\"  NAME=\"checkboxes\"  VALUE=\"Bread\"

CHECKED>Bread

<INPUT  TYPE=\"checkbox\"  NAME=\"checkboxes\"  VALUE=

\"Cruise  missiles\"  CHECKED>Cruise  missiles  </P>";

Результат представлен на рис. 19.3. Пользователь может выбрать более одной кнопки в группы. Когда данные группы будут переданы сценарию lib2.cgi, $in('checkboxes') возвратит строку с несколькими значениями. Подпрограмма SplitParam пакета cgi-lib.pl делает из этой строки список:

if  (&ReadParse(*in))  {

print

"You  use  these  products:  <EM>",

join(", ", &SplitParam($in{'checkboxes'})), "</EM>,"; }

Результат вы можете наблюдать на рис. 19.4 — там выведены все выбранные пользователем кнопки, разделенные запятыми.

Работаем со списками

Список задается тегом <SELECT>. В нашем случае этот элемент предназначен для выбора уровня доходов. Пункты списка определяются тегами <OPTION>, а выделенный по умолчанию элемент помечается атрибутом SELECTED:

print

"Please  indicate  your  income  level:  <P><SELECT

NAME=\"list\"  SIZE=4>

<OPTION  VALUE=\"Highest\">Highest

<OPTION  SELECTED  VALUE=\"High\">High

<OPTION  VALUE=\"Medium\">Medium

<OPTION  VALUE=\"Low\">Low

</SELECT><P>";

Здесь же атрибутом SIZE определяется количество видимых элементов (то есть высота списка в строках). Результат работы этого фрагмента кода показан на

рис.19.3. Вот как lib2.cgi читает и выводит на экран (см. рис. 19.4) данные, введенные пользователем:

if  (&ReadParse(*in))  {

print

"Your  income  level  is:  <EM>",$in{'list'},

"<EM>.",  <p>;

}

Работаем с кнопками с зависимой фиксацией

Для создания группы кнопок с зависимой фиксацией (radiobuttons) использует ся тег <INPUT> с атрибутом TYPE, установленным в значение radio. Всем кнопкам в группе присваивается общее имя. Вот как это делается в lib1.cgi (первая кнопка имеет атрибут SELECTED, то есть выбирается по умолчанию):

print

"Please indicate  your  day  of  the  week:  <P>

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"1\"CHECKED>Sunday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"2\">Monday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"3\">Tuesday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"4\">Wensday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"5\">Thursday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"6\">Friday

<INPUT TYPE=\"radio\" NAME=\"radios\" VALUE=\"7\">Saturday";

Результат можно увидеть на рис. 19.3. Сценарий lib2.cgi проверяет, какая кнопка была выбрана, и выводит ее значение (см. рис. 19.4):

if (&ReadParse(*in))  {

print

"Today is day <EM>", $in{'radios'}, "</EM> of the week.";

}

Работаем с раскрывающимися списками

В раскрывающемся списке виден лишь один элемент. Создается он как обычный список тегом <SELECT>, но без атрибута SIZE (по умолчанию он равен единице). Например, вот так в сценарии lib1.cgi создается список, предназначенный для ввода данных о допустимом количестве нежданной почты:

print

"Thank  you  for  filling  out  our  Survey.  Please  indicate  how  much  unsolicited  mail  you  like  to  get:

<SELECT  NAME=\"popupmenu\">

<OPTION  VALUE=\"Very  much\">Very  much

<OPTION  VALUE=\"A  lot\">A  lot

<OPTION  VALUE=\"Not  so  much\">Not  so  much

<OPTION  VALUE=\"None\">None

</SELECT><P>";

 

Закрываем форму HTML

Результат показан на рис. 19.3. Сценарий lib2.cgi проверяет выбор пользователя и выводит его на экран (см. рис. 19.4):

if (&ReadParse(*in))  {

print

"How much unsolicited mail you like: <EM>",

$in{'popupmenu'}, "</EM>.",  "<p>"; }

Работаем со скрытыми полями данных

Скрытые элементы управления создаются тегом <INPUT> с атрибутом TYPE, установленным в hidden. Ниже определяется скрытое поле данных с именем hiddendata и текстом Rosebud:

print

"<INPUT TYPE=\"hidden\" NAME=\"hiddendata\" VALUE=\"Rosebud\">";

Вот часть кода lib2.cgi, читающая текст из скрытого элемента управления и выводящая его на экран (см. рис. 19.4):

if (&ReadParse(*in))  {

print

"The hidden data is <EM>", $in{'hiddendata'}, "</EM>."; }

Создание кнопок Submit и Reset

Для создания кнопки подтверждения запроса (Submit) используется тег <INPUT> с атрибутом TYPE, установленным в submit. Кнопки отмены запроса и очистки формы (Reset) задаются тем же тегом, но TYPE устанавливается в reset. Вот как в сценарии lib1.cgi создаются кнопки отмены и подтверждения:

print

"<CENTER>

<INPUT  TYPE=\"submit\"  NAME=\"submit\">

<INPUT  TYPE=\"reset\">

</CENTER>";

Результат показан на рис. 19.3 в начале главы. Когда пользователь нажимает кнопку подтверждения, данные элементов управления формы HTML передаются сценарию lib2.cgi; если нажата кнопка отмены, данные сбрасываются, то есть заменяются значениями по умолчанию.

Закрываем форму HTML

Форма HTML закрывается тегом </FORM>:

print  "</FORM>";

Когда lib1.cgi печатает </FORM>, она завершает форму, объединяющую элементы управления. Остается лишь завершить саму страницу HTML. Об этом читайте в следующем разделе.

Завершаем документ HTML

Подпрограмма HtmlBot пакета cgi-lib.pl закрывает Web-страницу. Она возвращает строку "</BODY>\n</HTML>\n":

print  &HtmlBot;

Частенько эта строка оказывается последней в сценарии. Не стали исключением и lib1.cgi с lib2.cgi.

Выводим все переменные

В этой главе значения данных, переданных элементами управления HTML, выводились непосредственно оператором print. Но это можно сделать и проще: то же самое делает подпрограмма PrintVariables, но только в фиксированном формате. Например, можно заменить lib2.cgi кодом, приведенным ниже:

#!/usr/local/bin/perl5

require  'cgi-lib.pl';

print  &PrintHeader

print  &HtmlTop  ("CGI  Example  Using  cgi-lib.pl");

if  (&ReadParse(*in))

{ print  &PrintVariables; }

print  &HtmlBot;

Результат показан на рис. 19.5. Все данные, переданные lib2.cgi, действительно есть, но не сопровождаются пояснительным текстом. В общем, PrintVariables полезна при отладке, но вряд ли встретится в завершенном проекте.

Рис. 19.5. Использование подпрограммы PrintVariables