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.Iterator;
025import java.util.List;
026import java.util.ListIterator;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029
030import org.nuxeo.runtime.api.Framework;
031import org.nuxeo.template.api.TemplateInput;
032import org.nuxeo.template.api.TemplateProcessorService;
033
034/**
035 * Helper class used to extract variable names from a FreeMarker template. This is used to initialize the
036 * {@link TemplateInput} parameters. Extraction is for now simple and system may not detect all the cases, but user is
037 * able to add parameters from the UI.
038 *
039 * @author Tiry (tdelprat@nuxeo.com)
040 */
041public class FreeMarkerVariableExtractor {
042
043    protected final static Pattern simpleVariableMatcher = Pattern.compile("\\$\\{([^\\}]*)\\}");
044
045    protected final static String[] spliters = new String[] { ".", "?", "=", ">", "<", "!", " ", "[" };
046
047    protected final static Pattern[] directiveMatchers = new Pattern[] { Pattern.compile("\\[\\#if\\s([^\\]]*)\\]"),
048            Pattern.compile("\\[\\#list\\s[\\d\\.\\.]*(.+)\\sas\\s([^\\]]*)\\]") };
049
050    protected final static Pattern[] assignMatchers = new Pattern[] { Pattern.compile("\\[\\#assign\\s(.+)=.*\\]") };
051
052    protected static final List<String> reservedContextKeywords = new ArrayList<String>();
053
054    protected static final String[] freeMarkerVariableSuffix = { "_index", "_has_next" };
055
056    protected static String extractVariableName(String match) {
057
058        String varName = match.trim();
059
060        if (varName.startsWith("!")) {
061            varName = varName.substring(1);
062        }
063
064        while (varName.startsWith("(")) {
065            varName = varName.substring(1);
066        }
067
068        int idx = varName.indexOf(".");
069        if (idx > 1) {
070            varName = varName.substring(0, idx);
071        }
072
073        for (String spliter : spliters) {
074            idx = varName.indexOf(spliter);
075            if (idx > 1) {
076                varName = varName.substring(0, idx);
077            }
078        }
079        return varName;
080    }
081
082    public static void resetReservedContextKeywords() {
083        synchronized (reservedContextKeywords) {
084            reservedContextKeywords.clear();
085        }
086    }
087
088    protected static List<String> getreservedContextKeywords() {
089        synchronized (reservedContextKeywords) {
090            if (reservedContextKeywords.size() == 0) {
091                TemplateProcessorService tps = Framework.getService(TemplateProcessorService.class);
092                if (tps != null) {
093                    reservedContextKeywords.addAll(tps.getReservedContextKeywords());
094                }
095            }
096        }
097        return reservedContextKeywords;
098    }
099
100    public static List<String> extractVariables(String content) {
101
102        List<String> variables = new ArrayList<String>();
103
104        List<String> blackListedVariables = new ArrayList<String>();
105
106        if (content.length() > 10000) {
107            // split content in multilines
108            // otherwise some regexp won't capture everything
109            content = content.replaceAll("</", "\n</");
110        }
111
112        Matcher matcher = simpleVariableMatcher.matcher(content);
113
114        matcher.matches();
115        while (matcher.find()) {
116            if (matcher.groupCount() > 0) {
117                String v = extractVariableName(matcher.group(1));
118                if (!variables.contains(v)) {
119                    variables.add(v);
120                }
121            }
122        }
123
124        for (Pattern dPattern : directiveMatchers) {
125            Matcher dmatcher = dPattern.matcher(content);
126            dmatcher.matches();
127            while (dmatcher.find()) {
128                if (dmatcher.groupCount() > 0) {
129                    String v = extractVariableName(dmatcher.group(1));
130                    if (!variables.contains(v)) {
131                        variables.add(v);
132                    }
133                    if (dmatcher.groupCount() > 1) {
134                        String localVariable = extractVariableName(dmatcher.group(2));
135                        blackListedVariables.add(localVariable);
136                        for (String suffix : freeMarkerVariableSuffix) {
137                            blackListedVariables.add(localVariable + suffix);
138                        }
139                    }
140                }
141            }
142        }
143
144        for (Pattern dPattern : assignMatchers) {
145            Matcher dmatcher = dPattern.matcher(content);
146            dmatcher.matches();
147            while (dmatcher.find()) {
148                if (dmatcher.groupCount() > 0) {
149                    String v = extractVariableName(dmatcher.group(1));
150                    blackListedVariables.add(extractVariableName(v));
151                }
152            }
153        }
154
155        // remove internal variables
156        for (String bVar : blackListedVariables) {
157            variables.remove(bVar);
158        }
159
160        // remove reserved variables that don't need specific bindings
161        for (String bVar : getreservedContextKeywords()) {
162            variables.remove(bVar);
163        }        
164
165        // remove any non valid variable names
166        ListIterator<String> varIter = variables.listIterator();
167        while (varIter.hasNext()) {
168            String var = varIter.next();
169            if (var.contains("<") || var.contains(">")) {
170                varIter.remove();
171            } else if (var.contains("\n")) {
172                varIter.set(var.replaceAll("\n", "").trim());
173            } else if (var.startsWith(".")) {
174                // remove FM "Special Variables"
175                varIter.remove();
176            }
177        }
178
179        return variables;
180    }
181
182}