使用 Perl 生成原生 Excel 文件

作者:Mike Diehl

多年来,我可能为客户和同事创建了数千份报告。通常,我有一个基于 Web 的程序,可以生成 html 或逗号分隔格式的报告。 html 格式更漂亮,通常能满足用户的需求。逗号分隔格式允许用户轻松地将结果导入 Excel,或者对于那些更开明的用户,导入 Open Office。之后,他们会对原始数据进行格式化,并将最终报告发送到目的地。所有这些格式化工作都由客户手动完成,他们往往会定期需要相同的报告,因此这导致他们需要进行大量重复劳动。大多数时候,他们只是很高兴获得数据,并且不会抱怨必须稍微美化一下。但是,有一种更好的方法。

Spreadsheet::WriteExcel Perl 模块允许您生成数据,对其进行格式化,并以原生 Excel 格式输出。很多时候,我只是简单地列出一些预定义的报告,并为我的客户提供指向它们的超链接。

让我们来了解一个简单的 CGI 脚本,该脚本使用 Perl 的 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 脚本变得容易,而且我们都喜欢简单,对吧?然后我们连接到数据库并获取数据库句柄以供稍后使用。类似地,我们获取一个 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 变量中。我们没有向对象构造函数传递文件名,而是简单地传递 “-”,这会导致生成的电子表格输出到 SDTOUT。我们的报告将有一个简单的封面页,列出部门编号,所以让我们接下来添加工作表。为了将 “Division Number” 标签格式化为粗体文本,我们需要执行一些步骤。首先,我们需要使用 add_format() 方法创建一个新的格式对象。然后我们可以设置格式的各种属性;在本例中,我们只是将其设为粗体。我们可以将其设置为绿色背景上的红色前景、斜体和右对齐。但为什么要这样做呢?在本例中,我们只是想让标签突出显示,所以我们将其设为粗体。

最后,我们使用 write() 方法将一些数据放入我们的封面页。首先,我们将 “Division Number” 标签(粗体)放在第六行的第一列中。请注意,在 Excel 中,行和列从 1 开始,但在 Spreadsheet::WriteExcel 中,它们从 0 开始。另请注意,位置是 “row, column” 而不是 “horizontal, vertical”。当我第一次开始使用 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 循环添加列标题。这样做使得添加操作变得像向列表中添加新名称一样简单。

接下来我们向数据库请求一些数据。在本例中,我们请求按姓名和编号列出的客户列表,他们的电话号码以及他们欠款的金额。

我们以粗体打印出他们的姓名,然后以常规字体打印出他们的客户编号和电话号码。

最后一列更有趣一些。在最后一列中,我们检查客户是否欠我们超过 1000 美元。如果他们欠款超过 1000 美元,我们使用红色、逾期格式来打印他们欠我们的金额。否则,我们使用绿色、当前格式。

一旦我们打印完一整行,我们就使用 $row++ 行转到下一行,并从我们的数据集中获取下一条记录。

好了,我们快完成了。

#====================================================
$dbh->finish();
$workbook->close();
exit;
#====================================================

使用 finish() 方法释放数据库句柄通常是一个好主意。使用 close() 方法关闭电子表格句柄会导致缓冲区被刷新,并且我们的电子表格出现在 STDOUT 上。

当然,如果我们只想输出一个逗号分隔的文件,并让用户完成所有的导入和格式化工作,那么这可能是一个 10 行的程序。但是这个程序编写起来并不太难,而且结果要好得多。图 1 显示了我在手动设置列宽后生成的电子表格。Spreadsheet::WriteExcel Perl 模块允许我们设置各种类型的格式,以及生成电子表格公式。该模块非常易于使用,并且客户对结果的反应非常积极,我甚至不再浪费时间生成逗号分隔的报告了。

加载 Disqus 评论