最近工作上碰到了这个问题,就研究了一下。

结合了网上几位大哥的成果,我自己又优化了一下。

除了基础的导入参数之外,还优化了参数识别能力,添加了页面复制能力,并且保留了样式。

下面是我测试的word模板:

你好,现在是${time} ,我是${userName} 。

表格1

##{foreachTable}##

table1

序号

姓名

年龄

##{foreachRows}##

${number}

${name}

${age}${unit}

第二页,整个页面复制的模板 ,其实就是用一个上面的表格,然后把第三行内容行单元格的边隐藏掉就行了。

##{foreachTable}##

page1

##{foreachRows}##

页面复制测试。

本页介绍${name},详情看下表:

姓名

${name}

年龄

${age}

思想派别

${philosophy}

能力

${skill}

先是maven的依赖包:

    <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml-schemas</artifactId><version>4.1.0</version></dependency>

然后是我的代码,工具类的代码:

package com.zyy.testPro.word;import org.apache.poi.ooxml.POIXMLDocument;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** @ClassName:* @Description: POI的word文档模板工具类* @CreateDate: 2020-01-08 14:39* @Author:zhaoyangyang*/
public class WordTemplateUtils {/*** 根据模板生成word* @param inputUrl     模板的路径* @param params   需要替换的参数*/public static void getWord(String inputUrl,String outputUrl, Map<String, Object> params){try {XWPFDocument doc = new XWPFDocument(POIXMLDocument.openPackage(inputUrl));@SuppressWarnings("unchecked")Map<String, Object> parametersMap = (Map<String, Object>) params.get("parametersMap");replaceInPara(doc, parametersMap);    //替换文本里面的变量replaceInTable(doc, params); //替换表格里面的变量OutputStream os = new FileOutputStream(outputUrl);doc.write(os);close(os);} catch (IOException e) {e.printStackTrace();}}private static List<XWPFRun> getFormatRuns(XWPFParagraph paragraph){//遍历获取段落中所有的runsList<XWPFRun> runs = paragraph.getRuns();//合并逻辑for (Integer i = 0; i < runs.size(); i++) {String text0 = runs.get(i).getText(runs.get(i).getTextPosition());if (text0 != null && text0.contains("$")) {if((text0.length()>text0.indexOf("$")+1 && '{'==text0.charAt(text0.indexOf("$")+1)) || (runs.get(i+1)!=null && runs.get(i+1).getText(-1).startsWith("{"))){//记录分隔符中间跨越的runs数量,用于字符串拼接和替换int j = i;boolean flag=false;for (; j < runs.size(); j++) {String text1 = runs.get(j).getText(runs.get(j).getTextPosition());if (text1 != null && text1.contains("}")) {flag=true;break;}}if (flag) {//将中间设计的run拼成一个大字符串,并将所有run设为nullStringBuilder newText = new StringBuilder();for (int k = i; k <= j; k++) {String text2 = runs.get(k).text();newText.append(text2);runs.get(k).setText(null, 0);}//处理这种以${结尾的情况,不然这个会丢失if(newText.toString().trim().endsWith("${")){if(runs.size()>j){runs.get(j+1).setText("${"+runs.get(j+1).text(),0);}}//将新字符串分割为理想中的替换字符串和普通字符串String[] newArray= newText.toString().split("\\$\\{");for(String str1:newArray){if(str1.contains("}")){//包含替换字符串String[] newArray2= str1.split("}");if(str1.startsWith("}")){//替换字符串为空,}后面可能跟有普通字符串if(newArray2.length>0){XWPFRun run=paragraph.insertNewRun(i++);run.setText(newArray2[0]);run.getCTR().setRPr(runs.get(i).getCTR().getRPr());}}else{//替换字符串不为空,可能有普通字符床if(newArray2.length>1){XWPFRun run1=paragraph.insertNewRun(i++);run1.setText("${"+newArray2[0]+"}");run1.getCTR().setRPr(runs.get(i).getCTR().getRPr());XWPFRun run2=paragraph.insertNewRun(i++);run2.setText(newArray2[1]);run2.getCTR().setRPr(runs.get(i).getCTR().getRPr());}else{XWPFRun run=paragraph.insertNewRun(i++);run.setText("${"+newArray2[0]+"}");run.getCTR().setRPr(runs.get(i).getCTR().getRPr());}}}else{//普通字符串XWPFRun run=paragraph.insertNewRun(i++);run.setText(str1);run.getCTR().setRPr(runs.get(i).getCTR().getRPr());}}}}}}return runs;}/*** 替换段落里面的变量* @param doc    要替换的文档* @param params 参数*/private static void replaceInPara(XWPFDocument doc, Map<String, Object> 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, Object> params) {List<XWPFRun> runs;if (matcher(para.getParagraphText()).find()) {runs = getFormatRuns(para);for (int i = 0; i < runs.size(); i++) {XWPFRun run = runs.get(i);if(run!=null){String runText = run.toString();if (runText.length()>1 && '$' == runText.charAt(0) && '{' == runText.charAt(1)) {String key=runText.replace("${","").replace("}","").trim();Object value=params.get(key);if (value instanceof String) {//文字run.setText((String)value,0);}else{run.setText("",0);}}}}}}/*** 替换表格里面的变量* @param doc    要替换的文档* @param params 参数*/private static void replaceInTable(XWPFDocument doc, Map<String, Object> params) {Iterator<XWPFTable> iterator = doc.getTablesIterator();XWPFTable table;@SuppressWarnings("unchecked")Map<String, Object> parametersMap = (Map<String, Object>) params.get("parametersMap");while (iterator.hasNext()) {table = iterator.next();String tableText = table.getText();List<XWPFTableCell> tableCells = table.getRows().get(0).getTableCells();// 获取到模板表格第一行,用来判断表格类型// 查找到##{foreach标签,该表格需要处理循环if (tableText.indexOf("##{foreachTable}##") > -1) {//循环处理表格// 查找到##{foreach标签,该表格需要处理循环if (tableCells.size() != 2|| tableCells.get(0).getText().indexOf("##{foreachTable}##") < 0|| tableCells.get(0).getText().trim().length() == 0) {System.out.println("文档中有"+ "表格模板错误,模板表格第一行需要设置2个单元格,"+ "第一个单元格存储表格类型(##{foreachTable}## 或者 ##{foreachTableRow}##),第二个单元格定义数据源。");return;}String dataSource = tableCells.get(1).getText();System.out.println("读取到数据源:"+dataSource);if (!params.containsKey(dataSource)) {System.out.println("文档中" + dataSource + "表格模板数据源缺失");return;}@SuppressWarnings("unchecked")List<Map<String, Object>> tableDataList = (List<Map<String, Object>>) params.get(dataSource);// System.out.println("循环生成表格内部的行");insertTable(table, tableDataList,parametersMap);  //插入数据}else if(matcher(tableText).find()){//静态表格,替换数据replaceTable(table,parametersMap);}}}/*** 为表格替换数据* @param table* @param parametersMap*/private static void replaceTable(XWPFTable table, Map<String, Object> parametersMap){List<XWPFTableRow> rows = table.getRows();for (XWPFTableRow row : rows) {List<XWPFTableCell> cells = row.getTableCells();for (XWPFTableCell cell : cells) {List<XWPFParagraph> paras = cell.getParagraphs();for (XWPFParagraph para : paras) {replaceInPara(para, parametersMap);}}}}/*** 为表格插入数据,行数不够添加新行** @param table     需要插入数据的表格* @param tableList 插入数据集合*/private static void insertTable(XWPFTable table, List<Map<String,Object>> tableList, Map<String, Object> parametersMap) {table.removeRow(0);//删除第一行List<XWPFTableRow> TempTableRows = table.getRows();// 获取模板表格所有行int tagRowsIndex=0;for(int i=0;i<TempTableRows.size();i++){String rowText = TempTableRows.get(i).getCell(0).getText();// 获取到表格行的第一个单元格if (rowText.indexOf("##{foreachRows}##") > -1) {tagRowsIndex = i;i++;//跳过下一行模板行。}else{replaceTableRow(TempTableRows.get(i), parametersMap);// 处理标签替换}}/* 循环生成模板行 */XWPFTableRow tempRow = TempTableRows.get(tagRowsIndex + 1);// 获取到模板行for (int i = tableList.size()-1; i >= 0; i--) {//指定位置,复制行XWPFTableRow newCreateRow=copyRow(table,tempRow,tagRowsIndex + 2);replaceTableRow(newCreateRow, tableList.get(i));// 处理标签替换}table.removeRow(tagRowsIndex);//删除标签行table.removeRow(tagRowsIndex);//删除模板行}/*** 正则匹配字符串** @param str* @return*/private static Matcher matcher(String str) {Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);return  pattern.matcher(str);}/*** 将输入流中的数据写入字节数组** @param in* @return*/public static byte[] inputStream2ByteArray(InputStream in, boolean isClose) {byte[] byteArray = null;try {int total = in.available();byteArray = new byte[total];in.read(byteArray);} catch (IOException e) {e.printStackTrace();} finally {if (isClose) {try {in.close();} catch (Exception e2) {e2.getStackTrace();}}}return byteArray;}/*** 关闭输入流** @param is*/private static void close(InputStream is) {if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}}/*** 关闭输出流** @param os*/private static void close(OutputStream os) {if (os != null) {try {os.close();} catch (IOException e) {e.printStackTrace();}}}public static XWPFTableRow copyRow(XWPFTable table, XWPFTableRow sourceRow, int rowIndex){//在表格指定位置新增一行XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);//复制行属性targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());List<XWPFTableCell> cellList = sourceRow.getTableCells();if (null == cellList) {return null;}//复制列及其属性和内容for (XWPFTableCell sourceCell : cellList) {XWPFTableCell targetCell = targetRow.addNewTableCell();//列属性targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());//段落属性if(sourceCell.getParagraphs()!=null&&sourceCell.getParagraphs().size()>0){List<IBodyElement> bodyElements = sourceCell.getBodyElements();// 所有对象(段落+表格)int curT = 0;// 当前操作表格对象的索引int curP = 0;// 当前操作段落对象的索引IBodyElement currentElement=targetCell.getParagraphs().get(0);for(int i=0;i<bodyElements.size();i++){IBodyElement element=bodyElements.get(i);if(BodyElementType.TABLE.equals(element.getElementType())){// 处理表格//位置指针XmlCursor cursor;if(currentElement instanceof XWPFParagraph){cursor=((XWPFParagraph) currentElement).getCTP().newCursor();}else{cursor=((XWPFTable)currentElement).getCTTbl().newCursor();}cursor.toNextSibling();XWPFTable targetTable = targetCell.insertNewTbl(cursor);XWPFTable sourceTable = element.getBody().getTables().get(curT);//复制表格copyTable(targetTable,sourceTable);currentElement=targetTable;//更改当前元素curT++;}else{XmlCursor cursor;if(currentElement instanceof XWPFParagraph){cursor=((XWPFParagraph) currentElement).getCTP().newCursor();}else{cursor=((XWPFTable)currentElement).getCTTbl().newCursor();}cursor.toNextSibling();XWPFParagraph  targetParagraph=targetCell.insertNewParagraph(cursor);XWPFParagraph  sourceParagraph=sourceCell.getParagraphs().get(curP);//复制段落targetParagraph.getCTP().setPPr(sourceParagraph.getCTP().getPPr());// 设置段落样式for (XWPFRun sourceRun : sourceParagraph.getRuns()) {XWPFRun targetRun = targetParagraph.createRun();targetRun.getCTR().setRPr(sourceRun.getCTR().getRPr());// 设置文本targetRun.setText(sourceRun.text());}currentElement=targetParagraph;//更改当前元素curP++;}}}else{targetCell.setText(sourceCell.getText());}}return targetRow;}/*** 复制表格* @param targetTable* @param sourceTable*/public static void copyTable(XWPFTable targetTable, XWPFTable sourceTable) {targetTable.getCTTbl().setTblPr(sourceTable.getCTTbl().getTblPr());for(int i=0;i<sourceTable.getRows().size();i++){XWPFTableRow row=sourceTable.getRows().get(i);copyRow(targetTable,row,i);}}/*** 根据参数parametersMap对表格的一行进行标签的替换** @author Juveniless* @date 2017年11月23日 下午2:09:24* @param tableRow*            表格行* @param parametersMap*            参数map**/public static void replaceTableRow(XWPFTableRow tableRow, Map<String, Object> parametersMap) {List<XWPFTableCell> tableCells = tableRow.getTableCells();for (XWPFTableCell xWPFTableCell : tableCells) {List<XWPFParagraph> paragraphs = xWPFTableCell.getParagraphs();for (XWPFParagraph xwpfParagraph : paragraphs) {if(xwpfParagraph.getText().indexOf("${")>-1){replaceInPara(xwpfParagraph, parametersMap);}}List<XWPFTable> tables=xWPFTableCell.getTables();for (XWPFTable xwpfTable : tables) {replaceTable(xwpfTable,parametersMap);}}}}

测试类的代码:

package com.zyy.testPro.word;/*** @ClassName:* @Description:* @CreateDate: 2020-01-02 16:34* @Author:zhaoyangyang*/import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class Test {public static void main(String[] args) throws IOException {Map<String, Object> wordDataMap = new HashMap<String, Object>();// 存储报表全部数据Map<String, Object> parametersMap = new HashMap<String, Object>();// 存储报表中不循环的数据parametersMap.put("time", "2020-1-14");parametersMap.put("userName", "valhalla");List<Map<String, Object>> table1 = new ArrayList<Map<String, Object>>();Map<String, Object> map1=new HashMap<>();map1.put("number", "1");map1.put("name", "孔子");map1.put("age", "71");map1.put("unit", "岁");Map<String, Object> map2=new HashMap<>();map2.put("number", "2");map2.put("name", "墨子");map2.put("age", "91");map2.put("unit", "岁");Map<String, Object> map3=new HashMap<>();map3.put("number", "3");map3.put("name", "庄子");map3.put("age", "83");map3.put("unit", "岁");Map<String, Object> map4=new HashMap<>();map4.put("number", "4");map4.put("name", "韩非子");map4.put("age", "47");map4.put("unit", "岁");table1.add(map1);table1.add(map2);table1.add(map3);table1.add(map4);wordDataMap.put("table1", table1);List<Map<String, Object>> page1 = new ArrayList<Map<String, Object>>();Map<String, Object> obj1=new HashMap<>();obj1.put("name", "孔子");obj1.put("age", "71");obj1.put("philosophy", "儒家");obj1.put("skill", "教学");Map<String, Object> obj2=new HashMap<>();obj2.put("name", "墨子");obj2.put("age", "91");obj2.put("philosophy", "墨家");obj2.put("skill", "木工");Map<String, Object> obj3=new HashMap<>();obj3.put("name", "庄子");obj3.put("age", "83");obj3.put("philosophy", "道家");obj3.put("skill", "讲故事");Map<String, Object> obj4=new HashMap<>();obj4.put("name", "韩非子");obj4.put("age", "47");obj4.put("philosophy", "法家");obj4.put("skill", "权谋");page1.add(obj1);page1.add(obj2);page1.add(obj3);page1.add(obj4);wordDataMap.put("page1", page1);wordDataMap.put("parametersMap", parametersMap);WordTemplateUtils.getWord("C:\\ZhaoYangyang\\new doc.docx","C:\\ZhaoYangyang\\new doc2.docx",wordDataMap);}}

然后,就会看到,生成的文档。当然你也可以把它转换成byte[],传到浏览器下载。

就是这样。欢迎大家交流技术

基于POI的wod模板文件,导入参数,导出最终文件相关推荐

  1. Excel文件导入,导出

    前端Excel文件导入,导出 Excel文件导入,导出 功能快捷键 文件导入 文件解析 如何改变文本的样式 exportExcel.js 生成一个适合你的列表 创建一个表格 设定内容居中.居左.居右 ...

  2. springboot使用jxls导出excel___(万能通用模板)--- SpringBoot导入、导出Excel文件___SpringBoot整合EasyExcel模板导出Excel

    springboot使用jxls导出excel 实现思路: 首先在springBoot(或者SpringCloud)项目的默认templates目录放入提前定义好的Excel模板,然后在具体的导出接口 ...

  3. (万能通用模板)--- SpringBoot导入、导出Excel文件

    先把项目的demo发一下,看完文章可以看一下,demo 前言:最近做项目过程中使用到了一个权限管理框架:若依,使用过程中发现他的文件导入和导出功能非常的实用,在这里特此做一个小demo跟大家分享一下. ...

  4. php inputcsv,php实现CSV文件导入和导出

    这篇文章主要介绍了php实现CSV文件导入和导出的方法,具有一定的参考价值,需要的朋友可以参考下 项目开发中,很多时候要将外部CSV文件导入到数据库中或者将数据导出为CSV文件,那么具体该如何实现呢? ...

  5. h5页面如何预览excel文件_如何使用JavaScript实现前端导入和导出excel文件?(H5编辑器实战复盘)...

    前言 最近笔者终于把H5-Dooring的后台管理系统初步搭建完成, 有了初步的数据采集和数据分析能力, 接下来我们就复盘一下其中涉及的几个知识点,并一一阐述其在Dooring H5可视化编辑器中的解 ...

  6. 如何使用JavaScript实现前端导入和导出excel文件(H5编辑器实战复盘)

    前言 最近笔者终于把H5-Dooring的后台管理系统初步搭建完成, 有了初步的数据采集和数据分析能力, 接下来我们就复盘一下其中涉及的几个知识点,并一一阐述其在Dooring H5可视化编辑器中的解 ...

  7. 从零开始学R(四)——常用命令:帮助,文件导入与导出与基本操作

    R的常用命令 R的命令非常多,但是有一些命令是非常常用的,在这里我做一个汇总,便于大家使用.(Tips:关于下载和安装R的package以及工作目录的获得与设置命令在第二篇已经聊过,此时就不再赘述了, ...

  8. 如何使用JavaScript导入和导出Excel文件

    本文由葡萄城技术团队于原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. JavaScript是一个涵盖多种框架.直译式.可以轻松自定义客户端的脚本 ...

  9. java mvc 导入_Java SpringMVC文件导入和导出

    J示例代码: @ResponseBody @RequestMapping(value = "/fileUpload", method = RequestMethod.POST) p ...

最新文章

  1. 那些方式可以合并php数组,合并数组(PHP)
  2. 通过正则表达式分句提取中文内容
  3. linux日志自动按天保存,linux实现按天生成日志文件并自动清理
  4. Fiddler使用教程(收藏)
  5. 阿里钉钉向1000万企业组织免费开放在家办公系统
  6. fiddle下载安装教程
  7. Qt制作音乐播放器按钮
  8. 如何将mac中的资料拷贝到U盘,移动硬盘(实用!!!)
  9. 实体书回暖?码洋过10,000,000的随想
  10. 新版本微信PC端小程序打不开 一直加载 白屏等问题及解决方案
  11. Qt QImage scaled方法缩放中的问题
  12. MySql查询本周或下周过生日的人
  13. 上海浦东新区计算机学校排名2015,2015年上海市浦东新区初级中学最新排名.pdf
  14. isin用计算机怎么打,教你如何利用hackintool给你的电脑打缓冲帧
  15. ad15原理图中变压器种类_最简单的变压电路图大全(十一款最简单的变压电路设计原理图详解)...
  16. 笔记本重装后回收站文件怎么恢复
  17. 吸塑包装盒是怎么被制作生产出来的
  18. 【论文阅读】Dimensionality Reduction by Learning an Invariant Mapping
  19. puppet3d_发挥作用:Puppet3D如何使您的游戏栩栩如生
  20. pyscript的简单应用

热门文章

  1. 大数据千亿级离线数仓项目第一天 环境部署和etl
  2. 计算机院校看重保研er的本科背景吗?
  3. Win11删除EFI分区
  4. 杨永智:创业者需具备的六大基因 五大攻略(下)
  5. 信息学奥赛一本通2066
  6. vCenter学习笔记
  7. 网络实验之EtherChannel技术实践
  8. Charles工具使用教程,以及注意事项。
  9. GPT时代,最令人担心的其实是“塔斯马尼亚效应”
  10. 浅谈面试与简历——总结于尚硅谷视频《程序员面试指南》