hello大家好,今天我要给大家推荐一个非常棒的Word专用的模板引擎,Poi-tl(Poi-template-language)。这款引擎基于Apache Poi,可以根据用户输入的内容直接生成相应的word文档,很是方便。
前言
作为一名开发者,模板引擎,大家都应该用过吧,模板中有一系列占位符,模板+数据,传递给模板引擎,会输出一份新的文档,如下图,模板可以启动重用的作用。
常见的模板基本上都是文本格式的,比如 html 格式的、text 格式的,这种格式的模板处理起来相对比较容易,对应的模板引擎有很多,比如 java 中的 freemarker,velocity,thymeleaf。
如果我们的模板是 word 呢?
比如开发、测试、项目管理人员,通常需要写文档,而多数文档基本上格式都差不多,如果能够有一款模板引擎可以对 word 进行处理,传入不同的数据,就输出不同的 word 文档,那咱们的功能效率将大大提高。
Poi-tl
poi-tl(poi template language)是 Word 模板引擎,基于 Microsoft Word 模板和数据生成新的文档。
Word 模板拥有丰富的样式,poi-tl 在生成的文档中会完美保留模板中的样式,还可以为标签设置样式,标签的样式会被应用到替换后的文本上,因此你可以专注于模板设计。
这个是 java 开发的一款 word 模板引擎,如果你是搞 java 的,想必你肯定用过 POI,java 中最强大的 office 处理工具库,可以通过 java 代码轻松的操作 word、excel、ppt 中的所有内容,而 Poi-tl 底层用的就是 POI。
POI-TL 功能清单
引擎功能 | 描述 |
---|---|
文本 | 将标签渲染为文本 |
图片 | 将标签渲染为图片 |
表格 | 将标签渲染为表格 |
列表 | 将标签渲染为列表 |
图表 | 条形图(3D 条形图)、柱形图(3D 柱形图)、面积图(3D 面积图)、折线图(3D 折线图)、雷达图、饼图(3D 饼图)等图表渲染 |
If Condition 判断 | 隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Foreach Loop 循环 | 循环某些文档内容(包括文本、段落、图片、表格、列表、图表等) |
Loop 表格行 | 循环渲染表格的某一行 |
Loop 表格列 | 循环渲染表格的某一列 |
Loop 有序列表 | 支持有序列表的循环,同时支持多级列表 |
图片替换 | 将原有图片替换成另一张图片 |
书签、锚点、超链接 | 支持设置书签,文档内锚点和超链接功能 |
强大的表达式 | 完全支持 SpringEL 表达式,可以扩展更多的表达式:OGNL, MVEL… |
标签定制 | 支持自定义标签前后缀 |
文本框 | 文本框内标签支持 |
样式 | 模板即样式,同时代码也可以设置样式 |
模板嵌套 | 模板包含子模板,子模板再包含子模板 |
合并 | Word 合并 Merge,也可以在指定位置进行合并 |
用户自定义函数(插件) | 在文档任何位置执行函数 |
Poi-tl整体设计采用了Template + data-model = output模式.
Configure提供了模板配置功能,比如语法配置和插件配置:
/**
* 插件化配置
*
* @author Sayi
* @version 1.0.0
*/
public class Configure {
// defalut expression
private static final String DEFAULT_GRAMER_REGEX = "[\\w\\u4e00-\\u9fa5]+(\\.[\\w\\u4e00-\\u9fa5]+)*";
// Highest priority
private Map customPolicys = new HashMap();
// Low priority
private Map defaultPolicys = new HashMap();
/**
* 引用渲染策略
*/
private List> referencePolicies = new ArrayList<>();
/**
* 语法前缀
*/
private String gramerPrefix = "{{";
/**
* 语法后缀
*/
private String gramerSuffix = "}}";
/**
* 默认支持中文、字母、数字、下划线的正则
*/
private String grammerRegex = DEFAULT_GRAMER_REGEX;
/**
* 模板表达式模式,默认为POI_TL_MODE
*/
private ELMode elMode = ELMode.POI_TL_STANDARD_MODE;
/**
* 渲染数据校验不通过时的处理策略
*
* - DiscardHandler: 什么都不做
* - ClearHandler: 清空标签
* - AbortHandler: 抛出异常
*
*/
private ValidErrorHandler handler = new ClearHandler();
private Configure() {
plugin(GramerSymbol.TEXT, new TextRenderPolicy());
plugin(GramerSymbol.IMAGE, new PictureRenderPolicy());
plugin(GramerSymbol.TABLE, new MiniTableRenderPolicy());
plugin(GramerSymbol.NUMBERIC, new NumbericRenderPolicy());
plugin(GramerSymbol.DOCX_TEMPLATE, new DocxRenderPolicy());
}
/**
* 创建默认配置
*
* @return
*/
public static Configure createDefault() {
return newBuilder().build();
}
/**
* 构建器
*
* @return
*/
public static ConfigureBuilder newBuilder() {
return new ConfigureBuilder();
}
/**
* 新增或变更语法插件
*
* @param c
* 语法
* @param policy
* 策略
*/
public Configure plugin(char c, RenderPolicy policy) {
defaultPolicys.put(Character.valueOf(c), policy);
return this;
}
/**
* 新增或变更语法插件
*
* @param symbol
* 语法
* @param policy
* 策略
* @return
*/
Configure plugin(GramerSymbol symbol, RenderPolicy policy) {
defaultPolicys.put(symbol.getSymbol(), policy);
return this;
}
/**
* 自定义模板和策略
*
* @param tagName
* 模板名称
* @param policy
* 策略
*/
public void customPolicy(String tagName, RenderPolicy policy) {
customPolicys.put(tagName, policy);
}
/**
* 新增引用渲染策略
*
* @param policy
*/
public void referencePolicy(ReferenceRenderPolicy policy) {
referencePolicies.add(policy);
}
/**
* 获取标签策略
*
* @param tagName
* 模板名称
* @param sign
* 语法
*/
// Query Operations
public RenderPolicy getPolicy(String tagName, Character sign) {
RenderPolicy policy = getCustomPolicy(tagName);
return null == policy ? getDefaultPolicy(sign) : policy;
}
public List> getReferencePolicies() {
return referencePolicies;
}
private RenderPolicy getCustomPolicy(String tagName) {
return customPolicys.get(tagName);
}
private RenderPolicy getDefaultPolicy(Character sign) {
return defaultPolicys.get(sign);
}
public Map getDefaultPolicys() {
return defaultPolicys;
}
public Map getCustomPolicys() {
return customPolicys;
}
public Set getGramerChars() {
return defaultPolicys.keySet();
}
public String getGramerPrefix() {
return gramerPrefix;
}
public String getGramerSuffix() {
return gramerSuffix;
}
public String getGrammerRegex() {
return grammerRegex;
}
public ELMode getElMode() {
return elMode;
}
public ValidErrorHandler getValidErrorHandler() {
return handler;
}
public static class ConfigureBuilder {
private boolean regexForAll;
private Configure config;
public ConfigureBuilder() {
config = new Configure();
}
public ConfigureBuilder buildGramer(String prefix, String suffix) {
config.gramerPrefix = prefix;
config.gramerSuffix = suffix;
return this;
}
public ConfigureBuilder buildGrammerRegex(String reg) {
config.grammerRegex = reg;
return this;
}
public ConfigureBuilder supportGrammerRegexForAll() {
this.regexForAll = true;
return this;
}
public ConfigureBuilder setElMode(ELMode mode) {
config.elMode = mode;
return this;
}
public ConfigureBuilder setValidErrorHandler(ValidErrorHandler handler) {
config.handler = handler;
return this;
}
public ConfigureBuilder addPlugin(char c, RenderPolicy policy) {
config.plugin(c, policy);
return this;
}
public ConfigureBuilder customPolicy(String tagName, RenderPolicy policy) {
config.customPolicy(tagName, policy);
return this;
}
public ConfigureBuilder referencePolicy(ReferenceRenderPolicy policy) {
config.referencePolicy(policy);
return this;
}
public ConfigureBuilder bind(String tagName, RenderPolicy policy) {
config.customPolicy(tagName, policy);
return this;
}
public Configure build() {
if (config.elMode == ELMode.SPEL_MODE) {
regexForAll = true;
}
if (regexForAll) {
config.grammerRegex = RegexUtils.createGeneral(config.gramerPrefix,
config.gramerSuffix);
}
return config;
}
}
}
Visitor提供了模板解析功能:
/**
* 模板解析器
*
* @author Sayi
* @version 1.4.0
*/
public class TemplateVisitor implements Visitor {
private static Logger logger = LoggerFactory.getLogger(TemplateVisitor.class);
private Configure config;
private List eleTemplates;
private Pattern templatePattern;
private Pattern gramerPattern;
static final String FORMAT_TEMPLATE = "{0}{1}{2}{3}";
static final String FORMAT_GRAMER = "({0})|({1})";
public TemplateVisitor(Configure config) {
this.config = config;
initPattern();
}
@Override
public List visitDocument(XWPFDocument doc) {
if (null == doc) return null;
this.eleTemplates = new ArrayList();
logger.info("Visit the document start...");
visitParagraphs(doc.getParagraphs());
visitTables(doc.getTables());
visitHeaders(doc.getHeaderList());
visitFooters(doc.getFooterList());
logger.info("Visit the document end, resolve and create {} ElementTemplates.",
this.eleTemplates.size());
return eleTemplates;
}
void visitHeaders(List headers) {
if (null == headers) return;
for (XWPFHeader header : headers) {
visitParagraphs(header.getParagraphs());
visitTables(header.getTables());
}
}
void visitFooters(List footers) {
if (null == footers) return;
for (XWPFFooter footer : footers) {
visitParagraphs(footer.getParagraphs());
visitTables(footer.getTables());
}
}
void visitParagraphs(List paragraphs) {
if (null == paragraphs) return;
for (XWPFParagraph paragraph : paragraphs) {
visitParagraph(paragraph);
}
}
void visitTables(List tables) {
if (null == tables) return;
for (XWPFTable tb : tables) {
visitTable(tb);
}
}
void visitTable(XWPFTable table) {
if (null == table) return;
List rows = table.getRows();
if (null == rows) return;
for (XWPFTableRow row : rows) {
List cells = row.getTableCells();
if (null == cells) continue;
for (XWPFTableCell cell : cells) {
visitParagraphs(cell.getParagraphs());
visitTables(cell.getTables());
}
}
}
void visitParagraph(XWPFParagraph paragraph) {
if (null == paragraph) return;
RunningRunParagraph runningRun = new RunningRunParagraph(paragraph, templatePattern);
List refactorRun = runningRun.refactorRun();
if (null == refactorRun) return;
for (XWPFRun run : refactorRun) {
visitRun(run);
}
}
void visitRun(XWPFRun run) {
String text = null;
if (null == run || StringUtils.isBlank(text = run.getText(0))) return;
ElementTemplate elementTemplate = parseTemplateFactory(text, run);
if (null != elementTemplate) eleTemplates.add(elementTemplate);
}
private ElementTemplate parseTemplateFactory(String text, T obj) {
logger.debug("Resolve where text: {}, and create ElementTemplate", text);
// temp ,future need to word analyze
if (templatePattern.matcher(text).matches()) {
String tag = gramerPattern.matcher(text).replaceAll("").trim();
if (obj.getClass() == XWPFRun.class) {
return TemplateFactory.createRunTemplate(tag, config, (XWPFRun) obj);
} else if (obj.getClass() == XWPFTableCell.class)
// return CellTemplate.create(symbol, tagName, (XWPFTableCell)
// obj);
return null;
}
return null;
}
private void initPattern() {
String signRegex = getGramarRegex(config);
String prefixRegex = RegexUtils.escapeExprSpecialWord(config.getGramerPrefix());
String suffixRegex = RegexUtils.escapeExprSpecialWord(config.getGramerSuffix());
templatePattern = Pattern.compile(MessageFormat.format(FORMAT_TEMPLATE, prefixRegex,
signRegex, config.getGrammerRegex(), suffixRegex));
gramerPattern = Pattern
.compile(MessageFormat.format(FORMAT_GRAMER, prefixRegex, suffixRegex));
}
private String getGramarRegex(Configure config) {
List gramerChar = new ArrayList(config.getGramerChars());
StringBuilder reg = new StringBuilder("(");
for (int i = 0;; i++) {
Character chara = gramerChar.get(i);
String escapeExprSpecialWord = RegexUtils.escapeExprSpecialWord(chara.toString());
if (i == gramerChar.size() - 1) {
reg.append(escapeExprSpecialWord).append(")?");
break;
} else reg.append(escapeExprSpecialWord).append("|");
}
return reg.toString();
}
}
RenderPolicy是渲染策略扩展点,Render模块提供了RenderDataCompute表达式计算扩展点,通过RenderPolicy对每个标签进行渲染。
Poi-tl提供详细的示例代码讲解和官方文档.
点击下方卡片/微信搜索,关注公众号“天宇文创意乐派”(ID:gh_cc865e4c536b)
回复“poi-tl”获取仓库地址和官方文档
往期推荐
[
Win10系统电脑怎么关闭右下方任务栏的广告新闻
](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487189&idx=1&sn=5fa56554c6cd3ba3018729ab076db49a&chksm=ebb6edb6dcc164a0eb8ba38bb388c78694f46e5c57c69a28a52c17e1b50744548dcc28f7f0d2&scene=21#wechat_redirect)
[
官方出手!百度网盘有望免费提速了!
](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487189&idx=2&sn=93eb8e7458319c118e525716cdf775e7&chksm=ebb6edb6dcc164a00914102750e3694fe630b10bbed1b2f7c12d8ef82314e29686be5425ae9c&scene=21#wechat_redirect)
[
Linux常用命令!
](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487189&idx=3&sn=13b305957439725a2b18c28cd5148c5e&chksm=ebb6edb6dcc164a0a1dd3f05a744137653af64cbf9f5ed2b6668f190f156922ed8bc678ebab3&scene=21#wechat_redirect)
[
EA财报:有史以来最好的Q2《2042》备受玩家欢迎
](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487189&idx=4&sn=45380c3ad82afa2649f80b17d8b39883&chksm=ebb6edb6dcc164a0e6d17e2942c992f20098f73fe988f9a1696c81d226ef1a53f900b5e552f5&scene=21#wechat_redirect)
[
GitHub AI 编程工具 Copilot 已支持 IntelliJ IDEA、PyCharm
](http://mp.weixin.qq.com/s?__biz=MzI4MDQ5MTUzMg==&mid=2247487088&idx=1&sn=2ed7921517ac3ee058e0a2cd23650249&chksm=ebb6ed13dcc164057b5e780d9d6187c9ce423c31ac6088762a238f0f216a7c91a4ebf11bccba&scene=21#wechat_redirect)