2003/04/24 14:33:28
[org.ibex.core.git] / src / org / xwt / Template.java
index 324fea1..ba542fd 100644 (file)
@@ -95,6 +95,9 @@ public class Template {
 
     // Static data/methods ///////////////////////////////////////////////////////////////////
 
+    /** maximum length of a line */
+    private static final int MAX_COLUMN = 150;
+
     /** a template cache so that only one Template object is created for each xwt */
     private static Hashtable cache = new Hashtable(1000);
 
@@ -115,19 +118,19 @@ public class Template {
     }
 
     public static Template buildTemplate(InputStream is, String nodeName) {
+        return buildTemplate(is, nodeName, new TemplateHelper());
+    }
+
+    public static Template buildTemplate(InputStream is, String nodeName, TemplateHelper t) {
         try {
-            return new Template(is, nodeName);
-        } catch (XML.SAXParseException e) {
-            if (Log.on) Log.log(Template.class, "error parsing template at " + nodeName + ":" + e.getLineNumber() + "," + e.getColumnNumber());
-            if (Log.on) Log.log(Template.class, e);
-            return null;
-        } catch (XML.SAXException e) {
+            return new Template(is, nodeName, t);
+        } catch (XML.SchemaException e) {
             if (Log.on) Log.log(Template.class, "error parsing template " + nodeName);
-            if (Log.on) Log.log(Template.class, e);
+            if (Log.on) Log.log(Template.class, e.getMessage());
             return null;
-        } catch (TemplateException te) {
-            if (Log.on) Log.log(Template.class, "error parsing template " + nodeName);
-            if (Log.on) Log.log(Template.class, te);
+        } catch (XML.XMLException e) {
+            if (Log.on) Log.log(Template.class, "error parsing template at " + nodeName + ":" + e.getLine() + "," + e.getCol());
+            if (Log.on) Log.log(Template.class, e.getMessage());
             return null;
         } catch (IOException e) {
             if (Log.on) Log.log(Template.class, "IOException while parsing template " + nodeName + " -- this should never happen");
@@ -143,9 +146,9 @@ public class Template {
         this.nodeName = nodeName;
         cache.put(nodeName, this);
     }
-    private Template(InputStream is, String nodeName) throws XML.SAXException, IOException {
+    private Template(InputStream is, String nodeName, TemplateHelper th) throws XML.XMLException, IOException {
         this(nodeName);
-        new TemplateHelper().parseit(is, this);
+        th.parseit(is, this);
     }
 
     /** calculates, caches, and returns an integer approximation of how long it will take to apply this template, including pre/post and children */
@@ -410,16 +413,27 @@ public class Template {
     // XML Parsing /////////////////////////////////////////////////////////////////
 
     /** handles XML parsing; builds a Template tree as it goes */
-    private static class TemplateHelper extends XML {
+    static final class TemplateHelper extends XML {
 
-        TemplateHelper() {
-            for(int i=0; i<defaultImportList.length; i++) importlist.addElement(defaultImportList[i]);
-        }
+        TemplateHelper() { }
 
         /** parse an XML input stream, building a Template tree off of <tt>root</tt> */
-        void parseit(InputStream is, Template root) throws XML.SAXException, IOException {
+        void parseit(InputStream is, Template root) throws XML.XMLException, IOException {
+            rootNodeHasBeenEncountered = false;
+            templateNodeHasBeenEncountered = false;
+            staticNodeHasBeenEncountered = false;
+            templateNodeHasBeenFinished = false;
+            nameOfHeaderNodeBeingProcessed = null;
+
+            nodeStack.setSize(0);
+            importlist.setSize(0);
+            preapply.setSize(0);
+            postapply.setSize(0);
+
+            importlist.fromArray(defaultImportList);
+
             t = root;
-            parse(new TabAndMaxColumnEnforcingReader(new InputStreamReader(is), root.nodeName)); 
+            parse(new InputStreamReader(is)); 
         }
 
         /** parsing state: true iff we have already encountered the <xwt> open-tag */
@@ -453,79 +467,78 @@ public class Template {
         /** the template we're currently working on */
         Template t = null;
 
-        public void startElement(String name, String[] keys, Object[] vals, int line, int col) throws XML.SAXException {
-
+        public void startElement(XML.Element c) throws XML.SchemaException {
             if (templateNodeHasBeenFinished) {
-                throw new XML.SAXException("no elements may appear after the <template> node");
+                throw new XML.SchemaException("no elements may appear after the <template> node");
 
             } else if (!rootNodeHasBeenEncountered) {
-                if (!"xwt".equals(name)) throw new XML.SAXException("root element was not <xwt>");
-                if (keys.length != 0) throw new XML.SAXException("root element must not have attributes");
+                if (!"xwt".equals(c.localName)) throw new XML.SchemaException("root element was not <xwt>");
+                if (c.len != 0) throw new XML.SchemaException("root element must not have attributes");
                 rootNodeHasBeenEncountered = true;
                 return;
         
             } else if (!templateNodeHasBeenEncountered) {
-                if (nameOfHeaderNodeBeingProcessed != null) throw new XML.SAXException("can't nest header nodes");
-                nameOfHeaderNodeBeingProcessed = name;
+                if (nameOfHeaderNodeBeingProcessed != null) throw new XML.SchemaException("can't nest header nodes");
+                nameOfHeaderNodeBeingProcessed = c.localName;
 
-                if (name.equals("import")) {
-                    if (keys.length != 1 || !keys[0].equals("name"))
-                        throw new XML.SAXException("<import> node must have exactly one attribute, which must be called 'name'");
-                    String importpackage = vals[0].toString();
+                if (c.localName.equals("import")) {
+                    if (c.len != 1 || !c.keys[0].equals("name"))
+                        throw new XML.SchemaException("<import> node must have exactly one attribute, which must be called 'name'");
+                    String importpackage = c.vals[0].toString();
                     if (importpackage.endsWith(".*")) importpackage = importpackage.substring(0, importpackage.length() - 2);
                     importlist.addElement(importpackage);
                     return;
 
-                } else if (name.equals("redirect")) {
-                    if (keys.length != 1 || !keys[0].equals("target"))
-                        throw new XML.SAXException("<redirect> node must have exactly one attribute, which must be called 'target'");
+                } else if (c.localName.equals("redirect")) {
+                    if (c.len != 1 || !c.keys[0].equals("target"))
+                        throw new XML.SchemaException("<redirect> node must have exactly one attribute, which must be called 'target'");
                     if (t.redirect != null)
-                        throw new XML.SAXException("the <redirect> header element may not appear more than once");
-                    t.redirect = vals[0].toString();
+                        throw new XML.SchemaException("the <redirect> header element may not appear more than once");
+                    t.redirect = c.vals[0].toString();
                     return;
 
-                } else if (name.equals("preapply")) {
-                    if (keys.length != 1 || !keys[0].equals("name"))
-                        throw new XML.SAXException("<preapply> node must have exactly one attribute, which must be called 'name'");
-                    preapply.addElement(vals[0]);
+                } else if (c.localName.equals("preapply")) {
+                    if (c.len != 1 || !c.keys[0].equals("name"))
+                        throw new XML.SchemaException("<preapply> node must have exactly one attribute, which must be called 'name'");
+                    preapply.addElement(c.vals[0]);
                     return;
 
-                } else if (name.equals("postapply")) {
-                    if (keys.length != 1 || !keys[0].equals("name"))
-                        throw new XML.SAXException("<postapply> node must have exactly one attribute, which must be called 'name'");
-                    postapply.addElement(vals[0]);
+                } else if (c.localName.equals("postapply")) {
+                    if (c.len != 1 || !c.keys[0].equals("name"))
+                        throw new XML.SchemaException("<postapply> node must have exactly one attribute, which must be called 'name'");
+                    postapply.addElement(c.vals[0]);
                     return;
 
-                } else if (name.equals("static")) {
+                } else if (c.localName.equals("static")) {
                     if (staticNodeHasBeenEncountered)
-                        throw new XML.SAXException("the <static> header node may not appear more than once");
-                    if (keys.length > 0)
-                        throw new XML.SAXException("the <static> node may not have attributes");
+                        throw new XML.SchemaException("the <static> header node may not appear more than once");
+                    if (c.len > 0)
+                        throw new XML.SchemaException("the <static> node may not have attributes");
                     staticNodeHasBeenEncountered = true;
                     return;
 
-                } else if (name.equals("preserve")) {
-                    if (keys.length != 1 || !keys[0].equals("attributes"))
-                        throw new XML.SAXException("<preserve> node must have exactly one attribute, which must be called 'attributes'");
+                } else if (c.localName.equals("preserve")) {
+                    if (c.len != 1 || !c.keys[0].equals("attributes"))
+                        throw new XML.SchemaException("<preserve> node must have exactly one attribute, which must be called 'attributes'");
                     if (t.preserve != null)
-                        throw new XML.SAXException("<preserve> header element may not appear more than once");
+                        throw new XML.SchemaException("<preserve> header element may not appear more than once");
 
-                    StringTokenizer tok = new StringTokenizer(vals[0].toString(), ",", false);
+                    StringTokenizer tok = new StringTokenizer(c.vals[0].toString(), ",", false);
                     t.preserve = new String[tok.countTokens()];
                     for(int i=0; i<t.preserve.length; i++) t.preserve[i] = tok.nextToken();
                     return;
 
-                } else if (name.equals("template")) {
+                } else if (c.localName.equals("template")) {
                     // finalize importlist/preapply/postapply, since they can't change from here on
-                    t.startLine = line;
+                    t.startLine = getLine();
                     importlist.toArray(t.importlist = new String[importlist.size()]);
                     if (preapply.size() > 0) preapply.copyInto(t.preapply = new String[preapply.size()]);
                     if (postapply.size() > 0) postapply.copyInto(t.postapply = new String[postapply.size()]);
-                    importlist = preapply = postapply = null;
+                    importlist.setSize(0); preapply.setSize(0); postapply.setSize(0);
                     templateNodeHasBeenEncountered = true;
 
                 } else {
-                    throw new XML.SAXException("unrecognized header node \"" + name + "\"");
+                    throw new XML.SchemaException("unrecognized header node \"" + c.localName + "\"");
 
                 }
 
@@ -537,29 +550,31 @@ public class Template {
                 // instantiate a new node, and set its nodeName/importlist/preapply
                 Template t2 = new Template(t.nodeName + "." + t.childvect.size());
                 t2.importlist = t.importlist;
-                t2.startLine = line;
-                if (!name.equals("box")) t2.preapply = new String[] { name };
+                t2.startLine = getLine();
+                if (!c.localName.equals("box")) t2.preapply = new String[] { c.localName };
 
                 // make the new node the current node
                 t = t2;
 
             }
 
-            t.keys = keys;
-            t.vals = vals;
-
+            // TODO: Sort contents straight from one array to another
+            t.keys = new String[c.len];
+            t.vals = new Object[c.len];
+            System.arraycopy(c.keys, 0, t.keys, 0, c.len);
+            System.arraycopy(c.vals, 0, t.vals, 0, c.len);
             quickSortAttributes(0, t.keys.length - 1);
 
             for(int i=0; i<t.keys.length; i++) {
                 if (t.keys[i].equals("id")) {
-                    t.id = vals[i].toString().intern();
+                    t.id = t.vals[i].toString().intern();
                     t.keys[i] = null;
                     continue;
                 }
 
                 t.keys[i] = t.keys[i].intern();
 
-                String valString = vals[i].toString();
+                String valString = t.vals[i].toString();
                 
                 if (valString.equals("true")) t.vals[i] = Boolean.TRUE;
                 else if (valString.equals("false")) t.vals[i] = Boolean.FALSE;
@@ -575,8 +590,8 @@ public class Template {
                             hasNonNumeral = true;
                             break;
                         }
-                    if (valString.length() > 0 && !hasNonNumeral) vals[i] = new Double(valString);
-                    else vals[i] = valString.intern();
+                    if (valString.length() > 0 && !hasNonNumeral) t.vals[i] = new Double(valString);
+                    else t.vals[i] = valString.intern();
                 }
 
                 // bump thisbox to the front of the pack
@@ -607,7 +622,7 @@ public class Template {
             o = t.vals[right]; t.vals[right] = t.vals[i]; t.vals[i] = o;
             return i;
         }
-        
+
         /** simple quicksort, from http://sourceforge.net/snippet/detail.php?type=snippet&id=100240 */
         private void quickSortAttributes(int left, int right) {
             if (left >= right) return;
@@ -615,61 +630,17 @@ public class Template {
             quickSortAttributes(left, p - 1);
             quickSortAttributes(p + 1, right);
         }
-        
-        public void endElement(String name, int line, int col) throws XML.SAXException {
-
-            boolean hasNonWhitespace = false;
-
-            int len = t == null || t.content == null ? 0 : t.content.length();
-            for(int i=0; t.content != null && i<len; i++)
-                
-                // ignore double-slash comment blocks
-                if (t.content.charAt(i) == '/' && t.content.charAt(i + 1) == '/') {
-                    while(t.content.charAt(i) != '\n' && i<len) i++;
-                    i--;
-
-                // ignore /* .. */ comment blocks
-                } else if (i<len - 1 && t.content.charAt(i) == '/' && t.content.charAt(i + 1) == '*') {
-                    i += 2;
-                    while(i<len - 1 && !(t.content.charAt(i) == '*' && t.content.charAt(i + 1) == '/')) i++;
-                    if (i<len - 1 && t.content.charAt(i) == '*' && t.content.charAt(i + 1) == '/') i += 2;
-                    i--;
-
-                // check for named functions
-                } else if (i + 8 <= len && t.content.charAt(i) == 'f' && t.content.charAt(i+1) == 'u' &&
-                           t.content.charAt(i+2) == 'n' && t.content.charAt(i+3) == 'c' && t.content.charAt(i+4) == 't' &&
-                           t.content.charAt(i+5) == 'i' && t.content.charAt(i+6) == 'o' && t.content.charAt(i+7) == 'n') {
-                    int j = i + 8;
-                    while(j<len && Character.isWhitespace(t.content.charAt(j))) j++;
-                    if (j<len && t.content.charAt(j) != '(')
-                        throw new XML.SAXException("named functions are not permitted in XWT -- instead of \"function foo() { ... }\"," +
-                                        " use \"foo = function() { ... }\"");
-
-                // replace " and " with " && "
-                } else if (i + 5 < len && Character.isWhitespace(t.content.charAt(i)) &&
-                           t.content.charAt(i+1) == 'a' && t.content.charAt(i+2) == 'n' && t.content.charAt(i+3) == 'd' &&
-                           Character.isWhitespace(t.content.charAt(i + 4))) {
-                    t.content.setCharAt(i+1, '&');
-                    t.content.setCharAt(i+2, '&');
-                    t.content.setCharAt(i+3, ' ');
-                    hasNonWhitespace = true;
-
-                // generic check for nonwhitespace
-                } else if (!Character.isWhitespace(t.content.charAt(i))) {
-                    hasNonWhitespace = true;
 
-                }
-            
+        public void endElement(XML.Element c) throws XML.SchemaException {
             if (rootNodeHasBeenEncountered && !templateNodeHasBeenEncountered) {
-                if ("static".equals(nameOfHeaderNodeBeingProcessed) && hasNonWhitespace) t.staticscript = genscript(true);
+                if ("static".equals(nameOfHeaderNodeBeingProcessed) && t.content != null) t.staticscript = genscript(true);
                 nameOfHeaderNodeBeingProcessed = null;
 
             } else if (templateNodeHasBeenEncountered && !templateNodeHasBeenFinished) {
-
                 // turn our childvect into a Template[]
                 t.childvect.copyInto(t.children = new Template[t.childvect.size()]);
                 t.childvect = null;
-                if (hasNonWhitespace) t.script = genscript(false);
+                if (t.content != null) t.script = genscript(false);
                 
                 if (nodeStack.size() == 0) {
                     // </template>
@@ -700,8 +671,7 @@ public class Template {
                 if (Log.on) Log.log(this, "  ERROR: " + ee.getMessage());
                 thisscript = null;
             } catch (IOException ioe) {
-                if (Log.on) Log.log(this, "IOException while compiling script; this should never happen");
-                if (Log.on) Log.log(this, ioe);
+                if (Log.on) Log.log(this, "  ERROR: " + ioe.getMessage());
                 thisscript = null;
             }
 
@@ -711,74 +681,31 @@ public class Template {
             return thisscript;
         }
 
-        public void content(char[] ch, int start, int length, int line, int col) throws XML.SAXException {
-            if ("static".equals(nameOfHeaderNodeBeingProcessed) || templateNodeHasBeenEncountered) {
-                int contentlines = 0;
-                for(int i=start; i<start + length; i++) if (ch[i] == '\n') contentlines++;
-                line -= contentlines;
+        public void characters(char[] ch, int start, int length) throws XML.SchemaException {
+            // invoke the max-column-length and no-tab crusade
+            if (getCol() + length > MAX_COLUMN) throw new XML.SchemaException(
+                t.nodeName+ ":" + getLine() + ": lines longer than " + MAX_COLUMN + " characters not allowed");
+
+            for (int i=0; length >i; i++) if (ch[start+i] == '\t') throw new XML.SchemaException(
+                t.nodeName+ ":" + getLine() + "," + getCol() + ": tabs are not allowed in XWT files");
 
+            if ("static".equals(nameOfHeaderNodeBeingProcessed) || templateNodeHasBeenEncountered) {
                 if (t.content == null) {
-                    t.content_start = line;
+                    t.content_start = getLine();
                     t.content_lines = 0;
                     t.content = new StringBuffer();
                 }
 
-                for(int i=t.content_start + t.content_lines; i<line; i++) {
-                    t.content.append('\n');
-                    t.content_lines++;
-                }
-
                 t.content.append(ch, start, length);
-                t.content_lines += contentlines;
+                t.content_lines++;
 
             } else if (nameOfHeaderNodeBeingProcessed != null) {
-                throw new XML.SAXException("header node <" + nameOfHeaderNodeBeingProcessed + "> cannot have text content");
-
+                throw new XML.SchemaException("header node <" + nameOfHeaderNodeBeingProcessed + "> cannot have text content");
             }
-
         }
 
-    }
-
-    /** a filtering reader that watches for tabs and long lines */
-    private static class TabAndMaxColumnEnforcingReader extends FilterReader {
-        private int MAX_COLUMN = 150;
-        private int column = 0;
-        private int line = 1;
-        private boolean lastCharWasCR = false;
-        private String filename;
-        public TabAndMaxColumnEnforcingReader(Reader r, String filename) { super(r); this.filename = filename; }
-        public int read() {
-            if (Log.on) Log.log(this, this.getClass().getName() + ".read() not supported, this should never happen");
-            return -1;
+        public void whitespace(char[] ch, int start, int length) throws XML.SchemaException {
         }
-        public long skip(long numskip) {
-            if (Log.on) Log.log(this, this.getClass().getName() + ".skip() not supported; this should never happen");
-            return numskip;
-        }
-        public int read(char[] buf, int off, int len) throws IOException {
-            int ret = super.read(buf, off, len);
-            for(int i=off; i<off + ret; i++)
-                if (buf[i] == '\t') {
-                    throw new TemplateException(filename + ":" + line + "," + column + ": tabs are not allowed in XWT files");
-                } else if (buf[i] == '\r') {
-                    column = 0;
-                    line++;
-                    lastCharWasCR = true;
-                } else if (buf[i] == '\n') {
-                    column = 0;
-                    if (!lastCharWasCR) line++;
-                } else if (++column > MAX_COLUMN) {
-                    throw new TemplateException(filename + ":" + line + ": lines longer than " + MAX_COLUMN + " characters not allowed");
-                } else {
-                    lastCharWasCR = false;
-                }
-            return ret;
-        }
-    }
-
-    private static class TemplateException extends IOException {
-        TemplateException(String s) { super(s); }
     }
 
 }