OpenXMLでExcelファイルを操作しよう (2) - セルに値を設定したい

## はじめに

先月(2021/08)、技術評論社より『[C#コードレシピ集](https://www.amazon.co.jp/dp/4297122650)』を出版しました。この本には、385のコードレシピが掲載されています。執筆している段階では385を超えるレシピを予定していたのですが、ページ数の関係（この本700ページを超える分厚い本です）でいくつかのレシピは没にしています。その中にOpen XMLを使ってExcelを操作するものがあります。そのまま眠らせてしまうのは残念ので、Qiitaでこのレシピを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を使ってエクセルファイルを操作するレシピを掲載しています。


## 定義するメソッド

OpneXMLを利用してセルに値を設定するには、「シートを選択」「セルオブジェクトを作成」「セルに値を設定」という3つの手順を踏みます。

前回定義した、MyExcelBookクラスにこれらの機能を実装してみます。ここでは以下の4つのメソッドを定義しています。

メソッド | 機能
------------|----------------------------
SelectSheet | シートを選択 
CreateCell |　セルオブジェクトの作成、すでに存在している場合はそのセルを返す
SetValueAtCell | セルに値を設定
SetValue  | セルを作成し値を設定する。CreateCell, SetValueAtCellを連続して呼び出す

最後のSetValueメソッドは、CreateCell、SetValueAtCellのふたつのメソッドを連続して呼び出すことで、簡単にセルに値を設定する機能を呼び出せるようにしたものです。すでにCellが存在している場合は、上書きされます。

## MyExcelBookクラス

では、MyExcelBookクラスを示します。

```cs
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) {
        var book = new MyExcelBook();
        // Excelのドキュメントを新規に作成する
        book._document = SpreadsheetDocument.Create(filepath, SpreadsheetDocumentType.Workbook);

        // ドキュメントにワークブックを追加する
        book._workbookpart = book._document.AddWorkbookPart();
        book._workbookpart.Workbook = new Workbook();

        // ワークブックにシートの入れ物を追加する
        book._sheets = book._workbookpart.Workbook.AppendChild<Sheets>(new Sheets());
        return book;
    }
    
    public void Save() {
        _workbookpart.Workbook.Save();
        _document.Close();
    }

    public void CreateSheet(string sheetname) {
        // シート作成のための準備
        _worksheetPart = _workbookpart.AddNewPart<WorksheetPart>();
        _worksheetPart.Worksheet = new Worksheet(new SheetData());

        // シートを作成し追加する
        var max = (uint)(_sheets.Count() + 1);
        var sheet = new Sheet {
            Id = _workbookpart.GetIdOfPart(_worksheetPart),
            SheetId = max,
            Name = sheetname
        };
        _sheets.Append(sheet);
    }
    
    // 以降が追加したメンバー

    // シートを選択する
    public void SelectSheet(string sheetName) {
        // workbookから指定したシートを見つける
        var theSheet = _workbookpart.Workbook.Descendants<Sheet>().
            Where(s => s.Name == sheetName).FirstOrDefault();

        if (theSheet == null)
            throw new ArgumentException("sheetName");
        //  WorkSheetPartを取り出す。これが以降の操作で必要
        _worksheetPart = (WorksheetPart)(_workbookpart.GetPartById(theSheet.Id));
    }

    // セルを作成。存在していたらそのセルを返す
    public Cell CreateCell(string columnName, uint rowIndex) {
        // SheetDataを見つける
        var worksheet = _worksheetPart.Worksheet;
        var sheetData = worksheet.GetFirstChild<SheetData>();
        // 行を見つける
        var row = sheetData.Elements<Row>().FirstOrDefault(r => r.RowIndex == rowIndex);
        if (row == null) {
            //  行がなければ追加する
            row = new Row { RowIndex = rowIndex };
            sheetData.Append(row);
        }
        // その行の指定したカラムのセルを取り出す
        var cellReference = columnName + rowIndex;
        var cell = row.Elements<Cell>().FirstOrDefault(c => c.CellReference.Value == cellReference);
        if (cell != null)
            return cell;
        // セルがなければセルを追加する 
        var refCell = row.Elements<Cell>()
            .FirstOrDefault(c => string.Compare(c.CellReference.Value, cellReference, true) > 0);
        var newCell = new Cell() {
            CellReference = cellReference
        };
        row.InsertBefore(newCell, refCell);
        return newCell;
    }
    
    // 指定したセルに値を設定する
    public void SetValueAtCell(Cell cell, string text) {
        cell.CellValue = new CellValue(text);
        cell.DataType = new EnumValue<CellValues>(CellValues.String);
    }

    // column, rowで指定したセルに値を設定する。
    public void SetValue(string text, string columnName, uint rowIndex) {
        Cell cell = CreateCell(columnName, rowIndex);
        SetValueAtCell(cell, text);
    }
}
```

## MyExcelBookクラスを使って、セルに値を設定する

以下、エクセルのBookを新規作成し、sheet1のセルA1, B2, C3に値を設定している例です。


```cs
using var book = MyExcelBook.CreateBook("example.xlsx");
book.CreateSheet("sheet1");
book.SelectSheet("sheet1");
book.SetValue("Text 1", "A", 1);
book.SetValue("Text 2", "B", 2);
book.SetValue("Text 3", "C", 3);
book.Save();
```

## 実行結果

以下のようなExcelファイルが作成されます。

![スクリーンショット 2020-11-25 20.34.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29415/23b69951-3b57-4def-9488-dcfc67deb81f.png)