You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何处理Freemarker中用户输入的语法错误模板,避免ParseException?

解决方案

首先明确:TemplateExceptionHandler.IGNORE_HANDLER只处理运行时异常(比如变量不存在、表达式求值错误),而你遇到的ParseException是模板解析阶段的语法错误,这个阶段IGNORE_HANDLER根本不会生效,所以得从解析前预处理或者错误捕获修复入手。

下面是两个可行的方案:

方案一:模板预处理(推荐,简单高效)

用正则表达式提前扫描用户输入的模板,把明显不符合Freemarker变量语法的${...}块替换为空字符串。Freemarker合法的变量路径规则是:由字母、数字、下划线、点组成,且不能以点开头/结尾,不能有连续的点。

代码示例(Java)

import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class TemplateFixer {
    public static void main(String[] args) throws Exception {
        // 用户输入的带错误的模板
        String userTemplate = "<div>${message.}</div><p>${validVar}</p>${.invalidVar}<span>${user!name}</span>";
        
        // 预处理:替换所有语法错误的${}块为空字符串
        // 正则匹配规则:${开头,内容包含非法字符/以点开头/以点结尾,}结尾
        String processedTemplate = userTemplate.replaceAll(
            "\\$\\{(?:\\.|[^a-zA-Z0-9_.]+|.*\\.)\\}", 
            ""
        );

        // 初始化Freemarker配置
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
        cfg.setDefaultEncoding("UTF-8");
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);

        // 创建模板并渲染
        Template template = new Template("user-template", new StringReader(processedTemplate), cfg);
        Map<String, Object> dataModel = new HashMap<>();
        dataModel.put("validVar", "正常显示的内容");
        
        StringWriter writer = new StringWriter();
        template.process(dataModel, writer);
        
        System.out.println(writer.toString());
        // 输出结果:<div></div><p>正常显示的内容</p><span></span>
    }
}

方案二:捕获ParseException并动态修复模板

如果无法提前预判所有错误类型,可以捕获解析阶段的ParseException,根据异常提供的错误位置定位到错误的${}块,替换为空后重新解析,直到成功或达到重试上限。

代码示例(Java)

import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class DynamicTemplateFixer {
    public static void main(String[] args) throws Exception {
        String userTemplate = "<div>${message.}</div><p>${validVar}</p>${invalid!var}";
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
        cfg.setDefaultEncoding("UTF-8");
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
        
        String processedTemplate = userTemplate;
        boolean parseSuccess = false;
        int retryCount = 0;
        final int MAX_RETRIES = 3; // 防止无限循环

        Map<String, Object> dataModel = new HashMap<>();
        dataModel.put("validVar", "正常内容");

        while (!parseSuccess && retryCount < MAX_RETRIES) {
            try {
                Template template = new Template("dynamic", new StringReader(processedTemplate), cfg);
                StringWriter writer = new StringWriter();
                template.process(dataModel, writer);
                parseSuccess = true;
                System.out.println(writer.toString());
            } catch (ParseException e) {
                retryCount++;
                // 从异常中获取错误的行和列
                int errorLine = e.getLineNumber() - 1; // 转成数组索引
                int errorCol = e.getColumnNumber() - 1;
                
                String[] lines = processedTemplate.split("\n");
                if (errorLine >= 0 && errorLine < lines.length) {
                    String line = lines[errorLine];
                    // 往前找最近的${起始位置
                    int start = line.lastIndexOf("${", errorCol);
                    // 往后找对应的}结束位置
                    int end = line.indexOf("}", errorCol);
                    
                    if (start != -1 && end != -1) {
                        // 替换错误块为空字符串
                        lines[errorLine] = line.substring(0, start) + line.substring(end + 1);
                        processedTemplate = String.join("\n", lines);
                    } else {
                        // 无法定位错误块,终止重试
                        break;
                    }
                }
            } catch (Exception e) {
                // 处理其他异常
                e.printStackTrace();
                break;
            }
        }
    }
}

关键说明

  • 解析阶段的语法错误(比如${message.})属于Freemarker的编译期错误,IGNORE_HANDLER只负责运行时错误,所以之前的设置无效。
  • 方案一适合错误类型固定的场景,正则可以根据实际需求调整;方案二更灵活,但需要处理错误位置的定位逻辑,且要设置重试上限避免死循环。

内容的提问来源于stack exchange,提问作者vished2000

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 05:55:07