001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools.template_engine;
003
004
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collection;
010import java.util.List;
011
012import org.openstreetmap.josm.actions.search.SearchCompiler;
013import org.openstreetmap.josm.actions.search.SearchCompiler.Match;
014import org.openstreetmap.josm.tools.template_engine.Tokenizer.Token;
015import org.openstreetmap.josm.tools.template_engine.Tokenizer.TokenType;
016
017
018public class TemplateParser {
019    private final Tokenizer tokenizer;
020
021    private static final Collection<TokenType> EXPRESSION_END_TOKENS = Arrays.asList(TokenType.EOF);
022    private static final Collection<TokenType> CONDITION_WITH_APOSTROPHES_END_TOKENS = Arrays.asList(TokenType.APOSTROPHE);
023
024    public TemplateParser(String template) {
025        this.tokenizer = new Tokenizer(template);
026    }
027
028    private Token check(TokenType expectedToken) throws ParseError {
029        Token token = tokenizer.nextToken();
030        if (token.getType() != expectedToken)
031            throw new ParseError(token, expectedToken);
032        else
033            return token;
034    }
035
036    public TemplateEntry parse() throws ParseError {
037        return parseExpression(EXPRESSION_END_TOKENS);
038    }
039
040    private TemplateEntry parseExpression(Collection<TokenType> endTokens) throws ParseError {
041        List<TemplateEntry> entries = new ArrayList<>();
042        while (true) {
043            TemplateEntry templateEntry;
044            Token token = tokenizer.lookAhead();
045            if (token.getType() == TokenType.CONDITION_START) {
046                templateEntry = parseCondition();
047            } else if (token.getType() == TokenType.CONTEXT_SWITCH_START) {
048                templateEntry = parseContextSwitch();
049            } else if (token.getType() == TokenType.VARIABLE_START) {
050                templateEntry = parseVariable();
051            } else if (endTokens.contains(token.getType()))
052                return CompoundTemplateEntry.fromArray(entries.toArray(new TemplateEntry[entries.size()]));
053            else if (token.getType() == TokenType.TEXT) {
054                tokenizer.nextToken();
055                templateEntry = new StaticText(token.getText());
056            } else
057                throw new ParseError(token);
058            entries.add(templateEntry);
059        }
060    }
061
062    private TemplateEntry parseVariable() throws ParseError {
063        check(TokenType.VARIABLE_START);
064        String variableName = check(TokenType.TEXT).getText();
065        check(TokenType.END);
066
067        return new Variable(variableName);
068    }
069
070    private void skipWhitespace() throws ParseError {
071        Token token = tokenizer.lookAhead();
072        if (token.getType() == TokenType.TEXT && token.getText().trim().isEmpty()) {
073            tokenizer.nextToken();
074        }
075    }
076
077    private TemplateEntry parseCondition() throws ParseError {
078        check(TokenType.CONDITION_START);
079        Condition result = new Condition();
080        while (true) {
081
082            TemplateEntry condition;
083            Token searchExpression = tokenizer.skip('\'');
084            check(TokenType.APOSTROPHE);
085            condition = parseExpression(CONDITION_WITH_APOSTROPHES_END_TOKENS);
086            check(TokenType.APOSTROPHE);
087            if (searchExpression.getText().trim().isEmpty()) {
088                result.getEntries().add(condition);
089            } else {
090                try {
091                    result.getEntries().add(new SearchExpressionCondition(SearchCompiler.compile(searchExpression.getText(), false, false), condition));
092                } catch (org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
093                    throw new ParseError(searchExpression.getPosition(), e);
094                }
095            }
096            skipWhitespace();
097
098            Token token = tokenizer.lookAhead();
099            if (token.getType()  == TokenType.END) {
100                tokenizer.nextToken();
101                return result;
102            } else {
103                check(TokenType.PIPE);
104            }
105        }
106    }
107
108    private TemplateEntry parseContextSwitch() throws ParseError {
109
110        check(TokenType.CONTEXT_SWITCH_START);
111        Token searchExpression = tokenizer.skip('\'');
112        check(TokenType.APOSTROPHE);
113        TemplateEntry template = parseExpression(CONDITION_WITH_APOSTROPHES_END_TOKENS);
114        check(TokenType.APOSTROPHE);
115        ContextSwitchTemplate result;
116        if (searchExpression.getText().trim().isEmpty())
117            throw new ParseError(tr("Expected search expression"));
118        else {
119            try {
120                Match match = SearchCompiler.compile(searchExpression.getText(), false, false);
121                result = new ContextSwitchTemplate(match, template, searchExpression.getPosition());
122            } catch (org.openstreetmap.josm.actions.search.SearchCompiler.ParseError e) {
123                throw new ParseError(searchExpression.getPosition(), e);
124            }
125        }
126        skipWhitespace();
127        check(TokenType.END);
128        return result;
129    }
130
131}