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 }