Mercurial > hg > tema
view src/kryshen/tema/TemplateParser.java @ 28:fedd0147cb6f
Consistent separator names.
author | Mikhail Kryshen <mikhail@kryshen.net> |
---|---|
date | Thu, 14 May 2009 18:17:54 +0400 |
parents | 584c2f18bb48 |
children | 2c6edd9cf677 |
line wrap: on
line source
/* * Copyright 2006-2009 Mikhail Kryshen * * This file is part of Tema. * * Tema is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Tema is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the * GNU Lesser General Public License along with Tema. * If not, see <http://www.gnu.org/licenses/>. */ package kryshen.tema; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.io.Writer; import kryshen.tema.io.TemplateReader; /** * Parser for Tema templates. * * @author Mikhail Kryshen */ public class TemplateParser { /* Brackets. */ public static final String[] BRACKET_OPEN = {"<%", "[%"}; public static final String[] BRACKET_CLOSE = {"%>", "%]"}; public static final String ESCAPE_NEWLINE = "\\"; public static final String ESCAPE_WHITESPACE = "\\\\"; /* Separators. */ public static final char[] INVOKE_DATA_SEPARATORS = {':'}; public static final char[] VERBATIM_DATA_SEPARATORS = {'\\', '`'}; public static final char[] NOINVOKE_DATA_SEPARATORS = {'#'}; public static final char[] LIST_SEPARATORS = {' ', '\t', '\r', '\n'}; /** * Specifies how to parse function arguments and data. */ static enum DataFormat { INVOKE(true, false), VERBATIM(false, false), NOINVOKE(false, false), SUBFUNCTION(true, true), SUBFUNCTION_NOINVOKE(false, true); public final boolean invoke; public final boolean subfunction; DataFormat(boolean invoke, boolean subfunction) { this.invoke = invoke; this.subfunction = subfunction; } /** * Returns no-call equivalent to this format. */ public DataFormat noInvoke() { if (this == INVOKE) return NOINVOKE; if (this == SUBFUNCTION) return SUBFUNCTION_NOINVOKE; return this; } }; /** * Specifies how the template instruction was terminated. */ static enum Terminator { EOF, BRACKET, SEPARATOR; }; /** * The result of evaluating template instruction. */ static class Result { Terminator terminator; int retCode; /** No text had been parsed. */ boolean empty; Result(Terminator terminator, int retCode, boolean empty) { this.terminator = terminator; this.retCode = retCode; this.empty = empty; } Result() { this(null, -1, true); } }; protected final Context context; private int lastReturnCode = 0; private int termBracket = -1; public TemplateParser() { this.context = new Context(new GlobalContext()); } public TemplateParser(File baseDir) { this.context = new Context(new GlobalContext(), baseDir); } public TemplateParser(TemplateParser superParser) { this.context = new Context(superParser.context); } public TemplateParser(TemplateParser superParser, File baseDir) { this.context = new Context(superParser.context, baseDir); } public TemplateParser(Context context) { this.context = context; } /** * Parse template * * @param in TemplateReader to read template data. * @param out Writer for the processed data. * @throws IOException if case of I/O error. * @throws TemplateException in case of parsing error (syntax * error or invalid arguments in template instructions). */ public int parse(TemplateReader in, Writer out) throws IOException, TemplateException { return parse(in, out, DataFormat.INVOKE, null).retCode; } Result parse(TemplateReader in, Writer out, DataFormat format) throws IOException, TemplateException { return parse(in, out, format, null); } /** * Parse template. * * @param in TemplateReader to read template data. * @param out Writer to write processed data. * @param DataFormat specifies parsing mode. */ Result parse(TemplateReader in, Writer out, DataFormat format, char[] separators) throws IOException, TemplateException { Result result = new Result(); if (format.subfunction) { result.retCode = parseFunction(in, out, format); result.empty = false; return result; } while (true) { if (format != DataFormat.VERBATIM) { int openBracket = matchInput(in, BRACKET_OPEN); if (openBracket >= 0) { if (result.retCode < 0) result.retCode = 0; if (!format.invoke) { out.write(BRACKET_OPEN[openBracket]); } int tb = termBracket; termBracket = openBracket; int returnCode = parseFunction(in, out, format); lastReturnCode = returnCode; result.retCode += Math.abs(returnCode); result.empty = false; termBracket = tb; if (!format.invoke) { out.write(BRACKET_CLOSE[openBracket]); } continue; } } if (matchCloseBracket(in)) { result.terminator = Terminator.BRACKET; skipEscaped(in); break; } int c = in.read(); if (c < 0) { // if (termBracket >= 0) { // throw new TemplateException("Unexpected end of file", in); // } result.terminator = Terminator.EOF; break; } if (separators != null && isSeparator(c, separators)) { result.terminator = Terminator.SEPARATOR; break; } out.write(c); result.empty = false; } return result; } /** * Check if the closing bracket sequence follows in the reader. */ boolean checkCloseBracket(TemplateReader in) throws IOException { boolean match = matchCloseBracket(in); if (!match) return false; // Put the matched characters back to the stream in.unread(BRACKET_CLOSE[termBracket]); return true; } private boolean matchCloseBracket(TemplateReader in) throws IOException { if (termBracket < 0) return false; return matchInput(in, BRACKET_CLOSE[termBracket]); } private int matchInput(TemplateReader in, String[] s) throws IOException { for (int i = 0; i < s.length; i++) { if (matchInput(in, s[i])) return i; } return -1; } private boolean matchInput(TemplateReader in, String s) throws IOException { int n = s.length(); for (int k = 0; k < n; k++) { int c = in.read(); if (c != s.charAt(k)) { if (c >= 0) { in.unread(c); } for (int j = k - 1; j >= 0; j--) { in.unread(s.charAt(j)); } return false; } } return true; } void skip(TemplateReader in, char[] chars) throws IOException { readLoop: while (true) { int c = in.read(); if (c < 0) break; for (int i = 0; i < chars.length; i++) { if (c == chars[i]) continue readLoop; } in.unread(c); break; } } private void skipEscaped(TemplateReader in) throws IOException { if (matchInput(in, ESCAPE_WHITESPACE)) { int c = in.read(); if (c < 0) return; if (!Character.isWhitespace(c)) { in.unread(c); in.unread(ESCAPE_WHITESPACE); return; } do { c = in.read(); } while (Character.isWhitespace(c)); if (c >= 0) in.unread(c); return; } matchInput(in, new String[] { ESCAPE_NEWLINE + "\r\n", ESCAPE_NEWLINE + "\r", ESCAPE_NEWLINE + "\n"}); } boolean isSeparator(int c, char[] separators) { for (char p : separators) { if (c == p) return true; } return false; } private int parseFunction(TemplateReader in, Writer out, DataFormat fd) throws IOException, TemplateException { StringBuffer sb = new StringBuffer(); while (true) { if (termBracket >= 0 && matchInput(in, BRACKET_CLOSE[termBracket])) { throw new TemplateException( "Unexpected end of instruction", in); } int c = in.read(); if (c < 0) throw new TemplateException("Unexpected end of file.", in); if (!fd.invoke) out.write(c); if (isSeparator(c, LIST_SEPARATORS)) { return invoke( fd.invoke ? sb.toString() : "true", fd.invoke ? DataFormat.SUBFUNCTION : DataFormat.SUBFUNCTION_NOINVOKE, in, out); } else if (isSeparator(c, INVOKE_DATA_SEPARATORS)) { skipEscaped(in); return invoke( fd.invoke ? sb.toString() : "true", fd.invoke ? DataFormat.INVOKE : DataFormat.NOINVOKE, in, out); } else if (isSeparator(c, VERBATIM_DATA_SEPARATORS)) { return invoke( fd.invoke ? sb.toString() : "true", DataFormat.VERBATIM, in, out); } else if (isSeparator(c, NOINVOKE_DATA_SEPARATORS)) { skipEscaped(in); return invoke( fd.invoke ? sb.toString() : "true", DataFormat.NOINVOKE, in, out); } else { sb.append((char)c); } } } public int parseValue(Object value, Writer out) throws IOException { if (value == null) return 0; String svalue = value.toString(); if (svalue == null || svalue.length() == 0) return 0; out.write(svalue); return 1; } private int invoke(String name, DataFormat fd, TemplateReader in, Writer out) throws IOException, TemplateException { FunctionDataParser fdp = new FunctionDataParser(this, fd, in, name); int r = invoke(name, fdp, out); if (fdp.hasMoreData()) { /* Skip remaining function data. */ fdp.skipData(); } return r; } public int invoke(String name, FunctionDataParser fdp, Writer out) throws IOException, TemplateException { Object value = context.get(name); if (value instanceof Function) { return ((Function) value).invoke(fdp, out); } if (value instanceof Context) { String code = fdp.getData(); if (fdp.getLastReturnCode() == 0) { return 0; } TemplateParser parser = new TemplateParser((Context) value); return parser.parse( new TemplateReader(new StringReader(code), fdp.getTemplateReader()), out); } return parseValue(value, out); } public int getLastReturnCode() { return lastReturnCode; } public Context getContext() { return context; } }