【问题标题】:Having trouble reading excel file with the OpenXML sdk使用 OpenXML sdk 读取 excel 文件时遇到问题
【发布时间】:2014-08-06 16:38:04
【问题描述】:

我有一个从 excel 文件中读取并将结果存储在 DataSet 中的函数。我有另一个写入 excel 文件的函数。当我尝试从常规的人工生成的 excel 文件中读取时,excel 读取函数返回空白 DataSet,但是当我从写入函数生成的 excel 文件中读取时,它工作得非常好。然后,即使我只是复制并粘贴函数生成的 excel 文件的内容,该函数也无法在常规生成的 excel 文件上工作。我终于找到了它,但我不知道从哪里开始。我的代码有问题吗?

这里是excel生成函数:

public static Boolean writeToExcel(string fileName, DataSet data)
{
    Boolean answer = false;
    using (SpreadsheetDocument excelDoc = SpreadsheetDocument.Create(tempPath + fileName, SpreadsheetDocumentType.Workbook))
    {
        WorkbookPart workbookPart = excelDoc.AddWorkbookPart();
        workbookPart.Workbook = new Workbook();
        WorksheetPart worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
        Sheets sheets = excelDoc.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());
        Sheet sheet = new Sheet()
        {
            Id = excelDoc.WorkbookPart.GetIdOfPart(worksheetPart),
            SheetId = 1,
            Name = "Page1"
        };

        sheets.Append(sheet);

        CreateWorkSheet(worksheetPart, data);
        answer = true;
    }

    return answer;
}

private static void CreateWorkSheet(WorksheetPart worksheetPart, DataSet data)
{
    Worksheet worksheet = new Worksheet();
    SheetData sheetData = new SheetData();
    UInt32Value currRowIndex = 1U;
    int colIndex = 0;
    Row excelRow;
    DataTable table = data.Tables[0];

    for (int rowIndex = -1; rowIndex < table.Rows.Count; rowIndex++)
    {
        excelRow = new Row();
        excelRow.RowIndex = currRowIndex++;
        for (colIndex = 0; colIndex < table.Columns.Count; colIndex++)
        {
            Cell cell = new Cell()
            {
                CellReference = Convert.ToString(Convert.ToChar(65 + colIndex)),
                DataType = CellValues.String
            };

            CellValue cellValue = new CellValue();

            if (rowIndex == -1)
            {
                cellValue.Text = table.Columns[colIndex].ColumnName.ToString();
            }
            else
            {
                cellValue.Text = (table.Rows[rowIndex].ItemArray[colIndex].ToString() != "") ? table.Rows[rowIndex].ItemArray[colIndex].ToString() : "*";
            }

            cell.Append(cellValue);
            excelRow.Append(cell);
        }

        sheetData.Append(excelRow);
    }

    SheetFormatProperties formattingProps = new SheetFormatProperties()
    {
        DefaultColumnWidth = 20D,
        DefaultRowHeight = 20D
    };

    worksheet.Append(formattingProps);
    worksheet.Append(sheetData);
    worksheetPart.Worksheet = worksheet;
}

而读取功能如下:

public static void readInventoryExcel(string fileName, ref DataSet set)
{
    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(fileName, false))
    {
        WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
        WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
        SheetData sheetData = worksheetPart.Worksheet.Elements<SheetData>().First();
        int count = -1;
        foreach (Row r in sheetData.Elements<Row>())
        {
            if (count >= 0)
            {
                DataRow row = set.Tables[0].NewRow();
                row["SerialNumber"]         = r.ChildElements[1].InnerXml;
                row["PartNumber"]           = r.ChildElements[2].InnerXml;
                row["EntryDate"]            = r.ChildElements[3].InnerXml;
                row["RetirementDate"]       = r.ChildElements[4].InnerXml;
                row["ReasonForReplacement"] = r.ChildElements[5].InnerXml;
                row["RetirementTech"]       = r.ChildElements[6].InnerXml;
                row["IncludeInMaintenance"] = r.ChildElements[7].InnerXml;
                row["MaintenanceTech"]      = r.ChildElements[8].InnerXml;
                row["Comment"]              = r.ChildElements[9].InnerXml;
                row["Station"]              = r.ChildElements[10].InnerXml;
                row["LocationStatus"]       = r.ChildElements[11].InnerXml;
                row["AssetName"]            = r.ChildElements[12].InnerXml;
                row["InventoryType"]        = r.ChildElements[13].InnerXml;
                row["Description"]          = r.ChildElements[14].InnerXml;
                set.Tables[0].Rows.Add(row);
            }
            count++;
        }
    }
}

【问题讨论】:

    标签: c# asp.net excel openxml openxml-sdk


    【解决方案1】:

    我认为这是因为您只有一张工作表而 Excel 有三张。我不确定,但我认为床单是按相反的顺序返回的,所以你应该换行:

    WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
    

    到

    WorksheetPart worksheetPart = workbookPart.WorksheetParts.Last();
    

    如果您可以通过工作表名称识别WorksheetPart,则搜索它可能会更安全。您需要先找到Sheet,然后使用该ID 找到SheetPart:

    private WorksheetPart GetWorksheetPartBySheetName(WorkbookPart workbookPart, string sheetName)
    {
        //find the sheet first.
        IEnumerable<Sheet> sheets = workbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>().Where(s => s.Name == sheetName);
    
        if (sheets.Count() > 0)
        {
            string relationshipId = sheets.First().Id.Value;
            WorksheetPart worksheetPart = (WorksheetPart)workbookPart.GetPartById(relationshipId);
            return worksheetPart;
        }
    
        return null;
    }
    

    然后您可以使用:

    WorksheetPart worksheetPart = GetWorksheetPartBySheetName(workbookPart, "Sheet1");
    

    在查看您的代码时,我还注意到了一些您可能(或可能不!)感兴趣的事情:

    在您的代码中,您只是在阅读 InnerXml,所以这对您来说可能无关紧要,但 Excel 存储字符串的方式与您编写它们的方式不同,因此读取 Excel 生成的文件可能无法为您提供您期望的值.在您的示例中,您将字符串直接写入单元格,如下所示:

    但 Excel 使用 SharedStrings 概念,其中所有字符串都写入名为 sharedStrings.xml 的单独 XML 文件。该文件包含 Excel 文件中使用的字符串和引用,它的 that 值存储在工作表 XML 的单元格值中。

    sharedString.xml 如下所示:

    然后细胞看起来像这样:

    &lt;v&gt; 元素中的47 是对第 47 个共享字符串的引用。请注意,生成的 XML 中的类型(t 属性)是 str,但 Excel 生成的文件中的类型是 s。这表示你的是内联字符串,而他们的是共享字符串。

    您可以像阅读任何其他部分一样阅读 SharedStrings:

    var stringTable = workbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
    
    if (stringTable != null)
    {
        sharedString = stringTable.SharedStringTable.ElementAt(int.Parse(value)).InnerText;
    }
    

    其次,如果您查看代码生成的单元格引用和 Excel 生成的单元格引用,您会发现您只输出列而不是行(例如,您输出 A 而不是 A1)。要解决此问题,您应该更改行:

    CellReference = Convert.ToString(Convert.ToChar(65 + colIndex)),
    

    到

    CellReference = Convert.ToString(Convert.ToChar(65 + colIndex) + rowIndex.ToString()),
    

    希望对你有帮助。

    【讨论】:

    • 这很有帮助。但是现在我遇到了另一个我以前没有遇到过的问题。返回的 SheetData 包含工作表的所有行,而不仅仅是我正在使用的那些行,这导致我的应用程序崩溃。既然我发现了它崩溃的原因,我对如何确定其中实际包含信息的行数有点迷茫。至于其他的东西,我真的只是从网上拿了写代码,不像读代码。所以感谢您的提醒。我一走出这个烂摊子,就会去看看。
    • 您能否检查行中的单元格数是否与您期望的匹配,如果不匹配则忽略它(如果您的行始终是工作表中的第一行,则可能会跳出循环)。 r.Elements&lt;Cell&gt;().Count == x; 之类的东西,其中 x 是您预期的单元格数?
    • 好的,现在我遇到了一个问题,我的一些值与 Excel 电子表格中的值不同,我很确定这与您所说的共享字符串有关.那么,当我拥有特定单元格时,如何获取共享字符串?还是我应该以其他方式得到它?
    • 好帖子。非常感谢。
    • @petelids 这是一个结构非常好的答案,而且信息量很大。我完全忘记了这个共享字符串表,这让我很伤心。感谢您在此回复中提供如此详细的信息,非常感谢您的努力!
    【解决方案2】:

    不久前我在尝试对 Word 文档执行此操作时遇到了类似的问题(程序生成的工作正常,但人工生成的却不行)。我发现这个工具非常有用:

    http://www.microsoft.com/en-us/download/details.aspx?id=30425

    基本上,它会查看文件并向您显示 Microsoft 为读取它而生成的代码,以及文件本身的 xml 结构。像往常一样,Microsoft 产品有很多菜单,而且不是很直观,但是在单击一下之后,您将能够准确地看到任何两个文件的情况。我建议您打开一个工作的 excel 文件和一个不工作的文件并比较差异,看看是什么导致了您的问题。

    【讨论】:

    • 我正在为这个项目使用 OpenXml SDK。但是,它仍然给我错误。我应该查看 SDK 中的特定类吗?
    【解决方案3】:

    下面是我用来将特定工作表中的 Excel 文件读取到 DataTable 的 OpenXML 代码。

    首先,你会这样称呼它:

    DataTable dt = OpenXMLHelper.ExcelWorksheetToDataTable("C:\\SQL Server\\SomeExcelFile.xlsx", "Mikes Worksheet");
    

    这是代码:

        public class OpenXMLHelper
        {
            public static DataTable ExcelWorksheetToDataTable(string pathFilename, string worksheetName)
            {
                DataTable dt = new DataTable(worksheetName);
    
                using (SpreadsheetDocument document = SpreadsheetDocument.Open(pathFilename, 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();
                    if (theSheet == null)
                        throw new Exception("Couldn't find the worksheet: " + worksheetName);
    
                    // Retrieve a reference to the worksheet part.
                    WorksheetPart wsPart = (WorksheetPart)(document.WorkbookPart.GetPartById(theSheet.Id));
                    Worksheet workSheet = wsPart.Worksheet;
    
                    string 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
                    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 item 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;
                        }
                        dt.Rows.Add(dataRow);
                    }
    
                    //  Copy the array of strings into a DataTable
                    for (int col = 0; col < numOfColumns; col++)
                        dt.Columns.Add("Column_" + col.ToString());
    
                    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);
                    }
    
    #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;
                }
            }
    
            private 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;
            }
        }
    

    顺便提一下,我们公司的一些 Excel 工作表顶部有一个或多个空白行。奇怪的是,这会阻止其他一些 OpenXML 库正确读取此类工作表。

    这段代码特意为工作表中的每个单元格创建了一个DataTable,即使是顶部的空白单元格也是如此。

    【讨论】:

    • 对其进行了测试。我看到 SheetDimension 在 open xml 规范中是可选的,这是不可靠的。此外,SheetDimension 返回尺寸 =“A1:XEZ64”,因此我们需要一个大小为 16380x64 的 DataTable。 16380 列。这是针对我拥有的一些示例 excelfile,其水平线跨越所有列,但只有几列包含数据。但一个很好的起点,我认为它只需要一些改进。
    猜你喜欢
    • 2019-02-21
    • 1970-01-01
    • 2013-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多