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    }