view src/kryshen/tema/TemplateParser.java @ 2:6c41a0b43e58

Tema 0.3 (imported from CVS).
author Mikhail Kryshen <mikhail@kryshen.net>
date Tue, 19 Feb 2008 20:32:17 +0300
parents 548a93c24e55
children a20217d78068
line source
1 /*
2 * Copyright 2006-2008 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 *
20 * $Id: TemplateParser.java,v 1.58 2008/02/19 00:20:48 mikhail Exp $
21 */
23 package kryshen.tema;
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.StringReader;
28 import java.io.Writer;
29 import kryshen.tema.io.TemplateReader;
31 /**
32 * Parser for Tema templates.
33 *
34 * @author Mikhail Kryshen
35 */
36 public class TemplateParser {
37 /* Brackets. */
38 public static final String[] BRACKET_OPEN = {"<%", "[%"};
39 public static final String[] BRACKET_CLOSE = {"%>", "%]"};
41 public static final String ESCAPE_NEWLINE = "\\";
42 public static final String ESCAPE_WHITESPACE = "\\\\";
44 /* Separators. */
45 public static final char[] REC_DATA_SEPARATORS = {':'};
46 public static final char[] VERBATIM_DATA_SEPARATORS = {'\\', '`'};
47 public static final char[] NOCALL_DATA_SEPARATORS = {'#'};
48 public static final char[] LIST_SEPARATORS = {' ', '\t', '\r', '\n'};
50 /**
51 * Specifies how to parse function arguments and data.
52 */
53 static enum DataFormat {
54 RECURSIVE(true, false),
55 VERBATIM(false, false),
56 NOCALL(false, false),
57 SUBFUNCTION(true, true),
58 SUBFUNCTION_NOCALL(false, true);
60 public final boolean call;
61 public final boolean subfunction;
63 DataFormat(boolean call, boolean subfunction) {
64 this.call = call;
65 this.subfunction = subfunction;
66 }
68 /**
69 * Returns no-call equivalent to this format.
70 */
71 public DataFormat noCall() {
72 if (this == RECURSIVE)
73 return NOCALL;
75 if (this == SUBFUNCTION)
76 return SUBFUNCTION_NOCALL;
78 return this;
79 }
80 };
82 /**
83 * Specifies how the template instruction was terminated.
84 */
85 static enum Terminator {
86 EOF, BRACKET, SEPARATOR;
87 };
89 /**
90 * The result of evaluating template instruction.
91 */
92 static class Result {
93 Terminator terminator;
94 int retCode;
96 /** No text had been parsed. */
97 boolean empty;
99 Result(Terminator terminator, int retCode, boolean empty) {
100 this.terminator = terminator;
101 this.retCode = retCode;
102 this.empty = empty;
103 }
105 Result() {
106 this(null, -1, true);
107 }
108 };
110 protected final Context context;
112 private int termBracket = -1;
114 public TemplateParser() {
115 this.context = new Context(new GlobalContext());
116 }
118 public TemplateParser(File baseDir) {
119 this.context = new Context(new GlobalContext(), baseDir);
120 }
122 public TemplateParser(TemplateParser superParser) {
123 this.context = new Context(superParser.context);
124 }
126 public TemplateParser(TemplateParser superParser, File baseDir) {
127 this.context = new Context(superParser.context, baseDir);
128 }
130 public TemplateParser(Context context) {
131 this.context = context;
132 }
134 /**
135 * Parse template
136 *
137 * @param in TemplateReader to read template data.
138 * @param out Writer for the processed data.
139 * @throws IOException if case of I/O error.
140 * @throws TemplateException in case of parsing error (syntax
141 * error or invalid arguments in template instructions).
142 */
143 public int parse(TemplateReader in, Writer out)
144 throws IOException, TemplateException {
145 return parse(in, out, DataFormat.RECURSIVE, null).retCode;
146 }
148 Result parse(TemplateReader in, Writer out, DataFormat format)
149 throws IOException, TemplateException {
150 return parse(in, out, format, null);
151 }
153 /**
154 * Parse template.
155 *
156 * @param in TemplateReader to read template data.
157 * @param out Writer to write processed data.
158 * @param DataFormat specifies parsing mode.
159 */
160 Result parse(TemplateReader in, Writer out, DataFormat format,
161 char[] separators)
162 throws IOException, TemplateException {
164 Result result = new Result();
166 if (format == DataFormat.SUBFUNCTION ||
167 format == DataFormat.SUBFUNCTION_NOCALL) {
169 result.retCode = parseFunction(in, out, format);
170 result.empty = false;
171 return result;
172 }
174 while (true) {
175 if (format != DataFormat.VERBATIM) {
176 int openBracket = matchInput(in, BRACKET_OPEN);
178 if (openBracket >= 0) {
179 if (result.retCode < 0)
180 result.retCode = 0;
182 if (!format.call) {
183 out.write(BRACKET_OPEN[openBracket]);
184 }
186 int tb = termBracket;
187 termBracket = openBracket;
188 result.retCode += Math.abs(parseFunction(in, out, format));
189 result.empty = false;
190 termBracket = tb;
192 if (!format.call) {
193 out.write(BRACKET_CLOSE[openBracket]);
194 }
196 continue;
197 }
198 }
200 if (matchCloseBracket(in)) {
201 result.terminator = Terminator.BRACKET;
202 skipEscaped(in);
203 break;
204 }
206 int c = in.read();
208 if (c < 0) {
209 // if (termBracket >= 0) {
210 // throw new TemplateException("Unexpected end of file", in);
211 // }
213 result.terminator = Terminator.EOF;
214 break;
215 }
217 if (separators != null && isSeparator(c, separators)) {
218 result.terminator = Terminator.SEPARATOR;
219 break;
220 }
222 out.write(c);
223 result.empty = false;
224 }
226 return result;
227 }
229 /**
230 * Check if the closing bracket sequence follows in the reader.
231 */
232 boolean checkCloseBracket(TemplateReader in) throws IOException {
233 boolean match = matchCloseBracket(in);
235 if (!match)
236 return false;
238 // Put the matched characters back to the stream
239 in.unread(BRACKET_CLOSE[termBracket].toCharArray());
241 return true;
242 }
244 private boolean matchCloseBracket(TemplateReader in)
245 throws IOException {
247 if (termBracket < 0)
248 return false;
250 return matchInput(in, BRACKET_CLOSE[termBracket]);
251 }
253 private int matchInput(TemplateReader in, String[] s)
254 throws IOException {
255 for (int i = 0; i < s.length; i++) {
256 if (matchInput(in, s[i]))
257 return i;
258 }
260 return -1;
261 }
263 private boolean matchInput(TemplateReader in, String s)
264 throws IOException {
265 int n = s.length();
266 for (int k = 0; k < n; k++) {
267 int c = in.read();
269 if (c != s.charAt(k)) {
270 if (c >= 0) {
271 in.unread(c);
272 }
274 for (int j = k - 1; j >= 0; j--) {
275 in.unread(s.charAt(j));
276 }
278 return false;
279 }
280 }
282 return true;
283 }
285 void skip(TemplateReader in, char[] chars) throws IOException {
286 readLoop:
287 while (true) {
288 int c = in.read();
290 if (c < 0)
291 break;
293 for (int i = 0; i < chars.length; i++) {
294 if (c == chars[i])
295 continue readLoop;
296 }
298 in.unread(c);
299 break;
300 }
301 }
303 private void skipEscaped(TemplateReader in) throws IOException {
304 if (matchInput(in, ESCAPE_WHITESPACE)) {
305 int c = in.read();
307 if (c < 0)
308 return;
310 if (!Character.isWhitespace(c)) {
311 in.unread(c);
312 in.unread(ESCAPE_WHITESPACE.toCharArray());
313 return;
314 }
316 do {
317 c = in.read();
318 } while (Character.isWhitespace(c));
320 if (c >= 0)
321 in.unread(c);
323 return;
324 }
326 matchInput(in,
327 new String[] {
328 ESCAPE_NEWLINE + "\r\n",
329 ESCAPE_NEWLINE + "\r",
330 ESCAPE_NEWLINE + "\n"});
331 }
333 boolean isSeparator(int c, char[] separators) {
334 for (char p : separators) {
335 if (c == p) return true;
336 }
338 return false;
339 }
341 private int parseFunction(TemplateReader in, Writer out, DataFormat fd)
342 throws IOException, TemplateException {
344 StringBuffer sb = new StringBuffer();
346 while (true) {
347 if (termBracket >= 0 &&
348 matchInput(in, BRACKET_CLOSE[termBracket])) {
349 throw new TemplateException(
350 "Unexpected end of instruction", in);
351 }
353 int c = in.read();
355 if (c < 0)
356 throw new TemplateException("Unexpected end of file.", in);
358 if (!fd.call)
359 out.write(c);
361 if (isSeparator(c, LIST_SEPARATORS)) {
362 return invoke(
363 fd.call ? sb.toString() : "true",
364 fd.call ?
365 DataFormat.SUBFUNCTION :
366 DataFormat.SUBFUNCTION_NOCALL,
367 in, out);
368 } else if (isSeparator(c, REC_DATA_SEPARATORS)) {
369 skipEscaped(in);
370 return invoke(
371 fd.call ? sb.toString() : "true",
372 fd.call ?
373 DataFormat.RECURSIVE :
374 DataFormat.NOCALL,
375 in, out);
376 } else if (isSeparator(c, VERBATIM_DATA_SEPARATORS)) {
377 return invoke(
378 fd.call ? sb.toString() : "true",
379 DataFormat.VERBATIM, in, out);
380 } else if (isSeparator(c, NOCALL_DATA_SEPARATORS)) {
381 skipEscaped(in);
382 return invoke(
383 fd.call ? sb.toString() : "true",
384 DataFormat.NOCALL, in, out);
385 } else {
386 sb.append((char)c);
387 }
388 }
389 }
391 public int parseValue(Object value, Writer out) throws IOException {
392 if (value == null)
393 return 0;
395 String svalue = value.toString();
397 if (svalue == null || svalue.length() == 0)
398 return 0;
400 out.write(svalue);
401 return 1;
402 }
404 private int invoke(String name, DataFormat fd,
405 TemplateReader in, Writer out)
406 throws IOException, TemplateException {
408 FunctionDataParser fdp = new FunctionDataParser(this, fd, in);
410 int r = invoke(name, fdp, out);
412 if (fdp.hasMoreData()) {
413 /* Skip remaining function data. */
414 fdp.skipData();
415 }
417 return r;
418 }
420 public int invoke(String name, FunctionDataParser fdp, Writer out)
421 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;
434 TemplateParser parser = new TemplateParser((Context) value);
435 return parser.parse(
436 new TemplateReader(new StringReader(code)), out);
437 }
439 return parseValue(value, out);
440 }
442 public Context getContext() {
443 return context;
444 }
445 }