【Springboot】-创建Word
生成word
使用freemarker生成word
准备模板
1、创建模板文件
首先先建立一个word文件,输入模板内容freemaker的内容,下面是本次演示的word文件。
然后将word文件另存为 .xml
文件,然后再把文件后缀改成.ftl
。将项目的resource目录下建立一个templates目录(非必须步骤)将 模板文件放到templates目录下
打开模板文件按 Ctrl + Shift + L 将模板内容格式化。
2、处理模板
2.1 处理普通文本
处理文本比较简单,将需要替换文本中直接用占位符 ${}
替换即可。
这里发现一个问题因为之前在word格式时我就已经替换了变量,但是在ftl变量却被 拆分成多段了(见图1)。但是这样是freemaker是无法成功替换变量的,所以需要手动处理成到一个段里(如图2),关于这点实在太无语了,因为没有找到比较好的处理办法,只能手工处理,在实际的开发工作中曾经花了几个小时来做这件事情。
图1:
图2
2.2 处理表格
如果模板里需要用变量填充表格,建议模板里的表格像word文件一样建一个两行的表格。在模板中<<w:tbl>> 表示一个表格 、<w: tr> 表示一行、<w: tc> 表示一列。因为FreeMarker 是利用列表一行一行循环填充的,所以我们可以根据关键字找到<<w:tbl>>标签,因为第一个 <w: tr>是表头注意不要改到了,找到第二个<w: tr>在前后分别加上如下语句即可,后面的表格里后面的行<w: tr>需要删掉,建议模板里的表格像word文件一样建一个两行的表格即可这样就不用删了:
<#list itemList as item>
</#list>
替换后的模板如下:
2.3 处理图片
如果模板里需要用变量填充图片,建议先在word文件里插入一张图片,这样在模板文件里找到<pkg:binaryData>标签直接里面把里面的图片base64字符替换成变量即可,word里可以通过植入base64字符来展示图片:
替换前:
替换后:
<pkg:binaryData>${image1}</pkg:binaryData>
到此模板已经调整完成,接下来就可以开始写代码了。
项目代码
1、引入依赖
在项目的pom文件里引入如下依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
2、生成代码
将图片转成Base64字符串的公共方法:
public static String getImageBase64Str(String imgFile) {
try( InputStream in = new FileInputStream(imgFile)) {
byte[] data = new byte[in.available()];
in.read(data);
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
根据模板文件生成word,主要生成的word的文件后缀必须是doc不能是docx,不然生成的文件无法打开。
public static void crateWord(Map<String, Object> dataMap, String templatePath, String targetFile){
String path = templatePath.substring(0,templatePath.lastIndexOf("/"));
String templateName = templatePath.substring(templatePath.lastIndexOf("/") + 1);
try (FileOutputStream out = new FileOutputStream(targetFile);
Writer writer = new BufferedWriter(new OutputStreamWriter(out, "utf-8"))){
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
configuration.setDefaultEncoding("utf-8");
configuration.setClassForTemplateLoading(FreeMakerTest.class, path);
//除了ClassForTemplateLoading外,另一种模板加载方式DirectoryForTemplateLoading,也可用
//ClassPathResource resource = new ClassPathResource(path);
//configuration.setDirectoryForTemplateLoading(resource.getFile());
//加载模板
Template template = configuration.getTemplate(templateName);
//渲染模板
template.process(dataMap, writer);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (TemplateException e) {
throw new RuntimeException(e);
}
}
验证生成word
新建的列表数据实体类:
public class Arrears{
private String name;
private Integer num;
private String endDay;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public String getEndDay() {
return endDay;
}
public void setEndDay(String endDay) {
this.endDay = endDay;
}
}
准备模板填充数据
private static Map<String, Object> prepareParam(){
LocalDate currentDate = LocalDate.now();
LocalDate endDate = currentDate.plusYears(1L);
List<Arrears> arrearsList = new ArrayList<>();
arrearsList.add(new Arrears(){{setName("一顿老魏");setNum(1);setEndDay("三月内");}});
arrearsList.add(new Arrears(){{setName("贵州大黄牛");setNum(1);setEndDay("一年内");}});
arrearsList.add(new Arrears(){{setName("v我50");setNum(1);setEndDay("一月内");}});
//填充所需要的数据
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("debtor", "陈有楚");
dataMap.put("nowYear", String.valueOf(currentDate.getYear()));
dataMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
dataMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
dataMap.put("arrears", "一顿老魏、贵州大黄牛、v我50");
dataMap.put("endYear", String.valueOf(endDate.getYear()));
dataMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
dataMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
dataMap.put("arrearsList", arrearsList);
dataMap.put("image1", getImageBase64Str("D:\\picture\\其他\\24-05-23-142418.png"));
dataMap.put("creditor", "知北游");
return dataMap;
}
测试代码:
public static void main(String[] args) throws IOException {
//准备参数
Map<String, Object> dataMap = prepareParam();
crateWord(dataMap,"/templates/qiantiao.ftl","D:\\test\\qiantiao.doc");
}
测试结果:
使用poi生成word
准备模板
1、创建模板文件
创建一个word文件,输入如下图所示的内容:
代码实践
1、引入依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.0</version>
</dependency>
2、自定义XWPFDocument
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import java.io.IOException;
import java.io.InputStream;
public class CustomXWPFDocument extends XWPFDocument {
public CustomXWPFDocument(InputStream in) throws IOException {
super(in);
}
public CustomXWPFDocument() {
super();
}
public CustomXWPFDocument(OPCPackage pkg) throws IOException {
super(pkg);
}
/**
* @param id
* @param width 宽
* @param height 高
* @param paragraph 段落
*/
public void createPicture(int id, int width, int height,
XWPFParagraph paragraph) {
final int EMU = 9525;
width *= EMU;
height *= EMU;
String blipId = super.getRelationId(super.getAllPictures().get(id));
CTInline inline = paragraph.createRun().getCTR().addNewDrawing()
.addNewInline();
String picXml = ""
+ "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"
+ " <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
+ " <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
+ " <pic:nvPicPr>" + " <pic:cNvPr id=\""
+ id
+ "\" name=\"Generated\"/>"
+ " <pic:cNvPicPr/>"
+ " </pic:nvPicPr>"
+ " <pic:blipFill>"
+ " <a:blip r:embed=\""
+ blipId
+ "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>"
+ " <a:stretch>"
+ " <a:fillRect/>"
+ " </a:stretch>"
+ " </pic:blipFill>"
+ " <pic:spPr>"
+ " <a:xfrm>"
+ " <a:off x=\"0\" y=\"0\"/>"
+ " <a:ext cx=\""
+ width
+ "\" cy=\""
+ height
+ "\"/>"
+ " </a:xfrm>"
+ " <a:prstGeom prst=\"rect\">"
+ " <a:avLst/>"
+ " </a:prstGeom>"
+ " </pic:spPr>"
+ " </pic:pic>"
+ " </a:graphicData>" + "</a:graphic>";
inline.addNewGraphic().addNewGraphicData();
XmlToken xmlToken = null;
try {
xmlToken = XmlToken.Factory.parse(picXml);
} catch (XmlException xe) {
xe.printStackTrace();
}
inline.set(xmlToken);
inline.setDistT(0);
inline.setDistB(0);
inline.setDistL(0);
inline.setDistR(0);
CTPositiveSize2D extent = inline.addNewExtent();
extent.setCx(width);
extent.setCy(height);
CTNonVisualDrawingProps docPr = inline.addNewDocPr();
docPr.setId(id);
docPr.setName("图片名称");
docPr.setDescr("描述信息");
}
}
3、公用的方法和变量
private final String REGEX = "\\$\\{(.+?)\\}";
private CustomXWPFDocument document;
public PoICreateWordFactory(String templatePath) throws IOException {
loadTemplate(templatePath);
}
/**
* 加载模板
*
* @param templatePath 模板路径
* @return 包含返回true, 不包含返回false
*/
private void loadTemplate(String templatePath) throws IOException {
try (InputStream in = Files.newInputStream(Paths.get(templatePath))) {
//转成word
this.document = new CustomXWPFDocument(in);
}
}
/**
* 生成word
*
* @param targetFile word生成路径
* @return 包含返回true, 不包含返回false
*/
public void createWordFile(String targetFile) throws IOException {
try (FileOutputStream out = new FileOutputStream(targetFile)){
document.write(out);
}
}
/**
* 判断文本中是否包含$
*
* @param text 文本
* @return 包含返回true, 不包含返回false
*/
public boolean checkText(String text) {
boolean check = false;
if (text.indexOf("$") != -1) {
check = true;
}
return check;
}
4、工具类引用的包名
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
5、段落文本替换
/**
* 替换段落文本
*
* @param textMap(数据源)
*/
public void replaceText(Map<String, Object> textMap) {
//获取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//获取到段落中的所有文本内容
String text = paragraph.getText();
//判断此段落中是否有需要进行替换的文本
if (checkText(text)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替换模板原来位置
Pattern pattern = Pattern.compile(REGEX);
Matcher matcher = pattern.matcher(run.toString());
if (matcher.find()) {
String key = matcher.group(1);
if(textMap.containsKey(key)){
run.setText(String.valueOf(textMap.get(key)), 0);
}
}
}
}
}
}
6、图片替换
/**
* 替换图片
*
* @param imageMap(数据源)
*/
public void replaceImage(Map<String, byte[]> imageMap) throws org.apache.poi.openxml4j.exceptions.InvalidFormatException {
//段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
Set<Map.Entry<String, byte[]>> imageSets = imageMap.entrySet();
for (XWPFParagraph paragraph : paragraphs) {
//获取到段落中的所有文本内容
String text = paragraph.getText();
//判断此段落中是否有需要进行替换的文本
if (checkText(text)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替换模板原来位置
Pattern pattern = Pattern.compile(REGEX);
String runText = run.toString();
Matcher matcher = pattern.matcher(runText);
if (matcher.find()) {
String key = matcher.group(1);
if(imageMap.containsKey(key)){
//清空原有内容
run.setText("", 0);
//设置图片
document.addPictureData(imageMap.get(key), XWPFDocument.PICTURE_TYPE_PNG);
//创建一个word图片,并插入到文档中-->像素可改
document.createPicture(document.getAllPictures().size() - 1, 240, 240,paragraph);
break;
}
}
}
}
}
}
7、表格替换
/**
* 替换表格内容
*
* @param index(表格索引:第几个表格)
* @param tableList(数据源)
* @Return void
* @Exception
*/
public void replaceTable(int index, List<List<String>> tableList) {
XWPFTable table = document.getTables().get(index);
//创建行,根据需要插入的数据添加新行,不处理表头
for (int i = 1; i <= tableList.size(); i++) {
table.createRow();
}
//遍历表格插入数据
List<XWPFTableRow> rows = table.getRows();
for (int i = 1; i < tableList.size()+1; i++) {
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
List<String> rowData = tableList.get(i - 1);
for (int j = 0; j < rowData.size(); j++) {
XWPFTableCell cell = cells.get(j);
String text = rowData.get(j);
cell.setText(text);
//表格样式一致-->没有此段表格会默认左对齐
//有此段会使表格格式一致
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
}
}
}
8、完整的工具类代码
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PoICreateWordFactory {
/**
* 生成ord
*
*/
public void crateWord(Map<String, Object> dataMap, String templatePath, String targetFile) throws IOException, InvalidFormatException {
//加载模板文件
loadTemplate(templatePath);
//将dataMap拆分成 textMap、imageMap、tableMap TODO 烂尾中
//段落替换变量
Map<String, Object> textMap = new HashMap<>();
//替换模板数据
replaceText(textMap);
//图片替换变量
Map<String, byte[]> imageMap = new HashMap<>();
replaceImage(imageMap);
//写入表格
List<List<String>> arrearsList = new ArrayList<>();
replaceTable(0, arrearsList);
//生成新的word
createWordFile(targetFile);
}
private final String REGEX = "\\$\\{(.+?)\\}";
private CustomXWPFDocument document;
public PoICreateWordFactory() {}
public PoICreateWordFactory(String templatePath) throws IOException {
loadTemplate(templatePath);
}
/**
* 加载模板
*
* @param templatePath 模板路径
* @return 包含返回true, 不包含返回false
*/
public void loadTemplate(String templatePath) throws IOException {
try (InputStream in = Files.newInputStream(Paths.get(templatePath))) {
//转成word
this.document = new CustomXWPFDocument(in);
}
}
/**
* 生成word
*
* @param targetFile word生成路径
* @return 包含返回true, 不包含返回false
*/
public void createWordFile(String targetFile) throws IOException {
try (FileOutputStream out = new FileOutputStream(targetFile)){
document.write(out);
}
}
/**
* 判断文本中是否包含$
*
* @param text 文本
* @return 包含返回true, 不包含返回false
*/
public boolean checkText(String text) {
boolean check = false;
if (text.indexOf("$") != -1) {
check = true;
}
return check;
}
/**
* 替换段落文本
*
* @param textMap(数据源)
*/
public void replaceText(Map<String, Object> textMap) {
//获取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//获取到段落中的所有文本内容
String text = paragraph.getText();
//判断此段落中是否有需要进行替换的文本
if (checkText(text)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替换模板原来位置
Pattern pattern = Pattern.compile(REGEX);
Matcher matcher = pattern.matcher(run.toString());
if (matcher.find()) {
String key = matcher.group(1);
if(textMap.containsKey(key)){
run.setText(String.valueOf(textMap.get(key)), 0);
}
}
}
}
}
}
/**
* 替换图片
*
* @param imageMap(数据源)
*/
public void replaceImage(Map<String, byte[]> imageMap) throws org.apache.poi.openxml4j.exceptions.InvalidFormatException {
//段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
Set<Map.Entry<String, byte[]>> imageSets = imageMap.entrySet();
for (XWPFParagraph paragraph : paragraphs) {
//获取到段落中的所有文本内容
String text = paragraph.getText();
//判断此段落中是否有需要进行替换的文本
if (checkText(text)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替换模板原来位置
Pattern pattern = Pattern.compile(REGEX);
String runText = run.toString();
Matcher matcher = pattern.matcher(runText);
if (matcher.find()) {
String key = matcher.group(1);
if(imageMap.containsKey(key)){
//清空原有内容
run.setText("", 0);
//设置图片
document.addPictureData(imageMap.get(key), XWPFDocument.PICTURE_TYPE_PNG);
//创建一个word图片,并插入到文档中-->像素可改
document.createPicture(document.getAllPictures().size() - 1, 240, 240,paragraph);
break;
}
}
}
}
}
}
/**
* 替换表格内容
*
* @param index(表格索引:第几个表格)
* @param tableList(数据源)
* @Return void
* @Exception
*/
public void replaceTable(int index, List<List<String>> tableList) {
XWPFTable table = document.getTables().get(index);
//创建行,根据需要插入的数据添加新行,不处理表头
for (int i = 1; i <= tableList.size(); i++) {
table.createRow();
}
//遍历表格插入数据
List<XWPFTableRow> rows = table.getRows();
for (int i = 1; i < tableList.size()+1; i++) {
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
List<String> rowData = tableList.get(i - 1);
for (int j = 0; j < rowData.size(); j++) {
XWPFTableCell cell = cells.get(j);
String text = rowData.get(j);
cell.setText(text);
//表格样式一致-->没有此段表格会默认左对齐
//有此段会使表格格式一致
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
}
}
}
}
验证模板生成
1、测试代码
public static void main(String[] args) throws IOException, InvalidFormatException {
String templatePath = "D:\\文章\\word生成\\poi\\qiantiao.docx";
String targetFile = "D:\\test\\qiantiao.docx";
//初始化,并加载模板文件
PoICreateWordFactory poICreateWordFactory = new PoICreateWordFactory(templatePath);
//段落替换变量
LocalDate currentDate = LocalDate.now();
LocalDate endDate = currentDate.plusYears(1L);
Map<String, Object> textMap = new HashMap<>();
textMap.put("debtor", "陈有楚");
textMap.put("nowYear", String.valueOf(currentDate.getYear()));
textMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
textMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
textMap.put("arrears", "一顿老魏、贵州大黄牛、v我50");
textMap.put("endYear", String.valueOf(endDate.getYear()));
textMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
textMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
textMap.put("creditor", "知北游");
//替换模板数据
poICreateWordFactory.replaceText(textMap);
//图片替换变量
FileInputStream imageInput = new FileInputStream("D:\\picture\\其他\\24-05-23-142418.png");
byte[] bytes = new byte[imageInput.available()];
imageInput.read(bytes);
imageInput.close();
Map<String, byte[]> imageMap = new HashMap<>();
imageMap.put("image1", bytes);
poICreateWordFactory.replaceImage(imageMap);
//写入表格
List<List<String>> arrearsList = new ArrayList<>();
arrearsList.add(Arrays.asList("一顿老魏", "1", "三月内"));
arrearsList.add(Arrays.asList("贵州大黄牛", "1", "一年内"));
arrearsList.add(Arrays.asList("v我50", "1", "一月内"));
//获取表格位置 0代表第一个表格,写死第一个,模板里也只有一个模板
poICreateWordFactory.replaceTable(0, arrearsList);
//生成新的word
poICreateWordFactory.createWordFile(targetFile);
}
2、生成效果
总结
其实从测试代码里就可以发现这其实只是一个半成品代码,文本替换、图片替换、表格替换甚至需要分别传递不同的数据map。本来是打算合并成一个dataMap,然后根据参数类来区分是文本、图片、表格的。然后拆分成多个数据map。但是在写这些代码时发现了也是基于poi开发的开源项目poi-tl。功能很全,我想实现的功能他都有,顿时我写的上面这些代码就失去了意义,然后就烂尾了。。。后面有时间介绍一下poi-tl这个开源项目使用方式吧,经过试验这个确实功能完善,非常推荐。
本文转自 https://www.cnblogs.com/fhey/p/17542195.html,如有侵权,请联系删除。
使用poi-tl生成word
前言
1、什么是poi-tl
poi-tl是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库。同类型的FreeMarker或Velocity基于文本模板和数据生成新的html页面或配置文件。而poi tl是一个基于Word模板和数据生成新文档的Word模板引擎。
Word模板具有丰富的样式。Poi-tl将在生成的文档中完美地保留模板中的样式。也可以设置标记的样式。标记的样式将应用于替换的文本,因此您可以专注于模板设计。
poi-tl是一个“无逻辑”模板引擎。没有复杂的控制结构和变量分配,只有标签,有些标签可以用文本、图片、表格等代替,有些标签会隐藏某些文档内容,而另一些标签会循环一系列文档内容。
像变量赋值或条件语句这样的“强大”构造可以很容易地在模板系统中专门修改应用程序的外观。。。然而,以分离为代价,将模板本身变成应用程序逻辑的一部分。
poi-tl支持自定义函数(插件),函数可以在Word模板的任何地方执行,在文档的任何地方做任何事情都是poi-tl的目标。
2、官方信息
2.1 源码仓库
GitHub - Sayi/poi-tl: Generate awesome word(docx) with template
2.2 中文文档
Poi-tl Documentation (deepoove.com)
2.3 开源协议
Apache License 2.0
3、poi-tl的优势
3.1 poi-tl和其他模板引擎的对比
下面表格是官方文档中提供的与其他模板引擎的对比
方案 | 移植性 | 功能性 | 易用性 |
---|---|---|---|
Poi-tl | Java跨平台 | Word模板引擎,基于Apache POI,提供更友好的API | 低代码,准备文档模板和数据即可 |
Apache POI | Java跨平台 | Apache项目,封装了常见的文档操作,也可以操作底层XML结构 | 文档不全,这里有一个教程:Apache POI Word快速入门 |
Freemarker | XML跨平台 | 仅支持文本,很大的局限性 | 不推荐,XML结构的代码几乎无法维护 |
OpenOffice | 部署OpenOffice,移植性较差 | - | 需要了解OpenOffice的API |
HTML浏览器导出 | 依赖浏览器的实现,移植性较差 | HTML不能很好的兼容Word的格式,样式糟糕 | - |
Jacob、winlib | Windows平台 | - | 复杂,完全不推荐使用 |
3.2 poi-tl Word模板引擎支持的功能
Word模板引擎功能 | 描述 |
---|---|
文本 | 将标签渲染为文本 |
图片 | 将标签渲染为图片 |
表格 | 将标签渲染为表格 |
列表 | 将标签渲染为列表 |
图表 | 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)、散点图等图表渲染 |
If Condition判断 | 根据条件隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Foreach Loop循环 | 根据集合循环某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Loop表格行 | 循环复制渲染表格的某一行 |
Loop表格列 | 循环复制渲染表格的某一列 |
Loop有序列表 | 支持有序列表的循环,同时支持多级列表 |
Highlight代码高亮 | word中代码块高亮展示,支持26种语言和上百种着色样式 |
Markdown | 将Markdown渲染为word文档 |
Word批注 | 完整的批注功能,创建批注、修改批注等 |
Word附件 | Word中插入附件 |
SDT内容控件 | 内容控件内标签支持 |
Textbox文本框 | 文本框内标签支持 |
图片替换 | 将原有图片替换成另一张图片 |
书签、锚点、超链接 | 支持设置书签,文档内锚点和超链接功能 |
Expression Language | 完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL… |
样式 | 模板即样式,同时代码也可以设置样式 |
模板嵌套 | 模板包含子模板,子模板再包含子模板 |
合并 | Word合并Merge,也可以在指定位置进行合并 |
用户自定义函数(插件) | 插件化设计,在文档任何位置执行函数 |
基本的使用配置
1、引入依赖
1.1 Maven
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.1</version>
</dependency>
1.2 Gradl
implementation 'com.deepoove:poi-tl:1.12.1'
2、配置
2.1 新建配置
ConfigureBuilder builder = Configure.builder();
2.2 标签前后缀替换
poi-tl所有的标签都是以{{
开头,以}}
结尾,这是为了致敬Google CTemplate。标签可以出现在任何位置,包括页眉,页脚,表格内部,文本框等,表格布局可以设计出很多优秀专业的文档,推荐使用表格布局。
当然如果你更偏爱freemarker ${}
的方式,也可以添加如下配置修改标签的前后缀配置:
builder.buildGramer("${", "}");
2.3 加载模板
XWPFTemplate template = XWPFTemplate.compile("template.docx", builder.buid());
poi-tl加载使用XWPFTemplate.compile方法来加载模板,支持模板以绝对路径(String),File、InputStream、XWPFDocument四种格式传入。
2.4 填充数据
poi-tl数据类似于哈希或者字典,可以是Map结构(key是标签名称):
Map<String, Object> data = new HashMap<>();
data.put("name", "Sayi");
data.put("start_time", "2019-08-04");
template.render(dataMap);
2.5 输出文件
poi-tl以流的方式进行输出:
template.write(OutputStream stream);
可以写到任意输出流中,比如文件流:
template.write(new FileOutputStream("output.docx"));
如网络流:
response.setContentType("application/octet-stream");
response.setHeader("Content-disposition","attachment;filename=\""+"out_template.docx"+"\"");
// HttpServletResponse response
OutputStream out = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(out);
template.write(bos);
bos.flush();
out.flush();
PoitlIOUtils.closeQuietlyMulti(template, bos, out);
各类模板标签替换和填充
1 文本
1.1 文本的标签如下
{{var}}
1.2 支持数据类型
String
:文本TextRenderData
:有样式的文本HyperlinkTextRenderData
:超链接和锚点文本Object
:调用 toString() 方法转化为文本
1.3 文本数据填充方式如下
代码示例
put("name", "Sayi");
put("author", new TextRenderData("000000", "Sayi"));
put("link", new HyperlinkTextRenderData("website", "http://deepoove.com"));
put("anchor", new HyperlinkTextRenderData("anchortxt", "anchor:appendix1"));
除了new操作符,还提供了更加优雅的工厂 Texts
和链式调用的方式轻松构建文本模型。
链式代码示例
put("author", Texts.of("Sayi").color("000000").create());
put("link", Texts.of("website").link("http://deepoove.com").create());
put("anchor", Texts.of("anchortxt").anchor("appendix1").create());
2 图片
2.1 图片的标签如下:
图片标签以@开始:{{@var}}
2.2 支持数据类型
String
:图片url或者本地路径,默认使用图片自身尺寸- `PictureRenderData
ByteArrayPictureRenderData
FilePictureRenderData
UrlPictureRenderData
2.3 图片数据填充方式如下
// 指定图片路径
put("image", "logo.png");
// svg图片
put("svg", "https://img.shields.io/badge/jdk-1.6%2B-orange.svg");
// 设置图片宽高
put("image1", Pictures.ofLocal("logo.png").size(120, 120).create());
// 图片流
put("streamImg", Pictures.ofStream(new FileInputStream("logo.jpeg"), PictureType.JPEG)
.size(100, 120).create());
// 网络图片(注意网络耗时对系统可能的性能影响)
put("urlImg", Pictures.ofUrl("http://deepoove.com/images/icecream.png")
.size(100, 100).create());
// java图片
put("buffered", Pictures.ofBufferedImage(bufferImage, PictureType.PNG)
.size(100, 100).create());
3 表格
3.1 表格的标签如下:
表格标签以#开始:{{#var}}
3.2 支持数据类型
TableRenderData
3.3 表格数据填充方式如下
1. 基础表格示例
// 一个2行2列的表格
put("table0", Tables.of(new String[][] {
new String[] { "00", "01" },
new String[] { "10", "11" }
}).border(BorderStyle.DEFAULT).create());
2. 表格样式示例
// 第0行居中且背景为蓝色的表格
RowRenderData row0 = Rows.of("姓名", "学历").textColor("FFFFFF")
.bgColor("4472C4").center().create();
RowRenderData row1 = Rows.create("李四", "博士");
put("table1", Tables.create(row0, row1));
3. 表格合并示例
// 合并第1行所有单元格的表格
RowRenderData row0 = Rows.of("列0", "列1", "列2").center().bgColor("4472C4").create();
RowRenderData row1 = Rows.create("没有数据", null, null);
MergeCellRule rule = MergeCellRule.builder().map(Grid.of(1, 0), Grid.of(1, 2)).build();
put("table3", Tables.of(row0, row1).mergeRule(rule).create());
4、列表
4.1 列表的标签如下:
列表标签以*开始:{{*var}}
4.2 支持数据类型
List<String>
NumberingRenderData
4.3 列表数据填充方式如下
put("list", Numberings.create("Plug-in grammar",
"Supports word text, pictures, table...",
"Not just templates"));
验证
1、准备模板
首先我们建立一个word文件,在word文件里填充一下内容。
2、准备测试代码
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.*;
import dto.Qiankuan;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.*;
public class PoitlTest {
public static void main(String[] args) throws IOException {
ConfigureBuilder builder = Configure.builder();
//获取模板的文件流
FileInputStream fileInputStream = new FileInputStream("D:\\文章\\word生成\\poi-tl\\qiantiao.docx");
HashMap<String, Object> dataMap = new HashMap<>();
//添加文本
LocalDate currentDate = LocalDate.now();
LocalDate endDate = currentDate.plusYears(1L);
dataMap.put("debtor", "陈有楚");
dataMap.put("nowYear", String.valueOf(currentDate.getYear()));
dataMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
dataMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
//验证换行的情况
dataMap.put("arrears", "\n一顿老魏,\n贵州大黄牛,\nv我50");
dataMap.put("endYear", String.valueOf(endDate.getYear()));
dataMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
dataMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
//添加列表
List<String> list = Arrays.asList("阿大", "阿二", "阿三");
Numberings.NumberingBuilder numberingBuilder = Numberings.of(NumberingFormat.DECIMAL);
for (String s : list) {
numberingBuilder.addItem(s);
}
dataMap.put("witness", numberingBuilder.create());
//添加图片,考虑到实际生产环境图片大都都从文件服务获取,所以这里用图片流做例子
PictureRenderData pictureRenderData = Pictures.ofStream(Files.newInputStream(Paths.get("D:\\picture\\其他\\24-05-23-142418.png")), PictureType.JPEG)
.size(300, 220).create();
dataMap.put("image1", pictureRenderData);
List<Qiankuan> qiankuanList = getQiankuanList();
//添加表格
//填充表头,表格的第一行
RowRenderData row0 = Rows.of("拖欠物品", "拖欠次数", "偿还期限").center().bgColor("4472C4").create();
Tables.TableBuilder tableBuilder = Tables.of(row0);
//填充表格内容
for (Qiankuan qiankuan : qiankuanList) {
RowRenderData row = Rows.create(qiankuan.getName(), String.valueOf(qiankuan.getCount()), qiankuan.getQixian());
tableBuilder.addRow(row);
}
//MergeCellRule rule = MergeCellRule.builder().map(MergeCellRule.Grid.of(1, 0), MergeCellRule.Grid.of(1, 2)).build();
//tableBuilder.mergeRule(rule);
dataMap.put("table1", tableBuilder.create());
ChartMultiSeriesRenderData chart = Charts
.ofMultiSeries("ChartTitle", new String[] { "中文", "English" })
.addSeries("countries", new Double[] { 15.0, 6.0 })
.addSeries("speakers", new Double[] { 223.0, 119.0 })
.create();
dataMap.put("barChart", chart);
XWPFTemplate template = XWPFTemplate.compile(fileInputStream, builder.build())
.render(dataMap);
template.writeAndClose(Files.newOutputStream(Paths.get("D:\\test\\qiantiao-poitl.docx")));
System.out.println("success");
}
static List<Qiankuan> getQiankuanList() {
List<Qiankuan> list = new ArrayList<>();
Qiankuan q1 = new Qiankuan();
q1.setName("一顿老魏");
q1.setCount(1);
q1.setQixian("三月内");
list.add(q1);
Qiankuan q2 = new Qiankuan();
q2.setName("一顿大黄牛");
q2.setCount(1);
q2.setQixian("半年内");
list.add(q2);
Qiankuan q3 = new Qiankuan();
q3.setName("特一特");
q3.setCount(3);
q3.setQixian("一周内");
list.add(q3);
Qiankuan q4 = new Qiankuan();
q4.setName("v我50");
q4.setCount(5);
q4.setQixian("一周内");
list.add(q4);
return list;
}
}
3、生成效果
本文转自 https://www.cnblogs.com/fhey/p/17544518.html,如有侵权,请联系删除。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库