前回の記事を書いている途中に、セルのデータを取得する方法についての記事を書き忘れていることに気がつきました。
そこで今回はセルのデータの取得について説明します。

今回の処理対象シートはこんな感じのシートです。

セルのデータ(の種類)はセルA1から順に「文字」、「数値」、「日付」、「数値(数式の結果)」、「論理値」、「エラー」です。
セルA4にはPI関数(円周率)がセットされています。セルA6には数式「=1/0」がセットされており、結果0除算エラーが生じています。

このシートのセルをProductivityToolで確認すると以下のようになっています(一部抜粋)。

c要素のt属性はセルのデータの種類を表します。t属性が"s"である場合はSharedStringのインデックスであることは以前の回で説明しました。
論理値の場合はt属性に"b"がセットされ、またエラーのあるセルの場合は"e"がセットされていることが分かります。

またt属性が必ずしもセットされているということではなく省略される場合もあることや、省略されている場合には数値として評価できることが見て取れます。

以下、Sheet1のすべてのセルを走査してその内容を取得するサンプルプログラムです。

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

namespace OpenXMLSDK_Sample6 {
    class Program {
        static void Main(string[] args) {
            const string FILE = @"C:\Users\Public\Documents\openxmlsdk13.xlsx";
            const string SHEET_NAME = "Sheet1";

            using (SpreadsheetDocument excelDoc = SpreadsheetDocument.Open(FILE, true)) {
                WorkbookPart bookPart = excelDoc.WorkbookPart;

                int sheet_index = GetWorksheetIndex(bookPart, SHEET_NAME);
                WorksheetPart sheet_part = GetWorksheetPart(bookPart, sheet_index);

                foreach(var row in sheet_part.Worksheet.Descendants<Row>()) {
                    foreach(var cell in row.Elements<Cell>()) {
                        System.Diagnostics.Debug.Print("{0}: {1}", cell.CellReference.Value, GetCellValue(bookPart, cell));
                    }
                }
            }
        }

        private static object GetCellValue(WorkbookPart bookPart, Cell cell) {
            if (cell == null || cell.CellValue == null) {
                return null;
            }
            string value_raw = cell.CellValue.InnerText;

            if (cell.DataType == null) {
                double value_double;
                if (double.TryParse(value_raw, out value_double)) {
                    return value_double;
                } else {
                    return value_raw;
                }
            }

            switch (cell.DataType.Value) {
                case CellValues.Boolean:
                    return value_raw == "1";
                case CellValues.Date:
                    return DateTime.FromOADate(Convert.ToDouble(value_raw));
                case CellValues.Error:
                    return value_raw;
                case CellValues.InlineString:
                    return cell.InlineString.Text.InnerText;
                case CellValues.Number:
                    return Convert.ToDouble(value_raw);
                case CellValues.SharedString:
                    return GetSharedString(bookPart, Convert.ToInt32(value_raw));
                case CellValues.String:
                    return value_raw;
                default:
                    return value_raw;
            }
        }

        private static string GetSharedString(WorkbookPart wb_part, int index) {
            if(wb_part == null && wb_part.GetPartsOfType<SharedStringTablePart>().Count() == 0) {
                return string.Empty;
            }
            SharedStringTablePart shareStringPart = wb_part.GetPartsOfType<SharedStringTablePart>().First();
            SharedStringItem sh_item = shareStringPart.SharedStringTable.Elements<SharedStringItem>().ElementAt(index);
            if(sh_item != null && sh_item.Text != null) {
                return sh_item.Text.InnerText;
            }else {
                return string.Empty;
            }
        }
        //以下省略
    }
}

「以下省略」とした部分に定義されているメソッドについては「セルの書式設定 - 表示形式」など、以前の記事をご覧ください。

23行目で今回新たに定義したGetCellValueを呼び出し、セルの値を取得しています。

GetCellValueの定義では、渡されたcellのDataType(cell要素のt属性)を手掛かりにCellValueを変換して返します。(44行目~61行目)
またDataTypeが存在しない(null)の場合はDoubleに変換して値を返します。(35行目~42行目)

日付セルの場合は日付シリアル値が入っているので、FromOADateを使ってDateTime型に変換して返します。(48行目)
文字列セル(SharedStringに実際の値が格納されている)の場合は今回新たに定義したGetSharedStringを使って実際の文字列を取得して返します。(56行目)

次回はセルのフォントや背景色をセットするコードについて説明する予定です。