first alpha release
[org.ibex.xt.git] / src / org / ibex / xt / Template.java
1 package org.ibex.xt;
2 import org.ibex.js.*;
3 import org.ibex.util.*;
4 import org.ibex.io.*;
5 import java.io.*;
6 import java.net.*;
7 import java.util.*;
8 import javax.servlet.*;
9 import javax.servlet.http.*;
10
11 public class Template extends Node.Stream.Filter implements Node.Stream.Functor {
12
13     static Template newTemplate(Servlet.ServletScope servletscope, JSScope scope, String str) {
14         try {
15             File f = new File(str);
16             if (!f.exists()) f = new File(str + ".xt");
17             if (!f.exists()) f = new File(str + ".xml");
18             return new Template(servletscope, scope, new InputStreamReader(new FileInputStream(f)));
19         } catch (Exception e) { throw new RuntimeException(e); }
20     }
21
22     static JSScope copyNodeToScope(Node n, JSScope scope) {
23         try {
24             for(int i=0; i<n.numattrs; i++) {
25                 scope.declare(n.attrs[i*2]);
26                 scope.put(n.attrs[i*2], n.attrs[i*2+1]);
27             }
28             return scope;
29         } catch (Exception e) { throw new RuntimeException(e); }
30     }
31
32     private class JSRewriter extends Node.Stream {
33         private Node.Stream in;
34         private JSScope scope;
35         public JSRewriter(Node.Stream in, JSScope scope) { this.in = in; this.scope = scope; }
36         protected boolean _read(Node n) { if (!in.read(n)) return false; transform(n, scope); return true; }
37     }
38
39     public static Node transform(Node n, JSScope scope) {
40         if (n.cdata != null) n.cdata = eval(n.cdata, scope).toString();
41         else for(int i=1; i<n.numattrs*2; i+=2) n.attrs[i] = eval(n.attrs[i], scope).toString();
42         return n;
43     }
44
45     private static Object eval(String s, JSScope scope) {
46         if (s == null) return null;
47         StringBuffer ret = new StringBuffer();
48         for(boolean first = true; s.indexOf("${") != -1; first = false) {
49             ret.append(s.substring(0, s.indexOf("${")));
50             String s2 = s.substring(s.indexOf("${")+2);
51             Object app = exec("return (" + s2.substring(0, s2.indexOf('}')) + ");\n", scope);
52             s = s.substring(s.indexOf('}') + 1);
53             //if (first && s.trim().length() == 0) return app;
54             if (!(app == null || app instanceof String || app instanceof Number || app instanceof Boolean))
55                 throw new RuntimeException("javascripts within ${...} can only return strings, numbers, and booleans; not a " +
56                                            app.getClass().getName());
57             ret.append(app == null ? "null" : app.toString());
58         }
59         ret.append(s);
60         return ret.toString();
61     }
62
63     public static Object exec(String s, JSScope scope) {
64         try {
65             return JS.eval(JS.cloneWithNewParentScope(JS.fromReader("input", 0, new StringReader(s)), scope));
66         } catch (Exception e) {
67             e.printStackTrace();
68             throw new RuntimeException(e);
69         }
70     }
71
72     private JSScope scope;
73     private Servlet.ServletScope servletscope;
74     private Node.Stream children;
75     public Node.Stream wrap(Node.Stream children) { this.children = children; return this; }
76     public Template(Servlet.ServletScope servletscope, JSScope scope, Reader template) {
77         super(new Node.Stream.FromXML(template));
78         this.scope = scope;
79         this.servletscope = servletscope;
80     }
81     public boolean _read(Node n) { boolean ret = __read(n); if (ret) transform(n, scope); return ret; }
82     public boolean __read(final Node n) {
83         if (!upstreamRead(n)) return false;
84         if (n.cdata != null) return true;
85         final String uri = n.uri;
86         final String name = n.name;
87         if (uri.indexOf(':') == -1)
88             throw new RuntimeException("uri does not contain a colon: " + uri + " (tag name " + name + ")");
89         final String method = uri.substring(0, uri.indexOf(':'));
90         final String rest = uri.substring(uri.indexOf(':')+1);
91         if (uri.equals("http://www.w3.org/1999/xhtml")) { return true;
92         } else if (method.equals("webinf")) {
93             return graft(newTemplate(servletscope, copyNodeToScope(transform(n, scope), new JSScope(servletscope)),
94                                      servletscope.getRealPath("/") + "/WEB-INF/" + rest + name), n).upstreamRead(n);
95         } else if (uri.equals("http://xt.ibex.org/")) {
96             //#switch(name)
97             case "if":       
98                 transform(n, scope);
99                 return graft("true".equals(n.attr("if")) ? new DropTag() : new DropAll(), n).upstreamRead(n);
100             case "js":       return graft(new JsTag(scope), n).upstreamRead(n);
101             case "foreach":  return graft(new ForEach(n, scope), n).upstreamRead(n);
102             case "children":
103                 if (children == null) return true;
104                 graft(new Node.Stream.ConstantFunctor(children), n);
105                 children = null;
106                 return upstreamRead(n);
107                 //#end
108                 return true;
109         } else if (method.equals("java")) {
110             try { return graft((Node.Stream.Functor)Class.forName(rest).newInstance(), n).upstreamRead(n); }
111             catch (Exception e) { throw new RuntimeException(e); }
112         }
113         throw new RuntimeException("Unknown namespace URI " + uri);    
114     }
115
116     private class ForEach extends Node.Stream.Filter implements Node.Stream.Functor {
117         private Node[] nodes = null;
118         private Vec array = new Vec();
119         private JSScope scope;
120         public ForEach(Node n, JSScope s) {
121             super(Node.Stream.NULL);
122             Vec v = ((JSArray)exec("return (" + n.attr("in").toString() + ");", this.scope = s)).toVec();
123             while(true) { Object o = v.pop(); if (o == null) break; array.push(o); }
124         }
125         public Node.Stream wrap(Node.Stream kids) {
126             Vec nodes = new Vec();
127             Node n2 = new Node();
128             while(kids.read(n2)) nodes.addElement(new Node(n2));
129             nodes.copyInto(this.nodes = new Node[nodes.size()]);
130             return this;
131         }
132         protected boolean _read(Node n) {
133             if (upstreamRead(n)) return true;
134             if (array.size() == 0) return false;
135             JSScope scope2 = new JSScope(scope);
136             try { scope2.declare("x"); scope2.put("x", array.pop()); } catch (JSExn e) { throw new RuntimeException(e); }
137             return graft(new ConstantFunctor(new JSRewriter(new Node.Stream() {
138                     private int i = 0;
139                     protected boolean _read(Node n) {
140                         if (i>=nodes.length) return false;
141                         n.copyFrom(nodes[i++]);
142                         return true;
143                     } }, scope2)), n).upstreamRead(n);
144         }
145     }
146
147     private class DropTag implements Node.Stream.Functor {
148         public Node.Stream wrap(Node.Stream kids) {
149             return kids;
150         } }
151
152     private class DropAll implements Node.Stream.Functor {
153         public Node.Stream wrap(Node.Stream kids) {
154             return new Node.Stream() { public boolean _read(Node n) { return false; } };
155         } }
156
157     private class JsTag implements Node.Stream.Functor {
158         JSScope scope;
159         public JsTag(JSScope scope) { this.scope = scope; }
160         public Node.Stream wrap(final Node.Stream s) {
161             return new Node.Stream() {
162                     protected boolean _read(Node n) {
163                         boolean ret = s.read(n);
164                         if (ret && n.cdata != null) {
165                             System.err.println("exec("+n.cdata+")");
166                             exec(n.cdata, scope);
167                             return _read(n);
168                         }
169                         return ret;
170                     }
171                 };
172         }
173     }
174 }