improvements to Doc.java, corresponding fixes to the reference
[org.ibex.core.git] / src / org / ibex / util / Doc.java
index f2780b0..9770a99 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
+// FEATURE: property tree
+
 package org.ibex.util;
 import java.util.*;
 import java.io.*;
 import org.ibex.util.*;
 
 public class Doc extends XML {
+    
+    public static boolean slides = false;
+    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 {
+        if (s.length > 0 && "slides".equals(s[0])) slides = true;
         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;
+
+        Node target = (Node)nodeStack.lastElement();
+        if (target == null) target = (Node)victim;
+        if (target != null) {
+            if (pending.length() > 0) target.addText(pending);
+            pending = "";
+        }
+
         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("math")) {         newGuy = new Math(e);
+        } else if (name.equals("property")) {     newGuy = new Property(e);
+
+        } 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 = "";
+
+    static Object victim = null;
+
     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() + ">");
+
+        Node target = (Node)nodeStack.lastElement();
+        if (target == null) target = (Node)victim;
+        if (preStart != -1) {
+            if (!e.getLocalName().equals("pre")) return;
+            target.addChild(new PRE(preStart, getGlobalOffset()));
+            preStart = -1;
+        } else if (skip > 0) {
+            skip--;
+            return;
         } else {
+            if (target != null) {
+                if (pending.length() > 0) target.addText(pending);
+                pending = "";
+            }
+            if (nodeStack.lastElement() instanceof Section) victim = nodeStack.lastElement();
             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");
-            }
-            mt2 = mt2.replaceAll(" \"", " ``");
-            mt2 = mt2.replaceAll("\"", "``");
-            return mt2;
-        }
-        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));
+        if (preStart != -1) return;
+        pending += 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("\\\\", "\\$\\\\backslash\\$");
+            s = s.replaceAll("\\$", "\\\\\\$");
+            s = s.replaceAll("\\\\\\$\\\\backslash\\\\\\$", "\\$\\\\backslash\\$");
+            s = s.replaceAll("\\{", "\\\\{");
+            s = s.replaceAll("\\}", "\\\\}");
+            s = s.replaceAll("\\*\\*([^\n]+?)\\*\\*", "{\\\\it{$1}}");
+            s = s.replaceAll("__([^\n]+?)__", "{\\\\textbf{$1}}");
+            s = s.replaceAll("\\[\\[([^\n]+?)\\]\\]", "{\\\\texttt{$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("\"", "''");
+            return s;
+        }
+    }
+
+
+    // 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 String caption;
+        public String width;
+        public String align;
+        public Image(XML.Element e) {
+            url = e.getAttrVal("url"); caption = e.getAttrVal("caption"); width = e.getAttrVal("width");
+            align = e.getAttrVal("align");
+        }
+        public void dumpLatex(StringBuffer sb) {
+            if (url.endsWith(".pdf")) {
+                if (width == null) {
+                    sb.append("\\begin{figure}[H]\n");
+                    sb.append("\\begin{center}\n");
+                    sb.append("\\epsfig{file="+url.substring(0, url.length() - 4)+",width=\\textwidth}\n");
+                    if (caption != null)
+                        sb.append("\\caption{"+fixLatex(caption)+"}\n");
+                    sb.append("\\end{center}\n");
+                    sb.append("\\end{figure}\n");
                 } else {
-                    ((Node)children.elementAt(i)).dumpLatex(p);
+                    if ("left".equals(align)) {
+                        sb.append("\\begin{wrapfigure}{l}{"+width+"}\n");
+                    } else {
+                        sb.append("\\begin{wrapfigure}{r}{"+width+"}\n");
+                    }
+                    sb.append("\\epsfig{file="+url.substring(0, url.length() - 4)+",width="+width+"}%\n");
+                    if (caption != null)
+                        sb.append("\\caption{"+fixLatex(caption)+"}\n");
+                    sb.append("\\end{wrapfigure}\n");
                 }
+            } else {
+                sb.append("\\hyperimage{" + url + "}");
             }
         }
     }
 
-    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 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 {
+        public 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] + "\n\n";
+            }
+            try {
+                TextNode clone = (TextNode)clone();
+                clone.mytext = "";
+                clone.canAcceptMoreText = true;
+                parent.addChild(clone);
+                clone.addText(o);
+            } catch (CloneNotSupportedException cnse) {
+                throw new RuntimeException(cnse);
+            }
         }
     }
 
-    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 Link extends TextNode {
+        public String url;
+        public String section;
+        public String appendix;
+        public String text;
+        public Link(XML.Element e) {
+            url = e.getAttrVal("url");            if ("".equals(url)) url = null;
+            appendix = e.getAttrVal("appendix");  if ("".equals(appendix)) appendix = null;
+            section = e.getAttrVal("section");    if ("".equals(section)) section = null;
+            text = e.getAttrVal("text");          if ("".equals(url)) text = null;
+            if (text == null) text = url;
+            if (text == null) text = section;
+            if (text == null) text = appendix;
+        }
+        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 + "}{" + text + "} ");
+            } else if (appendix != null) { sb.append("\\hyperlink{" + appendix + "}{" + text + "} ");
+            }
+        }
+    }
+
+    class PRE extends TextNode {
+        int gobble = 0;
+        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);
+            gobble = 9999;
+            int i = 0;
+            while(true) {
+                int start2 = i;
+                while(i<mytext.length() && mytext.charAt(i) == ' ') i++;
+                if (i==mytext.length()) break;
+                gobble = java.lang.Math.min(gobble, i - start2);
+                i = mytext.indexOf('\n', i);
+                if (i == -1) break;
+                i++;
+            }
+        }
+        public void dumpLatex(StringBuffer sb) {
+            sb.append("\n\n\\begin{Verbatim}[fontfamily=courier,fontsize=\\tiny,frame=single,rulecolor=\\color{CodeBorder},resetmargins=true,gobble="+gobble+"]\n");
+            sb.append(mytext);
+            sb.append("\\end{Verbatim}\n\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 && name.trim().indexOf(' ') == -1) {
+                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 {
+                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(' ')));
+            }
+            if (default_ != null) {
+                String fd = fixLatex(default_);
+                fd = fd.replaceAll(" ``", " \"");
+                fd = fd.replaceAll("''", "\"");
+                sb.append("\\\\{\\it default: }{\\texttt{" + fd + "}}\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 Math extends ParagraphNode {
+        String tex = "";
+        public Math(XML.Element e) { tex = e.getAttrVal("tex"); if (tex == null) tex = ""; }
+        public void addText(String s) { tex += s; }
+        public void dumpLatex(StringBuffer sb) { sb.append("\n\n$$\n" + tex.replaceAll("\n", " ") + "\n$$\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) {
+    class Section extends ParagraphNode {
+        String name;
+        public Section(XML.Element e) {
+            name = e.getAttrVal("title");
+            if (slides) super.addChild(new List());
+        }
+        public void addText(String s) {
+            if (slides) ((List)children.elementAt(0)).addText(s);
+            else super.addText(s);
+        }
+        public void addChild(Node n) {
+            /*            if (slides) ((List)children.elementAt(0)).addChild(n);
+                          else*/ super.addChild(n);
+        }
+        public void dumpLatex(StringBuffer sb) {
             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");
+            String base = "section";
+            int count = 0;
+            String pile = "";
+            for(Node n = parent; n != null; n = n.parent)
+                if (n instanceof Section || n instanceof Appendix) {
+                    base = "section";
+                    secs += "sub";
+                    for (int i=0; i<count; i++) pile += "\\hspace{1cm}";
+                    pile += ((Section)n).name + "\\\\\n";
+                    count++;
+                }
+            if (slides) {
+                sb.append(pile);
+                sb.append("\n\n\\begin{slide}\n");
+                sb.append("\\slideheading{"+fixLatex(name)+"}\n");
+                super.dumpLatex(sb);
+                sb.append("\n\n\\end{slide}\n");
             } else {
-                p.println("\n\n\\" + secs + "section{"+secname+"}\n");
+                if (secs.length() == 0)
+                    sb.append("\\newpage\n\n");
+                sb.append("\n\n\\hypertarget{" + name + "}{\\" + secs + base + "{" + name + "}}\n\n");
+                super.dumpLatex(sb);
             }
-            super.dumpLatex(p);
         }
     }
 
-    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;
+    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 {
+        TextNode textnode = null;
+        public List() { }
+        public void addChild(Node n) {
+            if (n != textnode) textnode = null;
+            super.addChild(n);
+        }
+        public void addText(String s) {
+            if (!(children.lastElement() == textnode) || (textnode == null && children.lastElement() == null))
+                addChild(textnode = new TextNode());
+            s = s.replaceAll("\n( +)\\- ", "\n\n$1 ");
+            textnode.addText(s);
+        }
+        public int indentof(String s) {
+            for(int i=0; i<s.length(); i++)
+                if (!Character.isWhitespace(s.charAt(i)))
+                    return i;
+            return s.length();
+        }
+        public void dumpLatex(StringBuffer sb0) {
+            StringBuffer sb = new StringBuffer();
+            boolean began = false;
+            boolean unusedItem = false;
+            List spawn = null;
+            int indentation = -1;
             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) { unusedItem = false; continue; }
+                if (kid instanceof List) unusedItem = true;
+                String txt = null;
+                if (kid instanceof String) {
+                    if (kid.toString().trim().length() == 0) continue;
+                    txt = (String)children.elementAt(i);
+                } else if (kid instanceof TextNode) {
+                    if (((TextNode)kid).mytext.trim().length() == 0) continue;
+                    txt = ((TextNode)kid).mytext;
                 } 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 (spawn != null) {
+                        spawn.dumpLatex(sb);
+                        unusedItem = false;
+                        spawn = null;
                     }
-                    ((Node)children.elementAt(i)).dumpLatex(p);
+                    ((Node)kid).dumpLatex(sb);
+                    continue;
+                }
+                if (txt.trim().length() == 0) continue;
+                if (indentation == -1) indentation = indentof(txt);
+                if (indentof(txt) > indentation) {
+                    if (spawn == null) spawn = new List();
+                    spawn.addText(txt);
+                    spawn.addChild(new ParagraphBreak());
+                    continue;
                 }
+                if (spawn != null) {
+                    spawn.dumpLatex(sb);
+                    unusedItem = false;
+                    spawn = null;
+                }
+                if (!unusedItem) { unusedItem = true; sb.append("\n\n\\item\n"); }
+                sb.append(fixLatex(txt));
+            }
+            if (spawn != null) {
+                spawn.dumpLatex(sb);
+                spawn = null;
             }
-            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 = "";
+            if (sb.toString().replaceAll("\\[.+\\]", "").trim().length() > 0) {
+                sb0.append("\n\\begin{itemize}\n");
+                sb0.append(sb.toString());
+                sb0.append("\n\\end{itemize}\n");
             }
-            p.println("\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 addText(String s) {
+            if (victim != null) ((Node)victim).addText(s);
+        }
+        public void addChild(Node n) {
+            if (!(n instanceof Section))
+                ((Section)victim).addChild(n);
+            else
+                super.addChild(n);
+        }
+        public void dumpLatex(StringBuffer sb) {
+            if (slides) {
+                sb.append("\\documentclass[letter]{seminar}\n");
+                sb.append("\\usepackage{calc}               % Simple computations with LaTeX variables\n");
+                sb.append("\\usepackage[hang]{caption2}     % Improved captions\n");
+                sb.append("\\usepackage{fancybox}           % To have several backgrounds\n");
+                sb.append("                                % (must be loaded before `fancyvrb')\n");
+                sb.append("\\usepackage{fancyhdr}           % Headers and footers definitions\n");
+                sb.append("\\usepackage{fancyvrb}           % Fancy verbatim environments\n");
+                sb.append("\\usepackage{wrapfig}\n");
+                sb.append("\\usepackage{float}\n");
+                sb.append("\\usepackage{amsmath}\n");
+                sb.append("\\usepackage{amssymb}\n");
+                sb.append("\\usepackage{pdftricks}\n");
+                sb.append("\\begin{psinputs}\n");
+                sb.append("  \\usepackage{pstcol}             % PSTricks with the standard color package\n");
+                sb.append("                                  % (before `graphicx' for the \\scalebox macro)\n");
+                sb.append("  \\usepackage{graphicx}           % Standard graphics package\n");
+                sb.append("  \\usepackage{multido}            % General loop macro\n");
+                sb.append("  \\usepackage{pifont}             % Ding symbols (mainly for lists)\n");
+                sb.append("  \\usepackage{pst-fr3d}           % PSTricks 3D framed boxes\n");
+                sb.append("  \\usepackage{pst-grad}           % PSTricks gradient mode\n");
+                sb.append("  \\usepackage{pst-node}           % PSTricks nodes\n");
+                sb.append("  \\usepackage{pst-slpe}           % Improved PSTricks gradients\n");
+                sb.append("\\end{psinputs}\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{float}\n");
+                sb.append("\\usepackage{fvrb-ex}\n");
+                sb.append("\\usepackage{bold-extra}\n");
+                sb.append("\\usepackage{ulem}\n");
+                sb.append("\\usepackage{amssymb,amsmath,epsfig,alltt}\n");
+                sb.append("\\usepackage{semcolor}           % Seminar colored slides\n");
+                sb.append("\\usepackage{semhelv}            % Seminar helvetica fonts\n");
+                sb.append("\\usepackage{semlayer}           % Seminar overlays\n");
+                sb.append("\\usepackage{slidesec}           % Seminar sections and list of slides\n");
+                sb.append("\\usepackage{url}                % Convenient URL typesetting\n");
+                sb.append("\\usepackage[pdftex,letterpaper,pdffitwindow=true,colorlinks=true,pdfpagemode=UseNone,\n");
+                sb.append("            bookmarks=true]{hyperref} % Hyperlinks for PDF versions\n");
+                sb.append("\\usepackage{hcolor}\n");
+                sb.append("\\slidepagestyle{fancy}\n");
+                sb.append("\n");
+                sb.append("\\slidesmag{4}     % Set magnification of slide\n");
+                sb.append("\\def\\SeminarPaperWidth{\\paperwidth / 2}\n");
+                sb.append("\\def\\SeminarPaperHeight{\\paperheight / 2}\n");
+                sb.append("\\slideframe{none} % No default frame\n");
+                sb.append("\n");
+                sb.append("  \n");
+                sb.append("\n");
+                sb.append("  % General size parameters\n");
+                sb.append("\\renewcommand{\\slideparindent}{5mm}\n");
+                sb.append("\\raggedslides[0mm]\n");
+                sb.append("%  \\renewcommand{\\slidetopmargin}{15.5mm}\n");
+                sb.append("%  \\renewcommand{\\slidebottommargin}{13mm}\n");
+                sb.append("%  \\renewcommand{\\slideleftmargin}{4mm}\n");
+                sb.append("%  \\renewcommand{\\sliderightmargin}{4mm}\n");
+                sb.append("  % To adjust the frame length to the header and footer ones\n");
+                sb.append("%  \\autoslidemarginstrue\n");
+                sb.append("  % We suppress the header and footer `fancyhdr' rules\n");
+                sb.append("\\fancyhf{} % Clear all fields\n");
+                sb.append("\\renewcommand{\\headrule}{}\n");
+                sb.append("\\renewcommand{\\footrule}{}\n");
+                sb.append("\n");
+                sb.append("%  \\usepackage{nohyperref}       % To deactivate the `hyperref' features\n");
+                sb.append("%  \\overlaysfalse                % To suppress overlays\n");
+                sb.append("%  \\def\\special@paper{}% Needed to avoid `hyperref' to collapse with ``dvips''\n");
+                sb.append("\\newslideframe{IMAGE}{%\n");
+                sb.append("  \\boxput{\\rput(0,0){%\n");
+                sb.append("      \\includegraphics[width=\\SeminarPaperHeight,height=\\SeminarPaperWidth]{background.pdf}}}{#1}}\n");
+                sb.append("\\slideframe*{IMAGE}\n");
+                sb.append("%\\renewcommand{\\slideleftmargin}{3cm}\n");
+                sb.append("%\\addtolength{\\slidewidth}{-\\slideleftmargin}\n");
+                sb.append("\\RequirePackage[T1]{fontenc}\n");
+                sb.append("\\RequirePackage{textcomp}\n");
+                sb.append("\\renewcommand{\\rmdefault}{trebuchet}\n");
+                sb.append("\\renewcommand{\\slidefonts}{%\n");
+                sb.append("  \\renewcommand{\\rmdefault}{trebuchet}%\n");
+                sb.append("  \\renewcommand{\\ttdefault}{courier}}%\n");
+                sb.append("  \\newcommand{\\ParagraphTitle}[2][black]{%\n");
+                sb.append("  \\noindent\\psshadowbox[fillstyle=solid,fillcolor=#1]{\\large{#2}}}\n");
+                sb.append("  \\newcommand{\\CenteredParagraphTitle}[2][black]{%\n");
+                sb.append("  \\centerline{\\psshadowbox[fillstyle=solid,fillcolor=#1]{\\large{#2}}}}\n");
+                sb.append("  \\renewcommand{\\makeslideheading}[1]{%\n");
+                sb.append("  \\CenteredParagraphTitle[black]{%\n");
+                sb.append("    \\textcolor{black}{\\huge\\textbf{#1}}}}\n");
+                sb.append("  \\renewcommand{\\makeslidesubheading}[1]{%\n");
+                sb.append("    \\CenteredParagraphTitle{\\Large\\theslidesubsection{} -- #1}}\n");
+                sb.append("  \\renewenvironment{dinglist}[2][black]\n");
+                sb.append("  {\\begin{list}{\\ding{#2}}{}}{\\end{list}}\n");
+                sb.append("  \\newcommand{\\DingListSymbolA}{43}\n");
+                sb.append("  \\newcommand{\\DingListSymbolB}{243}\n");
+                sb.append("  \\newcommand{\\DingListSymbolC}{224}\n");
+                sb.append("  \\newcommand{\\DingListSymbolD}{219}\n");
+                sb.append("  \\newcommand{\\eqbox}[2][0.6]{%\n");
+                sb.append("  \\centerline{\\psshadowbox[fillstyle=solid,fillcolor=gray]{%\n");
+                sb.append("  \\parbox{#1\\hsize}{%\n");
+                sb.append("      \\[\n");
+                sb.append("        \\textcolor{black} {#2}\n");
+                sb.append("      \\]}}}}\n");
+                sb.append("\\begin{document}\n");
+                sb.append("\\begin{slide}\n");
+                sb.append("\\begin{center}\n");
+                sb.append("\\ParagraphTitle{\\bf \\Large "+title+"}\n");
+                sb.append("\\vspace{5mm} \\\n");
+                sb.append("\\textit{\\large "+subtitle+"} \\\\\n");
+                sb.append("\\vspace{5mm} \\\n");
+                sb.append("\\textit{"+author+"} \\\n");
+                sb.append("\\end{center}\n");
+                sb.append("\\end{slide}\n\n");
+                super.dumpLatex(sb);
+                sb.append("\\end{document}");
+            } else {
+                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{float}\n");
+                sb.append("\\usepackage{wrapfig}\n");
+                sb.append("\\usepackage{fvrb-ex}\n");
+                sb.append("\\usepackage{bold-extra}\n");
+                sb.append("\\usepackage{ulem}\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");
+                sb.append("\\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}");
+            }
+        }
+    }
 }
+