読み取り、リサイクル、そして再利用: Excel、XML、および Java 技術によってレポート作成を楽にする: 第 2 回

XML と Excel との間でレポート形式を変換する

この連載の第 1 回では、Java™ 技術と Apache POI を利用して Microsoft® Excel® ファイルを読み取る手順を説明しました。けれども Excel ファイルの読み取りは始まりでしかありません。今回の記事では、レポート形式を変換しなければならないことを考えて青ざめている開発者を安心させるために、Excel と XML を統合します。

Shaene M Siders, Writer and Consultant, Dragon Under Glass

Photo of Shaene SidersShaene Siders は、技術、トレーニング、そして娯楽に関するコンサルタント兼ライターです。1996年に執筆活動と Web 開発会社を始め、2003年からは ClearCase および ClearQuest の IBM Rational 認定インストラクターとしても活躍しています。彼女はトレーニング・コースウェア・シリーズ「Java for Mad Scientists」の著者兼製作者であり、BuildForge、Rational Functional Tester、Rational Performance Tester、および Rational Manual Tester の IBM Rational 認定インストラクターも務めています。



2010年 3月 02日

今週、上司にあの上手い言い訳番号 432 を使ってみましたか?あるいは使いたいという衝動に駆られましたか?

願わくは、その必要がなかったことを祈ります。この連載の第 1 回では、Microsoft® Excel® スプレッドシートを Java™ 技術で操作する方法について説明しました (「参考文献」にリンクを記載している「読み取り、リサイクル、そして再利用: Excel、XML、および Java 技術によってレポート作成を楽にする: 第 1 回」を参照)。Apache POI をダウンロードして、それを使用するようにセットアップするだけで、まるで公園を散歩するのと同じくらい簡単に、Excel スプレッドシートをウォークスルーできるようになります。しかも、これは環境にも優しい方法です。

けれども Excel ファイルの読み取りは始まりでしかありません。今回の記事で説明するのは、読み取った Excel ファイルを、Apache POI と XOM (XML Object Model) を使用して XML オブジェクトに保存する方法です。Excel ファイルを XML オブジェクトに保存すれば、これらのオブジェクトを「リサイクル」して、まったく新しい Excel スプレッドシートと XML ファイルを作成できるようになります。

よく使われる頭字語

  • API: Application Programming Interface
  • ASCII: American Standard Code for Information Interchange
  • HSSF: Horrible Spread Sheet Format
  • HTML: HyperText Markup Language
  • XML: Extensible Markup Language
  • XSSF: XML SpreadSheet Format

サンプル・アプリケーション

サンプル・アプリケーションには、架空の Planet Power 社の Excel スプレッドシート、Employee_List.xls が含まれています。Planet Power 社の社長は、優秀な成績を上げている社員たちに、給料の 1 パーセントを彼のお気に入りの環境保護施策である「遺伝子組み換えによって巨大化した野生のハムスターの惑星間保護区域 (GEE WHIS)」に寄付するよう説き伏せました。サンプル・アプリケーションでは、その寄付額を計算し、保護区域に提出するための XML レポートを作成します。その一方で、社長に提出する Excel スプレッドシートも作成します。

この記事のサンプル・コードに従うには、まずサンプルをダウンロードしてファイルを C:\Planet Power に解凍してください。その後で、Eclipse を起動します。

Employees2 Eclipse プロジェクト

以下の手順に従って、サンプル・アプリケーションが含まれる Employees2 Eclipse プロジェクトをインポートしてください。

  1. Eclipse の「Package Explorer (パッケージ・エクスプローラー)」を右クリックし、「Import (インポート)」をクリックします。
  2. 「General (一般)」を展開し、「Existing Projects into Workspace (既存プロジェクトをワークスペースへ)」を選択したら、「Next (次へ)」をクリックします (図 1 を参照)。
    図 1. 既存のプロジェクトをワークスペースに取り込む
    Eclipse の「Import (インポート)」ダイアログのスクリーン・キャプチャー。ワークスペースに取り込む対象として既存のプロジェクトが選択されています。
  3. Select root directory (ルート・ディレクトリーの選択)」フィールドの隣にある「Browse (参照)」をクリックし、C:\Planet Power\Employees2 までナビゲートします。
  4. 「Employees2」フォルダーを選択して「OK」をクリックし、それから「Finish (完了)」をクリックします (図 2 を参照)。
    図 2. Eclipse へのプロジェクトのインポートを完了する
    Eclipse の「Import (インポート)」ダイアログのスクリーン・キャプチャー。Eclipse へのプロジェクトのインポートを完了する「Finish (完了)」ボタンが示されています。

以上の操作によって、「Package Explorer (パッケージ・エクスプローラー)」ペインに、「Employees2」フォルダーが表示された状態になります。

注: このプロジェクトでは、「Employees2」プロジェクトの src\default_package の下に置かれている ExcelXML.java ファイルを使用します。


開始手順

連載の第 1 回での最初のステップは、Apache POI とともに、例外クラスおよびファイル処理クラスをインポートすることでした (第 1 回のリンクについては「参照文献」を参照)。今回はさらに、XML API クラスと、数値を処理するためのクラスも追加します (リスト 1 を参照)。

リスト 1. クラスをインポートする (ExcelXML.java)
// File and exception handling imports
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;

// Apache POI imports
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFHyperlink;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFDataFormatter;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFDataFormat;

// XOM imports
import nu.xom.Document;
import nu.xom.Elements;
import nu.xom.Element;
import nu.xom.Attribute;
import nu.xom.Serializer;

// Imports for later calculations
import java.text.NumberFormat;

クラスのインポートが完了すると、main() メソッド内のプログラミングを開始することができます。


ファイルの処理

XML ファイルの読み取り

この記事では XML の書き込みについては説明しますが、XML ファイルの読み取りについては説明しません。XML を読み取る方法については、「参考文献」に記載されている XOM の Web サイトへのリンクにアクセスするか、ダウンロードした Eclipse プロジェクトに含まれている XMLReader.java という名前のサンプル・コードを参照してください。

リスト 2 のコードに、ファイルの例外処理構造のエラー (IOException) と数値変換のエラー (ParseException) を示します。

リスト 2. 例外処理をセットアップする (ExcelXML.java)
public class ExcelXML {

   public static void main(String[] args) {

      try {

      // Put file and number handling code here.

   // End try
   }
   catch (IOException e) {
      System.out.println("File Input/Output Exception!");
   }
   catch (ParseException e) {
      System.out.println("Number Parse Exception!");
   }

   // End main method
   }

// End class block
}

これで、XOM の作業に取り掛かれます。


XOM、Document、および XML オブジェクト

XOM は XML の操作を単純化するために、XML 文書をその構成部分を表すオブジェクトに構文解析します。XML 文書全体を表すクラスは nu.xom.Document です。Document から、他の部分を追加したり、他の部分にアクセスしたりすることができます。XML を操作するためのクラスには以下のものがあります。

  • nu.xom.Builder
  • nu.xom.Document
  • nu.xom.Elements
  • nu.xom.Element
  • nu.xom.Serializer

XML の主要な部分は要素と呼ばれます。要素はタグのペアと、その 2 つのタグに囲まれたコンテンツからなります。以下は、サンプル・ファイル weather_service.xml から抜粋した要素の例です。

<dawn>07:00</dawn>

「dawn」という単語、この単語を囲む括弧 (<>)、そしてスラッシュ (/) で構成されるのがタグで、そのコンテンツは 07:00 です。

要素には、他の要素またはテキスト・コンテンツ、あるいはその両方を含めることができます。要素を含む要素は親と呼ばれ、要素のなかにある要素は子要素、または子と呼ばれます。

XOM では、要素は nu.xom.Element オブジェクトによって表されます。複数の要素が 1 つにまとめられている場合は、nu.xom.Elements オブジェクトで表されます。

ルート要素

要素を検索するには、すべての整形式 XML 文書が必ず持っている 1 つの要素を使用します。それは、ルート要素です。ルートは、他のすべての要素のコンテナーとしての役割を果たします。文書にルートがない場合、その文書は適切な XML であるとは言えません。

ルートを検索するには Document オブジェクトで getRootElement() を使用します。まずは 1 つの要素のみを取得することから、文書の探査戦略は始まります。その後、ルートの子を操作するとしたら、Element.getChildElements() をアレンジして、すべての子要素のリストを取得するか、または指定した名前を持つ子要素のリストを取得します。単一の要素が必要であれば、Element.getFirstChildElement() により、指定した名前を持つ最初の子要素だけを取得します。

この方法に聞き覚えはありませんか? XML 文書を繰り返し処理して子の子を検索するという方法は、Excel ワークシートの繰り返し処理と似ています。

前回の記事では、Apache POI の getStringCellValue() メソッドを使って Excel の HSSFCell からストリング値を取得する方法を説明しました。それと同じように、XOM は Element.getValue() を使用して、XML 要素からストリングのコンテンツを取得します。ただし、セルを操作する場合とは異なり、XOM 要素を操作する場合には、データ型をテストする必要はありません。XML はすべてテキストだからです。


Excel と XML のマッシュアップと XML マークアップ

スプレッドシートをトラバースしてデータを抽出する際には、抽出したデータを任意の数のオブジェクト (String、Array、Squirrel) に保存することができます。この記事では XML 要素を使用します (そうすれば Squirrel での保存が容易になります)。さらに、2 つの形式の間での変換についても説明します。

リスト 3 は、ワークブックの情報を保存する HSSFWorkbook と XML Document を新しく作成するためのコードです。

リスト 3. ワークブックと XML Document を作成する (ExcelXML.java)
// First, create a new XML Document object to load the Excel sheet into XML.
// To create an XML Document, first create an element to be its root.
Element reportRoot = new Element("sheet");

// Create a new XML Document object using the root element
Document XMLReport = new Document(reportRoot);

// Set up a FileInputStream to represent the Excel spreadsheet
FileInputStream excelFIS = new FileInputStream("C:\\Planet Power\\Employee_List.xls");

// Create an Excel Workbook object using the FileInputStream created above 
HSSFWorkbook excelWB = new HSSFWorkbook(excelFIS);

リスト 3 の内容は、順序が逆のような気がしませんか?このコードは、Document を作成する前に新しい Element を作成しています。なぜでしょうか?

まったく新しい Document には、1 つの要件があります。それは、Element がそのルート要素でなければならないことです。前にも説明したように、Document はルート要素がなければ、整形式 XML 文書を表すことができません。したがって、Document を作成するときにはルート要素を渡さなければならないというわけです。

それとは逆に、ルートに属さない要素を作成することは可能です。これらの要素はどこにも属さない要素のままにしておくことも、文書のルートやその他の要素に追加することもできます。ただし、その前に、XML の構造を計画しておく必要があります。

XML の構造

XML は主に要素と属性を使用してデータの内容を表し、書式の設定を行います。属性とは名前と値のペアのことです。名前の部分が、その属性が保持するデータを表します。属性の値は、その属性が持つデータです。HTML コードの作成者は、以下のような属性を使用することに慣れています。

<font size="12">Hello, Planet!</font>

上記のリスト全体が、1 つの要素を構成します。タグは <font> で、その属性は size="12" です。属性の名前は size で、その値は 12 です。名前と値は等号 (=) によって結ばれています。一般に、データは要素に保存される一方、メタデータは属性に保存されます。

Employee_List.xls ワークブックの場合には、どのようにして XML に変換するのでしょうか。

マークアップ方法その 1: ワークブックの構造を模倣する

XML を構造化する 1 つの方法は、Excel ワークブックの物理構造を模倣するというものです。リスト 4 を見てください。

リスト 4. 一般的なワークブックの構造に基づく XML 構造
<sheet>
   <row>
      <cell>
         Thumb
      </cell>
      <cell>
         Green
      </cell>
      <cell>
         Growing Plants
      </cell>
      <cell>
         5:00 AM
      </cell>
      <cell>
         2:00 PM
      </cell>
      <cell>
         150,000
      </cell>
   </row>
</sheet>

この形式では、行とセルは明らかですが、それぞれのセルが保持するデータについてはどうでしょう。5:00 AM が、その従業員の勤務開始時間を意味するのか、終了時間を意味するのかは明らかでありません。そこで、列の名前をセルの属性として使用するという方法が考えられます。

列名をセルの属性として使用するという方法は、Excel スプレッドシートの構造を維持することが重要な場合には上手くいくかもしれません。けれども XOM の場合、XPath クエリーの作成方法を学ばない限り、要素の集合をそれぞれの属性値を基準に簡単に抽出する手段はありません。列名を属性として保存すると、特定の列からのすべての要素の集合を見つけるために、追加のコードが必要になってきます。XOM には要素をその名前で検索するメソッドがあるので、Excel の列名は属性としてではなく、要素の名前として使用することを検討してください。

マークアップ方法その 2: データ構造を模倣する

次に、表示形式に基づく XML を作成する代わりに、データの内容を表す構造の XML を検討してみます。データの内容を記述するのはまさに、XML が得意とするところです (リスト 5 を参照)。

リスト 5. データの内容の記述をベースにした XML 構造
<EmployeeData>
   <Employee>
      <EmployeeLastName>
         Thumb
      </EmployeeLastName>
      <EmployeeFirstName>
         Green
      </EmployeeFirstName>
      <MainSuperPower>
         Growing Plants
      </MainSuperPower>
      <DailyStartTime>
         5:00 AM
      </DailyStartTime>
      <DailyEndTime>
         2:00 PM
      </DailyEndTime>
      <Salary>
         150,000
      </Salary>
   </Employee>
</EmployeeData>

この方法では、各 Employee 要素で getChildElements("Salary") メソッドを使用することができるため、簡単に従業員の給料を検索することができます。

ただし、Excel の列名を要素名として使用するのは危険です。Excel の列では、XML 要素の名前に含められない文字 (スペースなど) でも使用できるからです。したがって、これらの文字は確実に、要素名として使用される可能性のある名前から削除しなければなりません。

このようにデータを構造化するには、データについて十分に理解していなければなりません。XML で Employee としなければならない Excel スプレッドシートの行をプログラムで求めるのは困難です。また、ルート要素の名前 (上記の例では EmployeeData) を求めるのも同じく簡単なことではありません。

さらに、構造が変更された場合や、社長がこのコードを他のスプレッドシートでも使用したいと要求してきた場合のことを考えてみてください。スプレッドシートの行で、従業員ではなく、ハムスターの種類を表示することも考えられます。その場合には、プログラムで行と呼んでいるものを、調整しなければなりません。

マークアップ方法その 3: Excel と XML をマッシュアップして混ぜ合わせる

今度は、Excel を構造化した XML に、データ構造化マークアップを混ぜ合わせる方法について検討してみましょう (リスト 6 を参照)。

リスト 6. ワークブックの構造にデータ・ベースのマークアップを混ぜ合わせる
<sheet>
   <row>
      <EmployeeLastName>
         Thumb
      </EmployeeLastName>
      <EmployeeFirstName>
         Green
      </EmployeeFirstName>
      <MainSuperPower>
         Growing Plants
      </MainSuperPower>
      <DailyStartTime>
         5:00 AM
      </DailyStartTime>
      <DailyEndTime>
         2:00 PM
      </DailyEndTime>
      <Salary>
         150,000
      </Salary>
   </row>
</sheet>

各 Excel スプレッドシートの最初の行に列名が含まれている場合、この混合形式には、複数の Excel ワークブックを扱えるだけの柔軟性があります。文書に含まれるデータおよび行は、スプレッドシートの間で一貫していない可能性があるため、汎用の sheet および row を要素名として使用します。このような名前にしても、コードを読むプログラマーはその意味を理解できるはずです。

注: 純粋なデータ中心のマークアップの場合と同じく、不正な文字が XML 要素名に入り込まないように注意してください。

この記事では、XML と Excel の混合形式を使用してセルの値を保存しますが、値以外のセルの情報、例えばデータ型や書式設定についてはどのように対処するのでしょうか。

データ型と書式設定はメタデータです。保存する必要のあるメタデータに応じて、dataFormatdataType などの属性を使用することができます。


変換コードの作成

XML の構造を決定した後は、Excel データを XML 要素に保存する作業に取り掛かります。連載の第 1 回 (「参考文献」にリンクを記載) と同じループを使用して Excel スプレッドシートをトラバースした後、XML を追加します。リスト 7 では、前回の記事で使用した Excel を読み取るためのコードを再利用しています。

リスト 7. Excel スプレッドシートのトラバースを開始する (ExcelXML.java)
// Traverse the workbook's rows and cells.			
// Remember, excelWB is the workbook object obtained earlier.

// Just use the first sheet in the book to keep the example simple.
// Pretend this is an outer loop (looping through sheets).

HSSFSheet oneSheet = excelWB.getSheetAt(0);

      // Now get the number of rows in the sheet
      int rows = oneSheet.getPhysicalNumberOfRows();				

      // Middle loop: Loop through rows in the sheet

         for (int rowNumber = 0; rowNumber < rows; rowNumber++) {
            HSSFRow oneRow = oneSheet.getRow(rowNumber);

            // Skip empty (null) rows.
            if (oneRow == null) {
               continue;
            }

スプレッドシートの行の繰り返し処理と同時に、それぞれの行を表す XML 要素を作成します (リスト 8 を参照)。

リスト 8. row 要素を作成する (ExcelXML.java)
            // Create an XML element to represent the row.
            Element rowElement = new Element("row");

空の行や null の行がある場合に備えて、まだ Document には行を追加しないでください。セルを追加した後に行が空でなければ、行のループ処理の最後で、行をルート要素に追加することができます。

続いて、セルを読み取るための内部ループを開始します (リスト 9 を参照)。

リスト 9. 続いて Excel セルを繰り返し処理する (ExcelXML.java)
            // Get the number of cells in the row
            int cells = oneRow.getPhysicalNumberOfCells();

            // Inner loop: Loop through each cell in the row

            for (int cellNumber = 0; cellNumber < cells; cellNumber++) {
               HSSFCell oneCell = oneRow.getCell(cellNumber);

               // If the cell is blank, the cell object is null, so don't 
               // try to use it. It will cause errors.
               // Use continue to skip it and just keep going.
               if (oneCell == null) {
                  continue;
               }

内部ループのなかでは、セルを表す XML 要素を作成します。要素の名前には、それぞれのセルに対応する列名を使用します。要素名を空にすることはできないため、リスト 10 ではそれぞれの名前を header にデフォルト設定します。最初の行の名前はデフォルトの header のままにし、以降の行では最初の行の要素に保存された名前に基づいて、新しい名前を設定します。最初の行の要素に保存された名前とは、Excel スプレッドシートの列名です。

リスト 10. 列名を要素名として使用して、セルの要素を作成する (ExcelXML.java)
// Set up a string to use just "header" as the element name
// to store the column header cells themselves.

String elementName="header";

// Figure out the column position of the Excel cell.
int cellColumnNumber = oneCell.getColumnIndex();

// If on the first Excel row, don't change the element name from "header,"  
// because the first row is headers. Before changing the element name,
// test to make sure you're past the first row, which is the zero row.

if (rowNumber > 0)

 // Set the elementName variable equal to the column name
elementName=reportRoot.getFirstChildElement("row").getChild(cellColumnNumber).getValue();

// Remove weird characters and spaces from elementName,
// as they're not allowed in element names.
elementName = elementName.replaceAll("[\\P{ASCII}]","");
elementName = elementName.replaceAll(" ", "");

// Create an XML element to represent the cell, using 
// the calculated elementName

Element cellElement = new Element(elementName);

リスト 10 のなかでは、さまざまなことが行われています。中央のコメント (// Set the elementName variable equal to the column name) の下にある長い行では、elementName が何かに設定されます。何に設定されるかと言うと、getValue() によって、ある要素に含まれるテキストの値に設定されることが、この行の終わりを見るとわかります。それは、どの要素の値なのでしょうか。

reportRoot 内の最初の row 要素 (reportRoot.getFirstChildElement("row")) のなかで、コードは getChild(cellColumnNumber) を使用して、子要素をそのインデックス番号を基準に検索します。スプレッドシートの最初の行は、すでに最初の row 要素に保存してあります。つまり、その行の子要素の値はスプレッドシートの列名であり、インデックス番号はスプレッドシートでの現在のセルの列番号と同じです。そのため、elementName に設定される値は、見出し要素からなる最初の行のなかで、そのインデックス番号に対応する列の名前ということになります。

このコードは次に、スプレッドシートに存在する可能性のある不正な文字を elementName ストリングから除去します。それにはまず、StringreplaceAll() メソッドですべての非 ASCII 文字を空のストリングに置き換え、次に、すべてのスペースを空のストリングに置き換えます。これで、両方の行が正規表現を使用することになります。正規表現については、「参考文献」を参照してください。

最後に、リスト 10 の終わりにある行が適切な列名を使用して要素を作成します。


属性と要素の追加

要素と同じく、属性も単独で作成することができます。属性は、addAttribute() メソッドを使って要素に追加するまでは、どこにも属さない状態にしておくことができます。属性を作成したら、セルのメタデータを取り込んでください。それには、Apache POI の HSSFCell オブジェクトの getDataFormatString() といったゲッター・メソッドを使用します (リスト 11 を参照)。

リスト 11. 属性を追加する (ExcelXML.java)
// Create an attribute to hold the cell's format.
// May be repeated for any other formatting item of interest.
String attributeValue = oneCell.getCellStyle().getDataFormatString();
Attribute dataFormatAttribute = new Attribute("dataFormat", attributeValue);

// Add the attribute to the cell element
cellElement.addAttribute(dataFormatAttribute);

これで、属性を持つ要素が存在するようになりました。

要素のデータを取得するには、必ず HSSFCell ごとにデータ型をテストして、どのゲッター・メソッドを使用するべきかを調べてください。いずれにしてもデータ型のテストは行うため、セルのデータ型情報を保存する属性を作成することもできます。

データを要素に追加し、その要素を行の子として追加するのは、ストリング値を操作している場合には簡単です (リスト 12 を参照)。

リスト 12. 属性を追加し、セルのテキストを要素に追加し、要素を行に追加する (ExcelXML.java)
switch (oneCell.getCellType()) {

   case HSSFCell.CELL_TYPE_STRING:

      // If the cell value is string, create an attribute
      // for the cellElement to state the data type is a string

      Attribute strTypeAttribute = new Attribute("dataType", "String");
      cellElement.addAttribute(strTypeAttribute);

      // Append the cell text into the element
      cellElement.appendChild(oneCell.getStringCellValue());

      // Append the cell element into the row
      rowElement.appendChild(cellElement);

      break;

case セクションは、データ型ごとに繰り返してください。ただし、数値データの場合には注意が必要です。連載の第 1 回で指摘したように、抽出した Excel データは、スプレッドシートのデータと同じではありません (「参考文献」に記載した第 1 回のリンクを参照)。抽出したデータは、何も処理していないそのままのデータです。日付などの特定の数値は、未処理のデータの場合、奇異な表示になってしまいます。書式設定なしで保存されているとしたら、正しい数値にならない場合もあります。そのため数値データには、要素に追加する前に適切な書式を設定しなければなりません。この目的を果たすためには、Apache POI の HSSFDataFormatter クラスとそのメソッド、formatCellValue() を使用することができます。その方法は、リスト 13 のとおりです。

リスト 13. 属性を追加し、数値データの書式を設定してから、cell 要素を追加する (ExcelXML.java)
   case HSSFCell.CELL_TYPE_NUMERIC:
      // If the cell value is a number, create an attribute
      // for the cellElement to state the data type is numeric
      Attribute cellAttribute = new Attribute("dataType", "Numeric");

      // Add the attribute to the cell
      cellElement.addAttribute(cellAttribute);

      // Apply the formatting from the cells to the raw data
      // to get the right format in the XML. First, create an
      // HSSFDataFormatter object.

      HSSFDataFormatter dataFormatter = new HSSFDataFormatter();

      // Then use the HSSFDataFormatter to return a formatted string
      // from the cell rather than a raw numeric value:
      String cellFormatted = dataFormatter.formatCellValue(oneCell);

      //Append the formatted data into the element
      cellElement.appendChild(cellFormatted);

      // Append the cell element into the row
      rowElement.appendChild(cellElement);

      break;

case セクションは、考えられるセル・タイプごとに繰り返します。完全なサンプル・コードについては、ExcelXML.java を参照してください。

セル・データを保存したら、内部ループを閉じます。行を表す中央のループを閉じる前には、row 要素が空であるかどうかをテストする必要があります。空でなければ row 要素をルート要素に追加し、その上で中央のループを閉じます (リスト 14 を参照)。

リスト 14. row 要素をルート要素に追加する (ExcelXML.java)
      // End inner loop
      }

   // Append the row element into the root 
   // if the row isn't empty.  
   if (rowElement.getChildCount() > 0) {
      reportRoot.appendChild(rowElement);
   }

   // End middle loop	
   }

これで、Excel ファイルが完全な XML 文書となりました。


XML の内部

XML を使用して、例えば給料の 1 パーセントを算出するなどの計算を行うときには、ストリングと数値との間で変換を行う必要があります。リスト 15 に、一例を示します。

リスト 15. 給料の 1 パーセントを計算して、その値を Donation 要素に保存する (ExcelXML.java)
// To get employees' salaries, iterate through row elements and get a collection of rows

Elements rowElements = reportRoot.getChildElements("row");

// For each row element

for (int i = 0; i < rowElements.size(); i++) {

   // Get the salary element, 
   // Calculate 1% of it and store it in a donation element.
      // Unless it's the first row (0), which needs a header element.
   if (i==0) {
      Element donationElement = new Element("header");
      donationElement.appendChild("Donation");

      Attribute dataType = new Attribute("dataType","String");
      donationElement.addAttribute(dataType);

      Attribute dataFormat = new Attribute("dataFormat","General");
      donationElement.addAttribute(dataFormat);

      // Append the donation element to the row element.
      rowElements.get(i).appendChild(donationElement);
   }

   // If the row is not the first row, put the donation in the element.
   else {
      Element donationElement = new Element("Donation");

      Attribute dataType = new Attribute("dataType","Numeric");
      donationElement.addAttribute(dataType);

      // The dataFormat of the donation should be the same 
      // number format as salary, which looking at the XML file tells
      // us is "#,##0".
      Attribute dataFormat = new Attribute("dataFormat","#,##0");
      donationElement.addAttribute(dataFormat);

      // Get the salary element and its value
      Element salaryElement = rowElements.get(i).getFirstChildElement("Salary");
      String salaryString = salaryElement.getValue();

      // Calculate 1% of the salary. Salary is a string
      // with commas, so it 
      // must be converted for the calculation.

      // Get a java.text.NumberFormat object for converting string to a double
      NumberFormat numberFormat = NumberFormat.getInstance(); 

      // Use numberFormat.parse() to convert string to double.
      // Throws ParseException
      Number salaryNumber = numberFormat.parse(salaryString);

      // Use Number.doubleValue() method on salaryNumber to 
      // return a double to use in the calculation.
      // Perform the calculation to figure out 1%.
      double donationAmount = salaryNumber.doubleValue()*.01;

      // Append the value of the donation into the donationElement.
      // donationAmount is a double and must be converted to a string.
      donationElement.appendChild(Double.toString(donationAmount));

      // Append the donation element to the row element
      rowElements.get(i).appendChild(donationElement);

      //End else
      }

// End for loop 
}

以上のように、行ごとに追加の Donation 要素を保存したら、XML Document オブジェクトの作成作業は完了です。

XOM では、nu.xom.Serailizer.write() を使用して簡単に Document オブジェクトを XML ファイルに書き込むことができます (リスト 16 を参照)。

リスト 16. XML を Excel に書き込む (ExcelXML.java)
   // Print out the XML version of the spreadsheet to see it in the console
   System.out.println(XMLReport.toXML());

   // To save the XML into a file for GEE WHIS, start with a FileOutputStream
   // to represent the file to write, C:\Planet Power\GEE_WHIS.xml.
   FileOutputStream hamsterFile = new FileOutputStream("C:\\Planet Power\\GEE_WHIS.xml");

   // Create a serializer to handle writing the XML
   Serializer saveTheHamsters = new Serializer(hamsterFile);

   // Set child element indent level to 5 spaces to make it pretty
   saveTheHamsters.setIndent(5);

   // Write the XML to the file C:\Planet Power\GEE_WHIS.xml
   saveTheHamsters.write(XMLReport);

ハムスターたちは、この新しい寄付レポートに大喜びすることでしょう。XML は、彼らのネイティブ言語だからです。


Excel に再び書き込む

XML を Excel スプレッドシートに書き込むには、XML を繰り返し処理して、セルの値と書式を設定します。リスト 17 に、行のループを設定して開始するためのコードを記載します。

リスト 17. XML を Excel に書き込むようにセットアップする (ExcelXML.java)
// Create a new Excel workbook and iterate through the XML 
// to fill the cells.
// Create an Excel workbook object 
HSSFWorkbook donationWorkbook = new HSSFWorkbook();

// Next, create a sheet for the workbook.	
HSSFSheet donationSheet = donationWorkbook.createSheet(); 

// Iterate through the row elements and then cell elements

// Outer loop: There was already an elements collection of all row elements
// created earlier. It's called rowElements. 
// For each row element in rowElements:

for (int j = 0; j < rowElements.size(); j++) {

   // Create a row in the workbook for each row element (j)
   HSSFRow createdRow = donationSheet.createRow(j);

   // Get the cell elements from that row element and add them to the workbook.
   Elements cellElements = rowElements.get(j).getChildElements();

行のループと同じく、セルのループを作成するのも簡単です。難しいのは、セルの書式を設定する部分です。

セルのフォント、枠線、数値形式 (日付/時刻形式を含む) などのスタイル・オプションを表すのは、HSSFCellStyle オブジェクトです。けれども、スタイルはワークブックごとに決められるものであり、セル単位ではありません。HSSFCellStyle オブジェクトが表すのは、ワークブックに存在し、セルに適用できる名前付きスタイルです。これらのスタイルは、Microsoft Office Word での名前付きスタイルと同じように、スタイル・オプションをグループにまとめたものです。同様に、HSSFDataFormat はワークブックごとに作成されますが、日付/時刻形式などの数値の書式設定のみを表します。

セルにスタイルを指定するには、ワークブックに新しい HSSFCellStyle を作成するか、既存の HSSFCellStyle を使用して、そのスタイルを HSSFCell.setCellStyle() を使ってセルに適用します。セルの数値形式を設定する場合は、セルに直接設定するのではなく、HSSFCellStyle の数値形式を設定してから、HSSFCellStyle をセルに適用します。

HSSFDataFormat オブジェクトは、ワークブック内の番号でインデックスが付けられます。HSSFCellStyle にどの HSSFDataFormat を使用するのかを指示するには、HSSFDataFormat のインデックス番号が必要となります。このインデックス番号は短整数型であり、HSSFDataFormat オブジェクトではありません。

幸い、HSSFDataFormat オブジェクトには getFormat() というメソッドがあります。目的の書式を表すストリングを渡すと、このメソッドは、ストリングと一致する HSSFDataFormat のインデックス番号を返します。インデックスは短整数型として返されます。一致するものがない場合には、新しい HSSFDataFormat を作成して、そのインデックスを返します。そのインデックスを使用して、該当する書式をセルのスタイルに適用し、そのセルのスタイルをセルに適用することができます (リスト 18 を参照)。

リスト 18. cell 要素をループして、適切な数値形式をセットアップしてからデータを挿入する
   // Middle loop: Loop through the cell elements.
   for (int k = 0; k < cellElements.size(); k++) {	

      // Create cells and cell styles. Use the row's
      // createCell (int column) method.
      // The column index is the same as the cell element index, which is k.
      HSSFCell createdCell = createdRow.createCell(k);	

      // To set the cell data format, retrieve it from the attribute 
      // where it was stored: the dataFormat attribute. Store it in a string.
      String dataFormatString = cellElements.get(k).getAttributeValue("dataFormat");

      // Create an HSSFCellStyle using the createCellStyle() method of the workbook.
      HSSFCellStyle currentCellStyle = donationWorkbook.createCellStyle();

      // Create an HSSFDataFormat object from the workbook's method
      HSSFDataFormat currentDataFormat = donationWorkbook.createDataFormat();

      // Get the index of the HSSFDataFormat to use. The index of the numeric format
      // matching the dataFormatString is returned by getFormat(dataFormatString).
      short dataFormatIndex = currentDataFormat.getFormat(dataFormatString);

      // Next, use the retrieved index to set the HSSFCellStyle object's DataFormat.
      currentCellStyle.setDataFormat(dataFormatIndex);

      // Then apply the HSSFCellStyle to the created cell.
      createdCell.setCellStyle(currentCellStyle);

セルのスタイルを設定した後、setCellValue() を使用して、データ型のほとんどをセルに挿入します。

数値データには特殊な処理が必要です。数値をテキストとしてではなく、数値として保存するには、まず数値を double 型に変換します。日付については double 型に変換すると誤った数値になるので、変換してはなりません。そのため、数値データのデータ形式をテストし、その数値が日付であるかどうかを判断する必要があります (リスト 19 を参照)。

リスト 19. 特定の数値データを double 型に変換してセルにデータを挿入する
      // Set cell value and types depending on the dataType attribute

      if (cellElements.get(k).getAttributeValue("dataType")=="String") {
         createdCell.setCellType(HSSFCell.CELL_TYPE_STRING);
         createdCell.setCellValue(cellElements.get(k).getValue());
      }

      if (cellElements.get(k).getAttributeValue("dataType")=="Numeric") {
         createdCell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);

         // In this spreadsheet, number styles are times, dates,
         // or salaries. To store as a number and not as text,
         // salaries should be converted to doubles first.
         // Dates and times should not be converted to doubles first,
         // or you'll be inputting the wrong date or time value.
         // Dates and times can be entered as Java Date objects.

         if (cellElements.get(k).getAttributeValue("dataFormat").contains("#")) {

            // If formatting contains a pound sign, it's not a date.
            // Use a Java NumberFormat to format the numeric type cell as a double,
            // because like before, the element has commas in it.
            NumberFormat numberFormat = NumberFormat.getInstance(); 
            Number cellValueNumber = numberFormat.parse(cellElements.get(k).getValue());
            createdCell.setCellValue(cellValueNumber.doubleValue());

            // Add a hyperlink to the fictional GEE WHIS Web site just
            // to demonstrate that you can.
            HSSFHyperlink hyperlink = new HSSFHyperlink(HSSFHyperlink.LINK_URL);
            hyperlink.setAddress("http://www.ibm.com/developerworks/");
            createdCell.setHyperlink(hyperlink);
         }

         else {
            // if it's a date, don't convert to double
            createdCell.setCellValue(cellElements.get(k).getValue());
         }

      }

//  Handle formula and error type cells. See ExcelXML.java for the full example.

      //End middle (cell) for loop
      }
   // End outer (row) for loop	
   }

書式設定は、TODAY()NOW() などの日付を作成する Excel 関数にも必要です。リスト 20 を見てください。

リスト 20. 適切な書式設定で Excel 関数を使用する
// Demonstrate functions:
// Add the TODAY() and NOW() functions at bottom of the Excel report
// to say when the workbook was opened.

// Find the last row and increment by two to skip a row
int lastRowIndex = donationSheet.getLastRowNum()+2;

// Create a row and three cells to hold the information.
HSSFRow lastRow = donationSheet.createRow(lastRowIndex);
HSSFCell notationCell = lastRow.createCell(0);
HSSFCell reportDateCell = lastRow.createCell(1);
HSSFCell reportTimeCell = lastRow.createCell(2);

// Set a regular string value in one cell
notationCell.setCellValue("Time:");

// Setting formula values uses setCellFormula()
reportDateCell.setCellFormula("TODAY()");
reportTimeCell.setCellFormula("NOW()");	

// Create HSSFCellStyle objects for the date and time cells.
// Use the createCellStyle() method of the workbook.

HSSFCellStyle dateCellStyle = donationWorkbook.createCellStyle();
HSSFCellStyle timeCellStyle = donationWorkbook.createCellStyle();

// Get a HSSFDataFormat object to set the time and date formats for the cell styles
HSSFDataFormat dataFormat = donationWorkbook.createDataFormat();

// Set the cell styles to the right format by using the index numbers of
// the desired formats retrieved from the getFormat() function of the HSSFDataFormat.
dateCellStyle.setDataFormat(dataFormat.getFormat("m/dd/yy"));
timeCellStyle.setDataFormat(dataFormat.getFormat("h:mm AM/PM"));

// Set the date and time cells to the appropriate HSSFCellStyles.
reportDateCell.setCellStyle(dateCellStyle);
reportTimeCell.setCellStyle(timeCellStyle);

必要なワークブック・オブジェクトが完成したら、最後にワークブックの write() メソッドを使用してファイルにそのワークブック・オブジェクトを書き込みます。

リスト 21. Excel ワークブックをファイルに書き込む
// Write out the workbook to a file. First,
// you need some sort of OutputStream to represent the file.
	
	String filePathString = "C:\\Planet Power\\Employee_Donations.xls";
	FileOutputStream donationStream = new FileOutputStream(filePathString);
	
	donationWorkbook.write(donationStream);

これで、GEE WHIS への寄付額を計算する Excel スプレッドシートが完成しました。


まとめ

レポートは完成しました。これで、Java プログラマーは Excel を読み取って XML を作成すると同時に、XML を Excel ファイルに再び書き込むこともできます。2 つの形式の間での変換について基本を理解した今、あなたはレポートを作成するという構想自体に共感を持ち始めていることでしょう。

社長はご機嫌です。そしてあなたは、「Planet Power 社の環境保護の英雄が遺伝子組み換えによって巨大化した野生のハムスターを救う手助けをする」という役割を果たしました。誰もが満足している今、レポート作成は、環境にとって実に有益だと言えます。


ダウンロード

内容ファイル名サイズ
Sample Excel spreadsheet and Java codeJava-Excel-XML-Planet-Power2.zip17KB

参考文献

学ぶために

製品や技術を入手するために

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Java technology
ArticleID=477896
ArticleTitle=読み取り、リサイクル、そして再利用: Excel、XML、および Java 技術によってレポート作成を楽にする: 第 2 回
publish-date=03022010