Open XML SDKを使用して、Excelファイル(.xlsxファイル)をC#やVisual Basicから作成したり、それに対して書き込み/読み込みを行う方法を説明する。
Microsoft Office(Word/Excel/PowerPoint)のファイルをプログラムから操作したいとき、OfficeがインストールされているWindows上ならばCOMインタフェースが使えることはよく知られている。では、Officeがインストールされていない環境のときは、どうしたらよいだろうか?
Office 2007から採用されたOpen XML形式のファイル(拡張子が.docx/.xlsx/.pptx)であれば、Officeがなくても、また、.NET Core/UWP/Xamarin.iOS/Xamarin.Androidといったプラットフォームであっても、アプリからOfficeのファイルを操作できるのだ。本稿では、Excelのファイルを操作する方法を紹介する。
なお、本稿で扱うのはMicrosoftのOpen XML SDKというライブラリである。Microsoft「純正」という安心感はあるが、使い方がかなり煩雑である。Excelファイルを扱うだけなら、サードパーティー製ではあるがClosedXMLというライブラリの方が簡単に使える。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。
なお、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2017以降が必要である。サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using System.Collections.Generic;
using System.Linq;
using static System.Console;
Imports System.Console
Imports DocumentFormat.OpenXml
Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Spreadsheet
Open XML形式のOfficeファイル(拡張子が.docx/.xlsx/.pptx)を読み書きするためのライブラリである。2014年にオープンソース化され、ソースコードはGitHubで公開されている(MITライセンス)。
できること: Open XML形式のOfficeファイルの編集
Open XML形式のOfficeファイルの実体は、XMLファイルを格納したzipアーカイブである。そのアーカイブを作成したり、アーカイブ内のデータを読み書きしたりできる。Excelでいえば、セルのデータや数式などの読み書きや書式の設定などが可能だ。
できないこと: Officeアプリの機能を利用すること
Open XML形式のOfficeファイルの読み書きは可能だが、Officeアプリの機能を利用しているわけではない。Officeアプリの機能、例えばExcelならば関数を実行したりグラフを生成したりといったことはできないのである。
サポートされているプラットフォームは幅広い。主なものを次に示す(本稿執筆時点)。
Open XML SDKのドキュメントは、docs.microsoft.comで公開されている。本稿執筆時点では、バージョン2.5のドキュメントになっている。Open XML SDK自体の最新バージョンは本稿執筆時点では2.8.1に上がっているが、APIの相違はほとんどないようである。本稿では、Excelファイルを操作する例を幾つか紹介するだけなので、詳しい使い方はこのドキュメントを参照していただきたい。ヒントとしては、困ったときは手動でExcelを操作してファイルに保存し、出力されたXMLデータを調べ、そのようなデータを作るにはどのAPIが使えるかを考えるとよいだろう。Open XML形式のOfficeファイルは、拡張子を「.zip」に変更するだけでその中を見られる。
GitHubからソースコードをダウンロードしてきてビルドしてもよいが、バイナリがNuGetで公開されている。
Visual Studioでプロジェクトを作ったら、NuGetで「OpenXml」を検索し(次の画像)、インストールする。
ここからは、Excelファイルを操作する方法を簡単に紹介していこう。細かい説明は省かせていただくので、詳細は前述のドキュメントを参照してほしい。
Excelファイルを新しく作り、空のワークシートを追加するコードは次のようになる。
const string ExcelFilePath = ".\\sample.xlsx";
// Excelファイルを作る
using (SpreadsheetDocument spreadsheetDocument
= SpreadsheetDocument.Create(ExcelFilePath, SpreadsheetDocumentType.Workbook))
{
// ワークブックを用意し、ワークシートを追加する
WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
workbookpart.Workbook = new Workbook();
WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet(new SheetData());
Sheets sheets
= spreadsheetDocument.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());
Sheet sheet = new Sheet()
{
Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart),
SheetId = 1,
Name = "サンプルシート"
};
sheets.Append(sheet);
WriteLine("ワークシートを追加しました。");
// 後述(1)(セルを追加)
// 後述(2)(セルに書式設定)
// ワークブックを保存する
workbookpart.Workbook.Save();
}
WriteLine("Excelファイルを保存しました。");
Const ExcelFilePath As String = ".\\sample.xlsx"
' Excelファイルを作る
Using spreadsheetDocument As SpreadsheetDocument _
= SpreadsheetDocument.Create(ExcelFilePath, SpreadsheetDocumentType.Workbook)
' ワークブックを用意し、ワークシートを追加する
Dim WorkbookPart As WorkbookPart = spreadsheetDocument.AddWorkbookPart()
WorkbookPart.Workbook = New Workbook()
Dim WorksheetPart As WorksheetPart = WorkbookPart.AddNewPart(Of WorksheetPart)()
WorksheetPart.Worksheet = New Worksheet(New SheetData())
Dim Sheets As Sheets _
= spreadsheetDocument.WorkbookPart.Workbook _
.AppendChild(Of Sheets)(New Sheets())
Dim Sheet As Sheet = New Sheet() With
{
.Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(WorksheetPart),
.SheetId = 1,
.Name = "サンプルシート"
}
Sheets.Append(Sheet)
WriteLine("ワークシートを追加しました。")
' 後述(1)(セルを追加)
' 後述(2)(セルに書式設定)
' ワークブックを保存する
WorkbookPart.Workbook.Save()
End Using
WriteLine("Excelファイルを保存しました。")
上のコードを実行すると、実行ファイルと同じフォルダに「sample.xlsx」が作られる。Excelで開いてみると、「サンプルシート」という名前の空のワークシートが作られているのが分かる。
Excelファイルを作り、続けてデータや数式を書き込むには、上記のコードの「後述(1)(セルを追加)」というコメント部分に次のようなコードを記述する。ここでは数字を2つと、数式を1つ追加している。
// 後述(1)(セルを追加)
// セルA1をワークシートに追加する
Cell cellA1 = InsertCellInWorksheet("A", 1, worksheetPart);
// セルA1に数字をセットする
cellA1.CellValue = new CellValue("10");
cellA1.DataType = new EnumValue<CellValues>(CellValues.Number);
// セルA2をワークシートに追加する
Cell cellA2 = InsertCellInWorksheet("A", 2, worksheetPart);
// セルA2に数字をセットする
cellA2.CellValue = new CellValue("20");
cellA2.DataType = new EnumValue<CellValues>(CellValues.Number);
// セルA3をワークシートに追加する
Cell cellA3 = InsertCellInWorksheet("A", 3, worksheetPart);
// セルA3に数式をセットする
cellA3.CellFormula = new CellFormula("=SUM(A1, A2)");
WriteLine("セルを3つ追加しました。");
' 後述(1)(セルを追加)
' セルA1をワークシートに追加する
Dim cellA1 As Cell = InsertCellInWorksheet("A", 1, WorksheetPart)
' セルA1に数字をセットする
cellA1.CellValue = New CellValue("10")
cellA1.DataType = New EnumValue(Of CellValues)(CellValues.Number)
' セルA2をワークシートに追加する
Dim cellA2 As Cell = InsertCellInWorksheet("A", 2, WorksheetPart)
' セルA2に数字をセットする
cellA2.CellValue = New CellValue("20")
cellA2.DataType = New EnumValue(Of CellValues)(CellValues.Number)
' セルA3をワークシートに追加する
Dim cellA3 As Cell = InsertCellInWorksheet("A", 3, WorksheetPart)
' セルA3に数式をセットする
cellA3.CellFormula = New CellFormula("=SUM(A1, A2)")
WriteLine("セルを3つ追加しました。")
上のコードでは、位置を指定してセルを追加するコードをInsertCellInWorksheetメソッドにまとめて書いている。このメソッドは、次のコードのようになる。ご覧の通り、かなり煩雑なコードを書かねばならない。
private static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart)
{
Worksheet worksheet = worksheetPart.Worksheet;
SheetData sheetData = worksheet.GetFirstChild<SheetData>();
string cellReference = columnName + rowIndex.ToString();
// 指定された位置のRowオブジェクト
Row row
= sheetData.Elements<Row>().FirstOrDefault(r => r.RowIndex == rowIndex);
if (row == null)
{
// Rowオブジェクトがまだ存在しないときは作る
row = new Row() { RowIndex = rowIndex };
sheetData.Append(row);
}
// 指定された位置のCellオブジェクト
Cell refCell
= row.Elements<Cell>().FirstOrDefault(c =>
c.CellReference.Value == cellReference);
if (refCell != null)
return refCell; // すでに存在するので、それを返す
// Cellオブジェクトがまだ存在しないときは作って挿入する
Cell nextCell
= row.Elements<Cell>().FirstOrDefault(c =>
string.Compare(c.CellReference.Value, cellReference, true) > 0);
Cell newCell = new Cell() { CellReference = cellReference };
row.InsertBefore(newCell, nextCell);
worksheet.Save();
return newCell;
}
Private Function InsertCellInWorksheet(columnName As String, rowIndex As UInt32, worksheetPart As WorksheetPart) As Cell
Dim worksheet As Worksheet = worksheetPart.Worksheet
Dim sheetData As SheetData = worksheet.GetFirstChild(Of SheetData)()
Dim cellReference As String = columnName + rowIndex.ToString()
' 指定された位置のRowオブジェクト
Dim row As Row _
= sheetData.Elements(Of Row)().FirstOrDefault(Function(r) _
r.RowIndex.Value = rowIndex)
If (row Is Nothing) Then
' Rowオブジェクトがまだ存在しないときは作る
row = New Row() With {.RowIndex = rowIndex}
sheetData.Append(row)
End If
' 指定された位置のCellオブジェクト
Dim refCell As Cell _
= row.Elements(Of Cell)().FirstOrDefault(Function(c) _
c.CellReference.Value = cellReference)
If (refCell IsNot Nothing) Then
Return refCell ' すでに存在するので、それを返す
End If
' Cellオブジェクトがまだ存在しないときは作って挿入する
Dim nextCell As Cell _
= row.Elements(Of Cell)().FirstOrDefault(Function(c) _
String.Compare(c.CellReference.Value, cellReference, True) > 0)
Dim newCell As Cell = New Cell() With {.CellReference = cellReference}
row.InsertBefore(newCell, nextCell)
worksheet.Save()
Return newCell
End Function
書式やセルの背景色などの設定は、スタイルシートというワークブックとは別のオブジェクトに保存される。セルには、スタイルシート内のスタイルを参照する番号を与える。スタイルシートのオブジェクトにはプログラムから既定のスタイルも設定する必要があるので、かなり煩雑なコードになってしまう。
例えばセルA3の書式を小数点表示にし、背景色を赤色に変えるコードは次のようになる。これは最初のコードの「後述(2)(セルに書式設定)」というコメント部分に記述する。
// 後述(2)(セルに書式設定)
// スタイルシートを用意する
WorkbookStylesPart stylesPart = workbookpart.AddNewPart<WorkbookStylesPart>();
stylesPart.Stylesheet = new Stylesheet();
// スタイルシートに空のフォント定義を追加する(必須)
stylesPart.Stylesheet.Fonts = new Fonts();
stylesPart.Stylesheet.Fonts.Count = 1;
stylesPart.Stylesheet.Fonts.AppendChild(new Font());
// スタイルシートに塗りつぶしの定義を追加する(必須)
stylesPart.Stylesheet.Fills = new Fills();
// 塗りつぶしの定義は2つが必須
stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.None } });
stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.Gray125 } });
// 赤色の塗りつぶしを追加する(これは必須ではない)
var solidRed = new PatternFill() { PatternType = PatternValues.Solid };
solidRed.ForegroundColor = new ForegroundColor { Rgb = HexBinaryValue.FromString("FFFF0000") };
solidRed.BackgroundColor = new BackgroundColor { Indexed = 64 };
stylesPart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = solidRed });
stylesPart.Stylesheet.Fills.Count = 3;
// スタイルシートに空のボーダー定義を追加する(必須)
stylesPart.Stylesheet.Borders = new Borders();
stylesPart.Stylesheet.Borders.Count = 1;
stylesPart.Stylesheet.Borders.AppendChild(new Border());
// スタイルシートに空のセルスタイルフォーマット定義を追加する(必須)
stylesPart.Stylesheet.CellStyleFormats = new CellStyleFormats();
stylesPart.Stylesheet.CellStyleFormats.Count = 1;
stylesPart.Stylesheet.CellStyleFormats.AppendChild(new CellFormat());
// スタイルシートに空のセルフォーマット定義を追加する(必須)
stylesPart.Stylesheet.CellFormats = new CellFormats();
stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat());
// スタイルシートに新しいセルフォーマット定義を追加する(これは必須ではない)
stylesPart.Stylesheet.CellFormats.AppendChild(new CellFormat()
{
FormatId = 0,
FontId = 0,
ApplyFont = true,
BorderId = 0,
FillId = 2, // 作成した赤色の塗りつぶし定義
ApplyFill = true,
NumberFormatId = 4, // #,##0.00
ApplyNumberFormat = BooleanValue.FromBoolean(true),
});
stylesPart.Stylesheet.CellFormats.Count
= new UInt32Value((uint)stylesPart.Stylesheet.CellFormats.Count());
// スタイルシートを保存する
stylesPart.Stylesheet.Save();
// セルにセルフォーマット定義を適用する
cellA3.StyleIndex = new UInt32Value(stylesPart.Stylesheet.CellFormats.Count - 1);
WriteLine("セルに書式を設定しました。");
' 後述(2)(セルに書式設定)
' スタイルシートを用意する
Dim stylesPart As WorkbookStylesPart = WorkbookPart.AddNewPart(Of WorkbookStylesPart)()
stylesPart.Stylesheet = New Stylesheet()
' スタイルシートに空のフォント定義を追加する(必須)
stylesPart.Stylesheet.Fonts = New Fonts()
stylesPart.Stylesheet.Fonts.Count = 1
stylesPart.Stylesheet.Fonts.AppendChild(New Font())
' スタイルシートに塗りつぶしの定義を追加する(必須)
stylesPart.Stylesheet.Fills = New Fills()
' 塗りつぶしの定義は2つが必須
stylesPart.Stylesheet.Fills.AppendChild(New Fill With {.PatternFill = New PatternFill With {.PatternType = PatternValues.None}})
stylesPart.Stylesheet.Fills.AppendChild(New Fill With {.PatternFill = New PatternFill With {.PatternType = PatternValues.Gray125}})
' 赤色の塗りつぶしを追加する(これは必須ではない)
Dim solidRed = New PatternFill() With {.PatternType = PatternValues.Solid}
solidRed.ForegroundColor = New ForegroundColor With {.Rgb = HexBinaryValue.FromString("FFFF0000")}
solidRed.BackgroundColor = New BackgroundColor With {.Indexed = 64}
stylesPart.Stylesheet.Fills.AppendChild(New Fill With {.PatternFill = solidRed})
stylesPart.Stylesheet.Fills.Count = 3
' スタイルシートに空のボーダー定義を追加する(必須)
stylesPart.Stylesheet.Borders = New Borders()
stylesPart.Stylesheet.Borders.Count = 1
stylesPart.Stylesheet.Borders.AppendChild(New Border())
' スタイルシートに空のセルスタイルフォーマット定義を追加する(必須)
stylesPart.Stylesheet.CellStyleFormats = New CellStyleFormats()
stylesPart.Stylesheet.CellStyleFormats.Count = 1
stylesPart.Stylesheet.CellStyleFormats.AppendChild(New CellFormat())
' スタイルシートに空のセルフォーマット定義を追加する(必須)
stylesPart.Stylesheet.CellFormats = New CellFormats()
stylesPart.Stylesheet.CellFormats.AppendChild(New CellFormat())
' スタイルシートに新しいセルフォーマット定義を追加する(これは必須ではない)
stylesPart.Stylesheet.CellFormats.AppendChild(New CellFormat() With
{
.FormatId = 0,
.FontId = 0,
.ApplyFont = True,
.BorderId = 0,
.FillId = 2, ' 作成した赤色の塗りつぶし定義
.ApplyFill = True,
.NumberFormatId = 4, ' #,##0.00
.ApplyNumberFormat = BooleanValue.FromBoolean(True)
})
stylesPart.Stylesheet.CellFormats.Count _
= New UInt32Value(CType(stylesPart.Stylesheet.CellFormats.LongCount(), UInteger))
' スタイルシートを保存する
stylesPart.Stylesheet.Save()
' セルにセルフォーマット定義を適用する
cellA3.StyleIndex = New UInt32Value(stylesPart.Stylesheet.CellFormats.Count.Value - 1UI)
WriteLine("セルに書式を設定しました。")
ここまでのコードを実行して出力された「sample.xlsx」ファイルをExcelで開いてみると、次の画像のようになっている。セルA3には数式を挿入したのだが、(Excelで開いたときに)自動計算されている。また、セルA3には書式が設定されている。
最後に、上で作成したExcelファイルを読み込み、セルのデータを読み書きする例を示そう(次のコード)。
const string ExcelFilePath = ".\\sample.xlsx";
// Excelファイルを開く
using (SpreadsheetDocument spreadsheetDocument
= SpreadsheetDocument.Open(ExcelFilePath, true,
new OpenSettings { AutoSave = false, }))
{
WriteLine("Excelファイルを開きました。");
// シート名からWorksheetオブジェクトを取得する
WorkbookPart workbookpart = spreadsheetDocument.WorkbookPart;
Sheet sheet = workbookpart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>()
.FirstOrDefault(s => s.Name == "サンプルシート");
string relationshipId = sheet.Id.Value;
WorksheetPart worksheetPart
= (WorksheetPart)workbookpart.GetPartById(relationshipId);
Worksheet worksheet = worksheetPart.Worksheet;
// 位置を指定してセルを取得する
Cell cellA1 = GetSpreadsheetCell(worksheet, "A", 1);
Cell cellA2 = GetSpreadsheetCell(worksheet, "A", 2);
Cell cellA3 = GetSpreadsheetCell(worksheet, "A", 3);
// セルの内容を読み取る
WriteLine($"A1: {cellA1.CellValue?.Text}");
// 出力:A1: 10
WriteLine($"A2: {cellA2.CellValue?.Text}");
// 出力:A2: 20
WriteLine($"A3(値): {cellA3.CellValue?.Text}");
WriteLine($"A3(数式): {cellA3.CellFormula?.Text}");
// 出力:A3(値):
// 出力:A3(数式): =SUM(A1, A2)
// セルA1の値を書き換える
cellA1.CellValue = new CellValue("15");
WriteLine($"A1の値を{cellA1.CellValue.Text}に書き換えました。");
// 出力:A1の値を15に書き換えました。
// 保存する
worksheet.Save();
}
Const ExcelFilePath As String = ".\\sample.xlsx"
' Excelファイルを開く
Using spreadsheetDocument As SpreadsheetDocument _
= SpreadsheetDocument.Open(ExcelFilePath, True,
New OpenSettings With {.AutoSave = False})
WriteLine("Excelファイルを開きました。")
' シート名からWorksheetオブジェクトを取得する
Dim workbookPart As WorkbookPart = spreadsheetDocument.WorkbookPart
Dim sheet As Sheet _
= workbookPart.Workbook.GetFirstChild(Of Sheets)().Elements(Of Sheet)() _
.FirstOrDefault(Function(s) s.Name = "サンプルシート")
Dim relationshipId As String = sheet.Id.Value
Dim worksheetPart As WorksheetPart = workbookPart.GetPartById(relationshipId)
Dim worksheet As Worksheet = worksheetPart.Worksheet
' 位置を指定してセルを取得する
Dim cellA1 As Cell = GetSpreadsheetCell(worksheet, "A", 1)
Dim cellA2 As Cell = GetSpreadsheetCell(worksheet, "A", 2)
Dim cellA3 As Cell = GetSpreadsheetCell(worksheet, "A", 3)
' セルの内容を読み取る
WriteLine($"A1: {cellA1.CellValue?.Text}")
' 出力:A1: 10
WriteLine($"A2: {cellA2.CellValue?.Text}")
' 出力:A2: 20
WriteLine($"A3(値): {cellA3.CellValue?.Text}")
WriteLine($"A3(数式): {cellA3.CellFormula?.Text}")
' 出力:A3(値):
' 出力:A3(数式): =SUM(A1, A2)
' セルA1の値を書き換える
cellA1.CellValue = New CellValue("15")
WriteLine($"A1の値を{cellA1.CellValue.Text}に書き換えました。")
' 出力:A1の値を15に書き換えました。
' 保存する
worksheet.Save()
End Using
上のコードでは、位置を指定してセルを取得するコードをGetSpreadsheetCellメソッドにまとめて書いている。このメソッドは、次のコードのようになる。
private static Cell GetSpreadsheetCell(Worksheet worksheet, string columnName, uint rowIndex)
{
string cellReference = columnName + rowIndex.ToString();
Row row
= worksheet.GetFirstChild<SheetData>().Elements<Row>()
.FirstOrDefault(r => r.RowIndex == rowIndex);
Cell cell
= row?.Elements<Cell>()
.FirstOrDefault(c => c.CellReference.Value == cellReference);
return cell;
}
Private Function GetSpreadsheetCell(worksheet As Worksheet, columnName As String, rowIndex As UInteger) As Cell
Dim cellReference As String = columnName + rowIndex.ToString()
Dim row As Row _
= worksheet.GetFirstChild(Of SheetData)().Elements(Of Row)() _
.FirstOrDefault(Function(r) r.RowIndex.Value = rowIndex)
Dim cell As Cell _
= row?.Elements(Of Cell)() _
.FirstOrDefault(Function(c) c.CellReference.Value = cellReference)
Return cell
End Function
Excel 2007以降のOpen XML形式のファイル(拡張子「.xlsx」)は、Open XML SDKを利用して幅広いプラットフォームで読み書きできる。ただし、Excelアプリの機能は利用できないので、例えば数式を与えて計算するなどといったことはできない。
利用可能バージョン:.NET Framework 3.5以降
カテゴリ:オープンソースライブラリ 処理対象:Windowsフォーム
カテゴリ:オープンソースライブラリ 処理対象:WPF
カテゴリ:オープンソースライブラリ 処理対象:Xamarin.Forms
カテゴリ:クラス・ライブラリ 処理対象:データ
関連TIPS:Excelファイルにアクセスするには?[C#、VB]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:構文:インスタンス化と同時にプロパティを設定するには?[C#/VB]
関連TIPS:構文:nullチェックを簡潔に記述するには?[C# 6.0]
Copyright© Digital Advantage Corp. All Rights Reserved.