view src/kryshen/tema/TemplateParser.java @ 29:2c6edd9cf677

Effective escaped newline matching.
author Mikhail Kryshen <mikhail@kryshen.net>
date Thu, 14 May 2009 18:55:32 +0400
parents fedd0147cb6f
children 54539dff18ca
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 INVOKE(true, false),
58 VERBATIM(false, false),
59 NOINVOKE(false, false),
60 SUBFUNCTION(true, true),
61 SUBFUNCTION_NOINVOKE(false, true);
63 public final boolean invoke;
64 public final boolean subfunction;
66 DataFormat(boolean invoke, boolean subfunction) {
67 this.invoke = invoke;
68 this.subfunction = subfunction;
69 }
71 /**
72 * Returns no-call equivalent to this format.
73 */
74 public DataFormat noInvoke() {
75 if (this == INVOKE)
76 return NOINVOKE;
78 if (this == SUBFUNCTION)
79 return SUBFUNCTION_NOINVOKE;
81 return this;
82 }
83 };
85 /**
86 * Specifies how the template instruction was terminated.
87 */
88 static enum Terminator {
89 EOF, BRACKET, SEPARATOR;
90 };
92 /**
93 * The result of evaluating template instruction.
94 */
95 static class Result {
96 Terminator terminator;
97 int retCode;
99 /** No text had been parsed. */
100 boolean empty;
102 Result(Terminator terminator, int retCode, boolean empty) {
103 this.terminator = terminator;
104 this.retCode = retCode;
105 this.empty = empty;
106 }
108 Result() {
109 this(null, -1, true);
110 }
111 };
113 protected final Context context;
115 private int lastReturnCode = 0;
116 private int termBracket = -1;
118 public TemplateParser() {
119 this.context = new Context(new GlobalContext());
120 }
122 public TemplateParser(File baseDir) {
123 this.context = new Context(new GlobalContext(), baseDir);
124 }
126 public TemplateParser(TemplateParser superParser) {
127 this.context = new Context(superParser.context);
128 }
130 public TemplateParser(TemplateParser superParser, File baseDir) {
131 this.context = new Context(superParser.context, baseDir);
132 }
134 public TemplateParser(Context context) {
135 this.context = context;
136 }
138 /**
139 * Parse template
140 *
141 * @param in TemplateReader to read template data.
142 * @param out Writer for the processed data.
143 * @throws IOException if case of I/O error.
144 * @throws TemplateException in case of parsing error (syntax
145 * error or invalid arguments in template instructions).
146 */
147 public int parse(TemplateReader in, Writer out)
148 throws IOException, TemplateException {
149 return parse(in, out, DataFormat.INVOKE, null).retCode;
150 }
152 Result parse(TemplateReader in, Writer out, DataFormat format)
153 throws IOException, TemplateException {
154 return parse(in, out, format, null);
155 }
157 /**
158 * Parse template.
159 *
160 * @param in TemplateReader to read template data.
161 * @param out Writer to write processed data.
162 * @param DataFormat specifies parsing mode.
163 */
164 Result parse(TemplateReader in, Writer out, DataFormat format,
165 char[] separators)
166 throws IOException, TemplateException {
168 Result result = new Result();
170 if (format.subfunction) {
171 result.retCode = parseFunction(in, out, format);
172 result.empty = false;
173 return result;
174 }
176 while (true) {
177 if (format != DataFormat.VERBATIM) {
178 int openBracket = matchInput(in, BRACKET_OPEN);
180 if (openBracket >= 0) {
181 if (result.retCode < 0)
182 result.retCode = 0;
184 if (!format.invoke) {
185 out.write(BRACKET_OPEN[openBracket]);
186 }
188 int tb = termBracket;
189 termBracket = openBracket;
190 int returnCode = parseFunction(in, out, format);
191 lastReturnCode = returnCode;
192 result.retCode += Math.abs(returnCode);
193 result.empty = false;
194 termBracket = tb;
196 if (!format.invoke) {
197 out.write(BRACKET_CLOSE[openBracket]);
198 }
200 continue;
201 }
202 }
204 if (matchCloseBracket(in)) {
205 result.terminator = Terminator.BRACKET;
206 skipEscaped(in);
207 break;
208 }
210 int c = in.read();
212 if (c < 0) {
213 // if (termBracket >= 0) {
214 // throw new TemplateException("Unexpected end of file", in);
215 // }
217 result.terminator = Terminator.EOF;
218 break;
219 }
221 if (separators != null && isSeparator(c, separators)) {
222 result.terminator = Terminator.SEPARATOR;
223 break;
224 }
226 out.write(c);
227 result.empty = false;
228 }
230 return result;
231 }
233 /**
234 * Check if the closing bracket sequence follows in the reader.
235 */
236 boolean checkCloseBracket(TemplateReader in) throws IOException {
237 boolean match = matchCloseBracket(in);
239 if (!match)
240 return false;
242 // Put the matched characters back to the stream
243 in.unread(BRACKET_CLOSE[termBracket]);
245 return true;
246 }
248 private boolean matchCloseBracket(TemplateReader in)
249 throws IOException {
251 if (termBracket < 0)
252 return false;
254 return matchInput(in, BRACKET_CLOSE[termBracket]);
255 }
257 private int matchInput(TemplateReader in, String[] s)
258 throws IOException {
259 for (int i = 0; i < s.length; i++) {
260 if (matchInput(in, s[i]))
261 return i;
262 }
264 return -1;
265 }
267 private boolean matchInput(TemplateReader in, String s)
268 throws IOException {
269 int n = s.length();
270 for (int k = 0; k < n; k++) {
271 int c = in.read();
273 if (c != s.charAt(k)) {
274 if (c >= 0) {
275 in.unread(c);
276 }
278 for (int j = k - 1; j >= 0; j--) {
279 in.unread(s.charAt(j));
280 }
282 return false;
283 }
284 }
286 return true;
287 }
289 void skip(TemplateReader in, char[] chars) throws IOException {
290 readLoop:
291 while (true) {
292 int c = in.read();
294 if (c < 0)
295 break;
297 for (int i = 0; i < chars.length; i++) {
298 if (c == chars[i])
299 continue readLoop;
300 }
302 in.unread(c);
303 break;
304 }
305 }
307 private void skipEscaped(TemplateReader in) throws IOException {
308 if (matchInput(in, ESCAPE_WHITESPACE)) {
309 int c = in.read();
311 if (c < 0)
312 return;
314 if (!Character.isWhitespace(c)) {
315 in.unread(c);
316 in.unread(ESCAPE_WHITESPACE);
317 return;
318 }
320 do {
321 c = in.read();
322 } while (Character.isWhitespace(c));
324 if (c >= 0)
325 in.unread(c);
327 return;
328 }
330 matchInput(in, ESCAPE_NEWLINE_COMBINED);
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.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 TemplateParser parser = new TemplateParser((Context) value);
437 return parser.parse(
438 new TemplateReader(new StringReader(code),
439 fdp.getTemplateReader()),
440 out);
441 }
443 return parseValue(value, out);
444 }
446 public int getLastReturnCode() {
447 return lastReturnCode;
448 }
450 public Context getContext() {
451 return context;
452 }
453 }