- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
近期参与的多个项目中,均涉及根据预定义模板生成Word文档以供前端下载的需求。以往,我们通常采用将Word文档转换为XML格式,并通过代码赋值变量的方式来实现这一功能。尽管此方法在技术层面可行,但当面对篇幅较长且包含大量变量的文档时,其弊端便显露无遗:代码冗长繁杂,模板维护困难,不利于后续的修改与扩展.
鉴于此,近期对市场上现有的解决方案进行了深入调研,旨在寻找一种更为高效、简洁的Word文档生成方式。经过综合评估,推荐以下两款优秀的组件,旨在为开发者提供更为便捷的开发体验.
方案 | 移植性 | 功能性 | 易用性 |
---|---|---|---|
XDocReport | Java跨平台 | 支持多种文档格式,强大的模板引擎,易于集成 | 模板与代码分离,易于管理与修改,适用于复杂文档,处理速度快 |
Poi-tl | Java跨平台 | 轻量级模板引擎,专注于Word文档生成,简洁易用 | 模板语法简洁,降低维护成本,针对Word文档优化,性能稳定 |
Apache POI | Java跨平台 | Apache项目,封装了常见的文档操作,也可以操作底层XML结构 | 文档不全,这里有一个教程:Apache POI Word快速入门 |
Freemarker | XML跨平台 | 需要手动转换Word为XML,代码量大 | 模板与代码紧密耦合,修改复杂,变量多时长文档生成效率低 |
OpenOffice | 部署OpenOffice,移植性较差 | - | 需要了解OpenOffice的API |
HTML浏览器导出 | 依赖浏览器的实现,移植性较差 | HTML不能很好的兼容Word的格式,样式糟糕 | - |
Jacob、winlib | Windows平台 | - | 复杂,完全不推荐使用 |
综上所述,XDocReport与Poi-tl两款组件在Word文档生成方面均表现出色,各有千秋。XDocReport功能全面,适用于大型企业级应用;而Poi-tl则以其轻量级、简洁易用的特点,更适合中小型项目及快速迭代开发场景。开发者可根据项目实际需求选择合适的组件,以提升开发效率与代码质量.
xdocreport是一个基于Apache POI和Velocity/Freemarker的Java库,主要用于生成和处理各种文档格式,如DOCX、ODT、PDF等。它通过模板引擎语法(如Freemarker、Velocity)将数据动态插入到文档中,支持多种格式的转换和文档生成.
代码托管地址:https://github.com/opensagres/xdocreport 。
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.core</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
<version>2.0.6</version>
</dependency>
插入能被正常替换的占位符是模板的核心,创建占位符:插入 - 文档部件 - 域 - 邮件合并 - 域代码 - 确定 。
插入域:Ctrl + F9 。
显示域:Alt + F9 。
/**
* 使用 xdocreport 生成 word
* 一共需要5步,其中只有第4步需要开发者自行实现
*/
public static void main(String[] args) {
try (
// 1. 定义输入流,读取模板
InputStream ins = Files.newInputStream(Paths.get(TEMP_PATH));
// 2. 定义输出流,输出文件
OutputStream out = Files.newOutputStream(Paths.get(OUT_PATH))
) {
// 3. 读取模板,创建 IXDocReport 对象
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
// 4. 创建上下文,设置变量
IContext context = report.createContext();
context.put("name", "张三");
// 5. 生成 word
report.process(context, out);
} catch (Exception e) {
log.error("生成word失败", e);
}
}
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* 文本输出
* 创建域 占位符使用 ${var}
* 域的创建方式:插入 - 文档部件 - 域 - 邮件合并 - 域代码 - 确定
* 注意:占位符中的 MERGEFIELD 不可删除
* 会保留模板中的样式
* @author dafeng
* @date 2024/12/27 9:59
*/
@Slf4j
public class Demo2 {
// 模板路径
private static final String TEMP_PATH = "D:\\deployment\\test\\xdoc-report\\demo2-temp.docx";
// 输出文档路径
private static final String OUT_PATH = "D:\\deployment\\test\\xdoc-report\\demo2-out.docx";
/**
* 使用 xdocreport 生成 word
*/
public static void main(String[] args) {
try (
// 1. 定义输入流,读取模板
InputStream ins = Files.newInputStream(Paths.get(TEMP_PATH));
// 2. 定义输出流,输出文件
OutputStream out = Files.newOutputStream(Paths.get(OUT_PATH))
) {
// 3. 读取模板,创建 IXDocReport 对象
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
// 4. 创建上下文,设置变量
IContext context = report.createContext();
// 填充变量
setContext(context);
// 5. 生成 word
report.process(context, out);
} catch (Exception e) {
log.error("生成word失败", e);
}
}
/**
* 自定义变量填充
*/
private static void setContext(IContext context) {
context.put("name", "张三");
}
}
import com.xajw.xdocreport.vo.User;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Date;
import java.util.HashMap;
/**
* 复杂对象
* ${var.v} ${var.v.x}
*
* @author dafeng
* @date 2024/12/27 9:59
*/
@Slf4j
public class Demo3 {
// 模板路径
private static final String TEMP_PATH = "D:\\deployment\\test\\xdoc-report\\demo3-temp.docx";
// 输出文档路径
private static final String OUT_PATH = "D:\\deployment\\test\\xdoc-report\\demo3-out.docx";
/**
* 使用 xdocreport 生成 word
*/
public static void main(String[] args) {
try (
// 1. 定义输入流,读取模板
InputStream ins = Files.newInputStream(Paths.get(TEMP_PATH));
// 2. 定义输出流,输出文件
OutputStream out = Files.newOutputStream(Paths.get(OUT_PATH))
) {
// 3. 读取模板,创建 IXDocReport 对象
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
// 4. 创建上下文,设置变量
IContext context = report.createContext();
// 填充变量
setContext(context);
// 5. 生成 word
report.process(context, out);
} catch (Exception e) {
log.error("生成word失败", e);
}
}
/**
* 自定义变量填充
*/
private static void setContext(IContext context) {
User user = new User("张三", 18, new Date(), new User.Address("中国", "陕西", "西安"));
context.put("user", user);
HashMap<String, Object> map = new HashMap<String, Object>(){{
put("name", "李四");
put("age", 19);
put("birthday", new Date());
put("address", new User.Address("中国", "四川", "成都"));
}};
context.put("map", map);
}
}
import com.xajw.xdocreport.vo.User;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 列表循环
* [#list varList as var] ${var} [/#list]
* 占位符需要有开始和结束
*
* @author dafeng
* @date 2024/12/27 9:59
*/
@Slf4j
public class Demo4 {
// 模板路径
private static final String TEMP_PATH = "D:\\deployment\\test\\xdoc-report\\demo4-temp.docx";
// 输出文档路径
private static final String OUT_PATH = "D:\\deployment\\test\\xdoc-report\\demo4-out.docx";
/**
* 使用 xdocreport 生成 word
*/
public static void main(String[] args) {
try (
// 1. 定义输入流,读取模板
InputStream ins = Files.newInputStream(Paths.get(TEMP_PATH));
// 2. 定义输出流,输出文件
OutputStream out = Files.newOutputStream(Paths.get(OUT_PATH))
) {
// 3. 读取模板,创建 IXDocReport 对象
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
// 4. 创建上下文,设置变量
IContext context = report.createContext();
// 填充变量
setContext(context);
// 5. 生成 word
report.process(context, out);
} catch (Exception e) {
log.error("生成word失败", e);
}
}
/**
* 自定义变量填充
*/
private static void setContext(IContext context) {
List<String> varList = new ArrayList<String>() {{
add("张三");
add("李四");
add("王五");
}};
context.put("varList", varList);
List<User> userList = new ArrayList<User>(){{
add(new User("张三", 18, new Date(), new User.Address("中国", "陕西", "西安")));
add(new User("李四", 19, new Date(), new User.Address("中国", "四川", "成都")));
add(new User("王五", 20, new Date(), new User.Address("中国", "河南", "郑州")));
}};
context.put("userList", userList);
List<List<String>> table = new ArrayList<List<String>>(){{
add(new ArrayList<String>(){{
add("第一行:第一列");
add("第一行:第二列");
}});
add(new ArrayList<String>(){{
add("第二行:第一列");
add("第二行:第二列");
}});
add(new ArrayList<String>(){{
add("第三行:第一列");
add("第三行:第二列");
}});
}};
context.put("table", table);
}
}
import com.xajw.xdocreport.vo.Dtl;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import lombok.extern.slf4j.Slf4j;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
/**
* 表格输出
*
* @author dafeng
* @date 2024/12/27 9:59
*/
@Slf4j
public class Demo5 {
// 模板路径
private static final String TEMP_PATH = "D:\\deployment\\test\\xdoc-report\\demo5-temp.docx";
// 输出文档路径
private static final String OUT_PATH = "D:\\deployment\\test\\xdoc-report\\demo5-out.docx";
/**
* 使用 xdocreport 生成 word
*/
public static void main(String[] args) {
try (
// 1. 定义输入流,读取模板
InputStream ins = Files.newInputStream(Paths.get(TEMP_PATH));
// 2. 定义输出流,输出文件
OutputStream out = Files.newOutputStream(Paths.get(OUT_PATH))
) {
// 3. 读取模板,创建 IXDocReport 对象
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
// 4. 创建上下文,设置变量
IContext context = report.createContext();
// 填充变量
setContext(context);
// 5. 生成 word
report.process(context, out);
} catch (Exception e) {
log.error("生成word失败", e);
}
}
/**
* 自定义变量填充
*/
private static void setContext(IContext context) {
List<Dtl> list2 = new ArrayList<Dtl>(){{
add(new Dtl("001", "国铁", "中国", new ArrayList<Dtl.Tender>(){{
add(new Dtl.Tender("张三", 18.0, "无"));
add(new Dtl.Tender("李四", 19.0, "无"));
add(new Dtl.Tender("王五", 20.0, "无"));
}}));
add(new Dtl("002", "电子所", "北京", new ArrayList<Dtl.Tender>(){{
add(new Dtl.Tender("赵六", 21.0, "无"));
add(new Dtl.Tender("钱七", 22.0, "无"));
add(new Dtl.Tender("孙八", 23.0, "无"));
}}));
add(new Dtl("003", "经纬公司", "陕西", new ArrayList<Dtl.Tender>(){{
add(new Dtl.Tender("周九", 24.0, "无"));
add(new Dtl.Tender("吴十", 25.0, "无"));
add(new Dtl.Tender("郑十一", 26.0, "无"));
}}));
}};
context.put("dtlList", list2);
}
}
开头编辑域 "@before-row[# list dtlList as dtl]" 。
结尾编辑域 "@after-row[/# list]" 。
@before-row和@after-row需要成对出现 。
编号 | 单位 | 地址 |
---|---|---|
«@before-row[#list itemList as item»«${item.code}» | «${item.unit}» | «${item.address}»«@after-row[/#list]» |
子表格开头编辑域和结尾编辑域不添加 @before-row和@after-row 。
这种实现方式子表格会多出一行空行 。
编号 | 单位 | 中标人 | 单价 |
---|---|---|---|
«@before-row[#list itemList as item»«${item.code}» | «${item.unit}» | «[#list item.tender as tender]»«${tender.spName}» | «${tender.price}» |
«[/#list]»«@after-row[/#list]» |
移除多余的空行 xxx为子表格循环对象 。
"[#if (!xxx_has_next)]" 。
"[#else]" 。
"[/#if]" 。
如下:在子表格最后一行只保留一个单元格,其他的单元格需删掉(如上图所示) 。
编号 | 单位 | 中标人 | 单价 |
---|---|---|---|
«@before-row[#list itemList as item»«${item.code}» | «${item.unit}» | «[#list item.tender as tender]»«${tender.spName}» | «${tender.price}»«[#if (!tender_has_next)]»«[#else]» |
«[/#if]»«[/#list]»«@after-row[/#list]» |
如下:
红色为最外层循环 。
蓝色为子表格循环 。
绿色为隐藏子表格空白行 。
编号 | 中标人 |
---|---|
«@before-row[#list itemList as item»«${item.code}» | «[#list item.tender as tender]»«${tender.spName}»«[#if (!tender_has_next)]»«[#else]» |
«[/#if]»«[/#list]»«@after-row[/#list]» |
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.images.ByteArrayImageProvider;
import fr.opensagres.xdocreport.document.images.FileImageProvider;
import fr.opensagres.xdocreport.document.images.IImageProvider;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
import fr.opensagres.xdocreport.template.formatter.NullImageBehaviour;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 图片输出
*
* @author dafeng
* @date 2024/12/27 9:59
*/
@Slf4j
public class Demo6 {
// 模板路径
private static final String TEMP_PATH = "D:\\deployment\\test\\xdoc-report\\demo6-temp.docx";
// 输出文档路径
private static final String OUT_PATH = "D:\\deployment\\test\\xdoc-report\\demo6-out.docx";
/**
* 使用 xdocreport 生成 word
*/
public static void main(String[] args) {
try (
// 1. 定义输入流,读取模板
InputStream ins = Files.newInputStream(Paths.get(TEMP_PATH));
// 2. 定义输出流,输出文件
OutputStream out = Files.newOutputStream(Paths.get(OUT_PATH))
) {
// 3. 读取模板,创建 IXDocReport 对象
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);
// 4. 创建上下文,设置变量
IContext context = report.createContext();
// 填充变量
setContext(report, context);
// 5. 生成 word
report.process(context, out);
} catch (Exception e) {
log.error("生成word失败", e);
}
}
/**
* 自定义变量填充
*/
private static void setContext(IXDocReport report, IContext context) throws XDocReportException {
FieldsMetadata metadata = report.createFieldsMetadata();
// 1. 单个图片
File img = new File("D:\\deployment\\test\\xdoc-report\\1.png");
context.put("img", new FileImageProvider(img));
metadata.addFieldAsImage("img");
// 2. 循环图片
IImageProvider p1 = new FileImageProvider(new File("D:\\deployment\\test\\xdoc-report\\1.png"));
IImageProvider p2 = new FileImageProvider(new File("D:\\deployment\\test\\xdoc-report\\2.png"));
IImageProvider p3 = new FileImageProvider(new File("D:\\deployment\\test\\xdoc-report\\3.png"));
IImageProvider p4 = new FileImageProvider(new File("D:\\deployment\\test\\xdoc-report\\4.png"));
List<Map<String, IImageProvider>> list = new ArrayList<>();
list.add(Map.of("pic", p1));
list.add(Map.of("pic", p2));
list.add(Map.of("pic", p3));
list.add(Map.of("pic", p4));
context.put("picList", list);
//映射:picture为模板中书签名,item.pic为word模板循环中的变量名
metadata.addFieldAsImage("picture","item.pic", NullImageBehaviour.RemoveImageTemplate);
}
private static void setContext1(IXDocReport report, IContext context) {
File file = new File("D:\\deployment\\test\\xdoc-report\\a.jpg");
try (FileInputStream in = new FileInputStream(file)){
FieldsMetadata metadata = report.createFieldsMetadata();
metadata.addFieldAsImage("img");
context.put("img", new ByteArrayImageProvider(in));
}catch (Exception e){
log.error("获取图片失败", e);
}
}
}
poi-tl是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库,你可以非常方便的加入到你的项目中。模板是Docx格式的Word文档,你可以使用Microsoft office、WPS Office、Pages等任何你喜欢的软件制作模板,也可以使用Apache POI代码来生成模板.
所有的标签都是以{{开头,以}}结尾,标签可以出现在任何位置,包括页眉,页脚,表格内部,文本框等,表格布局可以设计出很多优秀专业的文档,推荐使用表格布局.
poi-tl模板遵循“所见即所得”的设计,模板和标签的样式会被完全保留.
代码托管地址:https://github.com/Sayi/poi-tl 。
指导文档地址:https://deepoove.com/poi-tl/ 。
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,也可以在指定位置进行合并 |
用户自定义函数(插件) | 插件化设计,在文档任何位置执行函数 |
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version>
</dependency>
注:Poi-tl 创建模板相较于XDocReport比较简单,无需使用域,直接使用 {{var}} 即可 。
public static void main(String[] args) throws Exception {
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH).render(
new HashMap<String, Object>(){{
put("title", "Hi, poi-tl Word模板引擎");
}});
template.writeAndClose(new FileOutputStream(OUT_PATH));
}
{{title}}
import com.deepoove.poi.XWPFTemplate;
import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
/**
* poi-tl Word模板引擎
*
* @author dafeng
* @date 2024/12/20 13:41
*/
public class PoiTlUtil {
private static final String TEMP_PATH = "D:\\deployment\\test\\tl\\template.docx";
private static final String OUT_PATH = "D:\\deployment\\test\\tl\\output.docx";
public static void main(String[] args) throws Exception {
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH).render(genData());
template.writeAndClose(Files.newOutputStream(Paths.get(OUT_PATH)));
}
private static Object genData() {
return new HashMap<String, Object>() {{
// 文本
put("title", "Hi, poi-tl Word模板引擎");
}};
}
}
{{@image}}
{{@svg}}
{{@image1}}
{{@streamImg}}
{{@urlImg}}
{{@buffered}}
/**
* poi-tl Word模板引擎
*
* @author dafeng
* @date 2024/12/20 13:41
*/
public class PoiTlUtil {
private static final String TEMP_PATH = "D:\\deployment\\test\\tl\\template.docx";
private static final String OUT_PATH = "D:\\deployment\\test\\tl\\output.docx";
public static void main(String[] args) throws Exception {
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH).render(genData());
template.writeAndClose(Files.newOutputStream(Paths.get(OUT_PATH)));
}
private static Object genData() throws FileNotFoundException {
return new HashMap<String, Object>() {{
// 指定图片路径
put("image", "F:\\Z-图片\\a.jpg");
// svg图片
put("svg", "https://img1.baidu.com/it/u=1960110688,1786190632&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=281");
// 图片文件
put("image1", Pictures.ofLocal("F:\\a.jpg").size(120, 120).create());
// 图片流
put("streamImg", Pictures.ofStream(new FileInputStream("F:\\logo.png"), PictureType.PNG)
.size(100, 120).create());
// 网络图片(注意网络耗时对系统可能的性能影响)
put("urlImg", Pictures.ofUrl("http://xxx.com/icecream.png").size(100, 100).create());
// java图片,我们可以利用Java生成图表插入到word文档中
put("buffered", Pictures.ofBufferedImage(new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB), PictureType.PNG).size(100, 100).create());
}};
}
}
{{*list}}
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.Numberings;
import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
/**
* poi-tl Word模板引擎
*
* @author dafeng
* @date 2024/12/20 13:41
*/
public class PoiTlUtil {
private static final String TEMP_PATH = "D:\\deployment\\test\\tl\\template.docx";
private static final String OUT_PATH = "D:\\deployment\\test\\tl\\output.docx";
public static void main(String[] args) throws Exception {
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH).render(genData());
template.writeAndClose(Files.newOutputStream(Paths.get(OUT_PATH)));
}
private static Object genData() throws FileNotFoundException {
return new HashMap<String, Object>() {{
// 列表
put("list", Numberings.create("Plug-in grammar", "Supports word text, pictures, table...", "Not just templates"));
}};
}
}
{{#table0}}
{{#table1}}
{{#table2}}
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.MergeCellRule;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.Rows;
import com.deepoove.poi.data.Tables;
import com.deepoove.poi.data.style.BorderStyle;
import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
/**
* poi-tl Word模板引擎
*
* @author dafeng
* @date 2024/12/20 13:41
*/
public class PoiTlUtil2 {
private static final String TEMP_PATH = "D:\\deployment\\test\\tl\\template.docx";
private static final String OUT_PATH = "D:\\deployment\\test\\tl\\output.docx";
public static void main(String[] args) throws Exception {
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH).render(genData());
template.writeAndClose(Files.newOutputStream(Paths.get(OUT_PATH)));
}
private static Object genData() throws FileNotFoundException {
return new HashMap<String, Object>() {{
// 3. 表格
// 一个2行2列的表格
put("table0", Tables.of(new String[][]{
new String[]{"00", "01"},
new String[]{"10", "11"}
}).border(BorderStyle.DEFAULT).create());
// 第0行居中且背景为蓝色的表格
RowRenderData row0 = Rows.of("姓名", "学历").textColor("FFFFFF")
.bgColor("4472C4").center().create();
RowRenderData row1 = Rows.create("李四", "博士");
put("table1", Tables.create(row0, row1));
// 合并第1行所有单元格的表格
RowRenderData row2 = Rows.of("列0", "列1", "列2").center().bgColor("4472C4").create();
RowRenderData row3 = Rows.create("没有数据", null, null);
MergeCellRule rule = MergeCellRule.builder().map(MergeCellRule.Grid.of(1, 0), MergeCellRule.Grid.of(1, 2)).build();
put("table2", Tables.of(row2, row3).mergeRule(rule).create());
}};
}
}
货物明细和人工费在同一个表格中,货物明细需要展示所有货物,人工费需要展示所有费用。{{goods}} 是个标准的标签,将 {{goods}} 置于循环行的上一行,循环行设置要循环的标签和内容,注意此时的标签应该使用 [] ,以此来区别 poi-tl 的默认标签语法。同理,{{labors}} 也置于循环行的上一行.
模板 。
Java代码 。
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.Rows;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import com.xajw.export.app.tl.vo.DetailData;
import com.xajw.export.app.tl.vo.Goods;
import com.xajw.export.app.tl.vo.Labor;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* Poi-tl 表格行循环
* @author dafeng
* @date 2024/12/20 17:06
*/
public class PoiTlTableUtil1 {
private static final String TEMP_PATH = "D:\\deployment\\test\\tl\\template_table.docx";
private static final String OUT_PATH = "D:\\deployment\\test\\tl\\template_table_out.docx";
public static void main(String[] args) throws Exception {
// 绑定插件
LoopRowTableRenderPolicy rowPolicy = new LoopRowTableRenderPolicy(); // 表格行循环
Configure config = Configure.builder()
.bind("goods", rowPolicy)
.bind("labors", rowPolicy)
.build();
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH, config).render(genData());
template.writeAndClose(Files.newOutputStream(Paths.get(OUT_PATH)));
}
private static Object genData() {
return new HashMap<String, Object>() {{
List<Goods> goods = new ArrayList<>();
List<Labor> labors = new ArrayList<>();
for (int i = 0; i < 5; i++) {
goods.add(new Goods(i + 1, "商品" + i, "描述" + i, 10, 20, 30, 40));
labors.add(new Labor("类别" + i, 10, 20, 30));
}
put("goods", goods);
put("labors", labors);
put("total", 1220);
}};
}
}
模板 。
Java代码 。
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.Rows;
import com.deepoove.poi.plugin.table.LoopColumnTableRenderPolicy;
import com.xajw.export.app.tl.vo.DetailData;
import com.xajw.export.app.tl.vo.Goods;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* Poi-tl 表格列循环
*
* @author dafeng
* @date 2024/12/20 17:06
*/
public class PoiTlTableUtil1 {
private static final String TEMP_PATH = "D:\\deployment\\test\\tl\\template_table.docx";
private static final String OUT_PATH = "D:\\deployment\\test\\tl\\template_table_out.docx";
public static void main(String[] args) throws Exception {
// 绑定插件
LoopColumnTableRenderPolicy columnPolicy = new LoopColumnTableRenderPolicy(); // 表格列循环
Configure config = Configure.builder()
.bind("products", columnPolicy)
.build();
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH, config).render(genData());
template.writeAndClose(Files.newOutputStream(Paths.get(OUT_PATH)));
}
private static Object genData() {
return new HashMap<String, Object>() {{
List<Goods> products = new ArrayList<>();
for (int i = 0; i < 5; i++) {
products.add(new Goods(i + 1, "商品" + i, "描述" + i, 10, 20, 30, 40));
}
put("products", products);
put("total", 1220);
}};
}
}
当需求中的表格更加复杂的时候,我们完全可以设计好那些固定的部分,将需要动态渲染的部分单元格交给自定义模板渲染策略。poi-tl提供了抽象表格策略 DynamicTableRenderPolicy 来实现这样的功能.
模板 。
Java代码 。
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.Rows;
import com.deepoove.poi.plugin.table.LoopColumnTableRenderPolicy;
import com.xajw.export.app.tl.vo.DetailData;
import com.xajw.export.app.tl.vo.Goods;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* Poi-tl 动态表格
*
* @author dafeng
* @date 2024/12/20 17:06
*/
public class PoiTlTableUtil {
private static final String TEMP_PATH = "D:\\deployment\\test\\tl\\template_table.docx";
private static final String OUT_PATH = "D:\\deployment\\test\\tl\\template_table_out.docx";
public static void main(String[] args) throws Exception {
// 绑定插件
DetailTablePolicy detailTablePolicy = new DetailTablePolicy(); // 动态表格表格
Configure config = Configure.builder()
.bind("detailTable", detailTablePolicy)
.build();
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH, config).render(genData());
template.writeAndClose(Files.newOutputStream(Paths.get(OUT_PATH)));
}
private static Object genData() {
DetailData detailTable = new DetailData();
RowRenderData good = Rows.of("4", "墙纸", "书房+卧室", "1500", "/", "400", "1600").center().create();
List<RowRenderData> goods = Arrays.asList(good, good, good);
detailTable.setGoods(goods);
RowRenderData labor = Rows.of("油漆工", "2", "200", "400").center().create();
List<RowRenderData> labors = Arrays.asList(labor, labor, labor, labor);
detailTable.setLabors(labors);
return new HashMap<String, Object>() {{
put("detailTable", detailTable);
put("total", 1220);
}};
}
}
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import com.xajw.export.app.tl.vo.DetailData;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import java.util.List;
public class DetailTablePolicy extends DynamicTableRenderPolicy {
// 货品填充数据所在行数
int goodsStartRow = 2;
// 人工费填充数据所在行数
int laborsStartRow = 5;
@Override
public void render(XWPFTable table, Object data) throws Exception {
if (null == data) return;
DetailData detailData = (DetailData) data;
// 人工费 先创建表格后面的行数据
List<RowRenderData> labors = detailData.getLabors();
table.removeRow(laborsStartRow);
// 循环插入行
for (RowRenderData labor : labors) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(laborsStartRow);
for (int j = 0; j < 7; j++) {
insertNewTableRow.createCell();
}
// 合并单元格
TableTools.mergeCellsHorizonal(table, laborsStartRow, 0, 3);
// 单行渲染
TableRenderPolicy.Helper.renderRow(table.getRow(laborsStartRow), labor);
}
// 货物
List<RowRenderData> goods = detailData.getGoods();
table.removeRow(goodsStartRow);
for (RowRenderData good : goods) {
XWPFTableRow insertNewTableRow = table.insertNewTableRow(goodsStartRow);
for (int j = 0; j < 7; j++) {
insertNewTableRow.createCell();
}
TableRenderPolicy.Helper.renderRow(table.getRow(goodsStartRow), good);
}
}
}
{{?announce}}
Top of the world!
{{/announce}}
{{?person}}
Hi {{name}}!,{{age}}
{{/person}}
{{?paragraphList}}
{{content}}
{{/paragraphList}}
{{?produces}}
{{_index+1}}. {{=#this}} {{_is_first}} {{_is_last}} {{_has_next}} {{_is_even_item}} {{_is_odd_item}}
{{/produces}}
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import com.deepoove.poi.XWPFTemplate;
import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* poi-tl Word模板引擎
*
* @author dafeng
* @date 2024/12/20 13:41
*/
public class PoiTlUtil {
private static final String TEMP_PATH = "D:\\deployment\\test\\tl\\template.docx";
private static final String OUT_PATH = "D:\\deployment\\test\\tl\\output.docx";
public static void main(String[] args) throws Exception {
XWPFTemplate template = XWPFTemplate.compile(TEMP_PATH).render(genData());
template.writeAndClose(Files.newOutputStream(Paths.get(OUT_PATH)));
}
private static Object genData() throws FileNotFoundException {
return new HashMap<String, Object>() {{
// 5. 区块对
put("announce", false);
Map<String, String> person = MapUtil
.builder("name", "张三")
.put("age", "18")
.build();
put("person", person);
List<Map> wordDataList = new ArrayList<>();
wordDataList.add(MapUtil.builder("content", "明月几时有,把酒问青天。").build());
wordDataList.add(MapUtil.builder("content", "不知天上宫阙,今夕是何年?").build());
wordDataList.add(MapUtil.builder("content", "我欲乘风归去,又恐琼楼玉宇,高处不胜寒。").build());
wordDataList.add(MapUtil.builder("content", "大江东去,浪淘尽,千古风流人物。").build());
put("paragraphList", wordDataList);
put("produces", ListUtil.of("application/json", "application/xml", "text/html", "text/plain"));
}};
}
}
poi-tl提供了类 Configure 来配置常用的设置,使用方式如下:
ConfigureBuilder builder = Configure.builder();
XWPFTemplate.compile("template.docx", builder.buid());
组件默认使用 {{}} 的方式来致敬Google CTemplate,如果你更偏爱freemarker ${} 的方式:
builder.buildGramer("${", "}");
组件默认的图片标签是以@开始,若希望使用%开始作为图片标签:
builder.addPlugin('%', new PictureRenderPolicy());
也可以自由更改的标签标识类型 。
builder.addPlugin('@', new TableRenderPolicy());
builder.addPlugin('#', new PictureRenderPolicy());
这样{{@var}}就变成了表格标签,{{#var}}变成了图片标签,虽然不建议改变默认标签标识,但是从中可以看到poi-tl插件的灵活度,在插件章节中我们将会看到如何自定义自己的标签.
标签默认支持中文、字母、数字、下划线的组合,但可以通过正则表达式来配置标签的规则,如不允许中文:
builder.buildGrammerRegex("[\\w]+(\\.[\\w]+)*");
若允许除了标签前后缀外的任意字符:
builder.buildGrammerRegex(RegexUtils.createGeneral("{{", "}}"));
Spring Expression Language 是一个强大的表达式语言,支持在运行时查询和操作对象图,可作为独立组件使用,需要引入相应的依赖:
官方文档:https://docs.spring.io/spring-framework/docs/5.3.18/reference/html/core.html#expressions 。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.18</version>
</dependency>
为了在模板标签中使用SpringEL表达式,需要将标签配置为SpringEL模式:
builder.useSpringEL();
{{name}}
{{name.toUpperCase()}}
{{name == 'poi-tl'}}
{{empty?:'这个字段为空'}}
{{sex ? '男' : '女'}}
{{new java.text.SimpleDateFormat('yyyy-MM-dd HH:mm:ss').format(time)}}
{{price/10000 + '万元'}}
{{dogs[0].name}}
{{localDate.format(T(java.time.format.DateTimeFormatter).ofPattern('yyyy年MM月dd日'))}}
最后此篇关于Java生成Word文档之XDocReport和Poi-tl的文章就讲到这里了,如果你想了解更多关于Java生成Word文档之XDocReport和Poi-tl的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
如何将扩展名为 ttf 和 otf 的新字体导入 POI API,而不将这些字体安装到环境中? Is there a jar that i should update it with the path
在这个问题的所有引用资料中,它没有解决并且不给maven因为没有在maven中做。错误是 包 org.apache.poi.ss.usermodel 可以从多个模块访问:poi、poi.ooxm在
上下文: 尝试使用 Apache POI 的 poi 和 poi-ooxml 4.0.0 版本 jar 打开 XLSX 文件 问题: 程序抛出错误,如下所示。当我使用 4.0.0 版本时,我发现此错误
刚开始使用 POI 3.10 创建 Word 文档(XWPF)。 大多数事情都是直截了当的,但我不明白如何添加页码。 我添加了页脚,但页脚中的文字在每一页上都相同 最佳答案 我在 LibreOffic
我正在使用 Apache POI 评估工作簿的每个公式单元格。当一个单元格包含对标准 excel 函数 NOW() 的调用时,Poi 会正确评估它并将调用替换为当前时间 - 格式为 VM 的默认时区。
我已经阅读了许多与我的要求相关的博客和论坛,但到目前为止,我能够在我得到的所有帮助下为第一级生成项目符号或编号。谁能指导我如何使用 apache poi 创建多级编号。 想知道 Apache POI
我正在使用 apache poi 创建 Excel 工作表。我有像 - 337499.939437217 这样的数字,我想在 Excel 中显示它,而不进行四舍五入。此外,单元格格式应为数字(对于某些
情况是,我合并了第一行的所有五个单元格,并在第一行的第一个单元格中插入了一个图像。我的要求是使图像在第一行水平居中。 我试过 cellStyle.setAlignment(CellStyle.ALIG
我正在尝试替换模板 DOCX使用 Apache 的文档 POI通过使用 XWPFDocument类(class)。我在文档中有标签和 JSON文件以读取替换数据。我的问题是 DOCX 中的文本行似乎以
好吧,老实说:标题并没有说出全部真相。我正在使用带有多个按钮(保存、关闭、编辑等)和一个执行 POI 操作的按钮的自定义控件 - 它生成一个 Word 文件。 我在这里遇到一个问题:点击 POI 按钮
有什么方法可以让我获得 excel 连续显示的格式化值,而不是我从流中返回的原始值? 或者这是否属于“公式评估”类别,这不支持? 最佳答案 如果您有 Cell您正在尝试从中获取数据,请尝试以下操作 D
在 xlsx 工作簿中,有一些单元格带有一些无界 SUMIF 公式,如下所示:SUMIF(MySheetname!$B:$B,$E4,MySheetname!$I:$I) . 使用 Apache PO
我正在创建一个 Java 程序来读取 Excel 工作表并创建一个逗号分隔的文件。当我运行带有空白列的示例 excel 文件时,第一行工作正常,但其余行跳过空白单元格。 我已经阅读了将空白单元格插入行
我目前正在使用 POI 使用 XSLF 编辑 PPTX 文件内嵌入图表中的数据。我找到了一个使用带有饼图的模板 ppt 的示例,效果非常好。我还尝试编辑折线图并且它有效。但是,当我尝试编辑嵌入式条形图
我正在学习使用 Selenium 和 Excel 进行数据驱动测试。我正在参加一门在线类(class),要求在 Maven 中添加 Apache poi 和 poi-ooxml 依赖项。 我正在努力理
我们有一个具有画廊功能的应用程序,我们想将图像导出到 powerpoint 演示文稿中。我能够做到这一点,但由于图像的大小和方向不同,图像边界几乎总是超出 ppt 幻灯片。我如何调整图像的大小(我不想
我有一个带有以下幻灯片布局的 pptx: System.out.println("Available slide layouts:"); for(XSLFSlideMaster master
我正在尝试使用 Java 中的 POI api 创建 Excel 工作表。在那个 Excel 工作表中,我想要一个只有 TIME 的单元格。通过设置它,我们可以像在数字列中那样将单元格包含在该特定列的
Apache Poi 可以计算和返回公式中函数的结果。但是对于特殊函数 HYPERLINK(),它只返回“显示值”,而不是实际计算的超链接值。 我有一个 Excel 文件,其中包含复杂的计算超链接,这
我正在使用 Apache POI。 我可以使用“org.apache.poi.hwpf.extractor.WordExtractor”从文档文件中读取文本 甚至使用“org.apache.poi.h
我是一名优秀的程序员,十分优秀!