Home | History | Annotate | Download | only in scripts
      1 #! /usr/bin/env python
      2 
      3 # Convert GNU texinfo files into HTML, one file per node.
      4 # Based on Texinfo 2.14.
      5 # Usage: texi2html [-d] [-d] [-c] inputfile outputdirectory
      6 # The input file must be a complete texinfo file, e.g. emacs.texi.
      7 # This creates many files (one per info node) in the output directory,
      8 # overwriting existing files of the same name.  All files created have
      9 # ".html" as their extension.
     10 
     11 
     12 # XXX To do:
     13 # - handle @comment*** correctly
     14 # - handle @xref {some words} correctly
     15 # - handle @ftable correctly (items aren't indexed?)
     16 # - handle @itemx properly
     17 # - handle @exdent properly
     18 # - add links directly to the proper line from indices
     19 # - check against the definitive list of @-cmds; we still miss (among others):
     20 # - @defindex (hard)
     21 # - @c(omment) in the middle of a line (rarely used)
     22 # - @this* (not really needed, only used in headers anyway)
     23 # - @today{} (ever used outside title page?)
     24 
     25 # More consistent handling of chapters/sections/etc.
     26 # Lots of documentation
     27 # Many more options:
     28 #       -top    designate top node
     29 #       -links  customize which types of links are included
     30 #       -split  split at chapters or sections instead of nodes
     31 #       -name   Allow different types of filename handling. Non unix systems
     32 #               will have problems with long node names
     33 #       ...
     34 # Support the most recent texinfo version and take a good look at HTML 3.0
     35 # More debugging output (customizable) and more flexible error handling
     36 # How about icons ?
     37 
     38 # rpyron 2002-05-07
     39 # Robert Pyron <rpyron (at] alum.mit.edu>
     40 # 1. BUGFIX: In function makefile(), strip blanks from the nodename.
     41 #    This is necessary to match the behavior of parser.makeref() and
     42 #    parser.do_node().
     43 # 2. BUGFIX fixed KeyError in end_ifset (well, I may have just made
     44 #    it go away, rather than fix it)
     45 # 3. BUGFIX allow @menu and menu items inside @ifset or @ifclear
     46 # 4. Support added for:
     47 #       @uref        URL reference
     48 #       @image       image file reference (see note below)
     49 #       @multitable  output an HTML table
     50 #       @vtable
     51 # 5. Partial support for accents, to match MAKEINFO output
     52 # 6. I added a new command-line option, '-H basename', to specify
     53 #    HTML Help output. This will cause three files to be created
     54 #    in the current directory:
     55 #       `basename`.hhp  HTML Help Workshop project file
     56 #       `basename`.hhc  Contents file for the project
     57 #       `basename`.hhk  Index file for the project
     58 #    When fed into HTML Help Workshop, the resulting file will be
     59 #    named `basename`.chm.
     60 # 7. A new class, HTMLHelp, to accomplish item 6.
     61 # 8. Various calls to HTMLHelp functions.
     62 # A NOTE ON IMAGES: Just as 'outputdirectory' must exist before
     63 # running this program, all referenced images must already exist
     64 # in outputdirectory.
     65 
     66 import os
     67 import sys
     68 import string
     69 import re
     70 
     71 MAGIC = '\\input texinfo'
     72 
     73 cmprog = re.compile('^@([a-z]+)([ \t]|$)')        # Command (line-oriented)
     74 blprog = re.compile('^[ \t]*$')                   # Blank line
     75 kwprog = re.compile('@[a-z]+')                    # Keyword (embedded, usually
     76                                                   # with {} args)
     77 spprog = re.compile('[\n@{}&<>]')                 # Special characters in
     78                                                   # running text
     79                                                   #
     80                                                   # menu item (Yuck!)
     81 miprog = re.compile('^\* ([^:]*):(:|[ \t]*([^\t,\n.]+)([^ \t\n]*))[ \t\n]*')
     82 #                   0    1     1 2        3          34         42        0
     83 #                         -----            ----------  ---------
     84 #                                 -|-----------------------------
     85 #                    -----------------------------------------------------
     86 
     87 
     88 
     89 
     90 class HTMLNode:
     91     """Some of the parser's functionality is separated into this class.
     92 
     93     A Node accumulates its contents, takes care of links to other Nodes
     94     and saves itself when it is finished and all links are resolved.
     95     """
     96 
     97     DOCTYPE = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'
     98 
     99     type = 0
    100     cont = ''
    101     epilogue = '</BODY></HTML>\n'
    102 
    103     def __init__(self, dir, name, topname, title, next, prev, up):
    104         self.dirname = dir
    105         self.name = name
    106         if topname:
    107             self.topname = topname
    108         else:
    109             self.topname = name
    110         self.title = title
    111         self.next = next
    112         self.prev = prev
    113         self.up = up
    114         self.lines = []
    115 
    116     def write(self, *lines):
    117         map(self.lines.append, lines)
    118 
    119     def flush(self):
    120         fp = open(self.dirname + '/' + makefile(self.name), 'w')
    121         fp.write(self.prologue)
    122         fp.write(self.text)
    123         fp.write(self.epilogue)
    124         fp.close()
    125 
    126     def link(self, label, nodename, rel=None, rev=None):
    127         if nodename:
    128             if nodename.lower() == '(dir)':
    129                 addr = '../dir.html'
    130                 title = ''
    131             else:
    132                 addr = makefile(nodename)
    133                 title = ' TITLE="%s"' % nodename
    134             self.write(label, ': <A HREF="', addr, '"', \
    135                        rel and (' REL=' + rel) or "", \
    136                        rev and (' REV=' + rev) or "", \
    137                        title, '>', nodename, '</A>  \n')
    138 
    139     def finalize(self):
    140         length = len(self.lines)
    141         self.text = ''.join(self.lines)
    142         self.lines = []
    143         self.open_links()
    144         self.output_links()
    145         self.close_links()
    146         links = ''.join(self.lines)
    147         self.lines = []
    148         self.prologue = (
    149             self.DOCTYPE +
    150             '\n<HTML><HEAD>\n'
    151             '  <!-- Converted with texi2html and Python -->\n'
    152             '  <TITLE>' + self.title + '</TITLE>\n'
    153             '  <LINK REL=Next HREF="'
    154                 + makefile(self.next) + '" TITLE="' + self.next + '">\n'
    155             '  <LINK REL=Previous HREF="'
    156                 + makefile(self.prev) + '" TITLE="' + self.prev  + '">\n'
    157             '  <LINK REL=Up HREF="'
    158                 + makefile(self.up) + '" TITLE="' + self.up  + '">\n'
    159             '</HEAD><BODY>\n' +
    160             links)
    161         if length > 20:
    162             self.epilogue = '<P>\n%s</BODY></HTML>\n' % links
    163 
    164     def open_links(self):
    165         self.write('<HR>\n')
    166 
    167     def close_links(self):
    168         self.write('<HR>\n')
    169 
    170     def output_links(self):
    171         if self.cont != self.next:
    172             self.link('  Cont', self.cont)
    173         self.link('  Next', self.next, rel='Next')
    174         self.link('  Prev', self.prev, rel='Previous')
    175         self.link('  Up', self.up, rel='Up')
    176         if self.name <> self.topname:
    177             self.link('  Top', self.topname)
    178 
    179 
    180 class HTML3Node(HTMLNode):
    181 
    182     DOCTYPE = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML Level 3//EN//3.0">'
    183 
    184     def open_links(self):
    185         self.write('<DIV CLASS=Navigation>\n <HR>\n')
    186 
    187     def close_links(self):
    188         self.write(' <HR>\n</DIV>\n')
    189 
    190 
    191 class TexinfoParser:
    192 
    193     COPYRIGHT_SYMBOL = "&copy;"
    194     FN_ID_PATTERN = "(%(id)s)"
    195     FN_SOURCE_PATTERN = '<A NAME=footnoteref%(id)s' \
    196                         ' HREF="#footnotetext%(id)s">' \
    197                         + FN_ID_PATTERN + '</A>'
    198     FN_TARGET_PATTERN = '<A NAME=footnotetext%(id)s' \
    199                         ' HREF="#footnoteref%(id)s">' \
    200                         + FN_ID_PATTERN + '</A>\n%(text)s<P>\n'
    201     FN_HEADER = '\n<P>\n<HR NOSHADE SIZE=1 WIDTH=200>\n' \
    202                 '<STRONG><EM>Footnotes</EM></STRONG>\n<P>'
    203 
    204 
    205     Node = HTMLNode
    206 
    207     # Initialize an instance
    208     def __init__(self):
    209         self.unknown = {}       # statistics about unknown @-commands
    210         self.filenames = {}     # Check for identical filenames
    211         self.debugging = 0      # larger values produce more output
    212         self.print_headers = 0  # always print headers?
    213         self.nodefp = None      # open file we're writing to
    214         self.nodelineno = 0     # Linenumber relative to node
    215         self.links = None       # Links from current node
    216         self.savetext = None    # If not None, save text head instead
    217         self.savestack = []     # If not None, save text head instead
    218         self.htmlhelp = None    # html help data
    219         self.dirname = 'tmp'    # directory where files are created
    220         self.includedir = '.'   # directory to search @include files
    221         self.nodename = ''      # name of current node
    222         self.topname = ''       # name of top node (first node seen)
    223         self.title = ''         # title of this whole Texinfo tree
    224         self.resetindex()       # Reset all indices
    225         self.contents = []      # Reset table of contents
    226         self.numbering = []     # Reset section numbering counters
    227         self.nofill = 0         # Normal operation: fill paragraphs
    228         self.values={'html': 1} # Names that should be parsed in ifset
    229         self.stackinfo={}       # Keep track of state in the stack
    230         # XXX The following should be reset per node?!
    231         self.footnotes = []     # Reset list of footnotes
    232         self.itemarg = None     # Reset command used by @item
    233         self.itemnumber = None  # Reset number for @item in @enumerate
    234         self.itemindex = None   # Reset item index name
    235         self.node = None
    236         self.nodestack = []
    237         self.cont = 0
    238         self.includedepth = 0
    239 
    240     # Set htmlhelp helper class
    241     def sethtmlhelp(self, htmlhelp):
    242         self.htmlhelp = htmlhelp
    243 
    244     # Set (output) directory name
    245     def setdirname(self, dirname):
    246         self.dirname = dirname
    247 
    248     # Set include directory name
    249     def setincludedir(self, includedir):
    250         self.includedir = includedir
    251 
    252     # Parse the contents of an entire file
    253     def parse(self, fp):
    254         line = fp.readline()
    255         lineno = 1
    256         while line and (line[0] == '%' or blprog.match(line)):
    257             line = fp.readline()
    258             lineno = lineno + 1
    259         if line[:len(MAGIC)] <> MAGIC:
    260             raise SyntaxError, 'file does not begin with %r' % (MAGIC,)
    261         self.parserest(fp, lineno)
    262 
    263     # Parse the contents of a file, not expecting a MAGIC header
    264     def parserest(self, fp, initial_lineno):
    265         lineno = initial_lineno
    266         self.done = 0
    267         self.skip = 0
    268         self.stack = []
    269         accu = []
    270         while not self.done:
    271             line = fp.readline()
    272             self.nodelineno = self.nodelineno + 1
    273             if not line:
    274                 if accu:
    275                     if not self.skip: self.process(accu)
    276                     accu = []
    277                 if initial_lineno > 0:
    278                     print '*** EOF before @bye'
    279                 break
    280             lineno = lineno + 1
    281             mo = cmprog.match(line)
    282             if mo:
    283                 a, b = mo.span(1)
    284                 cmd = line[a:b]
    285                 if cmd in ('noindent', 'refill'):
    286                     accu.append(line)
    287                 else:
    288                     if accu:
    289                         if not self.skip:
    290                             self.process(accu)
    291                         accu = []
    292                     self.command(line, mo)
    293             elif blprog.match(line) and \
    294                  'format' not in self.stack and \
    295                  'example' not in self.stack:
    296                 if accu:
    297                     if not self.skip:
    298                         self.process(accu)
    299                         if self.nofill:
    300                             self.write('\n')
    301                         else:
    302                             self.write('<P>\n')
    303                         accu = []
    304             else:
    305                 # Append the line including trailing \n!
    306                 accu.append(line)
    307         #
    308         if self.skip:
    309             print '*** Still skipping at the end'
    310         if self.stack:
    311             print '*** Stack not empty at the end'
    312             print '***', self.stack
    313         if self.includedepth == 0:
    314             while self.nodestack:
    315                 self.nodestack[-1].finalize()
    316                 self.nodestack[-1].flush()
    317                 del self.nodestack[-1]
    318 
    319     # Start saving text in a buffer instead of writing it to a file
    320     def startsaving(self):
    321         if self.savetext <> None:
    322             self.savestack.append(self.savetext)
    323             # print '*** Recursively saving text, expect trouble'
    324         self.savetext = ''
    325 
    326     # Return the text saved so far and start writing to file again
    327     def collectsavings(self):
    328         savetext = self.savetext
    329         if len(self.savestack) > 0:
    330             self.savetext = self.savestack[-1]
    331             del self.savestack[-1]
    332         else:
    333             self.savetext = None
    334         return savetext or ''
    335 
    336     # Write text to file, or save it in a buffer, or ignore it
    337     def write(self, *args):
    338         try:
    339             text = ''.join(args)
    340         except:
    341             print args
    342             raise TypeError
    343         if self.savetext <> None:
    344             self.savetext = self.savetext + text
    345         elif self.nodefp:
    346             self.nodefp.write(text)
    347         elif self.node:
    348             self.node.write(text)
    349 
    350     # Complete the current node -- write footnotes and close file
    351     def endnode(self):
    352         if self.savetext <> None:
    353             print '*** Still saving text at end of node'
    354             dummy = self.collectsavings()
    355         if self.footnotes:
    356             self.writefootnotes()
    357         if self.nodefp:
    358             if self.nodelineno > 20:
    359                 self.write('<HR>\n')
    360                 [name, next, prev, up] = self.nodelinks[:4]
    361                 self.link('Next', next)
    362                 self.link('Prev', prev)
    363                 self.link('Up', up)
    364                 if self.nodename <> self.topname:
    365                     self.link('Top', self.topname)
    366                 self.write('<HR>\n')
    367             self.write('</BODY>\n')
    368             self.nodefp.close()
    369             self.nodefp = None
    370         elif self.node:
    371             if not self.cont and \
    372                (not self.node.type or \
    373                 (self.node.next and self.node.prev and self.node.up)):
    374                 self.node.finalize()
    375                 self.node.flush()
    376             else:
    377                 self.nodestack.append(self.node)
    378             self.node = None
    379         self.nodename = ''
    380 
    381     # Process a list of lines, expanding embedded @-commands
    382     # This mostly distinguishes between menus and normal text
    383     def process(self, accu):
    384         if self.debugging > 1:
    385             print '!'*self.debugging, 'process:', self.skip, self.stack,
    386             if accu: print accu[0][:30],
    387             if accu[0][30:] or accu[1:]: print '...',
    388             print
    389         if self.inmenu():
    390             # XXX should be done differently
    391             for line in accu:
    392                 mo = miprog.match(line)
    393                 if not mo:
    394                     line = line.strip() + '\n'
    395                     self.expand(line)
    396                     continue
    397                 bgn, end = mo.span(0)
    398                 a, b = mo.span(1)
    399                 c, d = mo.span(2)
    400                 e, f = mo.span(3)
    401                 g, h = mo.span(4)
    402                 label = line[a:b]
    403                 nodename = line[c:d]
    404                 if nodename[0] == ':': nodename = label
    405                 else: nodename = line[e:f]
    406                 punct = line[g:h]
    407                 self.write('  <LI><A HREF="',
    408                            makefile(nodename),
    409                            '">', nodename,
    410                            '</A>', punct, '\n')
    411                 self.htmlhelp.menuitem(nodename)
    412                 self.expand(line[end:])
    413         else:
    414             text = ''.join(accu)
    415             self.expand(text)
    416 
    417     # find 'menu' (we might be inside 'ifset' or 'ifclear')
    418     def inmenu(self):
    419         #if 'menu' in self.stack:
    420         #    print 'inmenu   :', self.skip, self.stack, self.stackinfo
    421         stack = self.stack
    422         while stack and stack[-1] in ('ifset','ifclear'):
    423             try:
    424                 if self.stackinfo[len(stack)]:
    425                     return 0
    426             except KeyError:
    427                 pass
    428             stack = stack[:-1]
    429         return (stack and stack[-1] == 'menu')
    430 
    431     # Write a string, expanding embedded @-commands
    432     def expand(self, text):
    433         stack = []
    434         i = 0
    435         n = len(text)
    436         while i < n:
    437             start = i
    438             mo = spprog.search(text, i)
    439             if mo:
    440                 i = mo.start()
    441             else:
    442                 self.write(text[start:])
    443                 break
    444             self.write(text[start:i])
    445             c = text[i]
    446             i = i+1
    447             if c == '\n':
    448                 self.write('\n')
    449                 continue
    450             if c == '<':
    451                 self.write('&lt;')
    452                 continue
    453             if c == '>':
    454                 self.write('&gt;')
    455                 continue
    456             if c == '&':
    457                 self.write('&amp;')
    458                 continue
    459             if c == '{':
    460                 stack.append('')
    461                 continue
    462             if c == '}':
    463                 if not stack:
    464                     print '*** Unmatched }'
    465                     self.write('}')
    466                     continue
    467                 cmd = stack[-1]
    468                 del stack[-1]
    469                 try:
    470                     method = getattr(self, 'close_' + cmd)
    471                 except AttributeError:
    472                     self.unknown_close(cmd)
    473                     continue
    474                 method()
    475                 continue
    476             if c <> '@':
    477                 # Cannot happen unless spprog is changed
    478                 raise RuntimeError, 'unexpected funny %r' % c
    479             start = i
    480             while i < n and text[i] in string.ascii_letters: i = i+1
    481             if i == start:
    482                 # @ plus non-letter: literal next character
    483                 i = i+1
    484                 c = text[start:i]
    485                 if c == ':':
    486                     # `@:' means no extra space after
    487                     # preceding `.', `?', `!' or `:'
    488                     pass
    489                 else:
    490                     # `@.' means a sentence-ending period;
    491                     # `@@', `@{', `@}' quote `@', `{', `}'
    492                     self.write(c)
    493                 continue
    494             cmd = text[start:i]
    495             if i < n and text[i] == '{':
    496                 i = i+1
    497                 stack.append(cmd)
    498                 try:
    499                     method = getattr(self, 'open_' + cmd)
    500                 except AttributeError:
    501                     self.unknown_open(cmd)
    502                     continue
    503                 method()
    504                 continue
    505             try:
    506                 method = getattr(self, 'handle_' + cmd)
    507             except AttributeError:
    508                 self.unknown_handle(cmd)
    509                 continue
    510             method()
    511         if stack:
    512             print '*** Stack not empty at para:', stack
    513 
    514     # --- Handle unknown embedded @-commands ---
    515 
    516     def unknown_open(self, cmd):
    517         print '*** No open func for @' + cmd + '{...}'
    518         cmd = cmd + '{'
    519         self.write('@', cmd)
    520         if not self.unknown.has_key(cmd):
    521             self.unknown[cmd] = 1
    522         else:
    523             self.unknown[cmd] = self.unknown[cmd] + 1
    524 
    525     def unknown_close(self, cmd):
    526         print '*** No close func for @' + cmd + '{...}'
    527         cmd = '}' + cmd
    528         self.write('}')
    529         if not self.unknown.has_key(cmd):
    530             self.unknown[cmd] = 1
    531         else:
    532             self.unknown[cmd] = self.unknown[cmd] + 1
    533 
    534     def unknown_handle(self, cmd):
    535         print '*** No handler for @' + cmd
    536         self.write('@', cmd)
    537         if not self.unknown.has_key(cmd):
    538             self.unknown[cmd] = 1
    539         else:
    540             self.unknown[cmd] = self.unknown[cmd] + 1
    541 
    542     # XXX The following sections should be ordered as the texinfo docs
    543 
    544     # --- Embedded @-commands without {} argument list --
    545 
    546     def handle_noindent(self): pass
    547 
    548     def handle_refill(self): pass
    549 
    550     # --- Include file handling ---
    551 
    552     def do_include(self, args):
    553         file = args
    554         file = os.path.join(self.includedir, file)
    555         try:
    556             fp = open(file, 'r')
    557         except IOError, msg:
    558             print '*** Can\'t open include file', repr(file)
    559             return
    560         print '!'*self.debugging, '--> file', repr(file)
    561         save_done = self.done
    562         save_skip = self.skip
    563         save_stack = self.stack
    564         self.includedepth = self.includedepth + 1
    565         self.parserest(fp, 0)
    566         self.includedepth = self.includedepth - 1
    567         fp.close()
    568         self.done = save_done
    569         self.skip = save_skip
    570         self.stack = save_stack
    571         print '!'*self.debugging, '<-- file', repr(file)
    572 
    573     # --- Special Insertions ---
    574 
    575     def open_dmn(self): pass
    576     def close_dmn(self): pass
    577 
    578     def open_dots(self): self.write('...')
    579     def close_dots(self): pass
    580 
    581     def open_bullet(self): pass
    582     def close_bullet(self): pass
    583 
    584     def open_TeX(self): self.write('TeX')
    585     def close_TeX(self): pass
    586 
    587     def handle_copyright(self): self.write(self.COPYRIGHT_SYMBOL)
    588     def open_copyright(self): self.write(self.COPYRIGHT_SYMBOL)
    589     def close_copyright(self): pass
    590 
    591     def open_minus(self): self.write('-')
    592     def close_minus(self): pass
    593 
    594     # --- Accents ---
    595 
    596     # rpyron 2002-05-07
    597     # I would like to do at least as well as makeinfo when
    598     # it is producing HTML output:
    599     #
    600     #   input               output
    601     #     @"o                 @"o                umlaut accent
    602     #     @'o                 'o                 acute accent
    603     #     @,{c}               @,{c}              cedilla accent
    604     #     @=o                 @=o                macron/overbar accent
    605     #     @^o                 @^o                circumflex accent
    606     #     @`o                 `o                 grave accent
    607     #     @~o                 @~o                tilde accent
    608     #     @dotaccent{o}       @dotaccent{o}      overdot accent
    609     #     @H{o}               @H{o}              long Hungarian umlaut
    610     #     @ringaccent{o}      @ringaccent{o}     ring accent
    611     #     @tieaccent{oo}      @tieaccent{oo}     tie-after accent
    612     #     @u{o}               @u{o}              breve accent
    613     #     @ubaraccent{o}      @ubaraccent{o}     underbar accent
    614     #     @udotaccent{o}      @udotaccent{o}     underdot accent
    615     #     @v{o}               @v{o}              hacek or check accent
    616     #     @exclamdown{}       &#161;             upside-down !
    617     #     @questiondown{}     &#191;             upside-down ?
    618     #     @aa{},@AA{}         &#229;,&#197;      a,A with circle
    619     #     @ae{},@AE{}         &#230;,&#198;      ae,AE ligatures
    620     #     @dotless{i}         @dotless{i}        dotless i
    621     #     @dotless{j}         @dotless{j}        dotless j
    622     #     @l{},@L{}           l/,L/              suppressed-L,l
    623     #     @o{},@O{}           &#248;,&#216;      O,o with slash
    624     #     @oe{},@OE{}         oe,OE              oe,OE ligatures
    625     #     @ss{}               &#223;             es-zet or sharp S
    626     #
    627     # The following character codes and approximations have been
    628     # copied from makeinfo's HTML output.
    629 
    630     def open_exclamdown(self): self.write('&#161;')   # upside-down !
    631     def close_exclamdown(self): pass
    632     def open_questiondown(self): self.write('&#191;') # upside-down ?
    633     def close_questiondown(self): pass
    634     def open_aa(self): self.write('&#229;') # a with circle
    635     def close_aa(self): pass
    636     def open_AA(self): self.write('&#197;') # A with circle
    637     def close_AA(self): pass
    638     def open_ae(self): self.write('&#230;') # ae ligatures
    639     def close_ae(self): pass
    640     def open_AE(self): self.write('&#198;') # AE ligatures
    641     def close_AE(self): pass
    642     def open_o(self): self.write('&#248;')  # o with slash
    643     def close_o(self): pass
    644     def open_O(self): self.write('&#216;')  # O with slash
    645     def close_O(self): pass
    646     def open_ss(self): self.write('&#223;') # es-zet or sharp S
    647     def close_ss(self): pass
    648     def open_oe(self): self.write('oe')     # oe ligatures
    649     def close_oe(self): pass
    650     def open_OE(self): self.write('OE')     # OE ligatures
    651     def close_OE(self): pass
    652     def open_l(self): self.write('l/')      # suppressed-l
    653     def close_l(self): pass
    654     def open_L(self): self.write('L/')      # suppressed-L
    655     def close_L(self): pass
    656 
    657     # --- Special Glyphs for Examples ---
    658 
    659     def open_result(self): self.write('=&gt;')
    660     def close_result(self): pass
    661 
    662     def open_expansion(self): self.write('==&gt;')
    663     def close_expansion(self): pass
    664 
    665     def open_print(self): self.write('-|')
    666     def close_print(self): pass
    667 
    668     def open_error(self): self.write('error--&gt;')
    669     def close_error(self): pass
    670 
    671     def open_equiv(self): self.write('==')
    672     def close_equiv(self): pass
    673 
    674     def open_point(self): self.write('-!-')
    675     def close_point(self): pass
    676 
    677     # --- Cross References ---
    678 
    679     def open_pxref(self):
    680         self.write('see ')
    681         self.startsaving()
    682     def close_pxref(self):
    683         self.makeref()
    684 
    685     def open_xref(self):
    686         self.write('See ')
    687         self.startsaving()
    688     def close_xref(self):
    689         self.makeref()
    690 
    691     def open_ref(self):
    692         self.startsaving()
    693     def close_ref(self):
    694         self.makeref()
    695 
    696     def open_inforef(self):
    697         self.write('See info file ')
    698         self.startsaving()
    699     def close_inforef(self):
    700         text = self.collectsavings()
    701         args = [s.strip() for s in text.split(',')]
    702         while len(args) < 3: args.append('')
    703         node = args[0]
    704         file = args[2]
    705         self.write('`', file, '\', node `', node, '\'')
    706 
    707     def makeref(self):
    708         text = self.collectsavings()
    709         args = [s.strip() for s in text.split(',')]
    710         while len(args) < 5: args.append('')
    711         nodename = label = args[0]
    712         if args[2]: label = args[2]
    713         file = args[3]
    714         title = args[4]
    715         href = makefile(nodename)
    716         if file:
    717             href = '../' + file + '/' + href
    718         self.write('<A HREF="', href, '">', label, '</A>')
    719 
    720     # rpyron 2002-05-07  uref support
    721     def open_uref(self):
    722         self.startsaving()
    723     def close_uref(self):
    724         text = self.collectsavings()
    725         args = [s.strip() for s in text.split(',')]
    726         while len(args) < 2: args.append('')
    727         href = args[0]
    728         label = args[1]
    729         if not label: label = href
    730         self.write('<A HREF="', href, '">', label, '</A>')
    731 
    732     # rpyron 2002-05-07  image support
    733     # GNU makeinfo producing HTML output tries `filename.png'; if
    734     # that does not exist, it tries `filename.jpg'. If that does
    735     # not exist either, it complains. GNU makeinfo does not handle
    736     # GIF files; however, I include GIF support here because
    737     # MySQL documentation uses GIF files.
    738 
    739     def open_image(self):
    740         self.startsaving()
    741     def close_image(self):
    742         self.makeimage()
    743     def makeimage(self):
    744         text = self.collectsavings()
    745         args = [s.strip() for s in text.split(',')]
    746         while len(args) < 5: args.append('')
    747         filename = args[0]
    748         width    = args[1]
    749         height   = args[2]
    750         alt      = args[3]
    751         ext      = args[4]
    752 
    753         # The HTML output will have a reference to the image
    754         # that is relative to the HTML output directory,
    755         # which is what 'filename' gives us. However, we need
    756         # to find it relative to our own current directory,
    757         # so we construct 'imagename'.
    758         imagelocation = self.dirname + '/' + filename
    759 
    760         if   os.path.exists(imagelocation+'.png'):
    761             filename += '.png'
    762         elif os.path.exists(imagelocation+'.jpg'):
    763             filename += '.jpg'
    764         elif os.path.exists(imagelocation+'.gif'):   # MySQL uses GIF files
    765             filename += '.gif'
    766         else:
    767             print "*** Cannot find image " + imagelocation
    768         #TODO: what is 'ext'?
    769         self.write('<IMG SRC="', filename, '"',                     \
    770                     width  and (' WIDTH="'  + width  + '"') or "",  \
    771                     height and (' HEIGHT="' + height + '"') or "",  \
    772                     alt    and (' ALT="'    + alt    + '"') or "",  \
    773                     '/>' )
    774         self.htmlhelp.addimage(imagelocation)
    775 
    776 
    777     # --- Marking Words and Phrases ---
    778 
    779     # --- Other @xxx{...} commands ---
    780 
    781     def open_(self): pass # Used by {text enclosed in braces}
    782     def close_(self): pass
    783 
    784     open_asis = open_
    785     close_asis = close_
    786 
    787     def open_cite(self): self.write('<CITE>')
    788     def close_cite(self): self.write('</CITE>')
    789 
    790     def open_code(self): self.write('<CODE>')
    791     def close_code(self): self.write('</CODE>')
    792 
    793     def open_t(self): self.write('<TT>')
    794     def close_t(self): self.write('</TT>')
    795 
    796     def open_dfn(self): self.write('<DFN>')
    797     def close_dfn(self): self.write('</DFN>')
    798 
    799     def open_emph(self): self.write('<EM>')
    800     def close_emph(self): self.write('</EM>')
    801 
    802     def open_i(self): self.write('<I>')
    803     def close_i(self): self.write('</I>')
    804 
    805     def open_footnote(self):
    806         # if self.savetext <> None:
    807         #       print '*** Recursive footnote -- expect weirdness'
    808         id = len(self.footnotes) + 1
    809         self.write(self.FN_SOURCE_PATTERN % {'id': repr(id)})
    810         self.startsaving()
    811 
    812     def close_footnote(self):
    813         id = len(self.footnotes) + 1
    814         self.footnotes.append((id, self.collectsavings()))
    815 
    816     def writefootnotes(self):
    817         self.write(self.FN_HEADER)
    818         for id, text in self.footnotes:
    819             self.write(self.FN_TARGET_PATTERN
    820                        % {'id': repr(id), 'text': text})
    821         self.footnotes = []
    822 
    823     def open_file(self): self.write('<CODE>')
    824     def close_file(self): self.write('</CODE>')
    825 
    826     def open_kbd(self): self.write('<KBD>')
    827     def close_kbd(self): self.write('</KBD>')
    828 
    829     def open_key(self): self.write('<KEY>')
    830     def close_key(self): self.write('</KEY>')
    831 
    832     def open_r(self): self.write('<R>')
    833     def close_r(self): self.write('</R>')
    834 
    835     def open_samp(self): self.write('`<SAMP>')
    836     def close_samp(self): self.write('</SAMP>\'')
    837 
    838     def open_sc(self): self.write('<SMALLCAPS>')
    839     def close_sc(self): self.write('</SMALLCAPS>')
    840 
    841     def open_strong(self): self.write('<STRONG>')
    842     def close_strong(self): self.write('</STRONG>')
    843 
    844     def open_b(self): self.write('<B>')
    845     def close_b(self): self.write('</B>')
    846 
    847     def open_var(self): self.write('<VAR>')
    848     def close_var(self): self.write('</VAR>')
    849 
    850     def open_w(self): self.write('<NOBREAK>')
    851     def close_w(self): self.write('</NOBREAK>')
    852 
    853     def open_url(self): self.startsaving()
    854     def close_url(self):
    855         text = self.collectsavings()
    856         self.write('<A HREF="', text, '">', text, '</A>')
    857 
    858     def open_email(self): self.startsaving()
    859     def close_email(self):
    860         text = self.collectsavings()
    861         self.write('<A HREF="mailto:', text, '">', text, '</A>')
    862 
    863     open_titlefont = open_
    864     close_titlefont = close_
    865 
    866     def open_small(self): pass
    867     def close_small(self): pass
    868 
    869     def command(self, line, mo):
    870         a, b = mo.span(1)
    871         cmd = line[a:b]
    872         args = line[b:].strip()
    873         if self.debugging > 1:
    874             print '!'*self.debugging, 'command:', self.skip, self.stack, \
    875                   '@' + cmd, args
    876         try:
    877             func = getattr(self, 'do_' + cmd)
    878         except AttributeError:
    879             try:
    880                 func = getattr(self, 'bgn_' + cmd)
    881             except AttributeError:
    882                 # don't complain if we are skipping anyway
    883                 if not self.skip:
    884                     self.unknown_cmd(cmd, args)
    885                 return
    886             self.stack.append(cmd)
    887             func(args)
    888             return
    889         if not self.skip or cmd == 'end':
    890             func(args)
    891 
    892     def unknown_cmd(self, cmd, args):
    893         print '*** unknown', '@' + cmd, args
    894         if not self.unknown.has_key(cmd):
    895             self.unknown[cmd] = 1
    896         else:
    897             self.unknown[cmd] = self.unknown[cmd] + 1
    898 
    899     def do_end(self, args):
    900         words = args.split()
    901         if not words:
    902             print '*** @end w/o args'
    903         else:
    904             cmd = words[0]
    905             if not self.stack or self.stack[-1] <> cmd:
    906                 print '*** @end', cmd, 'unexpected'
    907             else:
    908                 del self.stack[-1]
    909             try:
    910                 func = getattr(self, 'end_' + cmd)
    911             except AttributeError:
    912                 self.unknown_end(cmd)
    913                 return
    914             func()
    915 
    916     def unknown_end(self, cmd):
    917         cmd = 'end ' + cmd
    918         print '*** unknown', '@' + cmd
    919         if not self.unknown.has_key(cmd):
    920             self.unknown[cmd] = 1
    921         else:
    922             self.unknown[cmd] = self.unknown[cmd] + 1
    923 
    924     # --- Comments ---
    925 
    926     def do_comment(self, args): pass
    927     do_c = do_comment
    928 
    929     # --- Conditional processing ---
    930 
    931     def bgn_ifinfo(self, args): pass
    932     def end_ifinfo(self): pass
    933 
    934     def bgn_iftex(self, args): self.skip = self.skip + 1
    935     def end_iftex(self): self.skip = self.skip - 1
    936 
    937     def bgn_ignore(self, args): self.skip = self.skip + 1
    938     def end_ignore(self): self.skip = self.skip - 1
    939 
    940     def bgn_tex(self, args): self.skip = self.skip + 1
    941     def end_tex(self): self.skip = self.skip - 1
    942 
    943     def do_set(self, args):
    944         fields = args.split(' ')
    945         key = fields[0]
    946         if len(fields) == 1:
    947             value = 1
    948         else:
    949             value = ' '.join(fields[1:])
    950         self.values[key] = value
    951 
    952     def do_clear(self, args):
    953         self.values[args] = None
    954 
    955     def bgn_ifset(self, args):
    956         if args not in self.values.keys() \
    957            or self.values[args] is None:
    958             self.skip = self.skip + 1
    959             self.stackinfo[len(self.stack)] = 1
    960         else:
    961             self.stackinfo[len(self.stack)] = 0
    962     def end_ifset(self):
    963         try:
    964             if self.stackinfo[len(self.stack) + 1]:
    965                 self.skip = self.skip - 1
    966             del self.stackinfo[len(self.stack) + 1]
    967         except KeyError:
    968             print '*** end_ifset: KeyError :', len(self.stack) + 1
    969 
    970     def bgn_ifclear(self, args):
    971         if args in self.values.keys() \
    972            and self.values[args] is not None:
    973             self.skip = self.skip + 1
    974             self.stackinfo[len(self.stack)] = 1
    975         else:
    976             self.stackinfo[len(self.stack)] = 0
    977     def end_ifclear(self):
    978         try:
    979             if self.stackinfo[len(self.stack) + 1]:
    980                 self.skip = self.skip - 1
    981             del self.stackinfo[len(self.stack) + 1]
    982         except KeyError:
    983             print '*** end_ifclear: KeyError :', len(self.stack) + 1
    984 
    985     def open_value(self):
    986         self.startsaving()
    987 
    988     def close_value(self):
    989         key = self.collectsavings()
    990         if key in self.values.keys():
    991             self.write(self.values[key])
    992         else:
    993             print '*** Undefined value: ', key
    994 
    995     # --- Beginning a file ---
    996 
    997     do_finalout = do_comment
    998     do_setchapternewpage = do_comment
    999     do_setfilename = do_comment
   1000 
   1001     def do_settitle(self, args):
   1002         self.startsaving()
   1003         self.expand(args)
   1004         self.title = self.collectsavings()
   1005     def do_parskip(self, args): pass
   1006 
   1007     # --- Ending a file ---
   1008 
   1009     def do_bye(self, args):
   1010         self.endnode()
   1011         self.done = 1
   1012 
   1013     # --- Title page ---
   1014 
   1015     def bgn_titlepage(self, args): self.skip = self.skip + 1
   1016     def end_titlepage(self): self.skip = self.skip - 1
   1017     def do_shorttitlepage(self, args): pass
   1018 
   1019     def do_center(self, args):
   1020         # Actually not used outside title page...
   1021         self.write('<H1>')
   1022         self.expand(args)
   1023         self.write('</H1>\n')
   1024     do_title = do_center
   1025     do_subtitle = do_center
   1026     do_author = do_center
   1027 
   1028     do_vskip = do_comment
   1029     do_vfill = do_comment
   1030     do_smallbook = do_comment
   1031 
   1032     do_paragraphindent = do_comment
   1033     do_setchapternewpage = do_comment
   1034     do_headings = do_comment
   1035     do_footnotestyle = do_comment
   1036 
   1037     do_evenheading = do_comment
   1038     do_evenfooting = do_comment
   1039     do_oddheading = do_comment
   1040     do_oddfooting = do_comment
   1041     do_everyheading = do_comment
   1042     do_everyfooting = do_comment
   1043 
   1044     # --- Nodes ---
   1045 
   1046     def do_node(self, args):
   1047         self.endnode()
   1048         self.nodelineno = 0
   1049         parts = [s.strip() for s in args.split(',')]
   1050         while len(parts) < 4: parts.append('')
   1051         self.nodelinks = parts
   1052         [name, next, prev, up] = parts[:4]
   1053         file = self.dirname + '/' + makefile(name)
   1054         if self.filenames.has_key(file):
   1055             print '*** Filename already in use: ', file
   1056         else:
   1057             if self.debugging: print '!'*self.debugging, '--- writing', file
   1058         self.filenames[file] = 1
   1059         # self.nodefp = open(file, 'w')
   1060         self.nodename = name
   1061         if self.cont and self.nodestack:
   1062             self.nodestack[-1].cont = self.nodename
   1063         if not self.topname: self.topname = name
   1064         title = name
   1065         if self.title: title = title + ' -- ' + self.title
   1066         self.node = self.Node(self.dirname, self.nodename, self.topname,
   1067                               title, next, prev, up)
   1068         self.htmlhelp.addnode(self.nodename,next,prev,up,file)
   1069 
   1070     def link(self, label, nodename):
   1071         if nodename:
   1072             if nodename.lower() == '(dir)':
   1073                 addr = '../dir.html'
   1074             else:
   1075                 addr = makefile(nodename)
   1076             self.write(label, ': <A HREF="', addr, '" TYPE="',
   1077                        label, '">', nodename, '</A>  \n')
   1078 
   1079     # --- Sectioning commands ---
   1080 
   1081     def popstack(self, type):
   1082         if (self.node):
   1083             self.node.type = type
   1084             while self.nodestack:
   1085                 if self.nodestack[-1].type > type:
   1086                     self.nodestack[-1].finalize()
   1087                     self.nodestack[-1].flush()
   1088                     del self.nodestack[-1]
   1089                 elif self.nodestack[-1].type == type:
   1090                     if not self.nodestack[-1].next:
   1091                         self.nodestack[-1].next = self.node.name
   1092                     if not self.node.prev:
   1093                         self.node.prev = self.nodestack[-1].name
   1094                     self.nodestack[-1].finalize()
   1095                     self.nodestack[-1].flush()
   1096                     del self.nodestack[-1]
   1097                 else:
   1098                     if type > 1 and not self.node.up:
   1099                         self.node.up = self.nodestack[-1].name
   1100                     break
   1101 
   1102     def do_chapter(self, args):
   1103         self.heading('H1', args, 0)
   1104         self.popstack(1)
   1105 
   1106     def do_unnumbered(self, args):
   1107         self.heading('H1', args, -1)
   1108         self.popstack(1)
   1109     def do_appendix(self, args):
   1110         self.heading('H1', args, -1)
   1111         self.popstack(1)
   1112     def do_top(self, args):
   1113         self.heading('H1', args, -1)
   1114     def do_chapheading(self, args):
   1115         self.heading('H1', args, -1)
   1116     def do_majorheading(self, args):
   1117         self.heading('H1', args, -1)
   1118 
   1119     def do_section(self, args):
   1120         self.heading('H1', args, 1)
   1121         self.popstack(2)
   1122 
   1123     def do_unnumberedsec(self, args):
   1124         self.heading('H1', args, -1)
   1125         self.popstack(2)
   1126     def do_appendixsec(self, args):
   1127         self.heading('H1', args, -1)
   1128         self.popstack(2)
   1129     do_appendixsection = do_appendixsec
   1130     def do_heading(self, args):
   1131         self.heading('H1', args, -1)
   1132 
   1133     def do_subsection(self, args):
   1134         self.heading('H2', args, 2)
   1135         self.popstack(3)
   1136     def do_unnumberedsubsec(self, args):
   1137         self.heading('H2', args, -1)
   1138         self.popstack(3)
   1139     def do_appendixsubsec(self, args):
   1140         self.heading('H2', args, -1)
   1141         self.popstack(3)
   1142     def do_subheading(self, args):
   1143         self.heading('H2', args, -1)
   1144 
   1145     def do_subsubsection(self, args):
   1146         self.heading('H3', args, 3)
   1147         self.popstack(4)
   1148     def do_unnumberedsubsubsec(self, args):
   1149         self.heading('H3', args, -1)
   1150         self.popstack(4)
   1151     def do_appendixsubsubsec(self, args):
   1152         self.heading('H3', args, -1)
   1153         self.popstack(4)
   1154     def do_subsubheading(self, args):
   1155         self.heading('H3', args, -1)
   1156 
   1157     def heading(self, type, args, level):
   1158         if level >= 0:
   1159             while len(self.numbering) <= level:
   1160                 self.numbering.append(0)
   1161             del self.numbering[level+1:]
   1162             self.numbering[level] = self.numbering[level] + 1
   1163             x = ''
   1164             for i in self.numbering:
   1165                 x = x + repr(i) + '.'
   1166             args = x + ' ' + args
   1167             self.contents.append((level, args, self.nodename))
   1168         self.write('<', type, '>')
   1169         self.expand(args)
   1170         self.write('</', type, '>\n')
   1171         if self.debugging or self.print_headers:
   1172             print '---', args
   1173 
   1174     def do_contents(self, args):
   1175         # pass
   1176         self.listcontents('Table of Contents', 999)
   1177 
   1178     def do_shortcontents(self, args):
   1179         pass
   1180         # self.listcontents('Short Contents', 0)
   1181     do_summarycontents = do_shortcontents
   1182 
   1183     def listcontents(self, title, maxlevel):
   1184         self.write('<H1>', title, '</H1>\n<UL COMPACT PLAIN>\n')
   1185         prevlevels = [0]
   1186         for level, title, node in self.contents:
   1187             if level > maxlevel:
   1188                 continue
   1189             if level > prevlevels[-1]:
   1190                 # can only advance one level at a time
   1191                 self.write('  '*prevlevels[-1], '<UL PLAIN>\n')
   1192                 prevlevels.append(level)
   1193             elif level < prevlevels[-1]:
   1194                 # might drop back multiple levels
   1195                 while level < prevlevels[-1]:
   1196                     del prevlevels[-1]
   1197                     self.write('  '*prevlevels[-1],
   1198                                '</UL>\n')
   1199             self.write('  '*level, '<LI> <A HREF="',
   1200                        makefile(node), '">')
   1201             self.expand(title)
   1202             self.write('</A>\n')
   1203         self.write('</UL>\n' * len(prevlevels))
   1204 
   1205     # --- Page lay-out ---
   1206 
   1207     # These commands are only meaningful in printed text
   1208 
   1209     def do_page(self, args): pass
   1210 
   1211     def do_need(self, args): pass
   1212 
   1213     def bgn_group(self, args): pass
   1214     def end_group(self): pass
   1215 
   1216     # --- Line lay-out ---
   1217 
   1218     def do_sp(self, args):
   1219         if self.nofill:
   1220             self.write('\n')
   1221         else:
   1222             self.write('<P>\n')
   1223 
   1224     def do_hline(self, args):
   1225         self.write('<HR>')
   1226 
   1227     # --- Function and variable definitions ---
   1228 
   1229     def bgn_deffn(self, args):
   1230         self.write('<DL>')
   1231         self.do_deffnx(args)
   1232 
   1233     def end_deffn(self):
   1234         self.write('</DL>\n')
   1235 
   1236     def do_deffnx(self, args):
   1237         self.write('<DT>')
   1238         words = splitwords(args, 2)
   1239         [category, name], rest = words[:2], words[2:]
   1240         self.expand('@b{%s}' % name)
   1241         for word in rest: self.expand(' ' + makevar(word))
   1242         #self.expand(' -- ' + category)
   1243         self.write('\n<DD>')
   1244         self.index('fn', name)
   1245 
   1246     def bgn_defun(self, args): self.bgn_deffn('Function ' + args)
   1247     end_defun = end_deffn
   1248     def do_defunx(self, args): self.do_deffnx('Function ' + args)
   1249 
   1250     def bgn_defmac(self, args): self.bgn_deffn('Macro ' + args)
   1251     end_defmac = end_deffn
   1252     def do_defmacx(self, args): self.do_deffnx('Macro ' + args)
   1253 
   1254     def bgn_defspec(self, args): self.bgn_deffn('{Special Form} ' + args)
   1255     end_defspec = end_deffn
   1256     def do_defspecx(self, args): self.do_deffnx('{Special Form} ' + args)
   1257 
   1258     def bgn_defvr(self, args):
   1259         self.write('<DL>')
   1260         self.do_defvrx(args)
   1261 
   1262     end_defvr = end_deffn
   1263 
   1264     def do_defvrx(self, args):
   1265         self.write('<DT>')
   1266         words = splitwords(args, 2)
   1267         [category, name], rest = words[:2], words[2:]
   1268         self.expand('@code{%s}' % name)
   1269         # If there are too many arguments, show them
   1270         for word in rest: self.expand(' ' + word)
   1271         #self.expand(' -- ' + category)
   1272         self.write('\n<DD>')
   1273         self.index('vr', name)
   1274 
   1275     def bgn_defvar(self, args): self.bgn_defvr('Variable ' + args)
   1276     end_defvar = end_defvr
   1277     def do_defvarx(self, args): self.do_defvrx('Variable ' + args)
   1278 
   1279     def bgn_defopt(self, args): self.bgn_defvr('{User Option} ' + args)
   1280     end_defopt = end_defvr
   1281     def do_defoptx(self, args): self.do_defvrx('{User Option} ' + args)
   1282 
   1283     # --- Ditto for typed languages ---
   1284 
   1285     def bgn_deftypefn(self, args):
   1286         self.write('<DL>')
   1287         self.do_deftypefnx(args)
   1288 
   1289     end_deftypefn = end_deffn
   1290 
   1291     def do_deftypefnx(self, args):
   1292         self.write('<DT>')
   1293         words = splitwords(args, 3)
   1294         [category, datatype, name], rest = words[:3], words[3:]
   1295         self.expand('@code{%s} @b{%s}' % (datatype, name))
   1296         for word in rest: self.expand(' ' + makevar(word))
   1297         #self.expand(' -- ' + category)
   1298         self.write('\n<DD>')
   1299         self.index('fn', name)
   1300 
   1301 
   1302     def bgn_deftypefun(self, args): self.bgn_deftypefn('Function ' + args)
   1303     end_deftypefun = end_deftypefn
   1304     def do_deftypefunx(self, args): self.do_deftypefnx('Function ' + args)
   1305 
   1306     def bgn_deftypevr(self, args):
   1307         self.write('<DL>')
   1308         self.do_deftypevrx(args)
   1309 
   1310     end_deftypevr = end_deftypefn
   1311 
   1312     def do_deftypevrx(self, args):
   1313         self.write('<DT>')
   1314         words = splitwords(args, 3)
   1315         [category, datatype, name], rest = words[:3], words[3:]
   1316         self.expand('@code{%s} @b{%s}' % (datatype, name))
   1317         # If there are too many arguments, show them
   1318         for word in rest: self.expand(' ' + word)
   1319         #self.expand(' -- ' + category)
   1320         self.write('\n<DD>')
   1321         self.index('fn', name)
   1322 
   1323     def bgn_deftypevar(self, args):
   1324         self.bgn_deftypevr('Variable ' + args)
   1325     end_deftypevar = end_deftypevr
   1326     def do_deftypevarx(self, args):
   1327         self.do_deftypevrx('Variable ' + args)
   1328 
   1329     # --- Ditto for object-oriented languages ---
   1330 
   1331     def bgn_defcv(self, args):
   1332         self.write('<DL>')
   1333         self.do_defcvx(args)
   1334 
   1335     end_defcv = end_deftypevr
   1336 
   1337     def do_defcvx(self, args):
   1338         self.write('<DT>')
   1339         words = splitwords(args, 3)
   1340         [category, classname, name], rest = words[:3], words[3:]
   1341         self.expand('@b{%s}' % name)
   1342         # If there are too many arguments, show them
   1343         for word in rest: self.expand(' ' + word)
   1344         #self.expand(' -- %s of @code{%s}' % (category, classname))
   1345         self.write('\n<DD>')
   1346         self.index('vr', '%s @r{on %s}' % (name, classname))
   1347 
   1348     def bgn_defivar(self, args):
   1349         self.bgn_defcv('{Instance Variable} ' + args)
   1350     end_defivar = end_defcv
   1351     def do_defivarx(self, args):
   1352         self.do_defcvx('{Instance Variable} ' + args)
   1353 
   1354     def bgn_defop(self, args):
   1355         self.write('<DL>')
   1356         self.do_defopx(args)
   1357 
   1358     end_defop = end_defcv
   1359 
   1360     def do_defopx(self, args):
   1361         self.write('<DT>')
   1362         words = splitwords(args, 3)
   1363         [category, classname, name], rest = words[:3], words[3:]
   1364         self.expand('@b{%s}' % name)
   1365         for word in rest: self.expand(' ' + makevar(word))
   1366         #self.expand(' -- %s of @code{%s}' % (category, classname))
   1367         self.write('\n<DD>')
   1368         self.index('fn', '%s @r{on %s}' % (name, classname))
   1369 
   1370     def bgn_defmethod(self, args):
   1371         self.bgn_defop('Method ' + args)
   1372     end_defmethod = end_defop
   1373     def do_defmethodx(self, args):
   1374         self.do_defopx('Method ' + args)
   1375 
   1376     # --- Ditto for data types ---
   1377 
   1378     def bgn_deftp(self, args):
   1379         self.write('<DL>')
   1380         self.do_deftpx(args)
   1381 
   1382     end_deftp = end_defcv
   1383 
   1384     def do_deftpx(self, args):
   1385         self.write('<DT>')
   1386         words = splitwords(args, 2)
   1387         [category, name], rest = words[:2], words[2:]
   1388         self.expand('@b{%s}' % name)
   1389         for word in rest: self.expand(' ' + word)
   1390         #self.expand(' -- ' + category)
   1391         self.write('\n<DD>')
   1392         self.index('tp', name)
   1393 
   1394     # --- Making Lists and Tables
   1395 
   1396     def bgn_enumerate(self, args):
   1397         if not args:
   1398             self.write('<OL>\n')
   1399             self.stackinfo[len(self.stack)] = '</OL>\n'
   1400         else:
   1401             self.itemnumber = args
   1402             self.write('<UL>\n')
   1403             self.stackinfo[len(self.stack)] = '</UL>\n'
   1404     def end_enumerate(self):
   1405         self.itemnumber = None
   1406         self.write(self.stackinfo[len(self.stack) + 1])
   1407         del self.stackinfo[len(self.stack) + 1]
   1408 
   1409     def bgn_itemize(self, args):
   1410         self.itemarg = args
   1411         self.write('<UL>\n')
   1412     def end_itemize(self):
   1413         self.itemarg = None
   1414         self.write('</UL>\n')
   1415 
   1416     def bgn_table(self, args):
   1417         self.itemarg = args
   1418         self.write('<DL>\n')
   1419     def end_table(self):
   1420         self.itemarg = None
   1421         self.write('</DL>\n')
   1422 
   1423     def bgn_ftable(self, args):
   1424         self.itemindex = 'fn'
   1425         self.bgn_table(args)
   1426     def end_ftable(self):
   1427         self.itemindex = None
   1428         self.end_table()
   1429 
   1430     def bgn_vtable(self, args):
   1431         self.itemindex = 'vr'
   1432         self.bgn_table(args)
   1433     def end_vtable(self):
   1434         self.itemindex = None
   1435         self.end_table()
   1436 
   1437     def do_item(self, args):
   1438         if self.itemindex: self.index(self.itemindex, args)
   1439         if self.itemarg:
   1440             if self.itemarg[0] == '@' and self.itemarg[1] and \
   1441                             self.itemarg[1] in string.ascii_letters:
   1442                 args = self.itemarg + '{' + args + '}'
   1443             else:
   1444                 # some other character, e.g. '-'
   1445                 args = self.itemarg + ' ' + args
   1446         if self.itemnumber <> None:
   1447             args = self.itemnumber + '. ' + args
   1448             self.itemnumber = increment(self.itemnumber)
   1449         if self.stack and self.stack[-1] == 'table':
   1450             self.write('<DT>')
   1451             self.expand(args)
   1452             self.write('\n<DD>')
   1453         elif self.stack and self.stack[-1] == 'multitable':
   1454             self.write('<TR><TD>')
   1455             self.expand(args)
   1456             self.write('</TD>\n</TR>\n')
   1457         else:
   1458             self.write('<LI>')
   1459             self.expand(args)
   1460             self.write('  ')
   1461     do_itemx = do_item # XXX Should suppress leading blank line
   1462 
   1463     # rpyron 2002-05-07  multitable support
   1464     def bgn_multitable(self, args):
   1465         self.itemarg = None     # should be handled by columnfractions
   1466         self.write('<TABLE BORDER="">\n')
   1467     def end_multitable(self):
   1468         self.itemarg = None
   1469         self.write('</TABLE>\n<BR>\n')
   1470     def handle_columnfractions(self):
   1471         # It would be better to handle this, but for now it's in the way...
   1472         self.itemarg = None
   1473     def handle_tab(self):
   1474         self.write('</TD>\n    <TD>')
   1475 
   1476     # --- Enumerations, displays, quotations ---
   1477     # XXX Most of these should increase the indentation somehow
   1478 
   1479     def bgn_quotation(self, args): self.write('<BLOCKQUOTE>')
   1480     def end_quotation(self): self.write('</BLOCKQUOTE>\n')
   1481 
   1482     def bgn_example(self, args):
   1483         self.nofill = self.nofill + 1
   1484         self.write('<PRE>')
   1485     def end_example(self):
   1486         self.write('</PRE>\n')
   1487         self.nofill = self.nofill - 1
   1488 
   1489     bgn_lisp = bgn_example # Synonym when contents are executable lisp code
   1490     end_lisp = end_example
   1491 
   1492     bgn_smallexample = bgn_example # XXX Should use smaller font
   1493     end_smallexample = end_example
   1494 
   1495     bgn_smalllisp = bgn_lisp # Ditto
   1496     end_smalllisp = end_lisp
   1497 
   1498     bgn_display = bgn_example
   1499     end_display = end_example
   1500 
   1501     bgn_format = bgn_display
   1502     end_format = end_display
   1503 
   1504     def do_exdent(self, args): self.expand(args + '\n')
   1505     # XXX Should really mess with indentation
   1506 
   1507     def bgn_flushleft(self, args):
   1508         self.nofill = self.nofill + 1
   1509         self.write('<PRE>\n')
   1510     def end_flushleft(self):
   1511         self.write('</PRE>\n')
   1512         self.nofill = self.nofill - 1
   1513 
   1514     def bgn_flushright(self, args):
   1515         self.nofill = self.nofill + 1
   1516         self.write('<ADDRESS COMPACT>\n')
   1517     def end_flushright(self):
   1518         self.write('</ADDRESS>\n')
   1519         self.nofill = self.nofill - 1
   1520 
   1521     def bgn_menu(self, args):
   1522         self.write('<DIR>\n')
   1523         self.write('  <STRONG><EM>Menu</EM></STRONG><P>\n')
   1524         self.htmlhelp.beginmenu()
   1525     def end_menu(self):
   1526         self.write('</DIR>\n')
   1527         self.htmlhelp.endmenu()
   1528 
   1529     def bgn_cartouche(self, args): pass
   1530     def end_cartouche(self): pass
   1531 
   1532     # --- Indices ---
   1533 
   1534     def resetindex(self):
   1535         self.noncodeindices = ['cp']
   1536         self.indextitle = {}
   1537         self.indextitle['cp'] = 'Concept'
   1538         self.indextitle['fn'] = 'Function'
   1539         self.indextitle['ky'] = 'Keyword'
   1540         self.indextitle['pg'] = 'Program'
   1541         self.indextitle['tp'] = 'Type'
   1542         self.indextitle['vr'] = 'Variable'
   1543         #
   1544         self.whichindex = {}
   1545         for name in self.indextitle.keys():
   1546             self.whichindex[name] = []
   1547 
   1548     def user_index(self, name, args):
   1549         if self.whichindex.has_key(name):
   1550             self.index(name, args)
   1551         else:
   1552             print '*** No index named', repr(name)
   1553 
   1554     def do_cindex(self, args): self.index('cp', args)
   1555     def do_findex(self, args): self.index('fn', args)
   1556     def do_kindex(self, args): self.index('ky', args)
   1557     def do_pindex(self, args): self.index('pg', args)
   1558     def do_tindex(self, args): self.index('tp', args)
   1559     def do_vindex(self, args): self.index('vr', args)
   1560 
   1561     def index(self, name, args):
   1562         self.whichindex[name].append((args, self.nodename))
   1563         self.htmlhelp.index(args, self.nodename)
   1564 
   1565     def do_synindex(self, args):
   1566         words = args.split()
   1567         if len(words) <> 2:
   1568             print '*** bad @synindex', args
   1569             return
   1570         [old, new] = words
   1571         if not self.whichindex.has_key(old) or \
   1572                   not self.whichindex.has_key(new):
   1573             print '*** bad key(s) in @synindex', args
   1574             return
   1575         if old <> new and \
   1576                   self.whichindex[old] is not self.whichindex[new]:
   1577             inew = self.whichindex[new]
   1578             inew[len(inew):] = self.whichindex[old]
   1579             self.whichindex[old] = inew
   1580     do_syncodeindex = do_synindex # XXX Should use code font
   1581 
   1582     def do_printindex(self, args):
   1583         words = args.split()
   1584         for name in words:
   1585             if self.whichindex.has_key(name):
   1586                 self.prindex(name)
   1587             else:
   1588                 print '*** No index named', repr(name)
   1589 
   1590     def prindex(self, name):
   1591         iscodeindex = (name not in self.noncodeindices)
   1592         index = self.whichindex[name]
   1593         if not index: return
   1594         if self.debugging:
   1595             print '!'*self.debugging, '--- Generating', \
   1596                   self.indextitle[name], 'index'
   1597         #  The node already provides a title
   1598         index1 = []
   1599         junkprog = re.compile('^(@[a-z]+)?{')
   1600         for key, node in index:
   1601             sortkey = key.lower()
   1602             # Remove leading `@cmd{' from sort key
   1603             # -- don't bother about the matching `}'
   1604             oldsortkey = sortkey
   1605             while 1:
   1606                 mo = junkprog.match(sortkey)
   1607                 if not mo:
   1608                     break
   1609                 i = mo.end()
   1610                 sortkey = sortkey[i:]
   1611             index1.append((sortkey, key, node))
   1612         del index[:]
   1613         index1.sort()
   1614         self.write('<DL COMPACT>\n')
   1615         prevkey = prevnode = None
   1616         for sortkey, key, node in index1:
   1617             if (key, node) == (prevkey, prevnode):
   1618                 continue
   1619             if self.debugging > 1: print '!'*self.debugging, key, ':', node
   1620             self.write('<DT>')
   1621             if iscodeindex: key = '@code{' + key + '}'
   1622             if key != prevkey:
   1623                 self.expand(key)
   1624             self.write('\n<DD><A HREF="%s">%s</A>\n' % (makefile(node), node))
   1625             prevkey, prevnode = key, node
   1626         self.write('</DL>\n')
   1627 
   1628     # --- Final error reports ---
   1629 
   1630     def report(self):
   1631         if self.unknown:
   1632             print '--- Unrecognized commands ---'
   1633             cmds = self.unknown.keys()
   1634             cmds.sort()
   1635             for cmd in cmds:
   1636                 print cmd.ljust(20), self.unknown[cmd]
   1637 
   1638 
   1639 class TexinfoParserHTML3(TexinfoParser):
   1640 
   1641     COPYRIGHT_SYMBOL = "&copy;"
   1642     FN_ID_PATTERN = "[%(id)s]"
   1643     FN_SOURCE_PATTERN = '<A ID=footnoteref%(id)s ' \
   1644                         'HREF="#footnotetext%(id)s">' + FN_ID_PATTERN + '</A>'
   1645     FN_TARGET_PATTERN = '<FN ID=footnotetext%(id)s>\n' \
   1646                         '<P><A HREF="#footnoteref%(id)s">' + FN_ID_PATTERN \
   1647                         + '</A>\n%(text)s</P></FN>\n'
   1648     FN_HEADER = '<DIV CLASS=footnotes>\n  <HR NOSHADE WIDTH=200>\n' \
   1649                 '  <STRONG><EM>Footnotes</EM></STRONG>\n  <P>\n'
   1650 
   1651     Node = HTML3Node
   1652 
   1653     def bgn_quotation(self, args): self.write('<BQ>')
   1654     def end_quotation(self): self.write('</BQ>\n')
   1655 
   1656     def bgn_example(self, args):
   1657         # this use of <CODE> would not be legal in HTML 2.0,
   1658         # but is in more recent DTDs.
   1659         self.nofill = self.nofill + 1
   1660         self.write('<PRE CLASS=example><CODE>')
   1661     def end_example(self):
   1662         self.write("</CODE></PRE>\n")
   1663         self.nofill = self.nofill - 1
   1664 
   1665     def bgn_flushleft(self, args):
   1666         self.nofill = self.nofill + 1
   1667         self.write('<PRE CLASS=flushleft>\n')
   1668 
   1669     def bgn_flushright(self, args):
   1670         self.nofill = self.nofill + 1
   1671         self.write('<DIV ALIGN=right CLASS=flushright><ADDRESS COMPACT>\n')
   1672     def end_flushright(self):
   1673         self.write('</ADDRESS></DIV>\n')
   1674         self.nofill = self.nofill - 1
   1675 
   1676     def bgn_menu(self, args):
   1677         self.write('<UL PLAIN CLASS=menu>\n')
   1678         self.write('  <LH>Menu</LH>\n')
   1679     def end_menu(self):
   1680         self.write('</UL>\n')
   1681 
   1682 
   1683 # rpyron 2002-05-07
   1684 class HTMLHelp:
   1685     """
   1686     This class encapsulates support for HTML Help. Node names,
   1687     file names, menu items, index items, and image file names are
   1688     accumulated until a call to finalize(). At that time, three
   1689     output files are created in the current directory:
   1690 
   1691         `helpbase`.hhp  is a HTML Help Workshop project file.
   1692                         It contains various information, some of
   1693                         which I do not understand; I just copied
   1694                         the default project info from a fresh
   1695                         installation.
   1696         `helpbase`.hhc  is the Contents file for the project.
   1697         `helpbase`.hhk  is the Index file for the project.
   1698 
   1699     When these files are used as input to HTML Help Workshop,
   1700     the resulting file will be named:
   1701 
   1702         `helpbase`.chm
   1703 
   1704     If none of the defaults in `helpbase`.hhp are changed,
   1705     the .CHM file will have Contents, Index, Search, and
   1706     Favorites tabs.
   1707     """
   1708 
   1709     codeprog = re.compile('@code{(.*?)}')
   1710 
   1711     def __init__(self,helpbase,dirname):
   1712         self.helpbase    = helpbase
   1713         self.dirname     = dirname
   1714         self.projectfile = None
   1715         self.contentfile = None
   1716         self.indexfile   = None
   1717         self.nodelist    = []
   1718         self.nodenames   = {}         # nodename : index
   1719         self.nodeindex   = {}
   1720         self.filenames   = {}         # filename : filename
   1721         self.indexlist   = []         # (args,nodename) == (key,location)
   1722         self.current     = ''
   1723         self.menudict    = {}
   1724         self.dumped      = {}
   1725 
   1726 
   1727     def addnode(self,name,next,prev,up,filename):
   1728         node = (name,next,prev,up,filename)
   1729         # add this file to dict
   1730         # retrieve list with self.filenames.values()
   1731         self.filenames[filename] = filename
   1732         # add this node to nodelist
   1733         self.nodeindex[name] = len(self.nodelist)
   1734         self.nodelist.append(node)
   1735         # set 'current' for menu items
   1736         self.current = name
   1737         self.menudict[self.current] = []
   1738 
   1739     def menuitem(self,nodename):
   1740         menu = self.menudict[self.current]
   1741         menu.append(nodename)
   1742 
   1743 
   1744     def addimage(self,imagename):
   1745         self.filenames[imagename] = imagename
   1746 
   1747     def index(self, args, nodename):
   1748         self.indexlist.append((args,nodename))
   1749 
   1750     def beginmenu(self):
   1751         pass
   1752 
   1753     def endmenu(self):
   1754         pass
   1755 
   1756     def finalize(self):
   1757         if not self.helpbase:
   1758             return
   1759 
   1760         # generate interesting filenames
   1761         resultfile   = self.helpbase + '.chm'
   1762         projectfile  = self.helpbase + '.hhp'
   1763         contentfile  = self.helpbase + '.hhc'
   1764         indexfile    = self.helpbase + '.hhk'
   1765 
   1766         # generate a reasonable title
   1767         title        = self.helpbase
   1768 
   1769         # get the default topic file
   1770         (topname,topnext,topprev,topup,topfile) = self.nodelist[0]
   1771         defaulttopic = topfile
   1772 
   1773         # PROJECT FILE
   1774         try:
   1775             fp = open(projectfile,'w')
   1776             print>>fp, '[OPTIONS]'
   1777             print>>fp, 'Auto Index=Yes'
   1778             print>>fp, 'Binary TOC=No'
   1779             print>>fp, 'Binary Index=Yes'
   1780             print>>fp, 'Compatibility=1.1'
   1781             print>>fp, 'Compiled file=' + resultfile + ''
   1782             print>>fp, 'Contents file=' + contentfile + ''
   1783             print>>fp, 'Default topic=' + defaulttopic + ''
   1784             print>>fp, 'Error log file=ErrorLog.log'
   1785             print>>fp, 'Index file=' + indexfile + ''
   1786             print>>fp, 'Title=' + title + ''
   1787             print>>fp, 'Display compile progress=Yes'
   1788             print>>fp, 'Full-text search=Yes'
   1789             print>>fp, 'Default window=main'
   1790             print>>fp, ''
   1791             print>>fp, '[WINDOWS]'
   1792             print>>fp, ('main=,"' + contentfile + '","' + indexfile
   1793                         + '","","",,,,,0x23520,222,0x1046,[10,10,780,560],'
   1794                         '0xB0000,,,,,,0')
   1795             print>>fp, ''
   1796             print>>fp, '[FILES]'
   1797             print>>fp, ''
   1798             self.dumpfiles(fp)
   1799             fp.close()
   1800         except IOError, msg:
   1801             print projectfile, ':', msg
   1802             sys.exit(1)
   1803 
   1804         # CONTENT FILE
   1805         try:
   1806             fp = open(contentfile,'w')
   1807             print>>fp, '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'
   1808             print>>fp, '<!-- This file defines the table of contents -->'
   1809             print>>fp, '<HTML>'
   1810             print>>fp, '<HEAD>'
   1811             print>>fp, ('<meta name="GENERATOR"'
   1812                         'content="Microsoft&reg; HTML Help Workshop 4.1">')
   1813             print>>fp, '<!-- Sitemap 1.0 -->'
   1814             print>>fp, '</HEAD>'
   1815             print>>fp, '<BODY>'
   1816             print>>fp, '   <OBJECT type="text/site properties">'
   1817             print>>fp, '     <param name="Window Styles" value="0x800025">'
   1818             print>>fp, '     <param name="comment" value="title:">'
   1819             print>>fp, '     <param name="comment" value="base:">'
   1820             print>>fp, '   </OBJECT>'
   1821             self.dumpnodes(fp)
   1822             print>>fp, '</BODY>'
   1823             print>>fp, '</HTML>'
   1824             fp.close()
   1825         except IOError, msg:
   1826             print contentfile, ':', msg
   1827             sys.exit(1)
   1828 
   1829         # INDEX FILE
   1830         try:
   1831             fp = open(indexfile  ,'w')
   1832             print>>fp, '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'
   1833             print>>fp, '<!-- This file defines the index -->'
   1834             print>>fp, '<HTML>'
   1835             print>>fp, '<HEAD>'
   1836             print>>fp, ('<meta name="GENERATOR"'
   1837                         'content="Microsoft&reg; HTML Help Workshop 4.1">')
   1838             print>>fp, '<!-- Sitemap 1.0 -->'
   1839             print>>fp, '</HEAD>'
   1840             print>>fp, '<BODY>'
   1841             print>>fp, '<OBJECT type="text/site properties">'
   1842             print>>fp, '</OBJECT>'
   1843             self.dumpindex(fp)
   1844             print>>fp, '</BODY>'
   1845             print>>fp, '</HTML>'
   1846             fp.close()
   1847         except IOError, msg:
   1848             print indexfile  , ':', msg
   1849             sys.exit(1)
   1850 
   1851     def dumpfiles(self, outfile=sys.stdout):
   1852         filelist = self.filenames.values()
   1853         filelist.sort()
   1854         for filename in filelist:
   1855             print>>outfile, filename
   1856 
   1857     def dumpnodes(self, outfile=sys.stdout):
   1858         self.dumped = {}
   1859         if self.nodelist:
   1860             nodename, dummy, dummy, dummy, dummy = self.nodelist[0]
   1861             self.topnode = nodename
   1862 
   1863         print>>outfile,  '<UL>'
   1864         for node in self.nodelist:
   1865             self.dumpnode(node,0,outfile)
   1866         print>>outfile,  '</UL>'
   1867 
   1868     def dumpnode(self, node, indent=0, outfile=sys.stdout):
   1869         if node:
   1870             # Retrieve info for this node
   1871             (nodename,next,prev,up,filename) = node
   1872             self.current = nodename
   1873 
   1874             # Have we been dumped already?
   1875             if self.dumped.has_key(nodename):
   1876                 return
   1877             self.dumped[nodename] = 1
   1878 
   1879             # Print info for this node
   1880             print>>outfile, ' '*indent,
   1881             print>>outfile, '<LI><OBJECT type="text/sitemap">',
   1882             print>>outfile, '<param name="Name" value="' + nodename +'">',
   1883             print>>outfile, '<param name="Local" value="'+ filename +'">',
   1884             print>>outfile, '</OBJECT>'
   1885 
   1886             # Does this node have menu items?
   1887             try:
   1888                 menu = self.menudict[nodename]
   1889                 self.dumpmenu(menu,indent+2,outfile)
   1890             except KeyError:
   1891                 pass
   1892 
   1893     def dumpmenu(self, menu, indent=0, outfile=sys.stdout):
   1894         if menu:
   1895             currentnode = self.current
   1896             if currentnode != self.topnode:    # XXX this is a hack
   1897                 print>>outfile, ' '*indent + '<UL>'
   1898                 indent += 2
   1899             for item in menu:
   1900                 menunode = self.getnode(item)
   1901                 self.dumpnode(menunode,indent,outfile)
   1902             if currentnode != self.topnode:    # XXX this is a hack
   1903                 print>>outfile, ' '*indent + '</UL>'
   1904                 indent -= 2
   1905 
   1906     def getnode(self, nodename):
   1907         try:
   1908             index = self.nodeindex[nodename]
   1909             return self.nodelist[index]
   1910         except KeyError:
   1911             return None
   1912         except IndexError:
   1913             return None
   1914 
   1915     # (args,nodename) == (key,location)
   1916     def dumpindex(self, outfile=sys.stdout):
   1917         print>>outfile,  '<UL>'
   1918         for (key,location) in self.indexlist:
   1919             key = self.codeexpand(key)
   1920             location = makefile(location)
   1921             location = self.dirname + '/' + location
   1922             print>>outfile, '<LI><OBJECT type="text/sitemap">',
   1923             print>>outfile, '<param name="Name" value="' + key + '">',
   1924             print>>outfile, '<param name="Local" value="' + location + '">',
   1925             print>>outfile, '</OBJECT>'
   1926         print>>outfile,  '</UL>'
   1927 
   1928     def codeexpand(self, line):
   1929         co = self.codeprog.match(line)
   1930         if not co:
   1931             return line
   1932         bgn, end = co.span(0)
   1933         a, b = co.span(1)
   1934         line = line[:bgn] + line[a:b] + line[end:]
   1935         return line
   1936 
   1937 
   1938 # Put @var{} around alphabetic substrings
   1939 def makevar(str):
   1940     return '@var{'+str+'}'
   1941 
   1942 
   1943 # Split a string in "words" according to findwordend
   1944 def splitwords(str, minlength):
   1945     words = []
   1946     i = 0
   1947     n = len(str)
   1948     while i < n:
   1949         while i < n and str[i] in ' \t\n': i = i+1
   1950         if i >= n: break
   1951         start = i
   1952         i = findwordend(str, i, n)
   1953         words.append(str[start:i])
   1954     while len(words) < minlength: words.append('')
   1955     return words
   1956 
   1957 
   1958 # Find the end of a "word", matching braces and interpreting @@ @{ @}
   1959 fwprog = re.compile('[@{} ]')
   1960 def findwordend(str, i, n):
   1961     level = 0
   1962     while i < n:
   1963         mo = fwprog.search(str, i)
   1964         if not mo:
   1965             break
   1966         i = mo.start()
   1967         c = str[i]; i = i+1
   1968         if c == '@': i = i+1 # Next character is not special
   1969         elif c == '{': level = level+1
   1970         elif c == '}': level = level-1
   1971         elif c == ' ' and level <= 0: return i-1
   1972     return n
   1973 
   1974 
   1975 # Convert a node name into a file name
   1976 def makefile(nodename):
   1977     nodename = nodename.strip()
   1978     return fixfunnychars(nodename) + '.html'
   1979 
   1980 
   1981 # Characters that are perfectly safe in filenames and hyperlinks
   1982 goodchars = string.ascii_letters + string.digits + '!@-=+.'
   1983 
   1984 # Replace characters that aren't perfectly safe by dashes
   1985 # Underscores are bad since Cern HTTPD treats them as delimiters for
   1986 # encoding times, so you get mismatches if you compress your files:
   1987 # a.html.gz will map to a_b.html.gz
   1988 def fixfunnychars(addr):
   1989     i = 0
   1990     while i < len(addr):
   1991         c = addr[i]
   1992         if c not in goodchars:
   1993             c = '-'
   1994             addr = addr[:i] + c + addr[i+1:]
   1995         i = i + len(c)
   1996     return addr
   1997 
   1998 
   1999 # Increment a string used as an enumeration
   2000 def increment(s):
   2001     if not s:
   2002         return '1'
   2003     for sequence in string.digits, string.lowercase, string.uppercase:
   2004         lastc = s[-1]
   2005         if lastc in sequence:
   2006             i = sequence.index(lastc) + 1
   2007             if i >= len(sequence):
   2008                 if len(s) == 1:
   2009                     s = sequence[0]*2
   2010                     if s == '00':
   2011                         s = '10'
   2012                 else:
   2013                     s = increment(s[:-1]) + sequence[0]
   2014             else:
   2015                 s = s[:-1] + sequence[i]
   2016             return s
   2017     return s # Don't increment
   2018 
   2019 
   2020 def test():
   2021     import sys
   2022     debugging = 0
   2023     print_headers = 0
   2024     cont = 0
   2025     html3 = 0
   2026     htmlhelp = ''
   2027 
   2028     while sys.argv[1] == ['-d']:
   2029         debugging = debugging + 1
   2030         del sys.argv[1]
   2031     if sys.argv[1] == '-p':
   2032         print_headers = 1
   2033         del sys.argv[1]
   2034     if sys.argv[1] == '-c':
   2035         cont = 1
   2036         del sys.argv[1]
   2037     if sys.argv[1] == '-3':
   2038         html3 = 1
   2039         del sys.argv[1]
   2040     if sys.argv[1] == '-H':
   2041         helpbase = sys.argv[2]
   2042         del sys.argv[1:3]
   2043     if len(sys.argv) <> 3:
   2044         print 'usage: texi2hh [-d [-d]] [-p] [-c] [-3] [-H htmlhelp]', \
   2045               'inputfile outputdirectory'
   2046         sys.exit(2)
   2047 
   2048     if html3:
   2049         parser = TexinfoParserHTML3()
   2050     else:
   2051         parser = TexinfoParser()
   2052     parser.cont = cont
   2053     parser.debugging = debugging
   2054     parser.print_headers = print_headers
   2055 
   2056     file = sys.argv[1]
   2057     dirname  = sys.argv[2]
   2058     parser.setdirname(dirname)
   2059     parser.setincludedir(os.path.dirname(file))
   2060 
   2061     htmlhelp = HTMLHelp(helpbase, dirname)
   2062     parser.sethtmlhelp(htmlhelp)
   2063 
   2064     try:
   2065         fp = open(file, 'r')
   2066     except IOError, msg:
   2067         print file, ':', msg
   2068         sys.exit(1)
   2069 
   2070     parser.parse(fp)
   2071     fp.close()
   2072     parser.report()
   2073 
   2074     htmlhelp.finalize()
   2075 
   2076 
   2077 if __name__ == "__main__":
   2078     test()
   2079