view src/kryshen/tema/TemplateParser.java @ 33:b637a4491862

Function data separator could be omitted if the data is empty.
author Mikhail Kryshen <mikhail@kryshen.net>
date Sat, 17 Oct 2009 03:39:07 +0400
parents bdd1c8d6b560
children
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 private static final String[] ESCAPE_NEWLINE_COMBINED = {
43 ESCAPE_NEWLINE + "\r\n",
44 ESCAPE_NEWLINE + "\r",
45 ESCAPE_NEWLINE + "\n"};
47 /* Separators. */
48 public static final char[] INVOKE_DATA_SEPARATORS = {':'};
49 public static final char[] VERBATIM_DATA_SEPARATORS = {'\\', '`'};
50 public static final char[] NOINVOKE_DATA_SEPARATORS = {'#'};
51 public static final char[] LIST_SEPARATORS = {' ', '\t', '\r', '\n'};
53 /**
54 * Specifies how to parse function arguments and data.
55 */
56 static enum DataFormat {
57 EMPTY(false, false),
58 INVOKE(true, false),
59 VERBATIM(false, false),
60 NOINVOKE(false, false),
61 SUBFUNCTION(true, true),
62 SUBFUNCTION_NOINVOKE(false, true);
64 /** Recursively invoke nested functions. */
65 public final boolean invoke;
67 /** Treat the data as another function call. */
68 public final boolean subfunction;
70 DataFormat(boolean invoke, boolean subfunction) {
71 this.invoke = invoke;
72 this.subfunction = subfunction;
73 }
75 /**
76 * Returns no-recursive-invoke equivalent to this format.
77 */
78 public DataFormat noInvoke() {
79 if (this == INVOKE)
80 return NOINVOKE;
82 if (this == SUBFUNCTION)
83 return SUBFUNCTION_NOINVOKE;
85 return this;
86 }
87 };
89 /**
90 * Specifies how the template instruction was terminated.
91 */
92 static enum Terminator {
93 EOF, BRACKET, SEPARATOR;
94 };
96 /**
97 * The result of evaluating template instruction.
98 */
99 static class Result {
100 Terminator terminator;
101 int retCode;
103 /** No text had been parsed. */
104 boolean empty;
106 Result(Terminator terminator, int retCode, boolean empty) {
107 this.terminator = terminator;
108 this.retCode = retCode;
109 this.empty = empty;
110 }
112 Result() {
113 this(null, -1, true);
114 }
115 };
117 protected final Context context;
119 private int lastReturnCode = 0;
120 private int termBracket = -1;
122 public TemplateParser() {
123 this.context = new Context(new GlobalContext());
124 }
126 public TemplateParser(File baseDir) {
127 this.context = new Context(new GlobalContext(), baseDir);
128 }
130 public TemplateParser(TemplateParser superParser) {
131 this.context = new Context(superParser.context);
132 }
134 public TemplateParser(TemplateParser superParser, File baseDir) {
135 this.context = new Context(superParser.context, baseDir);
136 }
138 public TemplateParser(Context context) {
139 this.context = context;
140 }
142 /**
143 * Parse template
144 *
145 * @param in TemplateReader to read template data.
146 * @param out Writer for the processed data.
147 * @throws IOException if case of I/O error.
148 * @throws TemplateException in case of parsing error (syntax
149 * error or invalid arguments in template instructions).
150 */
151 public int parse(TemplateReader in, Writer out)
152 throws IOException, TemplateException {
153 return parse(in, out, DataFormat.INVOKE, null).retCode;
154 }
156 Result parse(TemplateReader in, Writer out, DataFormat format)
157 throws IOException, TemplateException {
158 return parse(in, out, format, null);
159 }
161 /**
162 * Parse template.
163 *
164 * @param in TemplateReader to read template data.
165 * @param out Writer to write processed data.
166 * @param DataFormat specifies parsing mode.
167 */
168 Result parse(TemplateReader in, Writer out, DataFormat format,
169 char[] separators)
170 throws IOException, TemplateException {
172 Result result = new Result();
174 if (format.subfunction) {
175 result.retCode = parseFunction(in, out, format);
176 result.empty = false;
177 return result;
178 }
180 while (true) {
181 if (format != DataFormat.VERBATIM) {
182 int openBracket = matchInput(in, BRACKET_OPEN);
184 if (openBracket >= 0) {
185 if (result.retCode < 0)
186 result.retCode = 0;
188 if (!format.invoke) {
189 out.write(BRACKET_OPEN[openBracket]);
190 }
192 int tb = termBracket;
193 termBracket = openBracket;
194 int returnCode = parseFunction(in, out, format);
195 lastReturnCode = returnCode;
196 result.retCode += Math.abs(returnCode);
197 result.empty = false;
198 termBracket = tb;
200 if (!format.invoke) {
201 out.write(BRACKET_CLOSE[openBracket]);
202 }
204 continue;
205 }
206 }
208 if (matchCloseBracket(in)) {
209 result.terminator = Terminator.BRACKET;
210 skipEscaped(in);
211 break;
212 }
214 int c = in.read();
216 if (c < 0) {
217 // if (termBracket >= 0) {
218 // throw new TemplateException("Unexpected end of file", in);
219 // }
221 result.terminator = Terminator.EOF;
222 break;
223 }
225 if (separators != null && isSeparator(c, separators)) {
226 result.terminator = Terminator.SEPARATOR;
227 break;
228 }
230 out.write(c);
231 result.empty = false;
232 }
234 return result;
235 }
237 /**
238 * Check if the closing bracket sequence follows in the reader.
239 */
240 boolean checkCloseBracket(TemplateReader in) throws IOException {
241 boolean match = matchCloseBracket(in);
243 if (!match)
244 return false;
246 // Put the matched characters back to the stream
247 in.unread(BRACKET_CLOSE[termBracket]);
249 return true;
250 }
252 private boolean matchCloseBracket(TemplateReader in)
253 throws IOException {
255 if (termBracket < 0)
256 return false;
258 return matchInput(in, BRACKET_CLOSE[termBracket]);
259 }
261 private int matchInput(TemplateReader in, String[] s)
262 throws IOException {
263 for (int i = 0; i < s.length; i++) {
264 if (matchInput(in, s[i]))
265 return i;
266 }
268 return -1;
269 }
271 private boolean matchInput(TemplateReader in, String s)
272 throws IOException {
273 int n = s.length();
274 for (int k = 0; k < n; k++) {
275 int c = in.read();
277 if (c != s.charAt(k)) {
278 in.unread(c);
280 for (int j = k - 1; j >= 0; j--) {
281 in.unread(s.charAt(j));
282 }
284 return false;
285 }
286 }
288 return true;
289 }
291 void skip(TemplateReader in, char[] chars) throws IOException {
292 readLoop:
293 while (true) {
294 int c = in.read();
296 for (int i = 0; i < chars.length; i++) {
297 if (c == chars[i])
298 continue readLoop;
299 }
301 in.unread(c);
302 break;
303 }
304 }
306 private void skipEscaped(TemplateReader in) throws IOException {
307 if (matchInput(in, ESCAPE_WHITESPACE)) {
308 int c = in.read();
310 if (c < 0 || !Character.isWhitespace(c)) {
311 in.unread(c);
312 in.unread(ESCAPE_WHITESPACE);
313 return;
314 }
316 do {
317 c = in.read();
318 } while (c > 0 && Character.isWhitespace(c));
320 in.unread(c);
322 return;
323 }
325 matchInput(in, ESCAPE_NEWLINE_COMBINED);
326 }
328 boolean isSeparator(int c, char[] separators) {
329 for (char p : separators) {
330 if (c == p) return true;
331 }
333 return false;
334 }
336 private int parseFunction(TemplateReader in, Writer out, DataFormat fd)
337 throws IOException, TemplateException {
339 StringBuffer sb = new StringBuffer();
341 while (true) {
342 if (termBracket >= 0 &&
343 matchInput(in, BRACKET_CLOSE[termBracket])) {
345 // throw new TemplateException(
346 // "Unexpected end of instruction", in);
348 return invoke(
349 fd.invoke ? sb.toString() : "true",
350 DataFormat.EMPTY, in, out);
351 }
353 int c = in.read();
355 if (c < 0)
356 throw new TemplateException("Unexpected end of file.", in);
358 if (!fd.invoke)
359 out.write(c);
361 if (isSeparator(c, LIST_SEPARATORS)) {
362 return invoke(
363 fd.invoke ? sb.toString() : "true",
364 fd.invoke ?
365 DataFormat.SUBFUNCTION :
366 DataFormat.SUBFUNCTION_NOINVOKE,
367 in, out);
368 } else if (isSeparator(c, INVOKE_DATA_SEPARATORS)) {
369 skipEscaped(in);
370 return invoke(
371 fd.invoke ? sb.toString() : "true",
372 fd.invoke ?
373 DataFormat.INVOKE :
374 DataFormat.NOINVOKE,
375 in, out);
376 } else if (isSeparator(c, VERBATIM_DATA_SEPARATORS)) {
377 return invoke(
378 fd.invoke ? sb.toString() : "true",
379 DataFormat.VERBATIM, in, out);
380 } else if (isSeparator(c, NOINVOKE_DATA_SEPARATORS)) {
381 skipEscaped(in);
382 return invoke(
383 fd.invoke ? sb.toString() : "true",
384 DataFormat.NOINVOKE, 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, name);
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 {
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;
434 }
436 TemplateReader in = new TemplateReader(new StringReader(code),
437 fdp.getTemplateReader());
439 return ((Context) value).parseInContext(in, out, context);
440 }
442 return parseValue(value, out);
443 }
445 public int getLastReturnCode() {
446 return lastReturnCode;
447 }
449 public Context getContext() {
450 return context;
451 }
452 }