基于iText实现Excel转PDF:如何自动处理合并单元格与空单元格?
Excel转PDF:用iText+POI搞定合并单元格与空单元格
嘿,你的需求完全可以通过iText搭配Apache POI(你代码里已经在用HSSF了)实现!你现在的代码只处理了最基础的字符串单元格,而且列数固定,自然搞不定复杂结构的Excel。下面我给你梳理下关键改进点,再附上完整的实现代码:
现有代码的核心问题
- 仅处理
STRING类型的单元格,忽略了数字、日期等其他类型 - 列数硬编码为2,无法适配动态列数的Excel
- 完全没处理合并单元格和空单元格,这也是复杂Excel转PDF的核心痛点
关键解决方案
1. 动态获取列数
不要固定写死列数,通过遍历Excel的行来获取最大列数,确保PDF表格能匹配Excel的结构:
// 遍历所有行,找到最大列数 int columnCount = 0; for (int rowNum = 0; rowNum <= my_worksheet.getLastRowNum(); rowNum++) { Row row = my_worksheet.getRow(rowNum); if (row != null && row.getLastCellNum() > columnCount) { columnCount = row.getLastCellNum(); } } PdfPTable my_table = new PdfPTable(columnCount);
2. 处理合并单元格
POI提供了getMergedRegions()方法可以获取所有合并区域,我们需要把这些区域映射到PDF表格的PdfPCell的colspan和rowspan属性:
- 遍历所有合并区域,记录每个起始单元格的合并范围
- 在生成单元格时,判断当前单元格是否是合并区域的起始单元格,若是则设置对应的跨行跨列,同时跳过后续被合并的单元格
3. 处理空单元格
不要只遍历行里的现有单元格,而是按列索引循环,用Row.MissingCellPolicy.CREATE_NULL_AS_BLANK自动创建空白单元格:
Cell cell = row.getCell(col, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
4. 支持多种单元格类型
扩展判断逻辑,覆盖数字、日期、布尔值等常见类型,避免数据丢失:
switch (cell.getCellType()) { case STRING: table_cell = new PdfPCell(new Phrase(cell.getStringCellValue())); break; case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { table_cell = new PdfPCell(new Phrase(cell.getDateCellValue().toString())); } else { table_cell = new PdfPCell(new Phrase(String.valueOf(cell.getNumericCellValue()))); } break; case BOOLEAN: table_cell = new PdfPCell(new Phrase(String.valueOf(cell.getBooleanCellValue()))); break; default: table_cell = new PdfPCell(new Phrase("")); }
完整改进代码
import com.itextpdf.text.Document; import com.itextpdf.text.Phrase; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfWriter; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import java.io.FileInputStream; import java.io.FileOutputStream; public class ExcelToPdfEnhanced { public static void main(String[] args) throws Exception { // 读取Excel文件 FileInputStream inputDocument = new FileInputStream(new File("C:\\excel_to_pdf.xls")); HSSFWorkbook workbook = new HSSFWorkbook(inputDocument); HSSFSheet worksheet = workbook.getSheetAt(0); // 获取Excel的最大列数 int columnCount = 0; for (int rowNum = 0; rowNum <= worksheet.getLastRowNum(); rowNum++) { Row row = worksheet.getRow(rowNum); if (row != null && row.getLastCellNum() > columnCount) { columnCount = row.getLastCellNum(); } } if (columnCount <= 0) { throw new IllegalArgumentException("Excel工作表无有效列!"); } // 创建PDF文档 Document pdfDocument = new Document(); PdfWriter.getInstance(pdfDocument, new FileOutputStream("Excel2PDF_Enhanced_Output.pdf")); pdfDocument.open(); // 创建PDF表格,设置宽度占满页面 PdfPTable pdfTable = new PdfPTable(columnCount); pdfTable.setWidthPercentage(100); // 标记已处理的合并单元格,避免重复添加 boolean[][] processedCells = new boolean[worksheet.getLastRowNum() + 1][columnCount]; // 遍历所有行 for (int rowNum = 0; rowNum <= worksheet.getLastRowNum(); rowNum++) { Row row = worksheet.getRow(rowNum); if (row == null) { // 空行,添加对应数量的空白单元格 for (int col = 0; col < columnCount; col++) { pdfTable.addCell(new PdfPCell(new Phrase(""))); } continue; } // 按列索引遍历,确保每个列都被处理 for (int col = 0; col < columnCount; col++) { if (processedCells[rowNum][col]) { // 该单元格已被合并处理,跳过 continue; } Cell cell = row.getCell(col, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); PdfPCell pdfCell = null; // 处理不同类型的单元格 switch (cell.getCellType()) { case STRING: pdfCell = new PdfPCell(new Phrase(cell.getStringCellValue())); break; case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { pdfCell = new PdfPCell(new Phrase(cell.getDateCellValue().toString())); } else { pdfCell = new PdfPCell(new Phrase(String.valueOf(cell.getNumericCellValue()))); } break; case BOOLEAN: pdfCell = new PdfPCell(new Phrase(String.valueOf(cell.getBooleanCellValue()))); break; default: pdfCell = new PdfPCell(new Phrase("")); } // 检查当前单元格是否属于合并区域 for (CellRangeAddress region : worksheet.getMergedRegions()) { if (region.isInRange(rowNum, col)) { // 设置跨行跨列属性 pdfCell.setColspan(region.getLastColumn() - region.getFirstColumn() + 1); pdfCell.setRowspan(region.getLastRow() - region.getFirstRow() + 1); // 标记合并区域内的所有单元格为已处理 for (int r = region.getFirstRow(); r <= region.getLastRow(); r++) { for (int c = region.getFirstColumn(); c <= region.getLastColumn(); c++) { processedCells[r][c] = true; } } break; } } // 将单元格添加到PDF表格 pdfTable.addCell(pdfCell); } } // 完成PDF文档生成 pdfDocument.add(pdfTable); pdfDocument.close(); inputDocument.close(); workbook.close(); System.out.println("PDF生成完成!"); } }
代码说明
- 合并单元格处理:通过
CellRangeAddress获取合并范围,设置PdfPCell的跨行跨列属性,并用二维数组标记已处理单元格,避免重复添加 - 空单元格处理:使用
CREATE_NULL_AS_BLANK策略自动生成空白单元格,确保表格结构完整 - 动态列数:遍历所有行获取最大列数,适配不同结构的Excel文件
- 多类型支持:覆盖了字符串、数字、日期、布尔值等常见单元格类型,避免数据丢失
内容的提问来源于stack exchange,提问作者manuel_k




