view src/kryshen/tema/TemplateParser.java @ 15:e9d13c7ffeb1

Update header comment.
author Mikhail Kryshen <mikhail@kryshen.net>
date Tue, 24 Mar 2009 18:51:47 +0300
parents a20217d78068
children d53cd4995bd4
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 termBracket = -1;
112 public TemplateParser() {
113 this.context = new Context(new GlobalContext());
114 }
116 public TemplateParser(File baseDir) {
117 this.context = new Context(new GlobalContext(), baseDir);
118 }
120 public TemplateParser(TemplateParser superParser) {
121 this.context = new Context(superParser.context);
122 }
124 public TemplateParser(TemplateParser superParser, File baseDir) {
125 this.context = new Context(superParser.context, baseDir);
126 }
128 public TemplateParser(Context context) {
129 this.context = context;
130 }
132 /**
133 * Parse template
134 *
135 * @param in TemplateReader to read template data.
136 * @param out Writer for the processed data.
137 * @throws IOException if case of I/O error.
138 * @throws TemplateException in case of parsing error (syntax
139 * error or invalid arguments in template instructions).
140 */
141 public int parse(TemplateReader in, Writer out)
142 throws IOException, TemplateException {
143 return parse(in, out, DataFormat.RECURSIVE, null).retCode;
144 }
146 Result parse(TemplateReader in, Writer out, DataFormat format)
147 throws IOException, TemplateException {
148 return parse(in, out, format, null);
149 }
151 /**
152 * Parse template.
153 *
154 * @param in TemplateReader to read template data.
155 * @param out Writer to write processed data.
156 * @param DataFormat specifies parsing mode.
157 */
158 Result parse(TemplateReader in, Writer out, DataFormat format,
159 char[] separators)
160 throws IOException, TemplateException {
162 Result result = new Result();
164 if (format == DataFormat.SUBFUNCTION ||
165 format == DataFormat.SUBFUNCTION_NOCALL) {
167 result.retCode = parseFunction(in, out, format);
168 result.empty = false;
169 return result;
170 }
172 while (true) {
173 if (format != DataFormat.VERBATIM) {
174 int openBracket = matchInput(in, BRACKET_OPEN);
176 if (openBracket >= 0) {
177 if (result.retCode < 0)
178 result.retCode = 0;
180 if (!format.call) {
181 out.write(BRACKET_OPEN[openBracket]);
182 }
184 int tb = termBracket;
185 termBracket = openBracket;
186 result.retCode += Math.abs(parseFunction(in, out, format));
187 result.empty = false;
188 termBracket = tb;
190 if (!format.call) {
191 out.write(BRACKET_CLOSE[openBracket]);
192 }
194 continue;
195 }
196 }
198 if (matchCloseBracket(in)) {
199 result.terminator = Terminator.BRACKET;
200 skipEscaped(in);
201 break;
202 }
204 int c = in.read();
206 if (c < 0) {
207 // if (termBracket >= 0) {
208 // throw new TemplateException("Unexpected end of file", in);
209 // }
211 result.terminator = Terminator.EOF;
212 break;
213 }
215 if (separators != null && isSeparator(c, separators)) {
216 result.terminator = Terminator.SEPARATOR;
217 break;
218 }
220 out.write(c);
221 result.empty = false;
222 }
224 return result;
225 }
227 /**
228 * Check if the closing bracket sequence follows in the reader.
229 */
230 boolean checkCloseBracket(TemplateReader in) throws IOException {
231 boolean match = matchCloseBracket(in);
233 if (!match)
234 return false;
236 // Put the matched characters back to the stream
237 in.unread(BRACKET_CLOSE[termBracket]);
239 return true;
240 }
242 private boolean matchCloseBracket(TemplateReader in)
243 throws IOException {
245 if (termBracket < 0)
246 return false;
248 return matchInput(in, BRACKET_CLOSE[termBracket]);
249 }
251 private int matchInput(TemplateReader in, String[] s)
252 throws IOException {
253 for (int i = 0; i < s.length; i++) {
254 if (matchInput(in, s[i]))
255 return i;
256 }
258 return -1;
259 }
261 private boolean matchInput(TemplateReader in, String s)
262 throws IOException {
263 int n = s.length();
264 for (int k = 0; k < n; k++) {
265 int c = in.read();
267 if (c != s.charAt(k)) {
268 if (c >= 0) {
269 in.unread(c);
270 }
272 for (int j = k - 1; j >= 0; j--) {
273 in.unread(s.charAt(j));
274 }
276 return false;
277 }
278 }
280 return true;
281 }
283 void skip(TemplateReader in, char[] chars) throws IOException {
284 readLoop:
285 while (true) {
286 int c = in.read();
288 if (c < 0)
289 break;
291 for (int i = 0; i < chars.length; i++) {
292 if (c == chars[i])
293 continue readLoop;
294 }
296 in.unread(c);
297 break;
298 }
299 }
301 private void skipEscaped(TemplateReader in) throws IOException {
302 if (matchInput(in, ESCAPE_WHITESPACE)) {
303 int c = in.read();
305 if (c < 0)
306 return;
308 if (!Character.isWhitespace(c)) {
309 in.unread(c);
310 in.unread(ESCAPE_WHITESPACE);
311 return;
312 }
314 do {
315 c = in.read();
316 } while (Character.isWhitespace(c));
318 if (c >= 0)
319 in.unread(c);
321 return;
322 }
324 matchInput(in,
325 new String[] {
326 ESCAPE_NEWLINE + "\r\n",
327 ESCAPE_NEWLINE + "\r",
328 ESCAPE_NEWLINE + "\n"});
329 }
331 boolean isSeparator(int c, char[] separators) {
332 for (char p : separators) {
333 if (c == p) return true;
334 }
336 return false;
337 }
339 private int parseFunction(TemplateReader in, Writer out, DataFormat fd)
340 throws IOException, TemplateException {
342 StringBuffer sb = new StringBuffer();
344 while (true) {
345 if (termBracket >= 0 &&
346 matchInput(in, BRACKET_CLOSE[termBracket])) {
347 throw new TemplateException(
348 "Unexpected end of instruction", in);
349 }
351 int c = in.read();
353 if (c < 0)
354 throw new TemplateException("Unexpected end of file.", in);
356 if (!fd.call)
357 out.write(c);
359 if (isSeparator(c, LIST_SEPARATORS)) {
360 return invoke(
361 fd.call ? sb.toString() : "true",
362 fd.call ?
363 DataFormat.SUBFUNCTION :
364 DataFormat.SUBFUNCTION_NOCALL,
365 in, out);
366 } else if (isSeparator(c, REC_DATA_SEPARATORS)) {
367 skipEscaped(in);
368 return invoke(
369 fd.call ? sb.toString() : "true",
370 fd.call ?
371 DataFormat.RECURSIVE :
372 DataFormat.NOCALL,
373 in, out);
374 } else if (isSeparator(c, VERBATIM_DATA_SEPARATORS)) {
375 return invoke(
376 fd.call ? sb.toString() : "true",
377 DataFormat.VERBATIM, in, out);
378 } else if (isSeparator(c, NOCALL_DATA_SEPARATORS)) {
379 skipEscaped(in);
380 return invoke(
381 fd.call ? sb.toString() : "true",
382 DataFormat.NOCALL, in, out);
383 } else {
384 sb.append((char)c);
385 }
386 }
387 }
389 public int parseValue(Object value, Writer out) throws IOException {
390 if (value == null)
391 return 0;
393 String svalue = value.toString();
395 if (svalue == null || svalue.length() == 0)
396 return 0;
398 out.write(svalue);
399 return 1;
400 }
402 private int invoke(String name, DataFormat fd,
403 TemplateReader in, Writer out)
404 throws IOException, TemplateException {
406 FunctionDataParser fdp = new FunctionDataParser(this, fd, in);
408 int r = invoke(name, fdp, out);
410 if (fdp.hasMoreData()) {
411 /* Skip remaining function data. */
412 fdp.skipData();
413 }
415 return r;
416 }
418 public int invoke(String name, FunctionDataParser fdp, Writer out)
419 throws IOException, TemplateException {
420 Object value = context.get(name);
422 if (value instanceof Function) {
423 return ((Function) value).invoke(fdp, out);
424 }
426 if (value instanceof Context) {
427 String code = fdp.getData();
429 if (fdp.getLastReturnCode() == 0)
430 return 0;
432 TemplateParser parser = new TemplateParser((Context) value);
433 return parser.parse(
434 new TemplateReader(new StringReader(code)), out);
435 }
437 return parseValue(value, out);
438 }
440 public Context getContext() {
441 return context;
442 }
443 }