一、简介
本工具类复制即可使用,内附测试代码,包含以下操作:
-- word 中 属性值替换
-- word 中 列表动态插入数据
-- word 转 pdf
-- 版本更新:
1、链式调用
2、方法解耦
二、环境
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.2</version>
</dependency>
<dependency>
<groupId>freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.8</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>6.1.2</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>8.1.7</version> //版本号不能高于8.1.7
</dependency>
<!-- 条形码 -->
<dependency>
<groupId>net.sf.barcode4j</groupId>
<artifactId>barcode4j-light</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.activation/activation -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.5</version>
</dependency>
三、工具类
package com.dily.scaffold.common.utils;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import org.docx4j.Docx4J;
import org.docx4j.TraversalUtil;
import org.docx4j.XmlUtils;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.ClassFinder;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.jaxb.Context;
import org.docx4j.model.datastorage.migration.VariablePrepare;
import org.docx4j.model.table.TblFactory;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.*;
import org.springframework.core.io.ClassPathResource;
import javax.xml.bind.JAXBElement;
import java.io.*;
import java.util.*;
/**
* Time: 2021/12/20 13:22
* Author: Dily
* Remark:
* Word文档工具
*/
public class Docx4jUtils {
public static WordOption getWordOption() {
return new WordOption();
}
/**
* 获取文件中所有内容
*
* @param context word 正文: getWordMLPackage().getMainDocumentPart().getContent()
* @param toSearch 搜索的类型
* @return 符合搜索类型的所有对象
*/
public static List<Object> getAllElementFromObject(List<Object> context, Class<?> toSearch) {
List<Object> result = new ArrayList<>();
for (Object obj : context) {
if (obj instanceof JAXBElement)
obj = ((JAXBElement<?>) obj).getValue();
if (obj.getClass().equals(toSearch))
result.add(obj);
else if (obj instanceof ContentAccessor) {
List<Object> children = ((ContentAccessor) obj).getContent();
getAllElementFromObject(children, toSearch);
}
}
return result;
}
/**
* word 转 pdf
*
* @param wordPath word文档地址
* @return pdf地址
*/
public static String Docx2Pdf(String wordPath) {
OutputStream os = null;
InputStream is = null;
//输出pdf文件路径和名称
String pdfNoMarkPath = wordPath.substring(0, wordPath.indexOf('.')) + ".pdf";
try {
is = new FileInputStream(wordPath);
WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
Mapper fontMapper = new IdentityPlusMapper();
fontMapper.put("等线", PhysicalFonts.get("SimSun"));
fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun")); //解决宋体(正文)和宋体(标题)的乱码问题
PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
fontMapper.put("SimSun", PhysicalFonts.get("SimSun")); //宋体&新宋体
mlPackage.setFontMapper(fontMapper);
os = new FileOutputStream(pdfNoMarkPath);
//docx4j docx转pdf
FOSettings foSettings = Docx4J.createFOSettings();
foSettings.setWmlPackage(mlPackage);
Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
is.close();//关闭输入流
os.close();//关闭输出流
return pdfNoMarkPath;
} catch (Exception e) {
e.printStackTrace();
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
} finally {
File file = new File(wordPath);
if (file.isFile() && file.exists()) {
file.delete();
}
}
return "";
}
/**
* 添加水印图片
*
* @param inPdfPath 无水印pdf路径
* @param markPath 水印图片地址
* @return 生成的带水印的pdf路径
*/
public static String addTextMark(String inPdfPath, String markPath) {
PdfStamper stamp = null;
PdfReader reader = null;
try {
//输出pdf带水印文件路径和名称
String outPdfMarkPath = inPdfPath.substring(0, inPdfPath.indexOf('.')) + "水印.pdf";
//添加水印
reader = new PdfReader(inPdfPath, "PDF".getBytes());
stamp = new PdfStamper(reader, new FileOutputStream(outPdfMarkPath));
PdfContentByte under;
int pageSize = reader.getNumberOfPages();// 原pdf文件的总页数
//水印图片
Image image;
if (markPath.contains(":"))
image = Image.getInstance(markPath);
else {
ClassPathResource resource = new ClassPathResource(markPath);
image = Image.getInstance(resource.getFile().getPath());
}
for (int i = 1; i <= pageSize; i++) {
under = stamp.getOverContent(i);// 水印在之前文本下
for (int j = 0; j < 4; j++) {
image.setAbsolutePosition(0, j * 250 + 100);//水印位置
under.addImage(image);
image.setAbsolutePosition(200, j * 250 + 100);//水印位置
under.addImage(image);
image.setAbsolutePosition(400, j * 250 + 100);//水印位置
under.addImage(image);
}
}
stamp.close();// 关闭
reader.close();//关闭
return outPdfMarkPath;
} catch (Exception e) {
e.printStackTrace();
try {
if (stamp != null) {
stamp.close();
}
if (reader != null) {
reader.close();//关闭
}
} catch (Exception ex) {
ex.printStackTrace();
}
} finally {
//删除生成的无水印pdf
File file = new File(inPdfPath);
if (file.exists() && file.isFile()) {
file.delete();
}
}
return "";
}
/**
* word 单步操作类
*/
public static class WordOption {
private static WordprocessingMLPackage wordMLPackage;
public WordprocessingMLPackage getWordMLPackage() {
return wordMLPackage;
}
/**
* 创建一个空白 Docx 文档
*/
public WordOption createDocx() throws Docx4JException {
wordMLPackage = WordprocessingMLPackage.createPackage();
return this;
}
/**
* 加载 Docx 文件
*
* @param filePath 文件地址
* @return WordProcessingMLPackage操作包
*/
public WordOption loadDocx(String filePath) throws Docx4JException {
wordMLPackage = WordprocessingMLPackage.load(new File(filePath));
return this;
}
/**
* 添加带样式的文本/段落
*
* @param styled 样式
* @param conText 文本
*/
public WordOption addContext(String styled, String conText) {
wordMLPackage.getMainDocumentPart().addStyledParagraphOfText(styled, conText);
return this;
}
/**
* 添加文本/段落
*
* @param conText 正文
*/
public WordOption addContext(String conText) {
wordMLPackage.getMainDocumentPart().addParagraphOfText(conText);
return this;
}
/**
* 添加图片
*
* @param imagePath 图片地址
*/
public WordOption addImage(String imagePath) throws Exception {
BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, new File(imagePath));
Inline inline = imagePart.createImageInline("Filename hint", "Alternative text", 1, 2, false);
ObjectFactory factory = new ObjectFactory();
P paragraph = factory.createP();
R run = factory.createR();
paragraph.getContent().add(run);
Drawing drawing = factory.createDrawing();
run.getContent().add(drawing);
drawing.getAnchorOrInline().add(inline);
wordMLPackage.getMainDocumentPart().addObject(paragraph);
return this;
}
/**
* 添加空表格
*
* @param row 行数
* @param col 列数
*/
public WordOption addTable(int row, int col) {
Tbl table = TblFactory.createTable(row, col, 20000 / col);
wordMLPackage.getMainDocumentPart().addObject(table);
return this;
}
/**
* 添加带数据表格, 数据必须是整齐的
* 表头为数据的 key, 表头在第一行
*
* @param list 数据
*/
public WordOption addTableWithDataAndTopHeader(List<Map<String, String>> list) {
Set<String> keySet = new HashSet<>(list.get(0).keySet());
ObjectFactory factory = Context.getWmlObjectFactory();
Tbl table = TblFactory.createTable(0, 0, 20000 / list.get(0).size());
// 表头
Tr tableHeader = factory.createTr();
keySet.forEach(e -> {
Tc tableCell = factory.createTc();
tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e));
tableHeader.getContent().add(tableCell);
});
table.getContent().add(tableHeader);
// 数据
list.forEach(e -> {
Tr tableRow = factory.createTr();
keySet.forEach(item -> {
Tc tableCell = factory.createTc();
tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e.get(item)));
tableRow.getContent().add(tableCell);
});
table.getContent().add(tableRow);
});
wordMLPackage.getMainDocumentPart().addObject(table);
return this;
}
/**
* 添加带数据表格, 数据必须是整齐的
* 表头为数据的 key, 表头在第一列
*
* @param list 数据
*/
public WordOption addTableWithDataAndLeftHeader(List<Map<String, String>> list) {
Set<String> keySet = new HashSet<>(list.get(0).keySet());
ObjectFactory factory = Context.getWmlObjectFactory();
Tbl table = TblFactory.createTable(0, 0, 20000 / list.get(0).size());
keySet.forEach(e -> {
Tr tableRow = factory.createTr();
Tc tableHeader = factory.createTc();
tableHeader.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e));
tableRow.getContent().add(tableHeader);
list.forEach(item -> {
Tc tableCell = factory.createTc();
tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(item.get(e)));
tableRow.getContent().add(tableCell);
});
table.getContent().add(tableRow);
});
wordMLPackage.getMainDocumentPart().addObject(table);
return this;
}
/**
* 替换模板Docx中数据和表格数据动态添加
* 必须存在占位符
*
* @param data 全局替换属性Map
* @param tableDataList 列表属性
* @param tableTemplateIndex 表格模版位置
* @param tableIndex 表格位置
* @throws Exception 异常
*/
public WordOption replaceData(Map<String, String> data, List<Map<String, Object>> tableDataList, int tableTemplateIndex, int tableIndex) throws Exception {
// 构造循环列表的数据
ClassFinder find = new ClassFinder(Tbl.class);
new TraversalUtil(wordMLPackage.getMainDocumentPart().getContent(), find);
// 获取第二个表格属性
if (find.results.size() > 0) {
Tbl table = (Tbl) find.results.get(tableIndex);
// 第二行约定为模板
Tr dynamicTr = (Tr) table.getContent().get(tableTemplateIndex);
// 获取模板行的xml数据
String dynamicTrXml = XmlUtils.marshaltoString(dynamicTr);
// 循环填充模板表格行数据
int addIndex = tableTemplateIndex;
for (Map<String, Object> dataMap : tableDataList) {
addIndex++;
Tr newTr = (Tr) XmlUtils.unmarshallFromTemplate(dynamicTrXml, dataMap);
table.getContent().add(addIndex, newTr);
}
// 删除模板行的占位行
table.getContent().remove(tableTemplateIndex);
}
// 设置全局的变量替换
wordMLPackage.getMainDocumentPart().variableReplace(data);
return this;
}
/**
* 加载模板并替换数据
* 必须存在占位符
*
* @param data 数据属性map
* @return 输出文件路径
* @throws Exception 异常
*/
public WordOption replaceData(Map<String, String> data) throws Exception {
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
VariablePrepare.prepare(wordMLPackage);
// 替换属性
documentPart.variableReplace(data);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
wordMLPackage.save(outputStream);
return this;
}
public void save(String path) throws Docx4JException {
wordMLPackage.save(new File(path));
}
public void save(OutputStream outputStream) throws Docx4JException {
wordMLPackage.save(outputStream);
}
}
}
四、水印工具类
五、docx模板及替换结果
![](https://img-blog.csdnimg.cn/6c4c550aac494c359d2b69ebba2b9ff3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARGlseV9TdQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/8064389baec9495ba6f84de6d7d0b478.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBARGlseV9TdQ==,size_20,color_FFFFFF,t_70,g_se,x_16)