Skip to content

Commit 687c9ce

Browse files
committed
feat: Tokens now store escaped and unescaped values
1 parent cb981f2 commit 687c9ce

File tree

2 files changed

+66
-12
lines changed

2 files changed

+66
-12
lines changed

src/main/java/org/codejive/properties/PropertiesParser.java

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,37 +21,51 @@ public enum Type {
2121

2222
public static class Token {
2323
final Type type;
24+
final String raw;
2425
final String text;
2526

26-
Token(Type type, String text) {
27+
Token(Type type, String raw) {
28+
this(type, raw, null);
29+
}
30+
31+
Token(Type type, String raw, String text) {
2732
this.type = type;
33+
this.raw = raw;
2834
this.text = text;
2935
}
3036

3137
public Type getType() {
3238
return type;
3339
}
3440

41+
public String getRaw() {
42+
return raw;
43+
}
44+
3545
public String getText() {
36-
return text;
46+
return text != null ? text : raw;
3747
}
3848

3949
@Override
4050
public boolean equals(Object o) {
4151
if (this == o) return true;
4252
if (!(o instanceof Token)) return false;
4353
Token token = (Token) o;
44-
return text.equals(token.text);
54+
return type == token.type && raw.equals(token.raw) && Objects.equals(text, token.text);
4555
}
4656

4757
@Override
4858
public int hashCode() {
49-
return Objects.hash(text);
59+
return Objects.hash(type, raw, text);
5060
}
5161

5262
@Override
5363
public String toString() {
54-
return "Token('" + type + ", " + text + "')";
64+
if (text == null) {
65+
return "Token(" + type + ", '" + raw + "')";
66+
} else {
67+
return "Token(" + type + ", '" + raw + "'" + ", '" + text + "')";
68+
}
5569
}
5670
}
5771

@@ -62,6 +76,7 @@ public String toString() {
6276
private int pch;
6377
StringBuilder chr;
6478
StringBuilder str;
79+
boolean hasEscapes;
6580

6681
public PropertiesParser(Reader rdr) throws IOException {
6782
this.rdr = rdr;
@@ -100,18 +115,22 @@ public Token nextToken() throws IOException {
100115
nextState = null;
101116
}
102117
if (isValid.get()) {
103-
str.append(chr);
104118
nextChar();
105119
} else {
106120
String text = (state == Type.VALUE || state == Type.COMMENT) ? trimmedString() : string();
107-
Token token = new Token(state, text);
121+
Token token = hasEscapes ? new Token(state, text, unescape(text)) : new Token(state, text);
122+
hasEscapes = false;
108123
state = nextState;
109124
return token;
110125
}
111126
}
112127
}
113128

114129
private void nextChar() throws IOException {
130+
str.append(chr);
131+
if (chr.length() > 0 && chr.charAt(0) == '\\') {
132+
hasEscapes = true;
133+
}
115134
if (pch == -1) {
116135
ch = rdr.read();
117136
} else {
@@ -139,6 +158,41 @@ private void nextChar() throws IOException {
139158
}
140159
}
141160

161+
private static String unescape(String escape) {
162+
StringBuilder txt = new StringBuilder();
163+
for (int i = 0; i < escape.length(); i++) {
164+
char ch = escape.charAt(i);
165+
if (ch == '\\') {
166+
ch = escape.charAt(++i);
167+
switch (ch) {
168+
case 't':
169+
txt.append('\t');
170+
break;
171+
case 'f':
172+
txt.append('\f');
173+
break;
174+
case 'n':
175+
txt.append('\n');
176+
break;
177+
case 'r':
178+
txt.append('\r');
179+
break;
180+
case 'u':
181+
String num = escape.substring(i + 1, i + 5);
182+
txt.append((char) Integer.parseInt(num, 16));
183+
i += 4;
184+
break;
185+
default:
186+
txt.append(ch);
187+
break;
188+
}
189+
} else {
190+
txt.append(ch);
191+
}
192+
}
193+
return txt.toString();
194+
}
195+
142196
private void readEol(int cch) throws IOException {
143197
if (cch == '\n') {
144198
// If the next char is a \r we'll add it

src/test/java/org/codejive/properties/TestPropertiesParser.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ void testTokens() throws IOException {
6060
new Token(Type.WHITESPACE, "\n"),
6161
new Token(Type.KEY, "three"),
6262
new Token(Type.SEPARATOR, "="),
63-
new Token(Type.VALUE, "and escapes\\n\\t\\r\\f"),
63+
new Token(Type.VALUE, "and escapes\\n\\t\\r\\f", "and escapes\n\t\r\f"),
6464
new Token(Type.WHITESPACE, "\n "),
65-
new Token(Type.KEY, "\\ with\\ spaces"),
65+
new Token(Type.KEY, "\\ with\\ spaces", " with spaces"),
6666
new Token(Type.SEPARATOR, " = "),
6767
new Token(Type.VALUE, "everywhere"),
6868
new Token(Type.WHITESPACE, " \n"),
@@ -72,11 +72,11 @@ void testTokens() throws IOException {
7272
new Token(Type.WHITESPACE, "\n"),
7373
new Token(Type.KEY, "multiline"),
7474
new Token(Type.SEPARATOR, " = "),
75-
new Token(Type.VALUE, "one \\\n two \\\n\r\tthree"),
75+
new Token(Type.VALUE, "one \\\n two \\\n\r\tthree", "one \n two \n\r\tthree"),
7676
new Token(Type.WHITESPACE, "\n"),
7777
new Token(Type.KEY, "key.4"),
7878
new Token(Type.SEPARATOR, " = "),
79-
new Token(Type.VALUE, "\\u1234"),
79+
new Token(Type.VALUE, "\\u1234", "\u1234"),
8080
new Token(Type.WHITESPACE, "\n"),
8181
new Token(Type.COMMENT, "# final comment"))));
8282
}
@@ -85,7 +85,7 @@ void testTokens() throws IOException {
8585
void testStringify() throws IOException {
8686
StringReader rdr = new StringReader(props);
8787
Stream<Token> tokens = PropertiesParser.tokens(rdr);
88-
String props2 = tokens.map(Token::getText).collect(Collectors.joining());
88+
String props2 = tokens.map(Token::getRaw).collect(Collectors.joining());
8989
assertThat(props2, equalTo(props));
9090
}
9191
}

0 commit comments

Comments
 (0)