复制word文档,合并word文档

背景

现在有一个需求,需要将多个word文档合并为一个文档,并使用poi-el渲染模板生成一个新的文档。
在网上找了很多,没找到合适的,最后摸索出一个可行方案,基于XWPFDocument.getBodyElements方法的。
其实一开始考虑过这个方法,但当时对底层源码不熟悉,觉得无法分辨每个element的类型,比如chart对象其实是包含在XWPFParagraph对象中的,所以当时排除了这个方法。后来在网上找到了基于OPCPackage的,但是这种方法无法保证顺序,所以也排除了。最后想到了XWPFTemplate.render方法的底层源码,可以完美解决getBodyElements的类型问题,所以就使用了getBodyElements这个方案。
参考文档:https://codeleading.com/article/50704657377/

方案思路

使用XWPFDocument.getBodyElements()来获取整个doc的所有元素对象,元素对象供分为三类,分别为PARAGRAPH、TABLE、CONTENTCONTROL,其中,Picture、Chart均是包含在PARAGRAPH里的,关于这块可以参考底层代码TemplateResolver

完整代码

package word;import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.xwpf.CTDrawingWrapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTPlotArea;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;/*** 将多个word文档合并为一个word文件*/
public class CopyDoc{static String target = "C:\\Users\\huahua\\Desktop\\template.docx";static String dest = "C:\\Users\\huahua\\Desktop\\out.docx";public static List<XWPFDocument> readDoc(List<String> paths) throws Exception {List<XWPFDocument> documents = new ArrayList();for (String path : paths) {documents.add(new XWPFDocument(new FileInputStream(path)));}return documents;}public static void main(String[] args) throws Exception {List<String> paths = new ArrayList() {{add("C:\\Users\\huahua\\Desktop\\模板\\11.docx");add("C:\\Users\\huahua\\Desktop\\模板\\12.docx");add("C:\\Users\\huahua\\Desktop\\模板\\14.docx");}};List<XWPFDocument> document = readDoc(paths);insertContext(document, new ArrayList(){{add("标题1");add("标题2");add("标题3");}});renderData();}public static void renderData() throws IOException {XWPFTemplate compile = XWPFTemplate.compile(target);compile.render(new HashMap() {{put("1", "ajsdkaklsjd");put("2", "ajsdkaklsjd");put("3", "ajsdkaklsjd");}}).writeToFile(dest);}/*** 追加内容到页面** @throws IOException*/public static void insertContext(List<XWPFDocument> documents, List<String> titles) throws Exception {//模板文档XWPFDocument document1 = new XWPFDocument();XWPFDocument template = new XWPFDocument(new FileInputStream("C:\\Users\\huahua\\Desktop\\标题文档.docx"));// 获得模板文档的整体样式CTStyles wordStyles = template.getStyle();// 获取新建文档对象的样式XWPFStyles newStyles = document1.createStyles();// 关键行// 修改设置文档样式为静态块中读取到的样式newStyles.setStyles(wordStyles);int i = 1;for (XWPFDocument document : documents) {//一级标题XWPFParagraph paragraph = document1.createParagraph();paragraph.setStyle("2");XWPFRun run1 = paragraph.createRun();run1.addBreak();run1.setText(i+" ."+titles.get(i-1));Document document2 = new XWPFDocument();List<IBodyElement> bodyElements = document.getBodyElements();int chartPos = 0;for (IBodyElement element : bodyElements) {if (element.getElementType() == BodyElementType.PARAGRAPH) {XWPFParagraph sourcePara = (XWPFParagraph) element;XWPFParagraph targetParagraph = document1.createParagraph();copyPara(targetParagraph, sourcePara, document1, document, chartPos);} else if (element.getElementType() == BodyElementType.TABLE) {XWPFTable sourceTable = (XWPFTable) element;List<XWPFTableRow> rows = sourceTable.getRows();if (null == rows) continue;copyTable(sourceTable, document1);}}i++;}FileOutputStream fopts = new FileOutputStream(target);document1.write(fopts);document1.close();fopts.close();}/*** @param targetParagraph* @param sourcePara*/private static void copyPara(XWPFParagraph targetParagraph, XWPFParagraph sourcePara, XWPFDocument document, XWPFDocument srcDoc, int pos) throws Exception {//把传过来的段落中的ppr标签设置到新的段落里CTP ctp = targetParagraph.getCTP();ctp.setPPr(sourcePara.getCTPPr());List<XWPFRun> runs = sourcePara.getRuns();//获取run对象for (int k = 0; k < runs.size(); k++) {XWPFRun newRun = targetParagraph.createRun();XWPFRun oldRun = runs.get(k);CTRPr oldRPr = oldRun.getCTR().getRPr();newRun.getCTR().setRPr(oldRPr);//处理图表if (StringUtils.isBlank(oldRun.getText(0))) {CTR ctr = oldRun.getCTR();CTDrawing ctDrawing = CollectionUtils.isNotEmpty(ctr.getDrawingList()) ? ctr.getDrawingArray(0) : null;if (null == ctDrawing) continue;CTDrawingWrapper wrapper = new CTDrawingWrapper(ctDrawing);String rid = wrapper.getChartId();if (null == rid) continue;POIXMLDocumentPart documentPart = oldRun.getDocument().getRelationById(rid);if (null == documentPart || !(documentPart instanceof XWPFChart)) continue;XWPFChart chart1 = srcDoc.getCharts().get(pos);XWPFChart chart = document.createChart(newRun, 7 * Units.EMU_PER_CENTIMETER, 7 * Units.EMU_PER_CENTIMETER);CTPlotArea plotArea = chart1.getCTChart().getPlotArea();chart.getCTChart().setPlotArea(plotArea);chart.getCTChart().setLegend(chart1.getCTChart().getLegend());chart.setWorkbook(chart1.getWorkbook());CTNonVisualDrawingProps docPr = newRun.getCTR().getDrawingList().get(0).getInlineList().get(0).getDocPr();if (StringUtils.isBlank(docPr.getTitle())) {docPr.setTitle("{{test}}");}pos++;} else {newRun.setText(oldRun.getText(0));}targetParagraph.addRun(newRun);}}/*** 复制表格** @param source* @param document*/private static void copyTable(XWPFTable source, XWPFDocument document) {// 创建新的 CTTblCTTbl ctTbl = CTTbl.Factory.newInstance();// 复制原来的CTTblctTbl.set(source.getCTTbl());IBody iBody = source.getBody();XWPFTable newTable = new XWPFTable(ctTbl, iBody);document.createTable();document.setTable(document.getTables().size()-1, newTable);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/753008.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue项目跳转html页面

1. 把html页面以及相关文件放到public文件夹下&#xff08;目的和index.html同级&#xff09; 2.在vue项目中正常写跳转事件&#xff0c;只是路径写法需要注意

【理解指针(三)】

文章目录 一、数组名的理解&#xff08;1&#xff09;数组的地址与数组首元素地址的区别 二、使用指针访问数组&#xff08;1&#xff09;一维数组传参的本质 三、冒泡排序&#xff08;1&#xff09;什么是冒泡排序&#xff08;2&#xff09;写冒泡排序 四、结束语 一、数组名的…

堆排序(向下调整法,向上调整法详解)

目录 一、 二叉树的顺序结构 二、 堆的概念及结构 三、数组存储、顺序存储的规律 此处可能会有疑问&#xff0c;左右孩子的父节点计算为什么可以归纳为一个结论了&#xff1f; 四、大小堆解释 五、大小堆的实现&#xff08;向上和向下调整法&#xff09; 5.11向上调整法…

Unity Timeline学习笔记(1) - 创建TL和添加动画片段

Timeline在刚出的时候学习了一下&#xff0c;但是因为一些原因一直都没用在工作中使用。 版本也迭代了很久不用都不会用了&#xff0c;抽时间回顾和复习一下&#xff0c;做一个笔记后面可以翻出来看。 创建Timeline 首先我们创建一个场景&#xff0c;放入一个Plane地板&#…

Linux操作系统-08-常用的网络相关命令

1、ping命令 在linux下ping的话会一直ping下去&#xff0c;在windows下的话它会只ping四次就结束&#xff0c; [rootbastion ~]# ping 192.168.0.102 PING 192.168.0.102 (192.168.0.102) 56(84) bytes of data. 64 bytes from 192.168.0.102: icmp_seq1 ttl64 time0.571 ms…

Linux - 线程互斥和互斥锁

文章目录 前言一、为什么要线程互斥原子性 二、互斥锁互斥锁的创建与销毁互斥锁进行互斥 前言 前几节课&#xff0c;我们学习了多线程的基础概念&#xff0c;这节课&#xff0c;我们来对线程互斥和互斥锁的内容进行学习。 一、为什么要线程互斥 首先我们要明白&#xff0c;对…

悲观锁(Pessimistic Locking)是一种数据库锁定机制

悲观锁&#xff08;Pessimistic Locking&#xff09;是一种数据库锁定机制&#xff0c;用于防止多个事务同时修改同一数据记录。以下是关于悲观锁的一些详细信息&#xff1a; 锁定数据&#xff1a;当事务对一条记录进行操作时&#xff0c;悲观锁会阻止其他事务对这条记录进行修…

Matplotlib数据可视化实战-1数据可视化Matplotlib基础

1.1绘图的一般过程&#xff1a; 1.导入相关库 2.生成、读入或计算得到数据&#xff1b; 3.根据需要绘制折线图、散点图、柱状图、饼状图、雷达图、箱线图、三维曲线/曲面以及极坐标系图形&#xff1b; 4.根据需要设置图形属性&#xff1b; 5.显示或保存绘图结果。 例如&…

为什么JDK1.9要允许接口定义私有方法呢?

为什么JDK1.9要允许接口定义私有方法呢&#xff1f; 因为我们说接口是规范&#xff0c;规范是需要公开让大家遵守的。** 私有方法&#xff1a;因为有了默认方法和静态方法这样具有具体实现的方法&#xff0c;那么就可能出现多个方法有共同的代码可以抽取&#xff0c;而这些共…

c语言大小写字母的转换

通过ascll码表我们可以知道大写字母与小写字母相差32个数&#xff08;小写字母比大写字母大&#xff09;。因此&#xff0c;通过相加减32即可转换大小写字母。 #include <stdio.h>int main() {char ch c;char CH A;printf("%c\n", ch - 32);printf("%c…

计算机网络实验——学习记录

1. tun/tap模块&#xff1a;为Linux系统提供网络虚拟功能&#xff0c;tun位于网络OSI模型的三层&#xff08;网络层&#xff09;&#xff0c;tap位于网络的二层&#xff08;数据链路层&#xff09;。 1.1 验证是否包含tun/tap模块&#xff1a;modinfo tun&#xff1b; 1.2 验…

每日学习总结20240313

每日总结 20240313 1. 正则表达式 当使用C语言编写正则表达式的程序时&#xff0c;通常会用到以下四个函数来编译、匹配、释放正则表达式以及处理可能的错误&#xff1a; int regcomp(regex_t *preg, const char *regex, int cflags)int regexec(const regex_t *preg, cons…

Python进程与线程开发

目录 multiprocessing模块 线程的开发 threading模块 setDaemon 死锁 线程间的通信 multiprocessing模块 运行python的时候&#xff0c;我们都是在创建并运行一个进程&#xff0c;(linux中一个进程可以fork一个子进程&#xff0c;并让这个子进程exec另外一个程序)。在pyt…

java高频面试题-高级篇

1&#xff0c;TCP可靠传输除了三次握手&#xff0c;还有其他方式保证可靠性吗&#xff1f; 序列号和确认应答&#xff1a;TCP通过序列号和确认应答机制来确保数据包的有序传输和可靠接收。数据校验和&#xff1a;TCP在每个数据包中都包含一个校验和字段&#xff0c;用于检测数…

全局路径规划算法 - 动态规划算法Python实现

参考博客&#xff1a; &#xff08;1&#xff09;算法数据结构——动态规划算法&#xff08;Dynamic Programming&#xff09;超详细总结加应用案例讲解 &#xff08;2&#xff09;【路径规划】全局路径规划算法——动态规划算法&#xff08;含python实现&#xff09; &#xf…

【上交主办·EI会议】| 2024年模式分析与机器智能国际会议 (ICPAMI 2024)

会议简介 Brief Introduction 2024年模式分析与机器智能国际会议(ICPAMI 2024) 会议时间&#xff1a;2024年8月30日-9月1日 召开地点&#xff1a;中国上海 大会官网&#xff1a;www.icpami.org ICPAMI 2024将围绕“模式分析与机器智能”的最新研究领域展开&#xff0c;为研究人…

(零)OpenOFDM接收端整体思路

一旦捕获射频信号并将其下变频至基带&#xff0c;解码管道就会启动&#xff0c;包括&#xff1a; OFDM&#xff0c;多载波调制的一种。通过频分复用实现高速串行数据的并行传输, 它具有较好的抗多径衰落的能力&#xff0c;能够支持多用户接入。 OFDM主要思想是&#xff1a;将信…

Finding the Majority Element寻找主元素

Problem Is there the majority element in sequence A [1.. n]? If so, please find it out. An integer a in A is called the majority if it appears more than [n/2] times in A.寻找元素出现次数大于n/2 Algorithm 1 —— The brute-force method 遍历序列中的每个元…

对IO流原理及、分类及IO模型的一个大概认识【Java基础题】

1.流的分类 根据操作数据单位分类&#xff1a; 字节流字符流 一般来说&#xff0c;字符流会比字节流效率更高&#xff0c;因为1个字符一般比1个字节&#xff08;8bit&#xff09;大&#xff08;it depends on 具体的编码规则&#xff0c;例如UTF-8中1个中文等于3个字节&#xf…

物联网数据驾驶舱

在信息化时代&#xff0c;数据已经成为驱动企业发展的核心动力。特别是在物联网领域&#xff0c;海量数据的实时采集、分析和监控&#xff0c;对于企业的运营决策和业务优化具有至关重要的作用。HiWoo Cloud作为领先的物联网云平台&#xff0c;其数据监控功能以“物联网数据驾驶…