From d05917bf1b4cbe12a21300d90c28c38760cb2d17 Mon Sep 17 00:00:00 2001
From: Sandro Tosi <morph@debian.org>
Date: Thu, 8 Oct 2015 08:29:11 -0700
Subject: add the matplotlib sphinxext directory into doc, needed to build
 documentation

Forwarded: not-needed
Patch-Name: install_matplotlib_sphinxext
---
 doc/sphinxext/gen_gallery.py         | 124 ++++++++
 doc/sphinxext/gen_rst.py             | 158 ++++++++++
 doc/sphinxext/inheritance_diagram.py | 407 ++++++++++++++++++++++++++
 doc/sphinxext/math_symbol_table.py   | 159 ++++++++++
 doc/sphinxext/mathml.py              | 552 +++++++++++++++++++++++++++++++++++
 5 files changed, 1400 insertions(+)
 create mode 100644 doc/sphinxext/gen_gallery.py
 create mode 100644 doc/sphinxext/gen_rst.py
 create mode 100644 doc/sphinxext/inheritance_diagram.py
 create mode 100644 doc/sphinxext/math_symbol_table.py
 create mode 100644 doc/sphinxext/mathml.py

diff --git a/doc/sphinxext/gen_gallery.py b/doc/sphinxext/gen_gallery.py
new file mode 100644
index 0000000..d47df8a
--- /dev/null
+++ b/doc/sphinxext/gen_gallery.py
@@ -0,0 +1,124 @@
+# generate a thumbnail gallery of examples
+template = """\
+{%% extends "layout.html" %%}
+{%% set title = "Thumbnail gallery" %%}
+
+
+{%% block body %%}
+
+<h3>Click on any image to see full size image and source code</h3>
+<br/>
+
+%s
+{%% endblock %%}
+"""
+
+import os, glob, re, sys, warnings
+import matplotlib.image as image
+
+multiimage = re.compile('(.*)_\d\d')
+
+def make_thumbnail(args):
+    image.thumbnail(args[0], args[1], 0.3)
+
+def out_of_date(original, derived):
+    return (not os.path.exists(derived) or
+            os.stat(derived).st_mtime < os.stat(original).st_mtime)
+
+def gen_gallery(app, doctree):
+    if app.builder.name != 'html':
+        return
+
+    outdir = app.builder.outdir
+    rootdir = 'plot_directive/mpl_examples'
+
+    # images we want to skip for the gallery because they are an unusual
+    # size that doesn't layout well in a table, or because they may be
+    # redundant with other images or uninteresting
+    skips = set([
+        'mathtext_examples',
+        'matshow_02',
+        'matshow_03',
+        'matplotlib_icon',
+        ])
+
+    data = []
+    thumbnails = {}
+
+    for subdir in ('api', 'pylab_examples', 'mplot3d', 'widgets', 'axes_grid' ):
+        origdir = os.path.join('build', rootdir, subdir)
+        thumbdir = os.path.join(outdir, rootdir, subdir, 'thumbnails')
+        if not os.path.exists(thumbdir):
+            os.makedirs(thumbdir)
+
+        for filename in sorted(glob.glob(os.path.join(origdir, '*.png'))):
+            if filename.endswith("hires.png"):
+                continue
+
+            path, filename = os.path.split(filename)
+            basename, ext = os.path.splitext(filename)
+            if basename in skips:
+                continue
+
+            # Create thumbnails based on images in tmpdir, and place
+            # them within the build tree
+            orig_path = str(os.path.join(origdir, filename))
+            thumb_path = str(os.path.join(thumbdir, filename))
+            if out_of_date(orig_path, thumb_path) or True:
+                thumbnails[orig_path] = thumb_path
+
+            m = multiimage.match(basename)
+            if m is None:
+                pyfile = '%s.py'%basename
+            else:
+                basename = m.group(1)
+                pyfile = '%s.py'%basename
+
+            data.append((subdir, basename,
+                         os.path.join(rootdir, subdir, 'thumbnails', filename)))
+
+    link_template = """\
+    <a href="%s"><img src="%s" border="0" alt="%s"/></a>
+    """
+
+    if len(data) == 0:
+        warnings.warn("No thumbnails were found")
+
+    rows = []
+    for (subdir, basename, thumbfile) in data:
+        if thumbfile is not None:
+            link = 'examples/%s/%s.html'%(subdir, basename)
+            rows.append(link_template%(link, thumbfile, basename))
+
+    # Only write out the file if the contents have actually changed.
+    # Otherwise, this triggers a full rebuild of the docs
+    content = template%'\n'.join(rows)
+    gallery_path = os.path.join(app.builder.srcdir, '_templates', 'gallery.html')
+    if os.path.exists(gallery_path):
+        fh = file(gallery_path, 'r')
+        regenerate = fh.read() != content
+        fh.close()
+    else:
+        regenerate = True
+    if regenerate:
+        fh = file(gallery_path, 'w')
+        fh.write(content)
+        fh.close()
+
+    try:
+        import multiprocessing
+        app.builder.info("generating thumbnails... ", nonl=True)
+        pool = multiprocessing.Pool()
+        pool.map(make_thumbnail, thumbnails.iteritems())
+        pool.close()
+        pool.join()
+        app.builder.info("done")
+
+    except ImportError:
+        for key in app.builder.status_iterator(
+            thumbnails.iterkeys(), "generating thumbnails... ",
+            length=len(thumbnails)):
+            image.thumbnail(key, thumbnails[key], 0.3)
+
+def setup(app):
+    app.connect('env-updated', gen_gallery)
diff --git a/doc/sphinxext/gen_rst.py b/doc/sphinxext/gen_rst.py
new file mode 100644
index 0000000..94418ac
--- /dev/null
+++ b/doc/sphinxext/gen_rst.py
@@ -0,0 +1,158 @@
+"""
+generate the rst files for the examples by iterating over the pylab examples
+"""
+import os, glob
+
+import os
+import re
+import sys
+fileList = []
+
+def out_of_date(original, derived):
+    """
+    Returns True if derivative is out-of-date wrt original,
+    both of which are full file paths.
+
+    TODO: this check isn't adequate in some cases.  Eg, if we discover
+    a bug when building the examples, the original and derived will be
+    unchanged but we still want to force a rebuild.
+    """
+    return (not os.path.exists(derived) or
+            os.stat(derived).st_mtime < os.stat(original).st_mtime)
+
+noplot_regex = re.compile(r"#\s*-\*-\s*noplot\s*-\*-")
+
+def generate_example_rst(app):
+    rootdir = os.path.join(app.builder.srcdir, 'mpl_examples')
+    exampledir = os.path.join(app.builder.srcdir, 'examples')
+    if not os.path.exists(exampledir):
+        os.makedirs(exampledir)
+
+    datad = {}
+    for root, subFolders, files in os.walk(rootdir):
+        for fname in files:
+            if ( fname.startswith('.') or fname.startswith('#') or fname.startswith('_') or
+                 fname.find('.svn')>=0 or not fname.endswith('.py') ):
+                continue
+
+            fullpath = os.path.join(root,fname)
+            contents = file(fullpath).read()
+            # indent
+            relpath = os.path.split(root)[-1]
+            datad.setdefault(relpath, []).append((fullpath, fname, contents))
+
+    subdirs = datad.keys()
+    subdirs.sort()
+
+    fhindex = file(os.path.join(exampledir, 'index.rst'), 'w')
+    fhindex.write("""\
+.. _examples-index:
+
+####################
+Matplotlib Examples
+####################
+
+.. htmlonly::
+
+    :Release: |version|
+    :Date: |today|
+
+.. toctree::
+    :maxdepth: 2
+
+""")
+
+    for subdir in subdirs:
+        rstdir = os.path.join(exampledir, subdir)
+        if not os.path.exists(rstdir):
+            os.makedirs(rstdir)
+
+        outputdir = os.path.join(app.builder.outdir, 'examples')
+        if not os.path.exists(outputdir):
+            os.makedirs(outputdir)
+
+        outputdir = os.path.join(outputdir, subdir)
+        if not os.path.exists(outputdir):
+            os.makedirs(outputdir)
+
+        subdirIndexFile = os.path.join(rstdir, 'index.rst')
+        fhsubdirIndex = file(subdirIndexFile, 'w')
+        fhindex.write('    %s/index.rst\n\n'%subdir)
+
+        fhsubdirIndex.write("""\
+.. _%s-examples-index:
+
+##############################################
+%s Examples
+##############################################
+
+.. htmlonly::
+
+    :Release: |version|
+    :Date: |today|
+
+.. toctree::
+    :maxdepth: 1
+
+"""%(subdir, subdir))
+
+        sys.stdout.write(subdir + ", ")
+        sys.stdout.flush()
+
+        data = datad[subdir]
+        data.sort()
+
+        for fullpath, fname, contents in data:
+            basename, ext = os.path.splitext(fname)
+            outputfile = os.path.join(outputdir, fname)
+            #thumbfile = os.path.join(thumb_dir, '%s.png'%basename)
+            #print '    static_dir=%s, basename=%s, fullpath=%s, fname=%s, thumb_dir=%s, thumbfile=%s'%(static_dir, basename, fullpath, fname, thumb_dir, thumbfile)
+
+            rstfile = '%s.rst'%basename
+            outrstfile = os.path.join(rstdir, rstfile)
+
+            fhsubdirIndex.write('    %s\n'%rstfile)
+
+            if not out_of_date(fullpath, outrstfile):
+                continue
+
+            fh = file(outrstfile, 'w')
+            fh.write('.. _%s-%s:\n\n'%(subdir, basename))
+            title = '%s example code: %s'%(subdir, fname)
+            #title = '<img src=%s> %s example code: %s'%(thumbfile, subdir, fname)
+
+
+            fh.write(title + '\n')
+            fh.write('='*len(title) + '\n\n')
+
+            do_plot = (subdir in ('api',
+                                  'pylab_examples',
+                                  'units',
+                                  'mplot3d',
+                                  'axes_grid',
+                                  ) and
+                       not noplot_regex.search(contents))
+
+            if do_plot:
+                fh.write("\n\n.. plot:: %s\n\n::\n\n" % fullpath)
+            else:
+                fh.write("[`source code <%s>`_]\n\n::\n\n" % fname)
+                fhstatic = file(outputfile, 'w')
+                fhstatic.write(contents)
+                fhstatic.close()
+
+            # indent the contents
+            contents = '\n'.join(['    %s'%row.rstrip() for row in contents.split('\n')])
+            fh.write(contents)
+
+            fh.write('\n\nKeywords: python, matplotlib, pylab, example, codex (see :ref:`how-to-search-examples`)')
+            fh.close()
+
+        fhsubdirIndex.close()
+
+    fhindex.close()
+
+    print
+
+def setup(app):
+    app.connect('builder-inited', generate_example_rst)
diff --git a/doc/sphinxext/inheritance_diagram.py b/doc/sphinxext/inheritance_diagram.py
new file mode 100644
index 0000000..407fc13
--- /dev/null
+++ b/doc/sphinxext/inheritance_diagram.py
@@ -0,0 +1,407 @@
+"""
+Defines a docutils directive for inserting inheritance diagrams.
+
+Provide the directive with one or more classes or modules (separated
+by whitespace).  For modules, all of the classes in that module will
+be used.
+
+Example::
+
+   Given the following classes:
+
+   class A: pass
+   class B(A): pass
+   class C(A): pass
+   class D(B, C): pass
+   class E(B): pass
+
+   .. inheritance-diagram: D E
+
+   Produces a graph like the following:
+
+               A
+              / \
+             B   C
+            / \ /
+           E   D
+
+The graph is inserted as a PNG+image map into HTML and a PDF in
+LaTeX.
+"""
+
+import inspect
+import os
+import re
+import subprocess
+try:
+    from hashlib import md5
+except ImportError:
+    from md5 import md5
+
+from docutils.nodes import Body, Element
+from docutils.parsers.rst import directives
+from sphinx.roles import xfileref_role
+
+def my_import(name):
+    """Module importer - taken from the python documentation.
+
+    This function allows importing names with dots in them."""
+    
+    mod = __import__(name)
+    components = name.split('.')
+    for comp in components[1:]:
+        mod = getattr(mod, comp)
+    return mod
+
+class DotException(Exception):
+    pass
+
+class InheritanceGraph(object):
+    """
+    Given a list of classes, determines the set of classes that
+    they inherit from all the way to the root "object", and then
+    is able to generate a graphviz dot graph from them.
+    """
+    def __init__(self, class_names, show_builtins=False):
+        """
+        *class_names* is a list of child classes to show bases from.
+
+        If *show_builtins* is True, then Python builtins will be shown
+        in the graph.
+        """
+        self.class_names = class_names
+        self.classes = self._import_classes(class_names)
+        self.all_classes = self._all_classes(self.classes)
+        if len(self.all_classes) == 0:
+            raise ValueError("No classes found for inheritance diagram")
+        self.show_builtins = show_builtins
+
+    py_sig_re = re.compile(r'''^([\w.]*\.)?    # class names
+                           (\w+)  \s* $        # optionally arguments
+                           ''', re.VERBOSE)
+
+    def _import_class_or_module(self, name):
+        """
+        Import a class using its fully-qualified *name*.
+        """
+        try:
+            path, base = self.py_sig_re.match(name).groups()
+        except:
+            raise ValueError(
+                "Invalid class or module '%s' specified for inheritance diagram" % name)
+        fullname = (path or '') + base
+        path = (path and path.rstrip('.'))
+        if not path:
+            path = base
+        try:
+            module = __import__(path, None, None, [])
+            # We must do an import of the fully qualified name.  Otherwise if a
+            # subpackage 'a.b' is requested where 'import a' does NOT provide
+            # 'a.b' automatically, then 'a.b' will not be found below.  This
+            # second call will force the equivalent of 'import a.b' to happen
+            # after the top-level import above.
+            my_import(fullname)
+            
+        except ImportError:
+            raise ValueError(
+                "Could not import class or module '%s' specified for inheritance diagram" % name)
+
+        try:
+            todoc = module
+            for comp in fullname.split('.')[1:]:
+                todoc = getattr(todoc, comp)
+        except AttributeError:
+            raise ValueError(
+                "Could not find class or module '%s' specified for inheritance diagram" % name)
+
+        # If a class, just return it
+        if inspect.isclass(todoc):
+            return [todoc]
+        elif inspect.ismodule(todoc):
+            classes = []
+            for cls in todoc.__dict__.values():
+                if inspect.isclass(cls) and cls.__module__ == todoc.__name__:
+                    classes.append(cls)
+            return classes
+        raise ValueError(
+            "'%s' does not resolve to a class or module" % name)
+
+    def _import_classes(self, class_names):
+        """
+        Import a list of classes.
+        """
+        classes = []
+        for name in class_names:
+            classes.extend(self._import_class_or_module(name))
+        return classes
+
+    def _all_classes(self, classes):
+        """
+        Return a list of all classes that are ancestors of *classes*.
+        """
+        all_classes = {}
+
+        def recurse(cls):
+            all_classes[cls] = None
+            for c in cls.__bases__:
+                if c not in all_classes:
+                    recurse(c)
+
+        for cls in classes:
+            recurse(cls)
+
+        return all_classes.keys()
+
+    def class_name(self, cls, parts=0):
+        """
+        Given a class object, return a fully-qualified name.  This
+        works for things I've tested in matplotlib so far, but may not
+        be completely general.
+        """
+        module = cls.__module__
+        if module == '__builtin__':
+            fullname = cls.__name__
+        else:
+            fullname = "%s.%s" % (module, cls.__name__)
+        if parts == 0:
+            return fullname
+        name_parts = fullname.split('.')
+        return '.'.join(name_parts[-parts:])
+
+    def get_all_class_names(self):
+        """
+        Get all of the class names involved in the graph.
+        """
+        return [self.class_name(x) for x in self.all_classes]
+
+    # These are the default options for graphviz
+    default_graph_options = {
+        "rankdir": "LR",
+        "size": '"8.0, 12.0"'
+        }
+    default_node_options = {
+        "shape": "box",
+        "fontsize": 10,
+        "height": 0.25,
+        "fontname": "Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",
+        "style": '"setlinewidth(0.5)"'
+        }
+    default_edge_options = {
+        "arrowsize": 0.5,
+        "style": '"setlinewidth(0.5)"'
+        }
+
+    def _format_node_options(self, options):
+        return ','.join(["%s=%s" % x for x in options.items()])
+    def _format_graph_options(self, options):
+        return ''.join(["%s=%s;\n" % x for x in options.items()])
+
+    def generate_dot(self, fd, name, parts=0, urls={},
+                     graph_options={}, node_options={},
+                     edge_options={}):
+        """
+        Generate a graphviz dot graph from the classes that
+        were passed in to __init__.
+
+        *fd* is a Python file-like object to write to.
+
+        *name* is the name of the graph
+
+        *urls* is a dictionary mapping class names to http urls
+
+        *graph_options*, *node_options*, *edge_options* are
+        dictionaries containing key/value pairs to pass on as graphviz
+        properties.
+        """
+        g_options = self.default_graph_options.copy()
+        g_options.update(graph_options)
+        n_options = self.default_node_options.copy()
+        n_options.update(node_options)
+        e_options = self.default_edge_options.copy()
+        e_options.update(edge_options)
+
+        fd.write('digraph %s {\n' % name)
+        fd.write(self._format_graph_options(g_options))
+
+        for cls in self.all_classes:
+            if not self.show_builtins and cls in __builtins__.values():
+                continue
+
+            name = self.class_name(cls, parts)
+
+            # Write the node
+            this_node_options = n_options.copy()
+            url = urls.get(self.class_name(cls))
+            if url is not None:
+                this_node_options['URL'] = '"%s"' % url
+            fd.write('  "%s" [%s];\n' %
+                     (name, self._format_node_options(this_node_options)))
+
+            # Write the edges
+            for base in cls.__bases__:
+                if not self.show_builtins and base in __builtins__.values():
+                    continue
+
+                base_name = self.class_name(base, parts)
+                fd.write('  "%s" -> "%s" [%s];\n' %
+                         (base_name, name,
+                          self._format_node_options(e_options)))
+        fd.write('}\n')
+
+    def run_dot(self, args, name, parts=0, urls={},
+                graph_options={}, node_options={}, edge_options={}):
+        """
+        Run graphviz 'dot' over this graph, returning whatever 'dot'
+        writes to stdout.
+
+        *args* will be passed along as commandline arguments.
+
+        *name* is the name of the graph
+
+        *urls* is a dictionary mapping class names to http urls
+
+        Raises DotException for any of the many os and
+        installation-related errors that may occur.
+        """
+        try:
+            dot = subprocess.Popen(['dot'] + list(args),
+                                   stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                                   close_fds=True)
+        except OSError:
+            raise DotException("Could not execute 'dot'.  Are you sure you have 'graphviz' installed?")
+        except ValueError:
+            raise DotException("'dot' called with invalid arguments")
+        except:
+            raise DotException("Unexpected error calling 'dot'")
+
+        self.generate_dot(dot.stdin, name, parts, urls, graph_options,
+                          node_options, edge_options)
+        dot.stdin.close()
+        result = dot.stdout.read()
+        returncode = dot.wait()
+        if returncode != 0:
+            raise DotException("'dot' returned the errorcode %d" % returncode)
+        return result
+
+class inheritance_diagram(Body, Element):
+    """
+    A docutils node to use as a placeholder for the inheritance
+    diagram.
+    """
+    pass
+
+def inheritance_diagram_directive(name, arguments, options, content, lineno,
+                                  content_offset, block_text, state,
+                                  state_machine):
+    """
+    Run when the inheritance_diagram directive is first encountered.
+    """
+    node = inheritance_diagram()
+
+    class_names = arguments
+
+    # Create a graph starting with the list of classes
+    graph = InheritanceGraph(class_names)
+
+    # Create xref nodes for each target of the graph's image map and
+    # add them to the doc tree so that Sphinx can resolve the
+    # references to real URLs later.  These nodes will eventually be
+    # removed from the doctree after we're done with them.
+    for name in graph.get_all_class_names():
+        refnodes, x = xfileref_role(
+            'class', ':class:`%s`' % name, name, 0, state)
+        node.extend(refnodes)
+    # Store the graph object so we can use it to generate the
+    # dot file later
+    node['graph'] = graph
+    # Store the original content for use as a hash
+    node['parts'] = options.get('parts', 0)
+    node['content'] = " ".join(class_names)
+    return [node]
+
+def get_graph_hash(node):
+    return md5(node['content'] + str(node['parts'])).hexdigest()[-10:]
+
+def html_output_graph(self, node):
+    """
+    Output the graph for HTML.  This will insert a PNG with clickable
+    image map.
+    """
+    graph = node['graph']
+    parts = node['parts']
+
+    graph_hash = get_graph_hash(node)
+    name = "inheritance%s" % graph_hash
+    path = '_images'
+    dest_path = os.path.join(setup.app.builder.outdir, path)
+    if not os.path.exists(dest_path):
+        os.makedirs(dest_path)
+    png_path = os.path.join(dest_path, name + ".png")
+    path = setup.app.builder.imgpath
+
+    # Create a mapping from fully-qualified class names to URLs.
+    urls = {}
+    for child in node:
+        if child.get('refuri') is not None:
+            urls[child['reftitle']] = child.get('refuri')
+        elif child.get('refid') is not None:
+            urls[child['reftitle']] = '#' + child.get('refid')
+
+    # These arguments to dot will save a PNG file to disk and write
+    # an HTML image map to stdout.
+    image_map = graph.run_dot(['-Tpng', '-o%s' % png_path, '-Tcmapx'],
+                              name, parts, urls)
+    return ('<img src="%s/%s.png" usemap="#%s" class="inheritance"/>%s' %
+            (path, name, name, image_map))
+
+def latex_output_graph(self, node):
+    """
+    Output the graph for LaTeX.  This will insert a PDF.
+    """
+    graph = node['graph']
+    parts = node['parts']
+
+    graph_hash = get_graph_hash(node)
+    name = "inheritance%s" % graph_hash
+    dest_path = os.path.abspath(os.path.join(setup.app.builder.outdir, '_images'))
+    if not os.path.exists(dest_path):
+        os.makedirs(dest_path)
+    pdf_path = os.path.abspath(os.path.join(dest_path, name + ".pdf"))
+
+    graph.run_dot(['-Tpdf', '-o%s' % pdf_path],
+                  name, parts, graph_options={'size': '"6.0,6.0"'})
+    return '\n\\includegraphics{%s}\n\n' % pdf_path
+
+def visit_inheritance_diagram(inner_func):
+    """
+    This is just a wrapper around html/latex_output_graph to make it
+    easier to handle errors and insert warnings.
+    """
+    def visitor(self, node):
+        try:
+            content = inner_func(self, node)
+        except DotException, e:
+            # Insert the exception as a warning in the document
+            warning = self.document.reporter.warning(str(e), line=node.line)
+            warning.parent = node
+            node.children = [warning]
+        else:
+            source = self.document.attributes['source']
+            self.body.append(content)
+            node.children = []
+    return visitor
+
+def do_nothing(self, node):
+    pass
+
+def setup(app):
+    setup.app = app
+    setup.confdir = app.confdir
+
+    app.add_node(
+        inheritance_diagram,
+        latex=(visit_inheritance_diagram(latex_output_graph), do_nothing),
+        html=(visit_inheritance_diagram(html_output_graph), do_nothing))
+    app.add_directive(
+        'inheritance-diagram', inheritance_diagram_directive,
+        False, (1, 100, 0), parts = directives.nonnegative_int)
diff --git a/doc/sphinxext/math_symbol_table.py b/doc/sphinxext/math_symbol_table.py
new file mode 100644
index 0000000..6a11ec0
--- /dev/null
+++ b/doc/sphinxext/math_symbol_table.py
@@ -0,0 +1,159 @@
+symbols = [
+    ["Lower-case Greek",
+     5,
+     r"""\alpha \beta \gamma \chi \delta \epsilon \eta \iota \kappa
+         \lambda \mu \nu \omega \phi \pi \psi \rho \sigma \tau \theta
+         \upsilon \xi \zeta \digamma \varepsilon \varkappa \varphi
+         \varpi \varrho \varsigma \vartheta"""],
+    ["Upper-case Greek",
+     6,
+     r"""\Delta \Gamma \Lambda \Omega \Phi \Pi \Psi \Sigma \Theta
+     \Upsilon \Xi \mho \nabla"""],
+    ["Hebrew",
+     4,
+     r"""\aleph \beth \daleth \gimel"""],
+    ["Delimiters",
+     6,
+     r"""| \{ \lfloor / \Uparrow \llcorner \vert \} \rfloor \backslash
+         \uparrow \lrcorner \| \langle \lceil [ \Downarrow \ulcorner
+         \Vert \rangle \rceil ] \downarrow \urcorner"""],
+    ["Big symbols",
+     5,
+     r"""\bigcap \bigcup \bigodot \bigoplus \bigotimes \biguplus
+         \bigvee \bigwedge \coprod \oint \prod \sum \int"""],
+    ["Standard function names",
+     4,
+     r"""\arccos \csc \ker \min \arcsin \deg \lg \Pr \arctan \det \lim
+         \gcd \ln \sup \cot \hom \log \tan \coth \inf \max \tanh
+         \sec \arg \dim \liminf \sin \cos \exp \limsup \sinh \cosh"""],
+    ["Binary operation and relation symbols",
+     3,
+     r"""\ast \pm \slash \cap \star \mp \cup \cdot \uplus
+     \triangleleft \circ \odot \sqcap \triangleright \bullet \ominus
+     \sqcup \bigcirc \oplus \wedge \diamond \oslash \vee
+     \bigtriangledown \times \otimes \dag \bigtriangleup \div \wr
+     \ddag \barwedge \veebar \boxplus \curlywedge \curlyvee \boxminus
+     \Cap \Cup \boxtimes \bot \top \dotplus \boxdot \intercal
+     \rightthreetimes \divideontimes \leftthreetimes \equiv \leq \geq
+     \perp \cong \prec \succ \mid \neq \preceq \succeq \parallel \sim
+     \ll \gg \bowtie \simeq \subset \supset \Join \approx \subseteq
+     \supseteq \ltimes \asymp \sqsubset \sqsupset \rtimes \doteq
+     \sqsubseteq \sqsupseteq \smile \propto \dashv \vdash \frown
+     \models \in \ni \notin \approxeq \leqq \geqq \lessgtr \leqslant
+     \geqslant \lesseqgtr \backsim \lessapprox \gtrapprox \lesseqqgtr
+     \backsimeq \lll \ggg \gtreqqless \triangleq \lessdot \gtrdot
+     \gtreqless \circeq \lesssim \gtrsim \gtrless \bumpeq \eqslantless
+     \eqslantgtr \backepsilon \Bumpeq \precsim \succsim \between
+     \doteqdot \precapprox \succapprox \pitchfork \Subset \Supset
+     \fallingdotseq \subseteqq \supseteqq \risingdotseq \sqsubset
+     \sqsupset \varpropto \preccurlyeq \succcurlyeq \Vdash \therefore
+     \curlyeqprec \curlyeqsucc \vDash \because \blacktriangleleft
+     \blacktriangleright \Vvdash \eqcirc \trianglelefteq
+     \trianglerighteq \neq \vartriangleleft \vartriangleright \ncong
+     \nleq \ngeq \nsubseteq \nmid \nsupseteq \nparallel \nless \ngtr
+     \nprec \nsucc \subsetneq \nsim \supsetneq \nVDash \precnapprox
+     \succnapprox \subsetneqq \nvDash \precnsim \succnsim \supsetneqq
+     \nvdash \lnapprox \gnapprox \ntriangleleft \ntrianglelefteq
+     \lneqq \gneqq \ntriangleright \lnsim \gnsim \ntrianglerighteq
+     \coloneq \eqsim \nequiv \napprox \nsupset \doublebarwedge \nVdash
+     \Doteq \nsubset \eqcolon \ne
+     """],
+    ["Arrow symbols",
+     2,
+     r"""\leftarrow \longleftarrow \uparrow \Leftarrow \Longleftarrow
+     \Uparrow \rightarrow \longrightarrow \downarrow \Rightarrow
+     \Longrightarrow \Downarrow \leftrightarrow \updownarrow
+     \longleftrightarrow \updownarrow \Leftrightarrow
+     \Longleftrightarrow \Updownarrow \mapsto \longmapsto \nearrow
+     \hookleftarrow \hookrightarrow \searrow \leftharpoonup
+     \rightharpoonup \swarrow \leftharpoondown \rightharpoondown
+     \nwarrow \rightleftharpoons \leadsto \dashrightarrow
+     \dashleftarrow \leftleftarrows \leftrightarrows \Lleftarrow
+     \Rrightarrow \twoheadleftarrow \leftarrowtail \looparrowleft
+     \leftrightharpoons \curvearrowleft \circlearrowleft \Lsh
+     \upuparrows \upharpoonleft \downharpoonleft \multimap
+     \leftrightsquigarrow \rightrightarrows \rightleftarrows
+     \rightrightarrows \rightleftarrows \twoheadrightarrow
+     \rightarrowtail \looparrowright \rightleftharpoons
+     \curvearrowright \circlearrowright \Rsh \downdownarrows
+     \upharpoonright \downharpoonright \rightsquigarrow \nleftarrow
+     \nrightarrow \nLeftarrow \nRightarrow \nleftrightarrow
+     \nLeftrightarrow \to \Swarrow \Searrow \Nwarrow \Nearrow
+     \leftsquigarrow
+     """],
+    ["Miscellaneous symbols",
+     3,
+     r"""\neg \infty \forall \wp \exists \bigstar \angle \partial
+     \nexists \measuredangle \eth \emptyset \sphericalangle \clubsuit
+     \varnothing \complement \diamondsuit \imath \Finv \triangledown
+     \heartsuit \jmath \Game \spadesuit \ell \hbar \vartriangle \cdots
+     \hslash \vdots \blacksquare \ldots \blacktriangle \ddots \sharp
+     \prime \blacktriangledown \Im \flat \backprime \Re \natural
+     \circledS \P \copyright \ss \circledR \S \yen \AA \checkmark \$
+     \iiint \iint \iint \oiiint"""]
+]
+
+def run(state_machine):
+    def get_n(n, l):
+        part = []
+        for x in l:
+            part.append(x)
+            if len(part) == n:
+                yield part
+                part = []
+        yield part
+
+    lines = []
+    for category, columns, syms in symbols:
+        syms = syms.split()
+        syms.sort()
+        lines.append("**%s**" % category)
+        lines.append('')
+        max_width = 0
+        for sym in syms:
+            max_width = max(max_width, len(sym))
+        max_width = max_width * 2 + 16
+        header = "    " + (('=' * max_width) + ' ') * columns
+        format = '%%%ds' % max_width
+        for chunk in get_n(20, get_n(columns, syms)):
+            lines.append(header)
+            for part in chunk:
+                line = []
+                for sym in part:
+                    line.append(format % (":math:`%s` ``%s``" % (sym, sym)))
+                lines.append("    " + " ".join(line))
+            lines.append(header)
+            lines.append('')
+
+    state_machine.insert_input(lines, "Symbol table")
+    return []
+
+def math_symbol_table_directive(name, arguments, options, content, lineno,
+                                content_offset, block_text, state, state_machine):
+    return run(state_machine)
+
+def setup(app):
+    app.add_directive(
+        'math_symbol_table', math_symbol_table_directive,
+        False, (0, 1, 0))
+
+if __name__ == "__main__":
+    # Do some verification of the tables
+    from matplotlib import _mathtext_data
+
+    print "SYMBOLS NOT IN STIX:"
+    all_symbols = {}
+    for category, columns, syms in symbols:
+        if category == "Standard Function Names":
+            continue
+        syms = syms.split()
+        for sym in syms:
+            if len(sym) > 1:
+                all_symbols[sym[1:]] = None
+                if sym[1:] not in _mathtext_data.tex2uni:
+                    print sym
+
+    print "SYMBOLS NOT IN TABLE:"
+    for sym in _mathtext_data.tex2uni:
+        if sym not in all_symbols:
+            print sym
diff --git a/doc/sphinxext/mathml.py b/doc/sphinxext/mathml.py
new file mode 100644
index 0000000..098abd4
--- /dev/null
+++ b/doc/sphinxext/mathml.py
@@ -0,0 +1,552 @@
+from docutils import nodes
+from docutils.writers.html4css1 import HTMLTranslator
+from sphinx.latexwriter import LaTeXTranslator
+
+# Define LaTeX math node:
+class latex_math(nodes.General, nodes.Element):
+    pass
+
+def math_role(role, rawtext, text, lineno, inliner,
+              options={}, content=[]):
+    i = rawtext.find('`')
+    latex = rawtext[i+1:-1]
+    try:
+        mathml_tree = parse_latex_math(latex, inline=True)
+    except SyntaxError, msg:
+        msg = inliner.reporter.error(msg, line=lineno)
+        prb = inliner.problematic(rawtext, rawtext, msg)
+        return [prb], [msg]
+    node = latex_math(rawtext)
+    node['latex'] = latex
+    node['mathml_tree'] = mathml_tree
+    return [node], []
+
+
+try:
+    from docutils.parsers.rst import Directive
+except ImportError:
+    # Register directive the old way:
+    from docutils.parsers.rst.directives import _directives
+    def math_directive(name, arguments, options, content, lineno,
+                       content_offset, block_text, state, state_machine):
+        latex = ''.join(content)
+        try:
+            mathml_tree = parse_latex_math(latex, inline=False)
+        except SyntaxError, msg:
+            error = state_machine.reporter.error(
+                msg, nodes.literal_block(block_text, block_text), line=lineno)
+            return [error]
+        node = latex_math(block_text)
+        node['latex'] = latex
+        node['mathml_tree'] = mathml_tree
+        return [node]
+    math_directive.arguments = None
+    math_directive.options = {}
+    math_directive.content = 1
+    _directives['math'] = math_directive
+else:
+    class math_directive(Directive):
+        has_content = True
+        def run(self):
+            latex = ' '.join(self.content)
+            try:
+                mathml_tree = parse_latex_math(latex, inline=False)
+            except SyntaxError, msg:
+                error = self.state_machine.reporter.error(
+                    msg, nodes.literal_block(self.block_text, self.block_text),
+                    line=self.lineno)
+                return [error]
+            node = latex_math(self.block_text)
+            node['latex'] = latex
+            node['mathml_tree'] = mathml_tree
+            return [node]
+    from docutils.parsers.rst import directives
+    directives.register_directive('math', math_directive)
+
+def setup(app):
+    app.add_node(latex_math)
+    app.add_role('math', math_role)
+
+    # Add visit/depart methods to HTML-Translator:
+    def visit_latex_math_html(self, node):
+        mathml = ''.join(node['mathml_tree'].xml())
+        self.body.append(mathml)
+    def depart_latex_math_html(self, node):
+            pass
+    HTMLTranslator.visit_latex_math = visit_latex_math_html
+    HTMLTranslator.depart_latex_math = depart_latex_math_html
+
+    # Add visit/depart methods to LaTeX-Translator:
+    def visit_latex_math_latex(self, node):
+        inline = isinstance(node.parent, nodes.TextElement)
+        if inline:
+            self.body.append('$%s$' % node['latex'])
+        else:
+            self.body.extend(['\\begin{equation}',
+                              node['latex'],
+                              '\\end{equation}'])
+    def depart_latex_math_latex(self, node):
+            pass
+    LaTeXTranslator.visit_latex_math = visit_latex_math_latex
+    LaTeXTranslator.depart_latex_math = depart_latex_math_latex
+
+
+# LaTeX to MathML translation stuff:
+class math:
+    """Base class for MathML elements."""
+
+    nchildren = 1000000
+    """Required number of children"""
+
+    def __init__(self, children=None, inline=None):
+        """math([children]) -> MathML element
+
+        children can be one child or a list of children."""
+
+        self.children = []
+        if children is not None:
+            if type(children) is list:
+                for child in children:
+                    self.append(child)
+            else:
+                # Only one child:
+                self.append(children)
+
+        if inline is not None:
+            self.inline = inline
+
+    def __repr__(self):
+        if hasattr(self, 'children'):
+            return self.__class__.__name__ + '(%s)' % \
+                   ','.join([repr(child) for child in self.children])
+        else:
+            return self.__class__.__name__
+
+    def full(self):
+        """Room for more children?"""
+
+        return len(self.children) >= self.nchildren
+
+    def append(self, child):
+        """append(child) -> element
+
+        Appends child and returns self if self is not full or first
+        non-full parent."""
+
+        assert not self.full()
+        self.children.append(child)
+        child.parent = self
+        node = self
+        while node.full():
+            node = node.parent
+        return node
+
+    def delete_child(self):
+        """delete_child() -> child
+
+        Delete last child and return it."""
+
+        child = self.children[-1]
+        del self.children[-1]
+        return child
+
+    def close(self):
+        """close() -> parent
+
+        Close element and return first non-full element."""
+
+        parent = self.parent
+        while parent.full():
+            parent = parent.parent
+        return parent
+
+    def xml(self):
+        """xml() -> xml-string"""
+
+        return self.xml_start() + self.xml_body() + self.xml_end()
+
+    def xml_start(self):
+        if not hasattr(self, 'inline'):
+            return ['<%s>' % self.__class__.__name__]
+        xmlns = 'http://www.w3.org/1998/Math/MathML'
+        if self.inline:
+            return ['<math xmlns="%s">' % xmlns]
+        else:
+            return ['<math xmlns="%s" mode="display">' % xmlns]
+
+    def xml_end(self):
+        return ['</%s>' % self.__class__.__name__]
+
+    def xml_body(self):
+        xml = []
+        for child in self.children:
+            xml.extend(child.xml())
+        return xml
+
+class mrow(math): pass
+class mtable(math): pass
+class mtr(mrow): pass
+class mtd(mrow): pass
+
+class mx(math):
+    """Base class for mo, mi, and mn"""
+
+    nchildren = 0
+    def __init__(self, data):
+        self.data = data
+
+    def xml_body(self):
+        return [self.data]
+
+class mo(mx):
+    translation = {'<': '&lt;', '>': '&gt;'}
+    def xml_body(self):
+        return [self.translation.get(self.data, self.data)]
+
+class mi(mx): pass
+class mn(mx): pass
+
+class msub(math):
+    nchildren = 2
+
+class msup(math):
+    nchildren = 2
+
+class msqrt(math):
+    nchildren = 1
+
+class mroot(math):
+    nchildren = 2
+
+class mfrac(math):
+    nchildren = 2
+
+class msubsup(math):
+    nchildren = 3
+    def __init__(self, children=None, reversed=False):
+        self.reversed = reversed
+        math.__init__(self, children)
+
+    def xml(self):
+        if self.reversed:
+##            self.children[1:3] = self.children[2:0:-1]
+            self.children[1:3] = [self.children[2], self.children[1]]
+            self.reversed = False
+        return math.xml(self)
+
+class mfenced(math):
+    translation = {'\\{': '{', '\\langle': u'\u2329',
+                   '\\}': '}', '\\rangle': u'\u232A',
+                   '.': ''}
+    def __init__(self, par):
+        self.openpar = par
+        math.__init__(self)
+
+    def xml_start(self):
+        open = self.translation.get(self.openpar, self.openpar)
+        close = self.translation.get(self.closepar, self.closepar)
+        return ['<mfenced open="%s" close="%s">' % (open, close)]
+
+class mspace(math):
+    nchildren = 0
+
+class mstyle(math):
+    def __init__(self, children=None, nchildren=None, **kwargs):
+        if nchildren is not None:
+            self.nchildren = nchildren
+        math.__init__(self, children)
+        self.attrs = kwargs
+
+    def xml_start(self):
+        return ['<mstyle '] + ['%s="%s"' % item
+                               for item in self.attrs.items()] + ['>']
+
+class mover(math):
+    nchildren = 2
+    def __init__(self, children=None, reversed=False):
+        self.reversed = reversed
+        math.__init__(self, children)
+
+    def xml(self):
+        if self.reversed:
+            self.children.reverse()
+            self.reversed = False
+        return math.xml(self)
+
+class munder(math):
+    nchildren = 2
+
+class munderover(math):
+    nchildren = 3
+    def __init__(self, children=None):
+        math.__init__(self, children)
+
+class mtext(math):
+    nchildren = 0
+    def __init__(self, text):
+        self.text = text
+
+    def xml_body(self):
+        return [self.text]
+
+
+over = {'tilde': '~',
+        'hat': '^',
+        'bar': '_',
+        'vec': u'\u2192'}
+
+Greek = {
+    # Upper case greek letters:
+    'Phi': u'\u03a6', 'Xi': u'\u039e', 'Sigma': u'\u03a3', 'Psi': u'\u03a8', 'Delta': u'\u0394', 'Theta': u'\u0398', 'Upsilon': u'\u03d2', 'Pi': u'\u03a0', 'Omega': u'\u03a9', 'Gamma': u'\u0393', 'Lambda': u'\u039b'}
+greek = {
+    # Lower case greek letters:
+    'tau': u'\u03c4', 'phi': u'\u03d5', 'xi': u'\u03be', 'iota': u'\u03b9', 'epsilon': u'\u03f5', 'varrho': u'\u03f1', 'varsigma': u'\u03c2', 'beta': u'\u03b2', 'psi': u'\u03c8', 'rho': u'\u03c1', 'delta': u'\u03b4', 'alpha': u'\u03b1', 'zeta': u'\u03b6', 'omega': u'\u03c9', 'varepsilon': u'\u03b5', 'kappa': u'\u03ba', 'vartheta': u'\u03d1', 'chi': u'\u03c7', 'upsilon': u'\u03c5', 'sigma': u'\u03c3', 'varphi': u'\u03c6', 'varpi': u'\u03d6', 'mu': u'\u03bc', 'eta': u'\u03b7', 'theta': u'\u03b8', 'pi': u'\u03c0', 'varkappa': u'\u03f0', 'nu': u'\u03bd', 'gamma': u'\u03b3', 'lambda': u'\u03bb'}
+
+special = {
+    # Binary operation symbols:
+    'wedge': u'\u2227', 'diamond': u'\u22c4', 'star': u'\u22c6', 'amalg': u'\u2a3f', 'ast': u'\u2217', 'odot': u'\u2299', 'triangleleft': u'\u25c1', 'bigtriangleup': u'\u25b3', 'ominus': u'\u2296', 'ddagger': u'\u2021', 'wr': u'\u2240', 'otimes': u'\u2297', 'sqcup': u'\u2294', 'oplus': u'\u2295', 'bigcirc': u'\u25cb', 'oslash': u'\u2298', 'sqcap': u'\u2293', 'bullet': u'\u2219', 'cup': u'\u222a', 'cdot': u'\u22c5', 'cap': u'\u2229', 'bigtriangledown': u'\u25bd', 'times': u'\xd7', 'setminus': u'\u2216', 'circ': u'\u2218', 'vee': u'\u2228', 'uplus': u'\u228e', 'mp': u'\u2213', 'dagger': u'\u2020', 'triangleright': u'\u25b7', 'div': u'\xf7', 'pm': u'\xb1',
+    # Relation symbols:
+    'subset': u'\u2282', 'propto': u'\u221d', 'geq': u'\u2265', 'ge': u'\u2265', 'sqsubset': u'\u228f', 'Join': u'\u2a1d', 'frown': u'\u2322', 'models': u'\u22a7', 'supset': u'\u2283', 'in': u'\u2208', 'doteq': u'\u2250', 'dashv': u'\u22a3', 'gg': u'\u226b', 'leq': u'\u2264', 'succ': u'\u227b', 'vdash': u'\u22a2', 'cong': u'\u2245', 'simeq': u'\u2243', 'subseteq': u'\u2286', 'parallel': u'\u2225', 'equiv': u'\u2261', 'ni': u'\u220b', 'le': u'\u2264', 'approx': u'\u2248', 'precsim': u'\u227e', 'sqsupset': u'\u2290', 'll': u'\u226a', 'sqsupseteq': u'\u2292', 'mid': u'\u2223', 'prec': u'\u227a', 'succsim': u'\u227f', 'bowtie': u'\u22c8', 'perp': u'\u27c2', 'sqsubseteq': u'\u2291', 'asymp': u'\u224d', 'smile': u'\u2323', 'supseteq': u'\u2287', 'sim': u'\u223c', 'neq': u'\u2260',
+    # Arrow symbols:
+    'searrow': u'\u2198', 'updownarrow': u'\u2195', 'Uparrow': u'\u21d1', 'longleftrightarrow': u'\u27f7', 'Leftarrow': u'\u21d0', 'longmapsto': u'\u27fc', 'Longleftarrow': u'\u27f8', 'nearrow': u'\u2197', 'hookleftarrow': u'\u21a9', 'downarrow': u'\u2193', 'Leftrightarrow': u'\u21d4', 'longrightarrow': u'\u27f6', 'rightharpoondown': u'\u21c1', 'longleftarrow': u'\u27f5', 'rightarrow': u'\u2192', 'Updownarrow': u'\u21d5', 'rightharpoonup': u'\u21c0', 'Longleftrightarrow': u'\u27fa', 'leftarrow': u'\u2190', 'mapsto': u'\u21a6', 'nwarrow': u'\u2196', 'uparrow': u'\u2191', 'leftharpoonup': u'\u21bc', 'leftharpoondown': u'\u21bd', 'Downarrow': u'\u21d3', 'leftrightarrow': u'\u2194', 'Longrightarrow': u'\u27f9', 'swarrow': u'\u2199', 'hookrightarrow': u'\u21aa', 'Rightarrow': u'\u21d2',
+    # Miscellaneous symbols:
+    'infty': u'\u221e', 'surd': u'\u221a', 'partial': u'\u2202', 'ddots': u'\u22f1', 'exists': u'\u2203', 'flat': u'\u266d', 'diamondsuit': u'\u2662', 'wp': u'\u2118', 'spadesuit': u'\u2660', 'Re': u'\u211c', 'vdots': u'\u22ee', 'aleph': u'\u2135', 'clubsuit': u'\u2663', 'sharp': u'\u266f', 'angle': u'\u2220', 'prime': u'\u2032', 'natural': u'\u266e', 'ell': u'\u2113', 'neg': u'\xac', 'top': u'\u22a4', 'nabla': u'\u2207', 'bot': u'\u22a5', 'heartsuit': u'\u2661', 'cdots': u'\u22ef', 'Im': u'\u2111', 'forall': u'\u2200', 'imath': u'\u0131', 'hbar': u'\u210f', 'emptyset': u'\u2205',
+    # Variable-sized symbols:
+    'bigotimes': u'\u2a02', 'coprod': u'\u2210', 'int': u'\u222b', 'sum': u'\u2211', 'bigodot': u'\u2a00', 'bigcup': u'\u22c3', 'biguplus': u'\u2a04', 'bigcap': u'\u22c2', 'bigoplus': u'\u2a01', 'oint': u'\u222e', 'bigvee': u'\u22c1', 'bigwedge': u'\u22c0', 'prod': u'\u220f',
+    # Braces:
+    'langle': u'\u2329', 'rangle': u'\u232A'}
+
+sumintprod = ''.join([special[symbol] for symbol in
+                      ['sum', 'int', 'oint', 'prod']])
+
+functions = ['arccos', 'arcsin', 'arctan', 'arg', 'cos',  'cosh',
+             'cot',    'coth',   'csc',    'deg', 'det',  'dim',
+             'exp',    'gcd',    'hom',    'inf', 'ker',  'lg',
+             'lim',    'liminf', 'limsup', 'ln',  'log',  'max',
+             'min',    'Pr',     'sec',    'sin', 'sinh', 'sup',
+             'tan',    'tanh',
+             'injlim',  'varinjlim', 'varlimsup',
+             'projlim', 'varliminf', 'varprojlim']
+
+
+def parse_latex_math(string, inline=True):
+    """parse_latex_math(string [,inline]) -> MathML-tree
+
+    Returns a MathML-tree parsed from string.  inline=True is for
+    inline math and inline=False is for displayed math.
+
+    tree is the whole tree and node is the current element."""
+
+    # Normalize white-space:
+    string = ' '.join(string.split())
+
+    if inline:
+        node = mrow()
+        tree = math(node, inline=True)
+    else:
+        node = mtd()
+        tree = math(mtable(mtr(node)), inline=False)
+
+    while len(string) > 0:
+        n = len(string)
+        c = string[0]
+        skip = 1  # number of characters consumed
+        if n > 1:
+            c2 = string[1]
+        else:
+            c2 = ''
+##        print n, string, c, c2, node.__class__.__name__
+        if c == ' ':
+            pass
+        elif c == '\\':
+            if c2 in '{}':
+                node = node.append(mo(c2))
+                skip = 2
+            elif c2 == ' ':
+                node = node.append(mspace())
+                skip = 2
+            elif c2.isalpha():
+                # We have a LaTeX-name:
+                i = 2
+                while i < n and string[i].isalpha():
+                    i += 1
+                name = string[1:i]
+                node, skip = handle_keyword(name, node, string[i:])
+                skip += i
+            elif c2 == '\\':
+                # End of a row:
+                entry = mtd()
+                row = mtr(entry)
+                node.close().close().append(row)
+                node = entry
+                skip = 2
+            else:
+                raise SyntaxError('Syntax error: "%s%s"' % (c, c2))
+        elif c.isalpha():
+            node = node.append(mi(c))
+        elif c.isdigit():
+            node = node.append(mn(c))
+        elif c in "+-/()[]|=<>,.!'":
+            node = node.append(mo(c))
+        elif c == '_':
+            child = node.delete_child()
+            if isinstance(child, msup):
+                sub = msubsup(child.children, reversed=True)
+            elif isinstance(child, mo) and child.data in sumintprod:
+                sub = munder(child)
+            else:
+                sub = msub(child)
+            node.append(sub)
+            node = sub
+        elif c == '^':
+            child = node.delete_child()
+            if isinstance(child, msub):
+                sup = msubsup(child.children)
+            elif isinstance(child, mo) and child.data in sumintprod:
+                sup = mover(child)
+            elif (isinstance(child, munder) and
+                  child.children[0].data in sumintprod):
+                sup = munderover(child.children)
+            else:
+                sup = msup(child)
+            node.append(sup)
+            node = sup
+        elif c == '{':
+            row = mrow()
+            node.append(row)
+            node = row
+        elif c == '}':
+            node = node.close()
+        elif c == '&':
+            entry = mtd()
+            node.close().append(entry)
+            node = entry
+        else:
+            raise SyntaxError('Illegal character: "%s"' % c)
+        string = string[skip:]
+    return tree
+
+
+mathbb = {'A': u'\U0001D538',
+          'B': u'\U0001D539',
+          'C': u'\u2102',
+          'D': u'\U0001D53B',
+          'E': u'\U0001D53C',
+          'F': u'\U0001D53D',
+          'G': u'\U0001D53E',
+          'H': u'\u210D',
+          'I': u'\U0001D540',
+          'J': u'\U0001D541',
+          'K': u'\U0001D542',
+          'L': u'\U0001D543',
+          'M': u'\U0001D544',
+          'N': u'\u2115',
+          'O': u'\U0001D546',
+          'P': u'\u2119',
+          'Q': u'\u211A',
+          'R': u'\u211D',
+          'S': u'\U0001D54A',
+          'T': u'\U0001D54B',
+          'U': u'\U0001D54C',
+          'V': u'\U0001D54D',
+          'W': u'\U0001D54E',
+          'X': u'\U0001D54F',
+          'Y': u'\U0001D550',
+          'Z': u'\u2124'}
+
+negatables = {'=': u'\u2260',
+              '\in': u'\u2209',
+              '\equiv': u'\u2262'}
+
+
+def handle_keyword(name, node, string):
+    skip = 0
+    if len(string) > 0 and string[0] == ' ':
+        string = string[1:]
+        skip = 1
+    if name == 'begin':
+        if not string.startswith('{matrix}'):
+            raise SyntaxError('Expected "\begin{matrix}"!')
+        skip += 8
+        entry = mtd()
+        table = mtable(mtr(entry))
+        node.append(table)
+        node = entry
+    elif name == 'end':
+        if not string.startswith('{matrix}'):
+            raise SyntaxError('Expected "\end{matrix}"!')
+        skip += 8
+        node = node.close().close().close()
+    elif name == 'text':
+        if string[0] != '{':
+            raise SyntaxError('Expected "\text{...}"!')
+        i = string.find('}')
+        if i == -1:
+            raise SyntaxError('Expected "\text{...}"!')
+        node = node.append(mtext(string[1:i]))
+        skip += i + 1
+    elif name == 'sqrt':
+        sqrt = msqrt()
+        node.append(sqrt)
+        node = sqrt
+    elif name == 'frac':
+        frac = mfrac()
+        node.append(frac)
+        node = frac
+    elif name == 'left':
+        for par in ['(', '[', '|', '\\{', '\\langle', '.']:
+            if string.startswith(par):
+                break
+        else:
+            raise SyntaxError('Missing left-brace!')
+        fenced = mfenced(par)
+        node.append(fenced)
+        row = mrow()
+        fenced.append(row)
+        node = row
+        skip += len(par)
+    elif name == 'right':
+        for par in [')', ']', '|', '\\}', '\\rangle', '.']:
+            if string.startswith(par):
+                break
+        else:
+            raise SyntaxError('Missing right-brace!')
+        node = node.close()
+        node.closepar = par
+        node = node.close()
+        skip += len(par)
+    elif name == 'not':
+        for operator in negatables:
+            if string.startswith(operator):
+                break
+        else:
+            raise SyntaxError('Expected something to negate: "\\not ..."!')
+        node = node.append(mo(negatables[operator]))
+        skip += len(operator)
+    elif name == 'mathbf':
+        style = mstyle(nchildren=1, fontweight='bold')
+        node.append(style)
+        node = style
+    elif name == 'mathbb':
+        if string[0] != '{' or not string[1].isupper() or string[2] != '}':
+            raise SyntaxError('Expected something like "\mathbb{A}"!')
+        node = node.append(mi(mathbb[string[1]]))
+        skip += 3
+    elif name in greek:
+        node = node.append(mi(greek[name]))
+    elif name in Greek:
+        node = node.append(mo(Greek[name]))
+    elif name in special:
+        node = node.append(mo(special[name]))
+    elif name in functions:
+        node = node.append(mo(name))
+    else:
+        chr = over.get(name)
+        if chr is not None:
+            ovr = mover(mo(chr), reversed=True)
+            node.append(ovr)
+            node = ovr
+        else:
+            raise SyntaxError('Unknown LaTeX command: ' + name)
+
+    return node, skip
