2003/09/21 10:26:53
[org.ibex.core.git] / src / org / xwt / Template.java
1 // Copyright 2003 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.xwt.js.*;
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 JS.CompiledFunction staticscript = null;
77
78     /** the script on this node */
79     private JS.CompiledFunction 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     /** a template cache so that only one Template object is created for each xwt */
99     private static Hashtable cache = new Hashtable(1000);
100
101     /** The default importlist; in future revisions this will contain "xwt.*" */
102     public static final String[] defaultImportList = new String[] { };
103
104     /** returns the appropriate template, resolving and theming as needed */
105     public static Template getTemplate(String name, String[] importlist) {
106         String resolved = Resources.resolve(name + ".xwt", importlist);
107         Template t = resolved == null ? null : (Template)cache.get(resolved.substring(0, resolved.length() - 4));
108         if (t != null) return t;
109         if (resolved == null) return null;
110
111         // note that Templates in xwar's are instantiated as read in via loadStream() --
112         // the following code only runs when XWT is reading templates from a filesystem.
113         ByteArrayInputStream bais = new ByteArrayInputStream(Resources.getResource(resolved));
114         return buildTemplate(bais, resolved.substring(0, resolved.length() - 4));
115     }
116
117     public static Template buildTemplate(InputStream is, String nodeName) {
118         return buildTemplate(is, nodeName, new TemplateHelper());
119     }
120
121     public static Template buildTemplate(InputStream is, String nodeName, TemplateHelper t) {
122         try {
123             return new Template(is, nodeName, t);
124         } catch (XML.SchemaException e) {
125             if (Log.on) Log.log(Template.class, "error parsing template " + nodeName);
126             if (Log.on) Log.log(Template.class, e.getMessage());
127             return null;
128         } catch (XML.XMLException e) {
129             if (Log.on) Log.log(Template.class, "error parsing template at " + nodeName + ":" + e.getLine() + "," + e.getCol());
130             if (Log.on) Log.log(Template.class, e.getMessage());
131             return null;
132         } catch (IOException e) {
133             if (Log.on) Log.log(Template.class, "IOException while parsing template " + nodeName + " -- this should never happen");
134             if (Log.on) Log.log(Template.class, e);
135             return null;
136         }
137     }
138
139
140     // Methods to apply templates ////////////////////////////////////////////////////////
141
142     private Template(String nodeName) {
143         this.nodeName = nodeName;
144         cache.put(nodeName, this);
145     }
146     private Template(InputStream is, String nodeName, TemplateHelper th) throws XML.XMLException, IOException {
147         this(nodeName);
148         th.parseit(is, this);
149     }
150
151     /** calculates, caches, and returns an integer approximation of how long it will take to apply this template, including pre/post and children */
152     int numUnits() {
153         link();
154         if (numunits != -1) return numunits;
155         numunits = 1;
156         for(int i=0; _preapply != null && i<_preapply.length; i++) if (_preapply[i] != null) numunits += _preapply[i].numUnits();
157         for(int i=0; _postapply != null && i<_postapply.length; i++) if (_postapply[i] != null) numunits += _postapply[i].numUnits();
158         if (script != null) numunits += 10;
159         numunits += keys == null ? 0 : keys.length;
160         for(int i=0; children != null && i<children.length; i++) numunits += children[i].numUnits();
161         return numunits;
162     }
163     
164     /** Applies the template to Box b
165      *  @param pboxes a vector of all box parents on which to put $-references
166      *  @param ptemplates a vector of the nodeNames to recieve private references on the pboxes
167      */
168     void apply(Box b, Vec pboxes, Vec ptemplates, JS.Callable callback, int numerator, int denominator, Res resourceRoot) {
169
170         int original_numerator = numerator;
171
172         if (pboxes == null) {
173             pboxes = new Vec();
174             ptemplates = new Vec();
175         }
176
177         if (id != null && !id.equals(""))
178             for(int i=0; i<pboxes.size(); i++) {
179                 Box parent = (Box)pboxes.elementAt(i);
180                 String parentNodeName = (String)ptemplates.elementAt(i);
181                 parent.put("$" + id, b);
182             }
183
184         if (script != null || (redirect != null && !"self".equals(redirect))) {
185             pboxes.addElement(b);
186             ptemplates.addElement(nodeName);
187         }
188
189         int numids = pboxes.size();
190         
191         link();
192
193         for(int i=0; _preapply != null && i<_preapply.length; i++)
194             if (_preapply[i] != null) {
195                 _preapply[i].apply(b, null, null, callback, numerator, denominator, resourceRoot);
196                 numerator += _preapply[i].numUnits();
197             }
198
199         for (int i=0; children != null && i<children.length; i++) {
200             Box newkid = new Box();
201             children[i].apply(newkid, pboxes, ptemplates, callback, numerator, denominator, resourceRoot);
202             b.put(Integer.MAX_VALUE, newkid);
203             numerator += children[i].numUnits();
204         }
205
206         // whom to redirect to; doesn't take effect until after script runs
207         Box redir = null;
208         if (redirect != null && !"self".equals(redirect)) redir = (Box)b.get("$" + redirect);
209
210         if (script != null) try {
211             script.call(new JS.Array(), new PerInstantiationScope(b, resourceRoot));
212         } catch (JS.Exn e) {
213             if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
214         }
215
216         for(int i=0; keys != null && i<keys.length; i++) {
217             try {
218                 if (keys[i] == null) { }
219                 else if (keys[i].equals("border") || keys[i].equals("image") &&
220                         !vals[i].toString().startsWith("http://") && !vals[i].toString().startsWith("https://")) {
221                     String s = Resources.resolve(vals[i].toString() + ".png", importlist);
222                     if (s != null) b.put(keys[i], s.substring(0, s.length() - 4));
223                     else if (Log.on) Log.log(this, "unable to resolve image " + vals[i].toString() + " referenced in attributes of " + nodeName); 
224                 }
225                 else b.put(keys[i], vals[i]);
226             } catch(JS.Exn e) {
227                 if(Log.on) Log.log(this,"WARNING: uncaught ecmascript exception while putting attr \"" + keys[i] + 
228                     "\" of " + nodeName + " : " + e.getMessage());
229             }
230         }
231
232         if (redirect != null && !"self".equals(redirect)) b.redirect = redir;
233
234         for(int i=0; _postapply != null && i<_postapply.length; i++)
235             if (_postapply[i] != null) {
236                 _postapply[i].apply(b, null, null, callback, numerator, denominator, resourceRoot);
237                 numerator += _postapply[i].numUnits();
238             }
239
240         pboxes.setSize(numids);
241         ptemplates.setSize(numids);
242
243         numerator = original_numerator + numUnits();
244
245         if (callback != null)
246             try {
247                 JS.Array args = new JS.Array();
248                 args.addElement(new Double(numerator));
249                 args.addElement(new Double(denominator));
250                 callback.call(args);
251             } catch (JS.Exn e) {
252                 if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e);
253             }
254
255         if (Thread.currentThread() instanceof ThreadMessage) try {
256             XWT.sleep(0);
257         } catch (JS.Exn e) {
258             if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e);
259         }
260     }
261
262
263     // Theming Logic ////////////////////////////////////////////////////////////
264
265     /** helper method to recursively gather up the list of keys to be preserved */
266     private void gatherPreserves(Vec v) {
267         for(int i=0; preserve != null && i<preserve.length; i++) v.addElement(preserve[i]);
268         for(int i=0; _preapply != null && i<_preapply.length; i++) if (_preapply[i] != null) _preapply[i].gatherPreserves(v);
269         for(int i=0; _postapply != null && i<_postapply.length; i++) if (_postapply[i] != null) _postapply[i].gatherPreserves(v);
270     }
271
272     /** runs statics, resolves string references to other templates into actual Template instance references, and sets <tt>change</tt> as needed */
273     void link() { link(false); }
274
275     /** same as link(), except that with a true value, it will force a re-link */
276     private void link(boolean force) {
277
278         if (staticscript != null) try { 
279             JS.Scope s = Static.createStatic(nodeName, false);
280             if (staticscript != null) {
281                 JS.CompiledFunction temp = staticscript;
282                 staticscript = null;
283
284                 // we layer a transparent scope over the Static so that we can catch requests for the xwt object
285                 // yet not screw up paths that include a package called xwt (ie xwt.static.org.xwt.foo)
286                 JS.Scope varScope = new JS.Scope(s) {
287                         public boolean isTransparent() { return true; }
288                         public Object get(Object key) {
289                             if ("xwt".equals(key)) return XWT.singleton; else return super.get(key);
290                         } };
291
292                 temp.call(new JS.Array(), varScope);
293             }
294         } catch (JS.Exn e) {
295             if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
296         }
297
298         if (!(force || (preapply != null && _preapply == null) || (postapply != null && _postapply == null))) return;
299         
300         if (preapply != null) {
301             if (_preapply == null) _preapply = new Template[preapply.length];
302             for(int i=0; i<_preapply.length; i++) {
303                 Template t = getTemplate(preapply[i], importlist);
304                 if (t != _preapply[i]) changed = true;
305                 _preapply[i] = t;
306             }
307         }
308         if (postapply != null) {
309             if (_postapply == null) _postapply = new Template[postapply.length];
310             for(int i=0; i<_postapply.length; i++) {
311                 Template t = getTemplate(postapply[i], importlist);
312                 if (t != _postapply[i]) changed = true;
313                 _postapply[i] = t;
314             }
315         }
316
317         for(int i=0; children != null && i<children.length; i++) children[i].link(force);
318     }
319
320
321     // XML Parsing /////////////////////////////////////////////////////////////////
322
323     /** handles XML parsing; builds a Template tree as it goes */
324     static final class TemplateHelper extends XML {
325
326         TemplateHelper() { }
327
328         /** parse an XML input stream, building a Template tree off of <tt>root</tt> */
329         void parseit(InputStream is, Template root) throws XML.XMLException, IOException {
330             rootNodeHasBeenEncountered = false;
331             templateNodeHasBeenEncountered = false;
332             staticNodeHasBeenEncountered = false;
333             templateNodeHasBeenFinished = false;
334             nameOfHeaderNodeBeingProcessed = null;
335
336             nodeStack.setSize(0);
337             importlist.setSize(0);
338             preapply.setSize(0);
339             postapply.setSize(0);
340
341             importlist.fromArray(defaultImportList);
342
343             t = root;
344             parse(new InputStreamReader(is)); 
345         }
346
347         /** parsing state: true iff we have already encountered the <xwt> open-tag */
348         boolean rootNodeHasBeenEncountered = false;
349
350         /** parsing state: true iff we have already encountered the <template> open-tag */
351         boolean templateNodeHasBeenEncountered = false;
352
353         /** parsing state: true iff we have already encountered the <static> open-tag */
354         boolean staticNodeHasBeenEncountered = false;
355
356         /** parsing state: true iff we have already encountered the <template> close-tag */
357         boolean templateNodeHasBeenFinished = false;
358
359         /** parsing state: If we have encountered the open tag of a header node, but not the close tag, this is the name of
360          *  that tag; otherwise, it is null. */
361         String nameOfHeaderNodeBeingProcessed = null;
362
363         /** stack of Templates whose XML elements we have seen open-tags for but not close-tags */
364         Vec nodeStack = new Vec();
365
366         /** builds up the list of imports */
367         Vec importlist = new Vec();
368
369         /** builds up the list of preapplies */
370         Vec preapply = new Vec();
371
372         /** builds up the list of postapplies */
373         Vec postapply = new Vec();
374
375         /** the template we're currently working on */
376         Template t = null;
377
378         public void startElement(XML.Element c) throws XML.SchemaException {
379             if (templateNodeHasBeenFinished) {
380                 throw new XML.SchemaException("no elements may appear after the <template> node");
381
382             } else if (!rootNodeHasBeenEncountered) {
383                 if (!"xwt".equals(c.localName)) throw new XML.SchemaException("root element was not <xwt>");
384                 if (c.len != 0) throw new XML.SchemaException("root element must not have attributes");
385                 rootNodeHasBeenEncountered = true;
386                 return;
387         
388             } else if (!templateNodeHasBeenEncountered) {
389                 if (nameOfHeaderNodeBeingProcessed != null) throw new XML.SchemaException("can't nest header nodes");
390                 nameOfHeaderNodeBeingProcessed = c.localName;
391
392                 if (c.localName.equals("import")) {
393                     if (c.len != 1 || !c.keys[0].equals("name"))
394                         throw new XML.SchemaException("<import> node must have exactly one attribute, which must be called 'name'");
395                     String importpackage = c.vals[0].toString();
396                     if (importpackage.endsWith(".*")) importpackage = importpackage.substring(0, importpackage.length() - 2);
397                     importlist.addElement(importpackage);
398                     return;
399
400                 } else if (c.localName.equals("redirect")) {
401                     if (c.len != 1 || !c.keys[0].equals("target"))
402                         throw new XML.SchemaException("<redirect> node must have exactly one attribute, which must be called 'target'");
403                     if (t.redirect != null)
404                         throw new XML.SchemaException("the <redirect> header element may not appear more than once");
405                     t.redirect = c.vals[0].toString();
406                     if(t.redirect.equals("null")) t.redirect = null;
407                     return;
408
409                 } else if (c.localName.equals("preapply")) {
410                     if (c.len != 1 || !c.keys[0].equals("name"))
411                         throw new XML.SchemaException("<preapply> node must have exactly one attribute, which must be called 'name'");
412                     preapply.addElement(c.vals[0]);
413                     return;
414
415                 } else if (c.localName.equals("postapply")) {
416                     if (c.len != 1 || !c.keys[0].equals("name"))
417                         throw new XML.SchemaException("<postapply> node must have exactly one attribute, which must be called 'name'");
418                     postapply.addElement(c.vals[0]);
419                     return;
420
421                 } else if (c.localName.equals("static")) {
422                     if (staticNodeHasBeenEncountered)
423                         throw new XML.SchemaException("the <static> header node may not appear more than once");
424                     if (c.len > 0)
425                         throw new XML.SchemaException("the <static> node may not have attributes");
426                     staticNodeHasBeenEncountered = true;
427                     return;
428
429                 } else if (c.localName.equals("preserve")) {
430                     if (c.len != 1 || !c.keys[0].equals("attributes"))
431                         throw new XML.SchemaException("<preserve> node must have exactly one attribute, which must be called 'attributes'");
432                     if (t.preserve != null)
433                         throw new XML.SchemaException("<preserve> header element may not appear more than once");
434
435                     StringTokenizer tok = new StringTokenizer(c.vals[0].toString(), ",", false);
436                     t.preserve = new String[tok.countTokens()];
437                     for(int i=0; i<t.preserve.length; i++) t.preserve[i] = tok.nextToken();
438                     return;
439
440                 } else if (c.localName.equals("template")) {
441                     // finalize importlist/preapply/postapply, since they can't change from here on
442                     t.startLine = getLine();
443                     importlist.toArray(t.importlist = new String[importlist.size()]);
444                     if (preapply.size() > 0) preapply.copyInto(t.preapply = new String[preapply.size()]);
445                     if (postapply.size() > 0) postapply.copyInto(t.postapply = new String[postapply.size()]);
446                     importlist.setSize(0); preapply.setSize(0); postapply.setSize(0);
447                     templateNodeHasBeenEncountered = true;
448
449                 } else {
450                     throw new XML.SchemaException("unrecognized header node \"" + c.localName + "\"");
451
452                 }
453
454             } else {
455
456                 // push the last node we were in onto the stack
457                 nodeStack.addElement(t);
458
459                 // instantiate a new node, and set its nodeName/importlist/preapply
460                 Template t2 = new Template(t.nodeName + "." + t.childvect.size());
461                 t2.importlist = t.importlist;
462                 t2.startLine = getLine();
463                 if (!c.localName.equals("box")) t2.preapply = new String[] { c.localName };
464
465                 // make the new node the current node
466                 t = t2;
467
468             }
469
470             // TODO: Sort contents straight from one array to another
471             t.keys = new String[c.len];
472             t.vals = new Object[c.len];
473             System.arraycopy(c.keys, 0, t.keys, 0, c.len);
474             System.arraycopy(c.vals, 0, t.vals, 0, c.len);
475             quickSortAttributes(0, t.keys.length - 1);
476
477             for(int i=0; i<t.keys.length; i++) {
478                 if (t.keys[i].equals("id")) {
479                     t.id = t.vals[i].toString().intern();
480                     t.keys[i] = null;
481                     continue;
482                 }
483
484                 t.keys[i] = t.keys[i].intern();
485
486                 String valString = t.vals[i].toString();
487                 
488                 if (valString.equals("true")) t.vals[i] = Boolean.TRUE;
489                 else if (valString.equals("false")) t.vals[i] = Boolean.FALSE;
490                 else if (valString.equals("null")) t.vals[i] = null;
491                 else {
492                     boolean hasNonNumeral = false;
493                     boolean periodUsed = false;
494                     for(int j=0; j<valString.length(); j++)
495                         if (j == 0 && valString.charAt(j) == '-') {
496                         } else if (valString.charAt(j) == '.' && !periodUsed && j != valString.length() - 1) {
497                             periodUsed = true;
498                         } else if (!Character.isDigit(valString.charAt(j))) {
499                             hasNonNumeral = true;
500                             break;
501                         }
502                     if (valString.length() > 0 && !hasNonNumeral) t.vals[i] = new Double(valString);
503                     else t.vals[i] = valString.intern();
504                 }
505
506                 // bump thisbox to the front of the pack
507                 if (t.keys[i].equals("thisbox")) {
508                     t.keys[i] = t.keys[0];
509                     t.keys[0] = "thisbox";
510                     Object o = t.vals[0];
511                     t.vals[0] = t.vals[i];
512                     t.vals[i] = o;
513                 }
514             }
515         }
516
517         /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
518         private int partitionAttributes(int left, int right) {
519             int i, j, middle;
520             middle = (left + right) / 2;
521             String s = t.keys[right]; t.keys[right] = t.keys[middle]; t.keys[middle] = s;
522             Object o = t.vals[right]; t.vals[right] = t.vals[middle]; t.vals[middle] = o;
523             for (i = left - 1, j = right; ; ) {
524                 while (t.keys[++i].compareTo(t.keys[right]) < 0);
525                 while (j > left && t.keys[--j].compareTo(t.keys[right]) > 0);
526                 if (i >= j) break;
527                 s = t.keys[i]; t.keys[i] = t.keys[j]; t.keys[j] = s;
528                 o = t.vals[i]; t.vals[i] = t.vals[j]; t.vals[j] = o;
529             }
530             s = t.keys[right]; t.keys[right] = t.keys[i]; t.keys[i] = s;
531             o = t.vals[right]; t.vals[right] = t.vals[i]; t.vals[i] = o;
532             return i;
533         }
534
535         /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
536         private void quickSortAttributes(int left, int right) {
537             if (left >= right) return;
538             int p = partitionAttributes(left, right);
539             quickSortAttributes(left, p - 1);
540             quickSortAttributes(p + 1, right);
541         }
542
543         public void endElement(XML.Element c) throws XML.SchemaException {
544             if (rootNodeHasBeenEncountered && !templateNodeHasBeenEncountered) {
545                 if ("static".equals(nameOfHeaderNodeBeingProcessed) && t.content != null) t.staticscript = genscript(true);
546                 nameOfHeaderNodeBeingProcessed = null;
547
548             } else if (templateNodeHasBeenEncountered && !templateNodeHasBeenFinished) {
549                 // turn our childvect into a Template[]
550                 t.childvect.copyInto(t.children = new Template[t.childvect.size()]);
551                 t.childvect = null;
552                 if (t.content != null) t.script = genscript(false);
553                 
554                 if (nodeStack.size() == 0) {
555                     // </template>
556                     templateNodeHasBeenFinished = true;
557
558                 } else {
559                     // add this template as a child of its parent
560                     Template oldt = t;
561                     t = (Template)nodeStack.lastElement();
562                     nodeStack.setSize(nodeStack.size() - 1);
563                     t.childvect.addElement(oldt);
564                 }
565
566             }
567         }
568
569         private JS.CompiledFunction genscript(boolean isstatic) {
570             JS.CompiledFunction thisscript = null;
571             try {
572                 thisscript = JS.parse(t.nodeName + (isstatic ? "._" : ""), t.content_start, new StringReader(t.content.toString()));
573             } catch (JS.Exn ee) {
574                 if (Log.on) Log.log(this, "  ERROR: " + ee.getMessage());
575                 thisscript = null;
576             } catch (IOException ioe) {
577                 if (Log.on) Log.log(this, "  ERROR: " + ioe.getMessage());
578                 thisscript = null;
579             }
580
581             t.content = null;
582             t.content_start = 0;
583             t.content_lines = 0;
584             return thisscript;
585         }
586
587         public void characters(char[] ch, int start, int length) throws XML.SchemaException {
588             // invoke the no-tab crusade
589             for (int i=0; length >i; i++) if (ch[start+i] == '\t') throw new XML.SchemaException(
590                 t.nodeName+ ":" + getLine() + "," + getCol() + ": tabs are not allowed in XWT files");
591
592             if ("static".equals(nameOfHeaderNodeBeingProcessed) || templateNodeHasBeenEncountered) {
593                 if (t.content == null) {
594                     t.content_start = getLine();
595                     t.content_lines = 0;
596                     t.content = new StringBuffer();
597                 }
598
599                 t.content.append(ch, start, length);
600                 t.content_lines++;
601
602             } else if (nameOfHeaderNodeBeingProcessed != null) {
603                 throw new XML.SchemaException("header node <" + nameOfHeaderNodeBeingProcessed + "> cannot have text content");
604             }
605         }
606
607         public void whitespace(char[] ch, int start, int length) throws XML.SchemaException {
608         }
609     }
610
611     private static class PerInstantiationScope extends JS.Scope {
612         Res resourceRoot = null;
613         public PerInstantiationScope(Scope parentScope, Res resourceRoot) {
614             super(parentScope);
615             this.resourceRoot = resourceRoot;
616         }
617         public boolean isTransparent() { return true; }
618         public boolean has(Object key) { return false; }
619         public void declare(String s) { super.declare(s); }
620         public Object get(Object key) {
621             // FIXME: access statics here
622             if (Box.SpecialBoxProperty.specialBoxProperties.get(key) == null &&
623                 !super.has(key)) {
624                 Object ret = resourceRoot.get(key);
625                 if (ret != null) return ret;
626                 throw new JS.Exn("must declare " + key + " before using it!");
627             }
628             return super.get(key);
629         }
630         public void put(Object key, Object val) {
631             // FIXME: access statics here
632             if (Box.SpecialBoxProperty.specialBoxProperties.get(key) == null &&
633                 !super.has(key)) {
634                 throw new JS.Exn("must declare " + key + " before using it!");
635             }
636             super.put(key, val);
637         }
638     }
639
640 }
641
642