[模板引擎/文本渲染引擎] Jinjia2重要特性的使用指南
1 使用指南
CASE 渲染工具类
引入依赖
<!-- 内部含 guava:com.google.guava 等依赖
<dependency>
<groupId>com.hubspot.jinjava</groupId>
<artifactId>jinjava</artifactId>
<version>2.5.6</version>
</dependency>
JinjaTemplateUtils
import com.hubspot.jinjava.Jinjava;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 基于 Jinja 的模板渲染工具类
* https://jinja.palletsprojects.com/en/3.1.x/ [推荐]
* http://docs.jinkan.org/docs/jinja2/templates.html [推荐]
* http://docs.jinkan.org/docs/jinja2/templates.html#for [推荐]
* https://product.hubspot.com/blog/jinjava-a-jinja-for-your-java
* https://github.com/HubSpot/jinjava/blob/master/benchmark/resources/jinja/simple.jinja
* https://blog.csdn.net/a232884c/article/details/121982059
* https://wenku.baidu.com/view/152c14280440be1e650e52ea551810a6f524c806.html
*/
@Slf4j
public class JinjaTemplateUtils {
/**
* 异常标志
* @description 在通过本工具解析完成内容模板后,可通过判定结果文本中存在此异常标志
*/
private final static String EXCEPTION_FLAG = "THROW_RUNETIME_EXCEPTION";
/**
*
* @param template
* @param properties
* @return
*/
public static String render(String template, Properties properties) {
return render(template, properties);
}
/**
*
* @param template
* @param properties
* @return
*/
public static String render(String template, Map properties) {
return render(template, properties, false);
}
public static String render(String template, Map properties, boolean isLog) {
Jinjava jinjava = new Jinjava();
if(isLog){
log.debug("template: {}", template);
}
String renderedResultText = jinjava.render(template, properties);
if(isLog){
log.debug("renderedResultText: {}", renderedResultText);
}
checkExceptionInRenderedResultText(renderedResultText, template, properties);
return renderedResultText;
}
/**
* 检查渲染后的结果文本的异常情况
* @param renderedResultText 检查对象 := 渲染后的结果文本
* @param template 渲染前的模板文本
* @param properties 变量属性集合
*/
private static final void checkExceptionInRenderedResultText(String renderedResultText, String template, Map properties){
if(renderedResultText.contains(EXCEPTION_FLAG)){
//String errorMessageTemplate = "errorCode: %s,\n errorName: %s,\n errorCause: %s,\n template: %s,\n properties: %s";
throw new BusinessException(
"JINJA_TEMPLATE_ENGINE_ERROR",
"jinja template engine error!renderedResultText:" + renderedResultText + "template :" + template
);
}
}
}
CASE 为变量设置默认值
private final static Jinjava JINJAVA = new Jinjava();
/** 为变量设置默认值 | 共计 3 种方法 **/
@Test
public void defaultValueTest(){
//{{ variable|default(default_value) }}
// my_dict是一个字典,其中只有一个键值对。在模板中访问 my_dict['key2'] 时,由于key2不存在,所以会使用默认值 defaultValue3 来代替。
String template = "{% set my_dict = {'key1': 'value1'} %}" + "【{{ my_dict['key2']|default('defaultValue3') }}】";//defaultValue3
//String template = "{{testVal | default('defaultValue2') }}";//defaultValue2
//String template = "{{testVal or 'defaultValue'}}";//defaultValue
Map configMap = new HashMap();
String result = JINJAVA.render(template, configMap);
System.out.println("result: " + result);
}
CASE (数字、字符串)变量的判空
方式1:if variable
/**
* 测试 判断变量(数字、字符串)是否为空
*/
@Test
public void emptyTest01(){
String template = "[{% if testType %} test_type = '{{testType}}' {% endif %}]";
Map<String, Object> map = null;
map = new HashMap<>();
String renderResult1 = JinjaTemplateUtils.render(template, map);
log.info("[1] {}", renderResult1);//[1] []
map = new HashMap<>();
map.put("testType", null);
String renderResult2 = JinjaTemplateUtils.render(template, map);
log.info("[2] {}", renderResult2);//[2] []
map = new HashMap<>();
map.put("testType", 0);
String renderResult3 = JinjaTemplateUtils.render(template, map);
log.info("[3] {}", renderResult3);//[3] []
map = new HashMap<>();
map.put("testType", "0");
String renderResult4 = JinjaTemplateUtils.render(template, map);
log.info("[4] {}", renderResult4);//[4] [ test_type = '0' ]
map = new HashMap<>();
map.put("testType", 1);
String renderResult5 = JinjaTemplateUtils.render(template, map);
log.info("[5] {}", renderResult5);//[5] [ test_type = '1' ]
map = new HashMap<>();
map.put("testType", "1");
String renderResult6 = JinjaTemplateUtils.render(template, map);
log.info("[6] {}", renderResult6);//[6] [ test_type = '1' ]
assertTrue(true);
}
方式2:if variable is defined
/**
* 测试 判断变量(数字、字符串)是否为空
* @reference-doc
* [1] Jinja2 内置测试清单 - http://docs.jinkan.org/docs/jinja2/templates.html#id30
*/
@Test
public void emptyTest02(){
//String template = "[ {{ string(testType) }}]";// ERROR
String template = "[ {% if testType is defined %} test_type = '{{testType}}' {% endif %}]";// ERROR
Map<String, Object> map = null;
map = new HashMap<>();
map.put("testType", null);
String renderResult1 = JinjaTemplateUtils.render(template, map);
log.info("[1] {}", renderResult1);//[1] []
map = new HashMap<>();
map.put("testType", 0);
String renderResult2 = JinjaTemplateUtils.render(template, map);
log.info("[2] {}", renderResult2);//[ test_type = '0' ]
map = new HashMap<>();
map.put("testType", "0");
String renderResult3 = JinjaTemplateUtils.render(template, map);
log.info("[3] {}", renderResult3);//[ test_type = '0' ]
Assert.assertTrue(true);
}
CASE (数组、列表)变量的判空
/**
* 测试 判断变量(数组/列表)是否为空
* @note 结论 : list 变量的判空 : {% if listVar is defined and (listVar.size() > 0) %}
* @reference-doc
* [1] Jinja2 内置测试清单 - http://docs.jinkan.org/docs/jinja2/templates.html#id30
* [2] {@link List } / {@link com.hubspot.jinjava.objects.collections.PyList }
*/
@Test
public void emptyTest03(){
String template = "[ {% if users is defined and users.length() > 0 %} list variables is not empty! {% endif %}]";// 针对 null , String 变量
String template2 = "[ {% if users is defined and (!users.isEmpty()) and (users.size() > 0) %} list variables is not empty! {% endif %}]";// 针对 null , List 变量
Map<String, Object> map = null;
map = new HashMap<>();
map.put("users", null);
String renderResult1 = JinjaTemplateUtils.render(template, map);
log.info("[1] {}", renderResult1);//[1] []
map.put("users", "");
String renderResult2 = JinjaTemplateUtils.render(template, map);
log.info("[2] {}", renderResult2);//[2] []
String [] usersArray = new String [] { "jack" , "jane" };
//map.put("users", usersArray);
//String renderResult3 = JinjaTemplateUtils.render(template, map);
//log.info("[3] {}", renderResult3);//模板中调用 `users.length() > 0`, 将报错 : com.hubspot.jinjava.interpret.FatalTemplateErrorsException: Cannot find method length with 0 parameters in class [Ljava.lang.String;
List<String> usersList = Arrays.asList(usersArray);
map.put("users", usersList);
String renderResult4 = JinjaTemplateUtils.render(template2, map);
log.info("[4] {}", renderResult4);//[4] [ list variables is not empty! ]
//模板中调用 `users.length() > 0`, 将报错 : "com.hubspot.jinjava.interpret.FatalTemplateErrorsException: Cannot find method length with 0 parameters in class com.hubspot.jinjava.objects.collections.PyList"
}
CASE 字符串模式替换(以去除空格符为例)
/**
* 去除空格测试
* @description
* 需求背景: API查询-空格事件字段`network event`
*/
@Test
public void replaceEmptyCharTest(){
// 方法1 : 失败
//"select ... ,deviceId {% for i in eventFields %} ,{{i}}.replace(' ', '') as '{{i}}' {% endfor %} , ...";
// content: select ... ,deviceId ,network event.replace(' ', '') as 'network event' ,ADDR.replace(' ', '') as 'ADDR' , ...
// 方法2 : 利用 Java String.replace 方法 : 失败
//String template = "select ... ,deviceId {% for i in eventFields %} {% set field = i.replace(\" \", \"\") %} , {{field}} as `{{i}}` {% endfor %} , ...";
// content: select ... ,c , network(1个乱码的特殊符号)event as `network event` , ADDR as `ADDR` , ...
//方法3 : 利用 jinja 的 过滤器`|` 和 replace(s, old, new, count=None) API [√]
String template = "select ... ,deviceId {% for i in eventFields %} {% set field = i|replace(\" \", \"\") %} ,{{field}} as `{{i}}` {% endfor %} , ...";
//content: select ... ,deviceId , networkevent as `network event`, ADDR as `ADDR` , ...
Map<String, Object> params = new HashMap();
List<String> eventFields = new ArrayList<>();
eventFields.add("network event"); // 测验对象
eventFields.add("ADDR");
params.put("eventFields", eventFields);
String content = JinjaTempltaeUtils.render(template, params);
log.info("content: {}", content);// content : 5
Assert.assertTrue(true);
}
CASE and
语法
/**
* 测试 `and` 语法
* @referenced-doc
* [1] https://docs.jinkan.org/docs/jinja2/templates.html#if
*/
@Test
public void valueConditionMatchTest2(){
String template = "{% if testType is defined and testType2 is defined %}" +
" hello~ " +
"{% endif %}";
Map<String, Object> map = null;
map = new HashMap<>();
map.put("testType", "3");
map.put("testType2", "4");
String renderResult = JinjaTemplateUtils.render(template, map);
log.info("{}", renderResult);//and ( testType = '3' or length(test_type) = 0 or (test_type IS NULL) )
}
CASE ==
语法
// 字符串的 '==' 比较语法
@Test
public void stringEqualsTest(){
String template = "{% if contentType == '3' %}hello~{% endif %}";
Map<String, Object> map = null;
map = new HashMap<>();
map.put("contentType", '3');
String renderResult = JinjaTemplateUtils.render(template, map);
log.info("{}", renderResult);//hello~
}
CASE 用in
间接实现 eq
语法 与 集合元素存在性
/**
* 测试 值 `eq` 语法、变量值在集合内的存在性
* @referenced-doc
* [1] https://docs.jinkan.org/docs/jinja2/templates.html#if
*/
@Test
public void valueEqualsTest2(){
String template = "{% if timeZone in ('3', '4') %}" + //集合即 ('3', '4')
" hello~ " +
"{% endif %}";
Map<String, Object> map = null;
map = new HashMap<>();
map.put("timeZone", "3");
String renderResult = JinjaTemplateUtils.render(template, map);
log.info("[1] {}", renderResult);//[1] and ( timeZone = '3' or length(time_zone) = 0 or (time_zone IS NULL) )
}
CASE 集合元素存在性
基于
stringCollectionObject.contains(stringVar)
实现
/**
* 目标值是否在列表/集合中
*/
@Test
public void targetValueInCollectionTest(){
String template = "{% if testTypes.contains('31') %}hello~{% endif %}";
Map<String, Object> map = null;
map = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("31");
list.add("2");
if(list.contains("31")){
log.info("31 存在!");// 31 存在!
} else {
log.warn("31 不存在!");
}
map.put("testTypes", list);
String renderResult = JinjaTemplateUtils.render(template, map);
log.info("{}", renderResult);//hello~
}
CASE 多个变量值的条件比对(综合案例)
/**
* 值条件比对测试
* @reference-doc
* [1] If 表达式 - Jinja - http://docs.jinkan.org/docs/jinja2/templates.html#if
* [1] 内置测试清单 - Jinja - http://docs.jinkan.org/docs/jinja2/templates.html#tests
*/
@Test
public void valueConditionMatchTest(){
String template = "{% if testType is defined %}" +
" and ( testType = '{{testType}}' {% if testType in ['3', 3] %} or length(test_type) = 0 or (test_type IS NULL) {% endif %} )" +
"{% endif %}";
Map<String, Object> map = null;
map = new HashMap<>();
map.put("testType", "3");
String renderResult = JinjaTemplateUtils.render(template, map);
log.info("[1] {}", renderResult);//[1] and ( testType = '3' or length(test_type) = 0 or (test_type IS NULL) )
String template2 = "{% if testType in [3, '3'] %}" +
"hello~" +
"{% endif %}";
Map<String, Object> map2 = null;
map2 = new HashMap<>();
map2.put("testType", "3");
String renderResult2 = JinjaTemplateUtils.render(template2, map2);
log.info("[2] {}", renderResult2);//[1] and ( testType = '3' or length(test_type) = 0 or (test_type IS NULL) )
assertTrue(true);
}
CASE 变量未被定义的判别
/**
* 变量未被定义
*/
@Test
public void variableNotDefined(){
// 变量未被定义
String template = "{% if testSubType is not defined %}" +
" and ( testSubType = '{{testSubType}}' )" +
"{% endif %}";
Map<String, Object> map1 = null;
map1 = new HashMap<>();
map1.put("testSubType", "3");
String renderResult1 = JinjaTemplateUtils.render(template, map1);
log.info("[1] {}", renderResult1);//[1]
Map<String, Object> map2 = null;
map2 = new HashMap<>();
map2.put("testSubType", 3);
String renderResult2 = JinjaTemplateUtils.render(template, map2);
log.info("[2] {}", renderResult2);//[2]
Map<String, Object> map3 = null;
map3 = new HashMap<>();
map3.put("testSubType", null);
String renderResult3 = JinjaTemplateUtils.render(template, map3);
log.info("[3] {}", renderResult3);//[3] and ( testSubType = '3' )
Map<String, Object> map4 = null;
map4 = new HashMap<>();
//map4.put("testSubType", null);
String renderResult4 = JinjaTemplateUtils.render(template, map4);
log.info("[4] {}", renderResult4);//[4] and ( testSubType = '3' )
}
X 参考文献
- https://jinja.palletsprojects.com/en/3.1.x/ [推荐]
- http://docs.jinkan.org/docs/jinja2/templates.html [推荐]
- http://docs.jinkan.org/docs/jinja2/templates.html#for [推荐]
- https://product.hubspot.com/blog/jinjava-a-jinja-for-your-java
- https://github.com/HubSpot/jinjava/blob/master/benchmark/resources/jinja/simple.jinja
- https://blog.csdn.net/a232884c/article/details/121982059
- https://wenku.baidu.com/view/152c14280440be1e650e52ea551810a6f524c806.html
本文作者:
千千寰宇
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!