POI操作word填充数据,合并多个word为一个,遇到一些问题的解决

最近搞一个向word模板中替换占位符 填充数据,然后将多个word合并在一起的方法。网上一搜有很多资料,现在在这儿对过程中遇到的一些问题进行描述。 测试工程在这儿需要的可以点击下载.包括所需要的所有jar包和测试程序和word模板等。

问题描述

java 调用poi操作word,我用的是2007以上的word,格式是.docx,不适用于2007以前的.doc 格式的 word ,遇到的问题主要有三个:

  1. word段落中的占位符替换不成功
  2. word表格中的占位符替换之后样式丢失
  3. word合并,设置分页符之后,出现空白页的情况

问题分析

  1. 占位符替换不成功,主要是因为 段落的中的占位符 解析成块时 被拆分开了,所以没匹配上。

    解决办法是,对段落XWPFParagraph进行解析的时候,对解析出来的块XWPFRun的内容进行判断,若存在占位符的开始标记,则开始存储块XWPFRun,并将块的内容进行拼接,最后遇到占位符的结束标记,则将存储的块 保留第一个,后面的块的内容都置为空,再将拼接的内容进行占位符替换 后赋给保留的第一个块。详细逻辑 看后面代码。

  2. 表格中的替换,之前使用的是 在遍历到表格的单元格的时候,直接将内容清空,然后将替换好的内容填充进去,导致样式丢失了,所以不能这样替换。应该是遍历到单元格后,获取单元格中段落,调用段落中的替换占位符的方法,这样就能够保留表格内容的样式了。
    相关代码如下:

/*** 替换表格里面的变量* @param doc 要替换的文档* @param params 参数*/private static void replaceInTable(XWPFDocument doc, Map<String, String> params) {Iterator<XWPFTable> iterator = doc.getTablesIterator();XWPFTable table;List<XWPFTableRow> rows;List<XWPFTableCell> cells;List<XWPFParagraph> paras;while (iterator.hasNext()) {table = iterator.next();rows = table.getRows();for (XWPFTableRow row : rows) {cells = row.getTableCells();for (XWPFTableCell cell : cells) {//这种方法会导致表格中的格式丢失/*String cellTextString = cell.getText();for (Entry<String, Object> e : params.entrySet()) {if (cellTextString.contains("${"+e.getKey()+"}"))cellTextString = cellTextString.replace("${"+e.getKey()+"}", e.getValue().toString());}cell.removeParagraph(0);if(cellTextString.contains("${") && cellTextString.contains("}")){cellTextString = "";}cell.setText(cellTextString);*///调用段落替换占位符的方式paras = cell.getParagraphs();for (XWPFParagraph para : paras) {replaceInPara(para, params);}}}}}
  1. 拼接word,想要的效果是另起一页进行拼接,所以设置分页符(不设置分页符则是内容处进行拼接),但是会出现,空白页的情况。原因是 设置分页符时,会在页眉页脚增加一些东西,若果你这页内容比较满,设置分页符之后,就超出了一页,拼接时候 再另起一页,中间就空白了一页。解决方式是,将模板内容不要设置太满。但是内容会因为我填充的数据进行变化,所以不好控制。我最后发现,设置分页符拼接后的word页眉有个标记,这个标记就是控制分页的,所以我将这个标记复制到我的模板的页眉上,然后我程序中就不设置分页符,拼接后的word也是另起一页的效果,也不会出现空白页了。
    这个就是控制分页的分页符

相关代码如下:

//两个对象进行追加public static XWPFDocument mergeWord(XWPFDocument document1,XWPFDocument document2) throws Exception {//设置分页符---当刚好一页数据时,会导致出现空白页,后面出现分页符导致格式有一点差异//解决方法是,在模板头上增加分页符//XWPFParagraph p = document2.createParagraph();//p.setPageBreak(true);  CTBody src1Body = document1.getDocument().getBody();CTBody src2Body = document2.getDocument().getBody();XmlOptions optionsOuter = new XmlOptions();optionsOuter.setSaveOuter();String appendString = src2Body.xmlText(optionsOuter);String srcString = src1Body.xmlText();String prefix = srcString.substring(0,srcString.indexOf(">")+1);String mainPart = srcString.substring(srcString.indexOf(">")+1,srcString.lastIndexOf("<"));String sufix = srcString.substring( srcString.lastIndexOf("<") );String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));CTBody makeBody = CTBody.Factory.parse(prefix+mainPart+addPart+sufix);src1Body.set(makeBody);return document1;}

最后整合

最后将整个逻辑调整了下,做了测试感觉还行,问题是都解决了,但是后续还要和项目整合,还需要将一些方法给拆开。上面给了 测试工程的下载链接,资源都是网上找的。
模板:

替换后的效果:

下面是完整代码:

package com.cn.gwssi.demo;import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import org.apache.commons.io.IOUtils;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;/*** 对word模板填充数据,并合并成一个word*/
public class POIMergeDocUtil {public static void main(String[] args) throws Exception {List<String> paths = new ArrayList<String>();paths.add("d:\\1.docx");paths.add("d:\\2.docx");paths.add("d:\\3.docx");Map<String, String> param = new  HashMap<String, String>();param.put("username", "梅西");param.put("country", "阿根廷");param.put("type", "挖煤");String destDocx = "d:\\test.docx";exportWordUtils(paths,param,destDocx);System.out.println("执行成功=====");}/*** 对多个word模板填充数据,并合并成一个word输出* @param paths* @param param* @param destDocx* @throws Exception*/public static void exportWordUtils(List<String> paths,Map<String,String> param,String destDocx) throws Exception{OutputStream dest = null;List<XWPFDocument> xwpfDocuments = new ArrayList<XWPFDocument>();//循环向word填充数据for (String path : paths) {XWPFDocument xwpfDocument = generateWord(param,path);xwpfDocuments.add(xwpfDocument);}//合并wordXWPFDocument xwpfDocument = xwpfDocuments.get(0);for (int i = 1; i < xwpfDocuments.size(); i++) {xwpfDocument=mergeWord(xwpfDocument,xwpfDocuments.get(i));   }//输出worddest = new FileOutputStream(destDocx);xwpfDocument.write(dest);IOUtils.closeQuietly(dest);}/*** 替换word占位符的内容* @param param* @param filePath* @return*/public static XWPFDocument generateWord(Map<String, String> param, String filePath) {XWPFDocument doc = null;try {doc = new XWPFDocument(POIXMLDocument.openPackage(filePath));if (param != null && param.size() > 0){//处理段落replaceInPara(doc, param);//替换表格里面的变量replaceInTable(doc, param);}} catch (Exception e) {e.printStackTrace();}return doc;}/*** 遍历word段落信息* @param doc 要替换的文档* @param params 参数*/private static void replaceInPara(XWPFDocument doc, Map<String, String> params) {Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();XWPFParagraph para;while (iterator.hasNext()) {para = iterator.next();replaceInPara(para, params);}}/*** 替换段落里面的变量* @param para 要替换的段落* @param params 参数*/private static void replaceInPara(XWPFParagraph para, Map<String, String> param) {List<XWPFRun> runs;String tempString = "";char lastChar = ' ';if (matcher(para.getParagraphText()).find()) {runs = para.getRuns();Set<XWPFRun> runSet = new HashSet<XWPFRun>();for (XWPFRun run : runs) {String text = run.getText(0);System.out.println("=======>"+text);if(text==null)continue;text = replaceText(text,param);run.setText("",0);run.setText(text,0);for(int i=0;i<text.length();i++){char ch = text.charAt(i);if(ch == '$'){runSet = new HashSet<XWPFRun>();runSet.add(run);tempString = text;}else if(ch == '{'){if(lastChar == '$'){if(runSet.contains(run)){}else{runSet.add(run);tempString = tempString+text;}}else{runSet = new HashSet<XWPFRun>(); tempString = "";}}else if(ch == '}'){if(tempString!=null&&tempString.indexOf("${")>=0){if(runSet.contains(run)){}else{runSet.add(run);tempString = tempString+text;}}else{runSet = new HashSet<XWPFRun>(); tempString = ""; }if(runSet.size()>0){String replaceText = replaceText(tempString,param);if(!replaceText.equals(tempString)){int index = 0;XWPFRun aRun = null;for(XWPFRun tempRun:runSet){tempRun.setText("",0);if(index==0){aRun = tempRun;}index++;}aRun.setText(replaceText,0);}runSet = new HashSet<XWPFRun>(); tempString = ""; }}else{if(runSet.size()<=0)continue;if(runSet.contains(run))continue;runSet.add(run);tempString = tempString+text;}lastChar = ch;}}//这种方法会导致占位符被拆开解析,不能识别替换掉/*for (int i = 0; i < runs.size(); i++) {XWPFRun run = runs.get(i);String runText = run.toString();System.out.println("====>run:"+runText);matcher = this.matcher(runText);if (matcher.find()) {while ((matcher = this.matcher(runText)).find()) {runText = matcher.replaceFirst(String.valueOf(param.get(matcher.group(1))));}// 直接调用XWPFRun的setText()方法设置文本时,在底层会重新创建一个XWPFRun,把文本附加在当前文本后面,// 所以我们不能直接设值,需要先删除当前run,然后再自己手动插入一个新的run。para.removeRun(i);if(runText.equals("null")){runText="";}para.insertNewRun(i).setText(runText);}}*/}}/*** 替换表格里面的变量* @param doc 要替换的文档* @param params 参数*/private static void replaceInTable(XWPFDocument doc, Map<String, String> params) {Iterator<XWPFTable> iterator = doc.getTablesIterator();XWPFTable table;List<XWPFTableRow> rows;List<XWPFTableCell> cells;List<XWPFParagraph> paras;while (iterator.hasNext()) {table = iterator.next();rows = table.getRows();for (XWPFTableRow row : rows) {cells = row.getTableCells();for (XWPFTableCell cell : cells) {//这种方法会导致表格中的格式丢失/*String cellTextString = cell.getText();for (Entry<String, Object> e : params.entrySet()) {if (cellTextString.contains("${"+e.getKey()+"}"))cellTextString = cellTextString.replace("${"+e.getKey()+"}", e.getValue().toString());}cell.removeParagraph(0);if(cellTextString.contains("${") && cellTextString.contains("}")){cellTextString = "";}cell.setText(cellTextString);*///调用段落替换占位符的方式paras = cell.getParagraphs();for (XWPFParagraph para : paras) {replaceInPara(para, params);}}}}}/*** 替换占位符* @param text* @param map* @return*/private static String replaceText(String text, Map<String, String> map) {if(text != null){/*for (Entry<String, String> entry : map.entrySet()) {if (text.contains("${"+entry.getKey()+"}")){text = text.replace("${"+entry.getKey()+"}", entry.getValue().toString());}}*/Matcher matcher = matcher(text);if (matcher.find()) {while ((matcher = matcher(text)).find()) {text = matcher.replaceFirst(String.valueOf(map.get(matcher.group(1))));}if(text.equals("null")){text="";}}}return text;}/*** 正则匹配字符串* @param str* @return*/private static Matcher matcher(String str) {Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);Matcher matcher = pattern.matcher(str);return matcher;}//两个对象进行追加public static XWPFDocument mergeWord(XWPFDocument document1,XWPFDocument document2) throws Exception {//设置分页符---当刚好一页数据时,会导致出现空白页,后面出现分页符导致格式有一点差异//解决方法是,在模板头上增加分页符//XWPFParagraph p = document2.createParagraph();//p.setPageBreak(true); CTBody src1Body = document1.getDocument().getBody();CTBody src2Body = document2.getDocument().getBody();XmlOptions optionsOuter = new XmlOptions();optionsOuter.setSaveOuter();String appendString = src2Body.xmlText(optionsOuter);String srcString = src1Body.xmlText();String prefix = srcString.substring(0,srcString.indexOf(">")+1);String mainPart = srcString.substring(srcString.indexOf(">")+1,srcString.lastIndexOf("<"));String sufix = srcString.substring( srcString.lastIndexOf("<") );String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));CTBody makeBody = CTBody.Factory.parse(prefix+mainPart+addPart+sufix);src1Body.set(makeBody);return document1;}}

POI操作word填充数据,合并多个word为一个,遇到一些问题的解决相关推荐

  1. 简单实现POI操作Excel生成数据透视图

    简单实现POI操作Excel生成数据透视图 需求 思考 尝试 实现 搞完收工,去画模板了 需求 财务部门需要做一自动导出数据报表的功能,其中要有指定格式的数据透视图(柱状图.饼状图等等) 思考 正常来 ...

  2. poi操作excel之列合并

    poi操作excel之列合并 每篇一句励志:学会快乐,因为只有开心度过每一天,活得才精彩. // 此代码只支持多列合并 // 参数1:sheet.参数2:要合并的列 public static XSS ...

  3. 填充数据合并单元格并导出excel代码实现

    以下代码是动态填充数据,这些数据是从数据库取得的批量的,并合并单元格,最终导出excel.但是有一点需要注意,一定是先填充数据在合并单元格,而且每个单元格的样式都是提前设置好的 . 举个例子:合并单元 ...

  4. pandas基础操作大全之数据合并

    在pandas 基础操作大全之数据读取&清洗&分析中介绍了pandas常见的数据处理操作,现在继续对pandas常用的数据合并操作做下介绍,便于大家快速了解,也方便后续需要时快速查询. ...

  5. POI 操作 Excel -大数据量高效读写

    前言 poi的读取的三种模式 模式 说明 读写性 SXSSF 内存中保留一定行数数据,超过行数,将索引最低的数据刷入硬盘 只写 eventmodel 基于事件驱动,SAX的方式解析excel,cup和 ...

  6. phpexcel导出大量数据合并单元格_PHPExcel处理一个单元格内多条数据拆分成多个单元格多条数据...

    日期: 2020年6月17日 分类: PHP Tags: Excel 阅读量: 1,221 一.描述需求 如图,当我们遇到一条数据中,某一项内容有多条数据,为了方便文档查阅,我们需要在那一项数据进行拆 ...

  7. 把计算机知识列表合为一列,怎么把相同表格的数据合并

    1. 在EXCEL表格里面怎么把相同名称的不同数据合并到一起 1.Excel打开文档. 2.Excel打开文档后,点击插入数据透视表. 3.点击插入数据透视表后,选中区域中就框选数据,然后选择一个要放 ...

  8. 读取Excel 数据并写入到Word示例

    读取Excel 数据并写入到Word示例 0x01 读取Excel 数据并写入到Word示例 1.1 配置pom.xml 1.2 配置 application.properties 1.3 自定义配置 ...

  9. 基于Python中docx与docxcompose批量合并多个Word文档文件并逐一添加分页符

      现有多个Word文件,需将其按名称顺序合并为一个新的Word文件,且需保证每一次合并时,都另起一页(即新的Word文件一页中,不能出现两个及以上的原本Word文件的内容).   一般的,实现多个W ...

最新文章

  1. Gradle for Android 第三篇( 依赖管理 )
  2. php脏在哪里,逍遥游户外联盟-人身上最“脏”的地方是哪里?3个地方,建议经常清洗 -...
  3. 使用 pylint 检测python代码质量(sonar-scanner调用pylint,然后数据交给sonar服务器)
  4. mysql数据库备份及还原
  5. 毫秒级预测,性能卓越!检测、跟踪、行为识别都搞定!这套行人分析系统重磅开源!...
  6. 关于E: Sub-process /usr/bin/dpkg returned an error code (1)错误解决
  7. 系统学习深度学习(十九)--GoogLeNetV1,V2,V3
  8. 2021高考武汉查询成绩时间,2021高考完什么时候可以查分数 查成绩的时间
  9. 淘宝自动下单软件//下单神器、、
  10. uniapp--微信小程序--云开发生成短连接h5跳转小程序
  11. 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例)
  12. ubuntu死机咋办_Ubuntu死机解决方法汇总
  13. 固态硬盘(SSD) 和机 械硬盘(HDD) 优缺点比较
  14. openmv一些常见问题与心得总结
  15. 4年Java经验面试总结(转)
  16. SQL Round 取整千 整百 整万
  17. 【原创】遥感影像法分析河流演变
  18. Google打印没有彩色,浏览器打印预览没有背景颜色和没有颜色
  19. 简单好用的网络保护软件 lulu for mac 支持big sur系统
  20. 嵌入式软件开发培训笔记——Java第二天(运算符、程序结构、数组与继承)

热门文章

  1. 在PS里制作小清新风格的天气图标
  2. 前端基础HTML5CSS3提高
  3. Python 实现视频剪辑
  4. Linux常用命令——poweroff命令
  5. 树莓派的入门配置,包括ftp 远程桌面,中问输入法,语音模块配置
  6. 浙江大学计算机应用基础登录,浙江大学远程教育计算机应用基础1.计算机基础知识题...
  7. 硬核浪漫!全球独有主动闪烁的卫星,用摩斯电码在七夕天空搭起一座“鹊桥”...
  8. 聊聊通用会员卡/通用打折卡
  9. (翻译)账号注册模式( Account Registration)
  10. 最新NZ在线源码交易平台虚拟交易系统多商家版PHP源码