Open XML SDK を使って Excel ファイルを操作する (2) - 基本構造編
クロスワープの大鷲です。
前回に引き続き、Open XML SDK(OOXML SDK)を使って、Office Open XML(OOXML)ファイルを操作する方法について紹介していきます。
今回は、Excel ファイルを構成する言語である SpreadsheetML の基本構造についてです。
- Excel ファイルと SpreadsheetML
- Excel ファイルの中身を見てみる
- 空っぽの Excel ファイルを作るコード
- SpreadsheetML の構成要素
- その他の主な要素
- 現実的なアプローチ
Excel ファイルと SpreadsheetML
Excel ファイルの構造については、今更言うまでもなく、皆さんご存知だと思います。
ファイルは「ブック」と呼ばれ、ブックの中には複数の「シート」があります。シートは 2 次元に並んだ「セル」の集合から成ります。
そのため、OOXML SDK を使って XML ファイルを組み立てる際にも、基本的にはこのようにします。
- ブックを作る
- シートを作る
- 行を作る
- セルを作る
Excel ファイルの中身を見てみる
Excel で空のファイルを作り、拡張子を zip に変更して展開してみましょう。
中身はこんな感じになっているはずです。
Book1.xlsx ├─ [Content_Types].xml ├─ _rels │ └─ .rels ├─ docProps │ ├─ app.xml │ └─ core.xml └─ xl ├─ _rels │ └─ workbook.xml.rel ├─ theme │ └─ theme1.xml ├─ worksheets │ └─ sheet1.xml ├─ styles.xml └─ workbook.xml
この構造を頭の片隅に置いておいてください。
空っぽの Excel ファイルを作るコード
using (var doc = SpreadsheetDocument.Create("Book.xlsx", SpreadsheetDocumentType.Workbook)) { /* [1] */ var workbookPart = doc.AddWorkbookPart(); /* [2] */ var workbook = workbookPart.Workbook = new Workbook(); /* [3] */ var worksheetPart = workbookPart.AddNewPart<WorksheetPart>(); /* [4] */ worksheetPart.Worksheet = new Worksheet(); /* [5] */ var sheets = new Sheets(); workbook.Append(sheets); /* [6] */ var sheet = new Sheet { Name = "Sheet1" }; sheets.Append(sheet); /* [7] */ string relationshipId = workbookPart.GetIdOfPart(worksheetPart); sheet.Id = relationshipId; doc.Save(); }
Excel ファイルそのものを表すクラスは SpreadsheetDocument です。
入れ物としてのファイルだけを作る
上記のコードには各行に行番号が振ってあります。
まず、これらのコードを全て無効にしてみましょう。
実質的にこういうことです。
using (var doc = SpreadsheetDocument.Create("Book.xlsx", SpreadsheetDocumentType.Workbook)) { doc.Save(); }
このコードを実行すると、Book.xlsx ファイルが作られます。まだ Excel で開いてはいけません。
拡張子を zip に変えて開いて見ましょう。
どうでしたか。中身は何もありませんね。
このコードは、空の zip ファイルを作るコードなのです。
少しずつ中身を組み立ててみる
では、上記のコードの [1] だけを有効にして実行してみましょう。
つまり、こういう状態です。
using (var doc = SpreadsheetDocument.Create("Book.xlsx", SpreadsheetDocumentType.Workbook)) { /* [1] */ var workbookPart = doc.AddWorkbookPart(); doc.Save(); }
拡張子を zip に変えて開くと、こういう状態になっています。
Book1.xlsx ├─ [Content_Types].xml ├─ _rels │ └─ .rels └─ xl └─ workbook.xml
workbook.xml は存在しますが、中身は空っぽです。
[2]、[3] と、一段階ずつ進めていくと、こうなります。
番号 | 起きること |
---|---|
すべて無効 | xlsx ファイルが作られるが、中身は空っぽ |
[1] のみ有効 | xl/workbook.xml が作られるが、中身は空っぽ |
[2] まで有効 | xl/workbook.xml に workbook 要素が作られる |
[3] まで有効 | xl/worksheets/sheet.xml が作られるが、中身は空っぽ |
[4] まで有効 | sheet.xml に worksheet 要素が作られる |
[5] まで有効 | xl/workbook.xml の workbook 要素内に sheets 要素が作られる |
[6] まで有効 | sheets 要素内に sheet 要素が作られる |
[7] まで有効 | sheet 要素に id 属性が書きこまれ、sheet.xml と関連付けられる |
SpreadsheetML の構成要素
まだまだ完全な Excel ファイルには遠く及びませんが、一応、基本的な構成要素は見えてきました。
- zip ファイル内の各ファイルを表す XxxPart クラス
- 各 XML ファイル内の要素を表すクラス
- 親の要素とこのファイルを関連付ける Relationship
最後の Relationship についてちょっと補足します。
workbook.xml に sheet 要素を作り、sheet.xml に worksheet 要素を作っても、それだけでは機能しません。
sheet 要素と sheet.xml を結びつけるために、同じ ID を持った Relashinship 要素が必要なのです。
workbook.xml(抜粋)
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <sheets> <sheet name="Sheet1" sheetId="1" r:id="rId1"/> </sheets> </workbook>
workbook.xml.rels(抜粋)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet.xml"/> </Relationships>
workbook.xml の sheet 要素の r:id="rId1" という属性は、workbook.xml.rels 内の id="rId1" 属性を持つ Relationship 要素を参照しています。
そして、この Relationship 要素が Target 属性で sheet.xml を参照することによって、sheet 要素と sheet.xml が関連付けられるのです。
一言で言うならば SpreadsheetML は、zip ファイル内のファイル ツリーと、各 XML ファイル内の XML ツリーという二重の木構造を、Relationship によって結びつけた構造と言えると思います。
その他の主な要素
これまで言及してこなかった、よく使う要素を簡単に解説しておきます。
要素 | XML ファイル | 意味 |
---|---|---|
sheetData | sheet.xml | シートのデータを格納するコンテナー要素です。この中に row 要素が入ります。 |
row | sheet.xml | 行を表す要素です。子要素として c 要素を持ちます。 |
c | sheet.xml | row 要素の中にあって、セルを表す要素です。 |
v | sheet.xml | c 要素内にあって、セルの値を表します。 |
mergedCell | sheet.xml | 結合されたセルを表す要素です。 |
definedName | workbook.xml | セル範囲に対して定義した名前を表します。 |
calcChain | calcChain.xml | 計算式が参照しているセルを表します。 |
現実的なアプローチ
OOXML SDK を使って SpreadsheetML を一から組み上げて行くのは(Excel の互換ソフトを開発しようというのでない限り)非現実的な選択肢でしょう。
現在開発中の MODD の経理システムでは、まず Excel を使ってテンプレートとなる xlsx ファイルを作っておいて、そのファイル(のコピー)に対して、テンプレートとなる行を挿入して、データを流し込んでいくことで、帳票を作成しています。
行を挿入するということは、行番号がずれるということです。そこだけ丁寧に対応すれば、それなりに使える帳票出力コードができると思います。