Do You PHP はてブロ

Do You PHPはてなからはてブロに移動しました

PHP Excel拡張モジュールをざっくり試してみた

PHPExcel』と来れば通常はPHPExcelなんですが、如何せん、『でかい・遅い・メモリ食う』と三拍子そろってしまってます。で、PHPのコア開発者のIlia氏が、LibXLを使った拡張モジュールを書いてしまったようです。


Since I broke my right hand 3 weeks ago while biking, I found myself with a lot of spare time :/. It is amazing just how limited your ability to do things becomes when you can only use one hand. So, to stave off the boredom, I've been slowly toiling away on a PHP Excel extension that I intend to use at work, which I've finally gotten ready for release today.
You can find it on github at: http://github.com/iliaal/php_excel.

LibXLは、昔のExcelのデフォルト形式であるBIFF7/8形式で出力可能なライブラリです。ちなみに、無償でも使えますがトライアル扱いとなり、1行目に『フルバージョンのライセンスを買ってね』($199〜$2,199)というメッセージが入ってしまいます。

ということで、ざっくり試してみました。

環境

  • CF-R8+VMwarePlayer2.5.4+CentOS5.4
  • PHP-5.2.13

インストール

まずはLibXLのインストール。基本的にバラすだけ。

$ wget http://www.libxl.com/download/libxl.tar.gz
$ tar zxf libxl.tar.gz
$ 

続いて、PHP Excel拡張モジュール。

$ wget http://github.com/downloads/iliaal/php_excel/php-excel-0.8.tar.bz2
$ tar jxf php-excel-0.8.tar.bz2
$ cd php_excel/
$ phpize
$ ./configure
       :
checking for excel support... yes, shared
checking for excel files in default path... not found
configure: error: Please reinstall the excel distribution
$ 

ということで、libxlの場所を指定して再度configure。

$ cd ../libxl-2.4.3
$ ln -s include_c include
$ cd -
$ ./configure --with-excel=shared,../libxl-2.4.3
$ make
$ sudo make install
$ 

動作確認

とりあえず、PHP Excel Extension - iBlog - Ilia Alshanetskyにあるサンプル(test.php)

<?php
$sT = microtime(1);

$x = new ExcelBook();
$s = $x->addSheet("Sheet 1");

for ($i = 0; $i < 1000; $i++) {
    for ($j = 0; $j < 10; $j++) {
        $s->write($i, $j, ($i * $j));
    }
}

$x->save("bench.xls");

$eT = microtime(1);

var_dump(($eT - $sT), memory_get_usage(1), memory_get_peak_usage(1));

を実行してみることに。

$ LD_LIBRARY_PATH=../libxl-2.4.3/lib php -dextension=excel.so test.php
php: symbol lookup error: /path/to/php/extensions/no-debug-non-zts-20060613/excel.so: undefined symbol: xlCreateBookCA
$ 

へっ。。。?ということで、lddで確認。

$ ldd /path/to/php/extensions/no-debug-non-zts-20060613/excel.so
        linux-gate.so.1 =>  (0x00599000)
        libc.so.6 => /lib/libc.so.6 (0x00753000)
        /lib/ld-linux.so.2 (0x00539000)
$ 

あの。。。libxl.soがリンクされてませんが。。。

再インストール

Makefileを見てみると、やっぱりlibxlをリンクしてない感じ。で、

$ cp -p Makefile Makefile.org
$ vi Makefile
$ diff Makefile.org Makefile
40c40
< LDFLAGS =
---
> LDFLAGS = -L../libxl-2.4.3/lib -lxl
$ make clean
$ make
$ sudo make install
$ LD_LIBRARY_PATH=../libxl-2.4.3/lib ldd /path/to/php/extensions/no-debug-non-zts-20060613/excel.so
        linux-gate.so.1 =>  (0x0091f000)
        libxl.so => ../libxl-2.4.3/lib/libxl.so (0x00110000)
        libc.so.6 => /lib/libc.so.6 (0x00bf9000)
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x003f4000)
        libm.so.6 => /lib/libm.so.6 (0x00d9f000)
        libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x009cb000)
        libpthread.so.0 => /lib/libpthread.so.0 (0x004df000)
        /lib/ld-linux.so.2 (0x00539000)
$ 

今度はヨサゲ。
ホントは次のような感じでもOKだと思ったんですが、うまくconfigureできなかったので、configure後にMakefileを編集するといった方法を採りました。

$ LDFLAGS="-L../libxl-2.4.3/lib -lxl" ./configure --with-excel=shared,../libxl-2.4.3
          :
checking for C compiler default output file name... a.out
checking whether the C compiler works... configure: error: cannot run C compiled programs.
If you meant to cross compile, use `--host'.
See `config.log' for more details.
$ 

再度、動作確認

CF-R8+VMwarePlayer+CentOS5.4上で実行。ほぼ一瞬(0.1秒以下)で10桁x1000行のセルに値を突っ込んだxlsファイルが作成されました。

$ ll *.xls
ls: *.xls: そのようなファイルやディレクトリはありません
$ 
$ LD_LIBRARY_PATH=../libxl-2.4.3/lib php -dextension=excel.so test.php
float(0.078369140625)
int(262144)
int(262144)
$ 
$ ll *.xls
-rw-rw-r-- 1 shimooka shimooka 147968  82 19:12 bench.xls
$ 

追加されるクラス/メソッド

LibXL documentationにある関数をOO的にしたものと考えてOKかと。とりあえず、定義されているものを抜き出すと以下のような感じ。定数は多すぎるので割愛(LibXL documentationを参照)。

  • class ExcelBook
    • public addFont(font)
    • public addFormat(format)
    • public getError()
    • public loadFile(filename)
    • public load(data)
    • public save(filename)
    • public getSheet(sheet)
    • public addSheet(name)
    • public copySheet(name, sheet_number)
    • public deleteSheet(sheet)
    • public sheetCount()
    • public activeSheet(sheet)
    • public getCustomFormat(id)
    • public addCustomFormat(format)
    • public packDate(timestamp)
    • public unpackDate(date)
    • public getActiveSheet()
    • public setActiveSheet(sheet)
    • public getDefaultFont()
    • public setDefaultFont(font, font_size)
    • public setLocale()
    • public addPictureFromFile(filename)
    • public addPictureFromString(data)
    • public __construct(license_name, license_key)
  • class ExcelSheet
    • public cellType(row, column)
    • public cellFormat(row, column)
    • public read(row, column, format)
    • public readRow(row, start_col, end_column)
    • public readCol(column, start_row, end_row)
    • public write(row, column, data, format, datatype)
    • public writeRow(row, data, start_column, format)
    • public writeCol(row, data, start_row, format)
    • public isFormula(row, column)
    • public isDate(row, column)
    • public insertRow(row_first, row_last)
    • public insertCol(col_first, col_last)
    • public removeRow(row_first, row_last)
    • public removeCol(col_first, col_last)
    • public colWidth(column)
    • public rowHeight(row)
    • public readComment(row, column)
    • public writeComment(row, column, value, author, width, height)
    • public setColWidth(column_start, column_end, width, hidden, format)
    • public setRowHeight(row, height, format, hidden)
    • public getMerge(row, column)
    • public setMerge(row_start, row_end, col_start, col_end)
    • public deleteMerge(row, column)
    • public addPictureScaled(row, column, pic_id, scale)
    • public addPictureDim(row, column, pic_id, width, height)
    • public horPageBreak(row, break)
    • public verPageBreak(col, break)
    • public splitSheet(row, column)
    • public groupRows(start_row, end_row, collapse)
    • public groupCols(start_column, end_column, collapse)
    • public clear(row_s, row_e, col_s, col_s)
    • public copy(row, col, to_row, to_col)
    • public firstRow()
    • public lastRow()
    • public firstCol()
    • public lastCol()
    • public displayGridlines()
    • public printGridlines()
    • public setDisplayGridlines(value)
    • public setPrintGridlines(value)
    • public zoom()
    • public zoomPrint()
    • public setZoom(value)
    • public setZoomPrint(value)
    • public setLandscape(value)
    • public landscape()
    • public paper()
    • public setPaper(value)
    • public header()
    • public footer()
    • public setHeader()
    • public setFooter()
    • public headerMargin()
    • public footerMargin()
    • public hcenter()
    • public vcenter()
    • public setHCenter(value)
    • public setVCenter(value)
    • public marginLeft()
    • public marginRight()
    • public marginTop()
    • public marginBottom()
    • public setMarginLeft(value)
    • public setMarginRight(value)
    • public setMarginTop(value)
    • public setMarginBottom(value)
    • public printHeaders()
    • public name()
    • public setName()
  • class ExcelFormat
    • public getFont()
    • public setFont(font)
    • public numberFormat(format)
    • public horizontalAlign(align_mode)
    • public verticalAlign(align_mode)
    • public wrap(wrap)
    • public rotate(angle)
    • public indent(indent)
    • public shrinkToFit(shrink)
    • public borderStyle(style)
    • public borderColor(color)
    • public borderLeftStyle(style)
    • public borderLeftColor(color)
    • public borderRightStyle(style)
    • public borderRightColor(color)
    • public borderTopStyle(style)
    • public borderTopColor(color)
    • public borderBottomStyle(style)
    • public borderBottomColor(color)
    • public borderDiagonalStyle(style)
    • public borderDiagonalColor(color)
    • public fillPattern(patern)
    • public patternForegroundColor(color)
    • public patternBackgroundColor(color)
    • public locked(locked)
    • public hidden(hidden)
    • final public __construct(book)
  • class ExcelFont
    • public size(size)
    • public name(name)
    • public underline(underline_style)
    • public mode(mode)
    • public color(color)
    • public bold(bold)
    • public strike(strike)
    • public italics(size)
    • final public __construct(book)

日本語は?

次のようなコードで試してみましたが、EXCEL2000で問題なく表示できました。

<?php
/**
 * 追加するシートの枚数
 */
define('SHEETS', 10);

/**
 * 新規Bookオブジェクトを作成
 */
$book = new ExcelBook();

/**
 * シートを10枚追加
 */
for ($i = 0; $i < SHEETS; $i++) {
    $book->addSheet("{$i}番目のシート");
}

/**
 * 各シートにデータを書き込み
 */
for ($i = 0; $i < SHEETS; $i++) {
    $sheet = $book->getSheet($i);

    for ($col = 0; $col < 10; $col++) {
        for ($row = 0; $row < 10; $row++) {
            $sheet->write($row, $col, '日本語です' . ($row * $col + $i));
        }
    }
}

/**
 * Bookを"multibytes.xls"として保存
 */
$book->save("multibytes.xls");

ちなみに、mbstring系の設定は以下のように全てUTF-8で統一してあり、上記スクリプトエンコーディングUTF-8です。

mbstring.internal_encoding = utf-8
mbstring.http_input = utf-8
mbstring.http_output = utf-8
mbstring.encoding_translation = Off
mbstring.detect_order = utf-8
mbstring.substitute_character = none;
mbstring.func_overload = 0
mbstring.strict_detection = Off
mbstring.script_encoding = utf-8

既存のxlsファイルに追記して別名で保存してみる

graph_org.xlsにはグラフだけ用意してあり、PHPスクリプトでデータを突っ込んで別名で保存する、というサンプルを作ってみました。これ、結構応用範囲が広いんじゃないかなぁ。

<?php
/**
 * 雛形となるxlsファイルを読み込み
 */
$book = new ExcelBook();
$book->loadFile('graph_org.xls');

/**
 * 最左のシートを取得
 */
$sheet = $book->getSheet(0);

/**
 * データを書き込み
 */
$sheet->write(1, 0, '項目X');  // セルA2
$sheet->write(1, 1, '項目Y');  // セルB2
for ($col = 0; $col < 2; $col++) {
    for ($row = 2; $row < 50; $row++) {
        $sheet->write($row, $col, pow($col + $row, 2) + $row);
    }
}

/**
 * Bookを"graph.xls"として保存
 */
$book->save("graph.xls");

まとめ

ざっくりとしか試してませんが、パフォーマンスも良く、結構使えるんじゃないかと思います。色やフォント、レイアウトなども指定できるようですが、まあ、1からゴリゴリxlsファイルを作るのは大変なので、既存ファイルを読み込んで、データを埋めたりシートをコピーしたりして結果を出力する、という使い方が良いんじゃないでしょうかね。
なお、各サンプルスクリプトと出力されたxlsファイルをDo You PHP?にUPしてありますので、興味がある方はどうぞ。

追記(2010/08/12 22:12)

2010/08/11付けでバージョン0.8.5がリリースされました。

libxlがリンクされない件も修正されています:-)