001 /*
002 * $Id: SimpleTemplateEngine.java,v 1.17 2005/07/22 09:37:33 cstein Exp $
003 *
004 * Copyright 2003 (C) Sam Pullara. All Rights Reserved.
005 *
006 * Redistribution and use of this software and associated documentation
007 * ("Software"), with or without modification, are permitted provided that the
008 * following conditions are met: 1. Redistributions of source code must retain
009 * copyright statements and notices. Redistributions must also contain a copy
010 * of this document. 2. Redistributions in binary form must reproduce the above
011 * copyright notice, this list of conditions and the following disclaimer in
012 * the documentation and/or other materials provided with the distribution. 3.
013 * The name "groovy" must not be used to endorse or promote products derived
014 * from this Software without prior written permission of The Codehaus. For
015 * written permission, please contact info@codehaus.org. 4. Products derived
016 * from this Software may not be called "groovy" nor may "groovy" appear in
017 * their names without prior written permission of The Codehaus. "groovy" is a
018 * registered trademark of The Codehaus. 5. Due credit should be given to The
019 * Codehaus - http://groovy.codehaus.org/
020 *
021 * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
022 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
024 * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
025 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
027 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
030 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
031 * DAMAGE.
032 *
033 */
034 package groovy.text;
035
036 import groovy.lang.Binding;
037 import groovy.lang.GroovyShell;
038 import groovy.lang.Script;
039 import groovy.lang.Writable;
040
041 import java.io.BufferedReader;
042 import java.io.IOException;
043 import java.io.PrintWriter;
044 import java.io.Reader;
045 import java.io.StringWriter;
046 import java.io.Writer;
047 import java.util.Map;
048
049 import org.codehaus.groovy.control.CompilationFailedException;
050 import org.codehaus.groovy.runtime.InvokerHelper;
051
052 /**
053 * This simple template engine uses JSP <% %> script and <%= %> expression syntax. It also lets you use normal groovy expressions in
054 * the template text much like the new JSP EL functionality. The variable 'out' is bound to the writer that the template is being written to.
055 *
056 * @author sam
057 * @author Christian Stein
058 */
059 public class SimpleTemplateEngine extends TemplateEngine {
060
061 private final boolean verbose;
062
063 public SimpleTemplateEngine() {
064 this(false);
065 }
066
067 public SimpleTemplateEngine(boolean verbose) {
068 this.verbose = verbose;
069 }
070
071 public Template createTemplate(Reader reader) throws CompilationFailedException, IOException {
072 SimpleTemplate template = new SimpleTemplate();
073 GroovyShell shell = new GroovyShell();
074 String script = template.parse(reader);
075 if (verbose) {
076 System.out.println("\n-- script source --");
077 System.out.print(script);
078 System.out.println("\n-- script end --\n");
079 }
080 template.script = shell.parse(script);
081 return template;
082 }
083
084 private static class SimpleTemplate implements Template {
085
086 protected Script script;
087
088 public Writable make() {
089 return make(null);
090 }
091
092 public Writable make(final Map map) {
093 return new Writable() {
094 /**
095 * Write the template document with the set binding applied to the writer.
096 *
097 * @see groovy.lang.Writable#writeTo(java.io.Writer)
098 */
099 public Writer writeTo(Writer writer) {
100 Binding binding;
101 if (map == null)
102 binding = new Binding();
103 else
104 binding = new Binding(map);
105 Script scriptObject = InvokerHelper.createScript(script.getClass(), binding);
106 PrintWriter pw = new PrintWriter(writer);
107 scriptObject.setProperty("out", pw);
108 scriptObject.run();
109 pw.flush();
110 return writer;
111 }
112
113 /**
114 * Convert the template and binding into a result String.
115 *
116 * @see java.lang.Object#toString()
117 */
118 public String toString() {
119 try {
120 StringWriter sw = new StringWriter();
121 writeTo(sw);
122 return sw.toString();
123 } catch (Exception e) {
124 return e.toString();
125 }
126 }
127 };
128 }
129
130 /**
131 * Parse the text document looking for <% or <%= and then call out to the appropriate handler, otherwise copy the text directly
132 * into the script while escaping quotes.
133 *
134 * @param reader
135 * @return
136 * @throws IOException
137 */
138 protected String parse(Reader reader) throws IOException {
139 if (!reader.markSupported()) {
140 reader = new BufferedReader(reader);
141 }
142 StringWriter sw = new StringWriter();
143 startScript(sw);
144 boolean start = false;
145 int c;
146 while ((c = reader.read()) != -1) {
147 if (c == '<') {
148 reader.mark(1);
149 c = reader.read();
150 if (c != '%') {
151 sw.write('<');
152 reader.reset();
153 } else {
154 reader.mark(1);
155 c = reader.read();
156 if (c == '=') {
157 groovyExpression(reader, sw);
158 } else {
159 reader.reset();
160 groovySection(reader, sw);
161 }
162 }
163 continue; // at least '<' is consumed ... read next chars.
164 }
165 if (c == '\"') {
166 sw.write('\\');
167 }
168 /*
169 * Handle raw new line characters.
170 */
171 if (c == '\n' || c == '\r') {
172 if (c == '\r') { // on Windows, "\r\n" is a new line.
173 reader.mark(1);
174 c = reader.read();
175 if (c != '\n') {
176 reader.reset();
177 }
178 }
179 sw.write("\\n\");\nout.print(\"");
180 continue;
181 }
182 sw.write(c);
183 }
184 endScript(sw);
185 String result = sw.toString();
186 return result;
187 }
188
189 private void startScript(StringWriter sw) {
190 sw.write("/* Generated by SimpleTemplateEngine */\n");
191 sw.write("out.print(\"");
192 }
193
194 private void endScript(StringWriter sw) {
195 sw.write("\");\n");
196 }
197
198 /**
199 * Closes the currently open write and writes out the following text as a GString expression until it reaches an end %>.
200 *
201 * @param reader
202 * @param sw
203 * @throws IOException
204 */
205 private void groovyExpression(Reader reader, StringWriter sw) throws IOException {
206 sw.write("\");out.print(\"${");
207 int c;
208 while ((c = reader.read()) != -1) {
209 if (c == '%') {
210 c = reader.read();
211 if (c != '>') {
212 sw.write('%');
213 } else {
214 break;
215 }
216 }
217 if (c != '\n' && c != '\r') {
218 sw.write(c);
219 }
220 }
221 sw.write("}\");\nout.print(\"");
222 }
223
224 /**
225 * Closes the currently open write and writes the following text as normal Groovy script code until it reaches an end %>.
226 *
227 * @param reader
228 * @param sw
229 * @throws IOException
230 */
231 private void groovySection(Reader reader, StringWriter sw) throws IOException {
232 sw.write("\");");
233 int c;
234 while ((c = reader.read()) != -1) {
235 if (c == '%') {
236 c = reader.read();
237 if (c != '>') {
238 sw.write('%');
239 } else {
240 break;
241 }
242 }
243 /* Don't eat EOL chars in sections - as they are valid instruction separators.
244 * See http://jira.codehaus.org/browse/GROOVY-980
245 */
246 // if (c != '\n' && c != '\r') {
247 sw.write(c);
248 //}
249 }
250 sw.write(";\nout.print(\"");
251 }
252
253 }
254 }