ground-up rewrite of org.ibex.util.Doc
authoradam <adam@megacz.com>
Thu, 25 Mar 2004 12:20:12 +0000 (12:20 +0000)
committeradam <adam@megacz.com>
Thu, 25 Mar 2004 12:20:12 +0000 (12:20 +0000)
darcs-hash:20040325122012-5007d-5bb81bca1b265ece2f6a704abf7649d10ba9f4db.gz

src/org/ibex/util/Doc.java

index f2780b0..70ef527 100644 (file)
+
+// FEATURE:   <code type="c++"/>          -- syntax highlighting
+// FEATURE:   <code linenumbers="true"/>  -- LaTeX moreverb package can help
+// FIXME: nest TextNodes within each other for bold+italic
+
 package org.ibex.util;
 import java.util.*;
 import java.io.*;
 import org.ibex.util.*;
 
 public class Doc extends XML {
+
+    Root root = null;
+    int skip = 0;
+    Vec nodeStack = new Vec();
+    String pending = "";
+    char[] buffer = new char[1024 * 1024];
+    int preStart = -1;
+    
+    public Doc() { }
+
     public static void main(String[] s) throws Exception {
         Doc d = new Doc();
-        d.parse(new InputStreamReader(System.in));
-        ((Node)d.nodeStack.elementAt(0)).dumpLatex(System.out);
-        System.out.println("\\end{document}");
+        Reader r = new InputStreamReader(System.in);
+        int len = 0;
+        while(true) {
+            int numread = r.read(d.buffer, len, d.buffer.length - len);
+            if (numread == -1) break;
+            len += numread;
+            if (len >= d.buffer.length) {
+                char[] newbuffer = new char[d.buffer.length * 2];
+                System.arraycopy(d.buffer, 0, newbuffer, 0, d.buffer.length);
+                d.buffer = newbuffer;
+            }
+        }
+        d.parse(new CharArrayReader(d.buffer));
+        StringBuffer sb = new StringBuffer();
+        d.root.dumpLatex(sb);
+        System.out.println(sb);
     }
-    
-    Vec top = new Vec();
-    Vec nodeStack = new Vec();
-    public Doc() { nodeStack.addElement(new Node()); }
 
     public void startElement(Element e) throws Exn {
+        if (preStart != -1) return;
         String name = e.getLocalName();
-        if (nodeStack.lastElement() != null && (nodeStack.lastElement() instanceof PRE)) {
-            ((PRE)nodeStack.lastElement()).addText("<" + e.getQName() + ">");
-            buffer++;
-            return;
+        Node newGuy = null;
+        if (name.equals("ibex-doc")) {            newGuy = new Root(e);
+        } else if (name.equals("section")) {      newGuy = new Section(e);
+        } else if (name.equals("heading")) {      newGuy = new Heading(e);
+        } else if (name.equals("appendix")) {     newGuy = new Appendix(e);
+        } else if (name.equals("list")) {         newGuy = new List();
+
+        } else if (name.equals("pre")) {          preStart = getGlobalOffset();
+
+        } else if (name.equals("definition")) {   newGuy = new Definition(e);
+        } else if (name.equals("property")) {     newGuy = new Property(e);
+
+        } else if (name.equals("b")) {            newGuy = new B();
+        } else if (name.equals("i")) {            newGuy = new I();
+        } else if (name.equals("t")) {            newGuy = new T();
+        } else if (name.equals("link")) {         newGuy = new Link(e);
+        } else if (name.equals("image")) {        newGuy = new Image(e);
+
+        } else {
+            System.err.println("warning: unknown tag " + name);
+            skip++;
         }
-        if (name.equals("ibex-doc")) {
-            String title = "You forgot the title, you idiot!";
-            String author = "Your Mom";
-            String email = null;
-            String subtitle = null;
-            for(int i=0; i<e.getAttrLen(); i++) {
-                if (e.getAttrKey(i).equals("title")) title = e.getAttrVal(i); 
-                if (e.getAttrKey(i).equals("author")) author = e.getAttrVal(i);
-                if (e.getAttrKey(i).equals("email")) email = e.getAttrVal(i);
-                if (e.getAttrKey(i).equals("subtitle")) subtitle = e.getAttrVal(i);
-            }
-            System.out.println("\\documentclass{article}");
-            System.out.println("\\def\\ninept{\\def\\baselinestretch{.95}\\let\\normalsize\\small\\normalsize}");
-            System.out.println("\\ninept");
-            System.out.println("\\usepackage{graphicx}");
-            System.out.println("\\usepackage{amssymb,amsmath,epsfig,alltt}");
-            System.out.println("\\sloppy");
-            System.out.println("\\usepackage{palatino}");
-            System.out.println("\\usepackage{sectsty}");
-            System.out.println("\\allsectionsfont{\\sffamily}");
-            System.out.println("\\sectionfont{\\pagebreak\\leftskip=-2cm\\hrulefill\\\\\\sffamily\\bfseries\\raggedleft\\vspace{1cm}}");
-            System.out.println("\\subsectionfont{\\dotfill\\\\\\sffamily\\raggedright\\hspace{-4cm}}");
-            System.out.println("\\newdimen\\sectskip");
-            System.out.println("\\newdimen\\subsectskip");
-            System.out.println("\\newdimen\\saveskip");
-            System.out.println("\\saveskip=\\leftskip");
-            System.out.println("\\sectskip=-2cm");
-            System.out.println("\\subsectskip=0cm");
-            System.out.println("\\let\\oldsection\\section");
-            System.out.println("\\let\\oldsubsection\\subsection");
-            System.out.println("\\def\\subsection#1{\\leftskip=\\sectskip\\oldsubsection{#1}\\leftskip=0cm}");
-            System.out.println("\\usepackage{parskip}");
-            System.out.println("\\usepackage{tabularx}");
-            System.out.println("\\usepackage{alltt}");
-            System.out.println("\\usepackage[pdftex,bookmarks=true]{hyperref}");
-            System.out.println("");
-            System.out.println("\\begin{document}");
-            System.out.println("");
-            System.out.println("\\title{\\textbf{\\textsf{");
-            System.out.println(title);
-            if (subtitle != null) System.out.println("\\\\{\\large " + subtitle + "}");
-            System.out.println("}}}");
-            if (author != null) {
-                System.out.println("\\author{");
-                System.out.println(author);
-                if (email != null) System.out.println("\\\\{\\tt " + email + "}");
-                System.out.println("}");
-            }
-            System.out.println("");
-            System.out.println("\\maketitle");
-            System.out.println("\\clearpage");
-            System.out.println("\\tableofcontents");
-            System.out.println("\\clearpage");
-            System.out.println("\\onecolumn");
-            nodeStack.addElement(new Node());
-        } else if (name.equals("section") || name.equals("appendix")) {
-            String secname = "unknown";
-            for(int i=0; i<e.getAttrLen(); i++) if (e.getAttrKey(i).equals("title")) secname = e.getAttrVal(i);
-            nodeStack.addElement(new Section(secname, name.equals("appendix")));
-        } else if (name.equals("b")) {       nodeStack.addElement(new B());
-        } else if (name.equals("i")) {       nodeStack.addElement(new I());
-        } else if (name.equals("tt")) {      nodeStack.addElement(new TT());
-        } else if (name.equals("list")) {    nodeStack.addElement(new List());
-        } else if (name.equals("pre")) {     nodeStack.addElement(new PRE());
-        } else if (name.equals("ref")) { buffer++;
-        } else if (name.equals("link")) {
-            buffer++;
-            for(int i=0; i<e.getAttrLen(); i++) if (e.getAttrKey(i).equals("text")) addText(e.getAttrVal(i));
-        } else if (name.equals("definition")) { buffer++;
-        } else if (name.equals("property")) { buffer++;
-        } else { System.err.println("warning: unknown tag " + name);
-        buffer++;
+        if (newGuy != null) {
+            Node parent = (Node)nodeStack.lastElement();
+            if (parent != null) parent.addChild(newGuy);
+            nodeStack.addElement(newGuy);
         }
     }
-    
-    int buffer = 0;
-    String pending = "";
+
     public void whitespace(char[] ch, int start, int length) throws Exn, IOException { characters(ch, start, length); }
-    void addText(String s) { ((Node)nodeStack.lastElement()).addText(s); }
     public void endElement(Element e) throws Exn, IOException {
-        if (buffer > 0) {
-            buffer--;
-            if (nodeStack.lastElement() instanceof PRE)
-                ((PRE)nodeStack.lastElement()).addText("</" + e.getLocalName() + ">");
+        if (preStart != -1) {
+            if (!e.getLocalName().equals("pre")) return;
+            ((Node)nodeStack.lastElement()).addChild(new PRE(preStart, getGlobalOffset()));
+            preStart = -1;
+        } else if (skip > 0) {
+            skip--;
+            return;
         } else {
             nodeStack.setSize(nodeStack.size() - 1);
         }
     }
     public void characters(char[] ch, int start, int length) throws Exn, IOException {
-        Node n = ((Node)nodeStack.lastElement());
-        if (n != null) n.addText(new String(ch, start, length));
-    }
-
-    boolean intt = false;
-    class Node {
-        Vec children = new Vec();
-        final Node parent;
-        public Node() { this((Node)nodeStack.lastElement()); }
-        public Node(Node parent) { this.parent = parent; if (parent != null) parent.add(this); }
-        public void add(Node child) { children.addElement(child); }
-        public void addText(String s) { children.addElement(s); }
-        void printText(PrintStream p, String mt2) { p.print(fix(mt2)); }
-        String fix(String mt2) {
-            mt2 = mt2.replaceAll("\\\\", "\\backslash ");
-            mt2 = mt2.replaceAll("LaTeX", "\\LaTeX");
-            mt2 = mt2.replaceAll("\\$", "\\\\\\$ ");
-            mt2 = mt2.replaceAll("\\%", "\\\\% ");
-            mt2 = mt2.replaceAll("#", "\\\\# ");
-            mt2 = mt2.replaceAll("\\{", "\\\\{ ");
-            mt2 = mt2.replaceAll("\\}", "\\\\} ");
-            mt2 = mt2.replaceAll("\\&", "\\\\& ");
-            mt2 = mt2.replaceAll("\\~", "\\\\~ ");
-            mt2 = mt2.replaceAll("_", "\\\\_");
-            if (!intt) {
-                mt2 = mt2.replaceAll("\" ", "'' ");
-                mt2 = mt2.replaceAll("\"\n", "''\n");
+        if (preStart != -1) return;
+        ((Node)nodeStack.lastElement()).addText(new String(ch, start, length));
+    }
+
+    abstract class Node {
+        public Node parent = null;
+        public abstract void addText(String s);
+        public abstract void addChild(Node s);
+        public abstract void dumpLatex(StringBuffer sb);
+        final String fixLatex(String s) {
+            if (s == null) return "";
+            s = s.replaceAll("\\$", "\\\\\\$ ");
+            s = s.replaceAll("\\\\([^\\$])", "\\$\\\\backslash\\$\\1");
+            s = s.replaceAll("LaTeX", "\\\\LaTeX");
+            s = s.replaceAll("\\%", "\\\\% ");
+            s = s.replaceAll("#", "\\\\#");
+            s = s.replaceAll("\\{", "\\\\{");
+            s = s.replaceAll("\\}", "\\\\}");
+            s = s.replaceAll("\\&", "\\\\&");
+            s = s.replaceAll("\\~", "\\\\~");
+            s = s.replaceAll("_", "\\\\_");
+            if (!(this instanceof T)) {
+                s = s.replaceAll(" \"", " ``");
+                s = s.replaceAll("\"", "''");
             }
-            mt2 = mt2.replaceAll(" \"", " ``");
-            mt2 = mt2.replaceAll("\"", "``");
-            return mt2;
+            return s;
         }
-        public void dumpLatex(PrintStream p) {
-            for(int i=0; i<children.size(); i++) {
-                if (children.elementAt(i) instanceof String) {
-                    printText(p, (String)children.elementAt(i));
-                } else {
-                    ((Node)children.elementAt(i)).dumpLatex(p);
-                }
+    }
+
+
+    // Empty Nodes //////////////////////////////////////////////////////////////////////////////
+
+    class EmptyNode extends Node {
+        public EmptyNode() { }
+        public void addChild(Node o) { throw new RuntimeException(this.getClass().getName() + " cannot have children"); }
+        public void addText(String o) { throw new RuntimeException(this.getClass().getName() + " cannot have content"); }
+        public void dumpLatex(StringBuffer sb) { }
+    }
+
+    class Image extends EmptyNode {
+        public String url;
+        public Image(XML.Element e) { url = e.getAttrVal("url"); }
+        public void dumpLatex(StringBuffer sb) { sb.append("\\hyperimage{" + url + "}"); }
+    }
+
+    class Heading extends EmptyNode {
+        public String text;
+        public Heading(XML.Element e) { text = e.getAttrVal("title"); }
+        public void dumpLatex(StringBuffer sb) { sb.append("\\vspace{.6cm}\\hypertarget{" +
+                                                           fixLatex(text) + "}{\\textbf{\\textsf{" +
+                                                           fixLatex(text) + "}}}"); }
+    }
+
+    class LineBreak extends EmptyNode {
+        public LineBreak() { }
+        public void dumpLatex(StringBuffer sb) { sb.append("\\\n"); }
+    }
+
+    class ParagraphBreak extends EmptyNode {
+        public ParagraphBreak() { }
+        public void dumpLatex(StringBuffer sb) { sb.append("\n\n"); }
+    }
+
+
+
+    // Non-Paragraph Nodes //////////////////////////////////////////////////////////////////////////////
+
+    /** Nodes which contain only text; they split themselves if anything else is added */
+    class TextNode extends Node implements Cloneable {
+        protected String mytext = "";
+        private boolean canAcceptMoreText = true;
+        public void dumpLatex(StringBuffer sb) { sb.append(fixLatex(mytext)); }
+        public void addChild(Node o) { canAcceptMoreText = false; parent.addChild(o); }
+        public void addText(String o) {
+            if (canAcceptMoreText) {
+                mytext += (String)o;
+                String[] split = mytext.split("\\n\\s*\\n");
+                if (split.length <= 1) return;
+                canAcceptMoreText = false;
+                mytext = split[0];
+                parent.addChild(new ParagraphBreak());
+                o = "";
+                for(int i=1; i<split.length; i++) o += split[i];
+            }
+            try {
+                TextNode clone = (TextNode)clone();
+                clone.mytext = "";
+                clone.canAcceptMoreText = true;
+                clone.addText(o);
+                parent.addChild(clone);
+            } catch (CloneNotSupportedException cnse) {
+                throw new RuntimeException(cnse);
             }
         }
     }
 
-    class PRE extends Node {
-        String myText = "";
-        public void addText(String s) { myText += s; }
-        public void dumpLatex(PrintStream p) {
-            p.println("\n\\begin{verbatim}\n");
-            p.print(myText);
-            p.println("\n\\end{verbatim}\n");
+    class T extends TextNode {
+        public void dumpLatex(StringBuffer sb) {
+            sb.append("{\\texttt{");
+            super.dumpLatex(sb);
+            sb.append("}}");
+        } }
+    class B extends TextNode {
+        public void dumpLatex(StringBuffer sb) {sb.append("{\\textbf{"); super.dumpLatex(sb); sb.append("}}");}}
+    class I extends TextNode {
+        public void dumpLatex(StringBuffer sb) {sb.append("{\\it{"); super.dumpLatex(sb); sb.append("}}");}}
+    class Link extends TextNode {
+        public String url;
+        public String section;
+        public String appendix;
+        public String text;
+        public Link(XML.Element e) {
+            url = e.getAttrVal("url");
+            text = e.getAttrVal("text");
+            appendix = e.getAttrVal("appendix");
+            section = e.getAttrVal("section");
+        }
+        public void dumpLatex(StringBuffer sb) {
+            // FIXME: dotted underline for section/appendix, solid underline for url
+            String text = fixLatex(this.text);
+            if (text == null) text = "\\tt " + (url != null ? url : section != null ? section : appendix);
+            if (url != null) {             sb.append("\\href{" + url + "}{\\uline{" + text + "}} ");
+            } else if (section != null) {  sb.append("\\hyperlink{" + section + "}{\\uwave{" + text + "}} ");
+            } else if (appendix != null) { sb.append("\\hyperlink{" + appendix + "}{\\uwave{" + text + "}} ");
+            }
         }
     }
 
-    class I extends Node { public void dumpLatex(PrintStream p) { p.print("{\\it "); super.dumpLatex(p); p.print("}"); } }
-    class B extends Node { public void dumpLatex(PrintStream p) { p.print("{\\bf "); super.dumpLatex(p); p.print("}"); } }
-    class TT extends Node { public void dumpLatex(PrintStream p) {
-        p.print("{\\tt ");
-        intt = true;
-        super.dumpLatex(p);
-        intt = false;
-        p.print("}"); } }
+    class PRE extends TextNode {
+        public PRE(int start, int end) {
+            while(Character.isWhitespace(buffer[start])) start++;
+            while(buffer[start] != '\n') start--;
+            start++;
+            end -= 6; // ugly hack since </pre> is 6 chars long...
+            if (Character.isWhitespace(buffer[end])) {
+                while(Character.isWhitespace(buffer[end])) end--;
+                end++;
+            }
+            mytext = new String(buffer, start, end - start);
+        }
+        public void dumpLatex(StringBuffer sb) {
+            sb.append("\n\n\\begin{Verbatim}[fontfamily=courier,fontsize=\\footnotesize,frame=single,rulecolor=\\color{CodeBorder},resetmargins=true]\n");
+            sb.append(mytext);
+            sb.append("\\end{Verbatim}\n\n");
+        }
+    }
 
-    class Section extends Node {
-        String secname;
-        boolean appendix = false;
-        public Section(String secname, boolean appendix) { this.secname = secname; this.appendix = appendix;}
-        public void dumpLatex(PrintStream p) {
-            String secs = "";
-            for(Node n = parent; n != null; n = n.parent) if (n instanceof Section) secs += "sub";
-            if (appendix) {
-                p.println("\n\n\\appendix{"+secname+"}\n");
+
+    // Paragraph Nodes //////////////////////////////////////////////////////////////////////////////
+
+    class ParagraphNode extends Node {
+        protected Vec children = new Vec();
+        public void addChild(Node n) { children.addElement(n); n.parent = this; }
+        public void addText(String s) {
+            if (children.size() == 0 || !(children.lastElement() instanceof String))
+                children.addElement(s);
+            else
+                children.setElementAt(children.lastElement() + s, children.size() - 1);
+        }
+        public void dumpLatex(StringBuffer sb) {
+            for(int i=0; i<children.size(); i++) {
+                if (children.elementAt(i) instanceof String) sb.append(fixLatex(children.elementAt(i).toString()));
+                else ((Node)children.elementAt(i)).dumpLatex(sb);
+            }
+        }
+    }
+
+    class Property extends ParagraphNode {
+        String name = "";
+        String type = "";
+        String default_ = null;
+        public Property(XML.Element e) {
+            name = e.getAttrVal("name");
+            if (name == null || name.equals("")) name = "ERROR";
+            type = e.getAttrVal("type");
+            default_ = e.getAttrVal("default");
+            if (default_ != null && default_.trim().length() == 0) default_ = null;
+        }
+        public void dumpLatex(StringBuffer sb) {
+            StringBuffer sb2 = new StringBuffer();
+            super.dumpLatex(sb2);
+            String s = sb2.toString();
+            while(Character.isSpace(s.charAt(0))) s = s.substring(1);
+            String fname = fixLatex(name);
+            type = type == null || type.equals("") ? "" : "({\\it{" + fixLatex(type) + "}})";
+            if (name.trim().length() > 13) {
+                sb.append("\n\n{\\color{CodeBorder}\\hspace{-2cm}\\dotfill\\\\\\color{black}}");
+                sb.append("\\marginpar{\\raggedleft{\\texttt{\\textbf{\\footnotesize{"+fname+"}}}}\\\\"+type+" }");
+                sb.append("\\\\");
+                sb.append(s);
             } else {
-                p.println("\n\n\\" + secs + "section{"+secname+"}\n");
+                sb.append("\n\n{\\color{CodeBorder}\\hspace{-2cm}\\dotfill\\\\\\color{black}}");
+                sb.append(s.substring(0, s.indexOf(' ')));
+                sb.append("\\marginpar{\\raggedleft{\\texttt{\\textbf{\\footnotesize{"+fname+"}}}}\\\\"+type+" }");
+                sb.append(s.substring(s.indexOf(' ')));
             }
-            super.dumpLatex(p);
+            if (default_ != null) {
+                sb.append("\\\\{\\it default: }{\\texttt{" + fixLatex(default_) + "}}\n\n");
+            } else {
+                sb.append("\n\n");
+            }
+        }
+    }
+
+    class Definition extends ParagraphNode {
+        String name = "";
+        public Definition(XML.Element e) { name = e.getAttrVal("term"); }
+        public void dumpLatex(StringBuffer sb) {
+            StringBuffer sb2 = new StringBuffer();
+            super.dumpLatex(sb2);
+            String s = sb2.toString();
+            while(Character.isSpace(s.charAt(0))) s = s.substring(1);
+            sb.append(s.substring(0, s.indexOf(' ')));
+            sb.append("\\marginpar{\\raggedleft{\\textbf{"+fixLatex(name)+"}}}");
+            sb.append(s.substring(s.indexOf(' ')));
         }
     }
 
-    class List extends Node {
-        boolean ordered = false;
-        public void dumpLatex(PrintStream p) {
-            p.println("\n\\begin{itemize}%\n");
-            String acc = "";
-            p.print("\n\\item%\n");
-            boolean used = false;
+    class Section extends ParagraphNode {
+        String name;
+        public Section(XML.Element e) { name = e.getAttrVal("title"); }
+        public void dumpLatex(StringBuffer sb) {
+            String secs = "";
+            String base = "section";
+            for(Node n = parent; n != null; n = n.parent)
+                if (n instanceof Section || n instanceof Appendix) {
+                    base = "section";
+                    secs += "sub";
+                }
+            if (secs.length() == 0)
+                sb.append("\\newpage\n\n");
+            sb.append("\n\n\\hypertarget{" + name + "}{\\" + secs + base + "{" + name + "}}\n\n");
+            super.dumpLatex(sb);
+        }
+    }
+
+    static boolean hitAppendix = false;
+    class Appendix extends Section {
+        public Appendix(XML.Element e) { super(e); }
+        public void dumpLatex(StringBuffer sb) {
+            if (!hitAppendix) sb.append("\\appendix\n");
+            hitAppendix = true;
+            super.dumpLatex(sb);
+        }
+    }
+
+    class List extends ParagraphNode {
+        Node textnode = null;
+        public List() { }
+        public void addText(String s) {
+            if (!(children.lastElement() == textnode) || (textnode == null && children.lastElement() == null))
+                addChild(textnode = new TextNode());
+            ((TextNode)children.lastElement()).addText(s);
+        }
+        public void dumpLatex(StringBuffer sb) {
+            sb.append("\n\\begin{itemize}%\n");
+            boolean unusedItem = false;
             for(int i=0; i<children.size(); i++) {
-                if (children.elementAt(i) instanceof String) {
-                    acc += fix(children.elementAt(i).toString());
+                Object kid = children.elementAt(i);
+                if (kid instanceof ParagraphBreak || kid instanceof List) unusedItem = false;
+                if (kid instanceof String) {
+                    if (kid.toString().trim().length() == 0) continue;
+                    if (!unusedItem) { unusedItem = true; sb.append("\n\n\\item\n"); }
+                    sb.append(children.elementAt(i));
                 } else {
-                    if (acc.length() > 0) {
-                        if (!used) acc = acc.replaceAll("^\\s*", "");
-                        if (children.elementAt(i) instanceof List) acc = acc.replaceAll("\\n\\s*$", "");
-                        acc = acc.replaceAll("\\n\\s*\\n", "\n\n\\\\item ");
-                        if (acc.trim().length() > 0) {
-                            used = true;
-                            p.print(acc);
-                        }
-                        acc = "";
+                    if (kid instanceof TextNode) {
+                        if (((TextNode)kid).mytext.trim().length() == 0) continue;
+                        if (!unusedItem) { unusedItem = true; sb.append("\n\n\\item\n"); }
                     }
-                    ((Node)children.elementAt(i)).dumpLatex(p);
+                    ((Node)kid).dumpLatex(sb);
                 }
             }
-            if (acc.length() > 0) {
-                if (!used) acc = acc.replaceAll("^\\s*", "");
-                acc = acc.replaceAll("\\n\\s*$", "");
-                p.print(acc.replaceAll("\\n\\s*\\n", "\n\n\\\\item "));
-                acc = "";
-            }
-            p.println("\n\\end{itemize}%\n");
+            sb.append("\n\\end{itemize}%\n");
         }
     }
 
+    class Root extends ParagraphNode {
+        String title = "You forgot the title, you idiot!";
+        String author = "Your Mom";
+        String email = null;
+        String subtitle = null;
+        public Root(XML.Element e) {
+            root = this;
+            title = e.getAttrVal("title");
+            author = e.getAttrVal("author");
+            email = e.getAttrVal("email");
+            subtitle = e.getAttrVal("subtitle");
+        }
+        public void dumpLatex(StringBuffer sb) {
+            sb.append("\\documentclass{article}\n");
+            sb.append("\\def\\ninept{\\def\\baselinestretch{.95}\\let\\normalsize\\small\\normalsize}\n");
+            sb.append("\\ninept\n");
+            sb.append("\\usepackage{color}\n");
+            sb.append("\\definecolor{CodeBorder}{rgb}{0.6,0.6,0.6}\n");
+            sb.append("\\definecolor{CodeBackground}{rgb}{0.93,0.93,0.93}\n");
+            sb.append("\\usepackage{graphicx}\n");
+            sb.append("\\usepackage{courier}\n");
+            sb.append("\\usepackage{fancyvrb}\n");
+            sb.append("\\usepackage{fvrb-ex}\n");
+            sb.append("\\usepackage{bold-extra}\n");
+            sb.append("\\usepackage{ulem}\n");
+            sb.append("\\usepackage{appendix}\n");
+            sb.append("\\usepackage{amssymb,amsmath,epsfig,alltt}\n");
+            sb.append("\\sloppy\n");
+            sb.append("\\usepackage{palatino}\n");
+            sb.append("\\usepackage{sectsty}\n");
+            sb.append("\\allsectionsfont{\\sffamily}\n");
+            sb.append("\\sectionfont{\\color{black}\\leftskip=-2cm\\hrulefill\\\\\\sffamily\\bfseries\\raggedleft\\vspace{1cm}}\n");
+            sb.append("\\subsectionfont{\\color{black}\\dotfill\\\\\\sffamily\\raggedright\\hspace{-4cm}}\n");
+            sb.append("\\newdimen\\sectskip\n");
+            sb.append("\\newdimen\\subsectskip\n");
+            sb.append("\\newdimen\\saveskip\n");
+            sb.append("\\saveskip=\\leftskip\n");
+            sb.append("\\sectskip=-2cm\n");
+            sb.append("\\subsectskip=0cm\n");
+            sb.append("\\let\\oldsection\\section\n");
+            sb.append("\\let\\oldsubsection\\subsection\n");
+            sb.append("\\def\\subsection#1{\\leftskip=\\sectskip\\oldsubsection{#1}\\leftskip=0cm}\n");
+            sb.append("\\usepackage{parskip}\n");
+            sb.append("\\usepackage{tabularx}\n");
+            sb.append("\\usepackage{alltt}\n");
+            sb.append("\\usepackage[pdftex,colorlinks=true,urlcolor=blue,linkcolor=blue,bookmarks=true]{hyperref}\n");
+            // FIXME: pdfauthor, pdftitle, pdfsubject, pdfkeywords?
+            sb.append("\n");
+            sb.append("\\begin{document}\n");
+            sb.append("\\reversemarginpar\n");
+            sb.append("\n");
+            sb.append("\\title{\\textbf{\\textsf{\n");
+            sb.append(title);
+            if (subtitle != null) sb.append("\\\\{\\large " + subtitle + "}\n");
+            sb.append("}}}\n");
+            if (author != null) {
+                sb.append("\\author{\n");
+                sb.append(author);
+                if (email != null) sb.append("\\\\{\\tt " + email + "}\n");
+                sb.append("}\n");
+            }
+            sb.append("\n");
+            sb.append("\\maketitle\n");
+            sb.append("\\clearpage\n");
+            sb.append("\\tableofcontents\n");
+            sb.append("\\clearpage\n");
+            sb.append("\\onecolumn\n");
+            super.dumpLatex(sb);
+            sb.append("\\end{document}");
+        }
+    }
 }
+