java生成docx文件、pdf文件、docx转pdf、docx转图片 pdf转图片工具
docx4j生成docx文件、pdf文件、docx转pdf、docx转图片 pdf转图片工具最近写项目时遇到一些操作数据填充word、pdf以及word转pdf、word转图片的需求。网络搜索资料经整理如下操作office文档、pdf一般来说有好几种实现方式1、docx4j+apache.pdfbox1.1 引入maven<!-- pdf 转图片--><dependency>
docx4j生成docx文件、pdf文件、docx转pdf、docx转图片 pdf转图片工具
最近写项目时遇到一些操作数据填充word、pdf以及word转pdf、word转图片的需求。网络搜索资料经整理如下
操作office文档、pdf一般来说有好几种实现方式
1、docx4j+apache.pdfbox
1.1 引入maven
<!-- pdf 转图片 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.25</version>
</dependency>
<!--
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.10.1</version>
</dependency>
-->
<!-- https://mvnrepository.com/artifact/fr.opensagres.xdocreport/org.apache.poi.xwpf.converter.pdf-->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
<version>1.0.4</version>
</dependency>
<!-- pdf 转图片 -->
<!-- docx4j 创建docx文件、pdf文档、word转pdf文档-->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-Internal</artifactId>
<version>8.2.4</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>8.2.4</version>
</dependency>
<!-- docx4j 创建docx文件、pdf文档、word转pdf文档-->
1.2 工具类
package com.sl.utils.office.word;
import com.sl.utils.id.IDUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.docx4j.Docx4J;
import org.docx4j.TraversalUtil;
import org.docx4j.XmlUtils;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.finders.RangeFinder;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.jaxb.Context;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* docx4j生成docx文件、pdf文件、docx转pdf、docx转图片 pdf转图片工具
*
* 有三种方式
* 1 通过占位符
*
* <p>
* 通过docx文件的书签、占位符替换变量
* <p>
* 通过占位符替换注意
* * 通过占位符替换注意 -----------坑坑坑坑 直接再docx文件中进行修改占位符不一定会连续!!!-----------
* ${var}必须是连续的,否则取不到变量。有时候取不到变量的时候可以抓换为xml然后查看你的变量是否是连续的
* 可以通过如下方式解决 现在docx文件中写入占位符然后
* 把当前docx文件用rar或zip打开,找到其中的 word/document.xml文件,修改占位符连续
* <p>
* 比如把
* <w:r>
* <w:t>${na</w:t>
* </w:r>
* <w:r>
* <w:t>me}</w:t>
* </w:r>
* 修改为
* <w:r>
* <w:t>${name}</w:t>
* </w:r>
* 2、全部通过书签
* 3、通过域变量
* 通过域变量需要重写 docx4居中 XmlUtils 工具类中unmarshallFromTemplate方法 以适配域变量
* 具体目录在 org.docx4j
* @author gaoxueyong
* @create at: 2021/12/28 下午15:02
*/
public class DocxAndPdfAndImgUtils {
private static final Logger log = LoggerFactory.getLogger(DocxAndPdfAndImgUtils.class);
private static WordprocessingMLPackage wordMLPackage;
private static ObjectFactory factory;
/**
* 通过docx模板获取docx模板转换的图片
* @param templatePath 模板文件
* @param mappings 要匹配的占位符数据
* @param fileMapping 书签名称对于的文件
* @return
*/
public static List<byte[]> getPngByDocxTemplate(String templatePath, Map<String, String> mappings, Map<String, byte[]> fileMapping) {
return pdfToImg(getPdfFile(templatePath, mappings, fileMapping));
}
/**
* 通过模板获取转换后docx的二进制数组
* @param templatePath 模板文件
* @param mappings 要匹配的占位符数据
* @param fileMapping 书签名称对于的文件
* @return
*/
public static byte[] getDocxByTemplate(String templatePath, Map<String, String> mappings, Map<String, byte[]> fileMapping) {
File docxFile = getDocxFile(templatePath, mappings, fileMapping);
try {
if (null == docxFile) {
return null;
}
byte[] bytes = Files.readAllBytes(docxFile.toPath());
if (docxFile.exists()) {
docxFile.delete();
}
return bytes;
} catch (IOException e) {
log.error("获取文件失败");
if (docxFile.exists()) {
docxFile.delete();
}
return null;
}
}
/**
* 通过模板获取转换后pdf文件
* @param templatePath 模板文件
* @param mappings 要匹配的占位符数据
* @param fileMapping 书签名称对于的文件
* @return
*/
public static byte[] getPdfFile(String templatePath, Map<String, String> mappings, Map<String, byte[]> fileMapping){
try {
File docxFile = getDocxFile(templatePath, mappings, fileMapping);
if(null == docxFile){
return null;
}
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxFile);
Path pdf = Files.createTempFile(IDUtils.getPrimaryId(), "pdf");
File pdfFile = pdf.toFile();
if(null == pdfFile){
log.error("创建文件失败");
return null;
}
Docx4J.toPDF(wordMLPackage, new FileOutputStream(pdfFile));
if(docxFile.exists()){
docxFile.delete();
}
byte[] bytes = Files.readAllBytes(pdf);
if(pdfFile.exists()){
pdfFile.delete();
}
return bytes;
} catch (Docx4JException e) {
log.error("bookReplaceVarText error:Docx4JException ", e);
return null;
} catch (Exception e) {
log.error("bookReplaceVarText error:Docx4JException ", e);
return null;
}
}
/**
* 通过文件输入流获取pdf文档的二进制数组
* @param docxInputstream
* @return
*/
public static byte[] getPdfByte(InputStream docxInputstream){
try {
if(null == docxInputstream){
return null;
}
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxInputstream);
Path pdf = Files.createTempFile(IDUtils.getPrimaryId(), "pdf");
File pdfFile = pdf.toFile();
if(null == pdfFile){
log.error("创建文件失败");
return null;
}
Docx4J.toPDF(wordMLPackage, new FileOutputStream(pdfFile));
byte[] bytes = Files.readAllBytes(pdf);
if(pdfFile.exists()){
pdfFile.delete();
}
return bytes;
} catch (Docx4JException e) {
log.error("bookReplaceVarText error:Docx4JException ", e);
return null;
} catch (Exception e) {
log.error("bookReplaceVarText error:Docx4JException ", e);
return null;
}
}
/**
* 通过模板获取转换后docx文件
* @param templatePath 模板文件
* @param mappings 要匹配的占位符数据
* @param fileMapping 书签名称对于的文件
* @return
*/
public static File getDocxFile(String templatePath, Map<String, String> mappings, Map<String, byte[]> fileMapping){
try {
wordMLPackage = WordprocessingMLPackage.load(new File(templatePath));
MainDocumentPart mainDocumentPart = wordMLPackage.getMainDocumentPart();
if(MapUtils.isNotEmpty(mappings)){
mainDocumentPart.variableReplace(mappings);
}
factory = Context.getWmlObjectFactory();
Document wmlDoc = (Document) mainDocumentPart.getJaxbElement();
Body body = wmlDoc.getBody();
// 提取正文中所有段落
List<Object> paragraphs = body.getContent();
// 提取书签并创建书签的游标
RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
new TraversalUtil(paragraphs, rt);
// 遍历书签
for (CTBookmark bm : rt.getStarts()) {
log.info("标签名称:" + bm.getName());
if(MapUtils.isEmpty(fileMapping)){
break;
}
if (fileMapping.containsKey(bm.getName())) {
addImage(wordMLPackage, bm, fileMapping.get(bm.getName()));
}else {
if(mappings.containsKey(bm.getName())){
replaceText (bm, mappings.get(bm.getName()));
}
}
}
wordMLPackage.setFontMapper(getFontMap());
Path docx = Files.createTempFile(IDUtils.getPrimaryId(), "docx");
File docxFile = docx.toFile();
Docx4J.save(wordMLPackage, docxFile);
return docxFile;
} catch (Docx4JException e) {
log.error("bookReplaceVarText error:Docx4JException ", e);
return null;
} catch (Exception e) {
log.error("bookReplaceVarText error:Docx4JException ", e);
return null;
}
}
/**
* 在标签处插入内容
* @param bm
* @param object
* @throws Exception
*/
public static void replaceText(CTBookmark bm, Object object) throws Exception {
if (object == null) {
return;
}
// do we have data for this one?
if (bm.getName() == null){
return;
}
String value = object.toString();
try {
// Can't just remove the object from the parent,
// since in the parent, it may be wrapped in a JAXBElement
List<Object> theList = null;
ParaRPr rpr = null;
if (bm.getParent() instanceof P) {
PPr pprTemp = ((P) (bm.getParent())).getPPr();
if (pprTemp == null) {
rpr = null;
} else {
rpr = ((P) (bm.getParent())).getPPr().getRPr();
}
theList = ((ContentAccessor) (bm.getParent())).getContent();
} else {
return;
}
int rangeStart = -1;
int rangeEnd = -1;
int i = 0;
for (Object ox : theList) {
Object listEntry = XmlUtils.unwrap(ox);
if (listEntry.equals(bm)) {
if (((CTBookmark) listEntry).getName() != null) {
rangeStart = i + 1;
}
} else if (listEntry instanceof CTMarkupRange) {
if (((CTMarkupRange) listEntry).getId().equals(bm.getId())) {
rangeEnd = i - 1;
break;
}
}
i++;
}
int x = i - 1;
// if (rangeStart > 0 && x >= rangeStart) {
// Delete the bookmark range
for (int j = x; j >= rangeStart; j--) {
theList.remove(j);
}
// now add a run
R run = factory.createR();
Text t = factory.createText();
// if (rpr != null)
// run.setRPr(paraRPr2RPr(rpr));
t.setValue(value);
run.getContent().add(t);
// t.setValue(value);
theList.add(rangeStart, run);
// }
} catch (ClassCastException cce) {
log.error("error", cce);
}
}
/**
* 插入图片
* @param wPackage
* @param bm
* @param bytes
*/
public static void addImage(WordprocessingMLPackage wPackage, CTBookmark bm,byte[] bytes) {
// log.info("addImage :->{},{},{}", wPackage, bm);
if(null == bytes){
return;
}
try {
// 这儿可以对单个书签进行操作,也可以用一个map对所有的书签进行处理
// 获取该书签的父级段落
P p = (P) (bm.getParent());
// R对象是匿名的复杂类型,然而我并不知道具体啥意思,估计这个要好好去看看ooxml才知道
R run = factory.createR();
// 读入图片并转化为字节数组,因为docx4j只能字节数组的方式插入图片
// byte[] bytes = IOUtils.toByteArray(new FileInputStream(file));
// InputStream is = new FileInputStream;
// byte[] bytes = IOUtils.toByteArray(inputStream);
// byte[] bytes = FileUtil.getByteFormBase64DataByImage("");
// 开始创建一个行内图片
BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wPackage, bytes);
// 最后一个参数是限制图片的宽度,缩放的依据
Inline inline = imagePart.createImageInline(null, null, 0, 1, false, 2350);
// 获取该书签的父级段落
// drawing理解为画布?
Drawing drawing = factory.createDrawing();
drawing.getAnchorOrInline().add(inline);
run.getContent().add(drawing);
p.getContent().add(run);
} catch (Exception e) {
log.error("", e);
}
}
/**
* 解决转换后数据显示异常的问题
* @return
*/
private static Mapper getFontMap(){
Mapper fontMapper = new IdentityPlusMapper();
fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
fontMapper.put("等线", PhysicalFonts.get("SimSun"));
fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
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-extB"));
fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
return fontMapper;
}
/**
* pdf 转图片
* @param pdfFile
* @return
*/
public static List<byte[]> pdfToImg(byte[] pdfFile) {
PDDocument doc = null;
List<byte[]> outFile = new ArrayList<>();
try {
doc = PDDocument.load(pdfFile);
PDFRenderer render = new PDFRenderer(doc);
int count = doc.getNumberOfPages();
for (int i = 0; i < count; i++) {
// 设置图片的分辨率
BufferedImage image = render.renderImageWithDPI(i, 296);
// 如果是PNG图片想要背景透明的话使用下面这个
// BufferedImage image = render.renderImageWithDPI(i, 296,
// ImageType.ARGB);
Path png = Files.createTempFile(IDUtils.getPrimaryId() + "_" + i, "png");
File pngFile = png.toFile();
ImageIO.write(image, "PNG", pngFile);
outFile.add(Files.readAllBytes(png));
pngFile.delete();
}
} catch (IOException e) {
log.error("pdf转换图片失败!");
} finally {
if (doc != null) {
try {
doc.close();
} catch (IOException e) {
log.error("关闭PDDocument失败");
}
}
return outFile;
}
}
}
1.2.1 重写 XmlUtils以适配域变量
重写 XmlUtils 工具类中unmarshallFromTemplate方法 以适配域变量
1.3 关于模板
1.3.1 新建一个docx文档如并添加占位符 (不推荐 可以全部都是用标签或域变量)
头像出需要添加书签,书签用以替换图片
1.3.2 修改占位符使其连续
用zip或rar工具打开docx文档并找到word/document.xml文件进行编辑
偷偷的告诉你,其实docx文档也是压缩包,可以修改其后缀直接变成zip文件
打开document.xml文档如下发现刚才的占位符是不连续的,我们要修改使其连续
使用编辑器修改后如下,然后把该文件写会到word问里,或者通过zip工具放进去
现在就可以直接调用工具方法替换数据以及图片了
1.3.2 使用域变量+书签(图片需要使用书签)
注意 使用域变量需要重写 docx4居中 XmlUtils 工具类中unmarshallFromTemplate方法 以适配域变量
2、xdocreport
2.1 引入maven
<!-- xdocreport 生成word、pdf -->
<!-- https://mvnrepository.com/artifact/fr.opensagres.xdocreport/fr.opensagres.xdocreport.document.docx -->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
<version>2.0.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/fr.opensagres.xdocreport/fr.opensagres.xdocreport.template.freemarker -->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.converter.docx.xwpf</artifactId>
<version>2.0.2</version>
</dependency>
<!-- xdocreport 生成word、pdf -->
<!-- pdf转图片 -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/fr.opensagres.xdocreport/fr.opensagres.poi.xwpf.converter.pdf -->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.poi.xwpf.converter.pdf</artifactId>
<version>2.0.2</version>
</dependency>
<!-- pdf转图片 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2.2 模板
注意 如果要动态替换图片需要先在模板里插入一张图片,否则无法替换图片
选中头像后的图片
2.3 逻辑代码
public static void main(String[] args) {
Map<String, Object> param = new HashMap<>();
param.put("user.username", "小明不怕不怕啦");
param.put("user.hobby", "爱玩dota");
param.put("name", "sssssssssssssssssssssss");
String rootPath = XdocreportUtils.class.getClassLoader().getResource("").getPath();
String filePath = String.format("%stemplates/xdocreport/xdocxtemplate.docx", rootPath);
Map<String, IImageProvider> imgMap = new HashMap<>();
IImageProvider logo = new FileImageProvider(new File("C:\\Users\\Administrator\\Desktop\\2021122211591833953227008.png"), true);
logo.setSize(500f, 500f);
imgMap.put("logo", logo);
IImageProvider pic = new FileImageProvider(new File("C:\\Users\\Administrator\\Desktop\\suoluePic.PNG"), true);
pic.setSize(200f, 200f);
imgMap.put("pic", pic);
exportDocx(param, imgMap, new File(filePath), "C:\\Users\\Administrator\\Desktop\\DocxProjectWithFreemarker_Out.docx");
exportPdf(param, imgMap, new File(filePath), "C:\\Users\\Administrator\\Desktop\\DocxProjectWithFreemarker_Out.pdf");
}
public static void exportDocx(Map<String, Object> param, Map<String, IImageProvider> imgMap, File templateFIle, String outPath) {
try {
InputStream in = new FileInputStream(templateFIle);
//载入模板
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(in, TemplateEngineKind.Freemarker);
IContext context = report.createContext();
//设置要替换的值
if (MapUtils.isNotEmpty(param)) {
for (Map.Entry<String, Object> entry : param.entrySet()) {
context.put(entry.getKey(), entry.getValue());
}
}
FieldsMetadata metadata = report.createFieldsMetadata();
report.setFieldsMetadata(metadata);
//替换图片
if (MapUtils.isNotEmpty(imgMap)) {
for (Map.Entry<String, IImageProvider> entry : imgMap.entrySet()) {
context.put(entry.getKey(), entry.getValue());
metadata.addFieldAsImage(entry.getKey());
}
}
report.setFieldsMetadata(metadata);
OutputStream out = new FileOutputStream(new File(outPath));
report.process(context, out);
out.close();
} catch (IOException e) {
log.error("导出docx文件出现异常!", e);
} catch (XDocReportException e) {
log.error("导出docx文件出现异常!", e);
}
}
public static void exportPdf(Map<String, Object> param, Map<String, IImageProvider> imgMap, File templateFIle, String outPath) {
try {
InputStream in = new FileInputStream(templateFIle);
//载入模板
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(in, TemplateEngineKind.Freemarker);
IContext context = report.createContext();
//设置要替换的值
if (MapUtils.isNotEmpty(param)) {
for (Map.Entry<String, Object> entry : param.entrySet()) {
context.put(entry.getKey(), entry.getValue());
}
}
FieldsMetadata metadata = report.createFieldsMetadata();
report.setFieldsMetadata(metadata);
//替换图片
if (MapUtils.isNotEmpty(imgMap)) {
for (Map.Entry<String, IImageProvider> entry : imgMap.entrySet()) {
context.put(entry.getKey(), entry.getValue());
metadata.addFieldAsImage(entry.getKey());
}
}
report.setFieldsMetadata(metadata);
OutputStream out = new FileOutputStream(new File(outPath));
Options options = Options.getTo(ConverterTypeTo.PDF).via(ConverterTypeVia.XWPF);
report.convert(context, options, out);
out.close();
} catch (IOException e) {
log.error("导出pdf文件出现异常!", e);
} catch (XDocReportException e) {
log.error("导出pdf文件出现异常!", e);
}
}
2.4 效果
3、冰蓝科技的Spire
3.1 引入maven配置
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.doc.free</artifactId>
<version>3.9.0</version>
</dependency>
<repositories>
<repository>
<id>com.e-iceblue</id>
<url>https://repo.e-iceblue.cn/repository/maven-public/</url>
</repository>
</repositories>
3.2 逻辑代码
import com.spire.doc.Document;
import com.spire.doc.FileFormat;
import com.spire.doc.documents.BookmarksNavigator;
import com.spire.doc.documents.Paragraph;
import com.spire.doc.documents.TextBodyPart;
import com.spire.doc.fields.DocPicture;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class SpireWordPicTest {
public static void main(String[] args) throws IOException {
//加载Word文档
Document doc = new Document("C:\\Users\\Administrator\\Desktop\\testDocTemplates.docx");
Map<String,String> mappings = new HashMap<>();
mappings.put("username","小明");
mappings.put("password","123456");
/**
* https://www.cnblogs.com/Yesi/p/11422349.html
* 需要先在模板里设置书签 然后替换
*
*/
for(Map.Entry<String,String > entry:mappings.entrySet()){
doc.replace(String.format("${%s}",entry.getKey()),entry.getValue(), false, true);
}
//定位到指定书签位置 设置二维码
BookmarksNavigator bookmarksNavigator = new BookmarksNavigator(doc);
bookmarksNavigator.moveToBookmark("headerPng", true, true);
Paragraph para= new Paragraph(doc);
DocPicture docPicture = para.appendPicture("C:\\Users\\Administrator\\Desktop\\企业微信截图_20211228152622.png");//设置图片宽度
docPicture.setWidth(110);
//设置图片高度
docPicture.setHeight(110);
TextBodyPart bodyPart = new TextBodyPart(doc);
bodyPart.getBodyItems().add(para);
bookmarksNavigator.replaceBookmarkContent(bodyPart);
//保存文档
doc.saveToFile("C:\\Users\\Administrator\\Desktop\\ReplaceAllMatchedText.docx", FileFormat.Docx_2013);
}
冰蓝科技的免费版代码执行效率很低,不知道正式版怎么样
https://www.e-iceblue.cn/spiredocforjava/spire-doc-for-java-program-guide-content.html
样例代码可参考 https://gitee.com/wahnn/SpringBoot2.x/tree/master/XdocreportAndDocx4j
更多推荐
所有评论(0)