<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>
LibreOfficeまたはApache OpenOfficeのCalcスプレッドシートを拡張機能を通してJavaまたはマクロプログラムから操作する(読むまたは書く)方法を知る
スプレッドシートセルへのリファレンスを得たので、スプレッドシートセルの値を得たい。
オーケー。
実のところ、それはあまり単純ではない。セルUNOオブジェクトが実装している'com.sun.star.table.XCell'UNOインスタンスにはメソッド、'getValue'があるが、このメソッドの戻りタイプは'double'だ。
ふむ?どういうことだ?
セルが文字列値を保持していれば、メソッドは0.0を戻す。セルが日付値を保持していれば、メソッドはある種の内部値を戻す(例えば、2017-01-01には42736.0)。セルが真偽値を保持していれば、メソッドは、TRUEには1.0、FALSEには0.0を戻す。セルが式を保持していれば、式の結果が文字列値ならば0.0を戻し、それ以外ならば式の結果に対応する値を戻す。
それでは、文字列値はどうすれば得られるのか?
セルが文字列値を保持しているときは、式の結果が文字列値である場合も含め、この文字列値を、'com.sun.star.text.XText'インターフェースのメソッド、'getString'で得ることができる。
セルUNOコンポーネントは'com.sun.star.text.XText'インターフェースを実装している。
そう。実は、この'getString'メソッドは、セルの値が何であれ、その表現文字列を戻す。
「表現文字列」というのは、セル上に表示される文字列のことか?
そう。例えば、真偽値の'TRUE'や小数値の'1.23'だ。
ふーむ、とにかく、セルの値を'getValue'メソッドで得るとき、戻りが、例えば、1.0だった場合、その値が何なのかが分からない。それは、整数の1なのか、小数の1.0なのか、真偽のTRUEなのか、それとも何かの日付なのか?
スプレッドシートセルの値が文字列でない場合、セルの内部値は常に'double'なのだと思われる。セルの表現は、セル値表現フォーマットによって制御されている。
「セル値表現フォーマット」というのは . . . 何だ?
セル上でマウスを右クリックし、'Formating Cells...(セルの書式設定...)'を選択して、'Numbers(数値)'タブで設定できるものだ。「数値フォーマット」と呼ばれているようだが、命名が変だと思われる。それには文字列に対するフォーマットも含まれていて、これは数値ではない。
なるほど。とにかく、セルのセル値表現フォーマットを得なければならないことになる。
セル値表現フォーマットは、セルUNOオブジェクトのプロパティ値として得ることができる。具体的には、セルUNOコンポーネントが実装している'com.sun.star.beans.XPropertySet'インターフェースのメソッド、'getPropertyValue'で得ることができる。プロパティ名は'NumberFormat'であり、プロパティ値は、整数であるセル値表現フォーマットキーだ。
それで、その整数のプロパティ値が何を意味するかをどうやって知ることができるのか?
スプレッドシートドキュメントUNOオブジェクトから'com.sun.star.util.XNumberFormats'インターフェースのインスタンスを得ることができ、そのインスタンスから、セル値表現フォーマットキー群とキー毎のフォーマット文字列を得ることができる。
それらが、そのドキュメントに登録されたセル値表現フォーマットキー群とそのフォーマット文字列なわけだ。
そう。
セル値表現フォーマットとセル値を得るから、我々は、セル値を、望みどおりに解釈することができる。
いいだろう。
我々がやりたいのは、セルのセル値表現フォーマットに応じて、セル値を適切なデータタイプで得ることだ。我々は、'getString'メソッドからセル値表現文字列を得て、この文字列を適切なデータタイプに変換することにする。具体的には、セル値表現フォーマットに応じて、java.time.LocalDate、java.time.LocalTime、java.time.LocalDateTime、java.lang.Boolean、java.lang.String、java.lang.Integer、java.lang.Doubleのインスタンスを得ることにする。. . . 我々の方法がパフォーマンスの観点から最適だとは私は特に主張しない。
いいだろう。
セル値表現フォーマットについて、あらゆる可能な表現フォーマットに対処することはここではしない。我々は、自分で使うつもりの特定の表現フォーマットだけに対処する。我々はそうした特定の表現フォーマットだけを使うつもりなので、実用的にはそれで十分だ。
特定の表現フォーマットというのは具体的には何なのか?
日付には'YYYY-MM-DD'、時刻には'HH:MM:SS'、日付時刻には'YYYY-MM-DD\"T\"HH:MM:SS'、真偽には'BOOLEAN'、文字列には'@'、整数には'0'、小数には'Global'または'Standard'だ。
'Global'または'Standard'というのはどういう意味だ?
なぜだか分からないが、LibreOfficeのロケール設定が'日本語'に設定されていると、'Standard'が使われる。他のほとんどの場合、'Global'が使われる。
ふーむ。
'YYYY-MM-DD\"T\"HH:MM:SS'などの表現フォーマットがドキュメント中に存在しなかったらどうなるのか?
存在しなければ、我々のプログラムが登録する。
おう。
セルに式が保持されている場合、式自体は、'com.sun.star.table.XCell'インターフェースのメソッド、'getFormula'で得ることができる。
なるほど。
結局、以下のコードを書いた。
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import com.sun.star.lang.Locale;
import com.sun.star.util.XNumberFormatsSupplier;
import com.sun.star.util.XNumberFormats;
import com.sun.star.util.MalformedNumberFormatException;
import com.sun.star.beans.XPropertySet;
import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.text.XText;
import com.sun.star.table.CellContentType;
try {
String l_dateFormatString = "YYYY-MM-DD";
String l_timeFormatString = "HH:MM:SS";
String l_dateTimeFormatString = "YYYY-MM-DD\"T\"HH:MM:SS";
String l_booleanFormatString = "BOOLEAN";
String l_textFormatString = "@";
String l_integerFormatString = "0";
String l_doubleFormatString = "Global";
Locale l_locale = new Locale ();
XNumberFormatsSupplier l_numberFormatsSupplier = UnoRuntime.queryInterface (XNumberFormatsSupplier.class, l_currentSpreadSheetsDocument);
XNumberFormats l_numberFormats = l_numberFormatsSupplier.getNumberFormats ();
int l_dateFormatIdentification = -1;
int l_timeFormatIdentification = -1;
int l_dateTimeFormatIdentification = -1;
int l_booleanFormatIdentification = -1;
int l_textFormatIdentification = -1;
int l_integerFormatIdentification = -1;
int l_doubleFormatIdentification = -1;
l_dateFormatIdentification = l_numberFormats.queryKey (l_dateFormatString, l_locale, false);
if (l_dateFormatIdentification == -1) {
l_dateFormatIdentification = l_numberFormats.addNew (l_dateFormatString, l_locale);
}
l_timeFormatIdentification = l_numberFormats.queryKey (l_timeFormatString, l_locale, false);
if (l_timeFormatIdentification == -1) {
l_timeFormatIdentification = l_numberFormats.addNew (l_timeFormatString, l_locale);
}
l_dateTimeFormatIdentification = l_numberFormats.queryKey (l_dateTimeFormatString, l_locale, false);
if (l_dateTimeFormatIdentification == -1) {
l_dateTimeFormatIdentification = l_numberFormats.addNew (l_dateTimeFormatString, l_locale);
}
l_booleanFormatIdentification = l_numberFormats.queryKey (l_booleanFormatString, l_locale, false);
if (l_booleanFormatIdentification == -1) {
l_booleanFormatIdentification = l_numberFormats.addNew (l_booleanFormatString, l_locale);
}
l_textFormatIdentification = l_numberFormats.queryKey (l_textFormatString, l_locale, false);
if (l_textFormatIdentification == -1) {
l_textFormatIdentification = l_numberFormats.addNew (l_textFormatString, l_locale);
}
l_integerFormatIdentification = l_numberFormats.queryKey (l_integerFormatString, l_locale, false);
if (l_integerFormatIdentification == -1) {
l_integerFormatIdentification = l_numberFormats.addNew (l_integerFormatString, l_locale);
}
l_doubleFormatIdentification = l_numberFormats.queryKey (l_doubleFormatString, l_locale, false);
if (l_doubleFormatIdentification == -1) {
l_doubleFormatString = "Standard";
l_doubleFormatIdentification = l_numberFormats.queryKey (l_doubleFormatString, locale, false);
if (l_doubleFormatIdentification == -1) {
// If your locale setting has a different default decimal fraction format string, you should use it.
}
}
CellContentType l_cellContentType = l_currentSpreadSheetCell.getType ();
if (l_cellContentType == CellContentType.EMPTY) {
}
else {
if (l_cellContentType == CellContentType.FORMULA) {
Publisher.show (l_currentSpreadSheetCell.getFormula ());
}
XPropertySet l_currentSpreadSheetCellInXPropertySet = (XPropertySet) UnoRuntime.queryInterface (XPropertySet.class, l_currentSpreadSheetCell);
XText l_currentSpreadSheetCellInXText = (XText) UnoRuntime.queryInterface (XText.class, l_currentSpreadSheetCell);
int l_numberFormat = ((Integer) l_currentSpreadSheetCellInXPropertySet.getPropertyValue ("NumberFormat")).intValue ();
String l_cellString = l_currentSpreadSheetCellInXText.getString ();
if (l_numberFormat == l_dateFormatIdentification) {
LocalDate l_date = LocalDate.parse (l_cellString, DateTimeFormatter.ISO_LOCAL_DATE);
Publisher.show (l_date.toString ());
}
else if (l_numberFormat == l_timeFormatIdentification) {
LocalTime l_time = LocalTime.parse (l_cellString, DateTimeFormatter.ISO_LOCAL_TIME);
Publisher.show (l_time.toString ());
}
else if (l_numberFormat == l_dateTimeFormatIdentification) {
LocalDateTime l_dateTime = LocalDateTime.parse (l_cellString, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
Publisher.show (l_dateTime.toString ());
}
else if (l_numberFormat == l_booleanFormatIdentification) {
Boolean l_boolean = Boolean.valueOf (l_cellString);
Publisher.show (l_boolean.toString ());
}
else if (l_numberFormat == l_textFormatIdentification) {
Publisher.show (l_cellString);
}
else {
Matcher l_matcher = Pattern.compile ("^(\\d*|\\d{1,3}(,\\d{3})*)(\\.\\d+)?([eE][-+]?\\d+)?$").matcher (l_cellString);
if (l_matcher.find ()) {
if (l_matcher.group (3) != null) {
Double l_double = Double.valueOf (l_cellString.replaceAll (",", ""));
Publisher.show (l_double.toString ());
}
else {
Integer l_integer = Integer.valueOf (l_cellString.replaceAll (",", ""));
Publisher.show (l_integer.toString ());
}
}
else {
Publisher.show (l_cellString);
}
}
}
}
catch (MalformedNumberFormatException | UnknownPropertyException | WrappedTargetException l_exception) {
Publisher.show (l_exception.toString ());
}
上記コードを毎回書くのは面倒なので、もちろん、これらの処理はラッパークラスに行なわせることにする。
それでは、そのラッパークラスを使えば、スプレッドシートセルの値を単一のメソッドで得られるのか?
そう。