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

如何获取当前Freemarker模板中使用的哈希与标量列表?

提取Freemarker模板中的哈希与标量变量

要提取Freemarker模板中引用的所有哈希(hashes)和标量(scalars),无需执行模板即可完成——你需要解析模板的抽象语法树(AST),因为Freemarker没有提供直接的API来获取这类静态元数据。你之前尝试的createProcessingEnvironment方法依赖模板执行,且需要传入有效数据模型,因此无法满足提前提取变量的需求。

实现方案:解析AST提取变量

通过Template.getRootTreeNode()获取模板的AST根节点,递归遍历所有节点,识别并收集变量引用。以下是完整的实现代码:

1. 变量提取工具类

import freemarker.core.*;
import freemarker.template.TemplateNode;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class TemplateVariableExtractor {
    private final Set<String> scalarVariables = new HashSet<>();
    private final Set<String> hashVariables = new HashSet<>();

    public void extract(TemplateNode rootNode) {
        traverseNode(rootNode);
    }

    private void traverseNode(TemplateNode node) {
        // 处理文本插值中的变量(如${username}、${user.name})
        if (node instanceof TextBlock) {
            TextBlock textBlock = (TextBlock) node;
            for (TemplateElement elem : textBlock.getElements()) {
                if (elem instanceof Interpolation) {
                    processExpression(((Interpolation) elem).getExpression());
                }
            }
        }

        // 处理指令参数中的变量(如#if(${user.isActive})、#foreach(item in items))
        if (node instanceof DirectiveCallPlace) {
            DirectiveCallPlace directive = (DirectiveCallPlace) node;
            Map<String, Expression> params = directive.getParameters();
            
            // 遍历指令参数表达式
            for (Expression exp : params.values()) {
                processExpression(exp);
            }
            
            // 收集foreach循环变量
            if ("foreach".equals(directive.getDirectiveName())) {
                Expression itemExp = params.get("item");
                if (itemExp instanceof Identifier) {
                    scalarVariables.add(((Identifier) itemExp).getName());
                }
            }
        }

        // 递归处理子节点
        List<TemplateNode> children = node.getChildNodes();
        for (TemplateNode child : children) {
            traverseNode(child);
        }
    }

    private void processExpression(Expression exp) {
        if (exp instanceof Identifier) {
            // 标量变量(如${username})
            scalarVariables.add(((Identifier) exp).getName());
        } else if (exp instanceof Dot) {
            // 哈希的点访问(如${user.name})
            Dot dotExp = (Dot) exp;
            Expression targetExp = dotExp.getTarget();
            String propertyName = dotExp.getPropertyName();
            
            // 递归处理目标表达式(如user.name中的user)
            processExpression(targetExp);
            
            // 记录父哈希
            if (targetExp instanceof Identifier) {
                hashVariables.add(((Identifier) targetExp).getName());
            }
            
            // 记录完整路径的标量(如user.name)
            scalarVariables.add(getExpressionPath(targetExp) + "." + propertyName);
        } else if (exp instanceof SquareBracket) {
            // 哈希的方括号访问(如${user["name"]})
            SquareBracket bracketExp = (SquareBracket) exp;
            Expression targetExp = bracketExp.getTarget();
            Expression indexExp = bracketExp.getIndex();
            
            processExpression(targetExp);
            
            if (targetExp instanceof Identifier) {
                hashVariables.add(((Identifier) targetExp).getName());
            }
            
            // 若索引为字符串常量,记录完整路径
            if (indexExp instanceof StringLiteral) {
                String index = ((StringLiteral) indexExp).getAsString();
                scalarVariables.add(getExpressionPath(targetExp) + "." + index);
            }
        }
        // 可扩展处理其他表达式类型:算术表达式、三元表达式等
    }

    private String getExpressionPath(Expression exp) {
        if (exp instanceof Identifier) {
            return ((Identifier) exp).getName();
        } else if (exp instanceof Dot) {
            Dot dotExp = (Dot) exp;
            return getExpressionPath(dotExp.getTarget()) + "." + dotExp.getPropertyName();
        } else if (exp instanceof SquareBracket) {
            SquareBracket bracketExp = (SquareBracket) exp;
            Expression indexExp = bracketExp.getIndex();
            if (indexExp instanceof StringLiteral) {
                return getExpressionPath(bracketExp.getTarget()) + "." + ((StringLiteral) indexExp).getAsString();
            }
        }
        return exp.toString();
    }

    public Set<String> getScalarVariables() {
        return scalarVariables;
    }

    public Set<String> getHashVariables() {
        return hashVariables;
    }
}

2. 使用工具类提取变量

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import java.io.IOException;
import java.util.Set;

public class TemplateProcessor {
    public static void main(String[] args) throws IOException {
        Configuration cfg = new Configuration(new Version("2.3.32"));
        cfg.setClassForTemplateLoading(TemplateProcessor.class, "/");
        cfg.setDefaultEncoding("UTF-8");
        
        // 加载目标模板
        Template template = cfg.getTemplate("origin-keycloak/themes/base/account/totp.ftl");
        
        // 提取变量
        TemplateVariableExtractor extractor = new TemplateVariableExtractor();
        extractor.extract(template.getRootTreeNode());
        
        // 获取结果
        Set<String> scalars = extractor.getScalarVariables();
        Set<String> hashes = extractor.getHashVariables();
        
        // 输出示例
        System.out.println("提取的标量变量:");
        scalars.forEach(System.out::println);
        
        System.out.println("\n提取的哈希变量:");
        hashes.forEach(System.out::println);
    }
}

关键说明

  • 静态解析:无需执行模板或传入数据模型,直接基于模板AST分析,适合批量处理大量更新的模板。
  • 覆盖场景:支持文本插值、指令参数、foreach循环变量,以及哈希的点访问和方括号访问。
  • 可扩展性:可根据模板实际使用的表达式类型(如算术运算、三元表达式)扩展processExpression方法,覆盖更多变量引用场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 11:27:50