web-dev-qa-db-fra.com

Qu'est-ce qui indique qu'une cellule Office Open XML contient une valeur Date/Time?

Je lis un fichier .xlsx à l'aide du Kit de développement Office Open XML et suis confus quant à la lecture des valeurs Date/Heure. Un de mes tableurs a ce balisage (généré par 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>

La cellule J2 a une valeur de série de date et un attribut de style s="9". Toutefois, la spécification Office Open XML indique que 9 correspond à un lien hypertexte suivi. Ceci est une capture d'écran de la page 4 999 de ECMA-376, Deuxième édition, partie 1 - Principes de base et références du langage de balisage.pdf.

alt text

Le fichier presetCellStyles.xml inclus dans la spécification fait également référence à builtinId 9 en tant que lien hypertexte suivi.

<followedHyperlink builtinId="9">

Tous les styles de la spécification sont simplement des styles de formatage visuels, pas des styles de nombre. Où les styles de nombre sont-ils définis et comment différencier une référence de style s="9" de l'indication d'un style de mise en forme de cellule (visuel) par rapport à un style de nombre?

Évidemment, je cherche au mauvais endroit pour faire correspondre les styles sur les cellules avec leurs formats numériques. Où est le bon endroit pour trouver cette information?

41
Samuel Neff

L'attribut s fait référence à une entrée style xf dans styles.xml. Le style xf référence à son tour un masque de format numérique. Pour identifier une cellule contenant une date, vous devez effectuer le style xf -> numberformat lookup, puis déterminer si ce masque numberformat est un masque date/heure numberformat (plutôt qu'un pourcentage ou un masque comptabilité formform).

Le fichier style.xml contient des éléments tels que:

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

Ce sont les entrées xf, qui vous donnent à leur tour un numFmtId qui référence le masque de format numérique.

Vous devriez trouver la section numFmts quelque part près du sommet de style.xml, en tant que partie de l'élément 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>

L'identifiant du format de numéro peut être ici ou l'un des formats intégrés. Les codes de format de nombre (numFmtId) inférieurs à 164 sont "intégrés".

La liste que j'ai est incomplète:

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

Les valeurs manquantes sont principalement liées aux formats de variantes est-asiatiques.

50
Mark Baker

La réponse choisie est précise, mais notez qu'Excel définit certains codes de format de nombre (numFmt) différemment de la spécification OpenXML. Selon la documentation de l'outil de développement Kit de développement Open XML SDK 2.5 (dans l'onglet "Implementer Notes" de la classe NumberingFormat):

La norme définit l'ID de format intégré 14: "mm-jj-aa"; 22: "m/j/aa h: mm"; 37: "#, ## 0; (#, ## 0)"; 38: "#, ## 0; [rouge]"; 39: "#, ## 0.00; (#, ## 0.00)"; 40: "#, ## 0.00; [rouge]"; 47: "mmss.0"; KOR fmt 55: "aaaa-mm-jj".

Excel définit l'ID de format intégré
14: "m/j/aaaa"
22: "m/j/aaaa h: mm"
37: "#, ## 0 _); (#, ## 0)"
38: "#, ## 0 _); [rouge]"
39: "#, ## 0.00 _); (#, ## 0.00)"
40: "#, ## 0.00 _); [rouge]"
47: "mm: ss.0"
55: "aaaa/mm/jj"

La plupart sont des variations mineures, mais n ° 14 est un doozy. J'ai perdu quelques heures à comprendre pourquoi les zéros ne sont pas ajoutés à des mois et des jours à un chiffre (par exemple, le 01/05/14 par rapport au 1/5/14).

6
softwaredev

Je pensais ajouter la solution que j’ai élaborée pour déterminer si la double valeur FromOADate est vraiment une date ou non. La raison en est que j'ai aussi un code postal dans mon fichier Excel. La numberingFormat sera nulle si c'est du texte.

Vous pouvez également utiliser la variable numberingFormatId et la comparer à une liste de Ids utilisée par Excel pour les dates.

Dans mon cas, j'ai explicitement déterminé le formatage de tous les champs pour le client.

    /// <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;
    }
4
Rob Scott

Dans styles.xml, voyez s'il existe un nœud numFmt. Je pense que cela tiendra un numFmtId de "9" qui se rapportera au format de date utilisé.

Je ne sais pas où cela se trouve dans l'ECMA, mais si vous recherchez numFmt, vous le trouverez peut-être.

1
Dick Kusleika

Au cas où quelqu'un d'autre aurait des difficultés avec cela, voici ce que j'ai fait:

1) Créez un nouveau fichier Excel et insérez une chaîne de date/heure dans la cellule A1.

2) Modifiez le formatage de la cellule en choisissant ce que vous voulez, puis enregistrez le fichier.

3) Exécutez le script powershell suivant pour extraire la feuille de style à partir de .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 contient maintenant les informations que vous pouvez injecter à DocumentFormat.OpenXml.Spreadsheet.Stylesheet(string outerXml), ce qui conduit à

4) Utilisez le fichier extrait pour construire un modèle objet 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();
0
Frison Alexander