001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 */
020
021package org.nuxeo.template.fm;
022
023import java.util.ArrayList;
024import java.util.List;
025import java.util.ListIterator;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029import org.nuxeo.runtime.api.Framework;
030import org.nuxeo.template.api.TemplateInput;
031import org.nuxeo.template.api.TemplateProcessorService;
032
033/**
034 * Helper class used to extract variable names from a FreeMarker template. This is used to initialize the
035 * {@link TemplateInput} parameters. Extraction is for now simple and system may not detect all the cases, but user is
036 * able to add parameters from the UI.
037 *
038 * @author Tiry (tdelprat@nuxeo.com)
039 */
040public class FreeMarkerVariableExtractor {
041
042    protected final static Pattern simpleVariableMatcher = Pattern.compile("\\$\\{([^\\}]*)\\}");
043
044    protected final static String[] spliters = new String[] { ".", "?", "=", ">", "<", "!", " ", "[" };
045
046    protected final static Pattern[] directiveMatchers = new Pattern[] { Pattern.compile("\\[\\#if\\s([^\\]]*)\\]"),
047            Pattern.compile("\\[\\#list\\s[\\d\\.\\.]*(.+)\\sas\\s([^\\]]*)\\]") };
048
049    protected final static Pattern[] assignMatchers = new Pattern[] { Pattern.compile("\\[\\#assign\\s(.+)=.*\\]") };
050
051    protected static final List<String> reservedContextKeywords = new ArrayList<>();
052
053    protected static final String[] freeMarkerVariableSuffix = { "_index", "_has_next" };
054
055    protected static String extractVariableName(String match) {
056
057        String varName = match.trim();
058
059        if (varName.startsWith("!")) {
060            varName = varName.substring(1);
061        }
062
063        while (varName.startsWith("(")) {
064            varName = varName.substring(1);
065        }
066
067        int idx = varName.indexOf(".");
068        if (idx > 1) {
069            varName = varName.substring(0, idx);
070        }
071
072        for (String spliter : spliters) {
073            idx = varName.indexOf(spliter);
074            if (idx > 1) {
075                varName = varName.substring(0, idx);
076            }
077        }
078        return varName;
079    }
080
081    public static void resetReservedContextKeywords() {
082        synchronized (reservedContextKeywords) {
083            reservedContextKeywords.clear();
084        }
085    }
086
087    protected static List<String> getreservedContextKeywords() {
088        synchronized (reservedContextKeywords) {
089            if (reservedContextKeywords.size() == 0) {
090                TemplateProcessorService tps = Framework.getService(TemplateProcessorService.class);
091                if (tps != null) {
092                    reservedContextKeywords.addAll(tps.getReservedContextKeywords());
093                }
094            }
095        }
096        return reservedContextKeywords;
097    }
098
099    public static List<String> extractVariables(String content) {
100
101        List<String> variables = new ArrayList<>();
102
103        List<String> blackListedVariables = new ArrayList<>();
104
105        if (content.length() > 10000) {
106            // split content in multilines
107            // otherwise some regexp won't capture everything
108            content = content.replaceAll("</", "\n</");
109        }
110
111        Matcher matcher = simpleVariableMatcher.matcher(content);
112
113        matcher.matches();
114        while (matcher.find()) {
115            if (matcher.groupCount() > 0) {
116                String v = extractVariableName(matcher.group(1));
117                if (!variables.contains(v)) {
118                    variables.add(v);
119                }
120            }
121        }
122
123        for (Pattern dPattern : directiveMatchers) {
124            Matcher dmatcher = dPattern.matcher(content);
125            dmatcher.matches();
126            while (dmatcher.find()) {
127                if (dmatcher.groupCount() > 0) {
128                    String v = extractVariableName(dmatcher.group(1));
129                    if (!variables.contains(v)) {
130                        variables.add(v);
131                    }
132                    if (dmatcher.groupCount() > 1) {
133                        String localVariable = extractVariableName(dmatcher.group(2));
134                        blackListedVariables.add(localVariable);
135                        for (String suffix : freeMarkerVariableSuffix) {
136                            blackListedVariables.add(localVariable + suffix);
137                        }
138                    }
139                }
140            }
141        }
142
143        for (Pattern dPattern : assignMatchers) {
144            Matcher dmatcher = dPattern.matcher(content);
145            dmatcher.matches();
146            while (dmatcher.find()) {
147                if (dmatcher.groupCount() > 0) {
148                    String v = extractVariableName(dmatcher.group(1));
149                    blackListedVariables.add(extractVariableName(v));
150                }
151            }
152        }
153
154        // remove internal variables
155        for (String bVar : blackListedVariables) {
156            variables.remove(bVar);
157        }
158
159        // remove reserved variables that don't need specific bindings
160        for (String bVar : getreservedContextKeywords()) {
161            variables.remove(bVar);
162        }
163
164        // remove any non valid variable names
165        ListIterator<String> varIter = variables.listIterator();
166        while (varIter.hasNext()) {
167            String var = varIter.next();
168            if (var.contains("<") || var.contains(">")) {
169                varIter.remove();
170            } else if (var.contains("\n")) {
171                varIter.set(var.replaceAll("\n", "").trim());
172            } else if (var.startsWith(".")) {
173                // remove FM "Special Variables"
174                varIter.remove();
175            }
176        }
177
178        return variables;
179    }
180
181}