Библиотека сайта rus-linux.net
Ошибка базы данных: Table 'a111530_forumnew.rlf1_users' doesn't exist
Создание Excel-файлов в Perl
Оригинал: Generating Native Excel Files in Perl
Автор: Mike Diehl
Дата: 11 августа 2008
Перевод: Александр Тарасов aka oioki
Дата перевода: 9 декабря 2008
За много лет мне приходилось создавать тысячи отчетов для клиентов и коллег. Обычно я пользовался веб-программой, генерирующей отчеты в HTML-формате или в формате, где разделителем служит запятая (CSV). HTML-формат намного симпатичнее и обычно этого пользователям хватает. Данные в формате CSV можно с легкостью импортировать в табличный процессор Open Office Calc или Excel. С помощью этих программ пользователи выполняют форматирование исходных данных и затем отправляют окончательный отчет куда нужно. Причем зачастую те, кто должны периодически отправлять отчеты одного и того же типа, неоднократно выполняют это форматирование, по сути каждый раз выполняя вручную одну и ту же работу. Они рады, что у них есть данные и не сильно беспокоятся о том, насколько красиво они представлены. Но есть решение лучше.
Perl-модуль Spreadsheet::WriteExcel
позволяет генерировать данные,
форматировать их и выводить в родной формат табличного процессора Excel. Мне
остается лишь дать пользователям гиперссылку на отчеты.
Рассмотрим простой CGI-скрипт, использующий модуль Spreadsheet::WriteExcel. Код, представленный в этой статье, основан на программе, написанной мной некоторое время назад для пользователей. Он не такой красивый (или корректный) какой должен быть. Это лишь пример того, чего можно достичь с помощью модуля Spreadsheet::WriteExcel. Однако уверяю вас, результаты работы даже такой программы впечатляют!
Вот основа скрипта:
#==================================================== #!/usr/bin/perl use DBI; use CGI; use Spreadsheet::WriteExcel; $dbh = DBI->connect("dbi:Pg:dbname=test;host=db.example.com", "postgres", "passwd") || die "Can't connect to database.\n"; $cgi = new CGI; print $cgi->header("application/excel"); #====================================================
В этом куске кода мы загружаем DBI-модуль - впоследствии нам придется запрашивать данные у СУБД PostgreSQL. Затем мы загружаем CGI-модуль, упрощающий написание CGI-скриптов, а мы ведь любим писать легко, ведь так? Затем мы подключаемся к базе данных и получаем ее handle-идентификатор для дальнейшего использования. Аналогично, получаем идентификатор CGI, для дальнейшей работы с модулем CGI.
Эта программа решает единственную задачу - возвращает файл в формате Excel, поэтому MIME-тип задается жестко. Если ваш скрипт содержит форму извлечения данных, или выпадающий список, в котором нужно выбрать отчет, тогда вызов header() стоит переместить в более подходящее для этого место.
Пора генерировать наш отчет.
#==================================================== my $workbook = Spreadsheet::WriteExcel->new("-"); my $worksheet = $workbook->add_worksheet("Cover Sheet"); my $bold = $workbook->add_format(); $bold->set_bold(); $worksheet->write(5, 0, "Division Number:", $bold); $worksheet->write(5, 1, $division_number); #====================================================
В этой части мы создаем идентификатор типа Spreadsheet::WriteExcel
и сохраняем его в переменной $workbook
. Здесь мы передаем конструктору объекта не имя файла, а просто "-". Это означает, что результирующая электронная таблица будет выведена не в файл, а на стандартный вывод STDOUT. Наш отчет будет содержать простой титульный лист, поэтому давайте сейчас добавим лист. Мне нужно вывести надпись "Division Number" полужирным шрифтом, для этого нужно проделать несколько шагов. Во-первых, создадим новый объект типа "формат", с помощью метода add_format()
. Затем этому формату можно приписывать различные атрибуты; в нашем случае нужно сделать его полужирным. Мы могли бы сделать буквы красными на зеленом фоне, наклонный шрифт и выравнивание справа. Но зачем? В нашем случае мы просто хотим выделить надпись, поэтому мы всего лишь сделали ее полужирной.
Наконец, мы помещаем данные в наш лист, с помощью метода write()
. Сначала создаем полужирную метку "Division Number", в первом столбце шестой строки. Обратите внимание, что в Excel строки и столбцы нумеруются с единицы, а в Spreadsheet::WriteExcel - с нуля. Еще одно замечание - порядок индексации - "строка, столбец", а не "горизонтальный индекс, вертикальный индекс". Когда я впервые воспользовался Spreadsheet::WriteExcel, я этого не понял, и удивлялся, почему мои таблицы выглядели столь забавно.
Переменная $division_number
не пуста, она содержит целочисленное значение, которое должно быть где-либо определено.
Теперь добавим второй лист и разместим на нем какие-либо настоящие данные.
#==================================================== $worksheet = $workbook->add_worksheet("Customer Details"); my $current = $workbook->add_format(); my $overdue = $workbook->add_format(); $current->set_color('green'); $current->set_num_format('$0.00'); $overdue->set_color('red'); $overdue->set_num_format('$0.00'); $col=0; foreach $i ("Customer Name", "Customer Number","Phone Number","Amount Due") { $worksheet->write(0, $col++, $i, $bold); } $sth = $dbh->prepare("select name, number, phone, due from customers where division=$division_number order by name"); $sth->execute(); my $row=1; while ($a = $sth->fetchrow_hashref()) { $worksheet->write($row,0, $a->{name}, $bold); $worksheet->write($row,1, $a->{number}); $worksheet->write($row,1, $a->{phone}); if ($a->{due} > 1000) { $worksheet->write($row,1, $a->{due},$overdue); } else { $worksheet->write($row,1, $a->{due}, $current); } $row++; } #====================================================
Теперь код выглядит действительно интересно. После создания нового листа, следующим шагом мы создаем еще два формата. Один формат будет красным - для клиентов-должников, и зеленым - для обычных клиентов. Затем, денежные суммы мы хотим отображать со знаком доллара, поэтому устанавливаем соответствующий формат ячейки.
Далее следует шапка таблицы. Я ленив и не люблю добавлять столбцы вручную, поэтому шапка таблицы печатается в цикле for. Кроме того, добавить новый столбец не представляет никаких сложностей - нужно лишь добавить его в список цикла for.
Теперь создаем запрос к базе данных. В данном случае мы запрашиваем список клиентов - имя, номер, номер телефона и величину долга.
Имя выводим полужирным шрифтом, далее следует номер клиента и телефонный номер, все это обычным шрифтом.
Последний столбец немного интереснее. В последнем столбце мы проверяем, задолжал ли клиент нам более чем $1000. Если это так, мы раскрашиваем его ячейку в красный цвет, тем самым выделяя его. Иначе раскрашиваем в зеленый формат $current
.
После того, как мы вывели всю строку, переходим на следующую, с помощью оператора $row++
и забираем следующую запись из нашего набора данных.
Мы почти закончили.
#==================================================== $dbh->finish(); $workbook->close(); exit; #====================================================
Всегда нужно убираться за собой, а именно завершать работу с идентификатором БД, вызывая метод finish()
. Закрытие идентификатора таблицы методом close()
приводит к очистке буферов и выдаче готовой таблицы на стандартный вывод STDOUT.
Конечно, если бы мы писали генератор CSV-файла, то программа уместилась бы в 10 строк. Но тогда бы пользователю пришлось совершать дополнительную работу, а именно импортирование данных и форматирование получившейся таблицы. В то же время написанная нами только что программа проста, а результаты ее работы намного симпатичнее. На рисунке показан результат работы скрипта, после того как я вручную изменил ширину некоторых столбцов. Модуль Spreadsheet::WriteExcel позволяет выполнять и другие, более сложные варианты форматирования, например, помещение формул в ячейку. Модуль настолько прост в использовании, а отзывы пользователей положительны, что я даже не хочу больше заниматься CSV-отчетами.