// Copyright 2000-2005 the Contributors, as shown in the revision logs.
// Licensed under the GNU General Public License version 2 ("the License").
// You may not use this file except in compliance with the License.
package org.ibex.core;
import java.io.*;
import java.util.*;
import org.ibex.js.*;
import org.ibex.util.*;
/**
* Encapsulates a template node (the <template/> element of a
* .ibex file, or any child element thereof).
*
* Note that the Template instance corresponding to the
* <template/> node carries all the header information -- hence
* some of the instance members are not meaningful on non-root
* Template instances. We refer to these non-root instances as
* anonymous templates.
*
* See the Ibex reference for information on the order in which
* templates are applied, attributes are put, and scripts are run.
*/
public class Template {
// Instance Members ///////////////////////////////////////////////////////
String id = null; ///< the id of this box
String redirect = null; ///< the id of the redirect target; only meaningful on a root node
private JS[] keys; ///< keys to be "put" to instances of this template; elements correspond to those of vals
private JS[] vals; ///< values to be "put" to instances of this template; elements correspond to those of keys
private String[] urikeys;
private String[] urivals;
private Vec children = new Vec(); ///< during XML parsing, this holds the list of currently-parsed children; null otherwise
private JS script = null; ///< the script on this node
Template prev;
Template prev2;
JS staticObject = null;
// Only used during parsing /////////////////////////////////////////////////////////////////
private StringBuffer content = null; ///< during XML parsing, this holds partially-read character data; null otherwise
private int content_start = 0; ///< line number of the first line of content
private int startLine = -1; ///< the line number that this element starts on
private Ibex ibex;
// Static data/methods ///////////////////////////////////////////////////////////////////
// for non-root nodes
private Template(Template t, int startLine) { prev = t; this.ibex = t.ibex; this.startLine = startLine; }
private Template(Ibex ibex) { this.ibex = ibex; }
// Methods to apply templates ////////////////////////////////////////////////////////
/** Applies the template to Box b
* @param pboxes a vector of all box parents on which to put $-references
* @param ptemplates a vector of the fileNames to recieve private references on the pboxes
*/
public void apply(Box b) throws JSExn {
try {
apply(b, null);
} catch (IOException e) {
b.clear(Box.VISIBLE);
b.mark_for_repack();
Log.warn(this, e);
throw new JSExn(e.toString());
} catch (JSExn e) {
b.clear(Box.VISIBLE);
b.mark_for_repack();
Log.warn(this, e);
throw e;
}
}
private static final JS[] callempty = new JS[0];
private void apply(Box b, PerInstantiationScope parentPis) throws JSExn, IOException {
if (prev != null) prev.apply(b, null);
if (prev2 != null) prev2.apply(b, null);
// FIXME this dollar stuff is all wrong
if (id != null) parentPis.putDollar(id, b);
PerInstantiationScope pis = new PerInstantiationScope(b, ibex, parentPis, staticObject);
for(int i=0; i 0)) {
switch (JSU.toString(val).charAt(0)) {
case '$':
val = pis.get(val);
if (val == null) throw new JSExn("unknown box id '"+JSU.str(vals[i])+"' referenced in XML attribute");
break;
case '.':
val = ibex.resolveString(JSU.toString(val).substring(1), false);
// FIXME: url case
// FIXME: should we be resolving all of these in the XML-parsing code?
}
}
b.putAndTriggerTraps(key, val);
}
}
// XML Parsing /////////////////////////////////////////////////////////////////
public static Template buildTemplate(String sourceName, JS s, Ibex ibex) {
try {
return new TemplateHelper(sourceName, s, ibex).t;
} catch (Exception e) {
Log.error(Template.class, e);
return null;
}
}
/** handles XML parsing; builds a Template tree as it goes */
static final class TemplateHelper extends XML {
String sourceName;
private int state = STATE_INITIAL;
private static final int STATE_INITIAL = 0;
private static final int STATE_IN_ROOT_NODE = 1;
private static final int STATE_IN_TEMPLATE_NODE = 2;
private static final int STATE_IN_META_NODE = 3;
private static final JS[] callempty = new JS[0];
StringBuffer static_content = null;
int static_content_start = 0;
Vec nodeStack = new Vec();
Template t = null;
int meta = 0;
Ibex ibex;
String initial_uri = "";
public TemplateHelper(String sourceName, JS s, Ibex ibex) throws XML.Exn, IOException, JSExn {
this.sourceName = sourceName;
this.ibex = ibex;
InputStream is = s.getInputStream();
Ibex.Blessing b = Ibex.Blessing.getBlessing(s).parent;
while(b != null) {
if(b.parentkey != null) initial_uri = JSU.toString(b.parentkey) + (initial_uri.equals("") ? "" : "." + initial_uri);
b = b.parent;
}
initial_uri = "";
parse(new InputStreamReader(is));
JS staticScript = parseScript(static_content, static_content_start);
t.staticObject = new JS.Obj();
JS staticScope = new PerInstantiationScope(null, ibex, null, t.staticObject);
if (staticScript != null) JSU.cloneWithNewGlobalScope(staticScript, staticScope).call(callempty);
}
private JS parseScript(StringBuffer content, int content_start) throws IOException {
if (content == null) return null;
String contentString = content.toString();
if (contentString.trim().length() > 0) return JSU.fromReader(sourceName, content_start, new StringReader(contentString));
return null;
}
public void startElement(Tree.Element c) throws XML.Exn {
Tree.Attributes a = c.getAttributes();
switch(state) {
case STATE_IN_META_NODE: { meta++; return; }
case STATE_INITIAL:
if (!"ibex".equals(c.getLocalName()))
throw new XML.Exn("root element was not ", XML.Exn.SCHEMA, getLine(), getCol());
if (a.attrSize() != 0)
throw new XML.Exn("root element must not have attributes", XML.Exn.SCHEMA, getLine(), getCol());
state = STATE_IN_ROOT_NODE;
return;
case STATE_IN_ROOT_NODE:
if ("ibex://meta".equals(c.getUri())) { state = STATE_IN_META_NODE; meta = 0; return; }
state = STATE_IN_TEMPLATE_NODE;
t = (t == null) ? new Template(ibex) : new Template(t, getLine());
break;
case STATE_IN_TEMPLATE_NODE:
nodeStack.addElement(t);
t = new Template(ibex);
break;
}
// FIXME: This is all wrong
if (!("ibex://ui".equals(c.getUri()) && "box".equals(c.getLocalName()))) {
String tagname = (c.getUri() == null || "".equals(c.getUri()) ? "" :
(c.getUri() + ".")) + c.getLocalName();
// GROSS hack
try {
// GROSSER hack
// t.prev2 = (Template)t.ibex.resolveString(tagname, false).call(null, null, null, null, 9999);
if(t.prev2 != null) throw new Error("FIXME: t.prev2 != null");
t.prev2 = ((Ibex.Blessing)t.ibex.resolveString(tagname, false)).getTemplate();
if(t.prev2 == null) throw new Exception("" + tagname + " not found");
} catch (Exception e) {
Log.error(Template.class, e);
}
}
Tree.Prefixes prefixes = c.getPrefixes();
t.urikeys = new String[prefixes.pfxSize()];
t.urivals = new String[prefixes.pfxSize()];
int ii = 0;
for (int i=0; i < prefixes.pfxSize(); i++) {
String key = prefixes.getPrefixKey(i);
String val = prefixes.getPrefixVal(i);
if (val.equals("ibex://ui")) continue;
if (val.equals("ibex://meta")) continue;
t.urikeys[ii] = key;
if (val.length() > 0 && val.charAt(0) == '.') val = val.substring(1);
t.urivals[ii] = val;
ii++;
}
// FIXME: 2-value Array
Basket.Array keys = new Basket.Array(a.attrSize());
Basket.Array vals = new Basket.Array(a.attrSize());
// process attributes into Vecs, dealing with any XML Namespaces in the process
ATTR: for (int i=0; i < a.attrSize(); i++) {
if (a.getKey(i).equals("id")) {
t.id = a.getVal(i).toString().intern();
continue ATTR;
}
// treat value starting with '.' as resource reference
String uri = a.getUri(i); if (uri != null && !uri.equals("")) uri = '.' + uri;
keys.add(a.getKey(i));
vals.add((a.getVal(i).startsWith(".") ? uri : "") + a.getVal(i));
}
if (keys.size() == 0) return;
// sort the attributes lexicographically
Basket.Array.sort(keys, vals, new Basket.CompareFunc() {
public int compare(Object a, Object b) { return ((String)a).compareTo((String)b); }
}, 0, keys.size());
t.keys = new JS[keys.size()];
t.vals = new JS[vals.size()];
// convert attributes to appropriate types and intern strings
for(int i=0; i 0 && !hasNonNumeral) t.vals[i] = JSU.N(Double.parseDouble((valString)));
else t.vals[i] = JSU.S(valString.intern()); // FEATURE: JS.intern() ?
}
}
}
public void endElement(Tree.Element c) throws XML.Exn, IOException {
switch(state) {
case STATE_IN_META_NODE: if (--meta < 0) state = STATE_IN_ROOT_NODE; return;
case STATE_IN_ROOT_NODE: return;
case STATE_IN_TEMPLATE_NODE: {
if (t.content != null) { t.script = parseScript(t.content, t.content_start); t.content = null; }
if (nodeStack.size() == 0) { state = STATE_IN_ROOT_NODE; return; }
Template oldt = t;
t = (Template)nodeStack.lastElement();
nodeStack.setSize(nodeStack.size() - 1);
t.children.addElement(oldt);
int oldt_lines = getLine() - oldt.startLine;
if (t.content == null) t.content = new StringBuffer();
for (int i=0; oldt_lines > i; i++) t.content.append('\n');
}
}
}
public void characters(char[] ch, int start, int length) throws XML.Exn {
for (int i=0; length >i; i++) if (ch[start+i] == '\t')
Log.error(Template.class, "tabs are not allowed in Ibex files ("+getLine()+":"+getCol()+")");
switch(state) {
case STATE_IN_TEMPLATE_NODE:
if (t.content == null) {
t.content_start = getLine();
t.content = new StringBuffer();
}
t.content.append(ch, start, length);
return;
case STATE_IN_ROOT_NODE:
if (static_content == null) {
static_content_start = getLine();
static_content = new StringBuffer();
}
static_content.append(ch, start, length);
return;
}
}
public void whitespace(char[] ch, int start, int length) throws XML.Exn { }
}
// FIXME: david, get to work
private static class PerInstantiationScope extends JS.Obj {
Ibex ibex = null;
PerInstantiationScope parentBoxPis = null;
JS myStatic = null;
JS box;
void putDollar(String key, Box target) throws JSExn {
if (parentBoxPis != null) parentBoxPis.putDollar(key, target);
JS jskey = JSU.S("$" + key);
//declare(jskey);
sput(jskey, target);
}
// JS:FIXME: ugly
void sput(JS key, JS val) throws JSExn { super.put(key,val); }
public PerInstantiationScope(JS box, Ibex ibex, PerInstantiationScope parentBoxPis, JS myStatic) {
this.parentBoxPis = parentBoxPis;
this.ibex = ibex;
this.myStatic = myStatic;
this.box = box;
}
public JS get(JS key) throws JSExn {
if(JSU.isString(key)) {
String s = JSU.toString(key);
// JS:FIXME This is a hack
if (super.get(key) != null) return super.get(key);
if (s.equals("ibex")) return ibex;
if (s.equals("")) return ibex.get(key);
if (s.equals("static")) return myStatic;
}
// JS:FIXME: This won't work with traps that do blocking operations
return box.getAndTriggerTraps(key);
}
// JS:FIXME: Everything below here should come from js.scope or something
public void put(JS key, JS val) throws JSExn { if(box != null) box.putAndTriggerTraps(key,val); else super.put(key,val); }
public void addTrap(JS key, JS f) throws JSExn { box.addTrap(key,f); }
public void delTrap(JS key, JS f) throws JSExn { box.delTrap(key,f); }
}
}