NPOI 2.0 と ClosedXML 0.67 の比較
先日 NPOI 2.0 のα版がリリースされました。
NPOIは現在 1.X系が Stable ですが、*.xls のみの対応で *.xlsx は未対応だったのでこれまで採用できませんでした。
しかし、NPOI 2.0 では *.xlsx の対応も出来るようなったとのことで試してみました。
結論から言うと、現時点の僕の知識では ClosedXML の方が扱いやすいです。
旧形式(*.xls)の扱いがないのであれば、 ClosedXML の方がオススメ。
知名度は断然 NPOI なんですけどね。
以下、サンプルコードにて説明します。
※VB.NET で書いてます。NPOI も ClosedXML もサンプルは C# なので本当はそっちで書きたいのですが。。
今回は罫線やグラフ等は扱わず、あるEXCELファイル(*.xls or *.xlsx)を開き、中身を読み込んでそれを別の新規EXCELファイルとCSVに書き出すというもの。
読み込むEXCELの中身は以下のような形になっています。
行の途中と最後にわざと空セルを用意しています
Book1.xlsx
番号 | 氏名 | 情報 | 誕生日 | 備考 |
000001 | 名前1 | 2012/08/01 | ||
000002 | 名前2 | 2012/08/02 | ||
000003 | 名前3 | 2012/08/03 |
NPOIのコードがコレ。
Imports System.IO Imports NPOI.HSSF.UserModel Imports NPOI.XSSF.UserModel Imports NPOI.SS.UserModel Module Module1 Sub Main() Dim xlsPath = "C:\TEST\Book1.xls" Dim xlsxPath = "C:\TEST\Book1.xlsx" Dim newXlsPath = "C:\TEST\NPOI.xls" Dim newXlsxPath = "C:\TEST\NPOI.xlsx" Dim csvPath = "C:\TEST\NPOI.csv" Dim xlsdata = readNPOI(xlsPath) writeNPOI(newXlsPath, xlsdata) Dim xlsxdata = readNPOI(xlsxPath) writeNPOI(newXlsxPath, xlsxdata) '*.csv の書き込み Using sw As New StreamWriter(csvPath) For Each _data In xlsxdata sw.WriteLine(_data) Next End Using End Sub Private Function readNPOI(filePath As String) As List(Of String) Dim book As IWorkbook = createFactory(filePath) Dim sheet = book.GetSheetAt(0) Dim listOfStr As New List(Of String) For rowCnt = 0 To sheet.LastRowNum Dim row = sheet.GetRow(rowCnt) Dim sb = New List(Of String) For colCnt = 0 To row.LastCellNum - 1 If row.GetCell(colCnt) IsNot Nothing Then sb.Add(row.GetCell(colCnt).ToString) Else sb.Add("") End If Next listOfStr.Add(String.Join(",", sb.ToArray())) Next Return listOfStr End Function Private Sub writeNPOI(filePath As String, datas As List(Of String)) FileDelete(filePath) Dim book As IWorkbook = createFactory(filePath) Dim sheet = book.CreateSheet("Sheet1") For rowCnt = 0 To datas.Count - 1 Dim ary = datas(rowCnt).Split(",") Dim row = sheet.CreateRow(rowCnt) For colCnt = 0 To ary.Length - 1 row.CreateCell(colCnt).SetCellValue(ary(colCnt)) Next Next Using fs As New FileStream(filePath, FileMode.Create) book.Write(fs) End Using End Sub Private Function createFactory(filePath As String) As IWorkbook If File.Exists(filePath) Then '既存のファイルを開く Using fs As New FileStream(filePath, FileMode.Open) If filePath.EndsWith(".xls") Then Return New HSSFWorkbook(fs) Else Return New XSSFWorkbook(fs) End If End Using Else '存在しなければ新規作成() If filePath.EndsWith(".xls") Then Return New HSSFWorkbook Else Return New XSSFWorkbook End If End If End Function Private Sub FileDelete(filePath As String) If File.Exists(filePath) Then File.Delete(filePath) End If End Sub End Module
ClosedXMLのコードがコレ。
Imports System.IO Imports ClosedXML.Excel Module Module1 Sub Main() Dim xlsxPath = "C:\TEST\Book1.xlsx" Dim newXlsxPath = "C:\TEST\closedXML.xlsx" Dim csvPath = "C:\TEST\closedXML.csv" Dim xlsxdata = readClosedXML(xlsxPath) writeClosedXML(newXlsxPath, xlsxdata) '*.csv の書き込み Using sw As New StreamWriter(csvPath) For Each _data In xlsxdata sw.WriteLine(_data) Next End Using End Sub Private Function readClosedXML(filePath As String) As List(Of String) Using wb = New XLWorkbook(filePath) Dim ws = wb.Worksheet(1) Dim listOfStr As New List(Of String) For rowNo = 1 To ws.RangeUsed.RangeAddress.LastAddress.RowNumber Dim sb = New List(Of String) For colNo = 1 To ws.RangeUsed.RangeAddress.LastAddress.ColumnNumber Dim cell = ws.Cell(rowNo, colNo) sb.Add(cell.Value.ToString) Next listOfStr.Add(String.Join(",", sb.ToArray())) Next Return listOfStr End Using End Function Private Sub writeClosedXML(filePath As String, datas As List(Of String)) FileDelete(filePath) Using wb = New XLWorkbook() Using ws = wb.Worksheets.Add("Sheet1") Dim listOfAry As New List(Of String()) For Each _data In datas listOfAry.Add(_data.Split(","c)) Next ws.Cell(1, 1).Value = listOfAry wb.SaveAs(filePath) End Using End Using End Sub Private Sub FileDelete(filePath As String) If File.Exists(filePath) Then File.Delete(filePath) End If End Sub End Module
NPOI は新旧両方の EXCEL 形式に対応しなければいけないので、createFactory メソッドの分コードが多いですが、それ以外の処理も ClosedXML の方がスマートです。
以下詳細を見ていきます。
読み込み(readNPOI、readClosedXML)について
最終列の扱い
ClosedXML はワークシートを読み込むだけで、以下の2つのプロパティで読み込むべき範囲(最終行と最終列)が分かります。
Dim ws = wb.Worksheet(1) ws.RangeUsed.RangeAddress.LastAddress.RowNumber '最終行 ws.RangeUsed.RangeAddress.LastAddress.ColumnNumber '最終列
対して、NPOI はシート、行、とオブジェクトを下りていかないと最終列が分かりません。
Dim sheet = book.GetSheetAt(0) Dim row = sheet.GetRow(rowCnt) row.LastCellNum '最終列
このような作りなので、NPOI の場合は列オブジェクトを取得するたびに最終列の値が変化します。
今回読み込むEXCELの場合、1行目は LastCellNum は "6"、2行目以降は LastCellNum は "5" となります。
これをClosedXMLだと全ての行に対して、6列分処理してくれます。
NPOI 行によって処理回数が異なるので、後で出てくる CSV ファイルを作る際、カンマの数が差異となって出てきます。
空セルの扱い
上記と同様で行途中の空セルの扱いについても異なります。
ClosedXML は空セルは空文字となるのに対し、 NPOI は Nothing(Null) になります。
よって、ループでセル値を取得する際に NPOI は Nothing の判断処理が入っています。
書き込み(writeNPOI、writeClosedXML)について
読み込みの時と同様、 NPOI ではシート、行、セルと作っていき、値をセットします。
対して ClosedXML は VBA チックにセルの列番号と行番号を指定して値をセットすることも出来ますが、今回のサンプルでは基点となるセルに配列を渡して、自動展開で値をセットしています。
ws.Cell(1, 1).Value = listOfAry
個人的にはこれは便利でした。
動的解析・デバッグ時に変数の中身を展開し、どのようにシートに値がセットされるかイメージしやすかったです。
他にも ClosedXML は VBA の書き方に近いので、添え字の考え方などもスマートな印象でした。
NPOI のコードでは
For rowCnt = 0 To datas.Count - 1
みたいに、「0から始まって配列数 -1 まで」みたいなコードがありますが、ClosedXML では出てこないです。
以上が現時点では NPOI より ClosedXML 思った理由でした。
NPOI はまだα版ですし今後の発展も楽しみですね。