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 }