Sam*_*eff 42 excel openxml openxml-sdk
我正在使用Office Open XML SDK读取.xlsx文件,并对阅读日期/时间值感到困惑.我的一个电子表格有此标记(由Excel 2010生成)
<x:row r="2" spans="1:22" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<x:c r="A2" t="s">
<x:v>56</x:v>
</x:c>
<x:c r="B2" t="s">
<x:v>64</x:v>
</x:c>
.
.
.
<x:c r="J2" s="9">
<x:v>17145</x:v>
</x:c>
Run Code Online (Sandbox Code Playgroud)
单元格J2具有日期序列值和样式属性s="9"
.但是,Office Open XML规范称9对应于后面的超链接.这是ECMA-376第4版第499页第1部分 - 基础和标记语言参考 .pdf的屏幕截图.
规范中包含的presetCellStyles.xml文件也将builtinId
9称为后续超链接.
<followedHyperlink builtinId="9">
Run Code Online (Sandbox Code Playgroud)
规范中的所有样式都只是可视化格式样式,而不是数字样式.定义的数字样式在哪里?如何区分样式引用s="9"
与指示单元格格式(可视)样式与数字样式?
显然,我正在寻找错误的地方,以匹配单元格上的样式与其数字格式.哪里是找到这些信息的合适地点?
Mar*_*ker 52
s属性引用styles.xml中的样式xf条目.样式xf又引用数字格式掩码.要识别包含日期的单元格,您需要执行样式xf - >数字格式查找,然后确定该数字格式掩码是否是日期/时间数字格式掩码(而不是例如百分比或会计数字格式掩码).
style.xml文件包含以下元素:
<xf numFmtId="14" ... applyNumberFormat="1" />
<xf numFmtId="1" ... applyNumberFormat="1" />
Run Code Online (Sandbox Code Playgroud)
这些是xf条目,它们又为您提供引用数字格式掩码的numFmtId.
您应该在style.xml顶部附近的某处找到numFmts部分,作为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>
Run Code Online (Sandbox Code Playgroud)
数字格式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# ??/??';
Run Code Online (Sandbox Code Playgroud)
缺失值主要与东亚变体格式有关.
选择的答案是定点,但请注意Excel定义了一些数字格式(numFmt)代码与OpenXML规范不同.根据Open XML SDK 2.5 Productivity Tool的文档(在NumberingFormat类的"Implementer Notes"选项卡上):
该标准定义了内置格式ID 14:"mm-dd-yy"; 22:"m/d/yy h:mm"; 37:"#,## 0;(#,## 0)"; 38:"#,## 0; [红色]"; 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是一个doozy.我浪费了几个小时来解决为什么前导零没有被添加到单位数月和日(例如01/05/14对1/5/14).
以为我会添加我已经确定的double值FromOADate
是否真的是日期的解决方案。原因是我的excel文件中也有一个邮政编码。的numberingFormat
,如果它是文本将是无效的。
或者,您可以使用numberingFormatId
和检查Ids
Excel用于日期的列表。
就我而言,我已经明确确定了客户端所有字段的格式。
/// <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;
}
Run Code Online (Sandbox Code Playgroud)