updated org.ibex.xt for new JS
[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         if (n.cdata != null) n.cdata = eval(n.cdata, scope).toString();
45         else for(int i=1; i<n.numattrs*2; i+=2) n.attrs[i] = eval(n.attrs[i], scope).toString();
46         return n;
47     }
48
49     private static Object eval(String s, Scope scope) {
50         if (s == null) return null;
51         StringBuffer ret = new StringBuffer();
52         for(boolean first = true; s.indexOf("${") != -1; first = false) {
53             ret.append(s.substring(0, s.indexOf("${")));
54             String s2 = s.substring(s.indexOf("${")+2);
55             Object app = exec("return (" + s2.substring(0, s2.indexOf('}')) + ");\n", scope);
56             s = s.substring(s.indexOf('}') + 1);
57             //if (first && s.trim().length() == 0) return app;
58             if (!(app == null || app instanceof String || app instanceof Number || app instanceof Boolean))
59                 throw new RuntimeException("javascripts within ${...} can only return strings, numbers, and booleans; not a " +
60                                            app.getClass().getName());
61             ret.append(app == null ? "null" : app.toString());
62         }
63         ret.append(s);
64         return ret.toString();
65     }
66
67     public static Object exec(String s, Scope scope) {
68         try {
69             return JSU.cloneWithNewGlobalScope(JSU.fromReader("input", 0, new StringReader(s)), scope).call(null,null);
70         } catch (Exception e) {
71             e.printStackTrace();
72             throw new RuntimeException(e);
73         }
74     }
75
76     private Scope scope;
77     private Servlet.ServletScope servletscope;
78     private Node.Stream children;
79     public Node.Stream wrap(Node.Stream children) { this.children = children; return this; }
80     public Template(Servlet.ServletScope servletscope, JS scope, Reader template) {
81         super(new Node.Stream.FromXML(template));
82         this.scope = new Scope(scope);
83         this.servletscope = servletscope;
84     }
85     public boolean _read(Node n) { boolean ret = __read(n); if (ret) transform(n, scope); return ret; }
86     public boolean __read(final Node n) {
87         if (!upstreamRead(n)) return false;
88         if (n.cdata != null) return true;
89         final String uri = n.uri;
90         final String name = n.name;
91         if (uri.indexOf(':') == -1)
92             throw new RuntimeException("uri does not contain a colon: " + uri + " (tag name " + name + ")");
93         final String method = uri.substring(0, uri.indexOf(':'));
94         final String rest = uri.substring(uri.indexOf(':')+1);
95         if (uri.equals("http://www.w3.org/1999/xhtml")) { return true;
96         } else if (method.equals("webinf")) {
97             return graft(newTemplate(servletscope, copyNodeToScope(transform(n, scope), new Scope(servletscope)),
98                                      servletscope.getRealPath("/") + "/WEB-INF/" + rest + name), n).upstreamRead(n);
99         } else if (uri.equals("http://xt.ibex.org/")) {
100             //#switch(name)
101             case "if":       
102                 transform(n, scope);
103                 return graft((Node.Stream.Functor)("true".equals(n.attr("if")) ? new DropTag() : new DropAll()), n).upstreamRead(n);
104             case "js":       return graft(new JsTag(scope), n).upstreamRead(n);
105             case "foreach":  return graft(new ForEach(n, scope), n).upstreamRead(n);
106             case "children":
107                 if (children == null) return true;
108                 graft(new Node.Stream.ConstantFunctor(children), n);
109                 children = null;
110                 return upstreamRead(n);
111                 //#end
112                 return true;
113         } else if (method.equals("java")) {
114             try { return graft((Node.Stream.Functor)Class.forName(rest).newInstance(), n).upstreamRead(n); }
115             catch (Exception e) { throw new RuntimeException(e); }
116         }
117         throw new RuntimeException("Unknown namespace URI " + uri);    
118     }
119
120     private class ForEach extends Node.Stream.Filter implements Node.Stream.Functor {
121         private Node[] nodes = null;
122         private Vec array = new Vec();
123         private Scope scope;
124         public ForEach(Node n, Scope s) {
125             super(Node.Stream.NULL);
126             try {
127                 JSArray a = ((JSArray)exec("return (" + n.attr("in").toString() + ");", this.scope = s));
128                 while(true) {
129                     JS o = a.call(JSU.S("pop"), new JS[] { });
130                     if (o == null) break;
131                     array.push(o);
132                 }
133             } catch (JSExn e) {
134                 throw new RuntimeException(e);
135             }
136         }
137         public Node.Stream wrap(Node.Stream kids) {
138             Vec nodes = new Vec();
139             Node n2 = new Node();
140             while(kids.read(n2)) nodes.addElement(new Node(n2));
141             nodes.copyInto(this.nodes = new Node[nodes.size()]);
142             return this;
143         }
144         protected boolean _read(Node n) {
145             if (upstreamRead(n)) return true;
146             if (array.size() == 0) return false;
147             Scope scope2 = new Scope(scope);
148             try { scope2.declare("x"); scope2.put(JSU.S("x"), (JS)array.pop()); } catch (JSExn e) { throw new RuntimeException(e); }
149             return graft(new ConstantFunctor(new JSRewriter(new Node.Stream() {
150                     private int i = 0;
151                     protected boolean _read(Node n) {
152                         if (i>=nodes.length) return false;
153                         n.copyFrom(nodes[i++]);
154                         return true;
155                     } }, scope2)), n).upstreamRead(n);
156         }
157     }
158
159     public static class Scope extends JS.Immutable {
160         private final JS parent;
161         private final Hash declared = new Hash();
162         public Scope(JS parent) { this.parent = parent; }
163         public JS get(JS key) throws JSExn {
164             if (declared.get(key)!=null) return super.get(key);
165             return parent.get(key);
166         }
167         public void put(JS key, JS val) throws JSExn {
168             if (declared.get(key)!=null) super.put(key, val);
169             else parent.put(key, val);
170         }
171         public void declare(JS key) { declared.put(key, Boolean.TRUE); }
172         public void declare(String key) { declare(JSU.S(key)); }
173         public void undeclare(JS key) { declared.remove(key); }
174         public void undeclare(String key) { undeclare(JSU.S(key)); }
175     }
176
177     private class DropTag implements Node.Stream.Functor {
178         public Node.Stream wrap(Node.Stream kids) {
179             return kids;
180         } }
181
182     private class DropAll implements Node.Stream.Functor {
183         public Node.Stream wrap(Node.Stream kids) {
184             return new Node.Stream() { public boolean _read(Node n) { return false; } };
185         } }
186
187     private class JsTag implements Node.Stream.Functor {
188         Scope scope;
189         public JsTag(Scope scope) { this.scope = scope; }
190         public Node.Stream wrap(final Node.Stream s) {
191             return new Node.Stream() {
192                     protected boolean _read(Node n) {
193                         boolean ret = s.read(n);
194                         if (ret && n.cdata != null) {
195                             System.err.println("exec("+n.cdata+")");
196                             exec(n.cdata, scope);
197                             return _read(n);
198                         }
199                         return ret;
200                     }
201                 };
202         }
203     }
204 }