// $Id: Latex2OldHtml.java,v 1.2 2004/02/27 16:30:01 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" is free software; you can redistribute
// it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

package com.meyling.principia.html;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.meyling.principia.io.Output;
import com.meyling.principia.io.TextInput;
import com.meyling.principia.io.Utility;
import com.meyling.principia.latex.Qedeq2Latex;


/**
 * This class provides static methods for converting latex into html.
 *
 * @version $Revision: 1.2 $
 * @author  Michael Meyling
 */
public final class Latex2OldHtml {

    /** converting map between LaTex and Html */
    private final static Map convert = new HashMap();


    /**
     * Make attribut initialization.
     */
    static {
        // fill conversion map
        convert.put("~",            "~");
        convert.put(",",            " ");
        convert.put(" ",            " ");
        convert.put("$",            "$");
        convert.put("&",            "&");
        convert.put("%",            "%");
        convert.put("#",            "#");
        convert.put("{",            "{");
        convert.put("}",            "}");
        convert.put("_",            "_");
        convert.put("\\",           "<br>");
        convert.put("par",          "<br>");
        convert.put("medskip",      "<p>");
        convert.put("bigskip",      "<p><p>");
        convert.put("quad",         "&nbsp;");
        convert.put("qquad",        "&nbsp;&nbsp;");
        convert.put("backslash",    "\\");
        convert.put("setminus",     "<font size=+1>\\</font>");
        convert.put("perp",         "&#094;");
        convert.put("le",           "&#163;");
        convert.put("leq",          "&#163;");
        convert.put("ge",           "&#179;");
        convert.put("geq",          "&#179;");
        convert.put("equiv",        "&#186;");
        convert.put("simeq",        "&#064;");
        convert.put("forall",       "&#034;");
        convert.put("exists",       "&#036;");
        convert.put("ni",           "&#039;");
        convert.put("cong",         "&#064;");
        convert.put("sim",          "&#126;");
        convert.put("infty",        "&#165;");
        convert.put("leftrightarrow", "&#171;");
        convert.put("leftarrow",    "&#172;");
        convert.put("uparrow",      "&#173;");
        convert.put("rightarrow",   "&#174;");
        convert.put("to",           "&#174;");
        convert.put("downarrow",    "&#175;");
        convert.put("longleftarrow","&#172;&#045;");
        convert.put("longrightarrow", "&#045;&#174;");
        convert.put("ldots",        "&#188;");
        convert.put("cdots",        "&#215;&#215;&#215;");
        convert.put("vdots",         ":");
        convert.put("dots",         "&#188;");
        convert.put("bigtriangledown", "&#209;");
        convert.put("prod",         "&#213;");
        convert.put("neg",          "&#216;");
        convert.put("lnot",         "&#216;");
        convert.put("wedge",        "&#217;");
        convert.put("land",         "&#217;");
        convert.put("vee",          "&#218;");
        convert.put("lor",          "&#218;");
        convert.put("sum",          "&#229;");
        convert.put("setminus",     "\\");
        convert.put("mapsto",       "&#241;&#174;");
        convert.put("leadsto",      "~&gt;");

        convert.put("Leftrightarrow", "&#219;");
        convert.put("Leftarrow",    "&#220;");
        convert.put("Uparrow",      "&#221;");
        convert.put("Rightarrow",   "&#222;");
        convert.put("Downarrow",    "&#223;");

        convert.put("partial",      "&#182;");
        convert.put("bullet",       "&#183;");
        convert.put("ne",           "&#185;");
        convert.put("neq",          "&#185;");
        convert.put("ldotx",        "&#188;");
        convert.put("mid",          "&#189;");

        convert.put("times",        "<font size=-2>&#067;</font>");
        convert.put("circ",         "<sub>&#176;</sub>");

        convert.put("otimes",       "&#196;");
        convert.put("oplus",        "&#197;");
        convert.put("emptyset",     "&#198;");
        convert.put("cap",          "&#199;");
        convert.put("bigcap",       "<font size=+3>&#199;</font>");
        convert.put("cup",          "&#200;");
        convert.put("bigcup",       "<font size=+3>&#200;</font>");
        convert.put("supset",       "&#201;");
        convert.put("supseteq",     "&#202;");
        convert.put("subset",       "&#204;");
        convert.put("subseteq",     "&#205;");
        convert.put("in",           "&#206;");
        convert.put("nin",          "&#207;");
        convert.put("notin",        "&#207;");

        convert.put("cdot",         "&#215;");

        convert.put("langle",       "&#225;");
        convert.put("rangle",       "&#241;");

        convert.put("Gamma",        "&#071;");
        convert.put("Delta",        "&#063;");
        convert.put("Theta",        "&#081;");
        convert.put("Lambda",       "&#076;");
        convert.put("Xi",           "&#081;");
        convert.put("Pi",           "&#213;");
        convert.put("Sigma",        "&#083;");
        convert.put("Upsilon",      "&#085;");
        convert.put("Phi",          "<font size=+2>&#102;</font>");
        convert.put("Psi",          "&#089;");
        convert.put("Omega",        "&#087;");

        convert.put("alpha",        "&#097;");
        convert.put("beta",         "&#098;");
        convert.put("gamma",        "&#103;");
        convert.put("delta",        "&#100;");
        convert.put("epsilon",      "&#101;");
        convert.put("zeta",         "&#122;");
        convert.put("eta",          "&#104;");
        convert.put("theta",        "&#113;");
        convert.put("iota",         "&#105;");
        convert.put("varphi",       "&#106;");
        convert.put("kappa",        "&#107;");
        convert.put("lambda",       "&#108;");
        convert.put("mu",           "&#109;");
        convert.put("nu",           "&#109;");
        convert.put("xi",           "&#120;");
        convert.put("pi",           "&#112;");
        convert.put("rho",          "&#114;");
        convert.put("sigma",        "&#115;");
        convert.put("tau",          "&#116;");
        convert.put("upsilon",      "&#117;");
        convert.put("varpi",        "&#118;");
        convert.put("phi",          "&#102;");
        convert.put("chi",          "&#099;");
        convert.put("psi",          "&#121;");
        convert.put("omega",        "&#119;");

        convert.put("ast",          "&#042;");

        convert.put("star",         "<font size=-1>#</font>");

        convert.put("vdash",        "<font size=-1>|-</font>");
        convert.put("dashv",        "<font size=-1>-|</font>");
        convert.put("models",       "<font size=-1>|=</font>");
        convert.put("mid",          "||");

        convert.put("left{",        "<font size=+1>{</font>");
        convert.put("right}",       "<font size=+1>}</font>");
        convert.put("left(",        "<font size=+1>(</font>");
        convert.put("right)",       "<font size=+1>)</font>");
        convert.put("left.",        "");
        convert.put("right.",       "");

        convert.put("\"a",           "&auml;");
        convert.put("\"o",           "&ouml;");
        convert.put("\"u",           "&uuml;");
        convert.put("\"s",           "&szlig;");
        convert.put("\"A",           "&Auml;");
        convert.put("\"O",           "&Ouml;");
        convert.put("\"U",           "&Uuml;");
        convert.put("\"'",           "''");
        convert.put("\"`",           "``");
        convert.put("/",             "");
        convert.put("hfill",         "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
        convert.put("ell",           "<font color=#00AA00>l</font>");
        convert.put("imath",         "<font color=#00AA00>i</font>");
        convert.put("jmath",         "<font color=#00AA00>j</font>");

        convert.put("tt",           new Tag("code"));
        convert.put("em",           new Tag("em"));
        convert.put("bf",           new Tag("b"));
        convert.put("mathbf",       new Tag("b"));
        convert.put("it",           new Tag("i"));
        convert.put("boldmath",     new Tag("b"));
        convert.put("tiny",         new Tag("font size=1"));
        convert.put("small",        new Tag("font size=2"));
        convert.put("normalsize",   new Tag("font size=3"));
        convert.put("large",        new Tag("font size=4"));
        convert.put("Large",        new Tag("font size=5"));
        convert.put("huge",         new Tag("font size=6"));
        convert.put("LARGE",        new Tag("font size=6"));
        convert.put("Huge",         new Tag("font size=7"));

        convert.put("mathcal",      new Tag("font color=#00AA00"));
        convert.put("mathfrak",     new Tag("font color=#008000"));
        convert.put("mathbb",       new Tag("b"));

        convert.put("footnote",     new Tag("em"));
    }


    /**
     * Takes the given file names (without ".tex") and converts them
     * into html. The resulting files are at the same position but
     * have the ending ".html".
     *
     * @param   argument    list of LaTeX files to convert
     */
    public static final void main (String[] argument) {
        System.out.println("Converting into html...");
        for (int i = 0; i < argument.length; i++) {
            TextInput input = null;
            try {
                StringBuffer in = new StringBuffer(
                    Utility.loadFile(argument[i] + ".tex"));
                StringBuffer out = new StringBuffer();
                input = new TextInput(in, argument[i] + ".tex",
                    argument[i] + ".tex");
                Output output = new Output(out);
                new Latex2OldHtml(input, output).closeFont();
                Utility.saveFile(argument[i] + ".html", new String(out));
            } catch (Exception e) {
                e.printStackTrace();
                System.err.println(argument[i] + ".tex"
                    + ":" + input.getRow() + ":"
                    + input.getColumn() + ":");
                System.err.println(e.getMessage());
                System.err.println(input.getLine());
                final StringBuffer pointer
                    = new StringBuffer(input.getColumn());
                for (int j = 0; j < input.getColumn() - 1; j++) {
                    pointer.append(' ');   // TODO Q & D
                }
                pointer.append("^");
                System.err.println(pointer);
            }
        }
        System.out.println();
        System.out.println("... converting done!");
    }


    /**
     * Takes the given LaTeX text and convert it
     * into html.
     *
     * @param   latex    LaTeX text to convert
     * @return  html text
     */
    public static final String convert(final String latex) {
        TextInput input = null;
        StringBuffer in = new StringBuffer(latex);
        StringBuffer out = new StringBuffer();
        input = new TextInput(in, "", "");
        Output output = new Output(out);
        new Latex2OldHtml(input, output).closeFont();
        return out.toString();
    }


    /** is font environment opened? */
    private boolean font = false;

    /** is math mode? */
    private boolean math = false;

    /** is symbol font active? */
    private boolean symbol = false;

    /** is emph active? */
    private boolean emph = false;

    /** is bold active? */
    private boolean bold = false;

    /** is in table ? */
    private boolean table = false;

    /** number of columns  */
    private int tableCols = 0;

    /** is the first item already done? */
    private boolean firstItem = false;

    /** input */
    private final TextInput input;

    /** output */
    private final Output output;


    /**
     * Constructor that does it all.
     *
     * @param   input   read from this object
     * @param   output  write to this object
     */
    public Latex2OldHtml(final TextInput input, final Output output) {
        this.input = input;
        this.output = output;
        while (!input.isEmpty()) {
//            try {
                readArgument();
//            } catch (IllegalArgumentException e) {
                // ignore
//            }
            if (input.getChar() == '}') {
                input.readChar();
            }
        }
    }


    /**
     * Reads an environment from input and writes the converted to output.
     */
    private final void readArgument()
            throws IllegalArgumentException {
        String key = null;
        List local = new ArrayList();

        while (!input.isEmpty()) {
            dealWithWhitespace();
            if (input.isEmpty()) {
                return;
            }
            final char c = input.getChar();
            if (c == '}') {
                for (int i = local.size() - 1; i >= 0; i--) {
                    write(((Tag) local.get(i)).getEnd());
                    if (((Tag) local.get(i)).getEnd().indexOf("</b>") >= 0) {
                        bold = false;
                    }
                }
                return;
            } else if (c == '%') {  // LaTex comment
                input.readChar();
                while (!input.isEmpty() && input.getChar() != '\012') {
                    input.readChar();
                }
                if (!input.isEmpty()) {
                    input.readChar();
                }
            } else if (c == '_') {
                input.readChar();
                closeFont();
                output.write("<sub>");
                if (input.getChar() == '{') {
                    input.readChar();   // {
                    readArgument();
                    input.readChar();   // }
                } else {
                    while (Character.isLetterOrDigit(input.getChar())) {
                        write(input.readChar());
                    }
                }
                closeFont();
                output.write("</sub>");
            } else if (c == '^') {
                input.readChar();
                closeFont();
                output.write("<sup>");
                if (input.getChar() == '{') {
                    input.readChar();   // {
                    readArgument();
                    input.readChar();   // }
                } else {
                    while (Character.isLetterOrDigit(input.getChar())) {
                        write(input.readChar());
                    }
                }
                closeFont();
                output.write("</sup>");
            } else if (c == '&') {
                input.readChar();
                if (table) {
                    closeCell();
                    output.write("<td>");
                } else {    // TODO ????
                    output.write('&');
                }
            } else if (c == '~') {
                input.readChar();
                output.write(" ");
            } else if (c == '\\') {
                input.readChar();
                if (Character.isLetter(input.getChar())) {
                    key = input.readLatexWord();
                    if (key.equals("left") || key.equals("right")) {
                        continue;
                    }
//                    System.out.println(key);
                } else {
                    key = "" + input.readChar();
                }
                if (key.equals("begin")) {
                    skipWhitespace();
                    if ('{' != input.readChar()) {
                        throw new IllegalArgumentException("{ expected");
                    }
                    final String env = input.readLatexWord();
                    if (convert.containsKey(env)) {
                        write(convert.get(env));
                    }
                    if ('*' == input.getChar()) {
                        input.readChar();
                    }
                    skipWhitespace();
                    if ('}' != input.readChar()) {
                        throw new IllegalArgumentException("} expected");
                    }
                    if (env.equals("equation")
                            || env.equals("eqnarray")
                            || env.equals("array")
                            || env.equals("split")
                            || env.equals("alignat")) {
                        math = true;
                        closeFont();
                        if (env.equals("eqnarray")
                                || env.equals("array")
                                || env.equals("split")
                                || env.equals("alignat")) {
                            output.writeln("<table><tr><td>");
                            tableCols = 0;
                            table = true;
                            skipWhitespace();
                            if ('{' == input.readChar()) {
                                while (!input.isEmpty()) {
                                    char cr;
                                    if ((cr = input.readChar()) == '}') {
                                        break;
                                    } else {
                                        if (cr == 'l' || cr == 'c' || cr == 'r') {
                                            tableCols++;
                                        }
                                    }
                                }
                            }
                        } else {
                            output.writeln("<p>");
                        }
                    } else if (env.equals("tabular")) {
                        closeFont();
                        output.writeln("<table><tr><td>");
                        table = true;
                        tableCols = 0;
                        skipWhitespace();
                        if ('{' == input.readChar()) {
                            while (!input.isEmpty()) {
                                char cr;
                                if ((cr = input.readChar()) == '}') {
                                    break;
                                } else {
                                    if (cr == 'l' || cr == 'c' || cr == 'r') {
                                        tableCols++;
                                    }
                                }
                            }
                        }
                    } else if (env.equals("itemize")
                            || env.equals("description")) {
                        closeFont();
                        output.writeln("<ul>");
                        firstItem = true;
                    } else if (env.equals("enumerate")) {
                        closeFont();
                        output.writeln("<ol>");
                        firstItem = true;
                    } else if (env.equals("verbatim")) {
                        closeFont();
                        output.writeln("<tt>");
                        while (!input.isEmpty()) {
                            if (input.getChar() == '\\') {
                                input.readChar();   // \
                                final String word = input.readLatexWord();
                                if (word.equals("end") && input.getChar() == '{') {
                                    input.readChar();       // {
                                    final String token = input.readLatexWord();
                                    if (token.equals("verbatim")) {
                                        if (input.getChar() == '*') {
                                            input.readChar();
                                        }
                                        input.readChar();   // }
                                        break;
                                    } else {
                                        output.write("\\" + word + "{" + token);
                                    }
                                } else {
                                    output.write("\\" + word);
                                }
                            } else if (input.getChar() != '\012') {
                                final char ch = input.readChar();
                                switch (ch) {
                                case '<':   output.write("&lt;");
                                    break;
                                case '&':   output.write("&amp;");
                                    break;
                                default:
                                    output.write(ch);
                                }

                            } else {
                                input.readChar();
                                output.writeln("<br>");
                            }
                        }
                        output.writeln("<br>");
                        output.writeln("</tt>");
                        closeFont();
                        continue;
                    }
                    readArgument();
                    if (convert.containsKey(env)) {
                        output.write(((Tag) convert.get(env)).getEnd());
                        if (((Tag) convert.get(key)).getEnd().indexOf("</b>") >= 0) {
                            bold = false;
                        }
                    }
                } else if (key.equals("end")) {
                    skipWhitespace();
                    if ('{' != input.readChar()) {
                        throw new IllegalArgumentException("{ expected");
                    }
                    final String env = input.readLatexWord();
                    if (convert.containsKey(env)) {
                        write(((Tag) convert.get(env)).getEnd());
                        if (((Tag) convert.get(key)).getEnd().indexOf("</b>") >= 0) {
                            bold = false;
                        }
                    }
                    if ('*' == input.getChar()) {
                        input.readChar();
                    }
                    skipWhitespace();
                    if ('}' != input.readChar()) {
                        throw new IllegalArgumentException("} expected");
                    }
                    if (env.equals("equation")
                            || env.equals("eqnarray")
                            || env.equals("array")
                            || env.equals("split")
                            || env.equals("alignat")) {
                        math = false;
                        closeFont();
                        if (env.equals("eqnarray")
                                || env.equals("array")
                                || env.equals("split")
                                || env.equals("alignat")) {
                            table = false;
                            output.writeln("</td></tr>");
                            output.writeln("</table>");
                        } else {
                            output.writeln("<p>");
                        }
                    } else if (env.equals("tabular")) {
                        closeFont();
                        output.writeln("</td></tr>");
                        output.writeln("</table>");
                        table = false;
                    } else if (env.equals("itemize")) {
                        closeFont();
                        if (!firstItem) {
                            output.writeln("</li>");
                        } else {
                            firstItem = false;
                        }
                        output.writeln("</ul>");
                    } else if (env.equals("description")) {
                        closeFont();
                        firstItem = false;
                        output.writeln("</ul>");
                    } else if (env.equals("enumerate")) {
                        closeFont();
                        if (!firstItem) {
                            output.writeln("</li>");
                        } else {
                            firstItem = false;
                        }
                        output.writeln("</ol>");
                    }
                    return;
                } else if (key.equals("hline")) {
                    if (table) {
                        output.writeln("<tr><th colspan=" + tableCols
                            + "><hr></th></tr><tr><td>");
                    } else {
                        output.writeln("<hr>");
                    }
                } else if (key.equals("verb")) {
                    final char separator = input.readChar();
                    String display = "";
                    while (!input.isEmpty() && separator != input.getChar()) {
                        display += input.readChar();
                    }
                    input.readChar();
                    closeFont();
                    output.write("<tt>" + display + "</tt>");
                } else if (key.equals("qedeq")) {// this is an LaTeX extensioin
                    skipWhitespace();
                    if ('{' != input.readChar()) {
                        throw new IllegalArgumentException("{ expected");
                    }

                    final StringBuffer qedeq = new StringBuffer();
                    while (!input.isEmpty() && input.getChar() != '}') {
                        if (input.getChar() != '\"') {
                            qedeq.append(input.readChar());
                        } else {
                            qedeq.append(input.readQuoted());
                        }
                    }
                    if ('}' != input.readChar()) {
                        throw new IllegalArgumentException("} expected");
                    }
                    output.write(convert(Qedeq2Latex.convert(qedeq.toString())));
// TODO
//                    System.out.println(Qedeq2Latex.convert(qedeq.toString()));
//                    System.out.println(convert(Qedeq2Latex.convert(qedeq.toString())));
// TODO
/*
                    f?nal StringBuffer out = new StringBuffer();
                    try {
                        TextInput input = new TextInput(in, argument[i] + "", "");
                        Output output = new Output(out);
                        Qedeq2Latex.readAll(input, output);
                        output.write(output);
                    } catch (Exception e) {

                        e.printStackTrace();
                        System.err.println(argument[i] + ".qedeq"
                            + ":" + input.getRow() + ":"
                            + input.getColumn() + ":");
                        System.err.println(e.getMessage());
                        System.err.println(input.getLine());
                        final StringBuffer pointer = Utility.getSpaces(input.getColumn());
                        for (int j = 0; j < input.getColumn() - 1; j++) {
                            pointer.append(' ');   // TODO Q & D
                        }
                        pointer.append("^");
                        System.err.println(pointer);
                        output.write(qedeq);
                    }
*/
                } else if (key.equals("item")) {
                    skipWhitespace();
                    String display = "";
                    if ('['  == input.getChar()) {
                        input.readChar();
                        while (!input.isEmpty() && ']' != input.getChar()) {
                            display += input.readChar();
                        }
                        input.readChar();
                        closeFont();
                        output.write("<br>");
                        output.write("<b>");
                        write(convert(display));
                        closeFont();
                        output.write("</b>");
                        firstItem = false;
                    } else {
                        closeFont();
                        if (!firstItem) {
                            output.write("</li>");
                        }
                        output.write("<li>");
                        firstItem = false;
                    }
                } else if (key.equals("cite")) {
                    skipWhitespace();
                    String display = "";
                    if ('['  == input.getChar()) {
                        input.readChar();
                        while (!input.isEmpty() && ']' != input.getChar()) {
                            display += input.readChar();
                        }
                        input.readChar();
                    }
                    if ('{' != input.getChar()) {
                        throw new IllegalArgumentException("'{' expected");
                    } else {
                        input.readChar();
                    }
                    String env = input.readLatexEnvironment();
                    if ('}' != input.readChar()) {
                        throw new IllegalArgumentException("} expected");
                    }
                    if (display.length() == 0) {
                        display = env;
                    }
                    env = Utility.replace(env, ":", "_");
                    output.write("<a href=\"#cite_" + env + "\">[" + display + "]</a>");
                } else if (key.equals("index")) {
                    skipWhitespace();
                    if ('{' == input.getChar()) {
                        input.readChar();
                        input.readLatexEnvironment();
                        if ('}' == input.getChar()) {
                            input.readChar();
                        }
                    }
                } else if (key.equals("bibitem")) {
                    skipWhitespace();
                    String display = "";
                    if ('['  == input.getChar()) {
                        input.readChar();
                        while ( !input.isEmpty() && ']' != input.getChar()) {
                            display += input.readChar();
                        }
                        input.readChar();
                    }
                    String env;
                    if ('{' != input.getChar()) {
                        env = display;
                    } else {
                        input.readChar();
                        env = input.readLatexEnvironment();
                        if ('}' != input.getChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        } else {
                            input.readChar();
                        }
                    }
                    if (display.length() == 0) {
                        display = env;
                    }
                    env = Utility.replace(env, ":", "_");
                    output.write("<a name=\"cite_" + env + "\">["
                        + display + "]</a>");
                } else if (key.equals("href")) {
                    skipWhitespace();
                    String display = "";
                    if ('{'  == input.getChar()) {
                        input.readChar();
                        while ( !input.isEmpty() && '}' != input.getChar()) {
                            display += input.readChar();
                        }
                        input.readChar();
                    }
                    String env;
                    if ('{' != input.getChar()) {
                        env = display;
                    } else {
                        input.readChar();
                        env = input.readLatexEnvironment();
                        if ('}' != input.getChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        } else {
                            input.readChar();
                        }
                    }
                    if (display.length() == 0) {
                        display = env;
                    }
                    env = Utility.replace(env, ":", "_");
                    output.write("<a href=\"" + display + "\">"
                        + env + "</a>");
                } else if (key.equals("label")) {
                    final String env;
                    if ('{' != input.readChar()) {
                        env = input.readLatexWord();
                        throw new IllegalArgumentException("'{' expected");
                    } else {
                        env = input.readLatexEnvironment();
                        if ('}' != input.readChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        }
                    }
                    output.write("<a name=\"label_" + env + "\">");
                    write("[" + env + "]");
                    output.write("</a>");
                } else if (key.equals("ref")) {
                    final String env;
                    if ('{' != input.readChar()) {
                        env = input.readLatexWord();
                        throw new IllegalArgumentException("'{' expected");
                    } else {
                        env = input.readLatexEnvironment();
                        if ('}' != input.readChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        }
                    }
                    output.write("<a href=\"#label_" + env + "\">" + env + "</a>");
                } else if (key.equals("url")) {
                    final String env;
                    if ('{' != input.readChar()) {
                        env = input.readLatexWord();
                        throw new IllegalArgumentException("'{' expected");
                    } else {
                        env = input.readLatexEnvironment();
                        if ('}' != input.readChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        }
                    }
                    output.write("<a href=\"" + env + "\">" + env + "</a>");
                } else if (key.equals("mbox") || key.equals("text")) {
                    boolean oldMath = math;
                    this.math = false;
                    skipWhitespace();
                    if ('{' != input.readChar()) {
                        throw new IllegalArgumentException("'{' expected");
                    }
                    readArgument();
                    skipWhitespace();
                    if ('}' != input.readChar()) {
                        throw new IllegalArgumentException("'}' expected");
                    }
                    this.math = oldMath;
                } else if (key.equals("chapter")) {
                    if ('*' == input.getChar()) {
                        input.readChar();
                    }
                    skipWhitespace();
                    closeFont();
                    math = false;
                    output.write("<h1>");
                    final String env;
                    if ('{' != input.getChar()) {
                        env = input.readLatexWord();
                        write(env);
                    } else {
                        input.readChar();
                        readArgument();
                        skipWhitespace();
                        if ('}' != input.readChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        }
                    }
                    closeFont();
                    output.write("</h1>");
                    closeFont();
                } else if (key.equals("section")) {
                    if ('*' == input.getChar()) {
                        input.readChar();
                    }
                    skipWhitespace();
                    closeFont();
                    math = false;
                    output.write("<h2>");
                    final String env;
                    if ('{' != input.getChar()) {
                        env = input.readLatexWord();
                        write(env);
                    } else {
                        input.readChar();
                        readArgument();
                        skipWhitespace();
                        if ('}' != input.readChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        }
                    }
                    closeFont();
                    output.write("</h2>");
                    closeFont();
                } else if (key.equals("subsection")) {
                    if ('*' == input.getChar()) {
                        input.readChar();
                    }
                    skipWhitespace();
                    closeFont();
                    math = false;
                    output.write("<h3>");
                    final String env;
                    if ('{' != input.getChar()) {
                        env = input.readLatexWord();
                        write(env);
                    } else {
                        input.readChar();
                        readArgument();
                        skipWhitespace();
                        if ('}' != input.readChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        }
                    }
                    closeFont();
                    output.write("</h3>");
                    closeFont();
                } else if (key.equals("subsubsection")) {
                    if ('*' == input.getChar()) {
                        input.readChar();
                    }
                    skipWhitespace();
                    closeFont();
                    math = false;
                    output.write("<h4>");
                    final String env;
                    if ('{' != input.getChar()) {
                        env = input.readLatexWord();
                        write(env);
                    } else {
                        input.readChar();
                        readArgument();
                        skipWhitespace();
                        if ('}' != input.readChar()) {
                            throw new IllegalArgumentException("} expected");
                        }
                    }
                    closeFont();
                    output.write("</h4>");
                    closeFont();
                } else if (key.equals("input")) {
                    skipWhitespace();
                    final String env;
                    if ('{' != input.getChar()) {
                        env = input.readLatexWord();
                    } else {
                        input.readChar();
                        env = input.readLatexEnvironment();
                        skipWhitespace();
                        if ('}' != input.readChar()) {
                            throw new IllegalArgumentException("} expected");
                        }
                    }
                    StringBuffer in;
                    try {
                        in = new StringBuffer(Utility.loadFile(env));
                    } catch (Exception ex) {
                        try {
                            in = new StringBuffer(Utility.loadFile(env + ".tex"));
                        } catch (Exception e) {
                            throw new IllegalArgumentException(e.toString());
                        }
                    }
                    TextInput ip = null;
                    try {
                        final StringBuffer out = new StringBuffer();
                        ip = new TextInput(in, env, env);
                        Output op = new Output(out);
                        new Latex2OldHtml(ip, op);
                        output.writeln(out);
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.err.println(env
                            + ":" + input.getRow() + ":"
                            + ip.getColumn() + ":");
                        System.err.println(e.getMessage());
                        System.err.println(ip.getLine());
                        final StringBuffer pointer = new StringBuffer(ip.getColumn());
                        for (int j = 0; j < ip.getColumn() - 1; j++) {
                            pointer.append(' ');   // TODO Q & D
                        }
                        pointer.append("^");
                        System.err.println(pointer);
                    }
                } else if (key.equals("\\")) {
                    skipWhitespace();
                    if (table) {
                        closeCell();
                        output.writeln("</tr>");
                        if (!(input.getChar() == '\\'
                                && input.getChar(1) == 'h'
                                && input.getChar(2) == 'l'
                                && input.getChar(3) == 'i'
                                && input.getChar(4) == 'n'
                                && input.getChar(5) == 'e')) {
                            output.writeln("<tr><td>");
                        }
                    } else {
                        output.writeln("<br>");
                    }
                } else if (key.equals("emph")) {
                    emph = !emph;
                    if (emph && font) {
                        output.write("<i>");
                    } else if (!emph && font) {
                        output.write("</i>");
                    }
                    if ('{' == input.getChar()) {
                        input.readChar();
                        readArgument();
                        if ('}' != input.getChar()) {
                            throw new IllegalArgumentException("'}' expected");
                        }
                        input.readChar();
                        emph = !emph;
                        if (emph && font) {
                            output.write("<i>");
                        } else if (!emph && font) {
                            output.write("</i>");
                        }
                    }
                } else if (convert.containsKey(key)) {
                    if (convert.get(key).toString().indexOf("<p>") >= 0) {
                        closeFont();
                        output.write(convert.get(key));
                    } else {
                        write(convert.get(key));
                        if (convert.get(key).toString().indexOf("<b>") >= 0) {
                            bold = true;
                        } else if (convert.get(key).toString().indexOf("</b>") >= 0) {
                            bold = false;
                        }
                    }
                    if (input.getChar() == '{') {
                        input.readChar();
                        readArgument();
                        if (convert.get(key) instanceof Tag) {
                            write(((Tag) convert.get(key)).getEnd());
                            if (((Tag) convert.get(key)).getEnd().indexOf("</b>") >= 0) {
                                bold = false;
                            }
                        }
                        input.readChar();
                    } else {
                        if (convert.get(key) instanceof Tag) {
                            local.add(convert.get(key));
                        }
                    }
                } else {
                    write("<font color=#FF0000>" + key + "</font> ");
                }
            } else if (c == '\"') {
                key = "" + input.readChar() + input.readChar();
                if (convert.containsKey(key)) {
                    write(convert.get(key));
                    if (input.getChar() == '{') {
                        input.readChar();
                        readArgument();
                        if (convert.get(key) instanceof Tag) {
                            write(((Tag) convert.get(key)).getEnd());
                            if (((Tag) convert.get(key)).getEnd()
                                    .indexOf("</b>") >= 0) {
                                bold = false;
                            }
                        }
                        input.readChar();
                    } else {
                        if (convert.get(key) instanceof Tag) {
                            local.add(convert.get(key));
                        }
                    }
                } else {
                    write(key);
                }
            } else if (c == '{') {
                input.readChar();
                readArgument();
                input.readChar();
            } else if (c == '$') {
                input.readChar();
                math = !math;
            } else {
                final char ch = input.readChar();
                switch (ch) {
                case '<':   write("&lt;");
                    break;
                case '>':   write("&gt;");
                    break;
                case '&':   write("&amp;");
                    break;
                case '"':   write("&quot;");
                    break;
                default:
                    write(ch);
                }
            }
        }
    }

    /**
     * Shortcut for writing to the output. The input parameter
     * is converted into a  <code>String</code> and the method
     * {@link #write(Object)} is called.
     *
     * @param   c   character to write
     */
    private final void write(final char c) {
        write(c + "");  // convert to String and use other method
    }


    /**
     * Shortcut for writing to the output. Method
     * {@link #write(Object)} is called.
     *
     * @param   key   write this String
     */
    private final void writeln(final String key) {
        write(key +"\n");
    }


    /**
     * Shortcut for writing to the output.
     *
     * @param   obj  object to write
     */
    private final void write(final Object obj) {
        final String key = obj.toString();
        if (!font || ((key.indexOf("&#") >= 0) ^ symbol)) {
            if (font) {
                if (emph) {
                    output.write("</i>");
                }
                if (bold) {
                    output.write("</b>");
                }
                output.writeln("</font>");
            }
            font = true;
            symbol = key.indexOf("&#") >= 0;
            if (symbol) {
                output.write("<font face=\"Symbol\">");
                if (bold) {
                    output.write("<b>");
                }
                if (emph) {
                    output.write("<i>");
                }
            } else {
                output.write("<font face=\"Helvetica,Arial\">");
                if (bold) {
                    output.write("<b>");
                }
                if (emph) {
                    output.write("<i>");
                }
            }
        }
        output.write(key);
    }


    /**
     * Close html font environment. Also closes <code>emph</code>.
     */
    private final void closeFont() {
        if (font) {
            if (emph) {
                output.write("</i>");
            }
            if (bold) {
                output.write("</b>");
            }
            output.writeln("</font>");
            font = false;
        }
    }


    /**
     * Close cell environment of an html table. Also closes font
     * environment.
     */
    private final void closeCell() {
        if (font) {
            closeFont();
        }
        if (table) {
            output.writeln("</td>");
        }
    }


    /**
     * Skips whitespace.
     */
    private final void skipWhitespace() {
        while (!input.isEmpty() && Character.isWhitespace(input.getChar())) {
            output.write(input.readChar());
        }
    }


    private final void dealWithWhitespace() {
        int lines = 0;
        while (!input.isEmpty() && Character.isWhitespace(input.getChar())) {
            if (input.getChar() == '\012') {
                lines++;
            }
            output.write(input.readChar());
        }
        if (lines > 1) {
            output.writeln("<br>");
        }
    }


    private static class Tag {

        final String begin;
        final String end;

        Tag() {  // ignore
            this.begin = "";
            this.end = "";
        }

        Tag(final String single) {
            final int i = single.indexOf(" ");
            if (i < 0) {
                this.begin = "<" + single + ">";
                this.end = "</" + single + ">";
            } else {
                this.begin = "<" + single + ">";
                this.end = "</" + single.substring(0, i) + ">";
            }
        }

        Tag(final String begin, final String end) {
            this.begin = "<" + begin + ">";
            this.end = "</" + end + ">";
        }

        final String getBegin() {
            return this.begin;
        }

        final String getEnd() {
            return this.end;
        }

        public final String toString() {
            return this.begin;
        }

    }

}
