В этой лекции описываются средства создания форматированных отчетов и технология формирования в Perl отчетов с помощью форматов, которая позволяет представлять выходные данные в форме, удобной для просмотра человеком или для вывода на принтер.
Цель лекции: научиться описывать формат заголовка страницы и формат строк отчета, разобраться с тонкостями описания полей в форматах. На примерах освоить заполнение формата отчета данными и вывод отчетов в разные выходные потоки.
Вся информация, хранящаяся в компьютерных системах, в конечном счете предназначена для людей, а людям нравится видеть ее упорядоченной и аккуратно представленной. Представление данных, специально подготовленное для просмотра на экране или на бумаге, принято называть отчетом. Отчеты создаются для отображения информации в удобном для восприятия виде, поэтому при формировании отчета данные сортируются, обобщаются, снабжаются пояснительными заголовками и располагаются по возможности компактно и эстетично. Все мы постоянно сталкиваемся с отчетами, когда видим данные в "причесанном" отформатированном виде: это списки, реестры, таблицы, формы, карточки и другие документы. Одной из целей разработки Perl было создание удобного инструмента для подготовки отчетов. Perl предоставлял механизм для создания простых отчетов и диаграмм, начиная с самых ранних версий, и он незначительно изменился с тех пор. Хотя имеющиеся в нем средства работы с отчетами весьма лаконичны, они достаточны, чтобы в большинстве ситуаций приготовить данные для форматированного постраничного вывода на экран, печатающее устройство или в файл. Подготовка отчета начинается с описания его формата.
Внешний вид отчета и расположение данных в нем описывается с помощью формата. Формат - это шаблон отчета, который состоит из литеральных строк (заголовков, пояснительных надписей, констант) и описания полей, куда при выводе отчета будут подставляться значения данных. Формат описывается с помощью ключевого слова format, после которого указывается имя формата и знак равенства. Далее со следующей строки располагается описание строк формата отчета (хотя они могут и отсутствовать). Описание формата заканчивается отдельной строкой, состоящей из единственной точки. Например, описание формата с именем FORMAT_NAME будет выглядеть так:
format FORMAT_NAME =
описание формата отчета
.
Форматы не исполняются, поскольку являются описаниями, как и определения подпрограмм, поэтому они могут помещаться в любом месте программного файла. Обычно они располагаются в конце исходного текста программы перед определением подпрограмм. Имя формата представляет собой правильный идентификатор. Его принято записывать заглавными буквами, и оно обычно совпадает с именем файлового дескриптора выходного потока, куда будет выводиться отчет. Имена форматов хранятся в отдельном пространстве имен, поэтому они не конфликтуют с именами переменных, подпрограмм, меток и файловых манипуляторов. Имя текущего формата для каждого потока хранится в специальной переменной $~ (или $FORMAT_NAME при включенной прагме use English). Если имя формата в описании не указано, подразумевается STDOUT.
Кроме основного формата, часто требуется описание отдельного формата для шапки страницы, которая будет выводиться в начале каждой страницы отчета. Формат заголовка страницы отчета отличается от основного формата тем, что к имени формата добавляется '_TOP'. Он описывается так:
format FORMAT_NAME_TOP =
описание формата шапки отчета
.
Имя текущего формата для шапки страницы, связанного с текущим выходным потоком, хранится в специальной переменной $^ ($FORMAT_TOP_NAME). При выводе отчета, если очередная строка не помещается на текущей странице, в выходной поток выводится символ прогона страницы, счетчик страниц увеличивается на единицу, и в начале следующей страницы в текущий поток выводится заголовок страницы отчета.
В описании формата отчета применяется несколько видов строк. Строка шаблонов (или строка полей) описывает поля отчета для заполнения данными. За каждой строкой шаблона следует одна или несколько строк аргументов (или строк значений), содержащих список источников данных (литералов, имен переменных и выражений, разделенных запятыми) для заполнения полей. Литеральная строка содержит информацию, без изменения выводимую в отчет. После литеральной строки строка значений не указывается. Строка комментариев, начинающаяся с символа #, служит для записи в формате примечаний и не выводится в отчет. В следующем примере приводятся образцы разных типов строк в описании формата:
format STDOUT =
1. Строка шаблонов содержит поля: @<<<<< и @###.##
'поле1', $field2
# 3. Комментарий: во 2-й строке данные для вставки в 1-ю
4. Литеральная строка выводится "как есть".
.
В комментариях и литеральных строках нет ничего особенного, самое интересное содержится в строках шаблонов, содержащих описания полей отчета.
Поле отчета - это пространство указанной ширины, расположенное в определенном месте отчета, куда помещаются данные в нужном представлении. (Например, конкретное поле в отчете может быть описано таким образом - "в начале первой строки заголовка каждой страницы должна выводиться текущая дата в следующем виде: день (две цифры с ведущим нулем), месяц (две цифры с ведущим нулем) и год (четыре цифры с ведущим нулем), разделенные точками".) Поля отчета в описании формата представлены в виде так называемых поледержателей (fieldholders). Поледержатель (или переменная поля) представляет из себя шаблон, описывающий тип поля, его ширину в символах, выравнивание значения внутри поля и другие преобразования, которые нужно выполнить над данными при размещении их в этом поле во время формирования отчета. Поледержатель начинается с символа начала шаблона (@ или ^), за которым следуют символы описания шаблона. Число символов в шаблоне, включая символ начала шаблона, определяет ширину помещаемых в отчет данных. Несколько примеров поледержателей с пояснениями приведены в таблице 10.1:
Таблица 10.1. Примеры описания полей в формате отчета
Поледержатель | Описания формата и преобразований |
---|---|
@<<<<<<<<<<< | Вывести текстовое значение в поле шириной в 12 символов. Выровнять его по левому краю, дополнив при необходимости пробелами справа до ширины поля. Слишком длинное значение усечь до ширины поля |
@<<<<<<<<... | Аналогично предыдущему примеру, но с выведением в конце поля многоточия, если значение усечено |
@####.### | Вывести числовое значение в поле шириной в 9 символов, отведя 5 цифр под целую и 3 цифры - под дробную часть числа. Выровнять его по правому краю и дополнить при необходимости целую часть числа пробелами слева до ширины поля и округлить дробную часть до 3 знаков. При попытке вывести число, целая часть которого не умещается в ширину поля, заполнить поле символом '#' как признак |
@0###.### | Так же, как в предыдущем примере, но с дополнением целой части значения ведущими нулями до ширины поля |
Полный список символов, применяемых для описания полей и форматов, приводится в таблице 10.2.
Таблица 10.2. Символы, применяемые при описании полей и форматов
Символ | Описание | Примеры использования |
---|---|---|
@ | начало обычного поля | @ @<< @||| @>> @## |
^ | начало специального поля | ^ ^<< ^||| ^>> ^## |
< | текстовое поле с выравниванием значения влево и добавлением пробелов справа | @<<<<< ^<<< |
| | текстовое поле с центрированием значения и добавлением пробелов с обеих сторон | @||||| ^||| |
> | текстовое поле с выравниванием значения вправо и добавлением пробелов слева | @>>>>> ^>>> |
# | числовое поле с выравниванием значения вправо с добавлением пробелов слева | @#### ^### |
0 | (вместо первого #) числовое поле с выравниванием значения вправо и добавлением нулей слева | @0### ^0## |
. | десятичная точка в числовом поле | @.### @0##.## |
... | закончить текстовое поле многоточием, чтобы показать усечение значения | @<<<<<... |
@* | поле переменной ширины со значением, состоящим из нескольких строк | @* |
^* | поле переменной ширины для следующих строк многострочного значения | ^* |
~ | подавление вывода строки с пустыми значениями полей | ^* ~ |
~~ | повторять строку, пока все значения полей не станут пустыми | ~~ ^* |
{} | группировка списка значений, который располагается на нескольких строках аргументов | {$one, $two, $three } |
# | (первым символом в строке) строка комментария в описании формата (не может располагаться между строкой шаблонов и строкой аргументов) | # это комментарий |
. | (единственным символом на отдельной строке) конец формата | format REPORT = описание формата . |
То, как применяются поледержатели при описании формата, можно увидеть из следующего примера:
format STDOUT =
Учетная карточка пользователя N @0###
$number
---------------------------------------------------------
Фамилия @<<<<<<<<<<<<<< | Login @<<<<<<<
$last_name, $login
Имя @<<<<<<<<<<< | Группа @<<<<<<<<<<<<<<<<<
$first_name, $group
Отчество @<<<<<<<<<<<<<<<<<< |
$middle_name
E-mail @<<<<<<<<<<<<<<<<<< | Телефон @>>>>>>>>>
$email, $phone
Ограничение дискового пространства @####.## Мегабайт
$quota
---------------------------------------------------------
Дата регистрации @# @<<<<<<<<< @### года
{$day,
$month_name,$year}
.
Из примера понятно, что формат отчета записывается в виде, максимально похожем на представление страницы отчета на экране или на бумаге. Каждому полю в строке шаблонов должно соответствовать скалярное значение в строке аргументов. Имена переменных в строке аргументов для наглядности часто располагаются под соответствующими поледержателями в предыдущей строке шаблонов, хотя это совсем не обязательно. Список переменных может находиться на нескольких строках аргументов (как это сделано в описании последней строки формата); в этом случае он должен заключаться в фигурные скобки. Имейте в виду, что скалярные переменные и массивы в строке аргументов разворачиваются в единый список скаляров, из которого по порядку берутся значения для заполнения полей.
Для форматированного вывода отчетов применяется функция write(), которая оформляет очередную порцию данных в соответствии с форматом отчета и выводит их в указанный выходной поток. Обращение к функции write() иногда называют вызовом формата. В качестве аргумента функции write() может передаваться файловый манипулятор выходного потока. Вызванная без аргументов, она направляет отчет в текущий выходной поток. Перед обращением к ней нужно заполнить новыми данными переменные, перечисленные в строках аргументов текущего формата. Обычно write() вызывается в цикле для вывода в отчет очередной строки. По историческим причинам для заполнения полей отчета часто используются глобальные переменные. Лексические переменные, объявленные с помощью my(), доступны в формате только тогда, когда формат и лексические переменные объявлены в одной области видимости. Подробно об областях видимости переменных будет рассказано в лекции 12.
Если для выходного потока описан формат начала страницы отчета, то перед выводом строк отчета функцией write() в начале каждой страницы автоматически размещаются данные шапки страницы в соответствии с форматом начала страницы. Программа для вывода данных по формату, заданному в предыдущем примере, может выглядеть таким образом:
# данные в записи входного файла разделены запятыми
open my $in, '<', 'users.txt' or die;
while (my $line = <$in>) {
local ($last_name, $first_name, $middle_name,
$login, $group, $email, $phone, $quota, $number,
$day, $month_name, $year) = split ',', $line;
# данные для отчета помещены в переменные
write STDOUT; # данные выводятся в STDOUT по формату
}
close $in or die;
# здесь располагается описание формата...
В результате выполнения этой программы в поток STDOUT будет выведен отчет, состоящий вот из таких карточек:
Учетная карточка пользователя N 00001
---------------------------------------------------------
Фамилия Wall | Login larry
Имя Larry | Группа root
Отчество |
E-mail larry@wall.org | Телефон +123456789
Ограничение дискового пространства 9876,54 Мегабайт
---------------------------------------------------------
Дата регистрации 18 декабря 1987 года
В каждую из выводимых в отчет карточек помещаются данные из одной записи входного файла.
Без указания файлового манипулятора вывод отчета функцией write() и обычный вывод данных функцией print() происходит в выходной поток по умолчанию (STDOUT). С помощью функции select() можно назначить другой выходной поток по умолчанию. При вызове ей передается файловый манипулятор, и она переключается на новый поток, который становится текущим выходным потоком по умолчанию. Функция select() возвращает имя ранее выбранного манипулятора, и это значение используется для восстановления предыдущего выходного потока. Это происходит таким образом:
$old_handle = # сохранить файловый манипулятор
select $new_handle; # переключиться на новый поток
write; # вывести в новый поток
select $old_handle; # восстановить предыдущий поток
При формировании сложного отчета может потребоваться возможность переключаться на разные форматы отчета. Установить для какого-либо потока определенный формат отчета можно путем присваивания имени формата переменной $~ ($FORMAT_NAME). Подобным же образом для конкретного потока устанавливается нужный формат заголовка страницы отчета: переменной $^ ($FORMAT_TOP_NAME) присваивается имя формата для шапки страницы. Это делается так:
$old_handle = select $out; # выбрать поток для отчета
$^ = 'REPORT_TOP'; # назначить формат для шапки отчета
$~ = 'REPORT'; # назначить формат для отчета
write $out; # вывести в $out по формату REPORT
select $old_handle; # вернуться к предыдущему потоку
Назначать для определенного потока формат отчета и заголовок страницы гораздо удобнее с помощью функций format_name() и format_top_name() из стандартного библиотечного модуля FileHandle. Это выглядит так:
use FileHandle; # подключить модуль работы с файлами
# назначить для потока $report формат отчета REPORT
format_name $report REPORT;
# назначить для потока $report формат заголовка PAGE
format_top_name $report PAGE;
# используя назначенные форматы,
write $report; # вывести строку отчета в $report
Обратите внимание, что при обращении к функциям format_name() и format_top_name() после файлового манипулятора не ставится запятая, так же как при вызове функции print().
Пока что в примерах использовались только обычные поля (regular fields), которые описываются поледержателями, начинающимися с символа @. Поледержатели, описание которых начинается с символа ^, представляют так называемые специальные поля (special fields), обладающие возможностью дополнительной обработки данных. Так, специальные числовые поля (например, ^###), содержащие неопределенное значение (undef), заполняются пробелами. Обычные числовые поля (например, @###) в этом случае выводят нули. Это демонстрирует следующий пример:
format STDOUT =
обычное:'@##.##' специальное:'^####'
undef, undef
.
write STDOUT; # вывод данных по формату в STDOUT
# выведет: обычное:' 0.00' специальное:' '
Специальные текстовые поля (например, ^<<<) используются для вывода в отчет данных, располагающихся на нескольких строках.
Есть несколько способов описать в формате данные, занимающие в отчете несколько строк. Если нужно поместить на странице отчета многострочное текстовое значение, то можно воспользоваться поледержателем @*, который просто выведет значение полностью, сохраняя имеющиеся в нем все символы перевода строки, кроме последнего. Например, так делается в этой программе:
format STDOUT =
Гамлет:
@*
$multi_line_text
(У. Шекспир)
.
$multi_line_text = "Быть\nИли не быть?\nВот в чем вопрос.";
write STDOUT;
В результате ее выполнения будет выведено известное высказывание с сохранением его разбивки на несколько строк:
Гамлет:
Быть
Или не быть?
Вот в чем вопрос.
(У. Шекспир)
Поледержатель ^* описывает в формате текстовое поле, значение которого должно выводиться на несколько строк определенной ширины. В строке аргументов такому полю должно соответствовать имя переменной: скаляра, элемента массива или хэша. Когда значение этой переменной помещается в поле отчета, из переменной извлекается часть текстового значения до первого разделителя строк. Если переменная употребляется в формате несколько раз, то ее значение уменьшается на число извлеченных символов при каждом обращении к ней. Вот как это выглядит на примере:
# выводимое многострочное значение
$text = "Что значит имя?\nРоза пахнет розой\n"
. "Хоть розой назови ее, хоть нет.";
write STDOUT;
# описание формата отчета
format STDOUT =
^*
$text
^*
$text
^* ~
$text
^* ~
$text
У.Шекспир, "Ромео и Джульетта"
.
Обратите внимание, что в формате для вывода значения переменной $text предусмотрены четыре строки. Причем первые две строки отчета (с шаблоном ^*) будут выводиться в любом случае: даже если $text содержит только одну строку текста, вторая строка будет заполнена пробелами. Чтобы не выводить пробельных строк, в описании третьей и четвертой строк указан шаблон подавления пустых строк (одна тильда, ~), который может располагаться в любом месте строки шаблона. После выполнения приведенной программы текст будет выведен в таком виде:
Что значит имя?
Роза пахнет розой
Хоть розой назови ее, хоть нет.
У.Шекспир, "Ромео и Джульетта"
Если заранее неизвестно минимальное количество выводимых строк, можно применить шаблон повторения многострочного поля (две тильды подряд в любом месте строки, ~~). В этом случае строка шаблона будет применяться многократно, пока не будет исчерпано многострочное значение, при этом пустые строки выводиться не будут. Тильды в шаблоне подавления пустых строк и в шаблоне повторения строк при выводе отчета заменяются на пробелы. Таким образом, формат в последнем примере можно заменить на следующий:
format STDOUT =
^* ~~
$text
У.Шекспир, "Ромео и Джульетта"
.
В результате будет выведен точно такой же отчет, как и с использованием предыдущего формата, но последний формат гораздо короче и выводит любое количество строк.
Для описания в формате текстового значения, которое должно выводиться на несколько строк, применяется поледержатель следующего вида: ^<<<<<. Это специальное поле иногда называется "заполняемым полем". Оно предназначается для форматирования текстового значения, которое при выводе в отчет делится на строки, не превышающие ширину шаблона. Деление на строки производится по границам слов. Источником выводимого текста обязательно должна быть переменная со скалярным значением, из которой при каждом ее употреблении в формате извлекается столько слов, сколько поместится в соответствующем поле отчета. Заполнение шаблона текстом и вывод отчета в несколько строк иллюстрирует следующий пример:
my $shakespeare = 'Две равно уважаемых семьи '
. 'В Вероне, где встречают нас событья, '
. 'Ведут междоусобные бои '
. 'И не хотят унять кровопролитья.';
my $text = $shakespeare;
write STDOUT;
# описание формата вывода
format STDOUT =
~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$text
У.Шекспир, "Ромео и Джульетта"
.
В этом примере для организации неявного цикла вывода также применяется шаблон повторения строк, поскольку неизвестно, сколько строк будет заполнено выводимым текстом. При выполнении примера будет выведен текст в таком виде:
Две равно уважаемых семьи В Вероне,
где встречают нас событья, Ведут
междоусобные бои И не хотят унять
кровопролитья.
У.Шекспир, "Ромео и Джульетта"
Подобным образом в отчет выводятся блоки текстовой информации: примечания, описания, адрес и т. п.
Кроме переменных, в которых хранятся имена формата ($~) и заголовка страницы формата ($^), есть еще несколько специальных переменных для хранения информации о форматах. Номер текущей страницы выводимого отчета содержится в переменной $% ($FORMAT_PAGE_NUMBER), и ее часто включают в формат отчета. В переменной $= ($FORMAT_LINES_PER_PAGE) хранится число строк на странице: по умолчанию - 60, но его можно изменить на нужное значение перед выводом отчета. В переменной $- ($FORMAT_LINES_LEFT) содержится число оставшихся на странице строк. Переменная $^L ($FORMAT_FORMFEED) хранит символ перевода страницы (formfeed character), который используется в отчетах для прогона принтера до новой страницы.
Специальная переменная $: ($FORMAT_LINE_BREAK_SEPARATOR) содержит набор символов разрыва строки, после которых строка может быть разделена при заполнении в формате специальных полей продолжения. Специальная переменная $^A ($ACCUMULATOR) является аккумулятором выводимых данных для функций formline() и write(), в котором накапливаются данные отчета перед их отправкой в выходной поток. При считывании данных для отчета из файла может пригодиться переменная $. ($INPUT_LINE_NUMBER), в которой хранится номер прочитанной из входного файла строки, что можно использовать для нумерации строк в отчете.
Дополнительные сведения о форматах и отчетах в Perl можно узнать из стандартной документации, обратившись за помощью к утилите
perldoc perlform
В завершение лекции приведем пример законченной программы (с образцом исходных данных), выводящей отчет о книгах по языку Perl.
open my $report, '>', '/report.txt' or die;
$old_handle = select $out; # выбрать поток для отчета
select $report;
$^ = 'HEAD';
$~ = 'REPORT';
# описание форматов для отчета
while() { # чтение одной записи данных
($authors, $title, $year, $nick) = split ':';
write $report; # вывод одной строки отчета
}
close $report or die;
# формат для заголовка страницы
format HEAD =
Классические книги по языку Perl
издательства O'Reilly
Лист @#
$%
---------------+--------------------+----+------------
Авторы | Заглавие |Год | Прозвище
---------------+--------------------+----+------------
.
format REPORT =
^<<<<<<<<<<<<<<|^<<<<<<<<<<<<<<<<<<<|@###|@>>>>>>>>>>>
$authors, $title, $year, $nick
^<<<<<<<<<<<<<<|^<<<<<<<<<<<<<<<<<<<| | ~~
$authors, $title
---------------+--------------------+----+------------
.
__DATA__
Cozens S.:Advanced Perl Programming,2nd ed.:2005:Panther Book
Friedl J.E.F.:Mastering Regular Expressions:1997:Owls Book
...
Результатом работы этой программы будет такой отчет, размещенный в файле report.txt:
Классические книги по языку Perl
издательства O'Reilly
Лист 1
---------------+--------------------+----+------------
Авторы | Заглавие |Год | Прозвище
---------------+--------------------+----+------------
Cozens S. |Advanced Perl |2005|Panther Book
|Programming,2nd ed. | |
---------------+--------------------+----+------------
Friedl J.E.F. |Mastering Regular |1997| Owls Book
|Expressions | |
---------------+--------------------+----+------------
Schwartz R.L., |Learning Perl, 4th |2005| Llama Book
Phoenix T., |ed. | |
brian d foy | | |
---------------+--------------------+----+------------
Конечно, изученные в этой лекции средства отчетов не могут сравниться с современными специализированными построителями отчетов, но во многих случаях бывает достаточно форматирования выходных данных в виде простых отчетов, которое в Perl делается достаточно легко, просто и наглядно. Эта лекция была "лирическим отступлением" перед тем как начать углубленное изучение техники программирования на языке Perl.