001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools.template_engine; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.Collection; 009import java.util.List; 010 011import org.openstreetmap.josm.data.osm.search.SearchCompiler; 012import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 013import org.openstreetmap.josm.data.osm.search.SearchParseError; 014import org.openstreetmap.josm.tools.template_engine.Tokenizer.Token; 015import org.openstreetmap.josm.tools.template_engine.Tokenizer.TokenType; 016 017/** 018 * Template parser. 019 */ 020public class TemplateParser { 021 private final Tokenizer tokenizer; 022 023 private static final Collection<TokenType> EXPRESSION_END_TOKENS = Arrays.asList(TokenType.EOF); 024 private static final Collection<TokenType> CONDITION_WITH_APOSTROPHES_END_TOKENS = Arrays.asList(TokenType.APOSTROPHE); 025 026 /** 027 * Constructs a new {@code TemplateParser}. 028 * @param template template string to parse 029 */ 030 public TemplateParser(String template) { 031 this.tokenizer = new Tokenizer(template); 032 } 033 034 private Token check(TokenType expectedToken) throws ParseError { 035 Token token = tokenizer.nextToken(); 036 if (token.getType() != expectedToken) 037 throw new ParseError(token, expectedToken); 038 else 039 return token; 040 } 041 042 /** 043 * Parse the template. 044 * @return the resulting template entry 045 * @throws ParseError if the template cannot be parsed 046 */ 047 public TemplateEntry parse() throws ParseError { 048 return parseExpression(EXPRESSION_END_TOKENS); 049 } 050 051 private TemplateEntry parseExpression(Collection<TokenType> endTokens) throws ParseError { 052 List<TemplateEntry> entries = new ArrayList<>(); 053 while (true) { 054 TemplateEntry templateEntry; 055 Token token = tokenizer.lookAhead(); 056 if (token.getType() == TokenType.CONDITION_START) { 057 templateEntry = parseCondition(); 058 } else if (token.getType() == TokenType.CONTEXT_SWITCH_START) { 059 templateEntry = parseContextSwitch(); 060 } else if (token.getType() == TokenType.VARIABLE_START) { 061 templateEntry = parseVariable(); 062 } else if (endTokens.contains(token.getType())) 063 return CompoundTemplateEntry.fromArray(entries.toArray(new TemplateEntry[0])); 064 else if (token.getType() == TokenType.TEXT) { 065 tokenizer.nextToken(); 066 templateEntry = new StaticText(token.getText()); 067 } else 068 throw new ParseError(token); 069 entries.add(templateEntry); 070 } 071 } 072 073 private TemplateEntry parseVariable() throws ParseError { 074 check(TokenType.VARIABLE_START); 075 String variableName = check(TokenType.TEXT).getText(); 076 check(TokenType.END); 077 078 return new Variable(variableName); 079 } 080 081 private void skipWhitespace() throws ParseError { 082 Token token = tokenizer.lookAhead(); 083 if (token.getType() == TokenType.TEXT && token.getText().trim().isEmpty()) { 084 tokenizer.nextToken(); 085 } 086 } 087 088 private TemplateEntry parseCondition() throws ParseError { 089 check(TokenType.CONDITION_START); 090 Collection<TemplateEntry> conditionEntries = new ArrayList<>(); 091 while (true) { 092 093 TemplateEntry condition; 094 Token searchExpression = tokenizer.skip('\''); 095 check(TokenType.APOSTROPHE); 096 condition = parseExpression(CONDITION_WITH_APOSTROPHES_END_TOKENS); 097 check(TokenType.APOSTROPHE); 098 String searchText = searchExpression.getText().trim(); 099 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}