如何使用OpenXML从Excel表格中检索选项卡名称

17
我有一个包含182列的电子表格文档。我需要将电子表格数据逐个标签页放入数据表中,但是我需要在从每个标签页添加数据时找出该标签页的名称,并将其添加到数据表中的一列中。
以下是我设置数据表的方式。
然后我在工作簿中循环,并深入到sheetData对象,遍历每一行和每一列,获取单元格数据。
DataTable dt = new DataTable();
for (int i = 0; i <= col.GetUpperBound(0); i++)
{
    try
    {
        dt.Columns.Add(new DataColumn(col[i].ToString(), typeof(string)));
    }
    catch (Exception e)
    {
        MessageBox.Show("Uploader  Error" + e.ToString());
        return null;
    }
}

dt.Columns.Add(new DataColumn("SheetName", typeof(string)));

然而,对于我用于 Data Table 的字符串数组的末尾位置,我需要添加选项卡名称。当我在 Open XML 中循环遍历工作表时,如何找到选项卡名称?

这是我目前的代码:

using (SpreadsheetDocument spreadSheetDocument = 
           SpreadsheetDocument.Open(Destination, false))
{
    WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;
    Workbook workbook = spreadSheetDocument.WorkbookPart.Workbook;

    Sheets sheets = 
        spreadSheetDocument
            .WorkbookPart
            .Workbook
            .GetFirstChild<DocumentFormat.OpenXml.Spreadsheet.Sheets>();

    OpenXmlElementList list = sheets.ChildElements;

    foreach (WorksheetPart worksheetpart in workbook.WorkbookPart.WorksheetParts)
    {
        Worksheet worksheet = worksheetpart.Worksheet;

        foreach (SheetData sheetData in worksheet.Elements<SheetData>())
        {
            foreach (Row row in sheetData.Elements())
            {
                string[] thisarr = new string[183];
                int index = 0;
                foreach (Cell cell in row.Elements())
                {
                    thisarr[(index)] = GetCellValue(spreadSheetDocument, cell);
                    index++;
                }
                thisarr[182] = ""; //need to add tabname here
                if (thisarr[0].ToString() != "")
                {
                    dt.Rows.Add(thisarr);
                }
            }
        }
    }
}

return dt;

注意:我之前是从“list”元素的InnerXML属性中获取选项卡名称的。

OpenXmlElementList list = sheets.ChildElements;

但是,我注意到当我在电子表格中循环时,它并没有按正确的顺序获取选项卡的名称。


http://msdn.microsoft.com/en-us/library/bb507946.aspx - Tim Williams
如果我只想提取标签名称,那就可以很好地工作。通过解析内部/外部xml,我可以做到这一点。但是我想在自己的For循环中完成它。在SheetData级别时,我无法访问Sheet,这让我很困扰。 - Kwalke001
你无法使用“worksheet”对象来获取它的名称吗?该页面上的最后一个代码示例显示了如何循环遍历工作表的属性:可以推测出工作表名称是其中之一(尽管我自己没有经验)。 - Tim Williams
工作表级别有一个“名称”属性,但它不包含选项卡名称。我只是按照以下方式进行操作:OpenXmlElementList列表= sheets.ChildElements; foreach(OpenXmlElement elm in list){string xml = elm.OuterXml;} - Kwalke001
4个回答

47

这里有一个方便的辅助方法,可用于获取与WorksheetPart相对应的Sheet:

public static Sheet GetSheetFromWorkSheet
    (WorkbookPart workbookPart, WorksheetPart worksheetPart)
{
    string relationshipId = workbookPart.GetIdOfPart(worksheetPart);
    IEnumerable<Sheet> sheets = workbookPart.Workbook.Sheets.Elements<Sheet>();
    return sheets.FirstOrDefault(s => s.Id.HasValue && s.Id.Value == relationshipId);
}

你可以从工作表的名称属性中获取名称:

Sheet sheet = GetSheetFromWorkSheet(myWorkbookPart, myWorksheetPart);
string sheetName = sheet.Name;

这将是 OP 所指的“标签名”。


为记录,相反的方法看起来像:

public static Worksheet GetWorkSheetFromSheet(WorkbookPart workbookPart, Sheet sheet)
{
    var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id);
    return worksheetPart.Worksheet;
}

...有了这个,我们还可以添加以下方法:

public static IEnumerable<KeyValuePair<string, Worksheet>> GetNamedWorksheets
    (WorkbookPart workbookPart)
{
    return workbookPart.Workbook.Sheets.Elements<Sheet>()
        .Select(sheet => new KeyValuePair<string, Worksheet>
            (sheet.Name, GetWorkSheetFromSheet(workbookPart, sheet)));
}

现在,您可以轻松地枚举所有工作表及其名称。如果您更喜欢基于名称的查找,请将所有内容放入字典中:
IDictionary<string, WorkSheet> wsDict = GetNamedWorksheets(myWorkbookPart)
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

...或者如果你只想要一个特定的工作表,可以通过名称进行选择:

public static Sheet GetSheetFromName(WorkbookPart workbookPart, string sheetName)
{
    return workbookPart.Workbook.Sheets.Elements<Sheet>()
        .FirstOrDefault(s => s.Name.HasValue && s.Name.Value == sheetName);
}

然后调用 GetWorkSheetFromSheet 方法来获取对应的工作表。


20

工作表名称存储在WorkbookPart中的Sheets元素中,该元素具有Sheet元素的子元素,每个Sheet元素对应于Excel文件中的一个工作表。你所要做的就是从那个Sheets元素中获取正确的索引,那就是你在循环中使用的Sheet。我添加了下面的代码片段来实现你想要的功能。

int sheetIndex = 0;
foreach (WorksheetPart worksheetpart in workbook.WorkbookPart.WorksheetParts)
{                     
    Worksheet worksheet = worksheetpart.Worksheet;

    // Grab the sheet name each time through your loop
    string sheetName = workbookPart.Workbook.Descendants<Sheet>().ElementAt(sheetIndex).Name;

    foreach (SheetData sheetData in worksheet.Elements<SheetData>())
    {

       ...
    }
    sheetIndex++;
}

8
根据我的经验,使用你的代码片段,sheetName 读取的工作表名称顺序是正确的(就像它们在文件中的顺序一样),但是 sheetData 不会按照 Excel 文件中的顺序读取。因此,代码会导致所有工作表的名称混乱。 - Skull
3
似乎问题出在"excel_file.xlsx\xl_rels\workbook.xml.rels"文件中,该文件以某种随机顺序存储了电子表格名称和对电子表格内容的引用。如果您手动将它们从1到N重新排序(如Id="rld1", Id="rl d2", ..., Id="rldN"),则读取文件后电子表格名称将与其内容对齐。但不知道该如何在代码中处理这个问题。 - Skull
9
用户@Skull指出,这个解决方案是错误的!需要查看的正确值是relationshipId,它不会每次都与元素索引重合 - 这只是巧合和运气。 (请参阅我的答案以了解如何检索和使用relationshipId。) - AnorZaken
以上所有评论都是正确的,这个解决方案是不正确的。 - h2nghia

6
    Using spreadsheetDocument As SpreadsheetDocument = spreadsheetDocument.Open("D:\Libro1.xlsx", True)

        Dim workbookPart As WorkbookPart = spreadsheetDocument.WorkbookPart

        workbookPart.Workbook.Descendants(Of Sheet)()



        Dim worksheetPart As WorksheetPart = workbookPart.WorksheetParts.Last
        Dim text As String



        For Each Sheet As Sheet In spreadsheetDocument.WorkbookPart.Workbook.Sheets
            Dim sName As String = Sheet.Name
            Dim sID As String = Sheet.Id

            Dim part As WorksheetPart = workbookPart.GetPartById(sID)
            Dim actualSheet As Worksheet = part.Worksheet

            Dim sheetData As SheetData = part.Worksheet.Elements(Of SheetData)().First

            For Each r As Row In sheetData.Elements(Of Row)()
                For Each c As Cell In r.Elements(Of Cell)()
                    text = c.CellValue.Text
                    Console.Write(text & " ")
                Next
            Next
        Next

    End Using

    Console.Read()

2
如果您能在代码之外添加一些关于您的方法为什么有效的讨论,那将是非常好的。 - ASGM
这种方式对我来说更有意义,因为你是通过名称搜索表格,获取该表格的ID,然后根据名称获取工作表(使用linq意味着你根本不需要循环)。被标记为答案的回复使用了需要循环查找所需内容的索引。 - wavydavy

0
worksheet.GetAttribute("name","").Value

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接