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