Jakarta POI Name Record 日本語対応

  • POI HSSF

Jakarta POI のHSSF(Excel97形式のファイルを読み書きするAPI)ではセルに対しての日本語の読み書きは正常だったのだが、Name Record(シート上の場所をあらわすエイリアスのようなもの)で日本語名を使っていると正しく名称が取れないという不具合がある。

Web上にほとんど情報が無かったので、ソースを読んで修正。
org.apache.poi.hssf.record.NameRecord.java(line:777)
修正前

field_12_name_text = StringUtil.getFromCompressedUnicode(data, 15 + offset,
LittleEndian.ubyteToInt(field_3_length_name_text));
start_of_name_definition += field_3_length_name_text;

修正後

if(field_11_compressed_unicode_flag==1){
field_12_name_text = StringUtil.getFromUnicodeLE(data, 15 + offset,
LittleEndian.ubyteToInt(field_3_length_name_text));
start_of_name_definition += field_3_length_name_text*2;
}else{
field_12_name_text = StringUtil.getFromCompressedUnicode(data, 15 + offset,
LittleEndian.ubyteToInt(field_3_length_name_text));
start_of_name_definition += field_3_length_name_text;
}

  • 作業の手引き

 -ソースコード検索 ローカルでEclipseを使ったり、Kodersを使って類似の処理を探ってみたり。http://koders.com/java/fid34925FBC91F751F4C7B283108CFE72DBF4465577.aspx?s=+%22UTF-16LE%22
http://koders.com/?s=+getFromUnicodeLE&_%3Abtn=Search&_%3Ala=Java&_%3Ali=*
 -OpenOfficeの方では正しく扱えているようなのでそちらのソースを探ってみる(残念ながらKodersでは検索対象になってない)
 -http://sc.openoffice.org/excelfileformat.pdf
を調べる。
P.176

Record NAME, BIFF8:
Offset Size Contents
0 2 Option flags, see below
2 1 Keyboard shortcut
3 1 Length of the name (character count, ln)
4 2 Size of the formula data (sz)
6 2 Not used
8 2 0 = Global name, otherwise index to sheet (one-based)
10 1 Length of menu text (character count, lm)
11 1 Length of description text (character count, ld)
12 1 Length of help topic text (character count, lh)
13 1 Length of status bar text (character count, ls)
14 var. Name (Unicode string without length field, ➜3.4)
var. sz Formula data (RPN token array without size field, ➜4)
[var.] var. (optional, only if lm > 0) Menu text (Unicode string without length field, ➜3.4)
[var.] var. (optional, only if ld > 0) Description text (Unicode string without length field, ➜3.4)
[var.] var. (optional, only if lh > 0) Help topic text (Unicode string without length field, ➜3.4)
[var.] var. (optional, only if ls > 0) Status bar text (Unicode string without length field, ➜3.4)

Name Recordの内部構造はこうなっているらしい。で14バイト目からが名前なのだが、先頭1バイトはUnicodeかどうか(上位バイト省略して圧縮されているかどうか。)のフラグ。
NameRecord.java(line:769)では
field_11_compressed_unicode_flag= data [14 + offset];
として値を取得しているが、それなのに、
field_12_name_text = StringUtil.getFromCompressedUnicode(data, 15 + offset,LittleEndian.ubyteToInt(field_3_length_name_text));
とすべて非Unicode扱いしている!とやっと気づき修正。さらに、次のプロパティの
値を読み込むオフセットも2倍進むので
start_of_name_definition += field_3_length_name_text*2;
と修正

  • 教訓

 オープンソースAPIを使うときは、不具合が起こったときソースを読んで修正をする必要が発生する可能性があることを認識するべし。特にWeb上に情報が少ないもの、開発者のコミュニティが弱いもの、CVSがあまり更新されていないものは要注意。

  • その他の問題

DrawingRecord#clone()
http://www.jajakarta.org/kvasir/bbs/technical/1060?msg=3#msg3636