Open XML SDK を使って Excel ファイルを操作する (1) - 準備編

クロスワープの大鷲です。
ご無沙汰しております。

最近は、eコマースサービス MODD の経理システムを作っています。データベースから月次の売り上げを集計して、請求書等の帳票を出力するシステムです。
集計した数字を帳票ファイルに出したいということで、当初は PDF 化を検討したのですが、諸事情あって現在は Excel 出力に落ち着いています。
というわけで今回は Open XML SDK を使って Excel ファイルを出力する方法についてです。

Office Open XML 形式の基礎知識

Office 2007 から採用されたドキュメント ファイルのフォーマットである Office Open XML(以下 OOXML とする)は、端的に言えば、いくつかの XML ファイル(と、画像などのファイル)を zip 形式で固めたものです。
そのため、拡張子を zip に変えて展開すれば、中身を見ることができます。
また、展開したものを書き換えて再圧縮すれば、Office のソフトウェアが無くても編集ができます。

本稿は Excel についてのみ述べています。
OOXML の中でも Excel ファイルを構成する言語を SpreadsheetML と言います。

Open XML SDK

Open XML SDK(以下 OOXML SDK とする)は、OOXML 形式のファイルの読み書きを支援するライブラリです。

OOXML SDK では、XML の各要素に対応したクラスがあり、要素の属性に対応したプロパティがあります。
例えば、SpreadsheetML の場合、行を表す row 要素(Row クラス)があり、行番号を表す r 属性(RowIndex プロパティ)があります。
また、セルを表す c 要素(Cell クラス)があり、そのセルの座標を示す r 属性(CellReference プロパティ)があります。

2 つのバージョン

OOXML SDK には、ダウンロード センターから入手できる ver.2.5 の他に、GitHub で公開されている ver.2.6 があります。*1
微妙にインターフェイスが変わっています。

OOXML SDK ver.2.5 は、.NET Framework に標準で備わっている System.IO.Packaging という機能に依存しています。
ただし、ver.2.6 以降は、この部分も新規に実装されており、標準の System.IO.Packaging には依存していません。
NuGet にも上がっているようですが、どれがオフィシャルなのかよくわからないため、私は GitHub からダウンロードしたソースをビルドして参照しています。
同様にする場合、System.IO.Packaging.dll もソースからビルドしたものを参照する必要がありますので注意してください。

割と茨の道

先程、OOXML SDK は、OOXML 形式の文書に含まれる XML ファイルの書き換えを支援するライブラリである、と言いました。
このライブラリは、それ以上でもそれ以下でもありません。Excel のようなことをするライブラリではないのです。

どういうことでしょうか。
例えば、こういう SpreadsheetML があったとします。

<row r="1">
  <c r="A1" t="str">
    <v>Hello</v>
  </c>
</row>
<row r="2">
  <c r="A2" t="str">
    <v>World</v>
  </c>
</row>

なんとなくイメージできるでしょうか。
Excel で見るとこういう状態です。
f:id:cw_owashi:20160125184539p:plain
ここで、2 行目に新しく行を挿入したとします。
f:id:cw_owashi:20160125184604p:plain
XML だと、こんな感じです。*2

<row r="1">
  <c r="A1" t="str">
    <v>Hello</v>
  </c>
</row>
<row r="2">
  <c r="A2" t="str">
    <v>OOXML</v>
  </c>
</row>
<row r="3">
  <c r="A3" t="str">
    <v>World</v>
  </c>
</row>

最初の XML に対して、どのような変更が行われたでしょうか。

<row r="1">
  <c r="A1" t="str">
    <v>Hello</v>
  </c>
</row>
<!-- ここから挿入 -->
<row r="2">
  <c r="A2" t="str">
    <v>OOXML</v>
  </c>
</row>
<!-- ここまで挿入 -->
<row r="3"> <!-- r 属性の値を 2 から 3 に書き換え -->
  <c r="A3" t="str"> <!-- r 属性の値を A2 から A3 に書き換え -->
    <v>World</v>
  </c>
</row>

Excel 上だと、行ヘッダーを右クリックして「挿入」というワンアクションですが、OOXML SDK では

  1. 2 行目の挿入
  2. 2 行目だった行の番号を 3 行目に変更
  3. A2 だったセルの座標を A3 に変更

というのを、全部コードでやらなければなりません。
さらに、どこかのセルの関数が A2 を参照していたりすると、その参照先も書き換えなければなりません。
こうやって XML の整合性を全部取っていかないと、不正なファイルができて、Excel で開いたときに怒られてしまいます。
f:id:cw_owashi:20160125185519p:plain
OOXML SDK を使って Excel ファイルを編集していると、このメッセージは嫌になるほど目にします。

お分かり頂けるでしょうか。
OOXML SDK は OOXML を書き換えるための最低限のインターフェイスしか提供しておらず、整合性を保って書き換えるのはプログラマーの仕事なのです。
しかも Excel は、XML のどこが悪いのかについて、詳細な情報を提供してはくれません。

正直、しんどいです。

Open XML Productivity Tool

Microsoft のダウンロード センター では、OOXML SDK のライブラリと一緒に、OpenXMLSDKToolV25.msi というファイルも公開されています。
これは Open XML Productivity Tool*3 というツールで、OOXML SDK で開発をするなら必携のツールです。是非インストールしましょう。
f:id:cw_owashi:20160125190322p:plain
OOXML の構造をグラフィカルに表示できるだけでなく、XML の不正な点を検出したり(Validate)、2 つの OOXML ファイルを比較(Compare Files)したりできます。
特に比較機能は有用です。
整合性が壊れた不正なファイルを Excel で開くと、ある程度は自動補正してくれます。
不正なファイルと補正されたファイルを見比べることで、どこが悪かったのかを探ることができるのです。
不正な点だけでなく、悪くない点も書き換えられてしまうので、あまりピンポイントというわけには行かないのですが、何も無いよりはずっとマシです。

ちなみに Validate 機能の方は、スキーマ違反しか検出してくれないので、整合性違反は指摘してくれません。
残念な機能です。

次回

SpreadsheetML の基本構造について解説する予定です。

*1:と思ったら 2.6.1 というのも出ていました。3.0 も出るようです。

*2:実際に Excel で編集すると、こうはなりません。これはあくまで最小限の例です。

*3:本当はもっと長い名前