Наши партнеры

UnixForum






Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> СУБД для Linux

Чтение Excel-файлов в Perl

Оригинал: Reading Native Excel Files in Perl
Автор: Mike Diehl
Дата: 2 сентября 2008
Перевод: Александр Тарасов aka oioki
Дата перевода: 9 декабря 2008

В предыдущей статье для сайта Linux Journal я рассказывал о веб-скрипте, выполняющем SQL-запрос к базе данных и на основе полученных данных создающем Excel-файл. Та статья была основана на программе, которую я писал для клиента. Сегодня я хочу рассказать о прямо противоположной задаче; наша программа будет принимать на входе таблицу Excel, извлекать из нее данные и вводить ее в базу данных. Эта программа также основана на программе для одного из моих клиентов. В общем, обе программы были написаны для одного и того же клиента.

Раньше этот мой клиент загружал данные в базу данных через обычный текстовый файл. Они работали со своими данными в Excel, а затем, когда требовалось положить эти данные в базу, делали экспорт. Однако у них были сложности в запоминании, разделяются ли их данные запятыми или табами. Одно лишь это было проблемой. К тому же не единственной.

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

John,Q,Public,15055551234

Очевидно, что мы храним здесь имя, первую букву отчества, фамилию и телефонный номер. Но такой формат не очень хорош для хранения сведений о сыне моего друга, его зовут John Q. Public, Jr:

John,Q,Public, Jr.,15055551234

Возможно, формат с разделением полей табами был бы более удобен:

John Q	Public, Jr.	15055551234

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

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

Так что хранить данные в формате Excel - хороший выбор. Вам может показаться, что я преувеличиваю, но у одного моего клиента постоянно возникали именно такие проблемы, это не шутка. Поэтому я решил пользоваться форматом Excel для ввода и вывода данных. Это было удобно для моего клиента, ведь он был знаком с обработкой данных в Excel, а я мог протестировать скрипты в OpenOffice Calc.

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

В этой статье будет приведен пример сценария, демонстрирующего возможности использования Perl-модуля Spreadsheet::ParseExcel.

Для демонстрации представим, что есть какие-то люди, которые ходят по школам и собирают информацию об учениках. Мы будет хранить полное имя и телефонный номер. Также мы хотим знать, в какой школе ребенок учится, и какая у него средняя оценка (от высшей A до F). Если средняя оценка A, тогда нужно раскрасить его ячейку в зеленый цвет; если же это плохой ученик, и его средняя оценка F, раскрасим его в красный. Для каждой школы имеется свой лист, названный по номеру школы. Очевидно, этот метод не очень эффективен, но как я уже сказал, этот вымышленный пример лишь демонстрирует нам возможности упомянутого модуля Perl.

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

Для начала создадим базу данных, в которой мы будем хранить результаты выборки. Потребуется лишь одна таблица, вот такая:

drop table children;

create table children (
       name varchar(50),
       phone char(11),
       school varchar(50),
       code char(6)
);

Знаю, varchar(50) на имя ребенка и его школу - возможно, многовато. Обратите внимание на поле code типа char(6). В этом поле будет храниться цветовая информация, в шестнадцатиричном формате RRGGBB.

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

<html>
<head>
<title>Страница загрузки данных</title>
</head>
<body>
Загрузка данных
<form name=main method=POST action=/cgi-bin/load.pl ENCTYPE="multipart/form-data">
<input type=file name=file>
<input type=submit>
</form>
</body>
</html>

Нужно сохранить этот код в html-файл куда-нибудь на веб-сервер. Тогда нашим пользователям будет достаточно открыть нужный URL-адрес с формой в веб-браузере, и они смогут загружать туда свои данные из файлов Excel в нашу базу данных.

Теперь нужно немного данных. Взгляните на следующие снимки.


Кажется, все нормально, но столбцы перемешаны. Для школы A столбцы расположены в другом порядке, нежели для школы B. Лучше решать эту неувязку в нашей программе, чем заставлять пользователей что-либо править. В общем, у нас есть данные для школ A и B. Очевидно. что один из наших учеников - отличник (средняя оценка A), а другой - двоечник (оцена F). Остальных можно назвать нормальными учениками.

Давайте уже перейдем к программе.

Эта программа достаточно сложна, поэтому будем разобъем ее на части и рассмотрим их отдельно.

Сначала идут обычные команды:

==================================================
#!/usr/bin/perl

use DBI;
use CGI;
use Spreadsheet::ParseExcel;

$cgi = new CGI;

$dbh = DBI->connect("dbi:Pg:dbname=test;host=db.example.com", "postgres", "password") ||
die "Can't connect to database.\n";

$file = $cgi->param("file");

$workbook = Spreadsheet::ParseExcel::Workbook->Parse($cgi->param("file"));
==================================================

Как вы видите, этот код почти такой же, как и в программе из предыдущей статьи. Я не пользуюсь опциями Perl "-w" или "use strict", потому что эта программа работает и выдает правильный результат, и мне неинтересно, что какие-то переменные неинициализированы и т.п. вещи; меня интересуют лишь результаты работы программы.

Итак, в этом куске кода мы создаем объекты CGI, DBI и Spreadsheet::ParseExcel.

В данном случае конструктору объекта Spreadsheet::ParseExcel передается идентификатор файла из объекта CGI.

Идем дальше:

==================================================
foreach $sheet (@{$workbook->{Worksheet}}) {
       foreach $col ($sheet->{MinCol} .. $sheet->{MaxCol}) {
               if ($sheet->{Cells}[0][$col]->{Val} eq "Name") {
                       $name = $col;
               }

               if ($sheet->{Cells}[0][$col]->{Val} eq "Phone") {
                       $phone = $col;
               }
       }
==================================================

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

==================================================
       foreach $row ($sheet->{MinRow}+1 .. $sheet->{MaxRow}) {
               $child_name = $sheet->{Cells}[$row][$name]->{Val};
               $child_phone = $sheet->{Cells}[$row][$phone]->{Val};

               $child_school = $sheet->{Name};

               $child_code = Spreadsheet::ParseExcel->ColorIdxToRGB(
                       $sheet->{Cells}[$row][$name]->{Format}->{Font}->{Color});

==================================================

В этом блоке пробегаем по всем строкам, так же, как мы пробегали до этого по столбцам. Естественно, в этот раз мы пропускаем заголовок таблицы. Затем мы считываем через объекта $sheet содержимое ячеек: имя ученика в переменную $child_name, номер телефона - в $child_phone. Затем получаем название школы $child_school из названия листа.

Наконец, считываем информацию $child_code о цветовом форматировании ячейки, содержащей имя.

==================================================
               $dbh->do("insert into children (name,phone,school,code) values
                       (\'$child_name\', \'$child_phone\',
                       \'$child_school\', \'$child_code\')");
       }
}
==================================================

Далее, вставляем данные в базу данных.

==================================================
print $cgi->header();

print <<EOF
<html>
<head>
<title>File Has Been Uploaded
</head>
<body>
Thank You.
</body>
</html>
EOF
;

exit;
==================================================

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

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

Результат выполнения запроса select * from children; следующий:

    name    |    phone    |  school  |  code
------------+-------------+----------+--------
 John Smith | 5551234     | School A | 000000
 Sam Adams  | 5554321     | School A | 00FF00
 Jane Doe   | 5550000     | School A | FF0000
 Mike D     | 5552222     | School B | 000000
 John A     | 5559999     | School B | 000000
(5 rows)

Это именно то, что мы ожидали.

Итак, с помощью модуля Spreadsheet::ParseExcel, мы минимизировали риск неверной обработки данных, и в то же время пользователям не придется работать с экзотическими форматами. Нам как программистам тоже хорошо - все работает, и данные успешно помещаются в базу данных.

Я привел простой пример решения выдуманной задачи, но, надеюсь, она сполна демонстрирует легкость обращения с Excel-файлами в Perl.