001    /*
002     * $Id: SimpleTemplateEngine.java,v 1.14 2005/05/12 09:31:48 cstein Exp $version Mar 8, 2004 2:11:00 AM $user 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    /**
054     * This simple template engine uses JSP <% %> script and <%= %> expression syntax.  It also lets you use normal groovy expressions in
055     * 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.
056     * 
057     * @author sam
058     */
059    public class SimpleTemplateEngine extends TemplateEngine {
060    
061        /* (non-Javadoc)
062         * @see groovy.util.TemplateEngine#createTemplate(java.io.Reader)
063         */
064        public Template createTemplate(Reader reader) throws CompilationFailedException, IOException {
065            SimpleTemplate template = new SimpleTemplate();
066            GroovyShell shell = new GroovyShell();
067            String script = template.parse(reader);
068            template.script = shell.parse(script);
069            return template;
070        }
071            
072        private static class SimpleTemplate implements Template {
073            
074            protected Script script;
075    
076            public Writable make() {
077                return make(null);
078            }
079    
080            public Writable make(final Map map) {
081                return new Writable() {
082                    /**
083                     * Write the template document with the set binding applied to the writer.
084                     *
085                     * @see groovy.lang.Writable#writeTo(java.io.Writer)
086                     */
087                    public Writer writeTo(Writer writer) {
088                        Binding binding;
089                        if (map == null) binding = new Binding(); else binding = new Binding(map);
090                        Script scriptObject = InvokerHelper.createScript(script.getClass(), binding);
091                        PrintWriter pw = new PrintWriter(writer);
092                        scriptObject.setProperty("out", pw);
093                        scriptObject.run();
094                        pw.flush();
095                        return writer;
096                    }
097    
098                    /**
099                     * Convert the template and binding into a result String.
100                     *
101                     * @see java.lang.Object#toString()
102                     */
103                    public String toString() {
104                        try {
105                            StringWriter sw = new StringWriter();
106                            writeTo(sw);
107                            return sw.toString();
108                        } catch (Exception e) {
109                            return e.toString();
110                        }
111                    }
112                };
113            }
114    
115            /**
116             * Parse the text document looking for <% or <%= and then call out to the appropriate handler, otherwise copy the text directly
117             * into the script while escaping quotes.
118             * 
119             * @param reader
120             * @return
121             * @throws IOException
122             */
123            protected String parse(Reader reader) throws IOException {
124                if (!reader.markSupported()) {
125                    reader = new BufferedReader(reader);
126                }
127                StringWriter sw = new StringWriter();
128                startScript(sw);
129                boolean start = false;
130                int c;
131                while((c = reader.read()) != -1) {
132                    if (c == '<') {
133                        reader.mark(1);
134                        c = reader.read();
135                        if (c != '%') {
136                            sw.write('<');
137                            reader.reset();
138                        } else {
139                            reader.mark(1);
140                            c = reader.read();
141                            if (c == '=') {
142                                groovyExpression(reader, sw);
143                            } else {
144                                reader.reset();
145                                groovySection(reader, sw);
146                            }
147                        }
148                        continue; // at least '<' is consumed ... read next chars.
149                    }
150                    if (c == '\"') {
151                        sw.write('\\');
152                    }
153                    sw.write(c);
154                }
155                endScript(sw);
156                String result = sw.toString();
157                //System.out.println( "source text:\n" + result );
158                return result;
159            }
160    
161            private void startScript(StringWriter sw) {
162                sw.write("/* Generated by SimpleTemplateEngine */ ");
163                sw.write("out.print(\"");
164            }
165    
166            private void endScript(StringWriter sw) {
167                sw.write("\");\n");
168            }
169    
170            /**
171             * Closes the currently open write and writes out the following text as a GString expression until it reaches an end %>.
172             * 
173             * @param reader
174             * @param sw
175             * @throws IOException
176             */
177            private void groovyExpression(Reader reader, StringWriter sw) throws IOException {
178                sw.write("\");out.print(\"${");
179                int c;
180                while((c = reader.read()) != -1) {
181                    if (c == '%') {
182                        c = reader.read();
183                        if (c != '>') {
184                            sw.write('%');
185                        } else {
186                            break;
187                        }
188                    }
189                    sw.write(c);
190                }
191                sw.write("}\");out.print(\"");
192            }
193    
194            /**
195             * Closes the currently open write and writes the following text as normal Groovy script code until it reaches an end %>.
196             * 
197             * @param reader
198             * @param sw
199             * @throws IOException
200             */
201            private void groovySection(Reader reader, StringWriter sw) throws IOException {
202                sw.write("\");");
203                int c;
204                while((c = reader.read()) != -1) {
205                    if (c == '%') {
206                        c = reader.read();
207                        if (c != '>') {
208                            sw.write('%');
209                        } else {
210                            break;
211                        }
212                    }
213                    sw.write(c);
214                }
215                sw.write(";out.print(\"");
216            }
217    
218        }
219    }