2011-01-18 20 views
37

Tôi đang đọc tệp .xlsx bằng cách sử dụng Office Open XML SDK và bị nhầm lẫn về việc đọc giá trị Ngày/Giờ. Một trong các bảng tính của tôi có đánh dấu này (được tạo bởi Excel 2010)Điều gì cho biết một ô văn phòng mở XML có chứa một giá trị ngày/thời gian?

<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> 

Di động J2 có giá trị chuỗi ngày tháng và thuộc tính kiểu s="9". Tuy nhiên, Office Open XML Specification nói rằng 9 tương ứng với một siêu liên kết được theo sau. Đây là ảnh chụp màn hình từ trang 4.999 của ECMA-376, Ấn bản thứ hai, Phần 1 - Nguyên tắc cơ bản và tham chiếu ngôn ngữ đánh dấu.pdf.

alt text

File presetCellStyles.xml kèm spec cũng đề cập đến builtinId 9 như một siêu liên kết sau.

<followedHyperlink builtinId="9"> 

Tất cả các kiểu trong spec chỉ là kiểu định dạng trực quan, chứ không phải kiểu số. Các kiểu số được định nghĩa ở đâu và cách thức phân biệt một tham chiếu kiểu dáng s="9" để chỉ ra kiểu định dạng ô (hình ảnh) so với kiểu số?

Rõ ràng là tôi đang tìm kiếm địa điểm sai để khớp các kiểu trên các ô với định dạng số của chúng. Đâu là nơi thích hợp để tìm thông tin này?

Trả lời

47

Thuộc tính s tham chiếu mục nhập xf kiểu trong tệp styles.xml. Phong cách xf lần lượt tham chiếu một mặt nạ định dạng số. Để xác định một ô chứa ngày tháng, bạn cần thực hiện tra cứu kiểu xf -> numberformat, sau đó xác định xem mặt nạ định dạng số đó có phải là mặt nạ định dạng số ngày/giờ (thay vì, ví dụ, phần trăm hoặc mặt nạ định dạng số kế toán).

File style.xml có các yếu tố như:

<xf numFmtId="14" ... applyNumberFormat="1" /> 
<xf numFmtId="1" ... applyNumberFormat="1" /> 

Đây là những mục xf, do đó cung cấp cho bạn một numFmtId tham chiếu đến mặt nạ định dạng số.

Bạn nên tìm ra numFmts phần đâu đó gần đỉnh của style.xml, như là một phần của phần tử 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 định dạng số có thể có mặt ở đây, hoặc nó có thể là một trong những built-in định dạng. Mã định dạng số (numFmtId) nhỏ hơn 164 là "tích hợp sẵn".

Danh sách mà tôi có là không đầy đủ:

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# ??/??'; 

Các giá trị còn thiếu chủ yếu có liên quan đến định dạng biến thể Đông Á.

+0

Cảm ơn! Rất chi tiết, chính xác những gì tôi cần. Bạn lấy danh sách 'numFmtId' được tích hợp sẵn ở đâu? Là danh sách đầy đủ trong spec ở đâu đó? Ở đâu khác? –

+2

Danh sách đầy đủ các định dạng số dựng sẵn có thể được tìm thấy trong phần 4 của các tài liệu Tiêu chuẩn Định dạng Tệp Mở XML của Văn phòng Ecma (http://www.ecma-international.org/news/TC45_current_work/TC45_available_docs.htm) cho các phần OpenXML 3,8,30 và 3,8.31 (trang 2127 đến 2143) –

+0

cảm ơn một lần nữa. Tôi tìm thấy danh sách trong phần _ECMA-376, Second Edition, Phần 1 - Nguyên tắc cơ bản và tham khảo ngôn ngữ đánh dấu phần 18.8.30 trang 1964. –

1

Trong styles.xml, xem có nút numFmt hay không. Tôi nghĩ rằng sẽ giữ một numFmtId của "9" mà sẽ liên quan đến định dạng ngày được sử dụng.

Tôi không biết vị trí đó trong ECMA, nhưng nếu bạn tìm kiếm numFmt, bạn có thể tìm thấy nó.

+0

s = "9" dùng để chỉ xfId, không phải là numFmtId –

-1

Tôi không rõ ràng cách xác định một ô có giá trị ngày/giờ không. Sau khi dành thời gian thử nghiệm, tôi đã tìm ra mã (see post) để tìm cả định dạng ngày/giờ được cài sẵn và tùy chỉnh.

6

Câu trả lời được chọn là điểm, nhưng lưu ý rằng Excel định nghĩa một số mã định dạng số (numFmt) khác với thông số kỹ thuật OpenXML. Theo tài liệu của Open XML SDK 2.5 Công cụ năng suất (trên tab "Ghi chú triển khai" cho lớp NumberingFormat):

Chuẩn xác định định dạng ID có sẵn: "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 định nghĩa sẵn trong định dạng ID
14: "m/d/yyyy"
22: "m/d/yyyy h: mm"
37: "#, ## 0 _); (#, ## 0) "
38:" #, ## 0 _); [Đỏ] "
39:" #, ## 0.00 _); (#, ## 0.00) "
40:" # , ## 0.00 _); [Red]"
47: "mm: ss.0"
55: "yyyy/mm/dd"

Hầu hết là các biến thể nhỏ, nhưng # 14 là một doozy. Tôi đã lãng phí một vài giờ khắc phục sự cố tại sao các số 0 đứng đầu không được thêm vào các chữ số một tháng và ngày (ví dụ: 01/05/14 so với 1/5/14).

3

Nghĩ rằng tôi sẽ thêm giải pháp của mình mà tôi đã kết hợp lại để xác định xem giá trị kép FromOADate thực sự là ngày hay không. Lý do là tôi có một mã zip trong tập tin excel của tôi là tốt. numberingFormat sẽ trống nếu là văn bản.

Hoặc bạn có thể sử dụng numberingFormatId và kiểm tra danh sách Ids mà Excel sử dụng cho các ngày.

Trong trường hợp của tôi, tôi đã xác định rõ ràng định dạng của tất cả các trường cho ứng dụng khách.

/// <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; 
    } 
+0

Tôi nhận được NumberFormatId = 14 và không có mục nào trong danh sách có NumberingFormat.Id == 14 –

0

Trong trường hợp bất cứ ai khác đang gặp khó khăn với điều này, đây là những gì tôi đã thực hiện:

1) Tạo mới file excel và đặt trong một chuỗi thời gian ngày trong ô A1

2) Thay đổi định dạng trên ô thành bất kỳ thứ gì bạn muốn, sau đó lưu tệp.

3) Run sau kịch bản PowerShell để trích xuất ra các kiểu từ .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 bây giờ chứa các thông tin mà bạn có thể tiêm để DocumentFormat.OpenXml.Spreadsheet.Stylesheet(string outerXml), dẫn đến

4) Sử dụng các tập tin giải nén xây dựng mô hình đối tượng excel

var style = File.ReadAllText(@"c:\PATH\TO\EXTRACTED\Style.xml"); 
var stylesheetPart = WorkbookPart_REFERENCE.AddNewPart<WorkbookStylesPart>(); 
stylesheetPart.Stylesheet = new Stylesheet(style); 
stylesheetPart.Stylesheet.Save(); 
Các vấn đề liên quan