2002/05/16 04:00:52
[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
94     // Static data/methods ///////////////////////////////////////////////////////////////////
95
96     /** a template cache so that only one Template object is created for each xwt */
97     private static Hashtable cache = new Hashtable(1000);
98
99     /** The default importlist; in future revisions this will contain "xwt.*" */
100     public static final String[] defaultImportList = new String[] { };
101
102     /** returns the appropriate template, resolving and theming as needed */
103     public static Template getTemplate(String name, String[] importlist) {
104         String resolved = Resources.resolve(name + ".xwt", importlist);
105         Template t = resolved == null ? null : (Template)cache.get(resolved.substring(0, resolved.length() - 4));
106         if (t != null) return t;
107         if (resolved == null) return null;
108
109         // note that Templates in xwar's are instantiated as read in via loadStream() --
110         // the following code only runs when XWT is reading templates from a filesystem.
111         ByteArrayInputStream bais = new ByteArrayInputStream(Resources.getResource(resolved));
112         return buildTemplate(bais, resolved.substring(0, resolved.length() - 4));
113     }
114
115     public static Template buildTemplate(InputStream is, String nodeName) {
116         try {
117             return new Template(is, nodeName);
118         } catch (XML.SAXParseException e) {
119             if (Log.on) Log.log(Template.class, "error parsing template at " + nodeName + ":" + e.getLineNumber() + "," + e.getColumnNumber());
120             if (Log.on) Log.log(Template.class, e);
121             return null;
122         } catch (XML.SAXException e) {
123             if (Log.on) Log.log(Template.class, "error parsing template " + nodeName);
124             if (Log.on) Log.log(Template.class, e);
125             return null;
126         } catch (TemplateException te) {
127             if (Log.on) Log.log(Template.class, "error parsing template " + nodeName);
128             if (Log.on) Log.log(Template.class, te);
129             return null;
130         } catch (IOException e) {
131             if (Log.on) Log.log(Template.class, "IOException while parsing template " + nodeName + " -- this should never happen");
132             if (Log.on) Log.log(Template.class, e);
133             return null;
134         }
135     }
136
137
138     // Methods to apply templates ////////////////////////////////////////////////////////
139
140     private Template() { } 
141     private Template(InputStream is, String nodeName) throws XML.SAXException, IOException {
142         this.nodeName = nodeName;
143         cache.put(nodeName, this);
144         new TemplateHelper().parseit(is, this);
145     }
146
147     /** calculates, caches, and returns an integer approximation of how long it will take to apply this template, including pre/post and children */
148     int numUnits() {
149         link();
150         if (numunits != -1) return numunits;
151         numunits = 1;
152         for(int i=0; _preapply != null && i<_preapply.length; i++) if (_preapply[i] != null) numunits += _preapply[i].numUnits();
153         for(int i=0; _postapply != null && i<_postapply.length; i++) if (_postapply[i] != null) numunits += _postapply[i].numUnits();
154         if (script != null) numunits += 10;
155         numunits += keys == null ? 0 : keys.length;
156         for(int i=0; children != null && i<children.length; i++) numunits += children[i].numUnits();
157         return numunits;
158     }
159     
160     /** Applies the template to Box b
161      *  @param pboxes a vector of all box parents on which to put $-references
162      *  @param ptemplates a vector of the nodeNames to recieve private references on the pboxes
163      */
164     void apply(Box b, Vec pboxes, Vec ptemplates) {
165
166         if (pboxes == null) {
167             pboxes = new Vec();
168             ptemplates = new Vec();
169         }
170
171         if (id != null && !id.equals(""))
172             for(int i=0; i<pboxes.size(); i++) {
173                 Box parent = (Box)pboxes.elementAt(i);
174                 String parentNodeName = (String)ptemplates.elementAt(i);
175                 parent.putPrivately("$" + id, b, parentNodeName);
176             }
177
178         if (script != null || (redirect != null && !"self".equals(redirect))) {
179             pboxes.addElement(b);
180             ptemplates.addElement(nodeName);
181         }
182
183         int numids = pboxes.size();
184         
185         link();
186
187         for(int i=0; _preapply != null && i<_preapply.length; i++)
188             if (_preapply[i] != null) _preapply[i].apply(b, null, null);
189
190         for (int i=0; children != null && i<children.length; i++)
191             b.put(Integer.MAX_VALUE, null, new Box(children[i], pboxes, ptemplates));
192
193         // whom to redirect to; doesn't take effect until after script runs
194         Box redir = null;
195         if (redirect != null && !"self".equals(redirect))
196             redir = (Box)b.getPrivately("$" + redirect, nodeName);
197
198         if (script != null) try {
199             Context cx = Context.enter();
200             script.exec(cx, b);
201         } catch (EcmaError e) {
202             if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
203             if (Log.on) Log.log(this, "         thrown while instantiating " + nodeName + " at " + e.getSourceName() + ":" + e.getLineNumber());
204         } catch (JavaScriptException e) {
205             if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
206             if (Log.on) Log.log(this, "         thrown while instantiating " + nodeName + " at " + e.sourceFile + ":" + e.line);
207         }
208
209         for(int i=0; keys != null && i<keys.length; i++)
210             if (keys[i] == null) { }
211             else if (keys[i].equals("border") || keys[i].equals("image")) {
212                 String s = Resources.resolve(vals[i].toString() + ".png", importlist);
213                 if (s != null) b.put(keys[i], null, s.substring(0, s.length() - 4));
214                 else if (Log.on) Log.log(this, "unable to resolve image " + vals[i].toString() + " referenced in attributes of " + nodeName); 
215             }
216             else b.put(keys[i], null, vals[i]);
217
218         if (redirect != null && !"self".equals(redirect)) b.redirect = redir;
219
220         for(int i=0; _postapply != null && i<_postapply.length; i++)
221             if (_postapply[i] != null) _postapply[i].apply(b, null, null);
222
223         pboxes.setSize(numids);
224         ptemplates.setSize(numids);
225
226         Main.instantiatedUnits += 1 + (script == null ? 0 : 10) + (keys == null ? 0 : keys.length);
227         Main.updateSplashScreen();
228     }
229
230
231     // Theming Logic ////////////////////////////////////////////////////////////
232
233     /** helper method to recursively gather up the list of keys to be preserved */
234     private void gatherPreserves(Vec v) {
235         for(int i=0; preserve != null && i<preserve.length; i++) v.addElement(preserve[i]);
236         for(int i=0; _preapply != null && i<_preapply.length; i++) if (_preapply[i] != null) _preapply[i].gatherPreserves(v);
237         for(int i=0; _postapply != null && i<_postapply.length; i++) if (_postapply[i] != null) _postapply[i].gatherPreserves(v);
238     }
239
240     /** adds a theme mapping, retemplatizing as needed */
241     public static void retheme(String from, String to) {
242         if (Log.on) Log.log(Template.class, "retheming from " + from + " to " + to);
243         XWF.flushXWFs();
244         Resources.mapFrom.addElement(from);
245         Resources.mapTo.addElement(to);
246
247         // clear changed marker and relink
248         Template[] t = new Template[cache.size()];
249         Enumeration e = cache.elements();
250         for(int i=0; e.hasMoreElements(); i++) t[i] = (Template)e.nextElement();
251         for(int i=0; i<t.length; i++) {
252             t[i].changed = false;
253             t[i].numunits = -1;
254             t[i].link(true);
255         }
256
257         for(int i=0; i<Surface.allSurfaces.size(); i++) {
258             Box b = ((Surface)Surface.allSurfaces.elementAt(i)).root;
259             if (b != null) reapply(b);
260         }
261     }
262
263     /** template reapplication procedure */
264     private static void reapply(Box b) {
265
266         // Ref 7.5.1: check if we need to retemplatize
267         boolean retemplatize = false;
268         if (b.templatename != null) {
269             Template t = getTemplate(b.templatename, b.importlist);
270             if (t != b.template) retemplatize = true;
271             b.template = t;
272         }
273         if (b.template != null && b.template.changed)
274             retemplatize = true;
275
276         if (retemplatize) {
277
278             // Ref 7.5.2: "Preserve all properties on the box mentioned in the <preserve> elements of any
279             //             of the templates which would be applied in step 7."
280             Vec keys = new Vec();
281             b.template.gatherPreserves(keys);
282             Object[] vals = new Object[keys.size()];
283             for(int i=0; i<keys.size(); i++) vals[i] = b.get(((String)keys.elementAt(i)), null);
284             
285             // Ref 7.5.3: "Remove and save all children of the box, or its redirect target, if it has one"
286             Box[] kids = null;
287             if (b.redirect != null) {
288                 kids = new Box[b.redirect.numChildren()];
289                 for(int i=b.redirect.numChildren() - 1; i >= 0; i--) {
290                     kids[i] = b.redirect.getChild(i);
291                     kids[i].remove();
292                 }
293             }
294             
295             // Ref 7.5.4: "Set the box's redirect target to self"
296             b.redirect = b;
297             
298             // Ref 7.5.5: "Remove all of the box's immediate children"
299             for(Box cur = b.getChild(b.numChildren() - 1); cur != null;) {
300                 Box oldcur = cur;
301                 cur = cur.prevSibling();
302                 oldcur.remove();
303             }
304             
305             // Ref 7.5.6: "Remove all traps set by scripts run during the application of any template to this box"
306             Trap.removeAllTrapsByBox(b);
307             
308             // Ref 7.5.7: "Apply the template to the box according to the usual application procedure"
309             b.template.apply(b, null, null);
310             
311             // Ref 7.5.8: "Re-add the saved children which were removed in step 3"
312             for(int i=0; kids != null && i<kids.length; i++) b.put(Integer.MAX_VALUE, null, kids[i]);
313             
314             // Ref 7.5.9: "Re-put any property values which were preserved in step 2"
315             for(int i=0; i<keys.size(); i++) b.put((String)keys.elementAt(i), null, vals[i]);
316         }        
317
318         // Recurse
319         for(Box j = b.getChild(0); j != null; j = j.nextSibling()) reapply(j);
320     }
321
322     /** runs statics, resolves string references to other templates into actual Template instance references, and sets <tt>change</tt> as needed */
323     void link() { link(false); }
324
325     /** same as link(), except that with a true value, it will force a re-link */
326     private void link(boolean force) {
327
328         if (staticscript != null) try { 
329             Scriptable s = Static.getStatic(nodeName);
330             if (staticscript != null) {
331                 Script temp = staticscript;
332                 ((InterpretedScript)temp).setParentScope(s);     // so we know how to handle Static.get("xwt")
333                 staticscript = null;
334                 temp.exec(Context.enter(), s);
335             }
336         } catch (EcmaError e) {
337             if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
338             if (Log.on) Log.log(this, "         thrown while executing <static/> block for " + nodeName +
339                                       " at " + e.getSourceName() + ":" + e.getLineNumber());
340         } catch (JavaScriptException e) {
341             if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
342             if (Log.on) Log.log(this, "         thrown while executing <static/> block for " + nodeName + " at " + e.sourceFile + ":" + e.line);
343         }
344
345         if (!(force || (preapply != null && _preapply == null) || (postapply != null && _postapply == null))) return;
346         
347         if (preapply != null) {
348             if (_preapply == null) _preapply = new Template[preapply.length];
349             for(int i=0; i<_preapply.length; i++) {
350                 Template t = getTemplate(preapply[i], importlist);
351                 if (t != _preapply[i]) changed = true;
352                 _preapply[i] = t;
353             }
354         }
355         if (postapply != null) {
356             if (_postapply == null) _postapply = new Template[postapply.length];
357             for(int i=0; i<_postapply.length; i++) {
358                 Template t = getTemplate(postapply[i], importlist);
359                 if (t != _postapply[i]) changed = true;
360                 _postapply[i] = t;
361             }
362         }
363
364         for(int i=0; children != null && i<children.length; i++) children[i].link(force);
365     }
366
367
368     // XML Parsing /////////////////////////////////////////////////////////////////
369
370     /** handles XML parsing; builds a Template tree as it goes */
371     private static class TemplateHelper extends XML {
372
373         TemplateHelper() {
374             for(int i=0; i<defaultImportList.length; i++) importlist.addElement(defaultImportList[i]);
375         }
376
377         /** parse an XML input stream, building a Template tree off of <tt>root</tt> */
378         void parseit(InputStream is, Template root) throws XML.SAXException, IOException {
379             t = root;
380             parse(new TabAndMaxColumnEnforcingReader(new InputStreamReader(is), root.nodeName)); 
381         }
382
383         /** parsing state: true iff we have already encountered the <xwt> open-tag */
384         boolean rootNodeHasBeenEncountered = false;
385
386         /** parsing state: true iff we have already encountered the <template> open-tag */
387         boolean templateNodeHasBeenEncountered = false;
388
389         /** parsing state: true iff we have already encountered the <static> open-tag */
390         boolean staticNodeHasBeenEncountered = false;
391
392         /** parsing state: true iff we have already encountered the <template> close-tag */
393         boolean templateNodeHasBeenFinished = false;
394
395         /** parsing state: If we have encountered the open tag of a header node, but not the close tag, this is the name of
396          *  that tag; otherwise, it is null. */
397         String nameOfHeaderNodeBeingProcessed = null;
398
399         /** stack of Templates whose XML elements we have seen open-tags for but not close-tags */
400         Vec nodeStack = new Vec();
401
402         /** builds up the list of imports */
403         Vec importlist = new Vec();
404
405         /** builds up the list of preapplies */
406         Vec preapply = new Vec();
407
408         /** builds up the list of postapplies */
409         Vec postapply = new Vec();
410
411         /** the template we're currently working on */
412         Template t = null;
413
414         public void startElement(String name, String[] keys, Object[] vals, int line, int col) throws XML.SAXException {
415
416             if (templateNodeHasBeenFinished) {
417                 throw new XML.SAXException("no elements may appear after the <template> node");
418
419             } else if (!rootNodeHasBeenEncountered) {
420                 if (!"xwt".equals(name)) throw new XML.SAXException("root element was not <xwt>");
421                 if (keys.length != 0) throw new XML.SAXException("root element must not have attributes");
422                 rootNodeHasBeenEncountered = true;
423                 return;
424         
425             } else if (!templateNodeHasBeenEncountered) {
426                 if (nameOfHeaderNodeBeingProcessed != null) throw new XML.SAXException("can't nest header nodes");
427                 nameOfHeaderNodeBeingProcessed = name;
428
429                 if (name.equals("import")) {
430                     if (keys.length != 1 || !keys[0].equals("name"))
431                         throw new XML.SAXException("<import> node must have exactly one attribute, which must be called 'name'");
432                     String importpackage = vals[0].toString();
433                     if (importpackage.endsWith(".*")) importpackage = importpackage.substring(0, importpackage.length() - 2);
434                     importlist.addElement(importpackage);
435                     return;
436
437                 } else if (name.equals("redirect")) {
438                     if (keys.length != 1 || !keys[0].equals("target"))
439                         throw new XML.SAXException("<redirect> node must have exactly one attribute, which must be called 'target'");
440                     if (t.redirect != null)
441                         throw new XML.SAXException("the <redirect> header element may not appear more than once");
442                     t.redirect = vals[0].toString();
443                     return;
444
445                 } else if (name.equals("preapply")) {
446                     if (keys.length != 1 || !keys[0].equals("name"))
447                         throw new XML.SAXException("<preapply> node must have exactly one attribute, which must be called 'name'");
448                     preapply.addElement(vals[0]);
449                     return;
450
451                 } else if (name.equals("postapply")) {
452                     if (keys.length != 1 || !keys[0].equals("name"))
453                         throw new XML.SAXException("<postapply> node must have exactly one attribute, which must be called 'name'");
454                     postapply.addElement(vals[0]);
455                     return;
456
457                 } else if (name.equals("static")) {
458                     if (staticNodeHasBeenEncountered)
459                         throw new XML.SAXException("the <static> header node may not appear more than once");
460                     if (keys.length > 0)
461                         throw new XML.SAXException("the <static> node may not have attributes");
462                     staticNodeHasBeenEncountered = true;
463                     return;
464
465                 } else if (name.equals("preserve")) {
466                     if (keys.length != 1 || !keys[0].equals("attributes"))
467                         throw new XML.SAXException("<preserve> node must have exactly one attribute, which must be called 'attributes'");
468                     if (t.preserve != null)
469                         throw new XML.SAXException("<preserve> header element may not appear more than once");
470
471                     StringTokenizer tok = new StringTokenizer(vals[0].toString(), ",", false);
472                     t.preserve = new String[tok.countTokens()];
473                     for(int i=0; i<t.preserve.length; i++) t.preserve[i] = tok.nextToken();
474                     return;
475
476                 } else if (name.equals("template")) {
477                     // finalize importlist/preapply/postapply, since they can't change from here on
478                     importlist.toArray(t.importlist = new String[importlist.size()]);
479                     if (preapply.size() > 0) preapply.copyInto(t.preapply = new String[preapply.size()]);
480                     if (postapply.size() > 0) postapply.copyInto(t.postapply = new String[postapply.size()]);
481                     importlist = preapply = postapply = null;
482                     templateNodeHasBeenEncountered = true;
483
484                 } else {
485                     throw new XML.SAXException("unrecognized header node \"" + name + "\"");
486
487                 }
488
489             } else {
490
491                 // push the last node we were in onto the stack
492                 nodeStack.addElement(t);
493
494                 // instantiate a new node, and set its nodeName/importlist/preapply
495                 Template t2 = new Template();
496                 t2.nodeName = t.nodeName + "." + t.childvect.size();
497                 t2.importlist = t.importlist;
498                 if (!name.equals("box")) t2.preapply = new String[] { name };
499
500                 // make the new node the current node
501                 t = t2;
502
503             }
504
505             t.keys = keys;
506             t.vals = vals;
507
508             quickSortAttributes(0, t.keys.length - 1);
509
510             for(int i=0; i<t.keys.length; i++) {
511                 if (t.keys[i].equals("id")) {
512                     t.id = vals[i].toString().intern();
513                     t.keys[i] = null;
514                     continue;
515                 }
516
517                 t.keys[i] = t.keys[i].intern();
518
519                 String valString = vals[i].toString();
520                 
521                 if (valString.equals("true")) t.vals[i] = Boolean.TRUE;
522                 else if (valString.equals("false")) t.vals[i] = Boolean.FALSE;
523                 else if (valString.equals("null")) t.vals[i] = null;
524                 else {
525                     boolean hasNonNumeral = false;
526                     boolean periodUsed = false;
527                     for(int j=0; j<valString.length(); j++)
528                         if (j == 0 && valString.charAt(j) == '-') {
529                         } else if (valString.charAt(j) == '.' && !periodUsed && j != valString.length() - 1)
530                             periodUsed = true;
531                         else if (valString.charAt(j) == '0' && (j == 0 || (j == 1 && valString.charAt(0) == '-'))) {
532                             hasNonNumeral = true;
533                             break;
534                         } else if (!Character.isDigit(valString.charAt(j))) {
535                             hasNonNumeral = true;
536                             break;
537                         }
538                     if (valString.length() > 0 && !hasNonNumeral) vals[i] = new Double(valString);
539                     else vals[i] = valString.intern();
540                 }
541
542                 // bump thisbox to the front of the pack
543                 if (t.keys[i].equals("thisbox")) {
544                     t.keys[i] = t.keys[0];
545                     t.keys[0] = "thisbox";
546                     Object o = t.vals[0];
547                     t.vals[0] = t.vals[i];
548                     t.vals[i] = o;
549                 }
550             }
551         }
552
553         /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
554         private int partitionAttributes(int left, int right) {
555             int i, j, middle;
556             middle = (left + right) / 2;
557             String s = t.keys[right]; t.keys[right] = t.keys[middle]; t.keys[middle] = s;
558             Object o = t.vals[right]; t.vals[right] = t.vals[middle]; t.vals[middle] = o;
559             for (i = left - 1, j = right; ; ) {
560                 while (t.keys[++i].compareTo(t.keys[right]) < 0);
561                 while (j > left && t.keys[--j].compareTo(t.keys[right]) > 0);
562                 if (i >= j) break;
563                 s = t.keys[i]; t.keys[i] = t.keys[j]; t.keys[j] = s;
564                 o = t.vals[i]; t.vals[i] = t.vals[j]; t.vals[j] = o;
565             }
566             s = t.keys[right]; t.keys[right] = t.keys[i]; t.keys[i] = s;
567             o = t.vals[right]; t.vals[right] = t.vals[i]; t.vals[i] = o;
568             return i;
569         }
570         
571         /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
572         private void quickSortAttributes(int left, int right) {
573             if (left >= right) return;
574             int p = partitionAttributes(left, right);
575             quickSortAttributes(left, p - 1);
576             quickSortAttributes(p + 1, right);
577         }
578         
579         public void endElement(String name, int line, int col) throws XML.SAXException {
580
581             boolean hasNonWhitespace = false;
582
583             int len = t == null || t.content == null ? 0 : t.content.length();
584             for(int i=0; t.content != null && i<len; i++)
585                 
586                 // ignore double-slash comment blocks
587                 if (t.content.charAt(i) == '/' && t.content.charAt(i + 1) == '/') {
588                     while(t.content.charAt(i) != '\n' && i<len) i++;
589                     i--;
590
591                 // ignore /* .. */ comment blocks
592                 } else if (i<len - 1 && t.content.charAt(i) == '/' && t.content.charAt(i + 1) == '*') {
593                     i += 2;
594                     while(i<len - 1 && !(t.content.charAt(i) == '*' && t.content.charAt(i + 1) == '/')) i++;
595                     if (i<len - 1 && t.content.charAt(i) == '*' && t.content.charAt(i + 1) == '/') i += 2;
596                     i--;
597
598                 // check for named functions
599                 } else if (i + 8 <= len && t.content.charAt(i) == 'f' && t.content.charAt(i+1) == 'u' &&
600                            t.content.charAt(i+2) == 'n' && t.content.charAt(i+3) == 'c' && t.content.charAt(i+4) == 't' &&
601                            t.content.charAt(i+5) == 'i' && t.content.charAt(i+6) == 'o' && t.content.charAt(i+7) == 'n') {
602                     int j = i + 8;
603                     while(j<len && Character.isWhitespace(t.content.charAt(j))) j++;
604                     if (j<len && t.content.charAt(j) != '(')
605                         throw new XML.SAXException("named functions are not permitted in XWT -- instead of \"function foo() { ... }\"," +
606                                         " use \"foo = function() { ... }\"");
607
608                 // replace " and " with " && "
609                 } else if (i + 5 < len && Character.isWhitespace(t.content.charAt(i)) &&
610                            t.content.charAt(i+1) == 'a' && t.content.charAt(i+2) == 'n' && t.content.charAt(i+3) == 'd' &&
611                            Character.isWhitespace(t.content.charAt(i + 4))) {
612                     t.content.setCharAt(i+1, '&');
613                     t.content.setCharAt(i+2, '&');
614                     t.content.setCharAt(i+3, ' ');
615                     hasNonWhitespace = true;
616
617                 // generic check for nonwhitespace
618                 } else if (!Character.isWhitespace(t.content.charAt(i))) {
619                     hasNonWhitespace = true;
620
621                 }
622             
623             if (rootNodeHasBeenEncountered && !templateNodeHasBeenEncountered) {
624                 if ("static".equals(nameOfHeaderNodeBeingProcessed) && hasNonWhitespace) t.staticscript = genscript(true);
625                 nameOfHeaderNodeBeingProcessed = null;
626
627             } else if (templateNodeHasBeenEncountered && !templateNodeHasBeenFinished) {
628
629                 // turn our childvect into a Template[]
630                 t.childvect.copyInto(t.children = new Template[t.childvect.size()]);
631                 t.childvect = null;
632                 if (hasNonWhitespace) t.script = genscript(false);
633                 
634                 if (nodeStack.size() == 0) {
635                     // </template>
636                     templateNodeHasBeenFinished = true;
637
638                 } else {
639                     // add this template as a child of its parent
640                     Template oldt = t;
641                     t = (Template)nodeStack.lastElement();
642                     nodeStack.setSize(nodeStack.size() - 1);
643                     t.childvect.addElement(oldt);
644                 }
645
646             }
647         }
648
649         private Script genscript(boolean isstatic) {
650             Script thisscript = null;
651             Context cx = Context.enter();
652             cx.setOptimizationLevel(-1);
653
654             try {
655                 thisscript = cx.compileReader(null, new StringReader(t.content.toString()), t.nodeName + (isstatic ? "._" : ""), t.content_start, null);
656             } catch (EcmaError ee) {
657                 if (Log.on) Log.log(this, ee.getMessage() + " at " + ee.getSourceName() + ":" + ee.getLineNumber());
658                 thisscript = null;
659             } catch (EvaluatorException ee) {
660                 if (Log.on) Log.log(this, "  ERROR: " + ee.getMessage());
661                 thisscript = null;
662             } catch (IOException ioe) {
663                 if (Log.on) Log.log(this, "IOException while compiling script; this should never happen");
664                 if (Log.on) Log.log(this, ioe);
665                 thisscript = null;
666             }
667
668             t.content = null;
669             t.content_start = 0;
670             t.content_lines = 0;
671             return thisscript;
672         }
673
674         public void content(char[] ch, int start, int length, int line, int col) throws XML.SAXException {
675             if ("static".equals(nameOfHeaderNodeBeingProcessed) || templateNodeHasBeenEncountered) {
676                 int contentlines = 0;
677                 for(int i=start; i<start + length; i++) if (ch[i] == '\n') contentlines++;
678                 line -= contentlines;
679
680                 if (t.content == null) {
681                     t.content_start = line;
682                     t.content_lines = 0;
683                     t.content = new StringBuffer();
684                 }
685
686                 for(int i=t.content_start + t.content_lines; i<line; i++) {
687                     t.content.append('\n');
688                     t.content_lines++;
689                 }
690
691                 t.content.append(ch, start, length);
692                 t.content_lines += contentlines;
693
694             } else if (nameOfHeaderNodeBeingProcessed != null) {
695                 throw new XML.SAXException("header node <" + nameOfHeaderNodeBeingProcessed + "> cannot have text content");
696
697             }
698
699         }
700
701     }
702
703     /** a filtering reader that watches for tabs and long lines */
704     private static class TabAndMaxColumnEnforcingReader extends FilterReader {
705         private int MAX_COLUMN = 150;
706         private int column = 0;
707         private int line = 1;
708         private boolean lastCharWasCR = false;
709         private String filename;
710         public TabAndMaxColumnEnforcingReader(Reader r, String filename) { super(r); this.filename = filename; }
711         public int read() {
712             if (Log.on) Log.log(this, this.getClass().getName() + ".read() not supported, this should never happen");
713             return -1;
714         }
715         public long skip(long numskip) {
716             if (Log.on) Log.log(this, this.getClass().getName() + ".skip() not supported; this should never happen");
717             return numskip;
718         }
719         public int read(char[] buf, int off, int len) throws IOException {
720             int ret = super.read(buf, off, len);
721             for(int i=off; i<off + ret; i++)
722                 if (buf[i] == '\t') {
723                     throw new TemplateException(filename + ":" + line + "," + column + ": tabs are not allowed in XWT files");
724                 } else if (buf[i] == '\r') {
725                     column = 0;
726                     line++;
727                     lastCharWasCR = true;
728                 } else if (buf[i] == '\n') {
729                     column = 0;
730                     if (!lastCharWasCR) line++;
731                 } else if (++column > MAX_COLUMN) {
732                     throw new TemplateException(filename + ":" + line + ": lines longer than " + MAX_COLUMN + " characters not allowed");
733                 } else {
734                     lastCharWasCR = false;
735                 }
736             return ret;
737         }
738     }
739
740     private static class TemplateException extends IOException {
741         TemplateException(String s) { super(s); }
742     }
743
744 }
745
746