001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools.template_engine; 003 004import java.util.Arrays; 005import java.util.HashSet; 006import java.util.Set; 007 008public class Tokenizer { 009 010 public static class Token { 011 private final TokenType type; 012 private final int position; 013 private final String text; 014 015 public Token(TokenType type, int position) { 016 this(type, position, null); 017 } 018 019 public Token(TokenType type, int position, String text) { 020 this.type = type; 021 this.position = position; 022 this.text = text; 023 } 024 025 public TokenType getType() { 026 return type; 027 } 028 029 public int getPosition() { 030 return position; 031 } 032 033 public String getText() { 034 return text; 035 } 036 037 @Override 038 public String toString() { 039 return type + (text != null ? ' ' + text : ""); 040 } 041 } 042 043 public enum TokenType { CONDITION_START, VARIABLE_START, CONTEXT_SWITCH_START, END, PIPE, APOSTROPHE, TEXT, EOF } 044 045 private final Set<Character> specialCharaters = new HashSet<>(Arrays.asList(new Character[] {'$', '?', '{', '}', '|', '\'', '!'})); 046 047 private final String template; 048 049 private int c; 050 private int index; 051 private Token currentToken; 052 private StringBuilder text = new StringBuilder(); 053 054 public Tokenizer(String template) { 055 this.template = template; 056 getChar(); 057 } 058 059 private void getChar() { 060 if (index >= template.length()) { 061 c = -1; 062 } else { 063 c = template.charAt(index++); 064 } 065 } 066 067 public Token nextToken() throws ParseError { 068 if (currentToken != null) { 069 Token result = currentToken; 070 currentToken = null; 071 return result; 072 } 073 int position = index; 074 075 text.setLength(0); 076 switch (c) { 077 case -1: 078 return new Token(TokenType.EOF, position); 079 case '{': 080 getChar(); 081 return new Token(TokenType.VARIABLE_START, position); 082 case '?': 083 getChar(); 084 if (c == '{') { 085 getChar(); 086 return new Token(TokenType.CONDITION_START, position); 087 } else 088 throw ParseError.unexpectedChar('{', (char) c, position); 089 case '!': 090 getChar(); 091 if (c == '{') { 092 getChar(); 093 return new Token(TokenType.CONTEXT_SWITCH_START, position); 094 } else 095 throw ParseError.unexpectedChar('{', (char) c, position); 096 case '}': 097 getChar(); 098 return new Token(TokenType.END, position); 099 case '|': 100 getChar(); 101 return new Token(TokenType.PIPE, position); 102 case '\'': 103 getChar(); 104 return new Token(TokenType.APOSTROPHE, position); 105 default: 106 while (c != -1 && !specialCharaters.contains((char) c)) { 107 if (c == '\\') { 108 getChar(); 109 if (c == 'n') { 110 c = '\n'; 111 } 112 } 113 text.append((char) c); 114 getChar(); 115 } 116 return new Token(TokenType.TEXT, position, text.toString()); 117 } 118 } 119 120 public Token lookAhead() throws ParseError { 121 if (currentToken == null) { 122 currentToken = nextToken(); 123 } 124 return currentToken; 125 } 126 127 public Token skip(char lastChar) { 128 currentToken = null; 129 int position = index; 130 StringBuilder result = new StringBuilder(); 131 while (c != lastChar && c != -1) { 132 if (c == '\\') { 133 getChar(); 134 } 135 result.append((char) c); 136 getChar(); 137 } 138 return new Token(TokenType.TEXT, position, result.toString()); 139 } 140}