POI操作word填充数据,合并多个word为一个,遇到一些问题的解决
POI操作word填充数据,合并多个word为一个,遇到一些问题的解决
最近搞一个向word模板中替换占位符 填充数据,然后将多个word合并在一起的方法。网上一搜有很多资料,现在在这儿对过程中遇到的一些问题进行描述。 测试工程在这儿需要的可以点击下载.包括所需要的所有jar包和测试程序和word模板等。
问题描述
java 调用poi操作word,我用的是2007以上的word,格式是.docx,不适用于2007以前的.doc 格式的 word ,遇到的问题主要有三个:
- word段落中的占位符替换不成功 ;
- word表格中的占位符替换之后样式丢失 ;
- word合并,设置分页符之后,出现空白页的情况
问题分析
占位符替换不成功,主要是因为 段落的中的占位符 解析成块时 被拆分开了,所以没匹配上。
解决办法是,对段落XWPFParagraph进行解析的时候,对解析出来的块XWPFRun的内容进行判断,若存在占位符的开始标记,则开始存储块XWPFRun,并将块的内容进行拼接,最后遇到占位符的结束标记,则将存储的块 保留第一个,后面的块的内容都置为空,再将拼接的内容进行占位符替换 后赋给保留的第一个块。详细逻辑 看后面代码。表格中的替换,之前使用的是 在遍历到表格的单元格的时候,直接将内容清空,然后将替换好的内容填充进去,导致样式丢失了,所以不能这样替换。应该是遍历到单元格后,获取单元格中段落,调用段落中的替换占位符的方法,这样就能够保留表格内容的样式了。
相关代码如下:
/*** 替换表格里面的变量* @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);}}}}}
- 拼接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为一个,遇到一些问题的解决相关推荐
- 简单实现POI操作Excel生成数据透视图
简单实现POI操作Excel生成数据透视图 需求 思考 尝试 实现 搞完收工,去画模板了 需求 财务部门需要做一自动导出数据报表的功能,其中要有指定格式的数据透视图(柱状图.饼状图等等) 思考 正常来 ...
- poi操作excel之列合并
poi操作excel之列合并 每篇一句励志:学会快乐,因为只有开心度过每一天,活得才精彩. // 此代码只支持多列合并 // 参数1:sheet.参数2:要合并的列 public static XSS ...
- 填充数据合并单元格并导出excel代码实现
以下代码是动态填充数据,这些数据是从数据库取得的批量的,并合并单元格,最终导出excel.但是有一点需要注意,一定是先填充数据在合并单元格,而且每个单元格的样式都是提前设置好的 . 举个例子:合并单元 ...
- pandas基础操作大全之数据合并
在pandas 基础操作大全之数据读取&清洗&分析中介绍了pandas常见的数据处理操作,现在继续对pandas常用的数据合并操作做下介绍,便于大家快速了解,也方便后续需要时快速查询. ...
- POI 操作 Excel -大数据量高效读写
前言 poi的读取的三种模式 模式 说明 读写性 SXSSF 内存中保留一定行数数据,超过行数,将索引最低的数据刷入硬盘 只写 eventmodel 基于事件驱动,SAX的方式解析excel,cup和 ...
- phpexcel导出大量数据合并单元格_PHPExcel处理一个单元格内多条数据拆分成多个单元格多条数据...
日期: 2020年6月17日 分类: PHP Tags: Excel 阅读量: 1,221 一.描述需求 如图,当我们遇到一条数据中,某一项内容有多条数据,为了方便文档查阅,我们需要在那一项数据进行拆 ...
- 把计算机知识列表合为一列,怎么把相同表格的数据合并
1. 在EXCEL表格里面怎么把相同名称的不同数据合并到一起 1.Excel打开文档. 2.Excel打开文档后,点击插入数据透视表. 3.点击插入数据透视表后,选中区域中就框选数据,然后选择一个要放 ...
- 读取Excel 数据并写入到Word示例
读取Excel 数据并写入到Word示例 0x01 读取Excel 数据并写入到Word示例 1.1 配置pom.xml 1.2 配置 application.properties 1.3 自定义配置 ...
- 基于Python中docx与docxcompose批量合并多个Word文档文件并逐一添加分页符
现有多个Word文件,需将其按名称顺序合并为一个新的Word文件,且需保证每一次合并时,都另起一页(即新的Word文件一页中,不能出现两个及以上的原本Word文件的内容). 一般的,实现多个W ...
最新文章
- Gradle for Android 第三篇( 依赖管理 )
- php脏在哪里,逍遥游户外联盟-人身上最“脏”的地方是哪里?3个地方,建议经常清洗 -...
- 使用 pylint 检测python代码质量(sonar-scanner调用pylint,然后数据交给sonar服务器)
- mysql数据库备份及还原
- 毫秒级预测,性能卓越!检测、跟踪、行为识别都搞定!这套行人分析系统重磅开源!...
- 关于E: Sub-process /usr/bin/dpkg returned an error code (1)错误解决
- 系统学习深度学习(十九)--GoogLeNetV1,V2,V3
- 2021高考武汉查询成绩时间,2021高考完什么时候可以查分数 查成绩的时间
- 淘宝自动下单软件//下单神器、、
- uniapp--微信小程序--云开发生成短连接h5跳转小程序
- 史上最简单的封装教程,五分钟学会封装系统(以封装Windows 7为例)
- ubuntu死机咋办_Ubuntu死机解决方法汇总
- 固态硬盘(SSD) 和机 械硬盘(HDD) 优缺点比较
- openmv一些常见问题与心得总结
- 4年Java经验面试总结(转)
- SQL Round 取整千 整百 整万
- 【原创】遥感影像法分析河流演变
- Google打印没有彩色,浏览器打印预览没有背景颜色和没有颜色
- 简单好用的网络保护软件 lulu for mac 支持big sur系统
- 嵌入式软件开发培训笔记——Java第二天(运算符、程序结构、数组与继承)
热门文章
- 在PS里制作小清新风格的天气图标
- 前端基础HTML5CSS3提高
- Python 实现视频剪辑
- Linux常用命令——poweroff命令
- 树莓派的入门配置,包括ftp 远程桌面,中问输入法,语音模块配置
- 浙江大学计算机应用基础登录,浙江大学远程教育计算机应用基础1.计算机基础知识题...
- 硬核浪漫!全球独有主动闪烁的卫星,用摩斯电码在七夕天空搭起一座“鹊桥”...
- 聊聊通用会员卡/通用打折卡
- (翻译)账号注册模式( Account Registration)
- 最新NZ在线源码交易平台虚拟交易系统多商家版PHP源码