是否可以 "pre-process" Freemarker 模板?

Is it possible to "pre-process" Freemarker templates?

考虑以下模板:

<#include "../header.txt"/>
<#list items as item>
Item name is: ${item.name}<br/>
</#list>

其中 header.txt 包含:

<html>
<head>
</head>
<body>

我想"pre-process"这个模板,这样得到的输出是:

<html>
<head>
</head>
<body>
<#list items as item>
Item name is: ${item.name}<br/>
</#list>

我希望能够扩展包含但解析变量。我如何使用 Freemarker 做到这一点?

FreeMarker 不支持这样做(只解析模板的某些部分)。您可以做的是使用您自己的解析器预处理模板。这通过使用您自己的 TemplateLoader 实现来支持,该实现委托给另一个 TemplateLoader(原始实现)并过滤内容。因此,您可以在第一次需要模板时即时应用您的转换,结果将被缓存(在 FreeMarker 的标准模板缓存中)。我建议使用您自己的语法(如 <%include '...'>),这样每个人都会看到那里正在发生一些特别的事情。

我最终不得不创建自己的扩展器。我希望它对某人有用:

/**
 * Utility class to perform Freemarker template expansion.
 * 
 * @author Chris Mepham
 */
public class FreemarkerTemplateExpander implements ApplicationAware {

    static final String INCLUDE_REGEX = "<#include \\"\S+\\"\/>";

    static final String PATH_REGEX = "\\"\S+\\"";

    private ModuleStateHolder moduleStateHolder;

    /**
     * Takes the Freemarker template String input and
     * 
     * recursively expands all of the includes.
     * 
     * @param input The String value of a Freemarker template.
     * 
     * @return The expanded version of the Freemarker template.
     */
    public final String expand(String module, String path, String input) {

        Assert.notNull(module, "module cannot be null");
        Assert.notNull(path, "path cannot be null");
        Assert.notNull(input, "input cannot be null");

        if (!hasText(input)) return input;

        // See if there is an include
        int indexOfNextInclude = getIndexOfNextInclude(Pattern.compile(INCLUDE_REGEX), input);

        // If there is no include just return the input text
        if (indexOfNextInclude == -1) return input;

        StringBuffer buffer = new StringBuffer();

        // Otherwise, get all the text up to the next include and add it to buffer
        String prefix = input.substring(0, indexOfNextInclude);
        if (hasText(prefix)) buffer.append(prefix);

        // Then get the contents of the include as a String
        String includeContents = getIncludeContents(module, path, input);
        if (hasText(includeContents)) buffer.append(includeContents);

        // Then get all the text after the next include
        int includeLastCharacterIndex = indexOfNextInclude + matchRegexPattern(input, INCLUDE_REGEX).length();

        String suffix = input.substring(includeLastCharacterIndex + 1);

        buffer.append(suffix);

        input = buffer.toString();

        return expand(module, path, input);
    }

    final String getIncludeContents(String module, String path1, String input) {

        // Get next include file relative path
        String nextIncludePath = getNextIncludePath(input);
        String resourcePath = getResourcePath(nextIncludePath);

        // Get file name
        String filename = getFilename(nextIncludePath);

        // Get file contents here
        String path = "templates." + resourcePath;
        InputStream resource = ClasspathResourceUtils.getClassPathResource(path, filename, getClassLoader(module));

        StringWriter writer = new StringWriter();
        try {
            IOUtils.copy(resource, writer, "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }

        return writer.toString();
    }

    static final int getIndexOfNextInclude(Pattern pattern, String input) {

        Matcher matcher = pattern.matcher(input);

        return matcher.find() ? matcher.start() : -1;
    }

    private static final String getNextIncludePath(String input) {

        String include = matchRegexPattern(input, INCLUDE_REGEX);

        if (include == null) return null;

        String path = matchRegexPattern(include, PATH_REGEX);
        path = path.replace("\"", "");

        return path;
    }

    private static final String matchRegexPattern(String input, String regex) {

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);

        while(matcher.find()) {

            return matcher.group(0);
        }

        return null;
    }

    private String getResourcePath(String path) {

        if (!path.contains("/")) return path;

        String resourcePath = path.substring(path.indexOf("/") + 1, path.lastIndexOf("/"));

        return resourcePath;

    }

    private String getFilename(String path) {

        if (!path.contains("/")) return path;

        return path.substring(path.lastIndexOf("/") + 1);
    }
}