From: crawshaw Date: Tue, 16 Nov 2004 12:01:08 +0000 (+0000) Subject: initial support for XML.Document X-Git-Url: http://git.megacz.com/?p=org.ibex.xt-crawshaw.git;a=commitdiff_plain;h=539b657bcea9488a467bccd6d43ede5e869cede2 initial support for XML.Document darcs-hash:20041116120108-2eb37-e4527797d2bd4a0a62891d5853c0108595ccdab5.gz --- diff --git a/src/ibex/xt/JSElement.java b/src/ibex/xt/JSElement.java new file mode 100644 index 0000000..1cd6879 --- /dev/null +++ b/src/ibex/xt/JSElement.java @@ -0,0 +1,146 @@ +package ibex.xt; + +import ibex.util.XML; +import org.ibex.js.JS; +import org.ibex.js.JSScope; + +import ibex.collection.*; +import java.util.*; + +import java.io.StringReader; +import java.io.Writer; +import java.io.IOException; + +public class JSElement extends JSScope implements XML.Element { + protected XML.Element wrapped; + + /** Creates an Element around wrapped, replacing + * references to it in its parent and children with this object. */ + public JSElement(XML.Element wrapped) { + super(findScope(wrapped)); + this.wrapped = wrapped; + + // remap parent and children + if (wrapped.getParent() != null) { + List c = wrapped.getParent().getChildren(); + c.remove(wrapped); c.add(this); + } + List c = wrapped.getChildren(); + for (int i=c.size(); i >= 0; i--) ((XML.Block)c.get(i)).setParent(this); + } + + public void toXML(Writer w) throws IOException { + // grab all related attributes + try { + XML.Attributes a = getAttributes(); + for(int i=0; i < a.attrSize(); i++) { + if (!"http://xt.ibex.org/".equals(a.getUri(i))) continue; + declare(a.getKey(i)); + put(a.getKey(i), eval(a.getVal(i))); + } + } catch (Exception e) { throw new RuntimeException(e); } + } + + private Object eval(String s) { + if (s == null) return null; + StringBuffer ret = new StringBuffer(); + while (s.indexOf("${") != -1) { + ret.append(s.substring(0, s.indexOf("${"))); + String s2 = s.substring(s.indexOf("${")+2); + Object app = exec("return (" + s2.substring(0, s2.indexOf('}')) + ");\n"); + s = s.substring(s.indexOf('}') + 1); + + if (!(app == null || + app instanceof String || + app instanceof Number || + app instanceof Boolean)) + throw new RuntimeException("javascripts within ${...} can only return " + + "strings, numbers, and booleans; not a " + + app.getClass().getName()); + + ret.append(app == null ? "null" : app.toString()); + } + ret.append(s); + return ret.toString(); + } + + public Object exec(String s) { + try { + return JS.eval(JS.cloneWithNewParentScope( + JS.fromReader("input", 0, new StringReader(s)), this)); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + // Pass Through /////////////////////////////////////////////////////////// + + public void setParent(XML.Element p) { wrapped.setParent(p); } + public XML.Element getParent() { return wrapped.getParent(); } + public XML.Attributes getAttributes() { return wrapped.getAttributes(); } + public XML.Prefixes getPrefixes() { return wrapped.getPrefixes(); } + public List getChildren() { return wrapped.getChildren(); } + public String getQName() { return wrapped.getQName(); } + public String getLocalName() { return wrapped.getLocalName(); } + public String getPrefix() { return wrapped.getPrefix(); } + public String getUri() { return wrapped.getUri(); } + + /** Works up the Element object model until an instance of a JSScope is found. */ + private static JSScope findScope(XML.Element e) { + while (e != null && !(e instanceof JSScope)) e = e.getParent(); + return (JSScope)e; + } + + /** A JSElement with the element attributes merged with a second + * element. + * + * All functions of the XML.Element interface are mapped onto the + * primary element, except getAttributes(). This function + * returns a MergedAttr instance with the secondary element + * acting as the primary attribute source. + */ + public static class Merge extends JSElement { + private final XML.Attributes a; + public Merge(XML.Element wrapped, XML.Element merge) { + super(wrapped); + a = new MergeAttr(merge.getAttributes(), wrapped.getAttributes()); + } + public XML.Attributes getAttributes() { return a; } + } + + /** Creates a single view onto two sets of Attributes, first + * checking the primary array for an entry, or + * otherwise returning any matching entry in the + * secondary Attributes object. + * + * FIXME: toXML() produces invalid XML if qname in both a and b. + */ + public static final class MergeAttr implements XML.Attributes { + private final XML.Attributes a, b; + public MergeAttr(XML.Attributes primary, XML.Attributes secondary) { + a = primary; b = secondary; + } + public int getIndex(String qname) { + int i = a.getIndex(qname); if (i >= 0) return i; + i = b.getIndex(qname); if (i >= 0) return i + a.attrSize(); + return -1; + } + public int getIndex(String uri, String key) { + int i = a.getIndex(uri, key); if (i >= 0) return i; + i = b.getIndex(uri, key); if (i >= 0) return i + b.attrSize(); + return -1; + } + public String getKey(int i) { + return i >= a.attrSize() ? b.getKey(i-a.attrSize()) : a.getKey(i); } + public String getVal(int i) { + return i >= a.attrSize() ? b.getVal(i-a.attrSize()) : a.getVal(i); } + public String getUri(int i) { + return i >= a.attrSize() ? b.getUri(i-a.attrSize()) : a.getUri(i); } + public String getPrefix(int i) { + return i >= a.attrSize() ? b.getUri(i-a.attrSize()) : a.getUri(i); } + public String getQName(int i) { + return i >= a.attrSize() ? b.getUri(i-a.attrSize()) : a.getUri(i); } + public int attrSize() { return a.attrSize() + b.attrSize(); } + } +} diff --git a/src/org/ibex/xt/Prevalence.java b/src/ibex/xt/Prevalence.java similarity index 94% rename from src/org/ibex/xt/Prevalence.java rename to src/ibex/xt/Prevalence.java index b237619..4904bbd 100644 --- a/src/org/ibex/xt/Prevalence.java +++ b/src/ibex/xt/Prevalence.java @@ -1,13 +1,14 @@ -package org.ibex.xt; +package ibex.xt; + import org.ibex.js.*; -import org.ibex.util.*; -import org.ibex.io.*; +import ibex.util.*; +//import org.ibex.io.*; import java.io.*; import java.net.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; -import com.thoughtworks.xstream.*; +//import com.thoughtworks.xstream.*; import org.prevayler.*; import org.prevayler.implementation.snapshot.*; @@ -46,6 +47,7 @@ public class Prevalence { String base = cx.getRealPath("/") + "WEB-INF" + File.separatorChar + "prevalent"; System.err.println("prevayling to " + base); pf.configurePrevalenceBase(base); + /* XStreamSnapshotManager manager = new XStreamSnapshotManager(new JS(), base, null) { protected XStream createXStream() { XStream xstream = new XStream(); @@ -56,7 +58,8 @@ public class Prevalence { }; System.err.println("configuring with " + manager); pf.configureSnapshotManager(manager); - //pf.configureSnapshotManager(new SnapshotManager(new JS(), base)); + */ + pf.configureSnapshotManager(new SnapshotManager(new JS(), base)); //pf.configureClassLoader(JSTransaction.class.getClassLoader()); prevayler = pf.create(); prevaylers.put(cx, prevayler); diff --git a/src/org/ibex/xt/Servlet.java b/src/ibex/xt/Servlet.java similarity index 64% rename from src/org/ibex/xt/Servlet.java rename to src/ibex/xt/Servlet.java index 1d18563..94d3e29 100644 --- a/src/org/ibex/xt/Servlet.java +++ b/src/ibex/xt/Servlet.java @@ -1,19 +1,22 @@ -package org.ibex.xt; -import org.ibex.js.*; -import org.ibex.util.*; -import org.ibex.io.*; +package ibex.xt; + +import ibex.util.XML; +import org.ibex.js.JS; +import org.ibex.js.JSArray; +import org.ibex.js.JSDate; +import org.ibex.js.JSExn; + import java.io.*; import java.net.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; -import com.thoughtworks.xstream.*; + import org.prevayler.*; import org.prevayler.implementation.snapshot.*; public class Servlet extends HttpServlet { - private ServletScope servletscope = null; private String path; private Prevayler prevayler; private JS prevalent; @@ -35,28 +38,33 @@ public class Servlet extends HttpServlet { } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { doGet(request, response); } - public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - servletscope = new ServletScope(request, response, cx); - path = cx.getRealPath(((HttpServletRequest)request).getServletPath()); - Reader xmlreader = new InputStreamReader(new FileInputStream(path)); - new Template(servletscope, new JSScope(servletscope), xmlreader).wrap(null).toXML(response.getWriter()); + public void doGet(HttpServletRequest rq, HttpServletResponse rs) throws IOException { + String path = cx.getRealPath(rq.getServletPath()); + Servlet.Scope scope = new Servlet.Scope(cx, rq, rs, prevayler); + try { Template.wrap(Template.parse(path, scope), scope).toXML(rs.getWriter()); } + catch (Exception e) { e.printStackTrace(); System.out.println("e = "+e); } } - public class ServletScope extends JSScope { - HttpServletRequest request; - HttpServletResponse response; - ServletContext cx; - public String getRealPath(String s) { return cx.getRealPath(s); } - public ServletScope(ServletRequest request, ServletResponse response, ServletContext cx) { - super(null); - this.request = (HttpServletRequest)request; - this.response = (HttpServletResponse)response; - this.cx = cx; + public static class Scope extends Template.Scope { + private final ServletContext cx; + private final HttpServletRequest request; + private final HttpServletResponse response; + private final Prevayler prevayler; + + public Scope(ServletContext cx, HttpServletRequest rq, HttpServletResponse rs, Prevayler p) { + super(null); this.cx = cx; request = rq; response = rs; prevayler = p; + } + + public String getLocalPath() { return cx.getRealPath("/") + "/WEB-INF/"; } + public void transaction(JS t) { + try { prevayler.execute(new Prevalence.JSTransaction(t)); } + catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } + private JS params = new JS() { - public Object get(Object key) { return request.getParameter(JS.toString(key)); } - public Enumeration keys() { return request.getParameterNames(); } - }; + public Object get(Object key) { return request.getParameter(JS.toString(key)); } + public Enumeration keys() { return request.getParameterNames(); } + }; private JS cookies = new JS() { /* public Object get(Object key) { return request.getCookie(JS.toString(key)); } @@ -64,19 +72,21 @@ public class Servlet extends HttpServlet { */ }; private JS sessionAttributes = new JS() { - public Object get(Object key) { return request.getSession(true).getAttribute(JS.toString(key)); } - public void put(Object key, Object val) { - if (val == null) request.getSession(true).removeAttribute(JS.toString(key)); - else request.setAttribute(JS.toString(key), val); } - public Enumeration keys() { return request.getSession(true).getAttributeNames(); } - }; + public Object get(Object key) { + return request.getSession(true).getAttribute(JS.toString(key)); } + public void put(Object key, Object val) { + if (val == null) request.getSession(true).removeAttribute(JS.toString(key)); + else request.setAttribute(JS.toString(key), val); } + public Enumeration keys() { return request.getSession(true).getAttributeNames(); } + }; private JS requestHeader = new JS() { - public Object get(Object key) { return request.getHeader(JS.toString(key)); } - public Enumeration keys() { return request.getHeaderNames(); } - }; + public Object get(Object key) { return request.getHeader(JS.toString(key)); } + public Enumeration keys() { return request.getHeaderNames(); } + }; private JS responseHeader = new JS() { - public void put(Object key, Object val) { response.setHeader(JS.toString(key), JS.toString(val)); } - }; + public void put(Object key, Object val) { + response.setHeader(JS.toString(key), JS.toString(val)); } + }; /** lets us put multi-level get/put/call keys all in the same method */ @@ -84,14 +94,14 @@ public class Servlet extends HttpServlet { Object key; Sub(Object key) { this.key = key; } public void put(Object key, Object val) throws JSExn { - ServletScope.this.put(JS.toString(this.key) + "." + JS.toString(key), val); } + Scope.this.put(JS.toString(this.key) + "." + JS.toString(key), val); } public Object get(Object key) throws JSExn { - return ServletScope.this.get(JS.toString(this.key) + "." + JS.toString(key)); } + return Scope.this.get(JS.toString(this.key) + "." + JS.toString(key)); } public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - return ServletScope.this.callMethod(this.key, a0, a1, a2, rest, nargs); + return Scope.this.callMethod(this.key, a0, a1, a2, rest, nargs); } public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - return ServletScope.this.callMethod(JS.toString(this.key) + "." + return Scope.this.callMethod(JS.toString(this.key) + "." + JS.toString(method), a0, a1, a2, rest, nargs); } } @@ -124,7 +134,7 @@ public class Servlet extends HttpServlet { case "request.remote": return getSub("request.remote"); case "request.remote.ip": return request.getRemoteAddr(); case "request.remote.host": return request.getRemoteHost(); - case "request.ssl": return request.isSecure(); + case "request.ssl": return new Boolean(request.isSecure()); case "request.path": return request.getPathInfo(); case "response": return getSub("response"); case "response.header": return responseHeader; @@ -134,20 +144,19 @@ public class Servlet extends HttpServlet { case "session.accessed": return new JSDate(request.getSession(true).getLastAccessedTime()); case "session.invalidate": return METHOD; case "page": return getSub("page"); - case "page.lastmodified": return new JSDate(new File(path).lastModified()); + //case "page.lastmodified": return new JSDate(new File(path).lastModified()); FIXME case "context": return getSub("context"); case "context.list": return METHOD; case "params": return params; case "cookie": return cookies; + case "xt.date": return new JSDate(); // TODO: discuss //#end return null; } public void put(Object key, Object val) throws JSExn { try { //#switch(JS.toString(key)) - case "transaction": - try { prevayler.execute(new Prevalence.JSTransaction((JS)val)); - } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } + case "transaction": transaction((JS)val); case "response.code": response.setStatus(JS.toInt(val)); case "response.redirect": response.sendRedirect(JS.toString(val)); case "response.contentType": response.setContentType(JS.toString(val)); diff --git a/src/ibex/xt/Template.java b/src/ibex/xt/Template.java new file mode 100644 index 0000000..076251b --- /dev/null +++ b/src/ibex/xt/Template.java @@ -0,0 +1,213 @@ +package ibex.xt; + +import ibex.util.XML; +import ibex.util.Vec; //FIXME remove + +import org.ibex.js.JS; +import org.ibex.js.JSScope; +import org.ibex.js.JSArray; +import org.ibex.js.JSExn; + +import ibex.collection.*; +import java.util.*; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.io.IOException; + + +public class Template extends JSElement { + public static Template parse(String path, Template.Scope s) throws IOException, XML.Exn { + Reader xmlreader = new BufferedReader(new InputStreamReader(new FileInputStream(path))); + XML.Document doc = new XML.Document(); + doc.parse(xmlreader); + return new Template(doc.getRoot(), s); + } + + public static XML.Element wrap(XML.Element e, Template.Scope s) throws IOException, XML.Exn { + final String uri = e.getUri(); + + if (uri.equals("http://xt.ibex.org/")) { + //#switch(e.getLocalName()) + case "if": e = new Template.If(e); break; + case "js": e = new Template.JSTag(e); break; + case "foreach": e = new Template.ForEach(e); break; + case "children": e = new Template.Children(e); break; + case "transaction": e = new Template.Transaction(e, s); break; + //#end + + } else if (uri.startsWith("http://xt.ibex.org/")) { + //#switch(uri.substring(19)) + case "io": System.out.println("ibex.xt.io not yet implemented"); // TODO + //#end + throw new RuntimeException("Unknown XT library: "+uri); + + } else if (uri.startsWith("local:")) { + Template t = parse(s.getLocalPath() + uri.substring(6), s); + + List c = e.getChildren(); + if (c.size() > 0) { + // move all children from e to placeholder + XML.Element placeholder = findPlaceholder(t); + if (placeholder == null) throw new RuntimeException( + "<"+e.getQName()+"> attempted to include children into a " + + "template which does not contain an tag."); + + placeholder.getChildren().addAll(e.getChildren()); + e.getChildren().clear(); + } + + // merge original attributes with replacement template + e = new JSElement.Merge(t, e); + + } else if (uri.startsWith("java:")) { + e = new Java(e); + } + + // wrap children + List c = e.getChildren(); + for (int i=0; i < c.size(); i++) + if (c.get(i) instanceof XML.Element) wrap((XML.Element)c.get(i), s); + + return e; + } + + /** Returns the first Template.Children child found. */ + private static Template.Children findPlaceholder(XML.Element e) { + if (e instanceof Template.Children) return (Template.Children)e; + List c = e.getChildren(); + for (int i=0; i < c.size(); i++) { + if (!(c.get(i) instanceof XML.Element)) continue; + Template.Children ret = findPlaceholder((XML.Element)c.get(i)); + if (ret != null) return ret; + } + return null; + } + + private Template.Scope tscope; + + public Template(XML.Element w, Template.Scope t) { super(w); tscope = t; } + + public JSScope getParentScope() { return tscope; } + + + public static final class If extends JSElement { + public If(XML.Element e) { super(e); } + + public void toXML(Writer w) throws IOException { + super.toXML(w); + + try { + Object varIf = get("if"); if (varIf != null) undeclare("if"); + if (varIf != null && !Boolean.getBoolean((String)varIf)) return; + } catch (JSExn e) { throw new RuntimeException(e); } + + List c = getChildren(); + for (int i=0; i < c.size(); i++) ((XML.Block)c.get(i)).toXML(w); + } + } + + public static final class JSTag extends JSElement { + public JSTag(XML.Element e) { + super(e); + List c = getChildren(); + for (int i=0; i < c.size(); i++) + if (c.get(i) instanceof XML.Element) throw new RuntimeException( + "<"+getPrefix()+":js> tags may not have child elements"); + } + + public void toXML(Writer w) throws IOException { + super.toXML(w); + + try { + Object varIf = get("if"); if (varIf != null) undeclare("if"); + if (varIf != null && !Boolean.getBoolean((String)varIf)) return; + + List c = getChildren(); + StringWriter s = new StringWriter(); + for (int i=0; i < c.size(); i++) ((XML.Block)c.get(i)).toXML(s); + exec(s.toString()); + } catch (JSExn e) { throw new RuntimeException(e); } + } + } + + public static final class ForEach extends JSElement { + public ForEach(XML.Element e) { super(e); } + + public void toXML(Writer w) throws IOException { + super.toXML(w); + + try { + Object varIn = get("in"); if (varIn != null) undeclare("in"); + Object varPut = get("put"); if (varPut != null) undeclare("put"); + Object varIf = get("if"); if (varIf != null) undeclare("if"); + if (varIf != null && !Boolean.getBoolean((String)varIf)) return; + + varIn = exec("return (" + varIn + ");"); + if (varIn == null || (varIn instanceof JSArray)) throw new RuntimeException( + "<"+getPrefix()+":foreach> requires attribute 'in' to specify " + + "the name of a valid js array in the current scope, not in='"+varIn+"'."); + + if (varPut == null) varPut = "x"; + else if (!(varPut instanceof String) || get(varPut) != null) + throw new RuntimeException( + "<"+getPrefix()+":foreach> 'put' attribute requires the name of "+ + "an undeclared variable, not put='"+varPut+"'."); + if (get(varPut) != null) throw new RuntimeException( + "<"+getPrefix()+":foreach> has no 'put' attribute defined and the "+ + "default variable 'x' already exists in the current scope."); + + List c = getChildren(); + + declare((String)varPut); + Vec v = ((JSArray)varIn).toVec(); + for (int j=0; j < v.size(); j++) { + put(varPut, v.elementAt(j)); + for (int i=0; i < c.size(); i++) ((XML.Block)c.get(i)).toXML(w); + } + } catch (JSExn e) { throw new RuntimeException(e); } + } + } + + public static final class Children extends JSElement { + public Children(XML.Element e) { super(e); } + } + + public static final class Transaction extends JSElement { + private final Template.Scope scope; // FIXME: HACK. unstatisise all tags, or do this to all + public Transaction(XML.Element e, Template.Scope s) { super(e); scope = s;} // TODO: check kids + + public void toXML(Writer w) throws IOException { + super.toXML(w); + + // FIXME: what about scope import? children? + List c = getChildren(); + StringWriter sw = new StringWriter(); + for (int i=0; i < c.size(); i++) ((XML.Block)c.get(i)).toXML(sw); + JS t = JS.fromReader("input", 0, new StringReader(sw.toString())); + t = JS.cloneWithNewParentScope(t, new JSScope(null)); + scope.transaction(t); + } + } + + public static final class Java extends JSElement { + // TODO what exactly? + public Java(XML.Element w) { super(w); } + } + + public abstract static class Scope extends JSScope { + public Scope(JSScope j) { super(j); } + + /** Returns the template path for local:/ namespace. */ + public abstract String getLocalPath(); + + /** Registers a new Prevayler transaction. */ + public abstract void transaction(JS t); + } + +} diff --git a/src/org/ibex/xt/Node.java b/src/org/ibex/xt/Node.java deleted file mode 100644 index f4fe0a4..0000000 --- a/src/org/ibex/xt/Node.java +++ /dev/null @@ -1,166 +0,0 @@ -package org.ibex.xt; -import org.ibex.js.*; -import org.ibex.util.*; -import org.ibex.io.*; -import java.io.*; -import java.net.*; -import java.util.*; - -public class Node { - - public String name = null; - public String cdata = null; - public int numattrs = 0; - public String[] attrs = null; - public String uri = null; - private int delta = 0; - - public Node() { } - public Node(Node n) { copyFrom(n); } - public final void clear() { name = null; cdata = null; numattrs = 0; delta = 0; uri = null; } - public final void copyFrom(Node n) { - name=n.name; cdata=n.cdata; numattrs=n.numattrs; delta=n.delta; uri=n.uri; - if (n.attrs == null) { attrs = null; return; } - attrs = new String[n.attrs.length]; System.arraycopy(n.attrs, 0, attrs, 0, attrs.length); } - public final String attr(String key) { - for(int i=0; i"); - } else { - w.write(">"); - while(n != null && n.delta > 0) n = toXML(w, n); - w.write(""); - } - } - if (n != null) n.delta++; - return n; - } - } -} diff --git a/src/org/ibex/xt/Template.java b/src/org/ibex/xt/Template.java deleted file mode 100644 index f3955f2..0000000 --- a/src/org/ibex/xt/Template.java +++ /dev/null @@ -1,174 +0,0 @@ -package org.ibex.xt; -import org.ibex.js.*; -import org.ibex.util.*; -import org.ibex.io.*; -import java.io.*; -import java.net.*; -import java.util.*; -import javax.servlet.*; -import javax.servlet.http.*; - -public class Template extends Node.Stream.Filter implements Node.Stream.Functor { - - static Template newTemplate(Servlet.ServletScope servletscope, JSScope scope, String str) { - try { - File f = new File(str); - if (!f.exists()) f = new File(str + ".xt"); - if (!f.exists()) f = new File(str + ".xml"); - return new Template(servletscope, scope, new InputStreamReader(new FileInputStream(f))); - } catch (Exception e) { throw new RuntimeException(e); } - } - - static JSScope copyNodeToScope(Node n, JSScope scope) { - try { - for(int i=0; i=nodes.length) return false; - n.copyFrom(nodes[i++]); - return true; - } }, scope2)), n).upstreamRead(n); - } - } - - private class DropTag implements Node.Stream.Functor { - public Node.Stream wrap(Node.Stream kids) { - return kids; - } } - - private class DropAll implements Node.Stream.Functor { - public Node.Stream wrap(Node.Stream kids) { - return new Node.Stream() { public boolean _read(Node n) { return false; } }; - } } - - private class JsTag implements Node.Stream.Functor { - JSScope scope; - public JsTag(JSScope scope) { this.scope = scope; } - public Node.Stream wrap(final Node.Stream s) { - return new Node.Stream() { - protected boolean _read(Node n) { - boolean ret = s.read(n); - if (ret && n.cdata != null) { - System.err.println("exec("+n.cdata+")"); - exec(n.cdata, scope); - return _read(n); - } - return ret; - } - }; - } - } -}