什么表示Office Open XML单元格包含date/时间值?
我正在使用Office Open XML SDK读取.xlsx文件,并对读取date/时间值感到困惑。 我的一个电子表格中有这个标记(由Excel 2010生成)
<x:row r="2" spans="1:22" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> <x:cr="A2" t="s"> <x:v>56</x:v> </x:c> <x:cr="B2" t="s"> <x:v>64</x:v> </x:c> . . . <x:cr="J2" s="9"> <x:v>17145</x:v> </x:c>
单元格J2中有一个date序列值和一个样式属性s="9"
。 但是,Office Open XML规范说,9对应于一个跟随的超链接。 这是ECMA-376第2版第1部分 – 基础和标记语言参考 .pdf第4,999页的屏幕截图。
规范中包含的presetCellStyles.xml文件也称为builtinId
9,作为后续超链接。
<followedHyperlink builtinId="9">
规范中的所有样式都是简单的可视化格式样式,而不是数字样式。 数字样式在哪里定义,以及如何区分样式引用s="9"
来指示单元格格式(视觉)样式还是数字样式?
很明显,我正在寻找错误的地方来匹配单元格上的样式和数字格式。 哪里可以find这些信息?
s属性在styles.xml中引用style xf条目。 xf样式依次引用数字格式掩码。 要标识包含date的单元格,您需要执行样式xf – > numberformat查找,然后确定该numberformat掩码是否是date/时间numberformat掩码(而不是例如百分比或会计号码格式掩码)。
style.xml文件具有如下元素:
<xf numFmtId="14" ... applyNumberFormat="1" /> <xf numFmtId="1" ... applyNumberFormat="1" />
这些是xf条目,这反过来给你一个引用数字格式掩码的numFmtId。
您应该在style.xml顶部附近的某处findnumFmts部分,作为styleSheet元素的一部分
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> <numFmts count="3"> <numFmt numFmtId="164" formatCode="[$-414]mmmm\ yyyy;@" /> <numFmt numFmtId="165" formatCode="0.000" /> <numFmt numFmtId="166" formatCode="#,##0.000" /> </numFmts>
数字格式ID可能在这里,或者它可能是内置格式之一。 小于164的数字格式代码(numFmtId)是“内置的”。
我拥有的名单是不完整的:
0 = 'General'; 1 = '0'; 2 = '0.00'; 3 = '#,##0'; 4 = '#,##0.00'; 9 = '0%'; 10 = '0.00%'; 11 = '0.00E+00'; 12 = '# ?/?'; 13 = '# ??/??'; 14 = 'mm-dd-yy'; 15 = 'd-mmm-yy'; 16 = 'd-mmm'; 17 = 'mmm-yy'; 18 = 'h:mm AM/PM'; 19 = 'h:mm:ss AM/PM'; 20 = 'h:mm'; 21 = 'h:mm:ss'; 22 = 'm/d/yy h:mm'; 37 = '#,##0 ;(#,##0)'; 38 = '#,##0 ;[Red](#,##0)'; 39 = '#,##0.00;(#,##0.00)'; 40 = '#,##0.00;[Red](#,##0.00)'; 44 = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)'; 45 = 'mm:ss'; 46 = '[h]:mm:ss'; 47 = 'mmss.0'; 48 = '##0.0E+0'; 49 = '@'; 27 = '[$-404]e/m/d'; 30 = 'm/d/yy'; 36 = '[$-404]e/m/d'; 50 = '[$-404]e/m/d'; 57 = '[$-404]e/m/d'; 59 = 't0'; 60 = 't0.00'; 61 = 't#,##0'; 62 = 't#,##0.00'; 67 = 't0%'; 68 = 't0.00%'; 69 = 't# ?/?'; 70 = 't# ??/??';
缺失值主要与东亚变体格式有关。
所选的答案是专注的,但请注意,Excel定义了一些与OpenXML规范不同的数字格式(numFmt)代码。 根据Open XML SDK 2.5生产力工具的文档(在NumberingFormat类的“实现者注释”选项卡上):
该标准定义了内置格式ID 14:“mm-dd-yy”; 22:“m / d / yy h:mm”; 37:“#,## 0;(#,## 0)”; 38:“#,## 0; [Red]”; 39:“#,## 0.00;(#,## 0.00)”; 40:“#,## 0.00; [红色]”; 47:“mmss.0”; KOR fmt 55:“yyyy-mm-dd”。
Excel定义了内置的格式ID
14:“m / d / yyyy”
22:“m / d / yyyy h:mm”
37:“#,## 0 _);(#,## 0)”
38:“#,## 0 _); [红色]”
39:“#,## 0.00 _);(#,## 0.00)”
40:“#,## 0.00 _); [红色]”
47:“mm:ss.0”
55:“yyyy / mm / dd”
大多数都是微小的变化,但#14是一个愚蠢的。 我浪费了几个小时,解决了为什么前导零不会被添加到单位数月和数天(例如01/05/14与1/5/14)。
以为我会添加我已经放在一起的解决scheme,以确定双值FromOADate
是否真的是一个date。 原因是我在我的Excel文件中也有一个邮政编码。 如果是文本, numberingFormat
将为空。
或者,您可以使用numberingFormatId
并针对Excel用于date的Ids
列表进行检查。
在我的情况下,我已明确确定客户端的所有字段的格式。
/// <summary> /// Creates the datatable and parses the file into a datatable /// </summary> /// <param name="fileName">the file upload's filename</param> private void ReadAsDataTable(string fileName) { try { DataTable dt = new DataTable(); using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(string.Format("{0}/{1}", UploadPath, fileName), false)) { WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart; IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>(); string relationshipId = sheets.First().Id.Value; WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument.WorkbookPart.GetPartById(relationshipId); Worksheet workSheet = worksheetPart.Worksheet; SheetData sheetData = workSheet.GetFirstChild<SheetData>(); IEnumerable<Row> rows = sheetData.Descendants<Row>(); var cellFormats = workbookPart.WorkbookStylesPart.Stylesheet.CellFormats; var numberingFormats = workbookPart.WorkbookStylesPart.Stylesheet.NumberingFormats; // columns omitted for brevity // skip first row as this row is column header names foreach (Row row in rows.Skip(1)) { DataRow dataRow = dt.NewRow(); for (int i = 0; i < row.Descendants<Cell>().Count(); i++) { bool isDate = false; var styleIndex = (int)row.Descendants<Cell>().ElementAt(i).StyleIndex.Value; var cellFormat = (CellFormat)cellFormats.ElementAt(styleIndex); if (cellFormat.NumberFormatId != null) { var numberFormatId = cellFormat.NumberFormatId.Value; var numberingFormat = numberingFormats.Cast<NumberingFormat>() .SingleOrDefault(f => f.NumberFormatId.Value == numberFormatId); // Here's yer string! Example: $#,##0.00_);[Red]($#,##0.00) if (numberingFormat != null && numberingFormat.FormatCode.Value.Contains("mm/dd/yy")) { string formatString = numberingFormat.FormatCode.Value; isDate = true; } } // replace '-' with empty string string value = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i), isDate); dataRow[i] = value.Equals("-") ? string.Empty : value; } dt.Rows.Add(dataRow); } } this.InsertMembers(dt); dt.Clear(); } catch (Exception ex) { LogHelper.Error(typeof(MemberUploadApiController), ex.Message, ex); } } /// <summary> /// Reads the cell's value /// </summary> /// <param name="document">current document</param> /// <param name="cell">the cell to read</param> /// <returns>cell's value</returns> private string GetCellValue(SpreadsheetDocument document, Cell cell, bool isDate) { string value = string.Empty; try { SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart; value = cell.CellValue.InnerXml; if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString) { return stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText; } else { // check if this is a date or zip. // integers will be passed into this else statement as well. if (isDate) { value = DateTime.FromOADate(double.Parse(value)).ToString(); } return value; } } catch (Exception ex) { LogHelper.Error(typeof(MemberUploadApiController), ex.Message, ex); } return value; }
在styles.xml中查看是否有numFmt节点。 我认为这将保持与所使用的date格式相关的“9”的numFmtId。
我不知道ECMA在哪里,但是如果你searchnumFmt,你可能会find它。
如果其他人遇到困难,这是我所做的:
1)创build一个新的Excel文件,并在单元格A1中放入一个date时间string
2)改变单元格的格式,然后保存文件。
3)运行下面的PowerShell脚本从.xlxs中提取样式表
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml") $xlsx = (ls C:\PATH\TO\FILE.xlsx).FullName $package = [DocumentFormat.OpenXml.Packaging.SpreadsheetDocument]::Open($xlsx, $true) [xml]$style = $package.WorkbookPart.WorkbookStylesPart.Stylesheet.OuterXml Out-File -InputObject $style.OuterXml -FilePath "style.xml"
style.xml
现在包含可以注入到DocumentFormat.OpenXml.Spreadsheet.Stylesheet(string outerXml)
,从而导致
4)使用提取的文件构buildexcel对象模型
var style = File.ReadAllText(@"c:\PATH\TO\EXTRACTED\Style.xml"); var stylesheetPart = WorkbookPart_REFERENCE.AddNewPart<WorkbookStylesPart>(); stylesheetPart.Stylesheet = new Stylesheet(style); stylesheetPart.Stylesheet.Save();
我不清楚如何可靠地确定一个单元格是否具有date/时间值。 花了一些时间试验后,我想出了代码(见后) ,将寻找内置和自定义date/时间格式。