poi。HSSFCell#setAsActiveCellの問題。

poiを使用していて、HSSFCell#setAsActiveCell()が効かないのに気がついた。
ググってみると動かないという書き込みがいくつかあって、本家にもバグ報告がされてるんだかされてないんだか・・・
poiのバージョンは3.8。
excelファイルは古い形式(拡張子がxls)。
対応策が見つからなかったので何が悪いのか調べてみた。


とりあえずHSSFCell#setAsActiveCell()の中身を見てみる。
どうやら選択範囲のデータを持っているのはorg.apache.poi.hssf.record.SelectionRecordというクラスのようだ。
このクラスのfield_2_row_active_cell、field_3_col_active_cellメンバを変更している。


ところがexcelでアクティブセルを変更したファイルを読んでみると、これ以外にもfield_6_refsメンバの内容も違っている。
何回かexcelで選択範囲とアクティブセルを変更して読み込んだ時の内容から、SelectionRecordクラスのメンバの意味を推測すると、
field_1_pane : よくわからない。常に3?
field_2_row_active_cell : アクティブセルの行インデックス。
field_3_col_active_cell : アクティブセルの列インデックス。
field_4_active_cell_ref_index : アクティブセルを含む選択範囲のインデックス。
field_6_refs : 選択範囲(先頭行、最終行、先頭列、最終列)の配列。
のようになっているらしい。


setAsActiveCell()でfield_2_row_active_cell、field_3_col_active_cellしか変更していないということは、選択範囲の外にアクティブセルがあるということになるのか?
だもんでexcelとしては正しくないアクティブセルは無視して選択範囲をアクティブセルとみなしてるということか。


選択範囲にないセルをアクティブにするのなら選択範囲も変えなきゃいかんだろうし、選択範囲内にしてもセルによってはfield_4_active_cell_ref_indexメンバも変えなきゃいかんだろう。


ということで、そこらへんに対応するようにpoiのコードを変更してみる。


まずorg.apache.poi.hssf.record.SelectionRecord。
下記のメソッドを追加。

public boolean isInRange(int row, int col) {
  for (CellRangeAddress8Bit area : field_6_refs) {
    if (area.isInRange(row, col)) {
      return true;
    }
  }
  return false;
}

public void setActiveCell(int row, int col) {
  field_2_row_active_cell = row;
  field_3_col_active_cell = col;
  setActiveCellRef(row, col);
}

public void setActiveCellRef(int row, int col) {
  for (int i = 0; i < field_6_refs.length; i++) {
    if (field_6_refs[i].isInRange(row, col)) {
      field_4_active_cell_ref_index = i;
      return;
    }
  }
}

下記のメソッドを修正

public void setActiveCellRow(int row) {
  field_2_row_active_cell = row;
  setActiveCellRef(row, getActiveCellCol());
}

public void setActiveCellCol(short col) {
  field_3_col_active_cell = col;
  setActiveCellRef(getActiveCellRow(), col);
}

本来なら行と列は別々に指定できるものではないので@Deprecatedにしたい気もする・・・


次にorg.apache.poi.hssf.model.InternalSheet。
下記のメソッドを追加。

public void setActiveCell(int row, int col) {
  //shouldn't have a sheet w/o a SelectionRecord, but best to guard anyway
  if (_selection != null) {
    if (_selection.isInRange(row, col)) {
      _selection.setActiveCell(row, col);
      return;
    }
    // 選択範囲はprivateメンバなので外からいじれない。
    // 特定のセルならともかく、セル範囲を選択するってことを想定してないような・・・
    // とりあえず今は気にしない。
    SelectionRecord newSelection = new SelectionRecord(row, col);
    int index = _records.indexOf(_selection);
    _records.set(index, newSelection);
    _selection = newSelection;
  }
}

下記のメソッドを修正。

public void setActiveCellRow(int row) {
  //shouldn't have a sheet w/o a SelectionRecord, but best to guard anyway
  if (_selection != null) {
    if (_selection.isInRange(row, getActiveCellCol())) {
      _selection.setActiveCellRow(row);
      return;
    }
    SelectionRecord newSelection = new SelectionRecord(row, getActiveCellCol());
    int index = _records.indexOf(_selection);
    _records.set(index, newSelection);
    _selection = newSelection;
  }
}

public void setActiveCellCol(short col) {
  //shouldn't have a sheet w/o a SelectionRecord, but best to guard anyway
  if (_selection != null) {
    if (_selection.isInRange(getActiveCellRow(), col)) {
      _selection.setActiveCellCol(col);
      return;
    }
    SelectionRecord newSelection = new SelectionRecord(getActiveCellRow(), col);
    int index = _records.indexOf(_selection);
    _records.set(index, newSelection);
    _selection = newSelection;
  }
}

本来なら行と列は・・・同上。


次にorg.apache.poi.hssf.usermodel.HSSFCell。
下記のメソッドを修正。

public void setAsActiveCell() {
  int row=_record.getRow();
  short col=_record.getColumn();
  // _sheet.getSheet().setActiveCellRow(row);
  // _sheet.getSheet().setActiveCellCol(col);
  _sheet.getSheet().setActiveCell(row, col);
}

これでHSSFCell#setAsActiveCell()が効くようになった。


2012/10/09追記
poi。HSSFCell#setAsActiveCellの問題。その2。 - たっくてっくのーと