2008-04-16

PHP で Excel 2003 の SummaryInformation を取得したい。

SummaryInformation は、エクスプローラで xls ファイルを右クリックしてコンテキストメニューの「プロパティ」を開き、「概要」タブなどに表示されている情報です。ワークシートの内容にアクセスするものは、いくつかヒットしました。

  • 茶漬けブログさんの Excel_Peruser
    ワークシートが読めます。ちょっと使ってみた範囲では完璧でした。素晴らしい。 Excel_Reviser と組合わせて既存の Excel ファイルをテンプレートとして使えます。
  • PHPExcel
    Excel 2007 (.xslx)の読み込みは、できるらしい。PHPExcel_DocumentProperties->getTitle など、それらしい実装があります。Excel 2007 持ってないから、チェックできない。
  • PHP-ExcelReader
    ワークシートが読める。
  • PEAR::Spreadsheet_Excel_Writer
    ワークシートの書き込みができるらしい。

ワークシートがやっぱりメインだよな。その他の資料としては、

1日やって、だめだったらあきらめるつもりで PHP-ExcelReader を使ってダンプを出して眺めたりしてました。それらしい、箇所はあるのですがたどり着く方法がなかなかわかりませんでした。そろそろ、見切りをつけようと思っていたところで、jakarta になんかあったことを思い出して、探してみると The Apache POI Project になってました。いつの間にかトッププロジェクトに昇格してる。

で、PropertySet 辺りの実装を参考に改めてダンプを見てみると、

「読めるっ!!読めるぞぉ」(ラピュタのムスカ風に)

甚だ、中途半端ですが必要な部分のみ parse できるだけの PHP 実装をしてみました。

    function parseProperty($data){
$offset = 0;
$result = array();
$result['byteOrder'] = GetInt2d($data, $offset);
$offset += 2;
// 以下は、Intel little endian きめうち
$result['format'] = GetInt2d($data, $offset);
$offset += 2;
$result['osVersion'] = GetInt4d($data, $offset);
$offset += 4;
$result['classID'] = unpack('C*', substr($data, $offset, 16));
$offset += 16;
$result['numSections'] = GetInt4d($data, $offset);
$offset += 4;
// section を取得
for ($secidx = 0 ; $secidx < $result['numSections'] ; $secidx++) {
$section = array();
$section['formatID'] = unpack('C*', substr($data, $offset, 16));
$offset += 16;
$section['offset'] = GetInt4d($data, $offset);
$offset += 4;
$propTop = $propTblOffset = $section['offset'];
$section['size'] = GetInt4d($data, $propTblOffset);
$propTblOffset += 4;
$section['numProperties'] = GetInt4d($data, $propTblOffset);
$propTblOffset += 4;
// UTF-16 の可能性もあるようだが今のところ SHIFT-JIS きめうち
// 以下の propertyID = 1 がコードページなので対応は可能じゃないかな。
$srcenc = 'SJIS-win';
for($propidx = 0 ; $propidx < $section['numProperties'] ; $propidx++) {
$property = array();
$property['propertyID'] = GetInt4d($data, $propTblOffset);
$propTblOffset += 4;
$property['propertyOffset'] = GetInt4d($data, $propTblOffset);
$propTblOffset += 4;
$propOffset = $propTop + $property['propertyOffset'];
$property['propertyType'] = GetInt4d($data, $propOffset);
$propOffset += 4;
// ここで、propertyType (Variant型)ごとの値取得
switch($property['propertyType']) {
case 2: // VT_I2 = 2 byte signed int.
$property['propertyLength'] = 2;
$property['propertyValue'] = GetInt2d($data, $propOffset);
$propOffset += $property['propertyLength'];
break;
case 30: // VT_LPSTR = null terminated string.
$property['propertyLength'] = GetInt4d($data, $propOffset);
$propOffset += 4;
$strval = mb_convert_encoding(
substr($data, $propOffset, $property['propertyLength']),
"UTF-8", $srcenc);
$property['propertyValue'] = str_replace("\x00", "", $strval);
$propOffset += $property['propertyLength'];
break;
case 64: // VT_LPSTR = null terminated string.
$property['propertyLength'] = 8;
$lowWord = GetInt4d($data, $propOffset);
$propOffset += 4;
$highWord = GetInt4d($data, $propOffset);
$propOffset += 4;
// EPOC の違いを変換。
// 1601-01-01 00:00:00 (Windows EPOC/100nsec)
// -> 1970-01-01 00:00:00 (unix EPOC/1sec) に変換
$unixTime = $highWord * 4294967296 + $lowWord;
$unixTime = floor($unixTime / (1000 * 1000 * 10));
$unixTime -= 11644473600;
$property['propertyValue'] = date('Y/m/d H:i:s', $unixTime);
break;
}
$section['properties'][$propidx] = $property;
}
$result['sections'][$secidx] = $section;
}
return $result;
}

これで、propertyID = 2 の propertyValue にタイトルの文字列が取得できます。日付関係も問題ないよう。手持ちのファイルでは、概ね OK でした。

もっと、効率的に書けるでしょうし、エンコーディングのきめうちや未対応の Variant 型など突込みどころも多いですが、後で必要になったらがんばるかもしれません。

0 件のコメント: