連載
» 2018年10月24日 05時00分 公開

.NET TIPS:ExcelなしでExcelファイルを操作するには?(ClosedXML編)[.NET 4.0、C#/VB]

ClosedXmlライブラリを使用して、Excelファイル(.xlsxファイル)の作成、書き込み/読み込み、LINQを使ったセルの値の取得などを行う方法を説明する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載「.NET TIPS」

 Microsoft Office(Word/Excel/PowerPoint)のファイルをプログラムから操作するためにOpen XML SDKというライブラリがMicrosoftから提供されているが、汎用的な作りになっているために、特にExcelファイルのデータや書式を扱うには煩雑なコードが必要になってしまっている。

 Excelファイルだけを扱うのであれば、サードパーティー製のClosedXMLというライブラリを使うと簡単だ。本稿では、そのClosedXMLの使い方を紹介する。

POINT ClosedXMLの情報(Open XML SDKとの比較)

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

本稿のサンプルコードに必要な宣言(上:C#、下:VB)

ClosedXMLとは?

 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 Framework 4.0〜
  • .NET Standard 2.0〜

 .NET Standard 2.0で動作するということは、それをサポートしている以下のプラットフォームからも利用できることになる。

  • .NET Core(ASP.NET Core)2.0〜
  • UWP 10.0.16299(1709)〜
  • Xamarin.iOS 10.14〜
  • Xamarin.Android 8.0〜

 ClosedXMLのドキュメントは、ClosedXMLのWikiで公開されている。本稿では、Excelファイルを操作する例を幾つか紹介するだけなので、詳しい使い方はこのドキュメントを参照していただきたい。残念ながら、日本語のレファレンスはまだないようだ。

 なお、ClosedXMLには次のような派生プロダクトもある。

ClosedXMLを導入するには?

 GitHubからソースコードをダウンロードしてきてビルドしてもよいが、バイナリがNuGetで公開されている。

 Visual Studioでプロジェクトを作ったら、NuGetで「ClosedXml」を検索し(次の画像)、インストールする。Open XML SDKなど、幾つかの必要なパッケージが一緒にインストールされる。

NuGetでClosedXMLを検索する(Visual Studio 2017) NuGetでClosedXMLを検索する(Visual Studio 2017)

Excelファイルを作り、データを書き込み、書式を設定するには?

 ここからは、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ファイルを保存しました。")

Excelファイルを作ってセルを追加し、書式を設定する例(上:C#、下:VB)
前回のコードと見比べてみてほしい。とても短いコードになった。また、コードの意味も把握しやすくなっている。

 上のコードを実行すると、実行ファイルと同じフォルダに「sample.xlsx」が作られる。Excelで開いてみると、次の画像のようになっている。セルA3には数式を挿入したのだが、(Excelで開いたときに)自動計算されている。また、セルA3には書式が設定されている。

ここまでのコードの実行結果(Excel 2016) ここまでのコードの実行結果(Excel 2016)

Excelファイルを読み込んでデータを読み書きするには?

 上で作成した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

Excelファイルを読み込んでデータを読み書きする例(上:C#、下:VB)
Excelのファイル上は、数式が入っているセルA3に値は存在していない。しかし、ClosedXMLはExcelの主な関数を独自に実装しているので、数式の計算結果が値として得られる。また、セルの値を書き換えたとき、そこを参照している数式の計算結果は自動的に更新される。ただし、Excelを呼び出しているわけではないので、計算結果がExcelと異なる場合もあり得ることには注意してほしい。

コレクションやDataTableを扱うには?

 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("ワークシートを追加しました。")

ワークシートにコレクションを挿入する例(上:C#、下:VB)
ここで、Listコレクションの要素は日付型と文字列型を持つタプルである。詳しくは、「構文:複数のオブジェクトを一時的に1つにまとめるには?[C#/VB、.NET Framework 4.7以降]」をご覧いただきたい。.NET Framework 4.7以前では、日付型と文字列型のプロパティを1つずつ持つデータクラスを作成し、タプルの部分をそれで置き換えてほしい。
この例ではコレクションを挿入したが、DataTableオブジェクトのデータを挿入するときも同様にInsertDataメソッドを使う。

 実行ファイルと同じフォルダに「sample.xlsx」がある状態で、上のコードを実行する。実行後にExcelで開いてみると、次の画像のようになっている。

ここまでのコードの実行結果(Excel 2016) ここまでのコードの実行結果(Excel 2016)

 最後に、上で作った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

ワークシートのデータを読み取ってコレクションやDataTableに変換する例(上:C#、下:VB)

まとめ

 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]


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。