通过 OpenXml SDK 的 XLSX 文件有效和无效

2024-04-21

我有一个程序将 System.Data.DataTable 导出到 XLSX / OpenXml 电子表格。最后让它大部分工作。但是,当在 Excel 中打开电子表格时,Excel 抱怨文件无效,需要修复,并给出此消息...

我们发现 中的某些内容存在问题。你想要我们吗 尝试尽可能地恢复?如果您信任该消息的来源 工作簿,单击“是”。

如果我单击“是”,则会返回此消息...

单击日志文件并打开它,仅显示此...

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <recoveryLog xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
        <logFileName>error268360_01.xml</logFileName>
        <summary>Errors were detected in file 'C:\Users\aabdi\AppData\Local\Temp\data.20190814.152538.xlsx'</summary>
        <repairedRecords>
            <repairedRecord>Repaired Records: Cell information from /xl/worksheets/sheet1.xml part</repairedRecord>
        </repairedRecords>
    </recoveryLog> 

显然,我们不想将其部署到这样的生产环境中。所以我一直在想办法解决这个问题。我整理了一个快速的小样本来验证 XML 并显示错误,基于此链接来自 MSDN https://blogs.msdn.microsoft.com/ericwhite/2010/03/04/validate-open-xml-documents-using-the-open-xml-sdk-2-0/。但是,当我运行该程序并加载 Excel 所抱怨的完全相同的 XLSX 文档时,验证器返回说该文件完全有效。所以我不知道从那里还能去哪里。

有没有更好的工具来尝试验证我的 XLSX XML?以下是我用来生成 XLSX 文件的完整代码。 (是的,它是在 VB.NET 中,它是一个遗留应用程序。)

如果我注释掉中的行For Each dr As DataRow循环,然后 XLSX 文件在 Excel 中可以正常打开(只是没有任何数据)。所以这与单个细胞有关,但我并没有对它们做太多事情。设置一个值和数据类型,就是这样。

我也尝试更换For Each循环进入ConstructDataRow具有以下内容,但它仍然输出相同的“坏”XML...

        rv.Append(
            (From dc In dr.Table.Columns
             Select ConstructCell(
                 NVL(dr(dc.Ordinal), String.Empty),
                 MapSystemTypeToCellType(dc.DataType)
             )
            ).ToArray()
        )

还尝试将呼叫替换为Append with AppendChild对于每个细胞也是如此,但这也没有帮助。

压缩后的 XLSX 文件(错误,带有虚拟数据)可在此处找到:
https://drive.google.com/open?id=1KVVWEqH7VHMxwbRA-Pn807SXHZ32oJWR https://drive.google.com/open?id=1KVVWEqH7VHMxwbRA-Pn807SXHZ32oJWR

完整数据表到 Excel XLSX 代码


    #Region " ToExcel "
    <Extension>
    Public Function ToExcel(ByVal target As DataTable) As Attachment
        Dim filename = Path.GetTempFileName()
        Using doc As SpreadsheetDocument = SpreadsheetDocument.Create(filename, DocumentFormat.OpenXml.SpreadsheetDocumentType.Workbook)
            Dim data = New SheetData()

            Dim wbp = doc.AddWorkbookPart()
            wbp.Workbook = New Workbook()
            Dim wsp = wbp.AddNewPart(Of WorksheetPart)()
            wsp.Worksheet = New Worksheet(data)

            Dim sheets = wbp.Workbook.AppendChild(New Sheets())
            Dim sheet = New Sheet() With {.Id = wbp.GetIdOfPart(wsp), .SheetId = 1, .Name = "Data"}
            sheets.Append(sheet)

            data.AppendChild(ConstructHeaderRow(target))
            For Each dr As DataRow In target.Rows
                data.AppendChild(ConstructDataRow(dr)) '// THIS LINE YIELDS THE BAD PARTS
            Next

            wbp.Workbook.Save()
        End Using

        Dim attachmentname As String = Path.Combine(Path.GetDirectoryName(filename), $"data.{Now.ToString("yyyyMMdd.HHmmss")}.xlsx")
        File.Move(filename, attachmentname)
        Return New Attachment(attachmentname, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    End Function

    Private Function ConstructHeaderRow(dt As DataTable) As Row
        Dim rv = New Row()
        For Each dc As DataColumn In dt.Columns
            rv.Append(ConstructCell(dc.ColumnName, CellValues.String))
        Next
        Return rv
    End Function

    Private Function ConstructDataRow(dr As DataRow) As Row
        Dim rv = New Row()
        For Each dc As DataColumn In dr.Table.Columns
            rv.Append(ConstructCell(NVL(dr(dc.Ordinal), String.Empty), MapSystemTypeToCellType(dc.DataType)))
        Next
        Return rv
    End Function

    Private Function ConstructCell(value As String, datatype As CellValues) As Cell
        Return New Cell() With {
        .CellValue = New CellValue(value),
        .DataType = datatype
        }
    End Function

    Private Function MapSystemTypeToCellType(t As System.Type) As CellValues
        Dim rv As CellValues
        Select Case True
            Case t Is GetType(String)
                rv = CellValues.String
            Case t Is GetType(Date)
                rv = CellValues.Date
            Case t Is GetType(Boolean)
                rv = CellValues.Boolean
            Case IsNumericType(t)
                rv = CellValues.Number
            Case Else
                rv = CellValues.String
        End Select

        Return rv
    End Function
    #End Region

对于其他进来并发现这个的人来说,我终于找到了这个Cell.DataType

设置值为CellValues.Date会导致 Excel 想要“修复”文档。 (显然对于日期,数据类型应该为 NULL https://learn.microsoft.com/en-us/office/open-xml/how-to-retrieve-the-values-of-cells-in-a-spreadsheet, and Date仅在 Office 2010 中使用)。

另外,如果您指定的 DataType 为CellValues.Boolean,那么 CellValue 需要为 0 或 1。“true”/“false”也会导致 Excel 想要“修复”您的电子表格。

此外,微软已经构建了一个更好的验证器工具,可以在此处下载:
https://www.microsoft.com/en-us/download/details.aspx?id=30425 https://www.microsoft.com/en-us/download/details.aspx?id=30425

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

通过 OpenXml SDK 的 XLSX 文件有效和无效 的相关文章

随机推荐