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    }