使用Apache POI复制含公式、图片的XWPFTableCell至新文档对应单元格的实现方案
使用Apache POI复制含公式、图片的XWPFTableCell至新文档对应单元格的实现方案
嗨,你遇到的问题其实很典型——Apache POI的基础文本复制逻辑不会自动处理数学公式、图片这类特殊内容,因为它们不是单纯的文本Run。我帮你完善了代码,不仅能复制普通文本和样式,还能完整保留单元格里的数学公式(OMML格式)、图片,以及类似化学公式的嵌入式对象,同时也补上了单元格样式的复制(比如宽度、边框这些)。
关键改进点说明
- 单元格样式复制:补上了源单元格宽度、边框、背景等样式的完整复制,避免目标单元格样式丢失
- 段落公式处理:数学公式以OMML格式存储在段落XML中,直接复制对应的
<m:oMath>节点就能完整保留公式结构 - Run中的图片复制:提取源图片的二进制数据和尺寸参数,在目标Run中重新创建完全一致的图片
- 嵌入式对象支持:针对化学公式这类OLE嵌入式对象,直接复制Run中的
<w:object>节点
完整修改后代码
主方法修正(修复输出顺序错误)
public ApiResponse generateDocument(HttpServletResponse response) throws Exception { try (XWPFDocument targetDoc = new XWPFDocument()){ ByteArrayOutputStream b = new ByteArrayOutputStream(); XWPFDocument courseDoc = new XWPFDocument(new FileInputStream("upload/yukla.docx")); XWPFTable targetTable = targetDoc.createTable(10, 5); for (IBodyElement ibodyelement : courseDoc.getBodyElements()) { if (ibodyelement instanceof XWPFTable courseTable) { cloneTable(courseTable, targetTable); } } // 修正:先写入文档到字节流,再复制到响应输出 targetDoc.write(b); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment; filename=VARIANTS.docx"); FileCopyUtils.copy(b.toByteArray(), response.getOutputStream()); courseDoc.close(); targetDoc.close(); return new ApiResponse("SUCCESS", true); } }
表格复制方法(保留原有逻辑)
private void cloneTable(XWPFTable courseTable, XWPFTable targetTable) { int cntRow = 0; for (XWPFTableRow row : courseTable.getRows()) { for (int i = 0; i < 5; i++) { XWPFTableCell courseCell = row.getCell(i); XWPFTableCell targetCell = targetTable.getRow(cntRow).getCell(i); try { cloneCell(courseCell, targetCell); } catch (Exception e) { e.printStackTrace(); } } cntRow++; } }
单元格复制方法(新增样式复制)
private void cloneCell(XWPFTableCell sourceCell, XWPFTableCell targetCell) throws Exception { // 复制单元格样式(宽度、边框、背景等) CTTcPr sourceTcPr = sourceCell.getCTTc().getTcPr(); if (sourceTcPr != null) { CTTcPr targetTcPr = targetCell.getCTTc().getTcPr(); if (targetTcPr == null) { targetTcPr = targetCell.getCTTc().addNewTcPr(); } targetTcPr.set(sourceTcPr); } // 复制段落及内容 for (XWPFParagraph sourcePr : sourceCell.getParagraphs()) { XWPFParagraph targetPr = targetCell.addParagraph(); cloneParagraph(targetPr, sourcePr); } }
段落复制方法(新增公式复制)
private void cloneParagraph(XWPFParagraph targetPr, XWPFParagraph sourcePr) throws Exception { // 复制段落样式 CTPPr sourcePPr = sourcePr.getCTP().getPPr(); if (sourcePPr != null) { CTPPr targetPPr = targetPr.getCTP().getPPr(); if (targetPPr == null) { targetPPr = targetPr.getCTP().addNewPPr(); } targetPPr.set(sourcePPr); } // 复制段落中的数学公式(OMML格式) List<CTOMath> sourceOMaths = sourcePr.getCTP().getOMathList(); for (CTOMath oMath : sourceOMaths) { targetPr.getCTP().addNewOMath().set(oMath); } // 复制Run内容 for (XWPFRun sourceRun : sourcePr.getRuns()) { XWPFRun targetRun = targetPr.createRun(); cloneRun(targetRun, sourceRun); } }
Run复制方法(新增图片、嵌入式对象复制)
private void cloneRun(XWPFRun targetRun, XWPFRun sourceRun) throws Exception { // 复制Run样式 CTRPr sourceRPr = sourceRun.getCTR().getRPr(); if (sourceRPr != null) { CTRPr targetRPr = targetRun.getCTR().getRPr(); if (targetRPr == null) { targetRPr = targetRun.getCTR().addNewRPr(); } targetRPr.set(sourceRPr); } // 复制文本内容 String text = sourceRun.getText(0); if (text != null) { targetRun.setText(text); } // 复制Run中的图片 List<XWPFPicture> sourcePictures = sourceRun.getEmbeddedPictures(); for (XWPFPicture pic : sourcePictures) { XWPFPictureData picData = pic.getPictureData(); byte[] picBytes = picData.getData(); int pictureType = picData.getPictureType(); // 按原图尺寸插入图片 targetRun.addPicture(new ByteArrayInputStream(picBytes), pictureType, picData.getFileName(), Units.toEMU(pic.getWidth()), Units.toEMU(pic.getHeight())); } // 复制嵌入式对象(如化学公式OLE对象) CTR sourceCTR = sourceRun.getCTR(); List<CTObject> sourceObjects = sourceCTR.getObjectList(); for (CTObject obj : sourceObjects) { targetRun.getCTR().addNewObject().set(obj); } }
额外说明
如果你的单元格里还有嵌套表格这类特殊内容,可以在cloneCell方法中加入对XWPFTable类型元素的判断和复制,逻辑和外层表格的cloneTable方法类似。
备注:内容来源于stack exchange,提问作者Anvar Primov




