2003/04/24 14:33:28
[org.ibex.core.git] / src / org / xwt / Template.java
1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt;
3
4 import java.io.*;
5 import java.util.zip.*;
6 import java.util.*;
7 import java.lang.*;
8 import org.mozilla.javascript.*;
9 import org.xwt.util.*;
10
11 /**
12  *  Encapsulates a template node (the <template/> element of a
13  *  .xwt file, or any child element thereof). Each instance of
14  *  Template has a <tt>nodeName</tt> -- this is the resource name of
15  *  the file that the template node occurs in, concatenated with the
16  *  path from the root element to this node, each step of which is in
17  *  the form .n for some integer n. Static nodes use the string "._"
18  *  as a path.
19  *
20  *  Note that the Template instance corresponding to the
21  *  &lt;template/&gt; node carries all the header information -- hence
22  *  some of the instance members are not meaningful on non-root
23  *  Template instances. We refer to these non-root instances as
24  *  <i>anonymous templates</i>.
25  *
26  *  See the XWT reference for information on the order in which
27  *  templates are applied, attributes are put, and scripts are run.
28  */
29 public class Template {
30
31     // Instance Members ///////////////////////////////////////////////////////
32
33     /** this instance's nodeName */
34     String nodeName;
35
36     /** the id of the redirect target; only meaningful on a root node */
37     String redirect = null;
38
39     /** templates that should be preapplied (in the order of application); only meaningful on a root node */
40     private String[] preapply;
41
42     /** 'linked' form of preapply -- the String references have been resolved into instance references */
43     private Template[] _preapply = null;
44
45     /** templates that should be postapplied (in the order of application); only meaningful on a root node */
46     private String[] postapply;
47
48     /** 'linked' form of postapply -- the String references have been resolved into instance references */
49     private Template[] _postapply = null;
50
51     /** keys to be "put" to instances of this template; elements correspond to those of vals */
52     private String[] keys;
53
54     /** values to be "put" to instances of this template; elements correspond to those of keys */
55     private Object[] vals;
56
57     /** array of strings representing the importlist for this template */
58     private String[] importlist;
59
60     /** child template objects */
61     private Template[] children;
62
63     /** an array of the names of properties to be preserved when retheming; only meaningful on a root node */
64     private String[] preserve = null;
65     
66     /** the <tt>id</tt> attribute on this node */
67     private String id = "";
68
69     /** see numUnits(); -1 means that this value has not yet been computed */
70     private int numunits = -1;
71
72     /** true iff the resolution of this template's preapply/postapply sets changed as a result of the most recent call to retheme() */
73     private boolean changed = false;
74
75     /** the script on the static node of this template, null if it has already been executed */
76     private Script staticscript = null;
77
78     /** the script on this node */
79     private Script script = null;
80
81     /** during XML parsing, this holds the list of currently-parsed children; null otherwise */
82     private Vec childvect = new Vec();
83
84     /** during XML parsing, this holds partially-read character data; null otherwise */
85     private StringBuffer content = null;
86
87     /** line number of the first line of <tt>content</tt> */
88     private int content_start = 0;
89
90     /** number of lines in <tt>content</tt> */
91     private int content_lines = 0;
92
93     /** the line number that this element starts on */
94     private int startLine = -1;
95
96     // Static data/methods ///////////////////////////////////////////////////////////////////
97
98     /** maximum length of a line */
99     private static final int MAX_COLUMN = 150;
100
101     /** a template cache so that only one Template object is created for each xwt */
102     private static Hashtable cache = new Hashtable(1000);
103
104     /** The default importlist; in future revisions this will contain "xwt.*" */
105     public static final String[] defaultImportList = new String[] { };
106
107     /** returns the appropriate template, resolving and theming as needed */
108     public static Template getTemplate(String name, String[] importlist) {
109         String resolved = Resources.resolve(name + ".xwt", importlist);
110         Template t = resolved == null ? null : (Template)cache.get(resolved.substring(0, resolved.length() - 4));
111         if (t != null) return t;
112         if (resolved == null) return null;
113
114         // note that Templates in xwar's are instantiated as read in via loadStream() --
115         // the following code only runs when XWT is reading templates from a filesystem.
116         ByteArrayInputStream bais = new ByteArrayInputStream(Resources.getResource(resolved));
117         return buildTemplate(bais, resolved.substring(0, resolved.length() - 4));
118     }
119
120     public static Template buildTemplate(InputStream is, String nodeName) {
121         return buildTemplate(is, nodeName, new TemplateHelper());
122     }
123
124     public static Template buildTemplate(InputStream is, String nodeName, TemplateHelper t) {
125         try {
126             return new Template(is, nodeName, t);
127         } catch (XML.SchemaException e) {
128             if (Log.on) Log.log(Template.class, "error parsing template " + nodeName);
129             if (Log.on) Log.log(Template.class, e.getMessage());
130             return null;
131         } catch (XML.XMLException e) {
132             if (Log.on) Log.log(Template.class, "error parsing template at " + nodeName + ":" + e.getLine() + "," + e.getCol());
133             if (Log.on) Log.log(Template.class, e.getMessage());
134             return null;
135         } catch (IOException e) {
136             if (Log.on) Log.log(Template.class, "IOException while parsing template " + nodeName + " -- this should never happen");
137             if (Log.on) Log.log(Template.class, e);
138             return null;
139         }
140     }
141
142
143     // Methods to apply templates ////////////////////////////////////////////////////////
144
145     private Template(String nodeName) {
146         this.nodeName = nodeName;
147         cache.put(nodeName, this);
148     }
149     private Template(InputStream is, String nodeName, TemplateHelper th) throws XML.XMLException, IOException {
150         this(nodeName);
151         th.parseit(is, this);
152     }
153
154     /** calculates, caches, and returns an integer approximation of how long it will take to apply this template, including pre/post and children */
155     int numUnits() {
156         link();
157         if (numunits != -1) return numunits;
158         numunits = 1;
159         for(int i=0; _preapply != null && i<_preapply.length; i++) if (_preapply[i] != null) numunits += _preapply[i].numUnits();
160         for(int i=0; _postapply != null && i<_postapply.length; i++) if (_postapply[i] != null) numunits += _postapply[i].numUnits();
161         if (script != null) numunits += 10;
162         numunits += keys == null ? 0 : keys.length;
163         for(int i=0; children != null && i<children.length; i++) numunits += children[i].numUnits();
164         return numunits;
165     }
166     
167     /** Applies the template to Box b
168      *  @param pboxes a vector of all box parents on which to put $-references
169      *  @param ptemplates a vector of the nodeNames to recieve private references on the pboxes
170      */
171     void apply(Box b, Vec pboxes, Vec ptemplates, Function callback, int numerator, int denominator) {
172
173         int original_numerator = numerator;
174
175         if (pboxes == null) {
176             pboxes = new Vec();
177             ptemplates = new Vec();
178         }
179
180         if (id != null && !id.equals(""))
181             for(int i=0; i<pboxes.size(); i++) {
182                 Box parent = (Box)pboxes.elementAt(i);
183                 String parentNodeName = (String)ptemplates.elementAt(i);
184                 parent.putPrivately("$" + id, b, parentNodeName);
185             }
186
187         if (script != null || (redirect != null && !"self".equals(redirect))) {
188             pboxes.addElement(b);
189             ptemplates.addElement(nodeName);
190         }
191
192         int numids = pboxes.size();
193         
194         link();
195
196         for(int i=0; _preapply != null && i<_preapply.length; i++)
197             if (_preapply[i] != null) {
198                 _preapply[i].apply(b, null, null, callback, numerator, denominator);
199                 numerator += _preapply[i].numUnits();
200             }
201
202         for (int i=0; children != null && i<children.length; i++) {
203             b.put(Integer.MAX_VALUE, null, new Box(children[i], pboxes, ptemplates, callback, numerator, denominator));
204             numerator += children[i].numUnits();
205         }
206
207         // whom to redirect to; doesn't take effect until after script runs
208         Box redir = null;
209         if (redirect != null && !"self".equals(redirect))
210             redir = (Box)b.getPrivately("$" + redirect, nodeName);
211
212         if (script != null) try {
213             Context cx = Context.enter();
214             script.exec(cx, b);
215         } catch (EcmaError e) {
216             if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
217             if (Log.on) Log.log(this, "         thrown while instantiating " + nodeName + " at " + e.getSourceName() + ":" + e.getLineNumber());
218         } catch (JavaScriptException e) {
219             if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
220             if (Log.on) Log.log(this, "         thrown while instantiating " + nodeName + " at " + e.sourceFile + ":" + e.line);
221         }
222
223         for(int i=0; keys != null && i<keys.length; i++) {
224             Context.enter().interpreterSourceFile = nodeName;
225             Context.enter().interpreterLine = startLine;
226             if (keys[i] == null) { }
227             else if (keys[i].equals("border") || keys[i].equals("image") &&
228                      !vals[i].toString().startsWith("http://") && !vals[i].toString().startsWith("https://")) {
229                 String s = Resources.resolve(vals[i].toString() + ".png", importlist);
230                 if (s != null) b.put(keys[i], null, s.substring(0, s.length() - 4));
231                 else if (Log.on) Log.log(this, "unable to resolve image " + vals[i].toString() + " referenced in attributes of " + nodeName); 
232             }
233             else b.put(keys[i], null, vals[i]);
234         }
235
236         if (redirect != null && !"self".equals(redirect)) b.redirect = redir;
237
238         for(int i=0; _postapply != null && i<_postapply.length; i++)
239             if (_postapply[i] != null) {
240                 _postapply[i].apply(b, null, null, callback, numerator, denominator);
241                 numerator += _postapply[i].numUnits();
242             }
243
244         pboxes.setSize(numids);
245         ptemplates.setSize(numids);
246
247         numerator = original_numerator + numUnits();
248
249         if (callback != null)
250             try {
251                 callback.call(Context.enter(), null, null, new Object[] { new Double(numerator), new Double(denominator) });
252             } catch (EcmaError e) {
253                 if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
254                 if (Log.on) Log.log(this, "         thrown from within progress callback at " + e.getSourceName() + ":" + e.getLineNumber());
255             } catch (JavaScriptException e) {
256                 if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
257                 if (Log.on) Log.log(this, "         thrown from within progress callback at " + e.sourceFile + ":" + e.line);
258             }
259
260         if (Thread.currentThread() instanceof ThreadMessage) try {
261             XWT.yield.call(Context.enter(), null, null, null);
262         } catch (JavaScriptException e) {
263             if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
264             if (Log.on) Log.log(this, "         thrown from within yield at " + e.sourceFile + ":" + e.line);
265         }
266     }
267
268
269     // Theming Logic ////////////////////////////////////////////////////////////
270
271     /** helper method to recursively gather up the list of keys to be preserved */
272     private void gatherPreserves(Vec v) {
273         for(int i=0; preserve != null && i<preserve.length; i++) v.addElement(preserve[i]);
274         for(int i=0; _preapply != null && i<_preapply.length; i++) if (_preapply[i] != null) _preapply[i].gatherPreserves(v);
275         for(int i=0; _postapply != null && i<_postapply.length; i++) if (_postapply[i] != null) _postapply[i].gatherPreserves(v);
276     }
277
278     /** adds a theme mapping, retemplatizing as needed */
279     public static void retheme(Function callback) {
280         XWF.flushXWFs();
281
282         // clear changed marker and relink
283         Template[] t = new Template[cache.size()];
284         Enumeration e = cache.elements();
285         for(int i=0; e.hasMoreElements(); i++) t[i] = (Template)e.nextElement();
286         for(int i=0; i<t.length; i++) {
287             t[i].changed = false;
288             t[i].numunits = -1;
289             t[i].link(true);
290         }
291
292         for(int i=0; i<Surface.allSurfaces.size(); i++) {
293             Box b = ((Surface)Surface.allSurfaces.elementAt(i)).root;
294             if (b != null) reapply(b);
295         }
296
297         if (callback != null)
298             try {
299                 callback.call(Context.enter(), null, null, new Object[] { new Double(1.0), new Double(1.0) });
300             } catch (EcmaError ex) {
301                 if (Log.on) Log.log(Template.class, "WARNING: uncaught interpreter exception: " + ex.getMessage());
302                 if (Log.on) Log.log(Template.class, "         thrown from within progress callback at " + ex.getSourceName() + ":" + ex.getLineNumber());
303             } catch (JavaScriptException ex) {
304                 if (Log.on) Log.log(Template.class, "WARNING: uncaught ecmascript exception: " + ex.getMessage());
305                 if (Log.on) Log.log(Template.class, "         thrown from within progress callback at " + ex.sourceFile + ":" + ex.line);
306             }
307     }
308
309     /** template reapplication procedure */
310     private static void reapply(Box b) {
311
312         // Ref 7.5.1: check if we need to retemplatize
313         boolean retemplatize = false;
314         if (b.templatename != null) {
315             Template t = getTemplate(b.templatename, b.importlist);
316             if (t != b.template) retemplatize = true;
317             b.template = t;
318         }
319         if (b.template != null && b.template.changed) retemplatize = true;
320
321         if (retemplatize) {
322
323             // Ref 7.5.2: "Preserve all properties on the box mentioned in the <preserve> elements of any
324             //             of the templates which would be applied in step 7."
325             Vec keys = new Vec();
326             b.template.gatherPreserves(keys);
327             Object[] vals = new Object[keys.size()];
328             for(int i=0; i<keys.size(); i++) vals[i] = b.get(((String)keys.elementAt(i)), null);
329             
330             // Ref 7.5.3: "Remove and save all children of the box, or its redirect target, if it has one"
331             Box[] kids = null;
332             if (b.redirect != null) {
333                 kids = new Box[b.redirect.numChildren()];
334                 for(int i=b.redirect.numChildren() - 1; i >= 0; i--) {
335                     kids[i] = b.redirect.getChild(i);
336                     kids[i].remove();
337                 }
338             }
339             
340             // Ref 7.5.4: "Set the box's redirect target to self"
341             b.redirect = b;
342             
343             // Ref 7.5.5: "Remove all of the box's immediate children"
344             for(Box cur = b.getChild(b.numChildren() - 1); cur != null;) {
345                 Box oldcur = cur;
346                 cur = cur.prevSibling();
347                 oldcur.remove();
348             }
349             
350             // Ref 7.5.6: "Remove all traps set by scripts run during the application of any template to this box"
351             Trap.removeAllTrapsByBox(b);
352             
353             // Ref 7.5.7: "Apply the template to the box according to the usual application procedure"
354             b.template.apply(b, null, null, null, 0, 1);
355             
356             // Ref 7.5.8: "Re-add the saved children which were removed in step 3"
357             for(int i=0; kids != null && i<kids.length; i++) b.put(Integer.MAX_VALUE, null, kids[i]);
358             
359             // Ref 7.5.9: "Re-put any property values which were preserved in step 2"
360             for(int i=0; i<keys.size(); i++) b.put((String)keys.elementAt(i), null, vals[i]);
361         }        
362
363         // Recurse
364         for(Box j = b.getChild(0); j != null; j = j.nextSibling()) reapply(j);
365     }
366
367     /** runs statics, resolves string references to other templates into actual Template instance references, and sets <tt>change</tt> as needed */
368     void link() { link(false); }
369
370     /** same as link(), except that with a true value, it will force a re-link */
371     private void link(boolean force) {
372
373         if (staticscript != null) try { 
374             Scriptable s = Static.createStatic(nodeName, false);
375             if (staticscript != null) {
376                 Script temp = staticscript;
377                 ((InterpretedScript)temp).setParentScope(s);     // so we know how to handle Static.get("xwt")
378                 staticscript = null;
379                 temp.exec(Context.enter(), s);
380             }
381         } catch (EcmaError e) {
382             if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
383             if (Log.on) Log.log(this, "         thrown while executing <static/> block for " + nodeName +
384                                       " at " + e.getSourceName() + ":" + e.getLineNumber());
385         } catch (JavaScriptException e) {
386             if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
387             if (Log.on) Log.log(this, "         thrown while executing <static/> block for " + nodeName + " at " + e.sourceFile + ":" + e.line);
388         }
389
390         if (!(force || (preapply != null && _preapply == null) || (postapply != null && _postapply == null))) return;
391         
392         if (preapply != null) {
393             if (_preapply == null) _preapply = new Template[preapply.length];
394             for(int i=0; i<_preapply.length; i++) {
395                 Template t = getTemplate(preapply[i], importlist);
396                 if (t != _preapply[i]) changed = true;
397                 _preapply[i] = t;
398             }
399         }
400         if (postapply != null) {
401             if (_postapply == null) _postapply = new Template[postapply.length];
402             for(int i=0; i<_postapply.length; i++) {
403                 Template t = getTemplate(postapply[i], importlist);
404                 if (t != _postapply[i]) changed = true;
405                 _postapply[i] = t;
406             }
407         }
408
409         for(int i=0; children != null && i<children.length; i++) children[i].link(force);
410     }
411
412
413     // XML Parsing /////////////////////////////////////////////////////////////////
414
415     /** handles XML parsing; builds a Template tree as it goes */
416     static final class TemplateHelper extends XML {
417
418         TemplateHelper() { }
419
420         /** parse an XML input stream, building a Template tree off of <tt>root</tt> */
421         void parseit(InputStream is, Template root) throws XML.XMLException, IOException {
422             rootNodeHasBeenEncountered = false;
423             templateNodeHasBeenEncountered = false;
424             staticNodeHasBeenEncountered = false;
425             templateNodeHasBeenFinished = false;
426             nameOfHeaderNodeBeingProcessed = null;
427
428             nodeStack.setSize(0);
429             importlist.setSize(0);
430             preapply.setSize(0);
431             postapply.setSize(0);
432
433             importlist.fromArray(defaultImportList);
434
435             t = root;
436             parse(new InputStreamReader(is)); 
437         }
438
439         /** parsing state: true iff we have already encountered the <xwt> open-tag */
440         boolean rootNodeHasBeenEncountered = false;
441
442         /** parsing state: true iff we have already encountered the <template> open-tag */
443         boolean templateNodeHasBeenEncountered = false;
444
445         /** parsing state: true iff we have already encountered the <static> open-tag */
446         boolean staticNodeHasBeenEncountered = false;
447
448         /** parsing state: true iff we have already encountered the <template> close-tag */
449         boolean templateNodeHasBeenFinished = false;
450
451         /** parsing state: If we have encountered the open tag of a header node, but not the close tag, this is the name of
452          *  that tag; otherwise, it is null. */
453         String nameOfHeaderNodeBeingProcessed = null;
454
455         /** stack of Templates whose XML elements we have seen open-tags for but not close-tags */
456         Vec nodeStack = new Vec();
457
458         /** builds up the list of imports */
459         Vec importlist = new Vec();
460
461         /** builds up the list of preapplies */
462         Vec preapply = new Vec();
463
464         /** builds up the list of postapplies */
465         Vec postapply = new Vec();
466
467         /** the template we're currently working on */
468         Template t = null;
469
470         public void startElement(XML.Element c) throws XML.SchemaException {
471             if (templateNodeHasBeenFinished) {
472                 throw new XML.SchemaException("no elements may appear after the <template> node");
473
474             } else if (!rootNodeHasBeenEncountered) {
475                 if (!"xwt".equals(c.localName)) throw new XML.SchemaException("root element was not <xwt>");
476                 if (c.len != 0) throw new XML.SchemaException("root element must not have attributes");
477                 rootNodeHasBeenEncountered = true;
478                 return;
479         
480             } else if (!templateNodeHasBeenEncountered) {
481                 if (nameOfHeaderNodeBeingProcessed != null) throw new XML.SchemaException("can't nest header nodes");
482                 nameOfHeaderNodeBeingProcessed = c.localName;
483
484                 if (c.localName.equals("import")) {
485                     if (c.len != 1 || !c.keys[0].equals("name"))
486                         throw new XML.SchemaException("<import> node must have exactly one attribute, which must be called 'name'");
487                     String importpackage = c.vals[0].toString();
488                     if (importpackage.endsWith(".*")) importpackage = importpackage.substring(0, importpackage.length() - 2);
489                     importlist.addElement(importpackage);
490                     return;
491
492                 } else if (c.localName.equals("redirect")) {
493                     if (c.len != 1 || !c.keys[0].equals("target"))
494                         throw new XML.SchemaException("<redirect> node must have exactly one attribute, which must be called 'target'");
495                     if (t.redirect != null)
496                         throw new XML.SchemaException("the <redirect> header element may not appear more than once");
497                     t.redirect = c.vals[0].toString();
498                     return;
499
500                 } else if (c.localName.equals("preapply")) {
501                     if (c.len != 1 || !c.keys[0].equals("name"))
502                         throw new XML.SchemaException("<preapply> node must have exactly one attribute, which must be called 'name'");
503                     preapply.addElement(c.vals[0]);
504                     return;
505
506                 } else if (c.localName.equals("postapply")) {
507                     if (c.len != 1 || !c.keys[0].equals("name"))
508                         throw new XML.SchemaException("<postapply> node must have exactly one attribute, which must be called 'name'");
509                     postapply.addElement(c.vals[0]);
510                     return;
511
512                 } else if (c.localName.equals("static")) {
513                     if (staticNodeHasBeenEncountered)
514                         throw new XML.SchemaException("the <static> header node may not appear more than once");
515                     if (c.len > 0)
516                         throw new XML.SchemaException("the <static> node may not have attributes");
517                     staticNodeHasBeenEncountered = true;
518                     return;
519
520                 } else if (c.localName.equals("preserve")) {
521                     if (c.len != 1 || !c.keys[0].equals("attributes"))
522                         throw new XML.SchemaException("<preserve> node must have exactly one attribute, which must be called 'attributes'");
523                     if (t.preserve != null)
524                         throw new XML.SchemaException("<preserve> header element may not appear more than once");
525
526                     StringTokenizer tok = new StringTokenizer(c.vals[0].toString(), ",", false);
527                     t.preserve = new String[tok.countTokens()];
528                     for(int i=0; i<t.preserve.length; i++) t.preserve[i] = tok.nextToken();
529                     return;
530
531                 } else if (c.localName.equals("template")) {
532                     // finalize importlist/preapply/postapply, since they can't change from here on
533                     t.startLine = getLine();
534                     importlist.toArray(t.importlist = new String[importlist.size()]);
535                     if (preapply.size() > 0) preapply.copyInto(t.preapply = new String[preapply.size()]);
536                     if (postapply.size() > 0) postapply.copyInto(t.postapply = new String[postapply.size()]);
537                     importlist.setSize(0); preapply.setSize(0); postapply.setSize(0);
538                     templateNodeHasBeenEncountered = true;
539
540                 } else {
541                     throw new XML.SchemaException("unrecognized header node \"" + c.localName + "\"");
542
543                 }
544
545             } else {
546
547                 // push the last node we were in onto the stack
548                 nodeStack.addElement(t);
549
550                 // instantiate a new node, and set its nodeName/importlist/preapply
551                 Template t2 = new Template(t.nodeName + "." + t.childvect.size());
552                 t2.importlist = t.importlist;
553                 t2.startLine = getLine();
554                 if (!c.localName.equals("box")) t2.preapply = new String[] { c.localName };
555
556                 // make the new node the current node
557                 t = t2;
558
559             }
560
561             // TODO: Sort contents straight from one array to another
562             t.keys = new String[c.len];
563             t.vals = new Object[c.len];
564             System.arraycopy(c.keys, 0, t.keys, 0, c.len);
565             System.arraycopy(c.vals, 0, t.vals, 0, c.len);
566             quickSortAttributes(0, t.keys.length - 1);
567
568             for(int i=0; i<t.keys.length; i++) {
569                 if (t.keys[i].equals("id")) {
570                     t.id = t.vals[i].toString().intern();
571                     t.keys[i] = null;
572                     continue;
573                 }
574
575                 t.keys[i] = t.keys[i].intern();
576
577                 String valString = t.vals[i].toString();
578                 
579                 if (valString.equals("true")) t.vals[i] = Boolean.TRUE;
580                 else if (valString.equals("false")) t.vals[i] = Boolean.FALSE;
581                 else if (valString.equals("null")) t.vals[i] = null;
582                 else {
583                     boolean hasNonNumeral = false;
584                     boolean periodUsed = false;
585                     for(int j=0; j<valString.length(); j++)
586                         if (j == 0 && valString.charAt(j) == '-') {
587                         } else if (valString.charAt(j) == '.' && !periodUsed && j != valString.length() - 1) {
588                             periodUsed = true;
589                         } else if (!Character.isDigit(valString.charAt(j))) {
590                             hasNonNumeral = true;
591                             break;
592                         }
593                     if (valString.length() > 0 && !hasNonNumeral) t.vals[i] = new Double(valString);
594                     else t.vals[i] = valString.intern();
595                 }
596
597                 // bump thisbox to the front of the pack
598                 if (t.keys[i].equals("thisbox")) {
599                     t.keys[i] = t.keys[0];
600                     t.keys[0] = "thisbox";
601                     Object o = t.vals[0];
602                     t.vals[0] = t.vals[i];
603                     t.vals[i] = o;
604                 }
605             }
606         }
607
608         /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
609         private int partitionAttributes(int left, int right) {
610             int i, j, middle;
611             middle = (left + right) / 2;
612             String s = t.keys[right]; t.keys[right] = t.keys[middle]; t.keys[middle] = s;
613             Object o = t.vals[right]; t.vals[right] = t.vals[middle]; t.vals[middle] = o;
614             for (i = left - 1, j = right; ; ) {
615                 while (t.keys[++i].compareTo(t.keys[right]) < 0);
616                 while (j > left && t.keys[--j].compareTo(t.keys[right]) > 0);
617                 if (i >= j) break;
618                 s = t.keys[i]; t.keys[i] = t.keys[j]; t.keys[j] = s;
619                 o = t.vals[i]; t.vals[i] = t.vals[j]; t.vals[j] = o;
620             }
621             s = t.keys[right]; t.keys[right] = t.keys[i]; t.keys[i] = s;
622             o = t.vals[right]; t.vals[right] = t.vals[i]; t.vals[i] = o;
623             return i;
624         }
625
626         /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
627         private void quickSortAttributes(int left, int right) {
628             if (left >= right) return;
629             int p = partitionAttributes(left, right);
630             quickSortAttributes(left, p - 1);
631             quickSortAttributes(p + 1, right);
632         }
633
634         public void endElement(XML.Element c) throws XML.SchemaException {
635             if (rootNodeHasBeenEncountered && !templateNodeHasBeenEncountered) {
636                 if ("static".equals(nameOfHeaderNodeBeingProcessed) && t.content != null) t.staticscript = genscript(true);
637                 nameOfHeaderNodeBeingProcessed = null;
638
639             } else if (templateNodeHasBeenEncountered && !templateNodeHasBeenFinished) {
640                 // turn our childvect into a Template[]
641                 t.childvect.copyInto(t.children = new Template[t.childvect.size()]);
642                 t.childvect = null;
643                 if (t.content != null) t.script = genscript(false);
644                 
645                 if (nodeStack.size() == 0) {
646                     // </template>
647                     templateNodeHasBeenFinished = true;
648
649                 } else {
650                     // add this template as a child of its parent
651                     Template oldt = t;
652                     t = (Template)nodeStack.lastElement();
653                     nodeStack.setSize(nodeStack.size() - 1);
654                     t.childvect.addElement(oldt);
655                 }
656
657             }
658         }
659
660         private Script genscript(boolean isstatic) {
661             Script thisscript = null;
662             Context cx = Context.enter();
663             cx.setOptimizationLevel(-1);
664
665             try {
666                 thisscript = cx.compileReader(null, new StringReader(t.content.toString()), t.nodeName + (isstatic ? "._" : ""), t.content_start, null);
667             } catch (EcmaError ee) {
668                 if (Log.on) Log.log(this, ee.getMessage() + " at " + ee.getSourceName() + ":" + ee.getLineNumber());
669                 thisscript = null;
670             } catch (EvaluatorException ee) {
671                 if (Log.on) Log.log(this, "  ERROR: " + ee.getMessage());
672                 thisscript = null;
673             } catch (IOException ioe) {
674                 if (Log.on) Log.log(this, "  ERROR: " + ioe.getMessage());
675                 thisscript = null;
676             }
677
678             t.content = null;
679             t.content_start = 0;
680             t.content_lines = 0;
681             return thisscript;
682         }
683
684         public void characters(char[] ch, int start, int length) throws XML.SchemaException {
685             // invoke the max-column-length and no-tab crusade
686             if (getCol() + length > MAX_COLUMN) throw new XML.SchemaException(
687                 t.nodeName+ ":" + getLine() + ": lines longer than " + MAX_COLUMN + " characters not allowed");
688
689             for (int i=0; length >i; i++) if (ch[start+i] == '\t') throw new XML.SchemaException(
690                 t.nodeName+ ":" + getLine() + "," + getCol() + ": tabs are not allowed in XWT files");
691
692             if ("static".equals(nameOfHeaderNodeBeingProcessed) || templateNodeHasBeenEncountered) {
693                 if (t.content == null) {
694                     t.content_start = getLine();
695                     t.content_lines = 0;
696                     t.content = new StringBuffer();
697                 }
698
699                 t.content.append(ch, start, length);
700                 t.content_lines++;
701
702             } else if (nameOfHeaderNodeBeingProcessed != null) {
703                 throw new XML.SchemaException("header node <" + nameOfHeaderNodeBeingProcessed + "> cannot have text content");
704             }
705         }
706
707         public void whitespace(char[] ch, int start, int length) throws XML.SchemaException {
708         }
709     }
710
711 }
712
713