1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
5 import java.util.zip.*;
8 import org.mozilla.javascript.*;
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 "._"
20 * Note that the Template instance corresponding to the
21 * <template/> 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>.
26 * See the XWT reference for information on the order in which
27 * templates are applied, attributes are put, and scripts are run.
29 public class Template {
31 // Instance Members ///////////////////////////////////////////////////////
33 /** this instance's nodeName */
36 /** the id of the redirect target; only meaningful on a root node */
37 String redirect = null;
39 /** templates that should be preapplied (in the order of application); only meaningful on a root node */
40 private String[] preapply;
42 /** 'linked' form of preapply -- the String references have been resolved into instance references */
43 private Template[] _preapply = null;
45 /** templates that should be postapplied (in the order of application); only meaningful on a root node */
46 private String[] postapply;
48 /** 'linked' form of postapply -- the String references have been resolved into instance references */
49 private Template[] _postapply = null;
51 /** keys to be "put" to instances of this template; elements correspond to those of vals */
52 private String[] keys;
54 /** values to be "put" to instances of this template; elements correspond to those of keys */
55 private Object[] vals;
57 /** array of strings representing the importlist for this template */
58 private String[] importlist;
60 /** child template objects */
61 private Template[] children;
63 /** an array of the names of properties to be preserved when retheming; only meaningful on a root node */
64 private String[] preserve = null;
66 /** the <tt>id</tt> attribute on this node */
67 private String id = "";
69 /** see numUnits(); -1 means that this value has not yet been computed */
70 private int numunits = -1;
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;
75 /** the script on the static node of this template, null if it has already been executed */
76 private Script staticscript = null;
78 /** the script on this node */
79 private Script script = null;
81 /** during XML parsing, this holds the list of currently-parsed children; null otherwise */
82 private Vec childvect = new Vec();
84 /** during XML parsing, this holds partially-read character data; null otherwise */
85 private StringBuffer content = null;
87 /** line number of the first line of <tt>content</tt> */
88 private int content_start = 0;
90 /** number of lines in <tt>content</tt> */
91 private int content_lines = 0;
93 /** the line number that this element starts on */
94 private int startLine = -1;
96 // Static data/methods ///////////////////////////////////////////////////////////////////
98 /** a template cache so that only one Template object is created for each xwt */
99 private static Hashtable cache = new Hashtable(1000);
101 /** The default importlist; in future revisions this will contain "xwt.*" */
102 public static final String[] defaultImportList = new String[] { };
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;
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));
117 public static Template buildTemplate(InputStream is, String nodeName) {
119 return new Template(is, nodeName);
120 } catch (XML.SAXParseException e) {
121 if (Log.on) Log.log(Template.class, "error parsing template at " + nodeName + ":" + e.getLineNumber() + "," + e.getColumnNumber());
122 if (Log.on) Log.log(Template.class, e);
124 } catch (XML.SAXException e) {
125 if (Log.on) Log.log(Template.class, "error parsing template " + nodeName);
126 if (Log.on) Log.log(Template.class, e);
128 } catch (TemplateException te) {
129 if (Log.on) Log.log(Template.class, "error parsing template " + nodeName);
130 if (Log.on) Log.log(Template.class, te);
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);
140 // Methods to apply templates ////////////////////////////////////////////////////////
142 private Template() { }
143 private Template(InputStream is, String nodeName) throws XML.SAXException, IOException {
144 this.nodeName = nodeName;
145 cache.put(nodeName, this);
146 new TemplateHelper().parseit(is, this);
149 /** calculates, caches, and returns an integer approximation of how long it will take to apply this template, including pre/post and children */
152 if (numunits != -1) return numunits;
154 for(int i=0; _preapply != null && i<_preapply.length; i++) if (_preapply[i] != null) numunits += _preapply[i].numUnits();
155 for(int i=0; _postapply != null && i<_postapply.length; i++) if (_postapply[i] != null) numunits += _postapply[i].numUnits();
156 if (script != null) numunits += 10;
157 numunits += keys == null ? 0 : keys.length;
158 for(int i=0; children != null && i<children.length; i++) numunits += children[i].numUnits();
162 /** Applies the template to Box b
163 * @param pboxes a vector of all box parents on which to put $-references
164 * @param ptemplates a vector of the nodeNames to recieve private references on the pboxes
166 void apply(Box b, Vec pboxes, Vec ptemplates, Function callback, int numerator, int denominator) {
168 int original_numerator = numerator;
170 if (pboxes == null) {
172 ptemplates = new Vec();
175 if (id != null && !id.equals(""))
176 for(int i=0; i<pboxes.size(); i++) {
177 Box parent = (Box)pboxes.elementAt(i);
178 String parentNodeName = (String)ptemplates.elementAt(i);
179 parent.putPrivately("$" + id, b, parentNodeName);
182 if (script != null || (redirect != null && !"self".equals(redirect))) {
183 pboxes.addElement(b);
184 ptemplates.addElement(nodeName);
187 int numids = pboxes.size();
191 for(int i=0; _preapply != null && i<_preapply.length; i++)
192 if (_preapply[i] != null) {
193 _preapply[i].apply(b, null, null, callback, numerator, denominator);
194 numerator += _preapply[i].numUnits();
197 for (int i=0; children != null && i<children.length; i++) {
198 b.put(Integer.MAX_VALUE, null, new Box(children[i], pboxes, ptemplates, callback, numerator, denominator));
199 numerator += children[i].numUnits();
202 // whom to redirect to; doesn't take effect until after script runs
204 if (redirect != null && !"self".equals(redirect))
205 redir = (Box)b.getPrivately("$" + redirect, nodeName);
207 if (script != null) try {
208 Context cx = Context.enter();
210 } catch (EcmaError e) {
211 if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
212 if (Log.on) Log.log(this, " thrown while instantiating " + nodeName + " at " + e.getSourceName() + ":" + e.getLineNumber());
213 } catch (JavaScriptException e) {
214 if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
215 if (Log.on) Log.log(this, " thrown while instantiating " + nodeName + " at " + e.sourceFile + ":" + e.line);
218 for(int i=0; keys != null && i<keys.length; i++) {
219 Context.enter().interpreterSourceFile = nodeName;
220 Context.enter().interpreterLine = startLine;
221 if (keys[i] == null) { }
222 else if (keys[i].equals("border") || keys[i].equals("image") &&
223 !vals[i].toString().startsWith("http://") && !vals[i].toString().startsWith("https://")) {
224 String s = Resources.resolve(vals[i].toString() + ".png", importlist);
225 if (s != null) b.put(keys[i], null, s.substring(0, s.length() - 4));
226 else if (Log.on) Log.log(this, "unable to resolve image " + vals[i].toString() + " referenced in attributes of " + nodeName);
228 else b.put(keys[i], null, vals[i]);
231 if (redirect != null && !"self".equals(redirect)) b.redirect = redir;
233 for(int i=0; _postapply != null && i<_postapply.length; i++)
234 if (_postapply[i] != null) {
235 _postapply[i].apply(b, null, null, callback, numerator, denominator);
236 numerator += _postapply[i].numUnits();
239 pboxes.setSize(numids);
240 ptemplates.setSize(numids);
242 numerator = original_numerator + numUnits();
244 if (callback != null)
246 callback.call(Context.enter(), null, null, new Object[] { new Double(numerator), new Double(denominator) });
247 } catch (EcmaError e) {
248 if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
249 if (Log.on) Log.log(this, " thrown from within progress callback at " + e.getSourceName() + ":" + e.getLineNumber());
250 } catch (JavaScriptException e) {
251 if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
252 if (Log.on) Log.log(this, " thrown from within progress callback at " + e.sourceFile + ":" + e.line);
255 if (Thread.currentThread() instanceof ThreadMessage) try {
256 XWT.yield.call(Context.enter(), null, null, null);
257 } catch (JavaScriptException e) {
258 if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
259 if (Log.on) Log.log(this, " thrown from within yield at " + e.sourceFile + ":" + e.line);
264 // Theming Logic ////////////////////////////////////////////////////////////
266 /** helper method to recursively gather up the list of keys to be preserved */
267 private void gatherPreserves(Vec v) {
268 for(int i=0; preserve != null && i<preserve.length; i++) v.addElement(preserve[i]);
269 for(int i=0; _preapply != null && i<_preapply.length; i++) if (_preapply[i] != null) _preapply[i].gatherPreserves(v);
270 for(int i=0; _postapply != null && i<_postapply.length; i++) if (_postapply[i] != null) _postapply[i].gatherPreserves(v);
273 /** adds a theme mapping, retemplatizing as needed */
274 public static void retheme(String from, String to) {
275 if (Log.on) Log.log(Template.class, "retheming from " + from + " to " + to);
277 Resources.mapFrom.addElement(from);
278 Resources.mapTo.addElement(to);
280 // clear changed marker and relink
281 Template[] t = new Template[cache.size()];
282 Enumeration e = cache.elements();
283 for(int i=0; e.hasMoreElements(); i++) t[i] = (Template)e.nextElement();
284 for(int i=0; i<t.length; i++) {
285 t[i].changed = false;
290 for(int i=0; i<Surface.allSurfaces.size(); i++) {
291 Box b = ((Surface)Surface.allSurfaces.elementAt(i)).root;
292 if (b != null) reapply(b);
296 /** template reapplication procedure */
297 private static void reapply(Box b) {
299 // Ref 7.5.1: check if we need to retemplatize
300 boolean retemplatize = false;
301 if (b.templatename != null) {
302 Template t = getTemplate(b.templatename, b.importlist);
303 if (t != b.template) retemplatize = true;
306 if (b.template != null && b.template.changed)
311 // Ref 7.5.2: "Preserve all properties on the box mentioned in the <preserve> elements of any
312 // of the templates which would be applied in step 7."
313 Vec keys = new Vec();
314 b.template.gatherPreserves(keys);
315 Object[] vals = new Object[keys.size()];
316 for(int i=0; i<keys.size(); i++) vals[i] = b.get(((String)keys.elementAt(i)), null);
318 // Ref 7.5.3: "Remove and save all children of the box, or its redirect target, if it has one"
320 if (b.redirect != null) {
321 kids = new Box[b.redirect.numChildren()];
322 for(int i=b.redirect.numChildren() - 1; i >= 0; i--) {
323 kids[i] = b.redirect.getChild(i);
328 // Ref 7.5.4: "Set the box's redirect target to self"
331 // Ref 7.5.5: "Remove all of the box's immediate children"
332 for(Box cur = b.getChild(b.numChildren() - 1); cur != null;) {
334 cur = cur.prevSibling();
338 // Ref 7.5.6: "Remove all traps set by scripts run during the application of any template to this box"
339 Trap.removeAllTrapsByBox(b);
341 // Ref 7.5.7: "Apply the template to the box according to the usual application procedure"
342 b.template.apply(b, null, null, null, 0, 1);
344 // Ref 7.5.8: "Re-add the saved children which were removed in step 3"
345 for(int i=0; kids != null && i<kids.length; i++) b.put(Integer.MAX_VALUE, null, kids[i]);
347 // Ref 7.5.9: "Re-put any property values which were preserved in step 2"
348 for(int i=0; i<keys.size(); i++) b.put((String)keys.elementAt(i), null, vals[i]);
352 for(Box j = b.getChild(0); j != null; j = j.nextSibling()) reapply(j);
355 /** runs statics, resolves string references to other templates into actual Template instance references, and sets <tt>change</tt> as needed */
356 void link() { link(false); }
358 /** same as link(), except that with a true value, it will force a re-link */
359 private void link(boolean force) {
361 if (staticscript != null) try {
362 Scriptable s = Static.createStatic(nodeName);
363 if (staticscript != null) {
364 Script temp = staticscript;
365 ((InterpretedScript)temp).setParentScope(s); // so we know how to handle Static.get("xwt")
367 temp.exec(Context.enter(), s);
369 } catch (EcmaError e) {
370 if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
371 if (Log.on) Log.log(this, " thrown while executing <static/> block for " + nodeName +
372 " at " + e.getSourceName() + ":" + e.getLineNumber());
373 } catch (JavaScriptException e) {
374 if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
375 if (Log.on) Log.log(this, " thrown while executing <static/> block for " + nodeName + " at " + e.sourceFile + ":" + e.line);
378 if (!(force || (preapply != null && _preapply == null) || (postapply != null && _postapply == null))) return;
380 if (preapply != null) {
381 if (_preapply == null) _preapply = new Template[preapply.length];
382 for(int i=0; i<_preapply.length; i++) {
383 Template t = getTemplate(preapply[i], importlist);
384 if (t != _preapply[i]) changed = true;
388 if (postapply != null) {
389 if (_postapply == null) _postapply = new Template[postapply.length];
390 for(int i=0; i<_postapply.length; i++) {
391 Template t = getTemplate(postapply[i], importlist);
392 if (t != _postapply[i]) changed = true;
397 for(int i=0; children != null && i<children.length; i++) children[i].link(force);
401 // XML Parsing /////////////////////////////////////////////////////////////////
403 /** handles XML parsing; builds a Template tree as it goes */
404 private static class TemplateHelper extends XML {
407 for(int i=0; i<defaultImportList.length; i++) importlist.addElement(defaultImportList[i]);
410 /** parse an XML input stream, building a Template tree off of <tt>root</tt> */
411 void parseit(InputStream is, Template root) throws XML.SAXException, IOException {
413 parse(new TabAndMaxColumnEnforcingReader(new InputStreamReader(is), root.nodeName));
416 /** parsing state: true iff we have already encountered the <xwt> open-tag */
417 boolean rootNodeHasBeenEncountered = false;
419 /** parsing state: true iff we have already encountered the <template> open-tag */
420 boolean templateNodeHasBeenEncountered = false;
422 /** parsing state: true iff we have already encountered the <static> open-tag */
423 boolean staticNodeHasBeenEncountered = false;
425 /** parsing state: true iff we have already encountered the <template> close-tag */
426 boolean templateNodeHasBeenFinished = false;
428 /** parsing state: If we have encountered the open tag of a header node, but not the close tag, this is the name of
429 * that tag; otherwise, it is null. */
430 String nameOfHeaderNodeBeingProcessed = null;
432 /** stack of Templates whose XML elements we have seen open-tags for but not close-tags */
433 Vec nodeStack = new Vec();
435 /** builds up the list of imports */
436 Vec importlist = new Vec();
438 /** builds up the list of preapplies */
439 Vec preapply = new Vec();
441 /** builds up the list of postapplies */
442 Vec postapply = new Vec();
444 /** the template we're currently working on */
447 public void startElement(String name, String[] keys, Object[] vals, int line, int col) throws XML.SAXException {
449 if (templateNodeHasBeenFinished) {
450 throw new XML.SAXException("no elements may appear after the <template> node");
452 } else if (!rootNodeHasBeenEncountered) {
453 if (!"xwt".equals(name)) throw new XML.SAXException("root element was not <xwt>");
454 if (keys.length != 0) throw new XML.SAXException("root element must not have attributes");
455 rootNodeHasBeenEncountered = true;
458 } else if (!templateNodeHasBeenEncountered) {
459 if (nameOfHeaderNodeBeingProcessed != null) throw new XML.SAXException("can't nest header nodes");
460 nameOfHeaderNodeBeingProcessed = name;
462 if (name.equals("import")) {
463 if (keys.length != 1 || !keys[0].equals("name"))
464 throw new XML.SAXException("<import> node must have exactly one attribute, which must be called 'name'");
465 String importpackage = vals[0].toString();
466 if (importpackage.endsWith(".*")) importpackage = importpackage.substring(0, importpackage.length() - 2);
467 importlist.addElement(importpackage);
470 } else if (name.equals("redirect")) {
471 if (keys.length != 1 || !keys[0].equals("target"))
472 throw new XML.SAXException("<redirect> node must have exactly one attribute, which must be called 'target'");
473 if (t.redirect != null)
474 throw new XML.SAXException("the <redirect> header element may not appear more than once");
475 t.redirect = vals[0].toString();
478 } else if (name.equals("preapply")) {
479 if (keys.length != 1 || !keys[0].equals("name"))
480 throw new XML.SAXException("<preapply> node must have exactly one attribute, which must be called 'name'");
481 preapply.addElement(vals[0]);
484 } else if (name.equals("postapply")) {
485 if (keys.length != 1 || !keys[0].equals("name"))
486 throw new XML.SAXException("<postapply> node must have exactly one attribute, which must be called 'name'");
487 postapply.addElement(vals[0]);
490 } else if (name.equals("static")) {
491 if (staticNodeHasBeenEncountered)
492 throw new XML.SAXException("the <static> header node may not appear more than once");
494 throw new XML.SAXException("the <static> node may not have attributes");
495 staticNodeHasBeenEncountered = true;
498 } else if (name.equals("preserve")) {
499 if (keys.length != 1 || !keys[0].equals("attributes"))
500 throw new XML.SAXException("<preserve> node must have exactly one attribute, which must be called 'attributes'");
501 if (t.preserve != null)
502 throw new XML.SAXException("<preserve> header element may not appear more than once");
504 StringTokenizer tok = new StringTokenizer(vals[0].toString(), ",", false);
505 t.preserve = new String[tok.countTokens()];
506 for(int i=0; i<t.preserve.length; i++) t.preserve[i] = tok.nextToken();
509 } else if (name.equals("template")) {
510 // finalize importlist/preapply/postapply, since they can't change from here on
512 importlist.toArray(t.importlist = new String[importlist.size()]);
513 if (preapply.size() > 0) preapply.copyInto(t.preapply = new String[preapply.size()]);
514 if (postapply.size() > 0) postapply.copyInto(t.postapply = new String[postapply.size()]);
515 importlist = preapply = postapply = null;
516 templateNodeHasBeenEncountered = true;
519 throw new XML.SAXException("unrecognized header node \"" + name + "\"");
525 // push the last node we were in onto the stack
526 nodeStack.addElement(t);
528 // instantiate a new node, and set its nodeName/importlist/preapply
529 Template t2 = new Template();
530 t2.nodeName = t.nodeName + "." + t.childvect.size();
531 t2.importlist = t.importlist;
533 if (!name.equals("box")) t2.preapply = new String[] { name };
535 // make the new node the current node
543 quickSortAttributes(0, t.keys.length - 1);
545 for(int i=0; i<t.keys.length; i++) {
546 if (t.keys[i].equals("id")) {
547 t.id = vals[i].toString().intern();
552 t.keys[i] = t.keys[i].intern();
554 String valString = vals[i].toString();
556 if (valString.equals("true")) t.vals[i] = Boolean.TRUE;
557 else if (valString.equals("false")) t.vals[i] = Boolean.FALSE;
558 else if (valString.equals("null")) t.vals[i] = null;
560 boolean hasNonNumeral = false;
561 boolean periodUsed = false;
562 for(int j=0; j<valString.length(); j++)
563 if (j == 0 && valString.charAt(j) == '-') {
564 } else if (valString.charAt(j) == '.' && !periodUsed && j != valString.length() - 1) {
566 } else if (!Character.isDigit(valString.charAt(j))) {
567 hasNonNumeral = true;
570 if (valString.length() > 0 && !hasNonNumeral) vals[i] = new Double(valString);
571 else vals[i] = valString.intern();
574 // bump thisbox to the front of the pack
575 if (t.keys[i].equals("thisbox")) {
576 t.keys[i] = t.keys[0];
577 t.keys[0] = "thisbox";
578 Object o = t.vals[0];
579 t.vals[0] = t.vals[i];
585 /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
586 private int partitionAttributes(int left, int right) {
588 middle = (left + right) / 2;
589 String s = t.keys[right]; t.keys[right] = t.keys[middle]; t.keys[middle] = s;
590 Object o = t.vals[right]; t.vals[right] = t.vals[middle]; t.vals[middle] = o;
591 for (i = left - 1, j = right; ; ) {
592 while (t.keys[++i].compareTo(t.keys[right]) < 0);
593 while (j > left && t.keys[--j].compareTo(t.keys[right]) > 0);
595 s = t.keys[i]; t.keys[i] = t.keys[j]; t.keys[j] = s;
596 o = t.vals[i]; t.vals[i] = t.vals[j]; t.vals[j] = o;
598 s = t.keys[right]; t.keys[right] = t.keys[i]; t.keys[i] = s;
599 o = t.vals[right]; t.vals[right] = t.vals[i]; t.vals[i] = o;
603 /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
604 private void quickSortAttributes(int left, int right) {
605 if (left >= right) return;
606 int p = partitionAttributes(left, right);
607 quickSortAttributes(left, p - 1);
608 quickSortAttributes(p + 1, right);
611 public void endElement(String name, int line, int col) throws XML.SAXException {
613 boolean hasNonWhitespace = false;
615 int len = t == null || t.content == null ? 0 : t.content.length();
616 for(int i=0; t.content != null && i<len; i++)
618 // ignore double-slash comment blocks
619 if (t.content.charAt(i) == '/' && t.content.charAt(i + 1) == '/') {
620 while(t.content.charAt(i) != '\n' && i<len) i++;
623 // ignore /* .. */ comment blocks
624 } else if (i<len - 1 && t.content.charAt(i) == '/' && t.content.charAt(i + 1) == '*') {
626 while(i<len - 1 && !(t.content.charAt(i) == '*' && t.content.charAt(i + 1) == '/')) i++;
627 if (i<len - 1 && t.content.charAt(i) == '*' && t.content.charAt(i + 1) == '/') i += 2;
630 // check for named functions
631 } else if (i + 8 <= len && t.content.charAt(i) == 'f' && t.content.charAt(i+1) == 'u' &&
632 t.content.charAt(i+2) == 'n' && t.content.charAt(i+3) == 'c' && t.content.charAt(i+4) == 't' &&
633 t.content.charAt(i+5) == 'i' && t.content.charAt(i+6) == 'o' && t.content.charAt(i+7) == 'n') {
635 while(j<len && Character.isWhitespace(t.content.charAt(j))) j++;
636 if (j<len && t.content.charAt(j) != '(')
637 throw new XML.SAXException("named functions are not permitted in XWT -- instead of \"function foo() { ... }\"," +
638 " use \"foo = function() { ... }\"");
640 // replace " and " with " && "
641 } else if (i + 5 < len && Character.isWhitespace(t.content.charAt(i)) &&
642 t.content.charAt(i+1) == 'a' && t.content.charAt(i+2) == 'n' && t.content.charAt(i+3) == 'd' &&
643 Character.isWhitespace(t.content.charAt(i + 4))) {
644 t.content.setCharAt(i+1, '&');
645 t.content.setCharAt(i+2, '&');
646 t.content.setCharAt(i+3, ' ');
647 hasNonWhitespace = true;
649 // generic check for nonwhitespace
650 } else if (!Character.isWhitespace(t.content.charAt(i))) {
651 hasNonWhitespace = true;
655 if (rootNodeHasBeenEncountered && !templateNodeHasBeenEncountered) {
656 if ("static".equals(nameOfHeaderNodeBeingProcessed) && hasNonWhitespace) t.staticscript = genscript(true);
657 nameOfHeaderNodeBeingProcessed = null;
659 } else if (templateNodeHasBeenEncountered && !templateNodeHasBeenFinished) {
661 // turn our childvect into a Template[]
662 t.childvect.copyInto(t.children = new Template[t.childvect.size()]);
664 if (hasNonWhitespace) t.script = genscript(false);
666 if (nodeStack.size() == 0) {
668 templateNodeHasBeenFinished = true;
671 // add this template as a child of its parent
673 t = (Template)nodeStack.lastElement();
674 nodeStack.setSize(nodeStack.size() - 1);
675 t.childvect.addElement(oldt);
681 private Script genscript(boolean isstatic) {
682 Script thisscript = null;
683 Context cx = Context.enter();
684 cx.setOptimizationLevel(-1);
687 thisscript = cx.compileReader(null, new StringReader(t.content.toString()), t.nodeName + (isstatic ? "._" : ""), t.content_start, null);
688 } catch (EcmaError ee) {
689 if (Log.on) Log.log(this, ee.getMessage() + " at " + ee.getSourceName() + ":" + ee.getLineNumber());
691 } catch (EvaluatorException ee) {
692 if (Log.on) Log.log(this, " ERROR: " + ee.getMessage());
694 } catch (IOException ioe) {
695 if (Log.on) Log.log(this, "IOException while compiling script; this should never happen");
696 if (Log.on) Log.log(this, ioe);
706 public void content(char[] ch, int start, int length, int line, int col) throws XML.SAXException {
707 if ("static".equals(nameOfHeaderNodeBeingProcessed) || templateNodeHasBeenEncountered) {
708 int contentlines = 0;
709 for(int i=start; i<start + length; i++) if (ch[i] == '\n') contentlines++;
710 line -= contentlines;
712 if (t.content == null) {
713 t.content_start = line;
715 t.content = new StringBuffer();
718 for(int i=t.content_start + t.content_lines; i<line; i++) {
719 t.content.append('\n');
723 t.content.append(ch, start, length);
724 t.content_lines += contentlines;
726 } else if (nameOfHeaderNodeBeingProcessed != null) {
727 throw new XML.SAXException("header node <" + nameOfHeaderNodeBeingProcessed + "> cannot have text content");
735 /** a filtering reader that watches for tabs and long lines */
736 private static class TabAndMaxColumnEnforcingReader extends FilterReader {
737 private int MAX_COLUMN = 150;
738 private int column = 0;
739 private int line = 1;
740 private boolean lastCharWasCR = false;
741 private String filename;
742 public TabAndMaxColumnEnforcingReader(Reader r, String filename) { super(r); this.filename = filename; }
744 if (Log.on) Log.log(this, this.getClass().getName() + ".read() not supported, this should never happen");
747 public long skip(long numskip) {
748 if (Log.on) Log.log(this, this.getClass().getName() + ".skip() not supported; this should never happen");
751 public int read(char[] buf, int off, int len) throws IOException {
752 int ret = super.read(buf, off, len);
753 for(int i=off; i<off + ret; i++)
754 if (buf[i] == '\t') {
755 throw new TemplateException(filename + ":" + line + "," + column + ": tabs are not allowed in XWT files");
756 } else if (buf[i] == '\r') {
759 lastCharWasCR = true;
760 } else if (buf[i] == '\n') {
762 if (!lastCharWasCR) line++;
763 } else if (++column > MAX_COLUMN) {
764 throw new TemplateException(filename + ":" + line + ": lines longer than " + MAX_COLUMN + " characters not allowed");
766 lastCharWasCR = false;
772 private static class TemplateException extends IOException {
773 TemplateException(String s) { super(s); }