view src/kryshen/tema/TemplateParser.java @ 28:fedd0147cb6f

Consistent separator names.
author Mikhail Kryshen <mikhail@kryshen.net>
date Thu, 14 May 2009 18:17:54 +0400
parents 584c2f18bb48
children 2c6edd9cf677
line source
1 /*
2 * Copyright 2006-2009 Mikhail Kryshen
3 *
4 * This file is part of Tema.
5 *
6 * Tema is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * Tema is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the
17 * GNU Lesser General Public License along with Tema.
18 * If not, see <http://www.gnu.org/licenses/>.
19 */
21 package kryshen.tema;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.StringReader;
26 import java.io.Writer;
27 import kryshen.tema.io.TemplateReader;
29 /**
30 * Parser for Tema templates.
31 *
32 * @author Mikhail Kryshen
33 */
34 public class TemplateParser {
35 /* Brackets. */
36 public static final String[] BRACKET_OPEN = {"<%", "[%"};
37 public static final String[] BRACKET_CLOSE = {"%>", "%]"};
39 public static final String ESCAPE_NEWLINE = "\\";
40 public static final String ESCAPE_WHITESPACE = "\\\\";
42 /* Separators. */
43 public static final char[] INVOKE_DATA_SEPARATORS = {':'};
44 public static final char[] VERBATIM_DATA_SEPARATORS = {'\\', '`'};
45 public static final char[] NOINVOKE_DATA_SEPARATORS = {'#'};
46 public static final char[] LIST_SEPARATORS = {' ', '\t', '\r', '\n'};
48 /**
49 * Specifies how to parse function arguments and data.
50 */
51 static enum DataFormat {
52 INVOKE(true, false),
53 VERBATIM(false, false),
54 NOINVOKE(false, false),
55 SUBFUNCTION(true, true),
56 SUBFUNCTION_NOINVOKE(false, true);
58 public final boolean invoke;
59 public final boolean subfunction;
61 DataFormat(boolean invoke, boolean subfunction) {
62 this.invoke = invoke;
63 this.subfunction = subfunction;
64 }
66 /**
67 * Returns no-call equivalent to this format.
68 */
69 public DataFormat noInvoke() {
70 if (this == INVOKE)
71 return NOINVOKE;
73 if (this == SUBFUNCTION)
74 return SUBFUNCTION_NOINVOKE;
76 return this;
77 }
78 };
80 /**
81 * Specifies how the template instruction was terminated.
82 */
83 static enum Terminator {
84 EOF, BRACKET, SEPARATOR;
85 };
87 /**
88 * The result of evaluating template instruction.
89 */
90 static class Result {
91 Terminator terminator;
92 int retCode;
94 /** No text had been parsed. */
95 boolean empty;
97 Result(Terminator terminator, int retCode, boolean empty) {
98 this.terminator = terminator;
99 this.retCode = retCode;
100 this.empty = empty;
101 }
103 Result() {
104 this(null, -1, true);
105 }
106 };
108 protected final Context context;
110 private int lastReturnCode = 0;
111 private int termBracket = -1;
113 public TemplateParser() {
114 this.context = new Context(new GlobalContext());
115 }
117 public TemplateParser(File baseDir) {
118 this.context = new Context(new GlobalContext(), baseDir);
119 }
121 public TemplateParser(TemplateParser superParser) {
122 this.context = new Context(superParser.context);
123 }
125 public TemplateParser(TemplateParser superParser, File baseDir) {
126 this.context = new Context(superParser.context, baseDir);
127 }
129 public TemplateParser(Context context) {
130 this.context = context;
131 }
133 /**
134 * Parse template
135 *
136 * @param in TemplateReader to read template data.
137 * @param out Writer for the processed data.
138 * @throws IOException if case of I/O error.
139 * @throws TemplateException in case of parsing error (syntax
140 * error or invalid arguments in template instructions).
141 */
142 public int parse(TemplateReader in, Writer out)
143 throws IOException, TemplateException {
144 return parse(in, out, DataFormat.INVOKE, null).retCode;
145 }
147 Result parse(TemplateReader in, Writer out, DataFormat format)
148 throws IOException, TemplateException {
149 return parse(in, out, format, null);
150 }
152 /**
153 * Parse template.
154 *
155 * @param in TemplateReader to read template data.
156 * @param out Writer to write processed data.
157 * @param DataFormat specifies parsing mode.
158 */
159 Result parse(TemplateReader in, Writer out, DataFormat format,
160 char[] separators)
161 throws IOException, TemplateException {
163 Result result = new Result();
165 if (format.subfunction) {
166 result.retCode = parseFunction(in, out, format);
167 result.empty = false;
168 return result;
169 }
171 while (true) {
172 if (format != DataFormat.VERBATIM) {
173 int openBracket = matchInput(in, BRACKET_OPEN);
175 if (openBracket >= 0) {
176 if (result.retCode < 0)
177 result.retCode = 0;
179 if (!format.invoke) {
180 out.write(BRACKET_OPEN[openBracket]);
181 }
183 int tb = termBracket;
184 termBracket = openBracket;
185 int returnCode = parseFunction(in, out, format);
186 lastReturnCode = returnCode;
187 result.retCode += Math.abs(returnCode);
188 result.empty = false;
189 termBracket = tb;
191 if (!format.invoke) {
192 out.write(BRACKET_CLOSE[openBracket]);
193 }
195 continue;
196 }
197 }
199 if (matchCloseBracket(in)) {
200 result.terminator = Terminator.BRACKET;
201 skipEscaped(in);
202 break;
203 }
205 int c = in.read();
207 if (c < 0) {
208 // if (termBracket >= 0) {
209 // throw new TemplateException("Unexpected end of file", in);
210 // }
212 result.terminator = Terminator.EOF;
213 break;
214 }
216 if (separators != null && isSeparator(c, separators)) {
217 result.terminator = Terminator.SEPARATOR;
218 break;
219 }
221 out.write(c);
222 result.empty = false;
223 }
225 return result;
226 }
228 /**
229 * Check if the closing bracket sequence follows in the reader.
230 */
231 boolean checkCloseBracket(TemplateReader in) throws IOException {
232 boolean match = matchCloseBracket(in);
234 if (!match)
235 return false;
237 // Put the matched characters back to the stream
238 in.unread(BRACKET_CLOSE[termBracket]);
240 return true;
241 }
243 private boolean matchCloseBracket(TemplateReader in)
244 throws IOException {
246 if (termBracket < 0)
247 return false;
249 return matchInput(in, BRACKET_CLOSE[termBracket]);
250 }
252 private int matchInput(TemplateReader in, String[] s)
253 throws IOException {
254 for (int i = 0; i < s.length; i++) {
255 if (matchInput(in, s[i]))
256 return i;
257 }
259 return -1;
260 }
262 private boolean matchInput(TemplateReader in, String s)
263 throws IOException {
264 int n = s.length();
265 for (int k = 0; k < n; k++) {
266 int c = in.read();
268 if (c != s.charAt(k)) {
269 if (c >= 0) {
270 in.unread(c);
271 }
273 for (int j = k - 1; j >= 0; j--) {
274 in.unread(s.charAt(j));
275 }
277 return false;
278 }
279 }
281 return true;
282 }
284 void skip(TemplateReader in, char[] chars) throws IOException {
285 readLoop:
286 while (true) {
287 int c = in.read();
289 if (c < 0)
290 break;
292 for (int i = 0; i < chars.length; i++) {
293 if (c == chars[i])
294 continue readLoop;
295 }
297 in.unread(c);
298 break;
299 }
300 }
302 private void skipEscaped(TemplateReader in) throws IOException {
303 if (matchInput(in, ESCAPE_WHITESPACE)) {
304 int c = in.read();
306 if (c < 0)
307 return;
309 if (!Character.isWhitespace(c)) {
310 in.unread(c);
311 in.unread(ESCAPE_WHITESPACE);
312 return;
313 }
315 do {
316 c = in.read();
317 } while (Character.isWhitespace(c));
319 if (c >= 0)
320 in.unread(c);
322 return;
323 }
325 matchInput(in,
326 new String[] {
327 ESCAPE_NEWLINE + "\r\n",
328 ESCAPE_NEWLINE + "\r",
329 ESCAPE_NEWLINE + "\n"});
330 }
332 boolean isSeparator(int c, char[] separators) {
333 for (char p : separators) {
334 if (c == p) return true;
335 }
337 return false;
338 }
340 private int parseFunction(TemplateReader in, Writer out, DataFormat fd)
341 throws IOException, TemplateException {
343 StringBuffer sb = new StringBuffer();
345 while (true) {
346 if (termBracket >= 0 &&
347 matchInput(in, BRACKET_CLOSE[termBracket])) {
348 throw new TemplateException(
349 "Unexpected end of instruction", in);
350 }
352 int c = in.read();
354 if (c < 0)
355 throw new TemplateException("Unexpected end of file.", in);
357 if (!fd.invoke)
358 out.write(c);
360 if (isSeparator(c, LIST_SEPARATORS)) {
361 return invoke(
362 fd.invoke ? sb.toString() : "true",
363 fd.invoke ?
364 DataFormat.SUBFUNCTION :
365 DataFormat.SUBFUNCTION_NOINVOKE,
366 in, out);
367 } else if (isSeparator(c, INVOKE_DATA_SEPARATORS)) {
368 skipEscaped(in);
369 return invoke(
370 fd.invoke ? sb.toString() : "true",
371 fd.invoke ?
372 DataFormat.INVOKE :
373 DataFormat.NOINVOKE,
374 in, out);
375 } else if (isSeparator(c, VERBATIM_DATA_SEPARATORS)) {
376 return invoke(
377 fd.invoke ? sb.toString() : "true",
378 DataFormat.VERBATIM, in, out);
379 } else if (isSeparator(c, NOINVOKE_DATA_SEPARATORS)) {
380 skipEscaped(in);
381 return invoke(
382 fd.invoke ? sb.toString() : "true",
383 DataFormat.NOINVOKE, in, out);
384 } else {
385 sb.append((char)c);
386 }
387 }
388 }
390 public int parseValue(Object value, Writer out) throws IOException {
391 if (value == null)
392 return 0;
394 String svalue = value.toString();
396 if (svalue == null || svalue.length() == 0)
397 return 0;
399 out.write(svalue);
400 return 1;
401 }
403 private int invoke(String name, DataFormat fd,
404 TemplateReader in, Writer out)
405 throws IOException, TemplateException {
407 FunctionDataParser fdp = new FunctionDataParser(this, fd, in, name);
409 int r = invoke(name, fdp, out);
411 if (fdp.hasMoreData()) {
412 /* Skip remaining function data. */
413 fdp.skipData();
414 }
416 return r;
417 }
419 public int invoke(String name, FunctionDataParser fdp, Writer out)
420 throws IOException, TemplateException {
422 Object value = context.get(name);
424 if (value instanceof Function) {
425 return ((Function) value).invoke(fdp, out);
426 }
428 if (value instanceof Context) {
429 String code = fdp.getData();
431 if (fdp.getLastReturnCode() == 0) {
432 return 0;
433 }
435 TemplateParser parser = new TemplateParser((Context) value);
436 return parser.parse(
437 new TemplateReader(new StringReader(code),
438 fdp.getTemplateReader()),
439 out);
440 }
442 return parseValue(value, out);
443 }
445 public int getLastReturnCode() {
446 return lastReturnCode;
447 }
449 public Context getContext() {
450 return context;
451 }
452 }