[模板引擎/文本渲染引擎] 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 参考文献

posted @ 2024-10-24 15:35  千千寰宇  阅读(14)  评论(0编辑  收藏  举报