SummaryInformation は、エクスプローラで xls ファイルを右クリックしてコンテキストメニューの「プロパティ」を開き、「概要」タブなどに表示されている情報です。ワークシートの内容にアクセスするものは、いくつかヒットしました。
ワークシートがやっぱりメインだよな。その他の資料としては、
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 型など突込みどころも多いですが、後で必要になったらがんばるかもしれません。