cinap_lenrekadd hg and python4bb9e2215 years agoHistory
2078 lines · 68.2 KB · Python
RawDownload
#! /usr/bin/env python # Convert GNU texinfo files into HTML, one file per node.# Based on Texinfo 2.14.# Usage: texi2html [-d] [-d] [-c] inputfile outputdirectory# The input file must be a complete texinfo file, e.g. emacs.texi.# This creates many files (one per info node) in the output directory,# overwriting existing files of the same name.  All files created have# ".html" as their extension.  # XXX To do:# - handle @comment*** correctly# - handle @xref {some words} correctly# - handle @ftable correctly (items aren't indexed?)# - handle @itemx properly# - handle @exdent properly# - add links directly to the proper line from indices# - check against the definitive list of @-cmds; we still miss (among others):# - @defindex (hard)# - @c(omment) in the middle of a line (rarely used)# - @this* (not really needed, only used in headers anyway)# - @today{} (ever used outside title page?) # More consistent handling of chapters/sections/etc.# Lots of documentation# Many more options:#       -top    designate top node#       -links  customize which types of links are included#       -split  split at chapters or sections instead of nodes#       -name   Allow different types of filename handling. Non unix systems#               will have problems with long node names#       ...# Support the most recent texinfo version and take a good look at HTML 3.0# More debugging output (customizable) and more flexible error handling# How about icons ? # rpyron 2002-05-07# Robert Pyron <rpyron@alum.mit.edu># 1. BUGFIX: In function makefile(), strip blanks from the nodename.#    This is necesary to match the behavior of parser.makeref() and#    parser.do_node().# 2. BUGFIX fixed KeyError in end_ifset (well, I may have just made#    it go away, rather than fix it)# 3. BUGFIX allow @menu and menu items inside @ifset or @ifclear# 4. Support added for:#       @uref        URL reference#       @image       image file reference (see note below)#       @multitable  output an HTML table#       @vtable# 5. Partial support for accents, to match MAKEINFO output# 6. I added a new command-line option, '-H basename', to specify#    HTML Help output. This will cause three files to be created#    in the current directory:#       `basename`.hhp  HTML Help Workshop project file#       `basename`.hhc  Contents file for the project#       `basename`.hhk  Index file for the project#    When fed into HTML Help Workshop, the resulting file will be#    named `basename`.chm.# 7. A new class, HTMLHelp, to accomplish item 6.# 8. Various calls to HTMLHelp functions.# A NOTE ON IMAGES: Just as 'outputdirectory' must exist before# running this program, all referenced images must already exist# in outputdirectory. import osimport sysimport stringimport re MAGIC = '\\input texinfo' cmprog = re.compile('^@([a-z]+)([ \t]|$)')        # Command (line-oriented)blprog = re.compile('^[ \t]*$')                   # Blank linekwprog = re.compile('@[a-z]+')                    # Keyword (embedded, usually                                                  # with {} args)spprog = re.compile('[\n@{}&<>]')                 # Special characters in                                                  # running text                                                  #                                                  # menu item (Yuck!)miprog = re.compile('^\* ([^:]*):(:|[ \t]*([^\t,\n.]+)([^ \t\n]*))[ \t\n]*')#                   0    1     1 2        3          34         42        0#                         -----            ----------  ---------#                                 -|-----------------------------#                    -----------------------------------------------------    class HTMLNode:    """Some of the parser's functionality is separated into this class.     A Node accumulates its contents, takes care of links to other Nodes    and saves itself when it is finished and all links are resolved.    """     DOCTYPE = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'     type = 0    cont = ''    epilogue = '</BODY></HTML>\n'     def __init__(self, dir, name, topname, title, next, prev, up):        self.dirname = dir        self.name = name        if topname:            self.topname = topname        else:            self.topname = name        self.title = title        self.next = next        self.prev = prev        self.up = up        self.lines = []     def write(self, *lines):        map(self.lines.append, lines)     def flush(self):        fp = open(self.dirname + '/' + makefile(self.name), 'w')        fp.write(self.prologue)        fp.write(self.text)        fp.write(self.epilogue)        fp.close()     def link(self, label, nodename, rel=None, rev=None):        if nodename:            if nodename.lower() == '(dir)':                addr = '../dir.html'                title = ''            else:                addr = makefile(nodename)                title = ' TITLE="%s"' % nodename            self.write(label, ': <A HREF="', addr, '"', \                       rel and (' REL=' + rel) or "", \                       rev and (' REV=' + rev) or "", \                       title, '>', nodename, '</A>  \n')     def finalize(self):        length = len(self.lines)        self.text = ''.join(self.lines)        self.lines = []        self.open_links()        self.output_links()        self.close_links()        links = ''.join(self.lines)        self.lines = []        self.prologue = (            self.DOCTYPE +            '\n<HTML><HEAD>\n'            '  <!-- Converted with texi2html and Python -->\n'            '  <TITLE>' + self.title + '</TITLE>\n'            '  <LINK REL=Next HREF="'                + makefile(self.next) + '" TITLE="' + self.next + '">\n'            '  <LINK REL=Previous HREF="'                + makefile(self.prev) + '" TITLE="' + self.prev  + '">\n'            '  <LINK REL=Up HREF="'                + makefile(self.up) + '" TITLE="' + self.up  + '">\n'            '</HEAD><BODY>\n' +            links)        if length > 20:            self.epilogue = '<P>\n%s</BODY></HTML>\n' % links     def open_links(self):        self.write('<HR>\n')     def close_links(self):        self.write('<HR>\n')     def output_links(self):        if self.cont != self.next:            self.link('  Cont', self.cont)        self.link('  Next', self.next, rel='Next')        self.link('  Prev', self.prev, rel='Previous')        self.link('  Up', self.up, rel='Up')        if self.name <> self.topname:            self.link('  Top', self.topname)  class HTML3Node(HTMLNode):     DOCTYPE = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML Level 3//EN//3.0">'     def open_links(self):        self.write('<DIV CLASS=Navigation>\n <HR>\n')     def close_links(self):        self.write(' <HR>\n</DIV>\n')  class TexinfoParser:     COPYRIGHT_SYMBOL = "&copy;"    FN_ID_PATTERN = "(%(id)s)"    FN_SOURCE_PATTERN = '<A NAME=footnoteref%(id)s' \                        ' HREF="#footnotetext%(id)s">' \                        + FN_ID_PATTERN + '</A>'    FN_TARGET_PATTERN = '<A NAME=footnotetext%(id)s' \                        ' HREF="#footnoteref%(id)s">' \                        + FN_ID_PATTERN + '</A>\n%(text)s<P>\n'    FN_HEADER = '\n<P>\n<HR NOSHADE SIZE=1 WIDTH=200>\n' \                '<STRONG><EM>Footnotes</EM></STRONG>\n<P>'      Node = HTMLNode     # Initialize an instance    def __init__(self):        self.unknown = {}       # statistics about unknown @-commands        self.filenames = {}     # Check for identical filenames        self.debugging = 0      # larger values produce more output        self.print_headers = 0  # always print headers?        self.nodefp = None      # open file we're writing to        self.nodelineno = 0     # Linenumber relative to node        self.links = None       # Links from current node        self.savetext = None    # If not None, save text head instead        self.savestack = []     # If not None, save text head instead        self.htmlhelp = None    # html help data        self.dirname = 'tmp'    # directory where files are created        self.includedir = '.'   # directory to search @include files        self.nodename = ''      # name of current node        self.topname = ''       # name of top node (first node seen)        self.title = ''         # title of this whole Texinfo tree        self.resetindex()       # Reset all indices        self.contents = []      # Reset table of contents        self.numbering = []     # Reset section numbering counters        self.nofill = 0         # Normal operation: fill paragraphs        self.values={'html': 1} # Names that should be parsed in ifset        self.stackinfo={}       # Keep track of state in the stack        # XXX The following should be reset per node?!        self.footnotes = []     # Reset list of footnotes        self.itemarg = None     # Reset command used by @item        self.itemnumber = None  # Reset number for @item in @enumerate        self.itemindex = None   # Reset item index name        self.node = None        self.nodestack = []        self.cont = 0        self.includedepth = 0     # Set htmlhelp helper class    def sethtmlhelp(self, htmlhelp):        self.htmlhelp = htmlhelp     # Set (output) directory name    def setdirname(self, dirname):        self.dirname = dirname     # Set include directory name    def setincludedir(self, includedir):        self.includedir = includedir     # Parse the contents of an entire file    def parse(self, fp):        line = fp.readline()        lineno = 1        while line and (line[0] == '%' or blprog.match(line)):            line = fp.readline()            lineno = lineno + 1        if line[:len(MAGIC)] <> MAGIC:            raise SyntaxError, 'file does not begin with %r' % (MAGIC,)        self.parserest(fp, lineno)     # Parse the contents of a file, not expecting a MAGIC header    def parserest(self, fp, initial_lineno):        lineno = initial_lineno        self.done = 0        self.skip = 0        self.stack = []        accu = []        while not self.done:            line = fp.readline()            self.nodelineno = self.nodelineno + 1            if not line:                if accu:                    if not self.skip: self.process(accu)                    accu = []                if initial_lineno > 0:                    print '*** EOF before @bye'                break            lineno = lineno + 1            mo = cmprog.match(line)            if mo:                a, b = mo.span(1)                cmd = line[a:b]                if cmd in ('noindent', 'refill'):                    accu.append(line)                else:                    if accu:                        if not self.skip:                            self.process(accu)                        accu = []                    self.command(line, mo)            elif blprog.match(line) and \                 'format' not in self.stack and \                 'example' not in self.stack:                if accu:                    if not self.skip:                        self.process(accu)                        if self.nofill:                            self.write('\n')                        else:                            self.write('<P>\n')                        accu = []            else:                # Append the line including trailing \n!                accu.append(line)        #        if self.skip:            print '*** Still skipping at the end'        if self.stack:            print '*** Stack not empty at the end'            print '***', self.stack        if self.includedepth == 0:            while self.nodestack:                self.nodestack[-1].finalize()                self.nodestack[-1].flush()                del self.nodestack[-1]     # Start saving text in a buffer instead of writing it to a file    def startsaving(self):        if self.savetext <> None:            self.savestack.append(self.savetext)            # print '*** Recursively saving text, expect trouble'        self.savetext = ''     # Return the text saved so far and start writing to file again    def collectsavings(self):        savetext = self.savetext        if len(self.savestack) > 0:            self.savetext = self.savestack[-1]            del self.savestack[-1]        else:            self.savetext = None        return savetext or ''     # Write text to file, or save it in a buffer, or ignore it    def write(self, *args):        try:            text = ''.join(args)        except:            print args            raise TypeError        if self.savetext <> None:            self.savetext = self.savetext + text        elif self.nodefp:            self.nodefp.write(text)        elif self.node:            self.node.write(text)     # Complete the current node -- write footnotes and close file    def endnode(self):        if self.savetext <> None:            print '*** Still saving text at end of node'            dummy = self.collectsavings()        if self.footnotes:            self.writefootnotes()        if self.nodefp:            if self.nodelineno > 20:                self.write('<HR>\n')                [name, next, prev, up] = self.nodelinks[:4]                self.link('Next', next)                self.link('Prev', prev)                self.link('Up', up)                if self.nodename <> self.topname:                    self.link('Top', self.topname)                self.write('<HR>\n')            self.write('</BODY>\n')            self.nodefp.close()            self.nodefp = None        elif self.node:            if not self.cont and \               (not self.node.type or \                (self.node.next and self.node.prev and self.node.up)):                self.node.finalize()                self.node.flush()            else:                self.nodestack.append(self.node)            self.node = None        self.nodename = ''     # Process a list of lines, expanding embedded @-commands    # This mostly distinguishes between menus and normal text    def process(self, accu):        if self.debugging > 1:            print '!'*self.debugging, 'process:', self.skip, self.stack,            if accu: print accu[0][:30],            if accu[0][30:] or accu[1:]: print '...',            print        if self.inmenu():            # XXX should be done differently            for line in accu:                mo = miprog.match(line)                if not mo:                    line = line.strip() + '\n'                    self.expand(line)                    continue                bgn, end = mo.span(0)                a, b = mo.span(1)                c, d = mo.span(2)                e, f = mo.span(3)                g, h = mo.span(4)                label = line[a:b]                nodename = line[c:d]                if nodename[0] == ':': nodename = label                else: nodename = line[e:f]                punct = line[g:h]                self.write('  <LI><A HREF="',                           makefile(nodename),                           '">', nodename,                           '</A>', punct, '\n')                self.htmlhelp.menuitem(nodename)                self.expand(line[end:])        else:            text = ''.join(accu)            self.expand(text)     # find 'menu' (we might be inside 'ifset' or 'ifclear')    def inmenu(self):        #if 'menu' in self.stack:        #    print 'inmenu   :', self.skip, self.stack, self.stackinfo        stack = self.stack        while stack and stack[-1] in ('ifset','ifclear'):            try:                if self.stackinfo[len(stack)]:                    return 0            except KeyError:                pass            stack = stack[:-1]        return (stack and stack[-1] == 'menu')     # Write a string, expanding embedded @-commands    def expand(self, text):        stack = []        i = 0        n = len(text)        while i < n:            start = i            mo = spprog.search(text, i)            if mo:                i = mo.start()            else:                self.write(text[start:])                break            self.write(text[start:i])            c = text[i]            i = i+1            if c == '\n':                self.write('\n')                continue            if c == '<':                self.write('&lt;')                continue            if c == '>':                self.write('&gt;')                continue            if c == '&':                self.write('&amp;')                continue            if c == '{':                stack.append('')                continue            if c == '}':                if not stack:                    print '*** Unmatched }'                    self.write('}')                    continue                cmd = stack[-1]                del stack[-1]                try:                    method = getattr(self, 'close_' + cmd)                except AttributeError:                    self.unknown_close(cmd)                    continue                method()                continue            if c <> '@':                # Cannot happen unless spprog is changed                raise RuntimeError, 'unexpected funny %r' % c            start = i            while i < n and text[i] in string.ascii_letters: i = i+1            if i == start:                # @ plus non-letter: literal next character                i = i+1                c = text[start:i]                if c == ':':                    # `@:' means no extra space after                    # preceding `.', `?', `!' or `:'                    pass                else:                    # `@.' means a sentence-ending period;                    # `@@', `@{', `@}' quote `@', `{', `}'                    self.write(c)                continue            cmd = text[start:i]            if i < n and text[i] == '{':                i = i+1                stack.append(cmd)                try:                    method = getattr(self, 'open_' + cmd)                except AttributeError:

Lines 1–500 of 2078.