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 }