view src/kryshen/tema/TemplateParser.java @ 18:d53cd4995bd4

Remember the return code of the last parsed instruction.
author Mikhail Kryshen <mikhail@kryshen.net>
date Thu, 26 Mar 2009 17:18:31 +0300
parents e9d13c7ffeb1
children 7b11f5174e29
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[] REC_DATA_SEPARATORS = {':'};
44 public static final char[] VERBATIM_DATA_SEPARATORS = {'\\', '`'};
45 public static final char[] NOCALL_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 RECURSIVE(true, false),
53 VERBATIM(false, false),
54 NOCALL(false, false),
55 SUBFUNCTION(true, true),
56 SUBFUNCTION_NOCALL(false, true);
58 public final boolean call;
59 public final boolean subfunction;
61 DataFormat(boolean call, boolean subfunction) {
62 this.call = call;
63 this.subfunction = subfunction;
64 }
66 /**
67 * Returns no-call equivalent to this format.
68 */
69 public DataFormat noCall() {
70 if (this == RECURSIVE)
71 return NOCALL;
73 if (this == SUBFUNCTION)
74 return SUBFUNCTION_NOCALL;
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.RECURSIVE, 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 == DataFormat.SUBFUNCTION ||
166 format == DataFormat.SUBFUNCTION_NOCALL) {
168 result.retCode = parseFunction(in, out, format);
169 result.empty = false;
170 return result;
171 }
173 while (true) {
174 if (format != DataFormat.VERBATIM) {
175 int openBracket = matchInput(in, BRACKET_OPEN);
177 if (openBracket >= 0) {
178 if (result.retCode < 0)
179 result.retCode = 0;
181 if (!format.call) {
182 out.write(BRACKET_OPEN[openBracket]);
183 }
185 int tb = termBracket;
186 termBracket = openBracket;
187 int returnCode = Math.abs(parseFunction(in, out, format));
188 lastReturnCode = returnCode;
189 result.retCode += returnCode;
190 result.empty = false;
191 termBracket = tb;
193 if (!format.call) {
194 out.write(BRACKET_CLOSE[openBracket]);
195 }
197 continue;
198 }
199 }
201 if (matchCloseBracket(in)) {
202 result.terminator = Terminator.BRACKET;
203 skipEscaped(in);
204 break;
205 }
207 int c = in.read();
209 if (c < 0) {
210 // if (termBracket >= 0) {
211 // throw new TemplateException("Unexpected end of file", in);
212 // }
214 result.terminator = Terminator.EOF;
215 break;
216 }
218 if (separators != null && isSeparator(c, separators)) {
219 result.terminator = Terminator.SEPARATOR;
220 break;
221 }
223 out.write(c);
224 result.empty = false;
225 }
227 return result;
228 }
230 /**
231 * Check if the closing bracket sequence follows in the reader.
232 */
233 boolean checkCloseBracket(TemplateReader in) throws IOException {
234 boolean match = matchCloseBracket(in);
236 if (!match)
237 return false;
239 // Put the matched characters back to the stream
240 in.unread(BRACKET_CLOSE[termBracket]);
242 return true;
243 }
245 private boolean matchCloseBracket(TemplateReader in)
246 throws IOException {
248 if (termBracket < 0)
249 return false;
251 return matchInput(in, BRACKET_CLOSE[termBracket]);
252 }
254 private int matchInput(TemplateReader in, String[] s)
255 throws IOException {
256 for (int i = 0; i < s.length; i++) {
257 if (matchInput(in, s[i]))
258 return i;
259 }
261 return -1;
262 }
264 private boolean matchInput(TemplateReader in, String s)
265 throws IOException {
266 int n = s.length();
267 for (int k = 0; k < n; k++) {
268 int c = in.read();
270 if (c != s.charAt(k)) {
271 if (c >= 0) {
272 in.unread(c);
273 }
275 for (int j = k - 1; j >= 0; j--) {
276 in.unread(s.charAt(j));
277 }
279 return false;
280 }
281 }
283 return true;
284 }
286 void skip(TemplateReader in, char[] chars) throws IOException {
287 readLoop:
288 while (true) {
289 int c = in.read();
291 if (c < 0)
292 break;
294 for (int i = 0; i < chars.length; i++) {
295 if (c == chars[i])
296 continue readLoop;
297 }
299 in.unread(c);
300 break;
301 }
302 }
304 private void skipEscaped(TemplateReader in) throws IOException {
305 if (matchInput(in, ESCAPE_WHITESPACE)) {
306 int c = in.read();
308 if (c < 0)
309 return;
311 if (!Character.isWhitespace(c)) {
312 in.unread(c);
313 in.unread(ESCAPE_WHITESPACE);
314 return;
315 }
317 do {
318 c = in.read();
319 } while (Character.isWhitespace(c));
321 if (c >= 0)
322 in.unread(c);
324 return;
325 }
327 matchInput(in,
328 new String[] {
329 ESCAPE_NEWLINE + "\r\n",
330 ESCAPE_NEWLINE + "\r",
331 ESCAPE_NEWLINE + "\n"});
332 }
334 boolean isSeparator(int c, char[] separators) {
335 for (char p : separators) {
336 if (c == p) return true;
337 }
339 return false;
340 }
342 private int parseFunction(TemplateReader in, Writer out, DataFormat fd)
343 throws IOException, TemplateException {
345 StringBuffer sb = new StringBuffer();
347 while (true) {
348 if (termBracket >= 0 &&
349 matchInput(in, BRACKET_CLOSE[termBracket])) {
350 throw new TemplateException(
351 "Unexpected end of instruction", in);
352 }
354 int c = in.read();
356 if (c < 0)
357 throw new TemplateException("Unexpected end of file.", in);
359 if (!fd.call)
360 out.write(c);
362 if (isSeparator(c, LIST_SEPARATORS)) {
363 return invoke(
364 fd.call ? sb.toString() : "true",
365 fd.call ?
366 DataFormat.SUBFUNCTION :
367 DataFormat.SUBFUNCTION_NOCALL,
368 in, out);
369 } else if (isSeparator(c, REC_DATA_SEPARATORS)) {
370 skipEscaped(in);
371 return invoke(
372 fd.call ? sb.toString() : "true",
373 fd.call ?
374 DataFormat.RECURSIVE :
375 DataFormat.NOCALL,
376 in, out);
377 } else if (isSeparator(c, VERBATIM_DATA_SEPARATORS)) {
378 return invoke(
379 fd.call ? sb.toString() : "true",
380 DataFormat.VERBATIM, in, out);
381 } else if (isSeparator(c, NOCALL_DATA_SEPARATORS)) {
382 skipEscaped(in);
383 return invoke(
384 fd.call ? sb.toString() : "true",
385 DataFormat.NOCALL, in, out);
386 } else {
387 sb.append((char)c);
388 }
389 }
390 }
392 public int parseValue(Object value, Writer out) throws IOException {
393 if (value == null)
394 return 0;
396 String svalue = value.toString();
398 if (svalue == null || svalue.length() == 0)
399 return 0;
401 out.write(svalue);
402 return 1;
403 }
405 private int invoke(String name, DataFormat fd,
406 TemplateReader in, Writer out)
407 throws IOException, TemplateException {
409 FunctionDataParser fdp = new FunctionDataParser(this, fd, in);
411 int r = invoke(name, fdp, out);
413 if (fdp.hasMoreData()) {
414 /* Skip remaining function data. */
415 fdp.skipData();
416 }
418 return r;
419 }
421 public int invoke(String name, FunctionDataParser fdp, Writer out)
422 throws IOException, TemplateException {
423 Object value = context.get(name);
425 if (value instanceof Function) {
426 return ((Function) value).invoke(fdp, out);
427 }
429 if (value instanceof Context) {
430 String code = fdp.getData();
432 if (fdp.getLastReturnCode() == 0)
433 return 0;
435 TemplateParser parser = new TemplateParser((Context) value);
436 return parser.parse(
437 new TemplateReader(new StringReader(code)), out);
438 }
440 return parseValue(value, out);
441 }
443 public int getLastReturnCode() {
444 return lastReturnCode;
445 }
447 public Context getContext() {
448 return context;
449 }
450 }