ClosedXmlライブラリを使用して、Excelファイル(.xlsxファイル)の作成、書き込み/読み込み、LINQを使ったセルの値の取得などを行う方法を説明する。
Microsoft Office(Word/Excel/PowerPoint)のファイルをプログラムから操作するためにOpen XML SDKというライブラリがMicrosoftから提供されているが、汎用的な作りになっているために、特にExcelファイルのデータや書式を扱うには煩雑なコードが必要になってしまっている。
Excelファイルだけを扱うのであれば、サードパーティー製のClosedXMLというライブラリを使うと簡単だ。本稿では、そのClosedXMLの使い方を紹介する。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。
なお、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2017以降が必要である。サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。
using ClosedXML.Excel;
using System;
using System.Collections.Generic;
using System.Linq;
using static System.Console;
Imports System.Console
Imports ClosedXML.Excel
Open XML形式のExcelファイル(拡張子が.xlsx/.xlsm)を読み書きするためのライブラリである。Microsoft「純正」のOpen XML SDKをラップし、Excelに特化することで使いやすいライブラリにしている。ソースコードはGitHubで公開されている(MITライセンス)。
できること: Open XML形式のExcelファイルの編集と数式の実行
Open XML形式のExcelファイルの実体は、XMLファイルを格納したzipアーカイブである。そのアーカイブを作成したり、アーカイブ内のデータを読み書きしたりできる。セルのデータや数式などの読み書きや書式の設定などが可能だ。さらに、Excelの主な関数を独自に実行する機能も持っている。
できないこと: Excelの機能を利用すること
Open XML形式のExcelファイルの読み書きは可能だが、Excelアプリの機能を利用しているわけではない。Excelアプリの機能、例えばグラフを生成したりはできないのである。ただし、Open XML SDKとは異なり、Excelの主な関数はサポートされている。
サポートされているプラットフォームは幅広い。主なものを次に示す(本稿執筆時点)。
.NET Standard 2.0で動作するということは、それをサポートしている以下のプラットフォームからも利用できることになる。
ClosedXMLのドキュメントは、ClosedXMLのWikiで公開されている。本稿では、Excelファイルを操作する例を幾つか紹介するだけなので、詳しい使い方はこのドキュメントを参照していただきたい。残念ながら、日本語のレファレンスはまだないようだ。
なお、ClosedXMLには次のような派生プロダクトもある。
GitHubからソースコードをダウンロードしてきてビルドしてもよいが、バイナリがNuGetで公開されている。
Visual Studioでプロジェクトを作ったら、NuGetで「ClosedXml」を検索し(次の画像)、インストールする。Open XML SDKなど、幾つかの必要なパッケージが一緒にインストールされる。
ここからは、Excelファイルを操作する方法を簡単に紹介していこう。細かい説明は省かせていただくので、詳細は前述のドキュメントを参照してほしい。
前回の記事「ExcelなしでExcelファイルを操作するには?(純正SDK編)[.NET 3.5、C#/VB]」(以降、「前回」と略記する)では、Open XML SDKを直接使ってExcelファイルの作成/セルの追加/書式設定などを行うのに随分と長いコードを書くことになった。ClosedXMLを使うと、同じ内容を次のコードのようにごく簡潔で分かりやすく書ける。
const string ExcelFilePath = ".\\sample.xlsx";
// Excelファイルを作る
using (var workbook = new XLWorkbook())
// ワークシートを追加する
using (var worksheet = workbook.Worksheets.Add("サンプルシート1"))
{
// セルに値や数式をセット
worksheet.Cell("A1").Value = 10;
worksheet.Cell("A2").SetValue(20);
worksheet.Cell("A3").FormulaA1 = "SUM(A1:A2)";
// セルに書式設定
var sumCellStyle = worksheet.Cell("A3").Style;
sumCellStyle.Fill.BackgroundColor = XLColor.Red; // 塗りつぶし
sumCellStyle.NumberFormat.Format = "#,##0.00"; // 数値の書式
// 次のようにメソッドチェーンでも書ける
//worksheet.Cell("A3").SetFormulaA1("SUM(A1:A2)")
// .Style.Fill.SetBackgroundColor(XLColor.Red)
// .NumberFormat.SetFormat("#,##0.00");
// ワークブックを保存する
workbook.SaveAs(ExcelFilePath);
}
WriteLine("Excelファイルを保存しました。");
Const ExcelFilePath As String = ".\\sample.xlsx"
' Excelファイルを作る
Using workbook = New XLWorkbook()
' ワークシートを追加する
Using worksheet = workbook.Worksheets.Add("サンプルシート1")
' セルに値や数式をセット
worksheet.Cell("A1").Value = 10
worksheet.Cell("A2").SetValue(20)
worksheet.Cell("A3").FormulaA1 = "SUM(A1:A2)"
' セルに書式設定
With worksheet.Cell("A3").Style
.Fill.BackgroundColor = XLColor.Red ' 塗りつぶし
.NumberFormat.Format = "#,##0.00" ' 数値の書式
End With
' ワークブックを保存する
workbook.SaveAs(ExcelFilePath)
End Using
End Using
WriteLine("Excelファイルを保存しました。")
上のコードを実行すると、実行ファイルと同じフォルダに「sample.xlsx」が作られる。Excelで開いてみると、次の画像のようになっている。セルA3には数式を挿入したのだが、(Excelで開いたときに)自動計算されている。また、セルA3には書式が設定されている。
上で作成したExcelファイルを読み込み、セルのデータを読み書きする例を示そう(次のコード)。これも前回のコードと見比べてほしいのだが、簡潔で分かりやすくなっている。
const string ExcelFilePath = ".\\sample.xlsx";
// Excelファイルを開く
using (var workbook = new XLWorkbook(ExcelFilePath))
// ワークシートを取得する
using (var worksheet = workbook.Worksheet("サンプルシート1"))
{
// 位置を指定してセルを取得する
var cellA1 = worksheet.Cell("A1");
var cellA2 = worksheet.Cell(2, "A"); // 行番号と列名でも指定可能
var cellA3 = worksheet.Cell(3, 1); // 行番号と列番号でも指定可能
WriteLine($"A1: {cellA1.GetValue<int>()}");
// 出力:A1: 10
WriteLine($"A2: {cellA2.GetValue<int>()}");
// 出力:A2: 20
WriteLine($"A3(値): {cellA3.GetValue<int>()}");
WriteLine($"A3(数式): {cellA3.FormulaA1}");
// 出力:A3(値): 30
// 出力:A3(数式): SUM(A1:A2)
// セルA1の値を書き換える
cellA1.Value = 15;
WriteLine($"A1の値を{cellA1.GetValue<int>()}に書き換えました。");
// 出力:A1の値を15に書き換えました。
WriteLine($"A3の値は{cellA3.GetValue<int>()}に変わりました。");
// 出力:A3の値は35に変わりました。
// ワークブックを保存する
workbook.Save();
}
Const ExcelFilePath As String = ".\\sample.xlsx"
' Excelファイルを開く
Using workbook = New XLWorkbook(ExcelFilePath)
' ワークシートを取得する
Using worksheet = workbook.Worksheet("サンプルシート1")
' 位置を指定してセルを取得する
Dim cellA1 = worksheet.Cell("A1")
Dim cellA2 = worksheet.Cell(2, "A") ' 行番号と列名でも指定可能
Dim cellA3 = worksheet.Cell(3, 1) ' 行番号と列番号でも指定可能
WriteLine($"A1: {cellA1.GetValue(Of Integer)()}")
' 出力:A1: 10
WriteLine($"A2: {cellA2.GetValue(Of Integer)()}")
' 出力:A2: 20
WriteLine($"A3(値): {cellA3.GetValue(Of Integer)()}")
WriteLine($"A3(数式): {cellA3.FormulaA1}")
' 出力:A3(値): 30
' 出力:A3(数式): SUM(A1:A2)
' セルA1の値を書き換える
cellA1.Value = 15
WriteLine($"A1の値を{cellA1.GetValue(Of Integer)()}に書き換えました。")
' 出力:A1の値を15に書き換えました。
WriteLine($"A3の値は{cellA3.GetValue(Of Integer)()}に変わりました。")
' 出力:A3の値は35に変わりました。
' ワークブックを保存する
workbook.Save()
End Using
End Using
ClosedXMLは、コレクションやLINQなどと相性が良い。
コレクションやDataTableオブジェクト(System.Data名前空間)を簡単にワークシートに挿入できる。次のコードは、コレクションを挿入している例だ。データを挿入するInsertDataメソッドは挿入したセルの範囲を返してくるので、続けてその範囲のセルを処理しやすい。また、その範囲から特定の行を抽出するには、ラムダ式で条件を指定できる。
const string ExcelFilePath = ".\\sample.xlsx";
// Excelファイルに貼り付けたいデータ:
// 例として、ここでは今月の日付と曜日のコレクションを用意する
List<(DateTime, string)> days = new List<(DateTime, string)>();
var firstDayOfThisMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
int daysInThisMonth = DateTime.DaysInMonth(firstDayOfThisMonth.Year, firstDayOfThisMonth.Month);
for (int i = 0; i < daysInThisMonth; i++)
{
var d = firstDayOfThisMonth.AddDays(i);
days.Add((d, d.DayOfWeek.ToString()));
}
// Excelファイルを開く
using (var workbook = new XLWorkbook(ExcelFilePath))
// ワークシートを追加する
using (var worksheet = workbook.Worksheets.Add("サンプルシート2"))
{
// タイトル行
var cellA1 = worksheet.Cell("A1");
cellA1.Value = "今月のカレンダー";
cellA1.Style.Alignment.SetHorizontal(XLAlignmentHorizontalValues.Center)
.Font.SetFontName("Meiryo").Font.SetBold(true)
.Font.SetFontColor(XLColor.White)
.Fill.SetBackgroundColor(XLColor.FromArgb(0x00A2E8));
worksheet.Range("A1:B1").Merge(); // セルを連結
// ワークシートに日付と曜日のコレクションを挿入する
var calendarRange = worksheet.Cell("A2").InsertData(days);
// 日曜日の行を抽出し、使われているセルの背景を赤色に変える
var sundayRows
= calendarRange.Rows(r => r.Cell(1).GetDateTime().DayOfWeek == 0);
foreach (var r in sundayRows)
r.CellsUsed().Style.Fill.BackgroundColor = XLColor.Red;
// ワークブックを保存する
workbook.Save();
}
WriteLine("ワークシートを追加しました。");
Const ExcelFilePath As String = ".\\sample.xlsx"
' Excelファイルに貼り付けたいデータ:
' 例として、ここでは今月の日付と曜日のコレクションを用意する
Dim days As List(Of (DateTime, String)) = New List(Of (DateTime, String))()
Dim firstDayOfThisMonth = New DateTime(DateTime.Today.Year, DateTime.Today.Month, 1)
Dim daysInThisMonth As Integer = DateTime.DaysInMonth(firstDayOfThisMonth.Year, firstDayOfThisMonth.Month)
For i As Integer = 0 To daysInThisMonth - 1
Dim d = firstDayOfThisMonth.AddDays(i)
days.Add((d, d.DayOfWeek.ToString()))
Next
' Excelファイルを開く
Using workbook = New XLWorkbook(ExcelFilePath)
' ワークシートを追加する
Using worksheet = workbook.Worksheets.Add("サンプルシート2")
' タイトル行
Dim cellA1 = worksheet.Cell("A1")
cellA1.Value = "今月のカレンダー"
With cellA1.Style
.Alignment.Horizontal = XLAlignmentHorizontalValues.Center
With .Font
.FontName = "Meiryo"
.Bold = True
.FontColor = XLColor.White
End With
.Fill.BackgroundColor = XLColor.FromArgb(&HA2E8)
End With
worksheet.Range("A1:B1").Merge() ' セルを連結
' ワークシートに日付と曜日のコレクションを挿入する
Dim calendarRange = worksheet.Cell("A2").InsertData(days)
' 日曜日の行を抽出し、使われているセルの背景を赤色に変える
Dim sundayRows _
= calendarRange.Rows(Function(r) r.Cell(1).GetDateTime().DayOfWeek = 0)
For Each r In sundayRows
r.CellsUsed().Style.Fill.BackgroundColor = XLColor.Red
Next
' ワークブックを保存する
workbook.Save()
End Using
End Using
WriteLine("ワークシートを追加しました。")
実行ファイルと同じフォルダに「sample.xlsx」がある状態で、上のコードを実行する。実行後にExcelで開いてみると、次の画像のようになっている。
最後に、上で作ったExcelファイルを読み取ってみよう(次のコード)。使われている(=何らかのデータや数式が入っている)セルの範囲を取得するRangeUsedメソッドがあるので、セルの位置を指定しなくても簡単にデータ範囲を取り出せる。そのセル範囲からコレクションを生成するにはLINQのSelect拡張メソッドが使える。DataTableオブジェクトに変換するのも簡単だ。
const string ExcelFilePath = ".\\sample.xlsx";
// Excelファイルを開く
using (var workbook = new XLWorkbook(ExcelFilePath))
// ワークシートを取得する
using (var worksheet = workbook.Worksheet("サンプルシート2"))
{
// 使われているセルの範囲
var rangeUsed = worksheet.RangeUsed();
WriteLine($"使用中のセル範囲:{rangeUsed.FirstCell().Address}〜{rangeUsed.LastCell().Address}");
// 1列目がDateTime型の行のみを取り出して、コレクションに変換する
IEnumerable<(DateTime,string)> data
= rangeUsed.Rows(r => r.Cell(1).DataType == XLDataType.DateTime)
.Select(r => (r.Cell(1).GetDateTime(), r.Cell(2).GetString()));
// 最初の7個を表示
foreach (var (d,s) in data.Take(7))
WriteLine($"{d:M/d}({s})");
// 出力:
// 10/1(Monday)
// 10/2(Tuesday)
// 10/3(Wednesday)
// 10/4(Thursday)
// 10/5(Friday)
// 10/6(Saturday)
// 10/7(Sunday)
// DataTableへの変換もサポートされている
System.Data.DataTable dataTable = rangeUsed.CreateTable().AsNativeDataTable();
// 8個目から7個を表示
foreach(System.Data.DataRow r in dataTable.Select().Skip(7).Take(7))
WriteLine($"{r[0]:M/d}({r[1]})");
// 出力:
// 10/8(Monday)
// 10/9(Tuesday)
// 10/10(Wednesday)
// 10/11(Thursday)
// 10/12(Friday)
// 10/13(Saturday)
// 10/14(Sunday)
}
Const ExcelFilePath As String = ".\\sample.xlsx"
' Excelファイルを開く
Using workbook = New XLWorkbook(ExcelFilePath)
' ワークシートを取得する
Using worksheet = workbook.Worksheet("サンプルシート2")
' 使われているセルの範囲
Dim rangeUsed = worksheet.RangeUsed()
WriteLine($"使用中のセル範囲:{rangeUsed.FirstCell().Address}〜{rangeUsed.LastCell().Address}")
' 出力:使用中のセル範囲:A1〜B32
' 1列目がDateTime型の行のみを取り出して、コレクションに変換する
Dim data As IEnumerable(Of (DateTime, String)) _
= rangeUsed.Rows(Function(r) r.Cell(1).DataType = XLDataType.DateTime) _
.Select(Function(r) (r.Cell(1).GetDateTime(), r.Cell(2).GetString()))
' 最初の7個を表示
For Each v As (d As DateTime, s As String) In data.Take(7)
WriteLine($"{v.d:M/d}({v.s})")
Next
' 出力:
' 10/1(Monday)
' 10/2(Tuesday)
' 10/3(Wednesday)
' 10/4(Thursday)
' 10/5(Friday)
' 10/6(Saturday)
' 10/7(Sunday)
' DataTableへの変換もサポートされている
Dim dataTable As System.Data.DataTable = rangeUsed.CreateTable().AsNativeDataTable()
' 8個目から7個を表示
For Each r As System.Data.DataRow In dataTable.Select().Skip(7).Take(7)
WriteLine($"{r(0):M/d}({r(1)})")
Next
' 出力:
' 10/8(Monday)
' 10/9(Tuesday)
' 10/10(Wednesday)
' 10/11(Thursday)
' 10/12(Friday)
' 10/13(Saturday)
' 10/14(Sunday)
End Using
End Using
Excel 2007以降のOpen XML形式のファイル(拡張子「.xlsx」)は、ClosedXMLを利用して幅広いプラットフォームで読み書きできる。Microsoft純正のOpen XML SDKと比べると、簡潔で分かりやすいコードが書ける上に、Excelの主な数式にも対応しているというメリットもある。
利用可能バージョン:.NET Framework 4.0以降
カテゴリ:オープンソースライブラリ 処理対象:Windowsフォーム
カテゴリ:オープンソースライブラリ 処理対象:WPF
カテゴリ:オープンソースライブラリ 処理対象:Xamarin.Forms
カテゴリ:クラスライブラリ 処理対象:データ
関連TIPS:ExcelなしでExcelファイルを操作するには?(純正SDK編)[.NET 3.5、C#/VB]
関連TIPS:Excelファイルにアクセスするには?[C#、VB]
関連TIPS:構文:複数のオブジェクトを一時的に1つにまとめるには?[C#/VB、.NET Framework 4.7以降]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
Copyright© Digital Advantage Corp. All Rights Reserved.