如何处理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
相关产品推荐
相关产品推荐

