模板格式

合同编号:{ contractNumber},合同序号:{ contractSequence}
买家: { buyerName}
卖家:{ sellerName}
业务员: { salesName}
项目名:{ projectName}
生效时间:{ effectiveTime}
生效时间:{ createTime }

商品名 出厂价 图片
row 1 col 1 row 1 col 2 $img{avatar}
row 2 col 1 row 2 col 2
商品名 出厂价 到位价 出货地
$begin{contractGoodsDTOS!}
{ goodsName } { outFactoryPrice! } 元/{unit} {!priceFlag(卖家运费规则)else({ shipPrice! }元/{unit})} {outGoodsAddress}
$end
文字 同一表格多个循环
$begin{workExperienceList!}
{companyName} {position}
$end
加价项名 加价项 价格
$begin{ contractAdditemDTOS! }
{ additemName } {$(children({ additemName }\n))} {$(children({ additemPrice }元/{additemUnit}\n))}
$end
合同序号 {contractSequence } 合同编号 { contractNumber }
生效时间 { effectiveTime }

3,本通知与前述通知有抵触之处,以本通知为准。
{sellerName}
{createTime}

语法

  • 普通文本 {}
  • 空值处理 {field!}
  • 行内文本循环 {$(children({ itemName },))}
  • 条件判断 {!flag(为true的时候)else(为false的时候={ shipPrice! }元/{unit})}
  • 换行符 \\n
  • 表格行循环 (支持同一表格多次循环)不支持嵌套
  • $begin{field}
  • {name} | {age}
  • $end

maven依赖

<!-- 只要这一个依赖即可 -->
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version>
</dependency>

解析代码

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.collections.CollectionUtils;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** @author zhenx* @date 2020/7/29 15:15* @description** 普通文本 {}* 空值处理 {field!}* 文本循环 {$(children({ itemName },))}* 条件判断 {!flag(为true的时候)else(为false的时候={ shipPrice! }元/{unit})}* 换行符 \\n** 表格行循环 (支持同一表格多次循环)不支持嵌套* $begin{field}* {name} | {age}* $end*/
public class WordTemplateUtils {private static final Logger log = LoggerFactory.getLogger(WordTemplateUtils.class);// 行内循环private static final Pattern FOREACH_TEXT = Pattern.compile("\\{\\$\\(.+?\\([\\d\\D]+?\\)\\)}");// 条件语句private static final Pattern BOOL_TEXT = Pattern.compile("\\{!.+?\\(.+?\\)else\\(.+?\\)}");// 普通文本private static final Pattern PAIN_TEXT = Pattern.compile("\\{.+?}");// 日期格式private static final String DATE_FORMAT = "yyyy-MM-dd";/*** 处理方法* @param docxFile 模版文件* @param model 填充模版的参数*/public static byte[] process(File docxFile, Object model) throws Exception {if (!docxFile.exists() || !docxFile.isFile()) {throw new IllegalArgumentException("文件为空");}return process(new FileInputStream(docxFile), model);}/*** @param input 模版文件* @param model 填充模版的参数*/public static byte[] process(InputStream input, Object object) throws Exception {XWPFDocument doc = new XWPFDocument(input);List<XWPFParagraph> paragraphs = doc.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {replaceParagraph(paragraph, object);}for (XWPFTable table : doc.getTables()) {try {handleTable(table, object);} catch (IllegalArgumentException e) {log.error("操作表格失败, 删除表格: {}", e.getMessage());deleteTable(table);}}ByteArrayOutputStream outputStream = new ByteArrayOutputStream();doc.write(outputStream);byte[] bytes = outputStream.toByteArray();doc.close();return bytes;}/*** 处理表格* @param table* @param param* @throws IllegalArgumentException*/private static void handleTable(XWPFTable table, Object param) throws Exception {// 处理表格内循环Set<Integer> deleteRows = new HashSet<>();handleTableEach(0, table, param, deleteRows);deleteRow(table, deleteRows);// 处理表格内普通文本handleTableText(table, param);}private static void deleteTable(XWPFTable table){List<XWPFTableRow> rows = table.getRows();int rowLength = rows.size();for (int i = 0; i < rowLength; i++) {table.removeRow(0);}}/*** 处理段落* @param paragraph* @param object*/private static void replaceParagraph(XWPFParagraph paragraph, Object object) {List<XWPFRun> runs = paragraph.getRuns();String text = paragraph.getText();Matcher matcher = PAIN_TEXT.matcher(text);if (!matcher.find()) {return;}for (XWPFRun run : runs) {run.setText("", 0);}XWPFRun targetRun = runs.get(0);String result = handleForeachText(text, object);if (result.contains("\\n")) {String[] split = result.split("\\\\n");for (String s : split) {XWPFRun run = paragraph.createRun();copyRun(run, targetRun);run.setText(s);run.addBreak();}} else {targetRun.setText(result, 0);}}/*** 删除行* @param table* @param deleteRows*/private static void deleteRow(XWPFTable table, Set<Integer> deleteRows) {Integer[] array = deleteRows.toArray(new Integer[]{});Arrays.sort(array);int i = 0;for (Integer r: array) {if(i == 0) {table.removeRow(r);i++;}else{table.removeRow(r - i);i++;}}}/*** 表格内循环* @param pos* @param table* @param param* @param deleteRows* @throws IllegalArgumentException*/private static void handleTableEach(int pos, XWPFTable table, Object param, Set<Integer> deleteRows) throws Exception {List<XWPFTableRow> rows = table.getRows();Set<Integer> tplListRows = new HashSet<>();List currentIterator = null;int start = -1;int end = -1;int i = pos;row:for (; i < rows.size(); i++) {XWPFTableRow row = rows.get(i);List<XWPFTableCell> cells = row.getTableCells();for (XWPFTableCell cell : cells) {String text = cell.getText().trim();if(text.matches("^\\$begin\\{.+?}$")) {if (currentIterator != null) {log.error("循环嵌套了: row {}", i);throw new RuntimeException("不允许循环嵌套");}start = i;String listKey = text.substring(7, text.length()-1).trim();boolean emptyFlag = listKey.endsWith("!");if (emptyFlag) {listKey = listKey.substring(0, listKey.length() - 1);}currentIterator = (List) getField(param, listKey);if (CollectionUtils.isEmpty(currentIterator)) {if (emptyFlag) {currentIterator = new ArrayList();} else {log.error("循环字段 {} 为空", listKey);throw new IllegalArgumentException();}}continue row;}else if (text.matches("^\\$end$")) {end = i;tplListRows.remove(i);break row;}else if (start != -1) {tplListRows.add(i);}}}if (start == -1 || end == -1 || CollectionUtils.isEmpty(tplListRows)) {return;}int insertRow = end + 1;for (Object o : currentIterator) {for (Integer r : tplListRows) {XWPFTableRow tplRow = table.getRow(r);XWPFTableRow row = table.insertNewTableRow(insertRow);row.getCtRow().setTrPr(tplRow.getCtRow().getTrPr());replaceRow(row, tplRow, o);insertRow++;}}for (int j = start; j <= end; j++) {deleteRows.add(j);}if (i < rows.size() - 1) {handleTableEach((i + 1), table, param, deleteRows);}}/*** 表格普通文本* @param table* @param param* @throws Exception*/private static void handleTableText(XWPFTable table, Object param) throws Exception {List<XWPFTableRow> rows = table.getRows();for (XWPFTableRow row : rows) {List<XWPFTableCell> cells = row.getTableCells();for (XWPFTableCell cell : cells) {// 处理图片if (handleCellImage(cell, param)) {continue;}handleCellText(cell, param);}}}/*** 处理表格内图片*/private static boolean handleCellImage(XWPFTableCell cell, Object param) throws Exception {String text = cell.getText().trim();if(text.matches("^\\$img\\{.+?}$")) {String key = text.substring(5, text.length() - 1).trim();Object field = getField(param, key);InputStream in = null;if (field instanceof InputStream) {in = (InputStream) field;}else if (field instanceof File) {in = new FileInputStream(((File) field));}else if (field instanceof byte[]) {in = new ByteArrayInputStream((byte[]) field);}if (in == null) {return false;}XWPFRun targetRun = null;for (XWPFParagraph paragraph : cell.getParagraphs()) {for (XWPFRun run : paragraph.getRuns()) {if (targetRun == null) {targetRun = run;}run.setText("", 0);}}if (targetRun == null) {return false;}targetRun.addPicture(in, XWPFDocument.PICTURE_TYPE_PNG, key + ".png", Units.toEMU(80), Units.toEMU(100));return true;}return false;}/*** 处理表格行* @param row* @param tplRow* @param param*/private static void replaceRow(XWPFTableRow row, XWPFTableRow tplRow, Object param) throws Exception {List<XWPFTableCell> cells = tplRow.getTableCells();for (XWPFTableCell tplCell : cells) {XWPFTableCell cell = row.createCell();copyTableCell(cell, tplCell);// 处理图片if (handleCellImage(cell, param)) {continue;}handleCellText(cell, param);}}private static void handleCellText(XWPFTableCell cell, Object param) {String text = cell.getText();Matcher matcher = PAIN_TEXT.matcher(text);if (!matcher.find()) {return;}for (XWPFParagraph paragraph : cell.getParagraphs()) {replaceParagraph(paragraph, param);}}/*** 复制单元格(列) 从sourceCell到targetCell* @param targetCell* @param sourceCell*/private static void copyTableCell(XWPFTableCell targetCell, XWPFTableCell sourceCell) {//表格属性if(sourceCell.getCTTc() != null) {targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());}//删除段落for(int pos = 0; pos < targetCell.getParagraphs().size(); pos++) {targetCell.removeParagraph(pos);}//添加段落for(XWPFParagraph sourceParag : sourceCell.getParagraphs()) {XWPFParagraph targetParag = targetCell.addParagraph();copyParagraph(targetParag, sourceParag);}}/*** 复制段落,从sourceParag到targetParag* @param targetParag* @param sourceParag*/private static void copyParagraph(XWPFParagraph targetParag, XWPFParagraph sourceParag) {targetParag.getCTP().setPPr(sourceParag.getCTP().getPPr());    //设置段落样式//移除所有的runfor(int pos = targetParag.getRuns().size() - 1; pos >= 0; pos-- ) {targetParag.removeRun(pos);}//copy新的runfor(XWPFRun sRun : sourceParag.getRuns()) {XWPFRun tarRun = targetParag.createRun();copyRun(tarRun, sRun);}}/*** 复制XWPFRun 从sourceRun到targetRun* @param targetRun* @param sourceRun*/private static void copyRun(XWPFRun targetRun, XWPFRun sourceRun) {//设置targetRun属性targetRun.getCTR().setRPr(sourceRun.getCTR().getRPr());targetRun.setText(sourceRun.text());//设置文本}private static Object getField(Object object, String field) {if (field.contains(".")) {String[] split = field.split("\\.");if (split.length > 1) {Object obj = getField(object, split[0]);if (obj == null) {return null;}return getField(obj, field.substring(field.indexOf(".") + 1));}}try {return BeanUtilsBean.getInstance().getPropertyUtils().getProperty(object, field.trim());} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {log.info(" getField[{}] fail", field);}return null;}private static String getProperty(Object object, String property) {String res = "";try {Object field = getField(object, property);if (field == null) {return "";}if (field instanceof Date) {res = new SimpleDateFormat(DATE_FORMAT).format(field);}else if (field instanceof BigDecimal){BigDecimal d = (BigDecimal) field;d = d.setScale(2, RoundingMode.HALF_UP);res = d.toPlainString();} else {res = BeanUtilsBean.getInstance().getConvertUtils().convert(field);}} catch (Exception e) {log.info(" getProperty[{}] fail", property);}return res == null ? "" : res;}private static String handleForeachText(String tpl, Object param) {Matcher rMatcher = FOREACH_TEXT.matcher(tpl);StringBuffer rSb = new StringBuffer();while (rMatcher.find()) {String group = rMatcher.group().substring(3);String name = group.substring(0, group.indexOf("("));List iterator = (List) getField(param, name);if (iterator == null) {rMatcher.appendReplacement(rSb, "");continue;}String reTpl = group.substring(name.length() + 1, group.length() - 3);StringBuilder builder = new StringBuilder();for (Object o : iterator) {String text  = handleBoolText(reTpl, o);builder.append(text);}rMatcher.appendReplacement(rSb, builder.toString());}rMatcher.appendTail(rSb);return handleBoolText(rSb.toString(), param);}private static String handleBoolText(String tpl, Object object) {Matcher matcher = BOOL_TEXT.matcher(tpl);StringBuffer sb = new StringBuffer();while (matcher.find()) {String group = matcher.group();group = group.substring(2, group.length() - 1);String key = group.substring(0, group.indexOf("("));group = group.substring(key.length());String[] split = group.split("else");String pre = split[0].substring(1, split[0].length() - 1);String suf = split[1].substring(1, split[1].length() - 1);Object field = getField(object, key);String target;if (Boolean.TRUE.equals(field)) {target = pre;} else {target = suf;}String text = "";try {text = handlePainText(target, object);} catch (IllegalArgumentException e) {}matcher.appendReplacement(sb, text);}matcher.appendTail(sb);try {return handlePainText(sb.toString(), object);} catch (IllegalArgumentException e) {return "";}}private static String handlePainText(String tpl, Object param) throws IllegalArgumentException {Matcher matcher = PAIN_TEXT.matcher(tpl);StringBuffer sb = new StringBuffer();while (matcher.find()) {String group = matcher.group();String name = group.substring(1, group.length() - 1).trim();boolean emptyFlag = name.endsWith("!");if (emptyFlag) {name = name.substring(0, name.length() - 1);}String value = getProperty(param, name);if (emptyFlag && StringUtils.isEmpty(value)) {throw new IllegalArgumentException();}matcher.appendReplacement(sb, value);}matcher.appendTail(sb);return sb.toString();}}

java poi操作word模版 导出word文档(附工具类)相关推荐

  1. java生成world文件_Java导出World文档(入门)

    第一步就是将World文档里面需要从数据库填充的部分用占位符替换 第二步:就是将此文档保存为Xml格式 第三步:将其放在resource目录下,并选中此文件,右键点击properties属性,将其编码 ...

  2. 采用itextpdf 实现java的PDF生成与导出功能,含封装工具类代码

    引入jar包 <dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</a ...

  3. 数据库设计文档生成工具类

    在企业级开发中.我们经常会有编写数据库表结构文档的时间付出,如果数据库表结构更新了还得手动更新维护到文档中,很是繁琐.GitHub 上发现了一个好工具 screw(螺丝钉),在此分享下 1.引入依赖 ...

  4. java poi 操作word遇到的问题

    java poi 操作word文本,图表,遇到的问题 直接上问题 模板字段匹配问题 图表问题 图表导出 问题:模板找不到对应图表 问题:数据填充后效果不达目标 图表中为零的数值去掉(!!!模板层面解决 ...

  5. java docx文档解析_带有docx4j的Java Word(.docx)文档

    java docx文档解析 几个月前,我需要创建一个包含许多表和段落的动态Word文档. 过去,我曾使用POI来实现此目的,但是我发现它很难使用,并且在创建更复杂的文档时对我来说效果不佳. 因此,对于 ...

  6. 用word模板导出word文档

    项目需求要把页面上的分析结果导出为word文档,实现的办法是POI.查了一下网上很多方式都采用FreeMark,自己认为比较麻烦,所以还是采取了POI导出.之前的框架是SSH的,现在换成了Spring ...

  7. java通过framer生成word_framemarker导出word(含图片)

    实现步骤: 1.编辑word模板,并将需要显示数据的地方标注出来. 2.将word模板另存为xml文件(为了兼容,最好使用Word 2003 XML文档) 3.替换标记. 使用xml编辑工具(我用的N ...

  8. 一、后端:针对用JAVA POI解决已知路径WORD文件增加自定义页眉,灵活设置页眉字体部分样式@2019

    一.获取添加页眉doc文件 我的项目文件路径: String reportSavePath= "****************"; DOCX文件一: changer.setAcc ...

  9. apipost生成word格式的接口文档,接口文档合并操作

    一.Apipost导出单个接口word 1.Apipost在分享完网址链接之后,有一个导出离线文档的功能.有导出HTML.导出MarkDown和导出word格式. 2.选择导出word文档 下载的为压 ...

最新文章

  1. SAP Fiori学习笔记
  2. 如何用手机维护Mysql数据库
  3. Vector的使用方法和自我理解
  4. Android manifest属性总结
  5. 添加softmax层_PyTorch入门之100行代码实现softmax回归分类
  6. Effective java -- 2 对于所有对象都通用到方法
  7. mysql插入ㄖ_原生JavaScript代码100个实例
  8. C#中的集合、哈希表、泛型集合、字典
  9. nyoj--120--校园网络(scc+缩点)
  10. YBROJ洛谷P3211:XOR和路径(线性基,期望dp)
  11. sqlserver2008驱动_Python连接数据库两种方法,QSqlDatabase,pymmsql,驱动名
  12. 编程语言对比 字面常量
  13. Cisco AP-Sniffer模式空口抓包
  14. 几个比较好的app开发框架
  15. 经典语录_生命的智慧
  16. 两台win10电脑连接同一个wifi怎么共享文件
  17. ESP32(arduino)和声音传感器数据采集并实现连接WiFi进行MQTT通信
  18. 【阿朱标红】O2O五年三次创业的九大经验(天天用车CEO翟光龙)
  19. 计算机网络实用知识,计算机网络实用技术知识点之ISDN的定义及特性
  20. 龙格现象 matlab,龙格现象的matlab实现

热门文章

  1. 社区团购小程序怎么做,全流程解析
  2. Typora:Typora快捷键
  3. HTML语言分栏左右比例怎么调整,wps怎么设置分栏排版?
  4. 17 张程序员专属壁纸(使用频率很高)
  5. 新年礼品选超人气MID口袋电脑,不错的选择!
  6. 基于C#Winform+MySQL的商务娱乐会馆自助服务系统
  7. 软件实现串口程序出售,9600收发毫无压力。 采用io管脚模拟,适合串口资源欠缺的芯片使用。 stm32,tms320f28xx,PIC等
  8. 魅族Android7.0刷机包,乐视X900+安卓7.1.2 魅族Flyme6刷机包 最新6.8.3.17R版 紫火20180510更新...
  9. QA:笔记本如何选?
  10. 网络学习-6.VLAN