我有一个程序将 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