【问题标题】:Reading excel file in c# using Microsoft DocumentFormat.OpenXml SDK使用 Microsoft DocumentFormat.OpenXml SDK 在 c# 中读取 excel 文件
【发布时间】:2017-07-04 18:40:19
【问题描述】:

我正在使用 Microsoft DocumentFormat.OpenXml SDK 从 excel 文件中读取数据。 这样做时,我会考虑单元格是否有空白值(如果是,也请阅读)。

现在,面对其中 workSheet.SheetDimension 为空的 Excel 工作表之一的问题,因此代码抛出异常。

使用的代码:

类 OpenXMLHelper { // 一个使用 OpenXML 打开 Excel 文件的辅助函数,并返回一个包含其中所有数据的 DataTable // 的工作表。 // // 我们在使用 OLEDB 读取 Excel 数据时遇到了很多问题(例如,ACE 驱动程序不再存在于新服务器上, // OLEDB 由于安全问题而无法工作,并且公然忽略工作表顶部的空白行),所以这是一个更 // 读取数据的稳定方法。 //

    public static DataTable ExcelWorksheetToDataTable(string pathFilename)
    {
        try
        {
            DataTable dt = new DataTable();
            string dimensions = string.Empty;

            using (FileStream fs = new FileStream(pathFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                using (SpreadsheetDocument document = SpreadsheetDocument.Open(fs, false))
                {
                    // Find the sheet with the supplied name, and then use that 
                    // Sheet object to retrieve a reference to the first worksheet.
                    //Sheet theSheet = document.WorkbookPart.Workbook.Descendants<Sheet>().Where(s => s.Name == worksheetName).FirstOrDefault();
                    //--Sheet theSheet = document.WorkbookPart.Workbook.Descendants<Sheet>().FirstOrDefault();

                    //--if (theSheet == null)
                    //--    throw new Exception("Couldn't find the worksheet: "+ theSheet.Id);

                    // Retrieve a reference to the worksheet part.
                    //WorksheetPart wsPart = (WorksheetPart)(document.WorkbookPart.GetPartById(theSheet.Id));
                    //--WorksheetPart wsPart = (WorksheetPart)(document.WorkbookPart.GetPartById(theSheet.Id));

                    WorkbookPart workbookPart = document.WorkbookPart;
                    WorksheetPart wsPart = workbookPart.WorksheetParts.FirstOrDefault();
                    Worksheet workSheet = wsPart.Worksheet;

                    dimensions = workSheet.SheetDimension.Reference.InnerText;       //  Get the dimensions of this worksheet, eg "B2:F4"

                    int numOfColumns = 0;
                    int numOfRows = 0;
                    CalculateDataTableSize(dimensions, ref numOfColumns, ref numOfRows);
                    //System.Diagnostics.Trace.WriteLine(string.Format("The worksheet \"{0}\" has dimensions \"{1}\", so we need a DataTable of size {2}x{3}.", worksheetName, dimensions, numOfColumns, numOfRows));

                    SheetData sheetData = workSheet.GetFirstChild<SheetData>();
                    IEnumerable<Row> rows = sheetData.Descendants<Row>();

                    string[,] cellValues = new string[numOfColumns, numOfRows];

                    int colInx = 0;
                    int rowInx = 0;
                    string value = "";
                    SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart;

                    //  Iterate through each row of OpenXML data, and store each cell's value in the appropriate slot in our [,] string array.
                    foreach (Row row in rows)
                    {
                        for (int i = 0; i < row.Descendants<Cell>().Count(); i++)
                        {
                            //  *DON'T* assume there's going to be one XML element for each column in each row...
                            Cell cell = row.Descendants<Cell>().ElementAt(i);
                            if (cell.CellValue == null || cell.CellReference == null)
                                continue;                       //  eg when an Excel cell contains a blank string

                            //  Convert this Excel cell's CellAddress into a 0-based offset into our array (eg "G13" -> [6, 12])
                            colInx = GetColumnIndexByName(cell.CellReference);             //  eg "C" -> 2  (0-based)
                            rowInx = GetRowIndexFromCellAddress(cell.CellReference) - 1;     //  Needs to be 0-based

                            //  Fetch the value in this cell
                            value = cell.CellValue.InnerXml;
                            if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
                            {
                                value = stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;
                            }

                            cellValues[colInx, rowInx] = value;
                        }
                    }

                    //  Copy the array of strings into a DataTable.
                    //  We don't (currently) make any attempt to work out which columns should be numeric, rather than string.
                    for (int col = 0; col < numOfColumns; col++)
                    {
                        //dt.Columns.Add("Column_" + col.ToString());
                        dt.Columns.Add(cellValues[col, 0]);
                    }

                    //foreach (Cell cell in rows.ElementAt(0))
                    //{
                    //    dt.Columns.Add(GetCellValue(doc, cell));
                    //}


                    for (int row = 0; row < numOfRows; row++)
                    {
                        DataRow dataRow = dt.NewRow();
                        for (int col = 0; col < numOfColumns; col++)
                        {
                            dataRow.SetField(col, cellValues[col, row]);
                        }
                        dt.Rows.Add(dataRow);
                    }

                    dt.Rows.RemoveAt(0);
                    //#if DEBUG
                    //                //  Write out the contents of our DataTable to the Output window (for debugging)
                    //                string str = "";
                    //                for (rowInx = 0; rowInx < maxNumOfRows; rowInx++)
                    //                {
                    //                    for (colInx = 0; colInx < maxNumOfColumns; colInx++)
                    //                    {
                    //                        object val = dt.Rows[rowInx].ItemArray[colInx];
                    //                        str += (val == null) ? "" : val.ToString();
                    //                        str += "\t";
                    //                    }
                    //                    str += "\n";
                    //                }
                    //                System.Diagnostics.Trace.WriteLine(str);
                    //#endif
                    return dt;
                }
            }
        }
        catch (Exception ex)
        {
            return null;
        }

    }

    public static void CalculateDataTableSize(string dimensions, ref int numOfColumns, ref int numOfRows)
    {
        //  How many columns & rows of data does this Worksheet contain ?  
        //  We'll read in the Dimensions string from the Excel file, and calculate the size based on that.
        //      eg "B1:F4" -> we'll need 6 columns and 4 rows.
        //
        //  (We deliberately ignore the top-left cell address, and just use the bottom-right cell address.)
        try
        {
            string[] parts = dimensions.Split(':');     // eg "B1:F4" 
            if (parts.Length != 2)
                throw new Exception("Couldn't find exactly *two* CellAddresses in the dimension");

            numOfColumns = 1 + GetColumnIndexByName(parts[1]);     //  A=1, B=2, C=3  (1-based value), so F4 would return 6 columns
            numOfRows = GetRowIndexFromCellAddress(parts[1]);
        }
        catch
        {
            throw new Exception("Could not calculate maximum DataTable size from the worksheet dimension: " + dimensions);
        }
    }

    public static int GetRowIndexFromCellAddress(string cellAddress)
    {
        //  Convert an Excel CellReference column into a 1-based row index
        //  eg "D42"  ->  42
        //     "F123" ->  123
        string rowNumber = System.Text.RegularExpressions.Regex.Replace(cellAddress, "[^0-9 _]", "");
        return int.Parse(rowNumber);
    }

    public static int GetColumnIndexByName(string cellAddress)
    {
        //  Convert an Excel CellReference column into a 0-based column index
        //  eg "D42" ->  3
        //     "F123" -> 5
        var columnName = System.Text.RegularExpressions.Regex.Replace(cellAddress, "[^A-Z_]", "");
        int number = 0, pow = 1;
        for (int i = columnName.Length - 1; i >= 0; i--)
        {
            number += (columnName[i] - 'A' + 1) * pow;
            pow *= 26;
        }
        return number - 1;
    }
}[enter image description here][1]

【问题讨论】:

    标签: c# excel


    【解决方案1】:

    SheetDimension 部分是可选的(因此您不能总是依赖它是最新的)。请参阅 OpenXML 规范的以下部分:

    18.3.1.35 维度(工作表维度)

    此元素指定工作表的使用范围。它指定的行和列边界 工作表中使用的单元格。 这是可选的,不是必需的。 使用的单元格包括带有公式、文本内容和单元格的单元格 格式化。当整列被格式化时,只有第一个单元格 该列被视为已使用。

    因此,没有任何 SheetDimension 部分的 Excel 文件是完全有效的,因此您不应依赖它存在于 Excel 文件中。

    因此,我建议简单地解析 SheetData 部分中包含的所有 Row 元素,并“计算”行数(而不是读取 SheetDimensions 部分来获取行数/列数) .这样您还可以考虑到 Excel 文件可能在数据之间包含完全空白的行。

    【讨论】:

    • 仅解析行并不能获取所需的结果。问题:第 1 行:5 个单元格(都有值)第 2 行:6 个单元格(单元格 1,2 为空白)。所以发生的事情是,当放置数据时,第 1 行将按原样打印,但第 2 行有 2 个空白单元格,这些单元格在插入数据表时向左移动。因此,我考虑了 SheetDimension。任何建议如何解决这个问题。代码 sn-p 会更清晰。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-31
    • 2014-10-08
    相关资源
    最近更新 更多