2011-01-18 12 views
37

Czytam plik .xlsx przy użyciu Office Open XML SDK i jestem zdezorientowany czytając wartości Date/Time. Jeden z moich arkuszy kalkulacyjnych ma ten znaczników (generowanych przez program Excel 2010)Co oznacza, że ​​komórka Office Open XML zawiera wartość daty/czasu?

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

komórkowych J2 ma wartość seryjnego datę w nim i atrybut stylu s="9". Jednak specyfikacja Office Open XML mówi, że 9 odpowiada hiperłączu. To jest zrzut ekranu ze strony 4 999 z ECMA-376, wydanie drugie, część 1 - Podstawy i język znaczników Reference.pdf.

alt text

Plik presetCellStyles.xml dołączone specyfikacja dotyczy również builtinId 9 jako następnie hiperlink.

<followedHyperlink builtinId="9"> 

Wszystkie style w specyfikacji są po prostu stylami formatowania wizualnego, a nie stylami liczb. Gdzie są zdefiniowane style numerów i jak odróżnić odwołanie do stylu s="9" od wskazania stylu formatowania komórki (wizualnego) w stosunku do stylu liczbowego?

Oczywiście szukam w niewłaściwym miejscu, aby dopasować style do komórek za pomocą ich formatów liczbowych. Gdzie można znaleźć te informacje?

Odpowiedz

47

Atrybut s odwołuje się do wpisu stylu xf w pliku styles.xml. Styl xf z kolei odwołuje się do maski formatu liczb. Aby zidentyfikować komórkę zawierającą datę, należy wykonać styl xf -> wyszukiwanie formatu liczbowego, a następnie określić, czy ta maska ​​liczbowa ma format liczbowy daty/czasu (a nie na przykład wartość procentowa lub maska ​​liczbowa).

Plik style.xml zawiera elementy, takie jak:

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

Są to wpisy XF, co z kolei daje numFmtId odwołujący maskę formatu numer.

Należy znaleźć sekcję numFmts gdzieś w górnej części style.xml, jako część elementu 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 format numeru może być tutaj, czy może to być jeden z wbudowanych formaty. Kody formatu liczb (numFmtId) mniejsze niż 164 są "wbudowane".

Lista który mam jest niekompletny:

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

Brakujące wartości związane są głównie z Azji Wschodniej formatów wariantowych.

+0

Dzięki! Bardzo szczegółowe, dokładnie to, czego potrzebowałem. Skąd masz niekompletną wbudowaną listę 'numFmtId'? Czy gdzieś znajduje się pełna lista w specyfikacji? Gdzieś indziej? –

+2

Pełna lista wbudowanych formatów liczbowych znajduje się w części 4 dokumentów standardowych formatów plików ECMA Office Open XML (http://www.ecma-international.org/news/TC45_current_work/TC45_available_docs.htm) dla sekcji OpenXML 3.8.30 i 3.8.31 (strony 2127 do 2143) –

+0

jeszcze raz dziękuję. Listę znalazłem w _ECMA-376, wydanie drugie, część 1 - Podstawy i język znaczników Reference_ section 18.8.30 strona 1964. –

1

W pliku styles.xml sprawdź, czy istnieje węzeł numFmt. Wydaje mi się, że będzie zawierać numFmtId "9", który będzie odnosił się do używanego formatu daty.

Nie wiem, gdzie to jest w ECMA, ale jeśli szukasz numFmt, możesz go znaleźć.

+0

s = "9" odnosi się do xfId, a nie numFmtId –

-1

Nie było dla mnie jasne, w jaki sposób wiarygodnie określić, czy komórka ma wartość daty/czasu. Po pewnym czasie eksperymentowania wpadłem na kod (see post), który wyszukałby zarówno wbudowane, jak i niestandardowe formaty daty/czasu.

6

Wybrana odpowiedź jest spot-on, ale należy pamiętać, że Excel definiuje niektóre kody formatu liczb (numFmt) inaczej niż specyfikacja OpenXML. Per dokumentacji Open XML SDK 2.5 Narzędzie propozycji Produktywności (w zakładce "Realizator programu Notes" dla klasy NumberingFormat):

Norma definiuje wbudowany w formacie ID 14: "MM-DD-YY"; 22: "m/d/rr h: mm"; 37: "#, ## 0; (#, ## 0)"; 38: "#, ## 0; [Red]"; 39: "#, ## 0.00; (#, ## 0.00)"; 40: "#, ## 0.00; [Red]"; 47: "mmss.0"; KOR fmt 55: "rrrr-mm-dd".

Excel określa wbudowany w formacie ID
14 "m/s, d/rok"
22 "m/s, d/rrrr H: MM"
37: „# ## 0 _) (#, ## 0) "
38:" #, ## 0 _); [Czerwony] "
39:" #, ## 0.00 _); (#, ## 0.00) "
40:" # ## 0,00 _) [Czerwony]”
47 "mm: ss.0"
55 "rrrr/mm/dd"

Większość to niewielkie odmiany, ale # 14 to doozy. Zmarnowałem kilka godzin na rozwiązywanie problemów, dlaczego wiodące zera nie były dodawane do jednocyfrowych miesięcy i dni (np. 01.05.14 w stosunku do 1/5/14).

3

Pomyślałem, że dodam rozwiązanie, które przygotowałem, aby ustalić, czy podwójna wartość FromOADate jest rzeczywiście datą czy nie. Powodem istnienia jest również kod pocztowy w moim pliku excel. numberingFormat będzie mieć wartość NULL, jeśli jest tekstem.

Można również użyć numberingFormatId i sprawdzić listę Ids, której program Excel używa dla dat.

W moim przypadku wyraźnie określiłem formatowanie wszystkich pól dla klienta.

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

Otrzymuję NumberFormatId = 14, i nie ma pozycji na liście z NumberingFormat.Id == 14 –

0

W przypadku ktokolwiek inny ma problemy ze czas z tym, oto co zrobiłem:

1) Utwórz nowy plik Excela i umieścić w ciąg czasu daty w komórce A1

2) Zmień formatowanie w komórce na cokolwiek chcesz, a następnie zapisz plik.

3) Uruchom następujący skrypt PowerShell do wyodrębnienia się stylów z .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 zawiera teraz informacje, które można wstrzyknąć do DocumentFormat.OpenXml.Spreadsheet.Stylesheet(string outerXml), co prowadzi do

4) Użyj wyodrębnionego pliku skonstruuj excela obiekt model

var style = File.ReadAllText(@"c:\PATH\TO\EXTRACTED\Style.xml"); 
var stylesheetPart = WorkbookPart_REFERENCE.AddNewPart<WorkbookStylesPart>(); 
stylesheetPart.Stylesheet = new Stylesheet(style); 
stylesheetPart.Stylesheet.Save(); 
Powiązane problemy