SSブログ

最近(?)、ClosedXMLのどうさがちょっとおかしいんだが。 [日記]

仕事でプログラムを作っているとExcelを操作するプログラムを書かなければならないことが良くあります(一方でMS-Wordの操作は全く無い)
操作すると言っても、目的はExcelファイルを読んだり書いたりするだけなのですが、昔のExcelファイルはファイル形式が公開されていないこともあり、COMコンポーネントとしてExcelそのものを操作してExcelファイルの読み書きをしていたことから、「Excelを操作するプログラム」と呼ばれることが多かったのです。

ちなみに、
昔のExcelファイルのファイル形式は 2008年に公開されています。
Officeのファイルが新しい形式(Office Open XML)に切り替わった後なので「え、今更?」って感じですけど。

閑話休題。
COMコンポーネントとしてExcelを操作する件について、こんな文書が本家Microsoftから出ています。
http://support.microsoft.com/kb/257757/ja
無人で動作するプログラムは、オートメーションでOfficeを操作するな、と。(オートメーション…COMコンポーネントと大体同じ意味)

途中で固まることがあるから無人だと対処のしようがなくて大問題、というのが理由ですが、だったら有人でも(途中で固まるようなプログラムは)問題大有りだろうと思うのですが、Microsoftとしても一切禁止とは言えないんだろうな。


さて、冒頭で話に出した「Excelを操作するプログラム」は、「Excelを操作すること」が目的ではなくて「Excelファイルの読み書き」が目的なので、べ、べつにExcelを操作できなくてもいいんだからねっ、と言ってみたところで何があるわけでもなく、まぁ、上で挙げたMicrosoftの文書にも似たようなことが書いてあります。
そして、新しいOfficeのファイル形式(Office Open XML)を操作するために、Microsoftは Open XML SDK というライブラリを公開しています。
http://msdn.microsoft.com/ja-jp/library/bb448854.aspx

この Open XML SDK について、一言で言い表すなら「超使いづらい」もしくは「超面倒くさい」。

Office Open XMLはXMLファイルをzip形式で圧縮したファイルなのですが、この中身を熟知していないとOpen XML SDKは使えません。内部のXMLファイルやXMLの要素が、なんとなくクラスと1対1に対応付けられていて、正しい形式になるように対応するクラスのインスタンスを操作するといった感じです。

その様な理由から、Open XML SDKを簡単に使うためのライブラリがいくつか存在しています。

そのうちの1つ、ClosedXML を使ってみました。
http://closedxml.codeplex.com/

試しに、Excelファイルをロードしてセルに値を設定して保存するプログラムを作ってみたところ、ちょっとおかしな事があるので、ここに残しておきます。
ちなみに、使ったバージョンは ClosedXML 0.69.1 です。将来のバージョンでは解消されているかもしれません。

(長い前置きだったな……)


最近(?)、ClosedXMLのどうさがちょっとおかしいんだが。

印刷範囲がおかしくなる。

印刷範囲が既に設定されているExcelファイルをロードして行を追加して保存します。

using (XLWorkbook wb = new XLWorkbook("sample.xlsx"))
using (IXLWorksheet ws = wb.Worksheet(1))
{
    ws.Row(1).InsertRowsAbove(1);
    wb.SaveAs("new_file.xlsx");
}

別にコードが間違っているわけではなく当たり前な実装なのですが、こうして作ったExcelファイルをMS-Excelで読もうとすると、次のエラーが出ます。

'○○○.xlsx' には読み取れない内容が含まれています。
このブックの内容を回復しますか?
ブックの発行元が信頼できる場合は、[はい] をクリックしてください。

「はい」というボタンを押すとExcelファイルの中身を表示することは出来ます。
が、修復画面にこんなメッセージが表示されて、

削除されたレコード: /xl/workbook.xml パーツ内の名前付き範囲 (ブック)

設定してあった印刷範囲が解除になってしまいます。

この症状、どんなファイルをロードしても起こるわけじゃないのがイヤラシイ。

原因調査のため、Excelファイルをzipで展開し、中身のXMLを調べました。
そして見つけた原因がコレ。

印刷範囲の情報は /xl/workbook.xml に、こんな感じで書かれています。

<x:definedName name="_xlnm.Print_Area" localSheetId="0">Sheet1!$A$1:$F$14</x:definedName>

(ロードして行追加して保存したらエラーが出るファイルが出来る)元のファイルの印刷範囲の指定がコレ。

<x:definedName name="_xlnm.Print_Area" localSheetId="0">Sheet1!$A:$F</x:definedName>

エラーが出るファイルの印刷範囲の指定がコレ。

<x:definedName name="_xlnm.Print_Area" localSheetId="0">Sheet1!$A$1:$F$1048577</x:definedName>

気付きましたか?

エラーになる元ファイルには、印刷範囲に行についての情報がありません。
これでどうして正しく印刷範囲が設定されているかというと、印刷範囲の行情報が未指定だと、セルにデータが入っている範囲が印刷範囲になるからです(と言うか、そうなるようにMS-Excelが出来ている?)

それを、ClosedXML は、最初の行から、可能な一番最後の行(1048576)までを印刷範囲だと解釈し、途中に行を1行追加したことで、設定可能な行の最大値を超えてしまい、エラーになったというわけです。

ちなみに、行が抜けている印刷範囲が設定されるExcelファイルは、MS-Excelで古い形式のExcelファイルをロードして、新しい形式に変換して保存すると作られます(MS-Office2010使用)。
解決策は、新しい形式に変換する際に、印刷範囲一旦解除して、設定し直せばOKです。


改ページプレビューの表示が解除になる。

表示モードが改ページプレビューになっているExcelファイルをロードして適当に編集して保存します。
すると、表示モードが改ページプレビューではなくて、標準になってしまいます。

原因は単純で ClosedXML はファイルのロード時に必ず表示モードを標準にするからです。

なので、必要なら保存前に表示モードを切り替えましょう。

using (XLWorkbook wb = new XLWorkbook("sample.xlsx"))
using (IXLWorksheet ws = wb.Worksheet(1))
{
    ~ 省略 ~

    ws.SheetView.View = XLSheetViewOptions.PageBreakPreview;
    wb.SaveAs("new_file.xlsx");
}

読み込み専用ファイルの別名保存が出来ない。

読み取り専用になっているExcelファイルをロードして適当に編集して、別名で保存しようとすると、UnauthorizedAccessException 例外が発生します。

この時、新しいファイルは出来ているのですが、ファイルの中身は編集されていません。

これも原因は単純で、
ClosedXML は、別名保存を行うと、元のファイルをコピーしてから(コピー先の)ファイルを更新します。
この時、ファイルの属性も一緒にコピーしてしまうので、ファイルの変更が出来ないというしょうもないオチ。

解決策は、保存する前に元ファイルの読み取り専用属性を外すしかないのかな。



ClosedXML は、.NET 環境でExcelファイルを操作するなら一番簡単に実現できるライブラリの部類になるんだろうけど、若干まだ詰めが甘いような。

他には、(JavaでOfficeファイルを扱うApache POI の .NET版の)NPOIというのがあります。
http://npoi.codeplex.com/

こっちは新形式だけじゃなく旧形式も扱えるし、POIとほとんど同じだから情報も沢山ありますが、作成したファイルがMS-Excelで読めなかったんですよね。
使用したのがβ版だったからかなぁ。いつの間にやら正式版が出ているし、後で試してみよう。


タグ:.net ClosedXML
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。