OpenXMLでExcelファイルを操作しよう (3) - セルの値を読み込みたい

## はじめに

『[C#コードレシピ集](https://www.amazon.co.jp/dp/4297122650)』では掲載できなかったOpen XMLを使ってExcelを操作するレシピを5回に分けて紹介しています。

1. [OpenXMLでExcelファイルを操作しよう (1) - Excelファイルを新規作成したい](https://qiita.com/gushwell/items/5c689e86014105313017)
1. [OpenXMLでExcelファイルを操作しよう (2) - セルに値を設定したい](https://qiita.com/gushwell/items/66ef40e9fedf8e0b6237)  
1. [OpenXMLでExcelファイルを操作しよう (3) - セルの値を読み込みたい](https://qiita.com/gushwell/items/d719fd287be1b4815f2f)  ← 今ここ
1. [OpenXMLでExcelファイルを操作しよう (4) - 全てのセルの値を取得したい](https://qiita.com/gushwell/items/456e9da791f1a8d5bb59)
1. [OpenXMLでExcelファイルを操作しよう (5) - セルに書式を設定したい](https://qiita.com/gushwell/items/51c90012e724088d318c)

コードレシピの環境は、C#9.0 + .NET 5 のコンソールアプリケーションです。

なお、『[C#コードレシピ集](https://www.amazon.co.jp/dp/4297122650)』では、NPOIを使ってエクセルファイルを操作するレシピを掲載しています。


## 定義するメソッド

前回は、指定したセルに値を設定するコードを示しまましたが、今度は指定したセルの値を取得するコードを示しましょう。
前回同様、MyExcelBookクラスに新しい機能を追加します。追加するのは以下の4つのメソッドです。

メソッド | 機能
------------|----------------------------
Open | エクセルファイルをオープン (静的メソッド)
GetCell | セルオブジェクトを取得
GetValueAtCell | セルの値を取得　(column, row指定)
GetValueAtCell | セルの値を取得　(Cell指定)

今回は、既存のエクセルファイルを扱えるように、Openメソッドも定義しています。

## MyExcelBookクラス

では、MyExcelBookクラスを示します。コードが長くなるので、前回までのコードは省略します。

```cs
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Packaging;

sealed class MyExcelBook : IDisposable {
    private MyExcelBook() { }

    public void Dispose() => _document?.Dispose();

    private SpreadsheetDocument _document;
    private WorkbookPart _workbookpart;
    private WorksheetPart _worksheetPart;
    private Sheets _sheets;

    // 前回までに定義したメソッド. 中身は省略
    public static MyExcelBook CreateBook(string filepath) { ... }
    public void Save() { ... }
    public void CreateSheet(string sheetname) { ... }
    public void SelectSheet(string sheetName) { ... }
    public Cell CreateCell(string columnName, uint rowIndex) { ... }
    public void SetValueAtCell(Cell cell, string text) { ... }
    public void SetValue(string text, string columnName, uint rowIndex) { ... }

    // 以降が追加したメンバー

    // 既存のエクセルファイルをオープンする
    public static MyExcelBook Open(string filePath) {
        var obj = new MyExcelBook();
        obj._document = SpreadsheetDocument.Open(filePath, false);
        obj._workbookpart = obj._document.WorkbookPart;
        return obj;
    }

    // 指定したセルオブジェクトを取得する
    public Cell GetCell(string columnName, uint rowIndex) {
        string addressName = columnName + rowIndex;
        return _worksheetPart.Worksheet.Descendants<Cell>()
                        .Where(c => c.CellReference == addressName)
                        .FirstOrDefault();
    }

    // セルの値を取得する　(column, row指定)
    public string GetCellValue(string columnName, uint rowIndex) {
        return GetCellValue(GetCell(columnName, rowIndex));
    }

    // セルの値を取得する　(Cell指定)
    public string GetCellValue(Cell cell) {
        if (cell == null)
            return null;
        // 式かどうか判定
        if (cell.CellFormula != null)
            // 式ならTextプロパティを返す。
            return cell.CellValue.Text;
        var value = cell.InnerText;
        if (cell.DataType == null)
            return value;
        // セルのデータタイプによって処理を振り分ける
        switch (cell.DataType.Value) {
            case CellValues.SharedString:  // 文字列
                // 単一の SharedStringTablePart への参照を取得する必要がある
                var stringTable = _workbookpart
                    .GetPartsOfType<SharedStringTablePart>()
                    .FirstOrDefault();
                if (stringTable != null) {
                    // valueはindexを表している。SharedStringTableから要素を取得
                    var element = stringTable.SharedStringTable
                        .ElementAt(int.Parse(value));
                    // そのInnterTextがセルの値
                    value = element.InnerText;
                }
                break;
            case CellValues.Boolean:    // ブール値
                value = (value != "0").ToString().ToLower();
                break;
        }
        return value;
    }
}
```

## MyExcelBookクラスを使ってセルの値を取得する

上記のMyExcelBookクラスを使って、セルの値を取得するコードを示します。

```cs
const string fileName = "example.xlsx";
using var xls = MyExcelBook.Open(fileName);
xls.SelectSheet("Sheet1");
var value = xls.GetCellValue("A", 1);
Console.WriteLine(value);
value = xls.GetCellValue("B", 2);
Console.WriteLine(value);
value = xls.GetCellValue("C", 3);
Console.WriteLine(value);
value = xls.GetCellValue("D", 4);
var v = DateTime.FromOADate(double.Parse(value));
Console.WriteLine(v);
```

## 実行結果

![スクリーンショット 2021-10-12 21.01.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29415/7273c145-7de0-3ebd-aa22-bf13aa7cf35a.png)


上記のエクセルファイルを入力ファイルとした場合の結果を以下に示します。

```
Text 1
Text 2
Text 3
2020/12/06 12:40:00
```

## 補足

このサンプルコードでは、日付のデータは数字文字列として取得されます。DateTime型にしたい場合には、この値をdouble型に変換後、`DateTime.FromOADate`メソッドでDateTime型に変換します。