-
Notifications
You must be signed in to change notification settings - Fork 284
Expand file tree
/
Copy pathMustacheParser.java
More file actions
278 lines (265 loc) · 10.7 KB
/
MustacheParser.java
File metadata and controls
278 lines (265 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package com.github.mustachejava;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The parser generates callbacks into the MustacheFactory to build them. Do not use these
* directly as you must manage the Mustache object lifecycle as well.
* <p/>
* <p/>
* User: sam
* Date: 5/14/11
* Time: 3:52 PM
*/
public class MustacheParser {
public static final String DEFAULT_SM = "{{";
public static final String DEFAULT_EM = "}}";
private MustacheFactory mf;
protected MustacheParser(MustacheFactory mf) {
this.mf = mf;
}
public Mustache compile(String file) {
Reader reader = mf.getReader(file);
if (reader == null) {
throw new MustacheNotFoundException(file);
}
return compile(reader, file);
}
public Mustache compile(Reader reader, String file) {
return compile(reader, file, DEFAULT_SM, DEFAULT_EM);
}
public Mustache compile(Reader reader, String file, String sm, String em) {
return compile(reader, null, new AtomicInteger(0), file, sm, em, true);
}
public Mustache compile(Reader reader, String file, String sm, String em, boolean startOfLine) {
return compile(reader, null, new AtomicInteger(0), file, sm, em, startOfLine);
}
protected Mustache compile(final Reader reader, String tag, final AtomicInteger currentLine, String file, String sm, String em, boolean startOfLine) throws MustacheException {
if (reader == null) {
throw new MustacheException("Reader is null");
}
Reader br;
if (reader.markSupported()) {
br = reader;
} else {
br = new BufferedReader(reader);
}
try {
boolean sawCR = false;
int startLine = currentLine.get();
MustacheVisitor mv = mf.createMustacheVisitor();
// Now we grab the mustache template
boolean onlywhitespace = true;
// Starting a new line
boolean iterable = currentLine.get() != 0;
currentLine.compareAndSet(0, 1);
StringBuilder out = new StringBuilder();
try {
int c;
while ((c = br.read()) != -1) {
if (c == '\r') {
sawCR = true;
continue;
}
// Increment the line
if (c == '\n') {
currentLine.incrementAndGet();
if (!iterable || (iterable && !onlywhitespace)) {
if (sawCR) out.append("\r");
out.append("\n");
}
out = write(mv, out, file, currentLine.intValue(), startOfLine);
iterable = false;
onlywhitespace = true;
startOfLine = true;
continue;
}
sawCR = false;
// Check for a mustache start
if (c == sm.charAt(0)) {
br.mark(1);
if (sm.length() == 1 || br.read() == sm.charAt(1)) {
// Two mustaches, now capture command
StringBuilder sb = new StringBuilder();
while ((c = br.read()) != -1) {
br.mark(1);
if (c == em.charAt(0)) {
if (em.length() > 1) {
if (br.read() == em.charAt(1)) {
// Matched end
break;
} else {
// Only one
br.reset();
}
} else break;
}
sb.append((char) c);
}
final String command = mf.translate(sb.toString());
if (command.length() == 0) {
throw new MustacheException("Empty mustache in " + file + ":" + currentLine);
}
final char ch = command.charAt(0);
final String variable = command.substring(1).trim();
switch (ch) {
case '#':
case '^':
case '<':
case '$': {
boolean oldStartOfLine = startOfLine;
startOfLine = startOfLine & onlywhitespace;
int line = currentLine.get();
final Mustache mustache = compile(br, variable, currentLine, file, sm, em, startOfLine);
int lines = currentLine.get() - line;
if (!onlywhitespace || lines == 0) {
write(mv, out, file, currentLine.intValue(), oldStartOfLine);
}
out = new StringBuilder();
switch (ch) {
case '#':
mv.iterable(new TemplateContext(sm, em, file, line, startOfLine), variable, mustache);
break;
case '^':
mv.notIterable(new TemplateContext(sm, em, file, line, startOfLine), variable, mustache);
break;
case '<':
mv.extend(new TemplateContext(sm, em, file, line, startOfLine), variable, mustache);
break;
case '$':
mv.name(new TemplateContext(sm, em, file, line, startOfLine), variable, mustache);
break;
}
iterable = lines != 0;
break;
}
case '/': {
// Tag end
if (!startOfLine || !onlywhitespace) {
write(mv, out, file, currentLine.intValue(), startOfLine);
}
if (!variable.equals(tag)) {
throw new MustacheException(
"Mismatched start/end tags: " + tag + " != " + variable + " in " + file + ":" + currentLine);
}
return mv.mustache(new TemplateContext(sm, em, file, 0, startOfLine));
}
case '>': {
out = write(mv, out, file, currentLine.intValue(), startOfLine);
startOfLine = startOfLine & onlywhitespace;
mv.partial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable);
break;
}
case '{': {
out = write(mv, out, file, currentLine.intValue(), startOfLine);
// Not escaped
String name = variable;
if (em.charAt(1) != '}') {
name = variable.substring(0, variable.length() - 1);
} else {
if (br.read() != '}') {
throw new MustacheException(
"Improperly closed variable in " + file + ":" + currentLine);
}
}
final String finalName = name;
mv.value(new TemplateContext(sm, em, file, currentLine.get(), false), finalName, false);
break;
}
case '&': {
// Not escaped
out = write(mv, out, file, currentLine.intValue(), startOfLine);
mv.value(new TemplateContext(sm, em, file, currentLine.get(), false), variable, false);
break;
}
case '%':
// Pragmas
if (!onlywhitespace) {
out = write(mv, out, file, currentLine.intValue(), startOfLine);
}
int index = variable.indexOf(" ");
String pragma;
String args;
if (index == -1) {
pragma = variable;
args = null;
} else {
pragma = variable.substring(0, index);
args = variable.substring(index + 1);
}
mv.pragma(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), pragma, args);
break;
case '!':
// Comment
out = write(mv, out, file, currentLine.intValue(), startOfLine);
break;
case '=':
// Change delimiters
out = write(mv, out, file, currentLine.intValue(), startOfLine);
String delimiters = command.replaceAll("\\s+", "");
int length = delimiters.length();
if (length > 6 || length / 2 * 2 != length) {
throw new MustacheException("Invalid delimiter string");
}
sm = delimiters.substring(1, length / 2);
em = delimiters.substring(length / 2, length - 1);
break;
case '+':
out = write(mv, out, file, currentLine.intValue(), startOfLine);
startOfLine = startOfLine & onlywhitespace;
mv.dynamicPartial(new TemplateContext(sm, em, file, currentLine.get(), startOfLine), variable);
break;
default: {
if (c == -1) {
throw new MustacheException(
"Improperly closed variable in " + file + ":" + currentLine);
}
// Reference
out = write(mv, out, file, currentLine.intValue(), startOfLine);
mv.value(new TemplateContext(sm, em, file, currentLine.get(), false), command.trim(), true);
break;
}
}
// Additional text is no longer at the start of the line
startOfLine = false;
continue;
} else {
// Only one
br.reset();
}
}
onlywhitespace = onlywhitespace && (c == ' ' || c == '\t' || c == '\r');
out.append((char) c);
}
write(mv, out, file, currentLine.intValue(), startOfLine);
if (tag == null) {
br.close();
} else {
throw new MustacheException("Failed to close '" + tag + "' tag at line " + startLine);
}
} catch (IOException e) {
throw new MustacheException("Failed to read", e);
}
mv.eof(new TemplateContext(sm, em, file, currentLine.get(), startOfLine));
return mv.mustache(new TemplateContext(sm, em, file, 0, startOfLine));
} catch (MustacheException me) {
try {
// We're going to blow the whole stack of compilation and
// close the readers on the way up.
br.close();
} catch (IOException e) {
// Ignore IOExceptions on close
}
throw me;
}
}
/**
* Ignore empty strings and append to the previous code if it was also a write.
*/
private StringBuilder write(MustacheVisitor mv, StringBuilder out, String file, int line, boolean startOfLine) {
String text = out.toString();
mv.write(new TemplateContext(null, null, file, line, startOfLine), text);
return new StringBuilder();
}
}