2017年7月15日土曜日

24: スプレッドシートセルから読んだりスプレッドシートセルに書いたりする、パート2

<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>

Main body START

LibreOfficeまたはApache OpenOfficeのCalcスプレッドシートを拡張機能を通してJavaまたはマクロプログラムから操作する(読むまたは書く)方法を知る

スプレッドシートセルの値はどのように得られるか?

-Hypothesizer

スプレッドシートセルへのリファレンスを得たので、スプレッドシートセルの値を得たい。

-Rebutter

オーケー。

-Hypothesizer

実のところ、それはあまり単純ではない。セルUNOオブジェクトが実装している'com.sun.star.table.XCell'UNOインスタンスにはメソッド、'getValue'があるが、このメソッドの戻りタイプは'double'だ。

-Rebutter

ふむ?どういうことだ?

-Hypothesizer

セルが文字列値を保持していれば、メソッドは0.0を戻す。セルが日付値を保持していれば、メソッドはある種の内部値を戻す(例えば、2017-01-01には42736.0)。セルが真偽値を保持していれば、メソッドは、TRUEには1.0、FALSEには0.0を戻す。セルが式を保持していれば、式の結果が文字列値ならば0.0を戻し、それ以外ならば式の結果に対応する値を戻す。

-Rebutter

それでは、文字列値はどうすれば得られるのか?

-Hypothesizer

セルが文字列値を保持しているときは、式の結果が文字列値である場合も含め、この文字列値を、'com.sun.star.text.XText'インターフェースのメソッド、'getString'で得ることができる。

-Rebutter

セルUNOコンポーネントは'com.sun.star.text.XText'インターフェースを実装している。

-Hypothesizer

そう。実は、この'getString'メソッドは、セルの値が何であれ、その表現文字列を戻す。

-Rebutter

「表現文字列」というのは、セル上に表示される文字列のことか?

-Hypothesizer

そう。例えば、真偽値の'TRUE'や小数値の'1.23'だ。

-Rebutter

ふーむ、とにかく、セルの値を'getValue'メソッドで得るとき、戻りが、例えば、1.0だった場合、その値が何なのかが分からない。それは、整数の1なのか、小数の1.0なのか、真偽のTRUEなのか、それとも何かの日付なのか?

-Hypothesizer

スプレッドシートセルの値が文字列でない場合、セルの内部値は常に'double'なのだと思われる。セルの表現は、セル値表現フォーマットによって制御されている。

-Rebutter

「セル値表現フォーマット」というのは . . . 何だ?

-Hypothesizer

セル上でマウスを右クリックし、'Formating Cells...(セルの書式設定...)'を選択して、'Numbers(数値)'タブで設定できるものだ。「数値フォーマット」と呼ばれているようだが、命名が変だと思われる。それには文字列に対するフォーマットも含まれていて、これは数値ではない。

-Rebutter

なるほど。とにかく、セルのセル値表現フォーマットを得なければならないことになる。

-Hypothesizer

セル値表現フォーマットは、セルUNOオブジェクトのプロパティ値として得ることができる。具体的には、セルUNOコンポーネントが実装している'com.sun.star.beans.XPropertySet'インターフェースのメソッド、'getPropertyValue'で得ることができる。プロパティ名は'NumberFormat'であり、プロパティ値は、整数であるセル値表現フォーマットキーだ。

-Rebutter

それで、その整数のプロパティ値が何を意味するかをどうやって知ることができるのか?

-Hypothesizer

スプレッドシートドキュメントUNOオブジェクトから'com.sun.star.util.XNumberFormats'インターフェースのインスタンスを得ることができ、そのインスタンスから、セル値表現フォーマットキー群とキー毎のフォーマット文字列を得ることができる。

-Rebutter

それらが、そのドキュメントに登録されたセル値表現フォーマットキー群とそのフォーマット文字列なわけだ。

-Hypothesizer

そう。

セル値表現フォーマットとセル値を得るから、我々は、セル値を、望みどおりに解釈することができる。

-Rebutter

いいだろう。

-Hypothesizer

我々がやりたいのは、セルのセル値表現フォーマットに応じて、セル値を適切なデータタイプで得ることだ。我々は、'getString'メソッドからセル値表現文字列を得て、この文字列を適切なデータタイプに変換することにする。具体的には、セル値表現フォーマットに応じて、java.time.LocalDate、java.time.LocalTime、java.time.LocalDateTime、java.lang.Boolean、java.lang.String、java.lang.Integer、java.lang.Doubleのインスタンスを得ることにする。. . . 我々の方法がパフォーマンスの観点から最適だとは私は特に主張しない。

-Rebutter

いいだろう。

-Hypothesizer

セル値表現フォーマットについて、あらゆる可能な表現フォーマットに対処することはここではしない。我々は、自分で使うつもりの特定の表現フォーマットだけに対処する。我々はそうした特定の表現フォーマットだけを使うつもりなので、実用的にはそれで十分だ。

-Rebutter

特定の表現フォーマットというのは具体的には何なのか?

-Hypothesizer

日付には'YYYY-MM-DD'、時刻には'HH:MM:SS'、日付時刻には'YYYY-MM-DD\"T\"HH:MM:SS'、真偽には'BOOLEAN'、文字列には'@'、整数には'0'、小数には'Global'または'Standard'だ。

-Rebutter

'Global'または'Standard'というのはどういう意味だ?

-Hypothesizer

なぜだか分からないが、LibreOfficeのロケール設定が'日本語'に設定されていると、'Standard'が使われる。他のほとんどの場合、'Global'が使われる。

-Rebutter

ふーむ。

'YYYY-MM-DD\"T\"HH:MM:SS'などの表現フォーマットがドキュメント中に存在しなかったらどうなるのか?

-Hypothesizer

存在しなければ、我々のプログラムが登録する。

-Rebutter

おう。

-Hypothesizer

セルに式が保持されている場合、式自体は、'com.sun.star.table.XCell'インターフェースのメソッド、'getFormula'で得ることができる。

-Rebutter

なるほど。

-Hypothesizer

結局、以下のコードを書いた。

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 ());
  }
-Hypothesizer

上記コードを毎回書くのは面倒なので、もちろん、これらの処理はラッパークラスに行なわせることにする。

-Rebutter

それでは、そのラッパークラスを使えば、スプレッドシートセルの値を単一のメソッドで得られるのか?

-Hypothesizer

そう。

Main body END

<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>