.NET（C#）でエクセルの値を取得する_OpenXml

.NET（C#）のライブラリOpenXmlを使ってエクセルの値を取得してみます。


# OpenXmlを使う準備
## 環境
vscode
.net8

## コンソールアプリ
```
dotnet new console -n TestOpenXml
cd TestOpenXml
dotnet add package DocumentFormat.OpenXml
```

# コード内容
## エクセルの文字取得

```c#
private static string GetExcelValue(string fileName)
{
    //すでに開いているExcelファイルを開けるようにする
    var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    using var document = SpreadsheetDocument.Open(fs, false);
    var wbPart = document.WorkbookPart;
    var sheets = wbPart?.Workbook?.Sheets.Cast<Sheet>();
    if (sheets is null) return null;

    // セルの値取得
    var cellsValue = sheets.SelectMany(sheet => ((WorksheetPart)wbPart!.GetPartById(sheet.Id!))
            .Worksheet.Descendants<Cell>()
            .Select(cell => GetCellValue(document, cell))
            .Where(x => x is not null));

    // オブジェクトの値取得
    var objectsValue = sheets.SelectMany(sheet => ((WorksheetPart)wbPart!.GetPartById(sheet.Id!))
            .DrawingsPart?.WorksheetDrawing?
            .Select(drawObject => drawObject?.OfType<Shape>()?
            .FirstOrDefault()?.InnerText) ?? []);

    return string.Join(",", cellsValue.Concat(objectsValue));
}
// 共通文字列テーブルを考慮した処理で取得します
private static string GetCellValue(SpreadsheetDocument document, Cell cell)
{
    string value = cell.CellValue?.InnerText;

    if (cell.DataType is not null && cell.DataType.Value == CellValues.SharedString)
    {
        int sharedStringIndex = int.Parse(value);
        SharedStringItem item = document.WorkbookPart.SharedStringTablePart.SharedStringTable
                .Elements<SharedStringItem>().ElementAt(sharedStringIndex);
        return item?.Text?.InnerText;
    }
    return value;

}
```
## 共通文字列テーブル
エクセルVBAにように、ブック→シート→セルの順序でアクセスするのではなく、セルの共通文字列テーブルを考慮する必要がありました。
`GetCellValue`関数の部分
[共有文字列テーブルを操作する](https://learn.microsoft.com/ja-jp/office/open-xml/spreadsheet/working-with-the-shared-string-table?tabs=cs)

## ワードの文字取得
```c#
private static string GetWordValue(string fileName)
{
    //すでに開いているwordファイルを開けるようにする
    var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    using var document = WordprocessingDocument.Open(fs, false);
    var documentValue = document.MainDocumentPart.Document.Body
            .Select(x => x.InnerText);

    return string.Join(",", documentValue);
}
```

## ファイルで分岐する
```c#
public static string GetFileValue(string fileName)
{
    return fileName switch
    {
        var x when !File.Exists(x) => null,
        var x when Regex.IsMatch(x, @"^.*\.xls.$") => GetExcelValue(x),
        var x when Regex.IsMatch(x, @"^.*\.doc.$") => GetWordValue(x),
        _ => null
    };
}
```

## 古いファイル形式は対象外
`.xls`や`.doc`のような古いファイル形式はOpenXmlで読み込めません。
これは、その形式がXMLベースではないため。

# 参考
- [スプレッドシート ドキュメント内のセルの値を取得する](https://learn.microsoft.com/ja-jp/office/open-xml/spreadsheet/how-to-retrieve-the-values-of-cells-in-a-spreadsheet?tabs=cs-0%2Ccs-2%2Ccs-3%2Ccs-4%2Ccs-5%2Ccs-6%2Ccs-7%2Ccs-8%2Ccs-9%2Ccs-10%2Ccs-11%2Ccs)
- [他のプロセスで開かれているExcelファイルをClosedXMLで開く](https://shuhelohelo.hatenablog.com/entry/2019/04/22/103123)
- [DrawingsPart クラス](https://learn.microsoft.com/ja-jp/dotnet/api/documentformat.openxml.packaging.drawingspart?view=openxml-3.0.1)
- [ワード文書とテキストファイルの相互変換（C#＋OpenXML）](https://zenn.dev/dsl_gunma/articles/2e8a59437bc029#%E3%82%B3%E3%83%BC%E3%83%89%EF%BC%92%EF%BC%9A%E3%83%AF%E3%83%BC%E3%83%89%E6%96%87%E6%9B%B8%E3%81%8B%E3%82%89%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E6%8A%BD%E5%87%BA)
- [Body クラス](https://learn.microsoft.com/ja-jp/dotnet/api/documentformat.openxml.wordprocessing.body?view=openxml-3.0.1&redirectedfrom=MSDN)