preliminary experiments with a wiki tag
[org.ibex.xt.git] / src / org / ibex / xt / Template.java
1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package org.ibex.xt;
6 import org.ibex.js.*;
7 import org.ibex.util.*;
8 import org.ibex.io.*;
9 import java.io.*;
10 import java.net.*;
11 import java.util.*;
12 import javax.servlet.*;
13 import javax.servlet.http.*;
14
15 public class Template extends Node.Stream.Filter implements Node.Stream.Functor {
16
17     static Template newTemplate(Servlet.ServletScope servletscope, Scope scope, String str) {
18         try {
19             File f = new File(str);
20             if (!f.exists()) f = new File(str + ".xt");
21             if (!f.exists()) f = new File(str + ".xml");
22             return new Template(servletscope, scope, new InputStreamReader(new FileInputStream(f)));
23         } catch (Exception e) { throw new RuntimeException(e); }
24     }
25
26     static Scope copyNodeToScope(Node n, Scope scope) {
27         try {
28             for(int i=0; i<n.numattrs; i++) {
29                 scope.declare(n.attrs[i*2]);
30                 scope.put(JSU.S(n.attrs[i*2]), JSU.S(n.attrs[i*2+1]));
31             }
32             return scope;
33         } catch (Exception e) { throw new RuntimeException(e); }
34     }
35
36     private class JSRewriter extends Node.Stream {
37         private Node.Stream in;
38         private Scope scope;
39         public JSRewriter(Node.Stream in, Scope scope) { this.in = in; this.scope = scope; }
40         protected boolean _read(Node n) { if (!in.read(n)) return false; transform(n, scope); return true; }
41     }
42
43     public static Node transform(Node n, Scope scope) {
44         try {
45             if (n.cdata != null) n.cdata = eval(n.cdata, scope).toString();
46             else for(int i=1; i<n.numattrs*2; i+=2) n.attrs[i] = eval(n.attrs[i], scope).toString();
47             return n;
48         } catch (JSExn e) { throw new RuntimeException(e); }
49     }
50
51     private static Object eval(String s, Scope scope) throws JSExn {
52         if (s == null) return null;
53         StringBuffer ret = new StringBuffer();
54         for(boolean first = true; s.indexOf("${") != -1; first = false) {
55             ret.append(s.substring(0, s.indexOf("${")));
56             String s2 = s.substring(s.indexOf("${")+2);
57             JS app = exec("return (" + s2.substring(0, s2.indexOf('}')) + ");\n", scope);
58             s = s.substring(s.indexOf('}') + 1);
59             //if (first && s.trim().length() == 0) return app;
60             if (!(app == null || app instanceof JSPrimitive))
61                 throw new RuntimeException("javascripts within ${...} can only return strings, numbers, and booleans; not a " +
62                                            app.getClass().getName());
63             ret.append(app == null ? "null" : JSU.toString(app));
64         }
65         ret.append(s);
66         return ret.toString();
67     }
68
69     public static JS exec(String s, Scope scope) {
70         try {
71             return JSU.cloneWithNewGlobalScope(JSU.fromReader("input", 0, new StringReader(s)), scope).call(null,null);
72         } catch (Exception e) {
73             e.printStackTrace();
74             throw new RuntimeException(e);
75         }
76     }
77
78     private Scope scope;
79     private Servlet.ServletScope servletscope;
80     private Node.Stream children;
81     public Node.Stream wrap(Node.Stream children) { this.children = children; return this; }
82     public Template(Servlet.ServletScope servletscope, JS scope, Reader template) {
83         super(new Node.Stream.FromXML(template));
84         this.scope = new Scope(scope);
85         this.servletscope = servletscope;
86     }
87     public boolean _read(Node n) { boolean ret = __read(n); if (ret) transform(n, scope); return ret; }
88     public boolean __read(final Node n) {
89         if (!upstreamRead(n)) return false;
90         if (n.cdata != null) return true;
91         String uri = n.uri;
92         if (uri == null) uri = "http://www.w3.org/1999/xhtml";  // FIXME FIXME FIXME!!!!
93         final String name = n.name;
94         if (uri.indexOf(':') == -1)
95             throw new RuntimeException("uri does not contain a colon: " + uri + " (tag name " + name + ")");
96         final String method = uri.substring(0, uri.indexOf(':'));
97         final String rest = uri.substring(uri.indexOf(':')+1);
98         if (uri.equals("http://www.w3.org/1999/xhtml")) { return true;
99         } else if (method.equals("webinf")) {
100             return graft(newTemplate(servletscope, copyNodeToScope(transform(n, scope), new Scope(servletscope)),
101                                      servletscope.getRealPath("/") + "/WEB-INF/" + rest + name), n).upstreamRead(n);
102         } else if (uri.equals("http://xt.ibex.org/form")) {
103             return graft(new InputTag(n.name), n).upstreamRead(n);
104         } else if (uri.equals("http://xt.ibex.org/")) {
105             //#switch(name)
106             case "form":  return graft(new FormTag(n.attr("class")), n).upstreamRead(n);
107             case "input": return graft(new InputTag(n.attr("field")), n).upstreamRead(n);
108             case "if":       
109                 transform(n, scope);
110                 return graft((Node.Stream.Functor)("true".equals(n.attr("if"))?new DropTag():new DropAll()), n).upstreamRead(n);
111             case "js":       return graft(new JsTag(scope), n).upstreamRead(n);
112             case "wiki":     return graft(new WikiTag(n, scope), n).upstreamRead(n);
113             case "foreach":  return graft(new ForEach(n, scope), n).upstreamRead(n);
114             case "children":
115                 if (children == null) return true;
116                 graft(new Node.Stream.ConstantFunctor(children), n);
117                 children = null;
118                 return upstreamRead(n);
119                 //#end
120                 return true;
121         } else if (method.equals("java")) {
122             try { return graft((Node.Stream.Functor)Class.forName(rest).newInstance(), n).upstreamRead(n); }
123             catch (Exception e) { throw new RuntimeException(e); }
124         }
125         throw new RuntimeException("Unknown namespace URI " + uri);    
126     }
127
128     private class ForEach extends Node.Stream.Filter implements Node.Stream.Functor {
129         private Node[] nodes = null;
130         private Vec array = new Vec();
131         private Scope scope;
132         public ForEach(Node n, Scope s) {
133             super(Node.Stream.NULL);
134             try {
135                 JSArray a = ((JSArray)exec("return (" + n.attr("in").toString() + ");", this.scope = s));
136                 while(true) {
137                     JS o = a.call(JSU.S("pop"), new JS[] { });
138                     System.out.println("called pop on a "+a.getClass().getName()+" of length " +a.size()+" , got " + (o==null ? null : o.getClass().getName()));
139                     if (o == null) break;
140                     array.push(o);
141                 }
142             } catch (JSExn e) {
143                 throw new RuntimeException(e);
144             }
145         }
146         public Node.Stream wrap(Node.Stream kids) {
147             Vec nodes = new Vec();
148             Node n2 = new Node();
149             while(kids.read(n2)) nodes.addElement(new Node(n2));
150             nodes.copyInto(this.nodes = new Node[nodes.size()]);
151             return this;
152         }
153         protected boolean _read(Node n) {
154             if (upstreamRead(n)) return true;
155             if (array.size() == 0) return false;
156             Scope scope2 = new Scope(scope);
157             try { scope2.declare("x"); scope2.put(JSU.S("x"), (JS)array.pop()); } catch (JSExn e) { throw new RuntimeException(e); }
158             return graft(new ConstantFunctor(new JSRewriter(new Node.Stream() {
159                     private int i = 0;
160                     protected boolean _read(Node n) {
161                         if (i>=nodes.length) return false;
162                         n.copyFrom(nodes[i++]);
163                         return true;
164                     } }, scope2)), n).upstreamRead(n);
165         }
166     }
167
168     private class WikiTag implements Node.Stream.Functor {
169         public WikiTag(Node n, Scope s) { }
170         public Node.Stream wrap(final Node.Stream kids) {
171             return new Node.Stream() {
172                     public boolean _read(Node n) {
173                         if (!kids.read(n)) return false;
174                         System.out.println("kids.cdata == " + n.cdata);
175                         if (n.cdata == null) return true;
176
177                         // FIXME: links, code, and math
178                         n.cdata = n.cdata.replaceAll("__([^_]+)__", "<u>$1</u>");
179                         n.cdata = n.cdata.replaceAll("\\~\\~([^\\~]+)\\~\\~", "<i>$1</i>");
180                         n.cdata = n.cdata.replaceAll("\\*\\*([^\\*]+)\\*\\*", "<b>$1</b>");
181                         n.cdata = n.cdata.replaceAll("==([^=]+)==", "<h2>$1</h2>");
182                         n.cdata = n.cdata.replaceAll("=<h2>([^=]+)</h2>=", "<h3>$1</h3>");
183                         n.cdata = n.cdata.replaceAll("=<h3>([^=]+)</h3>=", "<h4>$1</h4>");
184                         n.cdata = n.cdata.replaceAll("(\n|\r\n)[ ]*(\n|\r\n)", "\n<br/><br/>\n");
185                         return true;
186                     } };
187         }
188     }
189
190     private class InputTag implements Node.Stream.Functor {
191         final String fieldName;
192         public InputTag(String fieldName) { this.fieldName = fieldName; }
193         public Node.Stream wrap(Node.Stream kids) {
194             try {
195                 return new Node.Stream.FromXML(new StringReader(servletscope.currentForm.emit(fieldName)));
196             } catch (Exception e) { Log.warn(this, e); return null; }
197         }
198     }
199
200     private class FormTag implements Node.Stream.Functor {
201         final String classname;
202         public FormTag(String classname) { this.classname = classname; }
203         public Node.Stream wrap(final Node.Stream kids) {
204             try {
205                 servletscope.currentForm = (Form)Class.forName(classname).newInstance();
206                 Log.warn("emit", servletscope.currentForm.emit());
207                 return new Node.Stream() {
208                         boolean done = false;
209                         public boolean _read(Node n) {
210                             if (done) return kids._read(n);
211                             done = true;
212                             n.clear();
213                             n.name = "form";
214                             n.uri = "http://www.w3.org/1999/xhtml";
215                             n.numattrs = 1;
216                             n.attrs = new String[] { "action", "/servlet/"+classname };
217                             return true;
218                         }
219                     };
220             } catch (Exception e) {
221                 Log.warn(this, e);
222                 return null;
223             }
224         } }
225  
226     private class DropTag implements Node.Stream.Functor {
227         public Node.Stream wrap(Node.Stream kids) {
228             return kids;
229         } }
230
231     private class DropAll implements Node.Stream.Functor {
232         public Node.Stream wrap(Node.Stream kids) {
233             return new Node.Stream() { public boolean _read(Node n) { return false; } };
234         } }
235
236     private class JsTag implements Node.Stream.Functor {
237         Scope scope;
238         public JsTag(Scope scope) { this.scope = scope; }
239         public Node.Stream wrap(final Node.Stream s) {
240             return new Node.Stream() {
241                     protected boolean _read(Node n) {
242                         boolean ret = s.read(n);
243                         if (ret && n.cdata != null) {
244                             System.err.println("exec("+n.cdata+")");
245                             exec(n.cdata, scope);
246                             return _read(n);
247                         }
248                         return ret;
249                     }
250                 };
251         }
252     }
253 }