2011-03-21 41 views
14

Tengo archivos de Excel proporcionados por el usuario que deben convertirse a PDF. Usando excel interop, puedo hacerlo bien con .ExportAsFixedFormat(). Mi problema surge cuando un libro de trabajo tiene millones de filas. Esto se convierte en un archivo que tiene 50k + páginas. Eso estaría bien si el libro de trabajo tuviera contenido en todas esas filas. Sin embargo, cada vez que aparece uno de estos archivos, hay quizás 50 filas que tienen contenido y el resto están en blanco. ¿Cómo puedo eliminar las filas vacías para poder exportarlas a un PDF de tamaño decente?Eliminar filas vacías con Interop de Excel

  1. He tratado a partir de la fila final y, uno por uno, usando CountA para comprobar si la fila tiene contenido y si lo hace, eliminarlo. Esto no solo tarda una eternidad, sino que parece fallar después de unas 100k filas con el siguiente error:

    No se puede evaluar la expresión porque el código está optimizado o un marco nativo está encima de la pila de llamadas.

  2. He intentado usar SpecialCells(XlCellType.xlCellTypeLastCell, XlSpecialCellsValue.xlTextValues) pero eso incluye una fila si alguna celda tiene formato (como un color bg).

  3. He intentado usar Worksheet.UsedRange y luego eliminar todo después de eso, pero UsedRange tiene el mismo problema que el punto dos.


Este es el código que he intentado:

for (int i = 0; i < worksheets.Count; i++) 
{ 
    sheet = worksheets[i + 1]; 
    rows = sheet.Rows; 
    currentRowIndex = rows.Count; 
    bool contentFound = false; 

    while (!contentFound && currentRowIndex > 0) 
    { 
     currentRow = rows[currentRowIndex]; 

     if (Application.WorksheetFunction.CountA(currentRow) == 0) 
     { 
      currentRow.Delete(); 
     } 
     else 
     { 
      contentFound = true; 
     } 

     Marshal.FinalReleaseComObject(currentRow); 
     currentRowIndex--; 
    } 

    Marshal.FinalReleaseComObject(rows); 
    Marshal.FinalReleaseComObject(sheet); 
} 

for (int i = 0; i < worksheets.Count; i++) 
{ 
    sheet = worksheets[i + 1]; 
    rows = sheet.Rows; 

    lastCell = rows.SpecialCells(XlCellType.xlCellTypeLastCell, XlSpecialCellsValue.xlTextValues); 
    int startRow = lastCell.Row; 

    Range range = sheet.get_Range(lastCell.get_Address(RowAbsolute: startRow)); 
    range.Delete(); 

    Marshal.FinalReleaseComObject(range); 
    Marshal.FinalReleaseComObject(lastCell); 
    Marshal.FinalReleaseComObject(rows); 
    Marshal.FinalReleaseComObject(sheet); 
} 

¿Tengo un problema con mi código, ¿es un problema de interoperabilidad o tal vez es sólo una limitación sobre lo que Excel puede hacer? ¿Hay una mejor manera de hacer lo que estoy intentando?

+0

Me gustaría investigar este tema. ¿Tienes un archivo de demostración para hacer pruebas? –

+0

@PilgerstorferFranz Lo siento, no es así. Este proyecto se ha ido. – Chris

+0

¿Encuentra alguna solución? –

Respuesta

0

¿Ha intentado Sheet1.Range("A1").CurrentRegion.ExportAsFixedFormat() donde Sheet1 es un nombre de hoja válido y "A1" es una celda que puede probar para asegurarse de que se encuentra en el rango que desea exportar?

La pregunta sigue siendo, ¿por qué Excel piensa que hay datos en esas celdas "vacías"? Formateo? ¿Un área de impresión preexistente que debe borrarse? Sé que he encontrado situaciones como esa antes, esas son las únicas posibilidades que se me ocurren en este momento.

+0

Maldición, esto tampoco funciona. Tengo el mismo problema que mis puntos dos y tres. Sería genial si pudiera decirle a los usuarios que no hagan hojas de cálculo ridículas. D – Chris

0

prueba los siguientes pasos -

  1. copia Worksheet.UsedRange a otra hoja (hoja 2).
  2. utilización pasta especial para que formato se conserva
  3. try analizar la hoja 2 para las filas no utilizadas

Si esto no ayuda intento de repetir el paso 2 con el formato de datos que es despejado y luego analizar la hoja 2. siempre puede copiar información de formato más tarde (si son lo suficientemente simples)

+0

Probé la primera parte de lo que usted sugirió. Mismo problema que los puntos dos y tres. No intenté copiar sin formatear y luego volví a aplicar formato. ¿Cómo podría uno hacer eso? * si son lo suficientemente simples * - ¿eso significa copiar el formato no lo hará? siempre ser una opción viable? Dado que estas son hojas suministradas por el usuario, no se puede garantizar el formato que tendrán. – Chris

0

Si puede cargar primero el archivo Excel en un DataSet a través del OleDBAdapter, es relativamente fácil eliminar filas en blanco en la importación ... Pruebe esto OleDBAdapter Excel QA Publiqué por desbordamiento de pila.

A continuación, exporte el conjunto de datos a un nuevo archivo de Excel y conviértalo en PDF. Eso puede ser un gran "SI", aunque, por supuesto, depende del diseño de Excel (o la falta de).

+0

No estoy usando un DataSet. Necesito modificar el archivo de Excel real y se ve como ADO.NET [ no es compatible con la operación 'delete'] (http://support.microsoft.com/kb/316934) – Chris

+0

Ahh, no debería haber asumido que estaba usando el oledbadapter y DataSet. Voy a modificar mi respuesta. –

0

Tuve que resolver este problema hoy para lo que podría ser un subconjunto de sus posibles casos.

Si la hoja de cálculo cumple las siguientes condiciones: texto

  1. Todas las columnas con datos han encabezado en la línea 1.
  2. Todas las filas con datos están en secuencia hasta la primera fila en blanco.

A continuación, el código siguiente puede ayudar:

private static string[,] LoadCellData(Excel.Application excel, dynamic sheet) 
    { 
     int countCols = CountColsToFirstBlank(excel, sheet); 
     int countRows = CountRowsToFirstBlank(excel, sheet); 
     cellData = new string[countCols, countRows]; 
     string datum; 

     for (int i = 0; i < countCols; i++) 
     { 
      for (int j = 0; j < countRows; j++) 
      { 
       try 
       { 
        if (null != sheet.Cells[i + 1, j + 1].Value) 
        { 
         datum = excel.Cells[i + 1, j + 1].Value.ToString(); 
         cellData[i, j] = datum; 
        } 
       } 
       catch (Exception ex) 
       { 
        lastException = ex; 
        //Console.WriteLine(String.Format("LoadCellData [{1}, {2}] reported an error: [{0}]", ex.Message, i, j)); 
       } 
      } 
     } 

     return cellData; 
    } 

    private static int CountRowsToFirstBlank(Excel.Application excel, dynamic sheet) 
    { 
     int count = 0; 

     for (int j = 0; j < sheet.UsedRange.Rows.Count; j++) 
     { 
      if (IsBlankRow(excel, sheet, j + 1)) 
       break; 

      count++; 
     } 
     return count; 
    } 
    private static int CountColsToFirstBlank(Excel.Application excel, dynamic sheet) 
    { 
     int count = 0; 

     for (int i = 0; i < sheet.UsedRange.Columns.Count; i++) 
     { 
      if (IsBlankCol(excel, sheet, i + 1)) 
       break; 

      count++; 
     } 
     return count; 
    } 

    private static bool IsBlankCol(Excel.Application excel, dynamic sheet, int col) 
    { 
     for (int i = 0; i < sheet.UsedRange.Rows.Count; i++) 
     { 
      if (null != sheet.Cells[i + 1, col].Value) 
      { 
       return false; 
      } 
     } 

     return true; 
    } 
    private static bool IsBlankRow(Excel.Application excel, dynamic sheet, int row) 
    { 
     for (int i = 0; i < sheet.UsedRange.Columns.Count; i++) 
     { 
      if (null != sheet.Cells[i + 1, row].Value) 
      { 
       return false; 
      } 
     } 

     return true; 
    } 
+0

I no creas que esto es un workab La solución para el problema, ya que (como se señala en la pregunta) ** las celdas vacías ** que tienen formato, no deberían eliminarse. A menos que me equivoque, su fragmento eliminará esas filas erróneamente, ya que los valores serían 'null', mientras que el formato podría estar destinado a mantenerse. – gravity

-1

intenta lo siguiente código:

for (int i = 0; i < worksheets.Count; i++) 
{ 
    sheet = worksheets[i + 1]; 
    sheet.Columns("A:A").SpecialCells(XlCellType.xlCellTypeBlanks).EntireRow.Delete 
    sheet.Rows("1:1").SpecialCells(XlCellType.xlCellTypeBlanks).EntireColumn.Delete 
    Marshal.FinalReleaseComObject(sheet); 
} 
0

yo sugeriría que para obtener el recuento de filas que contienen algunos valores, usando CountA (como lo ha intentado en el punto 1). Luego copie esas filas en una hoja nueva y expórtela desde allí. Será más fácil copiar algunas filas en una hoja nueva y trabajar en ella, en lugar de intentar eliminar una gran cantidad de filas de la hoja de origen.

Para crear nueva hoja y la copia de filas que puede utilizar el siguiente código:

 excel.Worksheet tempSheet = workbook.Worksheets.Add(); 
     tempSheet.Name = sheetName; 
     workbook.Save(); 

// crear un nuevo método para la copia nuevas filas

// como rowIndex puede pasar el nº total de las filas que ha descubierto utilizando CountA

public void CopyRows(excel.Workbook workbook, string sourceSheetName, string DestSheetName, int rowIndex) 
     { 
      excel.Worksheet sourceSheet = (excel.Worksheet)workbook.Sheets[sourceSheetName]; 
      excel.Range source = (excel.Range)sourceSheet.Range["A" + rowIndex.ToString(), Type.Missing].EntireRow; 

      excel.Worksheet destSheet = (excel.Worksheet)workbook.Sheets[DestSheetName]; 
      excel.Range dest = (excel.Range)destSheet.Range["A" + rowIndex.ToString(), Type.Missing].EntireRow; 
      source.Copy(dest); 

      excel.Range newRow = (excel.Range)destSheet.Rows[rowIndex+1]; 
      newRow.Insert(); 
      workbook.Save(); 
     }