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 はまだα版ですし今後の発展も楽しみですね。