1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.tools.template_engine;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
5 |
|
---|
6 | import java.util.ArrayList;
|
---|
7 | import java.util.Arrays;
|
---|
8 | import java.util.Collection;
|
---|
9 | import java.util.List;
|
---|
10 |
|
---|
11 | import org.openstreetmap.josm.data.osm.search.SearchCompiler;
|
---|
12 | import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
|
---|
13 | import org.openstreetmap.josm.data.osm.search.SearchParseError;
|
---|
14 | import org.openstreetmap.josm.tools.template_engine.Tokenizer.Token;
|
---|
15 | import org.openstreetmap.josm.tools.template_engine.Tokenizer.TokenType;
|
---|
16 |
|
---|
17 | /**
|
---|
18 | * Template parser.
|
---|
19 | */
|
---|
20 | public class TemplateParser {
|
---|
21 | private final Tokenizer tokenizer;
|
---|
22 |
|
---|
23 | private static final Collection<TokenType> EXPRESSION_END_TOKENS = Arrays.asList(TokenType.EOF);
|
---|
24 | private static final Collection<TokenType> CONDITION_WITH_APOSTROPHES_END_TOKENS = Arrays.asList(TokenType.APOSTROPHE);
|
---|
25 |
|
---|
26 | /**
|
---|
27 | * Constructs a new {@code TemplateParser}.
|
---|
28 | * @param template template string to parse
|
---|
29 | */
|
---|
30 | public TemplateParser(String template) {
|
---|
31 | this.tokenizer = new Tokenizer(template);
|
---|
32 | }
|
---|
33 |
|
---|
34 | private Token check(TokenType expectedToken) throws ParseError {
|
---|
35 | Token token = tokenizer.nextToken();
|
---|
36 | if (token.getType() != expectedToken)
|
---|
37 | throw new ParseError(token, expectedToken);
|
---|
38 | else
|
---|
39 | return token;
|
---|
40 | }
|
---|
41 |
|
---|
42 | /**
|
---|
43 | * Parse the template.
|
---|
44 | * @return the resulting template entry
|
---|
45 | * @throws ParseError if the template cannot be parsed
|
---|
46 | */
|
---|
47 | public TemplateEntry parse() throws ParseError {
|
---|
48 | return parseExpression(EXPRESSION_END_TOKENS);
|
---|
49 | }
|
---|
50 |
|
---|
51 | private TemplateEntry parseExpression(Collection<TokenType> endTokens) throws ParseError {
|
---|
52 | List<TemplateEntry> entries = new ArrayList<>();
|
---|
53 | while (true) {
|
---|
54 | TemplateEntry templateEntry;
|
---|
55 | Token token = tokenizer.lookAhead();
|
---|
56 | if (token.getType() == TokenType.CONDITION_START) {
|
---|
57 | templateEntry = parseCondition();
|
---|
58 | } else if (token.getType() == TokenType.CONTEXT_SWITCH_START) {
|
---|
59 | templateEntry = parseContextSwitch();
|
---|
60 | } else if (token.getType() == TokenType.VARIABLE_START) {
|
---|
61 | templateEntry = parseVariable();
|
---|
62 | } else if (endTokens.contains(token.getType()))
|
---|
63 | return CompoundTemplateEntry.fromArray(entries.toArray(new TemplateEntry[0]));
|
---|
64 | else if (token.getType() == TokenType.TEXT) {
|
---|
65 | tokenizer.nextToken();
|
---|
66 | templateEntry = new StaticText(token.getText());
|
---|
67 | } else
|
---|
68 | throw new ParseError(token);
|
---|
69 | entries.add(templateEntry);
|
---|
70 | }
|
---|
71 | }
|
---|
72 |
|
---|
73 | private TemplateEntry parseVariable() throws ParseError {
|
---|
74 | check(TokenType.VARIABLE_START);
|
---|
75 | String variableName = check(TokenType.TEXT).getText();
|
---|
76 | check(TokenType.END);
|
---|
77 |
|
---|
78 | return new Variable(variableName);
|
---|
79 | }
|
---|
80 |
|
---|
81 | private void skipWhitespace() throws ParseError {
|
---|
82 | Token token = tokenizer.lookAhead();
|
---|
83 | if (token.getType() == TokenType.TEXT && token.getText().trim().isEmpty()) {
|
---|
84 | tokenizer.nextToken();
|
---|
85 | }
|
---|
86 | }
|
---|
87 |
|
---|
88 | private TemplateEntry parseCondition() throws ParseError {
|
---|
89 | check(TokenType.CONDITION_START);
|
---|
90 | Collection<TemplateEntry> conditionEntries = new ArrayList<>();
|
---|
91 | while (true) {
|
---|
92 |
|
---|
93 | TemplateEntry condition;
|
---|
94 | Token searchExpression = tokenizer.skip('\'');
|
---|
95 | check(TokenType.APOSTROPHE);
|
---|
96 | condition = parseExpression(CONDITION_WITH_APOSTROPHES_END_TOKENS);
|
---|
97 | check(TokenType.APOSTROPHE);
|
---|
98 | String searchText = searchExpression.getText().trim();
|
---|
99 | if (searchText.isEmpty()) {
|
---|
100 | conditionEntries.add(condition);
|
---|
101 | } else {
|
---|
102 | try {
|
---|
103 | conditionEntries.add(new SearchExpressionCondition(
|
---|
104 | SearchCompiler.compile(searchText), condition));
|
---|
105 | } catch (SearchParseError e) {
|
---|
106 | throw new ParseError(searchExpression.getPosition(), e);
|
---|
107 | }
|
---|
108 | }
|
---|
109 | skipWhitespace();
|
---|
110 |
|
---|
111 | Token token = tokenizer.lookAhead();
|
---|
112 | if (token.getType() == TokenType.END) {
|
---|
113 | tokenizer.nextToken();
|
---|
114 | return new Condition(conditionEntries);
|
---|
115 | } else {
|
---|
116 | check(TokenType.PIPE);
|
---|
117 | }
|
---|
118 | }
|
---|
119 | }
|
---|
120 |
|
---|
121 | private TemplateEntry parseContextSwitch() throws ParseError {
|
---|
122 |
|
---|
123 | check(TokenType.CONTEXT_SWITCH_START);
|
---|
124 | Token searchExpression = tokenizer.skip('\'');
|
---|
125 | check(TokenType.APOSTROPHE);
|
---|
126 | TemplateEntry template = parseExpression(CONDITION_WITH_APOSTROPHES_END_TOKENS);
|
---|
127 | check(TokenType.APOSTROPHE);
|
---|
128 | ContextSwitchTemplate result;
|
---|
129 | String searchText = searchExpression.getText().trim();
|
---|
130 | if (searchText.isEmpty())
|
---|
131 | throw new ParseError(tr("Expected search expression"));
|
---|
132 | else {
|
---|
133 | try {
|
---|
134 | Match match = SearchCompiler.compile(searchText);
|
---|
135 | result = new ContextSwitchTemplate(match, template, searchExpression.getPosition());
|
---|
136 | } catch (SearchParseError e) {
|
---|
137 | throw new ParseError(searchExpression.getPosition(), e);
|
---|
138 | }
|
---|
139 | }
|
---|
140 | skipWhitespace();
|
---|
141 | check(TokenType.END);
|
---|
142 | return result;
|
---|
143 | }
|
---|
144 | }
|
---|