(五)、JAVA基于OPENXML的word文档插入、合并、替换操作系列之word文件合并[支持多文件]
(五)、JAVA基于OPENXML的word文档插入、合并、替换操作系列之word文件合并[支持多文件]
- 二、word合并的多种方案简单比较
- 三、基于Open Xml WordprocessingML的word合并
- step 0、准备工作
- step 1、命名空间合并
- step 2、文档内图片合并
- step 3、内容区域合并
- step 4、合并整合输出
- 整点总结显得流弊
二、word合并的多种方案简单比较
- 基于jacob控件的实现
jacob一个Java-com中间件,通过这个组件利用Java程序调用win上面的dll来操作office实现文件的处理,这个因为是基于office本身的功能,效果上还可以,不过受限了操作系统,如果你的程序要放在linux上部署,这个就无法实现了,可以参考一下这位仁兄的 参考案例 - 基于pageoffice的实现
上面jacob是基于java后端的,那这个就是基于前端的, pageoffice是一个基于类似于ActiveX类的控件,它是将office以控件的形式加载到浏览器端来操作,当然我们只需要懂js,通过js api来调用就可以,这个方案解决了跨平台的问题,但pageoffice是一款商业软件,是需要收费的,具体也可参考一下这位仁兄的参考案例 - 基于docx4j的实现
docx4j还是挺强大的,我挺看好它,所以我认为这个实现是比较好的一种方案了,但实际上我自己没怎么用这个方案,因为在我自己折腾出办法以后才发现了它,我也就懒得去改了,并且我比较信赖自己的劳动成果(请允许我自恋一下),有兴趣可以看看这位仁兄的 docx的拆分和合并 还有这个 参考案例 - 基于POI的实现
这个方案可能是网上使用比较多的,我曾经也用过这个方案, 但这个方案似乎对于复杂的文档并不那么友好,比如有图片的文档似乎合并后图片会有问题,当然不排除个人对它的了解层度不够深或许也有别的办法能处理好,只是我不知道, 具体可以自行尝试一下,参考案例 - 其他实现方案
似乎像itext
这类库也可以、还有一些偏门的独家秘方的我了解的就不多了, 我能想到的还有比如通过python、.NET等一些语言把这个功能单独写成一个独立的服务,然后通过java去调用这个服务来实现也是可以的,只是看你愿不愿意这么做,值不值得在这个事上大展身手了。
三、基于Open Xml WordprocessingML的word合并
前面在word基础篇时提过,word解压后核心的一个文件document.xml
它里面就是WordprocessingML的结构,这算是word的老底了,我们的合并就是基于这货的,所以我这个方法比较粗鲁,但是透彻呀!
广告插播: 建议你往下看之前,如果你不了解WordprocessingML也没看过我前面的几篇笔记,强烈建议你先点上面传送过去看看,尤其是系列之二《图片在word结构中的存放、插入、替换图片》,广告完毕!
还记得我前面系列笔记之一《基础篇》中那个word文档中的document.xml
文件的结构吗,它是一个有着特殊格式的xml文件,精简一下它是这样的:
<xml-fragment xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" …… 省略更多 ……><!-- 以上为内容区、截取一部分 --><w:p><w:pPr><w:spacing w:after="450"/><w:ind w:left="120"/><w:jc w:val="center"/></w:pPr><w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="8466666" cy="5872268"/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="0" name="" descr=""/><wp:cNvGraphicFramePr><a:graphicFrameLocks noChangeAspect="true"/></wp:cNvGraphicFramePr><a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic><pic:nvPicPr><pic:cNvPr id="1" name=""/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="rId4"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="8466666" cy="5872268"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r></w:p><w:sectPr><w:pgSz w:w="11907" w:h="16839" w:code="9"/><w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"/></w:sectPr></xml-fragment>
如果单纯的把上面的东西当一个xml看的话,xml-fragment
应该叫根节点,它里面定义了许多的xmlns:xx
,我们把它叫作命名空间,每个文件所包含的命名空间都不一样,所以整体的文件合并都要包含命名空间、内容、图片
三块,合并大致分作以下五个步骤进行
step 0、准备工作
首先要加载文件,并把它转换为上面的xml文件格式,这里我也用到了docx4j、poi、dom4j中的一些东西,来对word、WordprocessingML的快速解析与处理.
在这里我准备了两个文件,source1.docx
、source2.docx
,它们的内容如下:
然后通过代码读入 source1.docx
source2.docx
如下:
// 本例借用dom4j来操作xml (WordprocessingML)SAXReader saxReader = new SAXReader();//取source1的内容,并转换 document来操作XWPFDocument source1 = new XWPFDocument(OPCPackage.open("/Users/tenney/Desktop/source1.docx"));CTBody templateBody = source1.getDocument().getBody();org.dom4j.Document document1 = saxReader.read(new ByteArrayInputStream(templateBody.xmlText().getBytes(Charset.defaultCharset())));//读取 source2的内容XWPFDocument source2 = new XWPFDocument(OPCPackage.open("/Users/tenney/Desktop/source2.docx"));CTBody templateBody2 = source1.getDocument().getBody();org.dom4j.Document document2 = saxReader.read(new ByteArrayInputStream(templateBody.xmlText().getBytes(Charset.defaultCharset())));
step 1、命名空间合并
我们知道每个文档转成WordprocessingML格式xml后,根节点xml-fragment
下面都有 xmlns:xx
的“命名空间”,要确保合并后的文档所有内容能被识别,这些 xmlns:xx
也必须被合并,这一步我们要做的事就是这个。
我们以source1.docx
为目标文件,将source2.docx
中的内容读取过来:
//通过正则,解析形如: xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" ,转换为 键值对,//schems 的结构为: xmlns:w16 = { xmlns:w16 : "http://schemas.microsoft.com/office/word/2018/wordml" }Map<String, NameValuePair<String>> schemas = new HashMap<>();Matcher matcher = Pattern.compile("^<\\S+?\\s+([^><]+\\=[^><]+)>").matcher(templateBody2.xmlText());if (matcher.find()){String[] schemaArr = matcher.group(1).split("\\s+");for (String attr : schemaArr){String[] s = attr.split("=");if(s.length > 1){schemas.put(s[0], new NameValuePair<>(s[0], s[1]));}else {
// logger.warn("不符合要求的xmlns定义:{}", attr);}}}/*** 执行合并, 通过dom4j的方法获取 source1的根节点, 也就是 xml-fragment,然后 通过 addAttribute() 来将 source2中的所有命名空间合并进去* 这个地方也可以通过字符的操作,在xml中直接合并到,但需要自己行处理重复的 命名空间, 通过dom4j的方法,不需要考虑,它会自动处理*/Element root = document1.getRootElement();schemas.values().forEach(s->root.addAttribute(s.getName(), s.getValue().replace("\"","")));
step 2、文档内图片合并
图片的合并分两步,第一步是将图片资源拷贝并追加到目标文档中, 第二步是处理图片的资源关联标识rId
(如果不记得了这是什么东东,请回去看系列文章之二篇)代码如下:
//得到文档2中的所有图片List<XWPFPictureData> allPictures = source2.getAllPictures();final String PICRID_PREFIX = "_PIC_ID_PREFIX";if(allPictures != null && !allPictures.isEmpty()){//步骤一、 将文档1的图片复制到文档2中//这个对象用来存储图片在原文档中的关联的rID,以及合并到新文档中产生的新的rId关联关系Map<String,String> picMap = new HashMap<>();// 记录图片合并前及合并后的IDfor (XWPFPictureData picture : allPictures) {String before = source2.getRelationId(picture);//将原文档中的图片加入到目标文档中//一张图片对应一个<w:drawing>标签,图片存在缓存中 ,并由一个ID来标签图片ID <a:blip r:embed="rId4"/>//合并xml时,图片不会带过来,所以先添加图片,将生成的ID替换原来的IDString after = null;try {if(picture.getData() == null || picture.getData().length < 1){logger.warn("图片数据丢失:{} - {}", before, picture.getFileName());}after = source1.addPictureData(picture.getData(), picture.getPictureType());picMap.put(before, after.replace("rId",PICRID_PREFIX)); //操作_1} catch (InvalidFormatException e) {logger.warn("提取文档图片失败:{}", e.getMessage(), e);}}//步骤二、处理图片在新文档的关联关系, 此时内容尚未合并到新文档,只是为合并做做准备// 将文档2直接转成xml,并处理掉图片rId关联关系String IMG_PATTERN = "r:embed=\"%s\"";String richText = source2.getDocument().getBody().xmlText();for (Map.Entry<String, String> set : picMap.entrySet()) {// 防止图片数量超10时,出现如 rId1 替换掉了 rId1, rId1x, rId1xxx 等,导致ID引用不正确,richText = richText.replace(String.format(IMG_PATTERN, set.getKey()), String.format(IMG_PATTERN, set.getValue()));
// richText = richText.replace(set.getKey(), set.getValue());}richText = richText.replace(PICRID_PREFIX, "rId");//操作_2}
本小节可能有点绕,稍微解释一下:
- 关于rId : 假设文档1、文档2中各有一张图片, 那么文档1中的图片1的
rId=“rId1”
同理文档2中的图片1也是rId=“rId1”
,如果将文档2中的图片1合并到文档1中,此时在文档1中生成的标识就变成了rId="rId2"
,因为文档1之前已经存在了一张图片, 所以合并内容时,也需要将该标识进行相应的替换,否则合并后的文档就是会因为资源引用错乱而无法打开, 也就是上面的 步骤一 - PICRID_PREFIX: 此操作是为了避免图片数量巨多时, 在进行替换时,出现部分字串替换的问题,比如直接用
rId5
替换目标文档中的内容,将会把rId5
、rId50
、rId5x
等所有ID都替换而出错,所以定义一个比较复杂的前缀来处理这个问题。
一般情况下建议先处理图片后,再处理内容,因为处理完图片后,在被合并的文档中一并处理掉图片关联rId, 然后再将处理好的内容一块合并至目标文档,避免合并到目标文档后再去替换rId不小心替换掉了不该替换的内容。
step 3、内容区域合并
这部分内容就比较简单的,简单到就是xml文件或者说字符串的合并,代码如下:
//内容合并 (这块合并比较简单,方法也比较多,反正就是xml文件的合并而已,我这里采用了dom4j来操作)org.dom4j.Document newDoc = saxReader.read(new ByteArrayInputStream(richText.getBytes(Charset.defaultCharset())));//得到待合并的文档根节点以下的所有元素List<Element> appends = newDoc.getDocument().getRootElement().elements();//sectPr 节点是用来定义文档的背景、宽度等设置的, 同一文档不能有多个,所以合并的时候忽略该节点final Set<String> mergeIgnoreNodes = new HashSet<>(Arrays.asList("sectPr"));//获取文档1的元素列表List<Element> elements = document1.getDocument().getRootElement().elements();for (Element e: appends){if(!mergeIgnoreNodes.contains(e.getName())){//从当前位置之后添加新元素,看到这行代码,是不是有点别的想法啊, 对的, 你想在什么位置插入都可以,并不一定要是追加到最后
// elements.add(++idx, (Element) e.detach());elements.add((Element) e.detach());}}
step 4、合并整合输出
没什么可说的,看输出结果吧。
//文档输出FileOutputStream fos = new FileOutputStream("/Users/tenney/Desktop/merge.docx");//将最终的内容重新设置到 word的CTBody中CTBody makeBody = CTBody.Factory.parse( document1.asXML());templateBody.set(makeBody);source1.write(fos);fos.close();
整点总结显得流弊
似乎这一堆的东西看起来有点避轻就重的感脚,为什么网上那么多简便的方法不用,非整的这么复杂,这个问题老头我想,即然你能看到这里也就不用我过多解释了。
我个人认为主要是了解它的原理,其次就是为了根据自己的需要去折腾它了,比如系列文章的后续章节,你将会看到更有趣的东西.
更多折腾可前往围观
(五)、JAVA基于OPENXML的word文档插入、合并、替换操作系列之word文件合并[支持多文件]相关推荐
- java openxml 操作 word,(三)、JAVA基于OPENXML的word文档插入、合并、替换操作系列之html转word...
(三).JAVA基于OPENXML的word文档插入.合并.替换操作系列之html转word 系列笔记传送门 富文本转word文档 准备待转换内容 内容清理与格式化 转换成word文档 输出结果展示 ...
- (一)JAVA基于OPENXML的word文档插入、合并、替换操作系列之基础篇
(一)JAVA基于OPENXML的word文档插入.合并.替换操作系列之基础篇 前言 什么是Open Xml? Open XML SDK 这系列笔记要做点什么? 涉及技术点 关于word.openxm ...
- web系统中巧用word文档的html格式创建多样式的word文档,WEB系统中巧用WORD文档的HTML格式创建多样式的WORD文档...
以计算机和现代网络技术为特征的现代信息技术极大地促进了社会经济的发展,基于各行各业的WEB系统的开发与应用也越来越多. >> WEB系统中巧用WORD文档的HTML格式创建多样式的WORD ...
- Python 操作Word文档插入图片和表格实例演示
Python 操作Word文档插入图片和表格实例演示 效果图 实现过程 ① python-docx 库安装 ② word 文档插入图片演示 ③ word 文档插入表格演示 [ 文章推荐 ] Pytho ...
- python 给word添加背景图片_Python如何使用word文档插入图片和表格
Python如何使用word文档插入图片和表格 发布时间:2020-10-26 13:49:29 来源:亿速云 阅读:101 作者:挣扎的蓝藻 这篇文章运用简单易懂的例子给大家介绍Python如何使用 ...
- 用python将word文档导入数据库_python读取word文档,插入mysql数据库的示例代码
表格内容如下: 1.实现批量导入word文档,取文档标题中的数字作为编号 2.除取上面打钩的内容需要匹配出来入库入库,其他内容全部直接入库mysql # wuyanfeng # -*- coding: ...
- html 插入本地视频,win7系统中Word文档插入本地视频的方法【图文】
win7系统安装Office办公软件无论生活还是办公对大家的帮助都是很大的,有时候编辑word文档需要插入本地视频,很多熟悉Office的朋友都知道在PPT中插入视频很方便,直接点击插入选项卡的视频选 ...
- python打开word并插入图片_Python操作word文档插入图片和表格的实例演示
前言 图片是Word的一种特殊内容,这篇文章主要介绍了关于Python操作word文档,向里面插入图片和表格的相关内容,下面话不多说了,来一起看看详细的代码 实例代码: # -*- coding: U ...
- word文档找不到smartart_图文详解Word文档插入SmartArt图形的方法
这篇文章主要以图文结合的方式详细介绍了Word文档插入SmartArt图形的方法,具体内容如下 win7系统下Word文档插入SmartArt图形的方法分享给大家,我们经常使用Word文档编辑或保存资 ...
最新文章
- Java时区切换时的需要注意
- c 输出空格_Python编程:案例详解输出函数print
- openresty 前端开发进阶一之http后端
- 深入解析hostname
- 初识类的构造方法 c# 1214
- Flex4之皮肤定制
- 网易云音乐失去韩国SM旗下歌曲版权 歌曲下架歌单变灰
- CentOS7下 libvirt+virt-manager 虚拟机迁移配置及错误处理
- OpenJDK 14 与 OpenJDK 8 及多个主要版本的性能基准测试对比
- 「我们的首要之务,并不是遥望模糊的远方,而是专心处理眼前的事务。」---这是卡内基先生所强调的克服忧虑、开创人生的关键。...
- (转)刘巍然-关于公钥与私钥
- 动漫头像1000张萌妹子图片,可以做高清头像壁纸
- 正则方程(机器学习)
- 安川ga700变频器故障码集_安川变频器GA700参数设定出错解决方法
- U盘转换NTFS格式
- activemq-messages-dequeud-but-not-consumed
- 【Linux】RHCE -- RHCSA 认证考试 模拟练习题解析
- Zircon 与 LK
- 软件测试计划分哪五大块,[liu yanling]软件测试分为哪几个计划过程阶段
- 前端面试——JS去除首尾空格代码