14

我有用户提供的需要转换为 PDF 的 excel 文件。使用 excel 互操作,我可以用.ExportAsFixedFormat(). 当工作簿有数百万行时,我的问题就出现了。这变成了一个有 50k+ 页的文件。如果工作簿在所有这些行中都有内容,那就很好了。但是,每次出现这些文件中的一个时,可能有 50 行有内容,其余的都是空白的。如何删除空行以便将其导出为大小合适的 PDF?

  1. 我尝试从最后一行开始,并逐一CountA检查该行是否有内容,如果有,则将其删除。这不仅需要永远,而且在大约 100k 行之后似乎会失败,并出现以下错误:

    无法计算表达式,因为代码已优化或本机框架位于调用堆栈顶部。

  2. 我尝试过使用SpecialCells(XlCellType.xlCellTypeLastCell, XlSpecialCellsValue.xlTextValues),但如果任何单元格具有格式(如 bg 颜色),则包含一行。

  3. 我试过使用Worksheet.UsedRange然后删除所有内容,但UsedRange问题与第二点相同。


这是我尝试过的代码:

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);
}

我的代码有问题吗,这是互操作问题,还是只是 Excel 功能的限制?有没有更好的方法来做我正在尝试的事情?

4

8 回答 8

1

我建议您使用 CountA 获取包含某些值的行数(正如您在第 1 点中所尝试的那样)。然后将这些行复制到新工作表中并从那里导出。将几行复制到新工作表并对其进行处理会更容易,而不是尝试从源工作表中删除大量行。

要创建新工作表和复制行,您可以使用以下代码:

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

//创建一个复制新行的新方法

//作为rowindex,您可以传递使用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();
        }
于 2017-12-14T10:01:20.037 回答
0

您是否尝试过Sheet1.Range("A1").CurrentRegion.ExportAsFixedFormat()Sheet1 是有效的工作表名称,而“A1”是可以测试以确保它位于要导出的范围内的单元格?

问题仍然存在,为什么 Excel 认为那些“空”单元格中有数据?格式化?需要清除的预先存在的打印区域?我知道我以前遇到过这样的情况,这是目前唯一想到的可能性。

于 2011-03-21T22:02:50.377 回答
0

我今天必须解决这个问题,因为这可能是你可能的情况的一个子集。

如果您的电子表格满足以下条件:

  1. 所有包含数据的列在第 1 行都有标题文本。
  2. 所有包含数据的行都按顺序排列,直到第一个 BLANK 行。

然后,以下代码可能会有所帮助:

    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;
    }
于 2012-05-21T18:27:21.087 回答
0

如果您可以首先通过 OleDBAdapter 将 Excel 文件加载到 DataSet 中,则在导入时删除空白行相对容易......试试我通过堆栈溢出发布的这个OleDBAdapter Excel QA 。

然后将 DataSet 导出到一个新的 Excel 文件并将该文件转换为 PDF。当然,这可能是一个很大的“IF”,具体取决于 excel 布局(或缺少)。

于 2011-05-05T16:19:18.303 回答
0

试试这些步骤 -

  1. 复制Worksheet.UsedRange到单独的工作表(工作表 2)。
  2. 使用特殊粘贴以便保留格式
  3. 尝试为未使用的行解析 sheet2

如果这无济于事,请尝试重复步骤 2,清除格式信息,然后解析 sheet2。您以后可以随时复制格式信息(如果它们足够简单)

于 2011-03-22T18:46:37.430 回答
0

我遇到了同样的问题,并设法使用 CurrentRegion 修复它:

                    var lastcell = sheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell);
                    var filledcells = sheet.Cells.Range[sheet.Cells.Item[1, 1],
                            sheet.Cells[lastcell.Row - 1, lastcell.Column]]
                        .CurrentRegion;
                    filledcells.ExportAsFixedFormat(

等等。据说 CurrentRegion 会扩展到单元格为空的边界,显然这意味着如果它包含许多空单元格,它也会缩小。

于 2020-09-12T07:03:18.837 回答
0

你可以试试下面的代码:

for (int rowIndex = workSheet.Dimension.Start.Row; rowIndex <= workSheet.Dimension.End.Row; rowIndex++)
                    {
                        //Assume the first row is the header. Then use the column match ups by name to determine the index.
                        //This will allow you to have the order of the header.Keys change without any affect.
                        var row = workSheet.Cells[string.Format("{0}:{0}", rowIndex)];
                        // check if the row and column cells are empty
                        bool allEmpty = row.All(c => string.IsNullOrWhiteSpace(c.Text));
                        if (allEmpty)
                            continue; // skip this row
                        else{
                               //here read header
                               if()
                                 {
                                  //some code
                                 }
                               else
                                  {
                                   //some code to read body
                                  }
                            }
                    }

希望这对您有所帮助,否则如果您需要有关代码的描述,请告诉我。

更新 :

  • 下面的代码用于检查工作表中有多少行。for 循环将遍历工作表的行尾。

for (int rowIndex = workSheet.Dimension.Start.Row; rowIndex <= workSheet.Dimension.End.Row; rowIndex++)

  • 在这里,我们使用 linq 检查行和列单元格是否为空:

bool allEmpty = row.All(c => string.IsNullOrWhiteSpace(c.Text));
if (allEmpty)
继续;// 如果为真,则跳过此行
else // 读取标题(假设它出现在工作表中)
// else 读取行数据,然后执行必要的步骤。

希望这现在可以清除。

于 2018-03-19T09:50:46.290 回答
-1

请尝试以下代码:

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);
}
于 2016-11-22T07:07:50.383 回答