600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 11.java程序员必知必会类库之word处理库

11.java程序员必知必会类库之word处理库

时间:2019-11-14 09:06:44

相关推荐

11.java程序员必知必会类库之word处理库

前言

正常业务中,可能涉及到和合作方签约电子合同,此时,我们需要先设计合同模板,维护固定内容,将可变的内容通过占位符替代,等签章的时候,生成pdf,然后可以根据设计的合同章的坐标,调用签章系统盖电子章。

此时涉及三步:

读取合同word模板,替换占位符,比如签约人,合同金额,签约时间等将word文档转换为pdf文件上传pdf文件到电子签章系统签章

这时候就需要我们掌握java代码读写word文档的技能。

1. 前期准备与说明

1.1 doc文件后缀与docx文件后缀区别

运行环境不同,docx格式的文件是Office及以上版本保存的新型文档,而doc是Word以及之前版本保存的文档。它们所占用的内存空间不同,docx更加节省空间。它们的响应速度有所不同,docx比doc的响应速度更加快捷,并且更加方便修改文件。docx格式的文件本质上是一个ZIP文件,是docx文件的容器。而doc则容纳文字格式、脚本语言及复原等资讯的文件。docx文件改后缀为zip文件,解压后可以看到如下所示:

其中word里面是很多xml文件

因为doc文件版本比较老,现在主流都用的是docx文件,下面示例统一以docx文件为演示格式

1.2 测试合同docx文件准备

假设合同模板内容如下,可变的信息我们通过${变量名}代替,后续真实业务中,基于不同的用户和合作方,替换文档内容

合同模板我上传到csdn里面了下载资源

2 POI 处理文档

2.1 介绍

POI可以灵活操作基于word文档。Apache POl中的HWPF具体提供了读写MicrosoftWord格式文件的功能。

优点:跨平台支持windows unix和linux

缺点:操作word的功能比较弱,必须针对doc和docx两种文档格式写不同的代码,兼容性差。相对与对word文件的处理来说,POI更适合excel处理,对于word实现一些简单文件的操作凑合,不能设置样式且生成的word文件格式不够规范

综合来说:但是如果只是基于合同模板生成word稳定,不手动拼接word,则POI就已经能满足我们的业务需求。具体选哪种方案,视具体业务而定

2.2 pom坐标引入

<!--解析docx文档的XWPFDocument对象在这个包里--><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.9</version></dependency><!--解析doc文档的HWPFDocument对象在这个包里,操作docx文件可不要这个包,这里只是一起记录一下--><dependency><groupId>org.apache.poi</groupId><artifactId>poi-scratchpad</artifactId><version>3.9</version></dependency><!--lombok组件,可以不接入,这里方便测试--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency>

2.3 使用

2.3.1 常用api

XWPFDocument类(文档类):xd.getAllPictures(); 返回图片列表xd.getParagraphs(); 返回段落列表xd.getTables(); 返回表格列表xd.getFooterList(); 返回脚脚列表xd.getFootnotes(); 返回脚注列表xd.getHeaderList(); 返回页眉列表XWPFParagraph类(段落类):xwpfParagraph.getText(); 返回段落的文本内容,包括图片中的文本和其中的sdt元素。xwpfParagraph.getParagraphText(); 返回段落的文本,但不返回段落中的任何对象xwpfParagraph.getRuns()); 返回段落run的列表XWPFTable类(表格类):xwpfTable.getRows(); 返回行对象XWPFTableRowXWPFTableRow类(表格行类):xwpfTableRow.getTableCells(); 返回单元格对象XWPFTableCellxwpfTableCell.getParagraphs(); 返回单元格里面的段落xwpfTableCell.getTables(); 返回单元格里面的表格

2.3.2 测试案例

测试demo:

@Slf4jpublic class DocXUtils {public static void main(String[] args) {String filePath = "E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\合同文档模板.docx";String suffix = filePath.substring(filePath.lastIndexOf("."));if(".docx".equals(suffix)){System.out.println("文件类型是.docx");dealDocXFile(filePath);}}public static void dealDocXFile(String filePath){InputStream input = null;try {//实例化解析docx文档的对象input = new FileInputStream(filePath);XWPFDocument xd = new XWPFDocument(input);log.info("开始解析文档中的所有图片----------------");//1 getAllPackagePictures()此包装中的所有图片List<XWPFPictureData> xwpfPictureDataList2 = xd.getAllPictures();if(CollUtil.isNotEmpty(xwpfPictureDataList2)){for (XWPFPictureData xwpfPictureData : xwpfPictureDataList2) {//图像的文件名System.out.println("图片名称:" + xwpfPictureData.getFileName());}}log.info("开始解析文档中的所有段落----------------");//文本的段落List<XWPFParagraph> xwpfParagraphList = xd.getParagraphs();dealParagraph(xwpfParagraphList);log.info("开始解析文档中的所有表格----------------");//文本的表格List<XWPFTable> xwpfTableList = xd.getTables();dealTable(xwpfTableList);log.info("开始解析文档中的页脚----------------");//页脚列表List <XWPFFooter> xwpfFooterList = xd.getFooterList();//System.out.println("页脚列表size:"+xwpfFooterList.size());if(CollUtil.isNotEmpty(xwpfFooterList)){for (XWPFFooter xwpfFooter : xwpfFooterList) {//页脚的文本的段落dealParagraph(xwpfFooter.getParagraphs());dealTable(xwpfFooter.getTables());System.out.println("getText:"+xwpfFooter.getText());}}log.info("开始解析文档中的脚注----------------");//脚注列表List <XWPFFootnote> xwpfFootnoteList = xd.getFootnotes();for(XWPFFootnote xwpfFootnote : xwpfFootnoteList){//处理脚注段落dealParagraph(xwpfFootnote.getParagraphs());dealTable(xwpfFootnote.getTables());}log.info("开始解析文档中的页眉----------------");//页眉列表List <XWPFHeader> xwpfHeaderList = xd.getHeaderList();if(CollUtil.isNotEmpty(xwpfHeaderList)){for(XWPFHeader xwpfHeader : xwpfHeaderList){dealParagraph(xwpfHeader.getParagraphs());dealTable(xwpfHeader.getTables());System.out.println(xwpfHeader.getText());}}} catch (FileNotFoundException e) {e.printStackTrace();System.out.println("文件没有找到");} catch (IOException e) {e.printStackTrace();System.out.println("发生io异常");}}public static void dealParagraph(List<XWPFParagraph> xwpfParagraphListList){if(CollUtil.isNotEmpty(xwpfParagraphListList)){for(XWPFParagraph xwpfParagraph : xwpfParagraphListList){if(ObjectUtil.isNull(xwpfParagraph) || StrUtil.isEmpty(xwpfParagraph.getText())){continue;}System.out.println("______________________________________________________________");//返回段落的文本内容,包括图片中的文本和其中的sdt元素。StringSystem.out.println("getText~~~~~~~~~~~:"+xwpfParagraph.getText());//返回段落的文本,但不返回段落中的任何对象.StringSystem.out.println("getParagraphText:~~"+xwpfParagraph.getParagraphText());System.out.println("getRuns~~~~~~~~~~~:"+xwpfParagraph.getRuns());}}}public static void dealTable(List<XWPFTable> xwpfTableList){if(CollUtil.isNotEmpty(xwpfTableList)){for(XWPFTable xwpfTable : xwpfTableList){//遍历行for(XWPFTableRow xwpfTableRow : xwpfTable.getRows()){//遍历单元格for(XWPFTableCell xwpfTableCell : xwpfTableRow.getTableCells()){//处理段落dealParagraph(xwpfTableCell.getParagraphs());dealTable(xwpfTableCell.getTables());}}}}}}

代码输出结果:

文件类型是.docx-04-25 17:01:49.282 [main] INFO com.wanlong.word.DocXUtils - 开始解析文档中的所有图片-----------------04-25 17:01:49.282 [main] INFO com.wanlong.word.DocXUtils - 开始解析文档中的所有段落----------------______________________________________________________________getText~~~~~~~~~~~:编号:${CONTRACT_NO}getParagraphText:~~编号:${CONTRACT_NO}getRuns~~~~~~~~~~~:[编号:, ${CONTRACT_NO}]______________________________________________________________getText~~~~~~~~~~~:${PRODUCT_NAME}产品开通申请getParagraphText:~~${PRODUCT_NAME}产品开通申请getRuns~~~~~~~~~~~:[${PRODUCT_NAME}, 产品开通申请]______________________________________________________________getText~~~~~~~~~~~:特别提示:本申请项下的以下${PRODUCT_NAME}产品服务(以下简称“本服务”),是由XXX为YYY所提供的服务getParagraphText:~~特别提示:本申请项下的以下${PRODUCT_NAME}产品服务(以下简称“本服务”),是由XXX为YYY所提供的服务getRuns~~~~~~~~~~~:[特别提示:, 本, 申请, 项下的以下, ${PRODUCT_NAME}, 产品服务, (以下简称“本服务”), ,是, 由, XXX为YYY, 所提供, 的服务]______________________________________________________________getText~~~~~~~~~~~:甲方: getParagraphText:~~甲方: getRuns~~~~~~~~~~~:[甲方, : ]______________________________________________________________getText~~~~~~~~~~~:用户姓名:${PERSONAL_NAME}getParagraphText:~~用户姓名:${PERSONAL_NAME}getRuns~~~~~~~~~~~:[用户姓名:, ${PERSONAL_NAME}]______________________________________________________________getText~~~~~~~~~~~:证件类型:${PERSONAL_ID_TYPE}getParagraphText:~~证件类型:${PERSONAL_ID_TYPE}getRuns~~~~~~~~~~~:[证件类型:, ${PERSONAL_ID_TYPE}]______________________________________________________________getText~~~~~~~~~~~:证件号码:${PERSONAL_ID_NO}getParagraphText:~~证件号码:${PERSONAL_ID_NO}getRuns~~~~~~~~~~~:[证件号码:, ${PERSONAL_ID_NO}]______________________________________________________________getText~~~~~~~~~~~:乙方: getParagraphText:~~乙方: getRuns~~~~~~~~~~~:[乙方, : ]______________________________________________________________getText~~~~~~~~~~~:公司名称:${ENT_NAME}getParagraphText:~~公司名称:${ENT_NAME}getRuns~~~~~~~~~~~:[公司名称, :, ${ENT_NAME}]______________________________________________________________getText~~~~~~~~~~~:统一社会信用代码:${ENT_ID_NO}getParagraphText:~~统一社会信用代码:${ENT_ID_NO}getRuns~~~~~~~~~~~:[统一社会信用代码, :, ${ENT_ID_NO}]______________________________________________________________getText~~~~~~~~~~~:甲方:${SIGNER_JIAFANG}getParagraphText:~~甲方:${SIGNER_JIAFANG}getRuns~~~~~~~~~~~:[甲方, :, ${SIGNER_JIAFANG}]______________________________________________________________getText~~~~~~~~~~~:乙方:${SIGNER_YIFANG}}getParagraphText:~~乙方:${SIGNER_YIFANG}}getRuns~~~~~~~~~~~:[乙方, :, ${SIGNER_, YI, FANG, }, }]-04-25 17:01:49.317 [main] INFO com.wanlong.word.DocXUtils - 开始解析文档中的所有表格-----------------04-25 17:01:49.317 [main] INFO com.wanlong.word.DocXUtils - 开始解析文档中的页脚-----------------04-25 17:01:49.317 [main] INFO com.wanlong.word.DocXUtils - 开始解析文档中的脚注-----------------04-25 17:01:49.317 [main] INFO com.wanlong.word.DocXUtils - 开始解析文档中的页眉----------------

上面注意一个细节

合同模板甲乙方下面是留着签章的,但是为了签章能定位到具体的坐标位置,所以有两个隐藏的占位符不同poi版本的jar包会有一些类不存在的问题,为了后面可以使用word转pdf,建议poi的版本不要太高

2.4 使用场景

2.4.1 按照模板替换报文

模板规则约定如下

占位符统一用${}包含,比如name,则文档占位符为 ${name}签章位置加一个隐藏的占位符,肉眼看不到,但是实际模板有隐藏的字体,这里通过设置字体为白色隐藏,但是代码可以正常读取,主要用于后续签章好锁定坐标位置签章占位符统一约定占位符为${sign}开头,比如甲方签章,则可以为 ${sign_jiafang}

2.4.1.1 校验占位符工具类

package com.wanlong.word;import org.apache.poi.xwpf.usermodel.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import java.util.List;import java.util.regex.Matcher;import java.util.regex.Pattern;/*** 校验word占位符*/public class CheckWordElementValue {private final static Logger logger = LoggerFactory.getLogger(CheckWordElementValue.class);public static List<String> getContractTemplateKey(InputStream is){logger.info("=== 校验word占位符--进入解析word文档方法 ===");List<String> list = new ArrayList<String>();try {logger.info("=== 校验word占位符--解析word文档 ===");XWPFDocument document = new XWPFDocument(is);//获取文本对象logger.info("=== 校验word占位符--获取文本对象 ===");List<XWPFParagraph> allParagraph = document.getParagraphs();logger.info("=== 校验word占位符--调用convertStr方法 ===");convertStr(allParagraph, list);logger.info("=== 校验word占位符--获取全部表格对象 ===");//获取全部表格对象List<XWPFTable> allTable = document.getTables();for (XWPFTable xwpfTable : allTable) {logger.info("=== 校验word占位符--获取表格行数据 ===");//获取表格行数据List<XWPFTableRow> rows = xwpfTable.getRows();for (XWPFTableRow xwpfTableRow : rows) {//获取表格单元格数据logger.info("=== 校验word占位符--获取表格单元格数据 ===");List<XWPFTableCell> cells = xwpfTableRow.getTableCells();for (XWPFTableCell xwpfTableCell : cells) {List<XWPFParagraph> paragraphs = xwpfTableCell.getParagraphs();convertStr(paragraphs, list);}}}} catch (IOException e) {logger.error("---=== 校验word是否还存在占位符时'文档解析异常' ===---");}return list;}public static void convertStr(List<XWPFParagraph> allParagraph, List<String> list) {for(XWPFParagraph xwpfParagraph : allParagraph) {String str = xwpfParagraph.getRuns().toString().replaceAll(", ", "");Matcher matcher = matcher(str);while(matcher.find()) {list.add(matcher.group(1).trim());}}}public static Matcher matcher(String str) {Pattern pattern = pile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);Matcher matcher = pattern.matcher(str);return matcher;}}

2.4.1.2. word文档替换占位符工具类

package com.wanlong.word;import mons.lang3.StringUtils;import org.apache.poi.POIXMLDocument;import org.apache.poi.xwpf.usermodel.*;import org.openxmlformats.schemas.wordprocessingml.x.main.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.Set;/*** 通过word模板生成新的word工具类*/public class WorderToNewWordUtils {private final static Logger logger = LoggerFactory.getLogger(WorderToNewWordUtils.class);public static void changeText(XWPFDocument document, Map<String, Object> textMap) {logger.info("=== 进入文本段落方法 === ");// 获取段落集合List<XWPFParagraph> paragraphs = document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {logger.info("=== 开始遍历获取paragraph === ");// 判断此段落时候需要进行替换String text = paragraph.getText();logger.info("=== 获取paragraphs文本段落内容 === : " + text);if (checkText(text)) {List<XWPFRun> runs = paragraph.getRuns();logger.info("=== 获取paragraph.getRuns()列表里面单个runs元素内容 === ");for (XWPFRun run : runs) {logger.info("=== 遍历XWPFRun内容=== :" + run.toString());if (checkUserCode(run.toString())) {logger.info("=== 进入签署人列表占位符替换方法 === :" + run.toString());if (!run.toString().equals("${SIGN_TIME}")) {CTRPr rpr = run.getCTR().isSetRPr() ? run.getCTR().getRPr() : run.getCTR().addNewRPr();logger.info("=== 判断占位符内容是否有字体颜色 === :" + rpr.getColor());if (rpr.getColor() != null) {rpr.unsetColor();}run.setColor("FFFFFF");run.setText(changeUserCodeValue(run.toString(), textMap), 0);}} else {logger.info("=== 普通占位符替换方法 === :" + run.toString());run.setText(changeValue(run.toString(), textMap), 0);}}}}}public static void changeTable(XWPFDocument document, Map<String, Object> textMap) {logger.info("=== changeTable方法,判断table内容是替换还是动态生成 ===");List<XWPFTable> tables = document.getTables();for (int i = 0; i < tables.size(); i++) {XWPFTable table = tables.get(i);if (table.getRows().size() > 1) {logger.info("=== 校验table内容是否含有占位符标识===");if (checkText(table.getText())) {List<XWPFTableRow> rows = table.getRows();String text = table.getRow(1).getCell(0).getText();logger.info("=== 获取table占位符标识=== :" + text);if (checkText(text)) {logger.info("=== table内容动态生成 ===");insertTable(table, textMap);} else {logger.info("=== table内容替换 ===");eachTable(rows, textMap);}}}if (table.getRows().size() <= 1) {logger.info("=== table.getRows().size() <= 1 table内容替换 ===");if (checkText(table.getText())) {List<XWPFTableRow> rows = table.getRows();eachTable(rows, textMap);}}}}public static void insertTable(XWPFTable table, Map<String, Object> textMap) {logger.info("=== insertTable方法,创建table内容 ===");String value = null;String text = table.getRow(1).getCell(0).getText();logger.info("=== 获取动态生成table占位符 === :" + text);Set<Map.Entry<String, Object>> textSets = textMap.entrySet();logger.info("=== 获取动态生成table占位符去匹配替换内容内容 ===");for (Map.Entry<String, Object> textSet : textSets) {String key = "${" + textSet.getKey() + "}";if (text.indexOf(key) != -1) {value = (String) textSet.getValue();}}if (StringUtils.isNotEmpty(value)) {table.getRow(1).getCell(0).removeParagraph(0);table.getRow(1).getCell(0).setText("");List<String[]> tableValue = getListValue(value);if (tableValue.size() >= 1) {for (int i = 1; i < tableValue.size(); i++) {XWPFTableRow row = table.createRow();}List<XWPFTableRow> tableRow = table.getRows();logger.info("=== 校验动态生成table头的单元格格式是否与替换内容格式符合 ===");if (tableRow.get(0).getTableCells().size() == tableValue.get(0).length) {for (int i = 1; i < tableRow.size(); i++) {logger.info("=== 校验动态生成table单元格数据大小是否与替换内容数据大小符合 ===");logger.info("=== for i === :" + i);logger.info("=== for tableValue.size() === : " + tableValue.size());if (i <= tableValue.size()) {XWPFTableRow newRow = table.getRow(i);List<XWPFTableCell> cells = newRow.getTableCells();try {for (int j = 0; j < cells.size(); j++) {logger.info("=== 遍历添加table单元格内容 ===");XWPFTableCell cell = cells.get(j);cell.setText(tableValue.get(i - 1)[j]);CTTc cttc = cell.getCTTc();CTP ctp = cttc.getPList().get(0);CTPPr ctppr = ctp.getPPr();if (ctppr == null) {ctppr = ctp.addNewPPr();}CTJc ctjc = ctppr.getJc();if (ctjc == null) {ctjc = ctppr.addNewJc();}ctjc.setVal(STJc.LEFT); //水平向左}} catch (Exception e) {logger.error("---=== 动态插入table内容错误 ===---", e);throw new RuntimeException(e);}}}}}}}public static void eachTable(List<XWPFTableRow> rows, Map<String, Object> textMap) {logger.info("=== 进入表格替换方法 ===");for (XWPFTableRow row : rows) {List<XWPFTableCell> cells = row.getTableCells();for (XWPFTableCell cell : cells) {//判断单元格是否需要替换logger.info("=== 判断表格替换单元格是否需要替换 ===");logger.info("=== 表格替换内容 === : " + cell.getText());if (checkText(cell.getText())) {List<XWPFParagraph> paragraphs = cell.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {List<XWPFRun> runs = paragraph.getRuns();for (XWPFRun run : runs) {logger.info("=== 表格替换占位符 === :" + cell.getText());run.setText(changeValue(run.toString(), textMap), 0);}}}}}}public static List<String[]> getListValue(String value) {logger.info("=== 进入getListValue方法,解析table内容 ===");List<String[]> listValue = new ArrayList<String[]>();try {if (StringUtils.isNotBlank(value)) {String[] splitValue = value.split("\\|");try {for (int i = 0; i < splitValue.length; i++) {String[] splitLaterValue = splitValue[i].split(",", -1);listValue.add(splitLaterValue);}} catch (Exception e) {logger.error("---=== 获取table内容,截取内容时出现错误 ===---", e);throw new RuntimeException(e);}}} catch (Exception e) {logger.error("---=== 获取table内容,截取内容时出现错误 ===---", e);throw new RuntimeException(e);}return listValue;}public static boolean checkText(String text) {boolean check = false;if (text.indexOf("$") != -1) {check = true;}return check;}public static String changeValue(String value, Map<String, Object> textMap) {try {Set<Map.Entry<String, Object>> textSets = textMap.entrySet();for (Map.Entry<String, Object> textSet : textSets) {String key = "${" + textSet.getKey() + "}";if (value.indexOf(key) != -1) {value = (String) textSet.getValue();}}} catch (Exception e) {logger.error("---=== 比较${}格式与替换内容是否匹配错误 ===---", e);throw new RuntimeException();}return value;}public static String changeUserCodeValue(String value, Map<String, Object> textMap) {try {Set<Map.Entry<String, Object>> textSets = textMap.entrySet();for (Map.Entry<String, Object> textSet : textSets) {// 匹配模板与替换值 格式# 预计String key = "${" + textSet.getKey() + "}";if (value.indexOf(key) != -1) {value = (String) textSet.getValue();}}} catch (Exception e) {logger.error("---=== 比较${}格式与替换内容是否匹配错误 ===---", e);throw new RuntimeException(e);}return value;}public static boolean checkUserCode(String text) {boolean check = false;if (text.indexOf("${SIGNER_") != -1) {check = true;}return check;}public static boolean changWord(String inputUrl, String outputUrl,Map<String, Object> textMap, List<String[]> tableList) {//模板转换默认成功boolean changeFlag = true;try {//获取docx解析对象XWPFDocument document = new XWPFDocument(POIXMLDocument.openPackage(inputUrl));//解析替换文本段落对象WorderToNewWordUtils.changeText(document, textMap);//解析替换表格对象WorderToNewWordUtils.changeTable(document, textMap);//生成新的wordFile file = new File(outputUrl);FileOutputStream stream = new FileOutputStream(file);document.write(stream);stream.close();} catch (IOException e) {e.printStackTrace();changeFlag = false;}return changeFlag;}}

2.4.1.3 word转pdf测试类

@Slf4jpublic class TestWord {@Testpublic void testWord() throws Exception{String path="E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\";//维护待替换的参数和值Map<String,Object> map=new HashMap<>();map.put("PRODUCT_NAME","余额宝");map.put("PERSONAL_NAME","张三");map.put("PERSONAL_ID_TYPE","身份证");map.put("PERSONAL_ID_NO","64199902122302X");map.put("CONTRACT_NO","hetong123456");map.put("ENT_NAME","支付宝测试有限公司");map.put("ENT_ID_NO","90232340341234");log.info("=== 解析word文档 ===");XWPFDocument document = new XWPFDocument(new FileInputStream("E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\合同文档模板.docx"));//解析替换文本对象log.info("=== 解析替换文本对象 ===");WorderToNewWordUtils.changeText(document, map);//解析替换表格对象log.info("=== 解析替换表格对象 ===");WorderToNewWordUtils.changeTable(document, map);log.info("=== 生成word ===");InputStream foundWordInputStream = foundWord(document);log.info("=== 校验生成的word是否含有占位符 ===");Boolean isContainPlaceholder = checkCreateWord(foundWordInputStream);if (isContainPlaceholder == true) {throw new Exception("文档没替换完毕");} else {log.info("=== 调生成word文档方法 ===");document.write(new FileOutputStream(path + "result.docx"));}}

2.4.1.4.生成替换后的合同如下

2.4.2 word转pdf

2.4.2.1 添加依赖

<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.9</version></dependency><dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>org.apache.poi.xwpf.converter.pdf</artifactId><version>1.0.4</version></dependency><!-- 工具类用到apache的类,可以重新替换--><dependency><groupId>mons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency>

2.4.2.2 测试类

@Testpublic void testWordToPdf() throws Exception {String in = "E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\result.docx";FileInputStream inputStream = new FileInputStream(in);XWPFDocument document = new XWPFDocument(inputStream);String out = "E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\test.pdf";FileOutputStream outputStream = new FileOutputStream(out);PdfOptions pdfOptions = PdfOptions.create();PdfConverter.getInstance().convert(document, outputStream, pdfOptions);}

2.4.2.3 转换结果

2.5 注意事项

在代码解析word文档的时候,可能会出现某些占位字符被分割解析,比如上面占位符的PERSONAL_ID_NO 变量,POI可能会切割为三个字符,PERSONAL ,ID,NO处理,此时替换的时候,会出现无法替换。此时建议调整模板的格式,多次尝试,直到能正常解析(可以尝试将完整字符放到记事本里,修改完粘贴过来)。因为模板调整完一般就不会变了,不建议代码做兼容处理POI的版本和POIConverter的POM版本会有关联关系,上面的版本号是经过测试的,版本不匹配会报错类不存在

3 POI-TL

3.1 介绍

poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档

3.2 pom坐标引入

<!-- word模板引擎--><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.8.2</version></dependency>

3.3 使用

3.3.1 合同模板

3.3.2 测试代码

package com.wanlong.word;import com.deepoove.poi.XWPFTemplate;import com.deepoove.poi.data.PictureRenderData;import com.deepoove.poi.util.BytePictureUtils;import org.junit.Test;import java.io.File;import java.io.FileOutputStream;import java.util.HashMap;import java.util.Map;/*** @author wanlong* @version 1.0* @description:* @date /4/25 10:11*/public class TestTLWord {@Testpublic void testCreate() throws Exception {Map<String, Object> map = new HashMap<>();map.put("PRODUCT_NAME", "余额宝");map.put("PERSONAL_NAME", "张三");map.put("PERSONAL_ID_TYPE", "身份证");map.put("PERSONAL_ID_NO", "64199902122302X");map.put("CONTRACT_NO", "hetong123456");map.put("ENT_NAME", "支付宝测试有限公司");map.put("ENT_ID_NO", "90232340341234");// 读取本地磁盘图片map.put("QIANZHANG_JIAFANG", new PictureRenderData(100, 100, "E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\picture\\logo.jpeg"));map.put("QIANZHANG_YIFANG", new PictureRenderData(100, 100, "E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\picture\\logo.jpeg"));// // 通过url读取网络图片// map.put("picture", new PictureRenderData(200, 400, ".png", BytePictureUtils.getUrlByteArray("https://res./a/wx_fed/weixin_portal/res/static/img/1EtCRvm.png")));File file = new File("E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\合同文档模板2.docx");XWPFTemplate template = pile(file).render(map);FileOutputStream out = new FileOutputStream(new File("E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\testtl.docx"));template.write(out);out.flush();out.close();template.close();}}

3.3.3 运行结果

3.3.4 word转pdf

这里要留意,如果用了POI-TL的jar做模板替换,因为这个jar依赖了OOXML的高版本jar包,所以转换PDF不能用上面的converterjar包,否则两个jar包都对ooxml有依赖,但是版本不一样,会有问题

笔者尝试了好几个版本都不太行,最后折中换了一个其他依赖,实现word转PDF.

3.3.4.1 添加依赖

<dependency><groupId>com.documents4j</groupId><artifactId>documents4j-local</artifactId><version>1.0.3</version></dependency><dependency><groupId>com.documents4j</groupId><artifactId>documents4j-transformer-msoffice-word</artifactId><version>1.0.3</version></dependency>

3.3.4.2 测试类

@Testpublic void testWordToPdf() throws Exception {String in = "E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\testtl.docx";String out = "E:\\weixinData\\WeChat Files\\wxid_gv8xbkloz0wc22\\FileStorage\\File\\-03\\test\\src\\main\\resources\\word\\test3.pdf";InputStream docxInputStream = null;OutputStream outputStream = null;IConverter converter=null;try {docxInputStream = new FileInputStream(new File(in));outputStream = new FileOutputStream(new File(out));converter = LocalConverter.builder().build();converter.convert(docxInputStream).as(DocumentType.DOCX).to(outputStream).as(DocumentType.PDF).execute();} catch (Exception e) {log.error("word转pdf异常",e);e.printStackTrace();} finally {outputStream.close();docxInputStream.close();converter.kill();}}

3.3.4.3 运行结果

PDF正常生成

3.4 注意事项

项目里面如果还依赖oomxml版本的话,要留意版本不要冲突,否则会有奇怪的报错,这里可以看到,只需要引用一个依赖,即可完成word文本占位符替换。POI-Tl包本身会有ooxml的一些引用

可以看到POI-TL在模板操作替换方面,使用极其简单,这个jar包初衷就是模板操作word更多关于POI-TL的使用,参考文末的文档网站如果有需求在一个项目中既实现word模板替换生成新的word文档,又想将word转换为PDF,笔者个人建议选择第一种方案,第二种方案相对来说没有经过我完整的生产验证,第一种是经过我们验证的。

4 其他类似类库简介

4.1 FreeMarker

FreeMarker生成word文档的功能是由XML+FreeMarker来实现的。先把word文件另存为xml,在xml文件中插入特殊的字符串占位符,将xml翻译为FreeMarker模板,最后用Java来解析FreeMarker模板,编码调用FreeMarker实现文本替换并输出Doc。

优点:比Java2word功能强大,代码相对简单,跨平台,也是纯Java编程。

缺点:模板制作复杂,生成的文件本质上是xml,不是真正的word文件格式,有很多常用的word格式无法处理或表现怪异,比如:超链、换行、乱码、部分生成的文件打不开等。

4.2 Java2word

Java2word是一个在Java程序中调用MS Office Word文档的组件(类库)。

该组件提供了一组简单的接口,以便Java程序调用他的服务操作Word文档。

这些服务包括:打开文档、新建文档、查找文字、替换文字,插入文字、插入图片、插入表格,在书签处插入文字、插入图片、插入表格等。

优点:足够简单,操作起来要比FreeMarker简单的多。

缺点:没有FreeMarker强大,不能够根据模版生成Word文档,word的文档的样式等信息都不能够很好的操作。

参考文档:

/poi-tl/

/article/22151181181/

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。