001 /* 002 * $Id: SimpleTemplateEngine.java 4032 2006-08-30 07:18:49Z mguillem $ 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 * @throws IOException 136 */ 137 protected String parse(Reader reader) throws IOException { 138 if (!reader.markSupported()) { 139 reader = new BufferedReader(reader); 140 } 141 StringWriter sw = new StringWriter(); 142 startScript(sw); 143 boolean start = false; 144 int c; 145 while ((c = reader.read()) != -1) { 146 if (c == '<') { 147 reader.mark(1); 148 c = reader.read(); 149 if (c != '%') { 150 sw.write('<'); 151 reader.reset(); 152 } else { 153 reader.mark(1); 154 c = reader.read(); 155 if (c == '=') { 156 groovyExpression(reader, sw); 157 } else { 158 reader.reset(); 159 groovySection(reader, sw); 160 } 161 } 162 continue; // at least '<' is consumed ... read next chars. 163 } 164 if (c == '\"') { 165 sw.write('\\'); 166 } 167 /* 168 * Handle raw new line characters. 169 */ 170 if (c == '\n' || c == '\r') { 171 if (c == '\r') { // on Windows, "\r\n" is a new line. 172 reader.mark(1); 173 c = reader.read(); 174 if (c != '\n') { 175 reader.reset(); 176 } 177 } 178 sw.write("\\n\");\nout.print(\""); 179 continue; 180 } 181 sw.write(c); 182 } 183 endScript(sw); 184 String result = sw.toString(); 185 return result; 186 } 187 188 private void startScript(StringWriter sw) { 189 sw.write("/* Generated by SimpleTemplateEngine */\n"); 190 sw.write("out.print(\""); 191 } 192 193 private void endScript(StringWriter sw) { 194 sw.write("\");\n"); 195 } 196 197 /** 198 * Closes the currently open write and writes out the following text as a GString expression until it reaches an end %>. 199 * 200 * @param reader 201 * @param sw 202 * @throws IOException 203 */ 204 private void groovyExpression(Reader reader, StringWriter sw) throws IOException { 205 sw.write("\");out.print(\"${"); 206 int c; 207 while ((c = reader.read()) != -1) { 208 if (c == '%') { 209 c = reader.read(); 210 if (c != '>') { 211 sw.write('%'); 212 } else { 213 break; 214 } 215 } 216 if (c != '\n' && c != '\r') { 217 sw.write(c); 218 } 219 } 220 sw.write("}\");\nout.print(\""); 221 } 222 223 /** 224 * Closes the currently open write and writes the following text as normal Groovy script code until it reaches an end %>. 225 * 226 * @param reader 227 * @param sw 228 * @throws IOException 229 */ 230 private void groovySection(Reader reader, StringWriter sw) throws IOException { 231 sw.write("\");"); 232 int c; 233 while ((c = reader.read()) != -1) { 234 if (c == '%') { 235 c = reader.read(); 236 if (c != '>') { 237 sw.write('%'); 238 } else { 239 break; 240 } 241 } 242 /* Don't eat EOL chars in sections - as they are valid instruction separators. 243 * See http://jira.codehaus.org/browse/GROOVY-980 244 */ 245 // if (c != '\n' && c != '\r') { 246 sw.write(c); 247 //} 248 } 249 sw.write(";\nout.print(\""); 250 } 251 252 } 253 }