Java Poi-tl根據(jù)模板導(dǎo)出Word文件
Poi-tl官網(wǎng):Poi-tl Documentation
最近又碰上了導(dǎo)出,真的服了,還是低代碼的導(dǎo)出,其中過(guò)程的曲折真的是一言難盡..........平臺(tái)各種適配,依賴沖突,真的是崩潰.......
簡(jiǎn)單說(shuō)一下為什么使用Poi-tl,最開始想用的是poi實(shí)現(xiàn),但是結(jié)果就是poi不好控制圖片的位置,因?yàn)閳D片插入之后是“浮于文字上方”,單純的插入圖片會(huì)占行數(shù),為了方便控制格式后續(xù)就采用了poi-tl但是他同樣也不支持插入圖片之后將圖片設(shè)置為“浮于文字上方”,也是找了一些博客參考,最終實(shí)現(xiàn)了想要的效果
參考博客:poi-tl導(dǎo)出word實(shí)現(xiàn)圖片環(huán)繞方式為浮于在文字上方辦法
老樣子上效果圖:

實(shí)現(xiàn)步驟
1.準(zhǔn)備模板

2.添加依賴
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.0-beta</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>3.代碼實(shí)現(xiàn)
package com.example.demo;
import cn.hutool.core.util.URLUtil;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.Pictures;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import com.example.controller.MyPictureRenderPolicy;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class StudentCardExporter001 {
public static void main(String[] args) {
// 創(chuàng)建數(shù)據(jù)映射列表
List<Map<String, Object>> sectionList = new ArrayList<>();
HashMap<String, Object> sectionMap = new HashMap<>();
sectionMap.put("zhenghao", "AS011");
sectionMap.put("chexing", "C2");
sectionMap.put("username", "楊無(wú)敵");
sectionMap.put("sex", "男");
sectionMap.put("birthday", "1983-12-06");
sectionMap.put("deptname", "北京XXXXXXX");
sectionMap.put("date", "2025-12-09");
sectionMap.put("localPicture", Pictures.ofLocal( "D:\\xiazai\\1212.jpg").size(45, 65).create());
sectionList.add(sectionMap);
// 這里可以設(shè)置多條數(shù)據(jù)
/*sectionMap.put("zhenghao", "AS011");
sectionMap.put("chexing", "C2");
sectionMap.put("username", "光頭強(qiáng)");
sectionMap.put("sex", "男");
sectionMap.put("birthday", "1983-12-06");
sectionMap.put("deptname", "北京XXXXXXX");
sectionMap.put("date", "2025-12-09");
sectionMap.put("localPicture", Pictures.ofLocal( "D:\\xiazai\\1212.jpg").size(45, 65).create());
sectionList.add(sectionMap);*/
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("sectionName", sectionList);
ArrayList<Object> titleList = new ArrayList<>();
// 創(chuàng)建最終的數(shù)據(jù)映射
for (int i = 0; i < 2; i++) {
HashMap<String, Object> titleMap = new HashMap<>();
titleMap.put("oneTitle", " 1、駕駛車輛必須攜帶此證,以備檢查。");
titleMap.put("tweTitle", " 2、持證人必須遵守本公司車輛管理規(guī)定及《中華\n" +
" 人民共和國(guó)道路交通安全法》。");
titleMap.put("threeTitle", " 3、遺失此證必須及時(shí)報(bào)告發(fā)證單位。");
titleMap.put("fourTitle", " 4、此證不得偽造、涂改,不得轉(zhuǎn)借他人;一經(jīng)發(fā)\n" +
" 現(xiàn),立即取消其本人準(zhǔn)駕資格并收回其此證。\n");
titleList.add(titleMap);
}
paramMap.put("attention", titleList);
// 加載模板文件
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
org.springframework.core.io.Resource[] resources = resolver.getResources("classpath:excelTemplate/model1.docx");
if (resources.length == 0) {
throw new IOException("Template file not found.");
}
InputStream inputStream = resources[0].getInputStream();
ConfigureBuilder builder = Configure.builder();
builder.addPlugin('%', new MyPictureRenderPolicy());
// 編譯并渲染模板
XWPFTemplate template = XWPFTemplate.compile(inputStream, builder.build()).render(paramMap);
// XWPFTemplate compile = XWPFTemplate.compile(inputStream, builder.build()).render(paramMap);
// 輸出生成的文檔
try (FileOutputStream out = new FileOutputStream("D:\\xiazai\\1209.docx")) {
template.write(out);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}自定義MyPictureRenderPolicy
package com.example.controller;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.PictureType;
import com.deepoove.poi.data.Pictures;
import com.deepoove.poi.data.style.PictureStyle;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.policy.AbstractRenderPolicy;
import com.deepoove.poi.render.RenderContext;
import com.deepoove.poi.util.BufferedImageUtils;
import com.deepoove.poi.util.SVGConvertor;
import com.deepoove.poi.util.UnitUtils;
import com.deepoove.poi.xwpf.BodyContainer;
import com.deepoove.poi.xwpf.BodyContainerFactory;
import com.deepoove.poi.xwpf.WidthScalePattern;
import com.deepoove.poi.xwpf.XWPFRunWrapper;
import com.example.demo.ExportUtil;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.function.Supplier;
public class MyPictureRenderPolicy extends AbstractRenderPolicy<Object> {
@Override
protected boolean validate(Object data) {
if (null == data) {
return false;
} else if (data instanceof PictureRenderData) {
return null != ((PictureRenderData) data).getPictureSupplier();
} else {
return true;
}
}
@Override
public void doRender(RenderContext<Object> context) throws Exception {
MyPictureRenderPolicy.Helper.renderPicture(context.getRun(), wrapper(context.getData()));
}
@Override
protected void afterRender(RenderContext<Object> context) {
this.clearPlaceholder(context, false);
}
@Override
protected void reThrowException(RenderContext<Object> context, Exception e) {
this.logger.info("Render picture " + context.getEleTemplate() + " error: {}", e.getMessage());
String alt = "";
if (context.getData() instanceof PictureRenderData) {
alt = ((PictureRenderData) context.getData()).getAltMeta();
}
context.getRun().setText(alt, 0);
}
private static PictureRenderData wrapper(Object object) {
return object instanceof PictureRenderData ? (PictureRenderData) object : Pictures.of(object.toString()).fitSize().create();
}
public static class Helper {
public static void renderPicture(XWPFRun run, PictureRenderData picture) throws Exception {
Supplier<byte[]> supplier = picture.getPictureSupplier();
byte[] imageBytes = (byte[]) supplier.get();
if (null == imageBytes) {
throw new IllegalStateException("Can't read picture byte arrays!");
} else {
PictureType pictureType = picture.getPictureType();
if (null == pictureType) {
pictureType = PictureType.suggestFileType(imageBytes);
}
if (null == pictureType) {
throw new RenderException("PictureRenderData must set picture type!");
} else {
PictureStyle style = picture.getPictureStyle();
if (null == style) {
style = new PictureStyle();
}
int width = style.getWidth();
int height = style.getHeight();
if (pictureType == PictureType.SVG) {
imageBytes = SVGConvertor.toPng(imageBytes, (float) width, (float) height);
pictureType = PictureType.PNG;
}
if (!isSetSize(style)) {
BufferedImage original = BufferedImageUtils.readBufferedImage(imageBytes);
width = original.getWidth();
height = original.getHeight();
if (style.getScalePattern() == WidthScalePattern.FIT) {
BodyContainer bodyContainer = BodyContainerFactory.getBodyContainer(((IBodyElement) run.getParent()).getBody());
int pageWidth = UnitUtils.twips2Pixel(bodyContainer.elementPageWidth((IBodyElement) run.getParent()));
if (width > pageWidth) {
double ratio = (double) pageWidth / (double) width;
width = pageWidth;
height = (int) ((double) height * ratio);
}
}
}
InputStream stream = new ByteArrayInputStream(imageBytes);
Throwable var25 = null;
try {
PictureStyle.PictureAlign align = style.getAlign();
if (null != align && run.getParent() instanceof XWPFParagraph) {
((XWPFParagraph) run.getParent()).setAlignment(ParagraphAlignment.valueOf(align.ordinal() + 1));
}
XWPFRunWrapper wrapper = new XWPFRunWrapper(run, false);
wrapper.addPicture(stream, pictureType.type(), "Generated", Units.pixelToEMU(width), Units.pixelToEMU(height));
CTDrawing drawing = run.getCTR().getDrawingArray(0);
CTGraphicalObject graphicalobject = drawing.getInlineArray(0).getGraphic();
//拿到新插入的圖片替換添加CTAnchor 設(shè)置浮動(dòng)屬性 刪除inline屬性
CTAnchor anchor = ExportUtil.getAnchorWithGraphic(graphicalobject, "Generated",
Units.toEMU(width), Units.toEMU(height),//圖片大小
Units.toEMU(180), Units.toEMU(-60), false);//相對(duì)當(dāng)前段落位置 需要計(jì)算段落已有內(nèi)容的左偏移
drawing.setAnchorArray(new CTAnchor[]{anchor});//添加浮動(dòng)屬性
drawing.removeInline(0);//刪除行內(nèi)屬性
} catch (Throwable var20) {
var25 = var20;
throw var20;
} finally {
if (stream != null) {
if (var25 != null) {
try {
stream.close();
} catch (Throwable var19) {
var25.addSuppressed(var19);
}
} else {
stream.close();
}
}
}
}
}
}
}
private static boolean isSetSize(PictureStyle style) {
return (style.getWidth() != 0 || style.getHeight() != 0) && style.getScalePattern() == WidthScalePattern.NONE;
}
}圖片處理
package com.example.demo;
import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
import java.math.BigInteger;
public class ExportUtil {
/**
* @param ctGraphicalObject 圖片數(shù)據(jù)
* @param deskFileName 圖片描述
* @param width 寬
* @param height 高
* @param leftOffset 水平偏移 left
* @param topOffset 垂直偏移 top
* @param behind 文字上方,文字下方
* @return
* @throws Exception
*/
public static CTAnchor getAnchorWithGraphic(CTGraphicalObject ctGraphicalObject,
String deskFileName, int width, int height,
int leftOffset, int topOffset, boolean behind) {
String anchorXML =
"<wp:anchor xmlns:wp=\"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" "
+ "simplePos=\"0\" relativeHeight=\"0\" behindDoc=\"" + ((behind) ? 1 : 0) + "\" locked=\"0\" layoutInCell=\"1\" allowOverlap=\"1\">"
+ "<wp:simplePos x=\"0\" y=\"0\"/>"
+ "<wp:positionH relativeFrom=\"character\">"
+ "<wp:posOffset>" + leftOffset + "</wp:posOffset>"
+ "</wp:positionH>"
+ "<wp:positionV relativeFrom=\"paragraph\">"
+ "<wp:posOffset>" + topOffset + "</wp:posOffset>"
+ "</wp:positionV>"
+ "<wp:extent cx=\"" + width + "\" cy=\"" + height + "\"/>"
+ "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>"
+ "<wp:wrapNone/>"
+ "<wp:docPr id=\"1\" name=\"Drawing 0\" descr=\"" + deskFileName + "\"/><wp:cNvGraphicFramePr/>"
+ "</wp:anchor>";
CTDrawing drawing = null;
try {
drawing = CTDrawing.Factory.parse(anchorXML);
} catch (XmlException e) {
e.printStackTrace();
}
CTAnchor anchor = drawing.getAnchorArray(0);
anchor.setGraphic(ctGraphicalObject);
// 減少左偏移量以使圖片更靠前
leftOffset = Math.max(0, leftOffset - 500); // 根據(jù)需要調(diào)整減去的值
anchor.getPositionH().setPosOffset(leftOffset);
anchor.getPositionV().setPosOffset(topOffset);
return anchor;
}
}到此這篇關(guān)于Java Poi-tl根據(jù)模板導(dǎo)出Word文件的文章就介紹到這了,更多相關(guān)Java Poi-tl導(dǎo)出Word內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud啟動(dòng)eureka server后,沒(méi)報(bào)錯(cuò)卻不能訪問(wèn)管理頁(yè)面(404問(wèn)題)
這篇文章主要介紹了SpringCloud啟動(dòng)eureka server后,沒(méi)報(bào)錯(cuò)卻不能訪問(wèn)管理頁(yè)面(404問(wèn)題),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
使用springCloud+nacos集成seata1.3.0搭建過(guò)程
這篇文章主要介紹了使用springCloud+nacos集成seata1.3.0搭建過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
JMeter自定義日志與日志分析的實(shí)現(xiàn)
JMeter與Java程序一樣,會(huì)記錄事件日志,本文就介紹一下JMeter自定義日志與日志分析的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
從零搭建SpringBoot+MyBatisPlus快速開發(fā)腳手架
這篇文章主要為大家介紹了從零搭建SpringBoot+MyBatisPlus快速開發(fā)腳手架示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
SpringBoot上傳文件大小受限問(wèn)題的解決辦法
最近有一次由于項(xiàng)目升級(jí)發(fā)現(xiàn)了一個(gè)上傳方面的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于SpringBoot上傳文件大小受限問(wèn)題的解決辦法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05
論Java Web應(yīng)用中調(diào)優(yōu)線程池的重要性
這篇文章主要論述Java Web應(yīng)用中調(diào)優(yōu)線程池的重要性,通過(guò)了解應(yīng)用的需求,組合最大線程數(shù)和平均響應(yīng)時(shí)間,得出一個(gè)合適的線程池配置2016-04-04
SpringBoot利用Junit動(dòng)態(tài)代理實(shí)現(xiàn)Mock方法
說(shuō)到Spring Boot 單元測(cè)試主要有兩個(gè)主流集成分別是Mockito,Junit,這個(gè)各有特點(diǎn),在實(shí)際開發(fā)中,我想要的測(cè)試框架應(yīng)該是這個(gè)框架集成者,本文給大家介紹了SpringBoot利用Junit動(dòng)態(tài)代理實(shí)現(xiàn)Mock方法,需要的朋友可以參考下2024-04-04

