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