2011-05-17 64 views
12

Estoy tratando de crear un archivo de Excel en formato xlsx usando OpenXML porque necesito usarlo en un servidor web.OpenXml y formato de fecha en la celda de Excel

No tengo ningún problema para completar los valores en las hojas; sin embargo, estoy luchando por establecer el formato de fecha clásico en una celda.

Debajo de una prueba rápida usando DocumentFormat.OpenXml y referencias de WindowsBase.

class Program 
{ 
    static void Main(string[] args) 
    { 
     BuildExel(@"C:\test.xlsx"); 
    } 

    public static void BuildExel(string fileName) 
    { 
     using (SpreadsheetDocument myWorkbook = 
       SpreadsheetDocument.Create(fileName, 
       SpreadsheetDocumentType.Workbook)) 
     { 
      // Workbook Part 
      WorkbookPart workbookPart = myWorkbook.AddWorkbookPart(); 
      var worksheetPart = workbookPart.AddNewPart<WorksheetPart>(); 
      string relId = workbookPart.GetIdOfPart(worksheetPart); 

      // File Version 
      var fileVersion = new FileVersion { ApplicationName = "Microsoft Office Excel" }; 

      // Style Part 
      WorkbookStylesPart wbsp = workbookPart.AddNewPart<WorkbookStylesPart>(); 
      wbsp.Stylesheet = CreateStylesheet(); 
      wbsp.Stylesheet.Save(); 

      // Sheets 
      var sheets = new Sheets(); 
      var sheet = new Sheet { Name = "sheetName", SheetId = 1, Id = relId }; 
      sheets.Append(sheet); 

      // Data 
      SheetData sheetData = new SheetData(CreateSheetData1()); 

      // Add the parts to the workbook and save 
      var workbook = new Workbook(); 
      workbook.Append(fileVersion); 
      workbook.Append(sheets); 
      var worksheet = new Worksheet(); 
      worksheet.Append(sheetData); 
      worksheetPart.Worksheet = worksheet; 
      worksheetPart.Worksheet.Save(); 
      myWorkbook.WorkbookPart.Workbook = workbook; 
      myWorkbook.WorkbookPart.Workbook.Save(); 
      myWorkbook.Close(); 
     } 
    } 

    private static Stylesheet CreateStylesheet() 
    { 
     Stylesheet ss = new Stylesheet(); 

     var nfs = new NumberingFormats(); 
     var nformatDateTime = new NumberingFormat 
     { 
      NumberFormatId = UInt32Value.FromUInt32(1), 
      FormatCode = StringValue.FromString("dd/mm/yyyy") 
     }; 
     nfs.Append(nformatDateTime); 
     ss.Append(nfs); 

     return ss; 
    } 

    private static List<OpenXmlElement> CreateSheetData1() 
    { 
     List<OpenXmlElement> elements = new List<OpenXmlElement>(); 

     var row = new Row(); 

     // Line 1 
     Cell[] cells = new Cell[2]; 

     Cell cell1 = new Cell(); 
     cell1.DataType = CellValues.InlineString; 
     cell1.InlineString = new InlineString { Text = new Text { Text = "Daniel" } }; 
     cells[0] = cell1; 

     Cell cell2 = new Cell(); 
     cell2.DataType = CellValues.Number; 
     cell2.CellValue = new CellValue((50.5).ToString()); 
     cells[1] = cell2; 

     row.Append(cells); 
     elements.Add(row); 

     // Line 2 
     row = new Row(); 
     cells = new Cell[1]; 
     Cell cell3 = new Cell(); 
     cell3.DataType = CellValues.Date; 
     cell3.CellValue = new CellValue(DateTime.Now.ToOADate().ToString()); 
     cell3.StyleIndex = 1; // <= here I try to apply the style... 
     cells[0] = cell3; 

     row.Append(cells); 
     elements.Add(row); 

     return elements; 
    } 

El código ejecutado crea el documento de Excel. Sin embargo, cuando intento abrir el documento, recibo este mensaje: "Excel encontró contenido ilegible en 'test.xlsx'. ¿Desea recuperar el contenido de este libro de trabajo? Si confía en el origen de este libro, haga clic en Sí “

Si quito la fila:.

cell3.StyleIndex = 1; 

puedo abrir el documento, pero la fecha si no se formatea, sólo aparece el número de la fecha .

Gracias por su ayuda para dar formato a la fecha.

Respuesta

4

Este blog me ayudó: http://polymathprogrammer.com/2009/11/09/how-to-create-stylesheet-in-excel-open-xml/

Mi problema era que yo quería añadir NumberingFormats a la hoja de estilo en lugar de agregar una nueva hoja de estilo por completo.Si quieres de eso, usar

Stylesheet.InsertAt<NumberingFormats>(new NumberingFormats(), 0); 

en lugar de

Stylesheet.AppendChild<NumberingFormats>(new NumberingFormats(), 0); 

sorpresa, los recuentos de orden ..

1

Creo que su problema está en NumberFormatId. Incorporado en formatos numéricos están numeradas del 0 - 163. Los formatos personalizados deben comenzar en 164.

+0

Si cambio el valor de IdFormatoNumerico y la StyleIndex (CELL3) de 164, todavía tiene una error. ¿El StyleIndex debe tener el mismo valor que NumberingFormatId? – Dan

+0

@ daner06, no, StyleIndex hace referencia al índice de estilo de celda. –

+1

¿Hay alguna lista de estos valores NumberFormatId en alguna parte? –

1

Su respuesta se puede encontrar en What indicates an Office Open XML Cell contains a Date/Time value?

El truco es que el StyleIndex (s-atributo) de la célula es, literalmente, una indexe en la lista de estilos de celda (elementos XF) en la parte de estilos de su hoja de cálculo. Cada uno de ellos señalará los identificadores de formato de número predefinidos que Samuel menciona. Si recuerdo correctamente, el ID de formato de número que está buscando es 14 o 15.

+0

A menos que haya 14 o 15 dentro de , ¿dónde? > 14 o 15, recibirás este mensaje. Y si ese estilo de celda 14 ° o 15 ° no es igual al formato que está buscando, obtendrá resultados inesperados. applyNumberFormat = "1", y numFmtId = "X", y , donde X es un número> 163 debería ayudar. Nunca he tenido formato personalizado para trabajar sin agregarlo explícitamente al archivo xlsx. – TamusJRoyce

6

https://github.com/closedxml/closedxml es básicamente la respuesta correcta, creo.

+2

No es así si se trata de grandes cantidades de datos.Identificamos un problema de memoria al usar esta biblioteca con grandes conjuntos de datos. –

6

Otro voto GRANDE GRANDE para: https://github.com/closedxml/closedxml

Después de tratar de construir mi propia clase de las partes y piezas repartidas alrededor de la red, incluyendo StackOverflow, me encontré con la biblioteca mencionada anteriormente y en unos momentos tenido un completo y funcional Archivo Excel.

He pegado mi intento a continuación para la edificación de cualquier persona que sienta la necesidad de completarlo. Está parcialmente completo y tiene problemas con la fecha y la creación de la celda de cadena.

Antes de intentar utilizar esta clase, primero descargue closedXML y pruebe eso primero.

Considérese advertido.

/// <summary> 
    /// This class allows for the easy creation of a simple Excel document who's sole purpose is to contain some export data. 
    /// The document is created using OpenXML. 
    /// </summary> 
    internal class SimpleExcelDocument : IDisposable 
    { 
     SheetData sheetData; 

     /// <summary> 
     /// Constructor is nothing special because the work is done at export. 
     /// </summary> 
     internal SimpleExcelDocument() 
     { 
      sheetData = new SheetData(); 
     } 

     #region Get Cell Reference 
     public Cell GetCell(string fullAddress) 
     { 
      return sheetData.Descendants<Cell>().Where(c => c.CellReference == fullAddress).FirstOrDefault(); 
     } 
     public Cell GetCell(uint rowId, uint columnId, bool autoCreate) 
     { 
      return GetCell(getColumnName(columnId), rowId, autoCreate); 
     } 
     public Cell GetCell(string columnName, uint rowId, bool autoCreate) 
     { 
      return getCell(sheetData, columnName, rowId, autoCreate); 
     } 
     #endregion 

     #region Get Cell Contents 
     // See: http://msdn.microsoft.com/en-us/library/ff921204.aspx 
     // 
     #endregion 


     #region Set Cell Contents 
     public void SetValue(uint rowId, uint columnId, bool value) 
     { 
      Cell cell = GetCell(rowId, columnId, true); 
      cell.DataType = CellValues.Boolean; 
      cell.CellValue = new CellValue(BooleanValue.FromBoolean(value)); 
     } 
     public void SetValue(uint rowId, uint columnId, double value) 
     { 
      Cell cell = GetCell(rowId, columnId, true); 
      cell.DataType = CellValues.Number; 
      cell.CellValue = new CellValue(DoubleValue.FromDouble(value)); 
     } 
     public void SetValue(uint rowId, uint columnId, Int64 value) 
     { 
      Cell cell = GetCell(rowId, columnId, true); 
      cell.DataType = CellValues.Number; 
      cell.CellValue = new CellValue(IntegerValue.FromInt64(value)); 
     } 
     public void SetValue(uint rowId, uint columnId, DateTime value) 
     { 
      Cell cell = GetCell(rowId, columnId, true); 
      //cell.DataType = CellValues.Date; 
      cell.CellValue = new CellValue(value.ToOADate().ToString()); 
      cell.StyleIndex = 1; 
     } 
     public void SetValue(uint rowId, uint columnId, string value) 
     { 
      Cell cell = GetCell(rowId, columnId, true); 
      cell.InlineString = new InlineString(value.ToString()); 
      cell.DataType = CellValues.InlineString; 
     } 
     public void SetValue(uint rowId, uint columnId, object value) 
     {    
      bool boolResult; 
      Int64 intResult; 
      DateTime dateResult; 
      Double doubleResult; 
      string stringResult = value.ToString(); 

      if (bool.TryParse(stringResult, out boolResult)) 
      { 
       SetValue(rowId, columnId, boolResult); 
      } 
      else if (DateTime.TryParse(stringResult, out dateResult)) 
      { 
       SetValue(rowId, columnId,dateResult); 
      } 
      else if (Int64.TryParse(stringResult, out intResult)) 
      { 
       SetValue(rowId, columnId, intResult); 
      } 
      else if (Double.TryParse(stringResult, out doubleResult)) 
      { 
       SetValue(rowId, columnId, doubleResult); 
      } 
      else 
      { 
       // Just assume that it is a plain string. 
       SetValue(rowId, columnId, stringResult); 
      } 
     } 
     #endregion 

     public SheetData ExportAsSheetData() 
     { 
      return sheetData; 
     } 

     public void ExportAsXLSXStream(Stream outputStream) 
     { 
      // See: http://blogs.msdn.com/b/chrisquon/archive/2009/07/22/creating-an-excel-spreadsheet-from-scratch-using-openxml.aspx for some ideas... 
      // See: http://stackoverflow.com/questions/1271520/opening-xlsx-in-office-2003 

      using (SpreadsheetDocument package = SpreadsheetDocument.Create(outputStream, SpreadsheetDocumentType.Workbook)) 
      { 
       // Setup the basics of a spreadsheet document. 
       package.AddWorkbookPart(); 
       package.WorkbookPart.Workbook = new Workbook(); 
       WorksheetPart workSheetPart = package.WorkbookPart.AddNewPart<WorksheetPart>(); 
       workSheetPart.Worksheet = new Worksheet(sheetData); 
       workSheetPart.Worksheet.Save(); 

       // create the worksheet to workbook relation 
       package.WorkbookPart.Workbook.AppendChild(new Sheets()); 
       Sheet sheet = new Sheet { 
        Id = package.WorkbookPart.GetIdOfPart(workSheetPart), 
        SheetId = 1, 
        Name = "Sheet 1" 
       }; 
       package.WorkbookPart.Workbook.GetFirstChild<Sheets>().AppendChild<Sheet>(sheet); 
       package.WorkbookPart.Workbook.Save(); 
       package.Close(); 
      } 
     } 

     #region Internal Methods 
     private static string getColumnName(uint columnId) 
     { 
      if (columnId < 1) 
      { 
       throw new Exception("The column # can't be less then 1."); 
      } 
      columnId--; 
      if (columnId >= 0 && columnId < 26) 
       return ((char)('A' + columnId)).ToString(); 
      else if (columnId > 25) 
       return getColumnName(columnId/26) + getColumnName(columnId % 26 + 1); 
      else 
       throw new Exception("Invalid Column #" + (columnId + 1).ToString()); 
     } 

     // Given a worksheet, a column name, and a row index, 
     // gets the cell at the specified column 
     private static Cell getCell(SheetData worksheet, 
        string columnName, uint rowIndex, bool autoCreate) 
     { 
      Row row = getRow(worksheet, rowIndex, autoCreate); 

      if (row == null) 
       return null; 

      Cell foundCell = row.Elements<Cell>().Where(c => string.Compare 
        (c.CellReference.Value, columnName + 
        rowIndex, true) == 0).FirstOrDefault(); 

      if (foundCell == null && autoCreate) 
      { 
       foundCell = new Cell(); 
       foundCell.CellReference = columnName; 
       row.AppendChild(foundCell); 
      } 
      return foundCell; 
     } 


     // Given a worksheet and a row index, return the row. 
     // See: http://msdn.microsoft.com/en-us/library/bb508943(v=office.12).aspx#Y2142 
     private static Row getRow(SheetData worksheet, uint rowIndex, bool autoCreate) 
     { 
      if (rowIndex < 1) 
      { 
       throw new Exception("The row # can't be less then 1."); 
      } 

      Row foundRow = worksheet.Elements<Row>().Where(r => r.RowIndex == rowIndex).FirstOrDefault(); 

      if (foundRow == null && autoCreate) 
      { 
       foundRow = new Row(); 
       foundRow.RowIndex = rowIndex; 
       worksheet.AppendChild(foundRow); 
      } 
      return foundRow; 
     } 
     #endregion 
     #region IDisposable Stuff 
     private bool _disposed; 
     //private bool _transactionComplete; 

     /// <summary> 
     /// This will dispose of any open resources. 
     /// </summary> 
     public void Dispose() 
     { 
      Dispose(true); 

      // Use SupressFinalize in case a subclass 
      // of this type implements a finalizer. 
      GC.SuppressFinalize(this); 
     } 

     protected virtual void Dispose(bool disposing) 
     { 
      // If you need thread safety, use a lock around these 
      // operations, as well as in your methods that use the resource. 
      if (!_disposed) 
      { 
       if (disposing) 
       { 
        //if (!_transactionComplete) 
        // Commit(); 
       } 

       // Indicate that the instance has been disposed. 
       //_transaction = null; 
       _disposed = true; 
      } 
     } 
     #endregion 
    } 
1

que tenían el mismo problema y terminó escribiendo mi propio escritor exportación a Excel. El código está ahí para resolver este problema, pero realmente sería mejor simplemente usar todo el exportador. Es rápido y permite un formato sustancial de las celdas. Puede revisarlo en

https://openxmlexporttoexcel.codeplex.com/

espero que ayude.

0

Espero que los siguientes enlaces sean de ayuda para los futuros visitantes.

Primero, Get the standards documentation.

ECMA-376 4th Edition Parte 1 es el documento más útil. Secciones de este documento que se refieren a esta pregunta son:

18.8.30

18.8.31 (sematics de esta mierda mierda)

18.8.45 (definición de un estilo que se entiende por Excel)

L.2.7.3.6 (¿Cómo se hace referencia a estilos)

2

Aquí es cómo aplicar un formato de fecha personalizado en una célula. En primer lugar, tenemos que las operaciones de búsqueda o crear el formato de hoja de estilo del libro de trabajo:

// get the stylesheet from the current sheet  
var stylesheet = spreadsheetDoc.WorkbookPart.WorkbookStylesPart.Stylesheet; 
// cell formats are stored in the stylesheet's NumberingFormats 
var numberingFormats = stylesheet.NumberingFormats; 

// cell format string    
const string dateFormatCode = "dd/mm/yyyy"; 
// first check if we find an existing NumberingFormat with the desired formatcode 
var dateFormat = numberingFormats.OfType<NumberingFormat>().FirstOrDefault(format => format.FormatCode == dateFormatCode); 
// if not: create it 
if (dateFormat == null) 
{ 
    dateFormat = new NumberingFormat 
       { 
        NumberFormatId = UInt32Value.FromUInt32(164), // Built-in number formats are numbered 0 - 163. Custom formats must start at 164. 
        FormatCode = StringValue.FromString(dateFormatCode) 
       }; 
numberingFormats.AppendChild(dateFormat); 
// we have to increase the count attribute manually ?!? 
numberingFormats.Count = Convert.ToUInt32(numberingFormats.Count()); 
// save the new NumberFormat in the stylesheet 
stylesheet.Save(); 
} 
// get the (1-based) index of the dateformat 
var dateStyleIndex = numberingFormats.ToList().IndexOf(dateFormat) + 1; 

Entonces, podemos aplicar nuestro formato a una celda, utilizando el styleindex resuelto:

cell.StyleIndex = Convert.ToUInt32(dateStyleIndex); 
+0

Esto provoca una advertencia de Registros reparados cuando abro la hoja de cálculo –

0

he encontrado con el el mismo problema se refiere al campo de fecha de formateo después de guardar el documento. Y la solución es añadir formato de número de la siguiente manera:

new NumberingFormat() { NumberFormatId = 164, FormatCode = StringValue.FromString($"[$-409]d\\-mmm\\-yyyy;@") } 

y añadir celular así:

cell.CellValue = new CellValue(date.ToOADate().ToString()); 
cell.StyleIndex = 1; // your style index using numbering format above 
cell.DataType = CellValues.Number; 
Cuestiones relacionadas