Moved into tools/sgmlconv/.
diff --git a/Doc/tools/sgmlconv/esis2sgml.py b/Doc/tools/sgmlconv/esis2sgml.py
new file mode 100755
index 0000000..5f8c6e8
--- /dev/null
+++ b/Doc/tools/sgmlconv/esis2sgml.py
@@ -0,0 +1,131 @@
+#! /usr/bin/env python
+
+"""Convert ESIS events to SGML or XML markup.
+
+This is limited, but seems sufficient for the ESIS generated by the
+latex2esis.py script when run over the Python documentation.
+"""
+__version__ = '$Revision$'
+
+import errno
+import re
+import string
+
+
+_data_rx = re.compile(r"[^\\][^\\]*")
+
+def decode(s):
+    r = ''
+    while s:
+        m = _data_rx.match(s)
+        if m:
+            r = r + m.group()
+            s = s[len(m.group()):]
+        elif s[1] == "\\":
+            r = r + "\\"
+            s = s[2:]
+        elif s[1] == "n":
+            r = r + "\n"
+            s = s[2:]
+        else:
+            raise ValueError, "can't handle " + `s`
+    return r
+
+
+def format_attrs(attrs):
+    attrs = attrs.items()
+    attrs.sort()
+    s = ''
+    for name, value in attrs:
+        s = '%s %s="%s"' % (s, name, value)
+    return s
+
+
+def do_convert(ifp, ofp, knownempties, xml=0):
+    attrs = {}
+    lastopened = None
+    knownempty = 0
+    lastempty = 0
+    while 1:
+        line = ifp.readline()
+        if not line:
+            break
+
+        type = line[0]
+        data = line[1:]
+        if data and data[-1] == "\n":
+            data = data[:-1]
+        if type == "-":
+            data = decode(data)
+            ofp.write(data)
+            if "\n" in data:
+                lastopened = None
+            knownempty = 0
+            lastempty = 0
+        elif type == "(":
+            if knownempty and xml:
+                ofp.write("<%s%s/>" % (data, format_attrs(attrs)))
+            else:
+                ofp.write("<%s%s>" % (data, format_attrs(attrs)))
+            if knownempty and data not in knownempties:
+                # accumulate knowledge!
+                knownempties.append(data)
+            attrs = {}
+            lastopened = data
+            lastempty = knownempty
+            knownempty = 0
+        elif type == ")":
+            if xml:
+                if not lastempty:
+                    ofp.write("</%s>" % data)
+            elif data not in knownempties:
+                if lastopened == data:
+                    ofp.write("</>")
+                else:
+                    ofp.write("</%s>" % data)
+            lastopened = None
+            lastempty = 0
+        elif type == "A":
+            name, type, value = string.split(data, " ", 2)
+            attrs[name] = decode(value)
+        elif type == "e":
+            knownempty = 1
+
+
+def sgml_convert(ifp, ofp, knownempties=()):
+    return do_convert(ifp, ofp, list(knownempties), xml=0)
+
+
+def xml_convert(ifp, ofp, knownempties=()):
+    return do_convert(ifp, ofp, list(knownempties), xml=1)
+
+
+def main():
+    import sys
+    #
+    convert = sgml_convert
+    if sys.argv[1:] and sys.argv[1] in ("-x", "--xml"):
+        convert = xml_convert
+        del sys.argv[1]
+    if len(sys.argv) == 1:
+        ifp = sys.stdin
+        ofp = sys.stdout
+    elif len(sys.argv) == 2:
+        ifp = open(sys.argv[1])
+        ofp = sys.stdout
+    elif len(sys.argv) == 3:
+        ifp = open(sys.argv[1])
+        ofp = open(sys.argv[2], "w")
+    else:
+        usage()
+        sys.exit(2)
+    # knownempties is ignored in the XML version
+    try:
+        convert(ifp, ofp)
+    except IOError, (err, msg):
+        if err != errno.EPIPE:
+            raise
+
+
+if __name__ == "__main__":
+    main()