ODFDOMを用いた表計算ドキュメントの作成

前回はJavaプログラムでODF(OpenDocument Format)文書を扱うためのオープンソースライブラリ「ODFDOM」を使って、ワープロドキュメント(.odtファイル)を作成する方法を紹介した。今回はそれに引き続き、表計算ドキュメント(.odsファイル)を作成してみたい。

基本的な方法はワープロ文書と同じで、まずはOdfDocumrnt抽象クラスを継承したOdfSpreadsheetDocumetクラスのインスタンスを作成し、それに対してスタイルやコンテンツを追加していく。テーブルを扱うためのクラスはorg.odftoolkit.odfdom.doc.tableパッケージにある。テーブル本体を表すのがOdfTableクラス、カラムを表すのがOdfTableColumnクラス、ローを表すのがOdfTableRowクラス、単体のセルを表すのがOdfTableCellクラスだ。

まずは各セルのコンテンツをそれぞれOdfTableCellオブジェクトとして作成し、それをOdfTableRowオブジェクトにappendCell()メソッドを使って追加することで1行分のコンテンツができる。さらにそのOdfTableRowオブジェクトをOdfTableにappendRow()メソッドを使って追加すれば、テーブルに表が挿入される。

次に示すcreateTextCell()メソッドは、テキストコンテンツを保持したOdfTableCellオブジェクトを生成して返す例である。

    /**
     * テキストコンテンツ用のセルを生成する
     */
    private OdfTableCell createTextCell(OdfFileDom contentDom, String content) {
        OdfTableCell cell = new OdfTableCell(contentDom);
        cell.setOfficeStringValueAttribute(content);
        cell.setOfficeValueTypeAttribute(OfficeValueTypeAttribute.Value.STRING.toString());
        OdfTextParagraph paragraph = new OdfTextParagraph(contentDom, null, content);
        cell.appendChild(paragraph);
        return cell;
    }

まずは元になるドキュメントオブジェクトのDOMを渡してOdfTableCellインスタンスを生成し、そこにsetOfficeStringValueAttribute()メソッドで文字列を渡すことによってテキストコンテンツをを持ったセルを作ることができる。コンテンツの型はsetOfficeValueTypeAttribute()メソッドで指定する。これはODF仕様に定められたoffice:value-type属性の値に該当するものだ。

また、この例ではOdfTableCellの子要素としてOdfTextParagraphオブジェクトを設定している。これにより、必要に応じてテキストコンテンツに任意のスタイルを指定できるようになる。

このcreateTextCell()メソッドを利用して表計算ドキュメントを作成したのが次の例だ。

package jp.co.mycom.toolde;

import org.odftoolkit.odfdom.OdfFileDom;
import org.odftoolkit.odfdom.doc.OdfSpreadsheetDocument;
import org.odftoolkit.odfdom.doc.office.OdfOfficeSpreadsheet;
import org.odftoolkit.odfdom.doc.table.*;
import org.odftoolkit.odfdom.doc.text.OdfTextParagraph;
import org.odftoolkit.odfdom.dom.attribute.office.OfficeValueTypeAttribute;
import org.w3c.dom.Node;

public class CreateSpreadsheet {
    public CreateSpreadsheet() {
        try {
            // スプレッドシート文書を作成
            OdfSpreadsheetDocument odsDoc = OdfSpreadsheetDocument.newSpreadsheetDocument();

            // コンテンツのルートとDOMを取得
            OdfFileDom contentDom = odsDoc.getContentDom();
            OdfOfficeSpreadsheet officeSpreadsheet = odsDoc.getContentRoot();

            // デフォルトで用意されているノードを削除
            Node childNode = officeSpreadsheet.getFirstChild();
            while (childNode != null) {
                officeSpreadsheet.removeChild(childNode);
                childNode = officeSpreadsheet.getFirstChild();
            }

            // テーブルコンテンツの作成
            OdfTable table = new OdfTable(contentDom);

            // ローの追加
            OdfTableRow row;

            row = new OdfTableRow(contentDom);
            row.appendCell(createTextCell(contentDom,"hoge"));
            row.appendCell(createTextCell(contentDom,"piyo"));
            row.appendCell(createTextCell(contentDom,"huga"));
            table.appendRow(row);

            row = new OdfTableRow(contentDom);
            row.appendCell(createTextCell(contentDom,"ほげ"));
            row.appendCell(createTextCell(contentDom,"ぴよ"));
            table.appendRow(row);

            // テーブルコンテンツの追加
            officeSpreadsheet.appendChild(table);
            // ファイルにセーブ
            odsDoc.save("sample.ods");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private OdfTableCell createTextCell(OdfFileDom contentDom, String content) {
        // 省略
    }

    public static void main(String[] args) {
        CreateSpreadsheet sample = new CreateSpreadsheet();
    }
}

OdfSpreadsheetDocumentオブジェクトはデフォルトでひとつの子ノードを持っているため、この例では最初にそれを削除している。その上でテーブルを作成し、2つのローを追加する。最後に、完成したドキュメントの内容を「sample.ods」というファイルに保存する。sample.odsの内容をOpenOffice.orgで開くと図1のようになっていることが確認できる。

図1 ODFDOMを用いて作成した表計算ドキュメントの例

数式を利用する例

上記の例ではセルにはテキストのみを挿入していたが、当然、テキスト以外の数値や日付/時刻、通貨、数式を挿入することもできる。OdfTableCellクラスにはそのための各種メソッドが用意されている。

たとえば数値を挿入する場合にはsetOfficeValueAttribute()メソッドを使用する。このメソッドで挿入された値は、ODF仕様のoffice:value属性の値に相当する。次のコードは、double値を挿入したセルを作成して返すメソッドの例である。

    private OdfTableCell createDoubleCell(OdfFileDom contentDom, double content) {
        OdfTableCell cell = new OdfTableCell(contentDom);
        cell.setOfficeValueAttribute(new Double(content));
        cell.setOfficeValueTypeAttribute(OfficeValueTypeAttribute.Value.FLOAT.toString());
        return cell;
    }

セル内で数式を扱うこともできる。その場合はsetTableFormulaAttribute()メソッドの引数に使用したい数式を指定すればよい。これはtable:formula属性の値に相当する。次のコードは、指定された文字列を数式としてセルに挿入して返すメソッドの例である。

    private OdfTableCell createFormulaCell(OdfFileDom contentDom, String content) {
        OdfTableCell cell = new OdfTableCell(contentDom);
        cell.setTableFormulaAttribute(content);
        cell.setOfficeValueTypeAttribute(OfficeValueTypeAttribute.Value.FLOAT.toString());
        return cell;
    }

これを踏まえて、テキストファイルからテストの点数の一覧を読み込み、その平均点を表示するテーブルを作成してみた。

package jp.co.mycom.toolde;

import java.io.*;
import org.odftoolkit.odfdom.OdfFileDom;
import org.odftoolkit.odfdom.doc.OdfSpreadsheetDocument;
import org.odftoolkit.odfdom.doc.office.OdfOfficeSpreadsheet;
import org.odftoolkit.odfdom.doc.table.*;
import org.odftoolkit.odfdom.doc.text.OdfTextParagraph;
import org.odftoolkit.odfdom.dom.attribute.office.OfficeValueTypeAttribute;
import org.w3c.dom.Node;

public class CreateSpreadsheet2 {
    public CreateSpreadsheet2() {
        try {
            // スプレッドシート文書を作成
            OdfSpreadsheetDocument odsDoc =
                OdfSpreadsheetDocument.newSpreadsheetDocument();

            OdfFileDom contentDom = odsDoc.getContentDom();
            OdfOfficeSpreadsheet officeSpreadsheet = odsDoc.getContentRoot();

            // デフォルトで用意されているノードを削除
            Node childNode = officeSpreadsheet.getFirstChild();
            while (childNode != null) {
                officeSpreadsheet.removeChild(childNode);
                childNode = officeSpreadsheet.getFirstChild();
            }

            // テーブルコンテンツの作成
            OdfTable table = new OdfTable(contentDom);

            OdfTableRow row;

            // 表のヘッダ部分
            row = new OdfTableRow(contentDom);
            row.appendCell(createTextCell(contentDom, "名前"));
            row.appendCell(createTextCell(contentDom, "得点"));
            table.appendRow(row);

            // ファイルからデータを読み込んで追加
            BufferedReader input = new BufferedReader(new FileReader("input.csv"));
            String buf;
            String[] data;
            int dataCount = 0;
            while((buf = input.readLine()) != null) {
                dataCount++;
                data = buf.replace("\"","").split(",");
                row = new OdfTableRow(contentDom);
                row.appendCell(createTextCell(contentDom, data[0]));
                row.appendCell(createDoubleCell
                               (contentDom, Double.parseDouble(data[1].trim())));
                table.appendRow(row);
            }

            // 平均点のローを追加
            table.appendRow(new OdfTableRow(contentDom));
            row = new OdfTableRow(contentDom);
            row.appendCell(createTextCell(contentDom, "平均点"));
            row.appendCell(createFormulaCell(contentDom,
                                             "=AVERAGE([.B2:.B" + (dataCount+1) + "]"));
            table.appendRow(row);

            // テーブルコンテンツの追加
            officeSpreadsheet.appendChild(table);
            // ファイルにセーブ
            odsDoc.save("sample2.ods");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private OdfTableCell createTextCell(OdfFileDom contentDom, String content) {
        // 省略
    }

    private OdfTableCell createDoubleCell(OdfFileDom contentDom, double content) {
        // 省略
    }

    private OdfTableCell createFormulaCell(OdfFileDom contentDom, String content) {
         // 省略
    }

    public static void main(String[] args) {
        CreateSpreadsheet2 sample = new CreateSpreadsheet2();
    }
}

このプログラムに次のようなデータを読み込ませると、生成されるODFドキュメントは図2のようになり、B10のセルで平均点が表示できていることがわかる。

"佐藤","80"
"鈴木","90"
"高橋","75"
"田中","85"
"渡辺","60"
"伊藤","70"
"山本","90"

図2 ODFDOMで数式を扱った例

今回は簡略化のためにスタイルを使用していないので味気ない見た目になってしまっているが、ローやカラム、セル、セル内のコンテンツに個別にスタイルを指定すればより一般的な見た目の表計算ドキュメントを作ることができる。たとえば、日付や時刻、通貨の表示フォーマットを指定するなどといったことも可能である。

もっとも、(残念なことだが)現時点ではODFDOMを使いこなすためにはODFそのものに対する知識もある程度必要となる。より一般的に使えるようにするためには、もう少し高レベルなAPIも提供する必要があるだろう。ODFについてはOASISのサイトも参照していただきたい。