001    package groovy.text;
002    
003    import groovy.lang.Binding;
004    import groovy.lang.GroovyShell;
005    import groovy.lang.Script;
006    import groovy.lang.Writable;
007    import groovy.util.IndentPrinter;
008    import groovy.util.Node;
009    import groovy.util.XmlNodePrinter;
010    import groovy.util.XmlParser;
011    import groovy.xml.QName;
012    
013    import java.io.IOException;
014    import java.io.PrintWriter;
015    import java.io.Reader;
016    import java.io.StringWriter;
017    import java.io.Writer;
018    import java.lang.ref.WeakReference;
019    import java.util.HashMap;
020    import java.util.Map;
021    
022    import javax.xml.parsers.ParserConfigurationException;
023    
024    import org.codehaus.groovy.control.CompilationFailedException;
025    import org.codehaus.groovy.runtime.InvokerHelper;
026    import org.xml.sax.SAXException;
027    
028    /**
029     * Template engine for xml data input.
030     *
031     * @author Christian Stein
032     */
033    public class XmlTemplateEngine extends TemplateEngine {
034    
035        private static class GspPrinter extends XmlNodePrinter {
036    
037            public GspPrinter(PrintWriter out, String indent) {
038                this(new IndentPrinter(out, indent));
039            }
040    
041            public GspPrinter(IndentPrinter out) {
042                super(out, "\\\"");
043            }
044    
045            protected void printGroovyTag(String tag, String text) {
046                if (tag.equals("scriptlet")) {
047                    out.print(text);
048                    out.print("\n");
049                    return;
050                }
051                if (tag.equals("expression")) {
052                    printLineBegin();
053                    out.print("${");
054                    out.print(text);
055                    out.print("}");
056                    printLineEnd();
057                    return;
058                }
059                throw new RuntimeException("Unsupported tag named \"" + tag + "\".");
060            }
061    
062            protected void printLineBegin() {
063                out.print("out.print(\"");
064                out.printIndent();
065            }
066    
067            protected void printLineEnd(String comment) {
068                out.print("\\n\");");
069                if (comment != null) {
070                    out.print(" // ");
071                    out.print(comment);
072                }
073                out.print("\n");
074            }
075    
076            protected boolean printSpecialNode(Node node) {
077                Object name = node.name();
078                if (name != null && name instanceof QName) {
079                    /*
080                     * FIXME Somethings wrong with the SAX- or XMLParser. Prefix should only contain 'gsp'?!
081                     */
082                    String s = ((QName) name).getPrefix();
083                    if (s.startsWith("gsp:")) {
084                        s = s.substring(4); // 4 = "gsp:".length()
085                        if (s.length() == 0) {
086                            throw new RuntimeException("No local part after 'gsp:' given in node " + node);
087                        }
088                        printGroovyTag(s, node.text());
089                        return true;
090                    }
091                }
092                return false;
093            }
094    
095        }
096    
097        private static class XmlTemplate implements Template {
098    
099            private final Script script;
100    
101            public XmlTemplate(Script script) {
102                this.script = script;
103            }
104    
105            public Writable make() {
106                return make(new HashMap());
107            }
108    
109            public Writable make(Map map) {
110                if (map == null) {
111                    throw new IllegalArgumentException("map must not be null");
112                }
113                return new XmlWritable(script, new Binding(map));
114            }
115    
116        }
117    
118        private static class XmlWritable implements Writable {
119    
120            private final Binding binding;
121            private final Script script;
122            private WeakReference result;
123    
124            public XmlWritable(Script script, Binding binding) {
125                this.script = script;
126                this.binding = binding;
127                this.result = new WeakReference(null);
128            }
129    
130            public Writer writeTo(Writer out) {
131                Script scriptObject = InvokerHelper.createScript(script.getClass(), binding);
132                PrintWriter pw = new PrintWriter(out);
133                scriptObject.setProperty("out", pw);
134                scriptObject.run();
135                pw.flush();
136                return out;
137            }
138    
139            public String toString() {
140                if (result.get() != null) {
141                    return result.get().toString();
142                }
143                String string = writeTo(new StringWriter(1024)).toString();
144                result = new WeakReference(string);
145                return string;
146            }
147    
148        }
149    
150        public static final String DEFAULT_INDENTION = "  ";
151    
152        private final GroovyShell groovyShell;
153        private final XmlParser xmlParser;
154        private String indention;
155    
156        public XmlTemplateEngine() throws SAXException, ParserConfigurationException {
157            this(DEFAULT_INDENTION, false);
158        }
159    
160        public XmlTemplateEngine(String indention, boolean validating) throws SAXException, ParserConfigurationException {
161            this(new XmlParser(validating, true), new GroovyShell(), indention);
162        }
163    
164        public XmlTemplateEngine(XmlParser xmlParser, GroovyShell groovyShell, String indention) {
165            this.groovyShell = groovyShell;
166            this.xmlParser = xmlParser;
167            this.indention = indention;
168        }
169    
170        public Template createTemplate(Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
171            Node root = null;
172            try {
173                root = xmlParser.parse(reader);
174            } catch (SAXException e) {
175                throw new RuntimeException("Parsing XML source failed.", e);
176            }
177    
178            if (root == null) {
179                throw new IOException("Parsing XML source failed: root node is null.");
180            }
181    
182            // new NodePrinter().print(root);
183            // new XmlNodePrinter().print(root);
184    
185            StringWriter writer = new StringWriter(1024);
186            writer.write("/* Generated by XmlTemplateEngine */\n");
187            new GspPrinter(new PrintWriter(writer), DEFAULT_INDENTION).print(root);
188            String scriptText = writer.toString();
189    
190            // System.err.println("\n-\n" + scriptText + "\n-\n");
191    
192            Script script = groovyShell.parse(scriptText);
193            Template template = new XmlTemplate(script);
194            return template;
195        }
196    
197        public String getIndention() {
198            return indention;
199        }
200    
201        public void setIndention(String indention) {
202            if (indention == null) {
203                indention = DEFAULT_INDENTION;
204            }
205            this.indention = indention;
206        }
207    
208        public String toString() {
209            return "XmlTemplateEngine";
210        }
211    
212    }