2012-05-11 12 views
12

Odio recurrir a StackOverflow por algo tan (aparentemente) básico, pero he estado peleando con Microsoft durante las últimas horas y parece estar llegando a un callejón sin salida. Estoy tratando de leer (grandes) hojas de cálculo Excel 2007+, y Google me ha informado amablemente que el uso del SDK OpenXML es una opción bastante popular. Así que le di una oportunidad a la cosa, leí algunos tutoriales, revisé las páginas de la biblioteca de Microsoft y obtuve muy poco de todas.Usando OpenXmlReader

Estoy usando una pequeña hoja de cálculo de prueba con solo una columna de números y una de cadenas de caracteres; las pruebas a gran escala vendrán después. Probé varias implementaciones similares a la que estoy a punto de publicar, y ninguna de ellas leyó datos. El código siguiente fue tomado principalmente de otro hilo de StackOverflow, donde parecía haber funcionado, no así para mí. Pensé que los verificaría/depuraría/ayudaría con esta versión, porque es probable que esté menos rota que cualquier otra cosa que haya escrito hoy.

static void ReadExcelFileSAX(string fileName) 
    { 
     using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(fileName, true)) 
     { 
      WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart; 
      WorksheetPart worksheetPart = workbookPart.WorksheetParts.First(); 

      OpenXmlPartReader reader = new OpenXmlPartReader(worksheetPart); 
      string text; 
      string rowNum; 
      while (reader.Read()) 
      { 
       if (reader.ElementType == typeof(Row)) 
       { 
        do 
        { 
         if (reader.HasAttributes) 
         { 
          rowNum = reader.Attributes.First(a => a.LocalName == "r").Value; 
          Console.Write("rowNum: " + rowNum); //we never even get here, I tested it with a breakpoint 
         } 

        } while (reader.ReadNextSibling()); // Skip to the next row 
        Console.ReadKey(); 
        break; // We just looped through all the rows so no need to continue reading the worksheet 
       } 
       if (reader.ElementType == typeof(Cell)) 
       { 

       } 

       if (reader.ElementType != typeof(Worksheet)) // Dont' want to skip the contents of the worksheet 
        reader.Skip(); // Skip contents of any node before finding the first row. 
      } 
      reader.Close(); 
      Console.WriteLine(); 
      Console.ReadKey(); 
     } 
    } 

Y, en una nota al margen, ¿hay alguna alternativa al uso del SDK de OpenXml que de alguna manera he pasado por alto?

+0

Pruebe usar la herramienta de productividad Open XML SDK 2.0 disponible aquí: http://www.microsoft.com/en-us/download/details.aspx?id=5124. Le permite abrir cualquier * xlsx * y ver su estructura o ver el código de C# que puede recrear el archivo. De esta forma, puede ver dónde están los valores que desea alcanzar en el archivo con el que está trabajando. –

Respuesta

18

Creo que tomó el WorksheetPart incorrecto para leer las filas.

La línea

workbookPart.WorksheetParts.First(); 

obtiene el primer WorksheetPart de la colección que no debe ser necesariamente la primera hoja de cálculo como se ve en Microsoft Excel.

Por lo tanto, itere a través de todos WorksheetParts y debería ver algunos resultados en la ventana de la consola .

static void ReadExcelFileSAX(string fileName) 
{ 
    using (SpreadsheetDocument spreadsheetDocument = 
            SpreadsheetDocument.Open(fileName, true)) 
    { 
    WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart; 

    // Iterate through all WorksheetParts 
    foreach (WorksheetPart worksheetPart in workbookPart.WorksheetParts) 
    {   
     OpenXmlPartReader reader = new OpenXmlPartReader(worksheetPart); 
     string text; 
     string rowNum; 
     while (reader.Read()) 
     { 
     if (reader.ElementType == typeof(Row)) 
     { 
      do 
      { 
      if (reader.HasAttributes) 
      { 
       rowNum = reader.Attributes.First(a => a.LocalName == "r").Value; 
       Console.Write("rowNum: " + rowNum); 
      } 

      } while (reader.ReadNextSibling()); // Skip to the next row 

      break; // We just looped through all the rows so no 
       // need to continue reading the worksheet 
     } 

     if (reader.ElementType != typeof(Worksheet)) 
      reader.Skip(); 
     } 
     reader.Close();  
    } 
    } 
} 

Para leer todos los valores de las celdas utilizar la siguiente función (todos los detalles de gestión de errores omitidos):

static void ReadAllCellValues(string fileName) 
{ 
    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(fileName, false)) 
    { 
    WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart; 

    foreach(WorksheetPart worksheetPart in workbookPart.WorksheetParts) 
    { 
     OpenXmlReader reader = OpenXmlReader.Create(worksheetPart); 

     while (reader.Read()) 
     { 
     if (reader.ElementType == typeof(Row)) 
     { 
      reader.ReadFirstChild(); 

      do 
      { 
      if (reader.ElementType == typeof(Cell)) 
      { 
       Cell c = (Cell)reader.LoadCurrentElement(); 

       string cellValue; 

       if (c.DataType != null && c.DataType == CellValues.SharedString) 
       { 
       SharedStringItem ssi = workbookPart.SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(int.Parse(c.CellValue.InnerText)); 

       cellValue = ssi.Text.Text; 
       } 
       else 
       { 
       cellValue = c.CellValue.InnerText; 
       } 

       Console.Out.Write("{0}: {1} ", c.CellReference, cellValue); 
      } 
      } while (reader.ReadNextSibling()); 
      Console.Out.WriteLine(); 
     }    
     } 
    } 
    } 
} 

En el código anterior se ve que las células con el tipo de datos SharedString deben ser manejados usando la SharedStringTablePart .

+0

Bueno, eso funcionó, más o menos. Resulta que las hojas de trabajo se enumeran al revés por alguna razón (por lo que la primera de mis tres hojas es en realidad el índice 3). Siguiente problema> No puedo entender cómo capturar una fila e inspeccionar su contenido.Puede ser simple, pero he estado en esto por más de 7 horas y mi cerebro está muriendo ... – Argent

+1

@Argent: actualicé mi respuesta con una función que leía todos los valores de celda de las hojas de trabajo incluidas en el archivo de Excel. – Hans

+0

Gracias! Esto es algo de lo que había averiguado por mi cuenta, pero su versión parece menos desordenada. Jugaré con él por un tiempo hoy, y me aseguraré de molestarlo si tengo más preguntas. Y mientras estamos en ello, ¿has visto algún tutorial/guía decente sobre cómo usar OpenXML? Lo estoy calculando sobre la marcha, y eso puede ser ... contraproducente. – Argent

0

Para leer celdas en blanco, estoy usando una variable asignada fuera del lector de filas y en while, estoy verificando si el índice de columnas es mayor o no de mi variable ya que se incrementa después de cada celda leída. si esto no coincide, estoy llenando mi columna con el valor que quiero. Este es el truco que utilicé para poner al día las celdas en blanco en mi valor de columna respectivo. Aquí está el código:

public static DataTable ReadIntoDatatableFromExcel(string newFilePath) 
     { 
      /*Creating a table with 20 columns*/ 
      var dt = CreateProviderRvenueSharingTable(); 

      try 
      { 
       /*using stream so that if excel file is in another process then it can read without error*/ 
       using (Stream stream = new FileStream(newFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 
       { 
        using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(stream, false)) 
        { 
         var workbookPart = spreadsheetDocument.WorkbookPart; 
         var workbook = workbookPart.Workbook; 

         /*get only unhide tabs*/ 
         var sheets = workbook.Descendants<Sheet>().Where(e => e.State == null); 

         foreach (var sheet in sheets) 
         { 
          var worksheetPart = (WorksheetPart)workbookPart.GetPartById(sheet.Id); 

          /*Remove empty sheets*/ 
          List<Row> rows = worksheetPart.Worksheet.Elements<SheetData>().First().Elements<Row>() 
           .Where(r => r.InnerText != string.Empty).ToList(); 

          if (rows.Count > 1) 
          { 
           OpenXmlReader reader = OpenXmlReader.Create(worksheetPart); 

           int i = 0; 
           int BTR = 0;/*Break the reader while empty rows are found*/ 

           while (reader.Read()) 
           { 
            if (reader.ElementType == typeof(Row)) 
            { 
             /*ignoring first row with headers and check if data is there after header*/ 
             if (i < 2) 
             { 
              i++; 
              continue; 
             } 

             reader.ReadFirstChild(); 

             DataRow row = dt.NewRow(); 

             int CN = 0; 

             if (reader.ElementType == typeof(Cell)) 
             { 
              do 
              { 
               Cell c = (Cell)reader.LoadCurrentElement(); 

               /*reader skipping blank cells so data is getting worng in datatable's rows according to header*/ 
               if (CN != 0) 
               { 
                int cellColumnIndex = 
                 ExcelHelper.GetColumnIndexFromName(
                  ExcelHelper.GetColumnName(c.CellReference)); 

                if (cellColumnIndex < 20 && CN < cellColumnIndex - 1) 
                { 
                 do 
                 { 
                  row[CN] = string.Empty; 
                  CN++; 
                 } while (CN < cellColumnIndex - 1); 
                } 
               } 

               /*stopping execution if first cell does not have any value which means empty row*/ 
               if (CN == 0 && c.DataType == null && c.CellValue == null) 
               { 
                BTR++; 
                break; 
               } 

               string cellValue = GetCellValue(c, workbookPart); 
               row[CN] = cellValue; 
               CN++; 

               /*if any text exists after T column (index 20) then skip the reader*/ 
               if (CN == 20) 
               { 
                break; 
               } 
              } while (reader.ReadNextSibling()); 
             } 

             /*reader skipping blank cells so fill the array upto 19 index*/ 
             while (CN != 0 && CN < 20) 
             { 
              row[CN] = string.Empty; 
              CN++; 
             } 

             if (CN == 20) 
             { 
              dt.Rows.Add(row); 
             } 
            } 
            /*escaping empty rows below data filled rows after checking 5 times */ 
            if (BTR > 5) 
             break; 
           } 
           reader.Close(); 
          }        
         } 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 
      return dt; 
     } 

    private static string GetCellValue(Cell c, WorkbookPart workbookPart) 
     { 
      string cellValue = string.Empty; 
      if (c.DataType != null && c.DataType == CellValues.SharedString) 
      { 
       SharedStringItem ssi = 
        workbookPart.SharedStringTablePart.SharedStringTable 
         .Elements<SharedStringItem>() 
         .ElementAt(int.Parse(c.CellValue.InnerText)); 
       if (ssi.Text != null) 
       { 
        cellValue = ssi.Text.Text; 
       } 
      } 
      else 
      { 
       if (c.CellValue != null) 
       { 
        cellValue = c.CellValue.InnerText; 
       } 
      } 
      return cellValue; 
     } 

public static int GetColumnIndexFromName(string columnNameOrCellReference) 
     { 
      int columnIndex = 0; 
      int factor = 1; 
      for (int pos = columnNameOrCellReference.Length - 1; pos >= 0; pos--) // R to L 
      { 
       if (Char.IsLetter(columnNameOrCellReference[pos])) // for letters (columnName) 
       { 
        columnIndex += factor * ((columnNameOrCellReference[pos] - 'A') + 1); 
        factor *= 26; 
       } 
      } 
      return columnIndex; 
     } 

     public static string GetColumnName(string cellReference) 
     { 
      /* Advance from L to R until a number, then return 0 through previous position*/ 
      for (int lastCharPos = 0; lastCharPos <= 3; lastCharPos++) 
       if (Char.IsNumber(cellReference[lastCharPos])) 
        return cellReference.Substring(0, lastCharPos); 

      throw new ArgumentOutOfRangeException("cellReference"); 
     } 

Código trabaja para: 1. Este código lee las celdas en blanco 2. omitir filas vacías después de leer completa. 3. lea la hoja de la primera en orden ascendente 4. si el archivo de Excel está siendo procesado por otro, OpenXML aún lo lee.

Cuestiones relacionadas