texi2html.py 68.5 KB
Newer Older
1
#! /usr/bin/env python3
Guido van Rossum's avatar
Guido van Rossum committed
2 3 4

# Convert GNU texinfo files into HTML, one file per node.
# Based on Texinfo 2.14.
5
# Usage: texi2html [-d] [-d] [-c] inputfile outputdirectory
Guido van Rossum's avatar
Guido van Rossum committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# 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?)

25 26 27
# More consistent handling of chapters/sections/etc.
# Lots of documentation
# Many more options:
Guido van Rossum's avatar
Guido van Rossum committed
28 29 30 31 32 33
#       -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
#       ...
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 ?
Guido van Rossum's avatar
Guido van Rossum committed
37

38 39 40
# rpyron 2002-05-07
# Robert Pyron <rpyron@alum.mit.edu>
# 1. BUGFIX: In function makefile(), strip blanks from the nodename.
41
#    This is necessary to match the behavior of parser.makeref() and
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
#    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.

Guido van Rossum's avatar
Guido van Rossum committed
66
import os
67
import sys
Guido van Rossum's avatar
Guido van Rossum committed
68
import string
69
import re
Guido van Rossum's avatar
Guido van Rossum committed
70 71 72

MAGIC = '\\input texinfo'

73 74 75 76 77
cmprog = re.compile('^@([a-z]+)([ \t]|$)')        # Command (line-oriented)
blprog = re.compile('^[ \t]*$')                   # Blank line
kwprog = re.compile('@[a-z]+')                    # Keyword (embedded, usually
                                                  # with {} args)
spprog = re.compile('[\n@{}&<>]')                 # Special characters in
Tim Peters's avatar
Tim Peters committed
78
                                                  # running text
79 80
                                                  #
                                                  # menu item (Yuck!)
81 82 83 84 85
miprog = re.compile(r'^\* ([^:]*):(:|[ \t]*([^\t,\n.]+)([^ \t\n]*))[ \t\n]*')
#                    0    1     1 2        3          34         42        0
#                          -----            ----------  ---------
#                                  -|-----------------------------
#                     -----------------------------------------------------
86

Guido van Rossum's avatar
Guido van Rossum committed
87

88

Tim Peters's avatar
Tim Peters committed
89

90 91
class HTMLNode:
    """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 95
    and saves itself when it is finished and all links are resolved.
    """
96

97 98 99 100 101 102 103
    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):
Guido van Rossum's avatar
Guido van Rossum committed
104 105 106 107 108 109 110 111 112 113 114
        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 = []
115

116
    def write(self, *lines):
117 118
        for line in lines:
            self.lines.append(line)
119

120
    def flush(self):
Guido van Rossum's avatar
Guido van Rossum committed
121 122 123 124 125
        fp = open(self.dirname + '/' + makefile(self.name), 'w')
        fp.write(self.prologue)
        fp.write(self.text)
        fp.write(self.epilogue)
        fp.close()
126

127
    def link(self, label, nodename, rel=None, rev=None):
Guido van Rossum's avatar
Guido van Rossum committed
128
        if nodename:
129
            if nodename.lower() == '(dir)':
Guido van Rossum's avatar
Guido van Rossum committed
130 131 132 133 134 135 136 137 138
                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')
139

140
    def finalize(self):
Guido van Rossum's avatar
Guido van Rossum committed
141
        length = len(self.lines)
142
        self.text = ''.join(self.lines)
Guido van Rossum's avatar
Guido van Rossum committed
143 144 145 146
        self.lines = []
        self.open_links()
        self.output_links()
        self.close_links()
147
        links = ''.join(self.lines)
Guido van Rossum's avatar
Guido van Rossum committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
        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
164 165

    def open_links(self):
Guido van Rossum's avatar
Guido van Rossum committed
166
        self.write('<HR>\n')
167 168

    def close_links(self):
Guido van Rossum's avatar
Guido van Rossum committed
169
        self.write('<HR>\n')
170 171

    def output_links(self):
Guido van Rossum's avatar
Guido van Rossum committed
172 173 174 175 176
        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')
177
        if self.name != self.topname:
Guido van Rossum's avatar
Guido van Rossum committed
178
            self.link('  Top', self.topname)
179 180 181 182 183 184 185


class HTML3Node(HTMLNode):

    DOCTYPE = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML Level 3//EN//3.0">'

    def open_links(self):
Guido van Rossum's avatar
Guido van Rossum committed
186
        self.write('<DIV CLASS=Navigation>\n <HR>\n')
187 188

    def close_links(self):
Guido van Rossum's avatar
Guido van Rossum committed
189
        self.write(' <HR>\n</DIV>\n')
190

191

Guido van Rossum's avatar
Guido van Rossum committed
192 193
class TexinfoParser:

194 195 196
    COPYRIGHT_SYMBOL = "&copy;"
    FN_ID_PATTERN = "(%(id)s)"
    FN_SOURCE_PATTERN = '<A NAME=footnoteref%(id)s' \
Guido van Rossum's avatar
Guido van Rossum committed
197 198
                        ' HREF="#footnotetext%(id)s">' \
                        + FN_ID_PATTERN + '</A>'
199
    FN_TARGET_PATTERN = '<A NAME=footnotetext%(id)s' \
Guido van Rossum's avatar
Guido van Rossum committed
200 201
                        ' HREF="#footnoteref%(id)s">' \
                        + FN_ID_PATTERN + '</A>\n%(text)s<P>\n'
202
    FN_HEADER = '\n<P>\n<HR NOSHADE SIZE=1 WIDTH=200>\n' \
Guido van Rossum's avatar
Guido van Rossum committed
203
                '<STRONG><EM>Footnotes</EM></STRONG>\n<P>'
204 205 206 207


    Node = HTMLNode

208 209
    # Initialize an instance
    def __init__(self):
Guido van Rossum's avatar
Guido van Rossum committed
210 211 212 213 214 215 216 217 218
        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
219
        self.htmlhelp = None    # html help data
Guido van Rossum's avatar
Guido van Rossum committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
        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
240 241 242 243 244

    # Set htmlhelp helper class
    def sethtmlhelp(self, htmlhelp):
        self.htmlhelp = htmlhelp

245 246
    # Set (output) directory name
    def setdirname(self, dirname):
Guido van Rossum's avatar
Guido van Rossum committed
247
        self.dirname = dirname
248 249 250

    # Set include directory name
    def setincludedir(self, includedir):
Guido van Rossum's avatar
Guido van Rossum committed
251
        self.includedir = includedir
252 253 254

    # Parse the contents of an entire file
    def parse(self, fp):
Guido van Rossum's avatar
Guido van Rossum committed
255 256 257 258 259
        line = fp.readline()
        lineno = 1
        while line and (line[0] == '%' or blprog.match(line)):
            line = fp.readline()
            lineno = lineno + 1
260
        if line[:len(MAGIC)] != MAGIC:
261
            raise SyntaxError('file does not begin with %r' % (MAGIC,))
Guido van Rossum's avatar
Guido van Rossum committed
262
        self.parserest(fp, lineno)
263 264 265

    # Parse the contents of a file, not expecting a MAGIC header
    def parserest(self, fp, initial_lineno):
Guido van Rossum's avatar
Guido van Rossum committed
266 267 268 269 270 271 272 273 274 275 276 277 278
        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:
279
                    print('*** EOF before @bye')
Guido van Rossum's avatar
Guido van Rossum committed
280 281
                break
            lineno = lineno + 1
282 283 284
            mo = cmprog.match(line)
            if mo:
                a, b = mo.span(1)
Guido van Rossum's avatar
Guido van Rossum committed
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
                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:
310
            print('*** Still skipping at the end')
Guido van Rossum's avatar
Guido van Rossum committed
311
        if self.stack:
312 313
            print('*** Stack not empty at the end')
            print('***', self.stack)
Guido van Rossum's avatar
Guido van Rossum committed
314 315 316 317 318
        if self.includedepth == 0:
            while self.nodestack:
                self.nodestack[-1].finalize()
                self.nodestack[-1].flush()
                del self.nodestack[-1]
319 320 321

    # Start saving text in a buffer instead of writing it to a file
    def startsaving(self):
322
        if self.savetext is not None:
Guido van Rossum's avatar
Guido van Rossum committed
323 324 325
            self.savestack.append(self.savetext)
            # print '*** Recursively saving text, expect trouble'
        self.savetext = ''
326 327 328

    # Return the text saved so far and start writing to file again
    def collectsavings(self):
Guido van Rossum's avatar
Guido van Rossum committed
329 330 331 332 333 334 335
        savetext = self.savetext
        if len(self.savestack) > 0:
            self.savetext = self.savestack[-1]
            del self.savestack[-1]
        else:
            self.savetext = None
        return savetext or ''
336 337 338

    # Write text to file, or save it in a buffer, or ignore it
    def write(self, *args):
Guido van Rossum's avatar
Guido van Rossum committed
339
        try:
340
            text = ''.join(args)
Guido van Rossum's avatar
Guido van Rossum committed
341
        except:
342
            print(args)
Guido van Rossum's avatar
Guido van Rossum committed
343
            raise TypeError
344
        if self.savetext is not None:
Guido van Rossum's avatar
Guido van Rossum committed
345 346 347 348 349
            self.savetext = self.savetext + text
        elif self.nodefp:
            self.nodefp.write(text)
        elif self.node:
            self.node.write(text)
350

351 352
    # Complete the current node -- write footnotes and close file
    def endnode(self):
353
        if self.savetext is not None:
354
            print('*** Still saving text at end of node')
Guido van Rossum's avatar
Guido van Rossum committed
355 356 357 358 359 360 361 362 363 364
            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)
365
                if self.nodename != self.topname:
Guido van Rossum's avatar
Guido van Rossum committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
                    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 = ''
381 382 383 384

    # Process a list of lines, expanding embedded @-commands
    # This mostly distinguishes between menus and normal text
    def process(self, accu):
Guido van Rossum's avatar
Guido van Rossum committed
385
        if self.debugging > 1:
386 387 388 389
            print('!'*self.debugging, 'process:', self.skip, self.stack, end=' ')
            if accu: print(accu[0][:30], end=' ')
            if accu[0][30:] or accu[1:]: print('...', end=' ')
            print()
390
        if self.inmenu():
Guido van Rossum's avatar
Guido van Rossum committed
391 392
            # XXX should be done differently
            for line in accu:
393 394
                mo = miprog.match(line)
                if not mo:
395
                    line = line.strip() + '\n'
Guido van Rossum's avatar
Guido van Rossum committed
396 397
                    self.expand(line)
                    continue
398 399 400 401 402
                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)
Guido van Rossum's avatar
Guido van Rossum committed
403 404 405 406 407 408 409 410 411
                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')
412
                self.htmlhelp.menuitem(nodename)
Guido van Rossum's avatar
Guido van Rossum committed
413 414
                self.expand(line[end:])
        else:
415
            text = ''.join(accu)
Guido van Rossum's avatar
Guido van Rossum committed
416
            self.expand(text)
Guido van Rossum's avatar
Guido van Rossum committed
417

418 419 420 421 422 423 424 425 426 427 428 429 430 431
    # 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')

432 433
    # Write a string, expanding embedded @-commands
    def expand(self, text):
Guido van Rossum's avatar
Guido van Rossum committed
434 435 436 437 438
        stack = []
        i = 0
        n = len(text)
        while i < n:
            start = i
439 440 441 442
            mo = spprog.search(text, i)
            if mo:
                i = mo.start()
            else:
Guido van Rossum's avatar
Guido van Rossum committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
                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:
465
                    print('*** Unmatched }')
Guido van Rossum's avatar
Guido van Rossum committed
466 467 468 469 470 471 472 473 474 475 476
                    self.write('}')
                    continue
                cmd = stack[-1]
                del stack[-1]
                try:
                    method = getattr(self, 'close_' + cmd)
                except AttributeError:
                    self.unknown_close(cmd)
                    continue
                method()
                continue
477
            if c != '@':
Guido van Rossum's avatar
Guido van Rossum committed
478
                # Cannot happen unless spprog is changed
479
                raise RuntimeError('unexpected funny %r' % c)
Guido van Rossum's avatar
Guido van Rossum committed
480
            start = i
481
            while i < n and text[i] in string.ascii_letters: i = i+1
Guido van Rossum's avatar
Guido van Rossum committed
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
            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:
                    self.unknown_open(cmd)
                    continue
                method()
                continue
            try:
                method = getattr(self, 'handle_' + cmd)
            except AttributeError:
                self.unknown_handle(cmd)
                continue
            method()
        if stack:
513
            print('*** Stack not empty at para:', stack)
514 515 516 517

    # --- Handle unknown embedded @-commands ---

    def unknown_open(self, cmd):
518
        print('*** No open func for @' + cmd + '{...}')
Guido van Rossum's avatar
Guido van Rossum committed
519 520
        cmd = cmd + '{'
        self.write('@', cmd)
521
        if cmd not in self.unknown:
Guido van Rossum's avatar
Guido van Rossum committed
522 523 524
            self.unknown[cmd] = 1
        else:
            self.unknown[cmd] = self.unknown[cmd] + 1
525 526

    def unknown_close(self, cmd):
527
        print('*** No close func for @' + cmd + '{...}')
Guido van Rossum's avatar
Guido van Rossum committed
528 529
        cmd = '}' + cmd
        self.write('}')
530
        if cmd not in self.unknown:
Guido van Rossum's avatar
Guido van Rossum committed
531 532 533
            self.unknown[cmd] = 1
        else:
            self.unknown[cmd] = self.unknown[cmd] + 1
Guido van Rossum's avatar
Guido van Rossum committed
534

535
    def unknown_handle(self, cmd):
536
        print('*** No handler for @' + cmd)
Guido van Rossum's avatar
Guido van Rossum committed
537
        self.write('@', cmd)
538
        if cmd not in self.unknown:
Guido van Rossum's avatar
Guido van Rossum committed
539 540 541
            self.unknown[cmd] = 1
        else:
            self.unknown[cmd] = self.unknown[cmd] + 1
542 543 544 545 546 547 548 549 550 551 552 553

    # XXX The following sections should be ordered as the texinfo docs

    # --- Embedded @-commands without {} argument list --

    def handle_noindent(self): pass

    def handle_refill(self): pass

    # --- Include file handling ---

    def do_include(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
554 555 556 557
        file = args
        file = os.path.join(self.includedir, file)
        try:
            fp = open(file, 'r')
558
        except IOError as msg:
559
            print('*** Can\'t open include file', repr(file))
Guido van Rossum's avatar
Guido van Rossum committed
560
            return
561
        print('!'*self.debugging, '--> file', repr(file))
Guido van Rossum's avatar
Guido van Rossum committed
562 563 564 565 566 567 568 569 570 571
        save_done = self.done
        save_skip = self.skip
        save_stack = self.stack
        self.includedepth = self.includedepth + 1
        self.parserest(fp, 0)
        self.includedepth = self.includedepth - 1
        fp.close()
        self.done = save_done
        self.skip = save_skip
        self.stack = save_stack
572
        print('!'*self.debugging, '<-- file', repr(file))
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587

    # --- Special Insertions ---

    def open_dmn(self): pass
    def close_dmn(self): pass

    def open_dots(self): self.write('...')
    def close_dots(self): pass

    def open_bullet(self): pass
    def close_bullet(self): pass

    def open_TeX(self): self.write('TeX')
    def close_TeX(self): pass

588 589
    def handle_copyright(self): self.write(self.COPYRIGHT_SYMBOL)
    def open_copyright(self): self.write(self.COPYRIGHT_SYMBOL)
590
    def close_copyright(self): pass
Guido van Rossum's avatar
Guido van Rossum committed
591

592 593
    def open_minus(self): self.write('-')
    def close_minus(self): pass
Guido van Rossum's avatar
Guido van Rossum committed
594

595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
    # --- Accents ---

    # rpyron 2002-05-07
    # I would like to do at least as well as makeinfo when
    # it is producing HTML output:
    #
    #   input               output
    #     @"o                 @"o                umlaut accent
    #     @'o                 'o                 acute accent
    #     @,{c}               @,{c}              cedilla accent
    #     @=o                 @=o                macron/overbar accent
    #     @^o                 @^o                circumflex accent
    #     @`o                 `o                 grave accent
    #     @~o                 @~o                tilde accent
    #     @dotaccent{o}       @dotaccent{o}      overdot accent
    #     @H{o}               @H{o}              long Hungarian umlaut
    #     @ringaccent{o}      @ringaccent{o}     ring accent
    #     @tieaccent{oo}      @tieaccent{oo}     tie-after accent
    #     @u{o}               @u{o}              breve accent
    #     @ubaraccent{o}      @ubaraccent{o}     underbar accent
    #     @udotaccent{o}      @udotaccent{o}     underdot accent
    #     @v{o}               @v{o}              hacek or check accent
    #     @exclamdown{}       &#161;             upside-down !
    #     @questiondown{}     &#191;             upside-down ?
    #     @aa{},@AA{}         &#229;,&#197;      a,A with circle
    #     @ae{},@AE{}         &#230;,&#198;      ae,AE ligatures
    #     @dotless{i}         @dotless{i}        dotless i
    #     @dotless{j}         @dotless{j}        dotless j
    #     @l{},@L{}           l/,L/              suppressed-L,l
    #     @o{},@O{}           &#248;,&#216;      O,o with slash
    #     @oe{},@OE{}         oe,OE              oe,OE ligatures
    #     @ss{}               &#223;             es-zet or sharp S
    #
    # The following character codes and approximations have been
    # copied from makeinfo's HTML output.

    def open_exclamdown(self): self.write('&#161;')   # upside-down !
    def close_exclamdown(self): pass
    def open_questiondown(self): self.write('&#191;') # upside-down ?
    def close_questiondown(self): pass
    def open_aa(self): self.write('&#229;') # a with circle
    def close_aa(self): pass
    def open_AA(self): self.write('&#197;') # A with circle
    def close_AA(self): pass
    def open_ae(self): self.write('&#230;') # ae ligatures
    def close_ae(self): pass
    def open_AE(self): self.write('&#198;') # AE ligatures
    def close_AE(self): pass
    def open_o(self): self.write('&#248;')  # o with slash
    def close_o(self): pass
    def open_O(self): self.write('&#216;')  # O with slash
    def close_O(self): pass
    def open_ss(self): self.write('&#223;') # es-zet or sharp S
    def close_ss(self): pass
    def open_oe(self): self.write('oe')     # oe ligatures
    def close_oe(self): pass
    def open_OE(self): self.write('OE')     # OE ligatures
    def close_OE(self): pass
    def open_l(self): self.write('l/')      # suppressed-l
    def close_l(self): pass
    def open_L(self): self.write('L/')      # suppressed-L
    def close_L(self): pass

658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
    # --- Special Glyphs for Examples ---

    def open_result(self): self.write('=&gt;')
    def close_result(self): pass

    def open_expansion(self): self.write('==&gt;')
    def close_expansion(self): pass

    def open_print(self): self.write('-|')
    def close_print(self): pass

    def open_error(self): self.write('error--&gt;')
    def close_error(self): pass

    def open_equiv(self): self.write('==')
    def close_equiv(self): pass

    def open_point(self): self.write('-!-')
    def close_point(self): pass

    # --- Cross References ---

    def open_pxref(self):
Guido van Rossum's avatar
Guido van Rossum committed
681 682
        self.write('see ')
        self.startsaving()
683
    def close_pxref(self):
Guido van Rossum's avatar
Guido van Rossum committed
684
        self.makeref()
685 686

    def open_xref(self):
Guido van Rossum's avatar
Guido van Rossum committed
687 688
        self.write('See ')
        self.startsaving()
689
    def close_xref(self):
Guido van Rossum's avatar
Guido van Rossum committed
690
        self.makeref()
691 692

    def open_ref(self):
Guido van Rossum's avatar
Guido van Rossum committed
693
        self.startsaving()
694
    def close_ref(self):
Guido van Rossum's avatar
Guido van Rossum committed
695
        self.makeref()
696 697

    def open_inforef(self):
Guido van Rossum's avatar
Guido van Rossum committed
698 699
        self.write('See info file ')
        self.startsaving()
700
    def close_inforef(self):
Guido van Rossum's avatar
Guido van Rossum committed
701
        text = self.collectsavings()
702
        args = [s.strip() for s in text.split(',')]
Guido van Rossum's avatar
Guido van Rossum committed
703 704 705 706
        while len(args) < 3: args.append('')
        node = args[0]
        file = args[2]
        self.write('`', file, '\', node `', node, '\'')
707 708

    def makeref(self):
Guido van Rossum's avatar
Guido van Rossum committed
709
        text = self.collectsavings()
710
        args = [s.strip() for s in text.split(',')]
Guido van Rossum's avatar
Guido van Rossum committed
711 712 713 714 715 716 717 718 719
        while len(args) < 5: args.append('')
        nodename = label = args[0]
        if args[2]: label = args[2]
        file = args[3]
        title = args[4]
        href = makefile(nodename)
        if file:
            href = '../' + file + '/' + href
        self.write('<A HREF="', href, '">', label, '</A>')
720

721 722 723 724 725
    # rpyron 2002-05-07  uref support
    def open_uref(self):
        self.startsaving()
    def close_uref(self):
        text = self.collectsavings()
726
        args = [s.strip() for s in text.split(',')]
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
        while len(args) < 2: args.append('')
        href = args[0]
        label = args[1]
        if not label: label = href
        self.write('<A HREF="', href, '">', label, '</A>')

    # rpyron 2002-05-07  image support
    # GNU makeinfo producing HTML output tries `filename.png'; if
    # that does not exist, it tries `filename.jpg'. If that does
    # not exist either, it complains. GNU makeinfo does not handle
    # GIF files; however, I include GIF support here because
    # MySQL documentation uses GIF files.

    def open_image(self):
        self.startsaving()
    def close_image(self):
        self.makeimage()
    def makeimage(self):
        text = self.collectsavings()
746
        args = [s.strip() for s in text.split(',')]
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
        while len(args) < 5: args.append('')
        filename = args[0]
        width    = args[1]
        height   = args[2]
        alt      = args[3]
        ext      = args[4]

        # The HTML output will have a reference to the image
        # that is relative to the HTML output directory,
        # which is what 'filename' gives us. However, we need
        # to find it relative to our own current directory,
        # so we construct 'imagename'.
        imagelocation = self.dirname + '/' + filename

        if   os.path.exists(imagelocation+'.png'):
            filename += '.png'
        elif os.path.exists(imagelocation+'.jpg'):
            filename += '.jpg'
        elif os.path.exists(imagelocation+'.gif'):   # MySQL uses GIF files
            filename += '.gif'
        else:
768
            print("*** Cannot find image " + imagelocation)
769 770 771 772 773 774 775 776 777
        #TODO: what is 'ext'?
        self.write('<IMG SRC="', filename, '"',                     \
                    width  and (' WIDTH="'  + width  + '"') or "",  \
                    height and (' HEIGHT="' + height + '"') or "",  \
                    alt    and (' ALT="'    + alt    + '"') or "",  \
                    '/>' )
        self.htmlhelp.addimage(imagelocation)


778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
    # --- Marking Words and Phrases ---

    # --- Other @xxx{...} commands ---

    def open_(self): pass # Used by {text enclosed in braces}
    def close_(self): pass

    open_asis = open_
    close_asis = close_

    def open_cite(self): self.write('<CITE>')
    def close_cite(self): self.write('</CITE>')

    def open_code(self): self.write('<CODE>')
    def close_code(self): self.write('</CODE>')

794 795
    def open_t(self): self.write('<TT>')
    def close_t(self): self.write('</TT>')
796 797 798 799 800 801 802

    def open_dfn(self): self.write('<DFN>')
    def close_dfn(self): self.write('</DFN>')

    def open_emph(self): self.write('<EM>')
    def close_emph(self): self.write('</EM>')

803 804
    def open_i(self): self.write('<I>')
    def close_i(self): self.write('</I>')
805 806

    def open_footnote(self):
807
        # if self.savetext is not None:
Guido van Rossum's avatar
Guido van Rossum committed
808 809
        #       print '*** Recursive footnote -- expect weirdness'
        id = len(self.footnotes) + 1
810
        self.write(self.FN_SOURCE_PATTERN % {'id': repr(id)})
Guido van Rossum's avatar
Guido van Rossum committed
811
        self.startsaving()
812 813

    def close_footnote(self):
Guido van Rossum's avatar
Guido van Rossum committed
814
        id = len(self.footnotes) + 1
815
        self.footnotes.append((id, self.collectsavings()))
816 817

    def writefootnotes(self):
Guido van Rossum's avatar
Guido van Rossum committed
818 819 820
        self.write(self.FN_HEADER)
        for id, text in self.footnotes:
            self.write(self.FN_TARGET_PATTERN
821
                       % {'id': repr(id), 'text': text})
Guido van Rossum's avatar
Guido van Rossum committed
822
        self.footnotes = []
823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841

    def open_file(self): self.write('<CODE>')
    def close_file(self): self.write('</CODE>')

    def open_kbd(self): self.write('<KBD>')
    def close_kbd(self): self.write('</KBD>')

    def open_key(self): self.write('<KEY>')
    def close_key(self): self.write('</KEY>')

    def open_r(self): self.write('<R>')
    def close_r(self): self.write('</R>')

    def open_samp(self): self.write('`<SAMP>')
    def close_samp(self): self.write('</SAMP>\'')

    def open_sc(self): self.write('<SMALLCAPS>')
    def close_sc(self): self.write('</SMALLCAPS>')

842 843
    def open_strong(self): self.write('<STRONG>')
    def close_strong(self): self.write('</STRONG>')
844

845 846
    def open_b(self): self.write('<B>')
    def close_b(self): self.write('</B>')
847 848 849 850 851 852 853

    def open_var(self): self.write('<VAR>')
    def close_var(self): self.write('</VAR>')

    def open_w(self): self.write('<NOBREAK>')
    def close_w(self): self.write('</NOBREAK>')

854 855 856 857 858 859 860 861 862 863
    def open_url(self): self.startsaving()
    def close_url(self):
        text = self.collectsavings()
        self.write('<A HREF="', text, '">', text, '</A>')

    def open_email(self): self.startsaving()
    def close_email(self):
        text = self.collectsavings()
        self.write('<A HREF="mailto:', text, '">', text, '</A>')

864 865 866 867 868 869
    open_titlefont = open_
    close_titlefont = close_

    def open_small(self): pass
    def close_small(self): pass

870 871
    def command(self, line, mo):
        a, b = mo.span(1)
Guido van Rossum's avatar
Guido van Rossum committed
872
        cmd = line[a:b]
873
        args = line[b:].strip()
Guido van Rossum's avatar
Guido van Rossum committed
874
        if self.debugging > 1:
875 876
            print('!'*self.debugging, 'command:', self.skip, self.stack, \
                  '@' + cmd, args)
Guido van Rossum's avatar
Guido van Rossum committed
877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
        try:
            func = getattr(self, 'do_' + cmd)
        except AttributeError:
            try:
                func = getattr(self, 'bgn_' + cmd)
            except AttributeError:
                # don't complain if we are skipping anyway
                if not self.skip:
                    self.unknown_cmd(cmd, args)
                return
            self.stack.append(cmd)
            func(args)
            return
        if not self.skip or cmd == 'end':
            func(args)
892 893

    def unknown_cmd(self, cmd, args):
894
        print('*** unknown', '@' + cmd, args)
895
        if cmd not in self.unknown:
Guido van Rossum's avatar
Guido van Rossum committed
896 897 898
            self.unknown[cmd] = 1
        else:
            self.unknown[cmd] = self.unknown[cmd] + 1
Guido van Rossum's avatar
Guido van Rossum committed
899

900
    def do_end(self, args):
901
        words = args.split()
Guido van Rossum's avatar
Guido van Rossum committed
902
        if not words:
903
            print('*** @end w/o args')
Guido van Rossum's avatar
Guido van Rossum committed
904 905
        else:
            cmd = words[0]
906
            if not self.stack or self.stack[-1] != cmd:
907
                print('*** @end', cmd, 'unexpected')
Guido van Rossum's avatar
Guido van Rossum committed
908 909 910 911 912 913 914 915
            else:
                del self.stack[-1]
            try:
                func = getattr(self, 'end_' + cmd)
            except AttributeError:
                self.unknown_end(cmd)
                return
            func()
916 917

    def unknown_end(self, cmd):
Guido van Rossum's avatar
Guido van Rossum committed
918
        cmd = 'end ' + cmd
919
        print('*** unknown', '@' + cmd)
920
        if cmd not in self.unknown:
Guido van Rossum's avatar
Guido van Rossum committed
921 922 923
            self.unknown[cmd] = 1
        else:
            self.unknown[cmd] = self.unknown[cmd] + 1
Guido van Rossum's avatar
Guido van Rossum committed
924

925
    # --- Comments ---
Guido van Rossum's avatar
Guido van Rossum committed
926

927 928
    def do_comment(self, args): pass
    do_c = do_comment
Guido van Rossum's avatar
Guido van Rossum committed
929

930
    # --- Conditional processing ---
Guido van Rossum's avatar
Guido van Rossum committed
931

932 933
    def bgn_ifinfo(self, args): pass
    def end_ifinfo(self): pass
Guido van Rossum's avatar
Guido van Rossum committed
934

935 936
    def bgn_iftex(self, args): self.skip = self.skip + 1
    def end_iftex(self): self.skip = self.skip - 1
Guido van Rossum's avatar
Guido van Rossum committed
937

938 939
    def bgn_ignore(self, args): self.skip = self.skip + 1
    def end_ignore(self): self.skip = self.skip - 1
Guido van Rossum's avatar
Guido van Rossum committed
940

941 942
    def bgn_tex(self, args): self.skip = self.skip + 1
    def end_tex(self): self.skip = self.skip - 1
Guido van Rossum's avatar
Guido van Rossum committed
943

944
    def do_set(self, args):
945
        fields = args.split(' ')
Guido van Rossum's avatar
Guido van Rossum committed
946 947 948 949
        key = fields[0]
        if len(fields) == 1:
            value = 1
        else:
950
            value = ' '.join(fields[1:])
Guido van Rossum's avatar
Guido van Rossum committed
951
        self.values[key] = value
952 953

    def do_clear(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
954
        self.values[args] = None
955 956

    def bgn_ifset(self, args):
957
        if args not in self.values or self.values[args] is None:
Guido van Rossum's avatar
Guido van Rossum committed
958 959 960 961
            self.skip = self.skip + 1
            self.stackinfo[len(self.stack)] = 1
        else:
            self.stackinfo[len(self.stack)] = 0
962
    def end_ifset(self):
963 964 965 966 967
        try:
            if self.stackinfo[len(self.stack) + 1]:
                self.skip = self.skip - 1
            del self.stackinfo[len(self.stack) + 1]
        except KeyError:
968
            print('*** end_ifset: KeyError :', len(self.stack) + 1)
969 970

    def bgn_ifclear(self, args):
971
        if args in self.values and self.values[args] is not None:
Guido van Rossum's avatar
Guido van Rossum committed
972 973 974 975
            self.skip = self.skip + 1
            self.stackinfo[len(self.stack)] = 1
        else:
            self.stackinfo[len(self.stack)] = 0
976 977 978 979 980 981
    def end_ifclear(self):
        try:
            if self.stackinfo[len(self.stack) + 1]:
                self.skip = self.skip - 1
            del self.stackinfo[len(self.stack) + 1]
        except KeyError:
982
            print('*** end_ifclear: KeyError :', len(self.stack) + 1)
Guido van Rossum's avatar
Guido van Rossum committed
983

984
    def open_value(self):
Guido van Rossum's avatar
Guido van Rossum committed
985
        self.startsaving()
986

987
    def close_value(self):
Guido van Rossum's avatar
Guido van Rossum committed
988
        key = self.collectsavings()
989
        if key in self.values:
Guido van Rossum's avatar
Guido van Rossum committed
990 991
            self.write(self.values[key])
        else:
992
            print('*** Undefined value: ', key)
993 994 995 996 997 998 999 1000

    # --- Beginning a file ---

    do_finalout = do_comment
    do_setchapternewpage = do_comment
    do_setfilename = do_comment

    def do_settitle(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1001 1002 1003
        self.startsaving()
        self.expand(args)
        self.title = self.collectsavings()
1004 1005 1006 1007 1008
    def do_parskip(self, args): pass

    # --- Ending a file ---

    def do_bye(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1009 1010
        self.endnode()
        self.done = 1
1011 1012 1013 1014 1015 1016 1017 1018

    # --- Title page ---

    def bgn_titlepage(self, args): self.skip = self.skip + 1
    def end_titlepage(self): self.skip = self.skip - 1
    def do_shorttitlepage(self, args): pass

    def do_center(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1019 1020 1021 1022
        # Actually not used outside title page...
        self.write('<H1>')
        self.expand(args)
        self.write('</H1>\n')
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
    do_title = do_center
    do_subtitle = do_center
    do_author = do_center

    do_vskip = do_comment
    do_vfill = do_comment
    do_smallbook = do_comment

    do_paragraphindent = do_comment
    do_setchapternewpage = do_comment
    do_headings = do_comment
    do_footnotestyle = do_comment

    do_evenheading = do_comment
    do_evenfooting = do_comment
    do_oddheading = do_comment
    do_oddfooting = do_comment
    do_everyheading = do_comment
    do_everyfooting = do_comment

    # --- Nodes ---

    def do_node(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1046 1047
        self.endnode()
        self.nodelineno = 0
1048
        parts = [s.strip() for s in args.split(',')]
Guido van Rossum's avatar
Guido van Rossum committed
1049 1050 1051 1052
        while len(parts) < 4: parts.append('')
        self.nodelinks = parts
        [name, next, prev, up] = parts[:4]
        file = self.dirname + '/' + makefile(name)
1053
        if file in self.filenames:
1054
            print('*** Filename already in use: ', file)
Guido van Rossum's avatar
Guido van Rossum committed
1055
        else:
1056
            if self.debugging: print('!'*self.debugging, '--- writing', file)
Guido van Rossum's avatar
Guido van Rossum committed
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
        self.filenames[file] = 1
        # self.nodefp = open(file, 'w')
        self.nodename = name
        if self.cont and self.nodestack:
            self.nodestack[-1].cont = self.nodename
        if not self.topname: self.topname = name
        title = name
        if self.title: title = title + ' -- ' + self.title
        self.node = self.Node(self.dirname, self.nodename, self.topname,
                              title, next, prev, up)
1067
        self.htmlhelp.addnode(self.nodename,next,prev,up,file)
1068

1069
    def link(self, label, nodename):
Guido van Rossum's avatar
Guido van Rossum committed
1070
        if nodename:
1071
            if nodename.lower() == '(dir)':
Guido van Rossum's avatar
Guido van Rossum committed
1072 1073 1074 1075 1076
                addr = '../dir.html'
            else:
                addr = makefile(nodename)
            self.write(label, ': <A HREF="', addr, '" TYPE="',
                       label, '">', nodename, '</A>  \n')
1077 1078 1079

    # --- Sectioning commands ---

1080
    def popstack(self, type):
Guido van Rossum's avatar
Guido van Rossum committed
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
        if (self.node):
            self.node.type = type
            while self.nodestack:
                if self.nodestack[-1].type > type:
                    self.nodestack[-1].finalize()
                    self.nodestack[-1].flush()
                    del self.nodestack[-1]
                elif self.nodestack[-1].type == type:
                    if not self.nodestack[-1].next:
                        self.nodestack[-1].next = self.node.name
                    if not self.node.prev:
                        self.node.prev = self.nodestack[-1].name
                    self.nodestack[-1].finalize()
                    self.nodestack[-1].flush()
                    del self.nodestack[-1]
                else:
                    if type > 1 and not self.node.up:
                        self.node.up = self.nodestack[-1].name
                    break
1100 1101

    def do_chapter(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1102 1103
        self.heading('H1', args, 0)
        self.popstack(1)
1104 1105

    def do_unnumbered(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1106 1107
        self.heading('H1', args, -1)
        self.popstack(1)
1108
    def do_appendix(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1109 1110
        self.heading('H1', args, -1)
        self.popstack(1)
1111
    def do_top(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1112
        self.heading('H1', args, -1)
1113
    def do_chapheading(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1114
        self.heading('H1', args, -1)
1115
    def do_majorheading(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1116
        self.heading('H1', args, -1)
1117 1118

    def do_section(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1119 1120
        self.heading('H1', args, 1)
        self.popstack(2)
1121 1122

    def do_unnumberedsec(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1123 1124
        self.heading('H1', args, -1)
        self.popstack(2)
1125
    def do_appendixsec(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1126 1127
        self.heading('H1', args, -1)
        self.popstack(2)
1128 1129
    do_appendixsection = do_appendixsec
    def do_heading(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1130
        self.heading('H1', args, -1)
1131 1132

    def do_subsection(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1133 1134
        self.heading('H2', args, 2)
        self.popstack(3)
1135
    def do_unnumberedsubsec(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1136 1137
        self.heading('H2', args, -1)
        self.popstack(3)
1138
    def do_appendixsubsec(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1139 1140
        self.heading('H2', args, -1)
        self.popstack(3)
1141
    def do_subheading(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1142
        self.heading('H2', args, -1)
1143 1144

    def do_subsubsection(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1145 1146
        self.heading('H3', args, 3)
        self.popstack(4)
1147
    def do_unnumberedsubsubsec(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1148 1149
        self.heading('H3', args, -1)
        self.popstack(4)
1150
    def do_appendixsubsubsec(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1151 1152
        self.heading('H3', args, -1)
        self.popstack(4)
1153
    def do_subsubheading(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1154
        self.heading('H3', args, -1)
1155 1156

    def heading(self, type, args, level):
Guido van Rossum's avatar
Guido van Rossum committed
1157 1158 1159 1160 1161 1162 1163
        if level >= 0:
            while len(self.numbering) <= level:
                self.numbering.append(0)
            del self.numbering[level+1:]
            self.numbering[level] = self.numbering[level] + 1
            x = ''
            for i in self.numbering:
1164
                x = x + repr(i) + '.'
Guido van Rossum's avatar
Guido van Rossum committed
1165
            args = x + ' ' + args
1166
            self.contents.append((level, args, self.nodename))
Guido van Rossum's avatar
Guido van Rossum committed
1167 1168 1169 1170
        self.write('<', type, '>')
        self.expand(args)
        self.write('</', type, '>\n')
        if self.debugging or self.print_headers:
1171
            print('---', args)
1172 1173

    def do_contents(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1174 1175
        # pass
        self.listcontents('Table of Contents', 999)
1176 1177

    def do_shortcontents(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1178 1179
        pass
        # self.listcontents('Short Contents', 0)
1180 1181 1182
    do_summarycontents = do_shortcontents

    def listcontents(self, title, maxlevel):
Guido van Rossum's avatar
Guido van Rossum committed
1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
        self.write('<H1>', title, '</H1>\n<UL COMPACT PLAIN>\n')
        prevlevels = [0]
        for level, title, node in self.contents:
            if level > maxlevel:
                continue
            if level > prevlevels[-1]:
                # can only advance one level at a time
                self.write('  '*prevlevels[-1], '<UL PLAIN>\n')
                prevlevels.append(level)
            elif level < prevlevels[-1]:
                # might drop back multiple levels
                while level < prevlevels[-1]:
                    del prevlevels[-1]
                    self.write('  '*prevlevels[-1],
                               '</UL>\n')
            self.write('  '*level, '<LI> <A HREF="',
                       makefile(node), '">')
            self.expand(title)
            self.write('</A>\n')
        self.write('</UL>\n' * len(prevlevels))
1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217

    # --- Page lay-out ---

    # These commands are only meaningful in printed text

    def do_page(self, args): pass

    def do_need(self, args): pass

    def bgn_group(self, args): pass
    def end_group(self): pass

    # --- Line lay-out ---

    def do_sp(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1218 1219 1220 1221
        if self.nofill:
            self.write('\n')
        else:
            self.write('<P>\n')
1222 1223

    def do_hline(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1224
        self.write('<HR>')
1225 1226 1227 1228

    # --- Function and variable definitions ---

    def bgn_deffn(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1229 1230
        self.write('<DL>')
        self.do_deffnx(args)
1231 1232

    def end_deffn(self):
Guido van Rossum's avatar
Guido van Rossum committed
1233
        self.write('</DL>\n')
1234 1235

    def do_deffnx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1236 1237 1238 1239 1240 1241 1242 1243
        self.write('<DT>')
        words = splitwords(args, 2)
        [category, name], rest = words[:2], words[2:]
        self.expand('@b{%s}' % name)
        for word in rest: self.expand(' ' + makevar(word))
        #self.expand(' -- ' + category)
        self.write('\n<DD>')
        self.index('fn', name)
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257

    def bgn_defun(self, args): self.bgn_deffn('Function ' + args)
    end_defun = end_deffn
    def do_defunx(self, args): self.do_deffnx('Function ' + args)

    def bgn_defmac(self, args): self.bgn_deffn('Macro ' + args)
    end_defmac = end_deffn
    def do_defmacx(self, args): self.do_deffnx('Macro ' + args)

    def bgn_defspec(self, args): self.bgn_deffn('{Special Form} ' + args)
    end_defspec = end_deffn
    def do_defspecx(self, args): self.do_deffnx('{Special Form} ' + args)

    def bgn_defvr(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1258 1259
        self.write('<DL>')
        self.do_defvrx(args)
1260 1261 1262 1263

    end_defvr = end_deffn

    def do_defvrx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1264 1265 1266 1267 1268 1269 1270 1271 1272
        self.write('<DT>')
        words = splitwords(args, 2)
        [category, name], rest = words[:2], words[2:]
        self.expand('@code{%s}' % name)
        # If there are too many arguments, show them
        for word in rest: self.expand(' ' + word)
        #self.expand(' -- ' + category)
        self.write('\n<DD>')
        self.index('vr', name)
1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284

    def bgn_defvar(self, args): self.bgn_defvr('Variable ' + args)
    end_defvar = end_defvr
    def do_defvarx(self, args): self.do_defvrx('Variable ' + args)

    def bgn_defopt(self, args): self.bgn_defvr('{User Option} ' + args)
    end_defopt = end_defvr
    def do_defoptx(self, args): self.do_defvrx('{User Option} ' + args)

    # --- Ditto for typed languages ---

    def bgn_deftypefn(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1285 1286
        self.write('<DL>')
        self.do_deftypefnx(args)
1287 1288 1289 1290

    end_deftypefn = end_deffn

    def do_deftypefnx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1291 1292 1293 1294 1295 1296 1297 1298
        self.write('<DT>')
        words = splitwords(args, 3)
        [category, datatype, name], rest = words[:3], words[3:]
        self.expand('@code{%s} @b{%s}' % (datatype, name))
        for word in rest: self.expand(' ' + makevar(word))
        #self.expand(' -- ' + category)
        self.write('\n<DD>')
        self.index('fn', name)
1299 1300 1301 1302 1303 1304 1305


    def bgn_deftypefun(self, args): self.bgn_deftypefn('Function ' + args)
    end_deftypefun = end_deftypefn
    def do_deftypefunx(self, args): self.do_deftypefnx('Function ' + args)

    def bgn_deftypevr(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1306 1307
        self.write('<DL>')
        self.do_deftypevrx(args)
1308 1309 1310 1311

    end_deftypevr = end_deftypefn

    def do_deftypevrx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1312 1313 1314 1315 1316 1317 1318 1319 1320
        self.write('<DT>')
        words = splitwords(args, 3)
        [category, datatype, name], rest = words[:3], words[3:]
        self.expand('@code{%s} @b{%s}' % (datatype, name))
        # If there are too many arguments, show them
        for word in rest: self.expand(' ' + word)
        #self.expand(' -- ' + category)
        self.write('\n<DD>')
        self.index('fn', name)
1321 1322

    def bgn_deftypevar(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1323
        self.bgn_deftypevr('Variable ' + args)
1324 1325
    end_deftypevar = end_deftypevr
    def do_deftypevarx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1326
        self.do_deftypevrx('Variable ' + args)
1327 1328 1329 1330

    # --- Ditto for object-oriented languages ---

    def bgn_defcv(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1331 1332
        self.write('<DL>')
        self.do_defcvx(args)
1333 1334 1335 1336

    end_defcv = end_deftypevr

    def do_defcvx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1337 1338 1339 1340 1341 1342 1343 1344 1345
        self.write('<DT>')
        words = splitwords(args, 3)
        [category, classname, name], rest = words[:3], words[3:]
        self.expand('@b{%s}' % name)
        # If there are too many arguments, show them
        for word in rest: self.expand(' ' + word)
        #self.expand(' -- %s of @code{%s}' % (category, classname))
        self.write('\n<DD>')
        self.index('vr', '%s @r{on %s}' % (name, classname))
1346 1347

    def bgn_defivar(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1348
        self.bgn_defcv('{Instance Variable} ' + args)
1349 1350
    end_defivar = end_defcv
    def do_defivarx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1351
        self.do_defcvx('{Instance Variable} ' + args)
1352 1353

    def bgn_defop(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1354 1355
        self.write('<DL>')
        self.do_defopx(args)
1356 1357 1358 1359

    end_defop = end_defcv

    def do_defopx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1360 1361 1362 1363 1364 1365 1366 1367
        self.write('<DT>')
        words = splitwords(args, 3)
        [category, classname, name], rest = words[:3], words[3:]
        self.expand('@b{%s}' % name)
        for word in rest: self.expand(' ' + makevar(word))
        #self.expand(' -- %s of @code{%s}' % (category, classname))
        self.write('\n<DD>')
        self.index('fn', '%s @r{on %s}' % (name, classname))
1368 1369

    def bgn_defmethod(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1370
        self.bgn_defop('Method ' + args)
1371 1372
    end_defmethod = end_defop
    def do_defmethodx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1373
        self.do_defopx('Method ' + args)
1374 1375 1376 1377

    # --- Ditto for data types ---

    def bgn_deftp(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1378 1379
        self.write('<DL>')
        self.do_deftpx(args)
1380 1381 1382 1383

    end_deftp = end_defcv

    def do_deftpx(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1384 1385 1386 1387 1388 1389 1390 1391
        self.write('<DT>')
        words = splitwords(args, 2)
        [category, name], rest = words[:2], words[2:]
        self.expand('@b{%s}' % name)
        for word in rest: self.expand(' ' + word)
        #self.expand(' -- ' + category)
        self.write('\n<DD>')
        self.index('tp', name)
1392 1393 1394 1395

    # --- Making Lists and Tables

    def bgn_enumerate(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1396 1397 1398 1399 1400 1401 1402
        if not args:
            self.write('<OL>\n')
            self.stackinfo[len(self.stack)] = '</OL>\n'
        else:
            self.itemnumber = args
            self.write('<UL>\n')
            self.stackinfo[len(self.stack)] = '</UL>\n'
1403
    def end_enumerate(self):
Guido van Rossum's avatar
Guido van Rossum committed
1404 1405 1406
        self.itemnumber = None
        self.write(self.stackinfo[len(self.stack) + 1])
        del self.stackinfo[len(self.stack) + 1]
1407 1408

    def bgn_itemize(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1409 1410
        self.itemarg = args
        self.write('<UL>\n')
1411
    def end_itemize(self):
Guido van Rossum's avatar
Guido van Rossum committed
1412 1413
        self.itemarg = None
        self.write('</UL>\n')
1414 1415

    def bgn_table(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1416 1417
        self.itemarg = args
        self.write('<DL>\n')
1418
    def end_table(self):
Guido van Rossum's avatar
Guido van Rossum committed
1419 1420
        self.itemarg = None
        self.write('</DL>\n')
1421 1422

    def bgn_ftable(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1423 1424
        self.itemindex = 'fn'
        self.bgn_table(args)
1425
    def end_ftable(self):
Guido van Rossum's avatar
Guido van Rossum committed
1426 1427
        self.itemindex = None
        self.end_table()
1428

1429 1430 1431 1432 1433 1434 1435
    def bgn_vtable(self, args):
        self.itemindex = 'vr'
        self.bgn_table(args)
    def end_vtable(self):
        self.itemindex = None
        self.end_table()

1436
    def do_item(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1437 1438
        if self.itemindex: self.index(self.itemindex, args)
        if self.itemarg:
1439
            if self.itemarg[0] == '@' and self.itemarg[1] and \
1440
                            self.itemarg[1] in string.ascii_letters:
Guido van Rossum's avatar
Guido van Rossum committed
1441 1442 1443 1444
                args = self.itemarg + '{' + args + '}'
            else:
                # some other character, e.g. '-'
                args = self.itemarg + ' ' + args
1445
        if self.itemnumber is not None:
Guido van Rossum's avatar
Guido van Rossum committed
1446 1447 1448 1449 1450 1451
            args = self.itemnumber + '. ' + args
            self.itemnumber = increment(self.itemnumber)
        if self.stack and self.stack[-1] == 'table':
            self.write('<DT>')
            self.expand(args)
            self.write('\n<DD>')
1452 1453 1454 1455
        elif self.stack and self.stack[-1] == 'multitable':
            self.write('<TR><TD>')
            self.expand(args)
            self.write('</TD>\n</TR>\n')
Guido van Rossum's avatar
Guido van Rossum committed
1456 1457 1458 1459
        else:
            self.write('<LI>')
            self.expand(args)
            self.write('  ')
1460 1461
    do_itemx = do_item # XXX Should suppress leading blank line

1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474
    # rpyron 2002-05-07  multitable support
    def bgn_multitable(self, args):
        self.itemarg = None     # should be handled by columnfractions
        self.write('<TABLE BORDER="">\n')
    def end_multitable(self):
        self.itemarg = None
        self.write('</TABLE>\n<BR>\n')
    def handle_columnfractions(self):
        # It would be better to handle this, but for now it's in the way...
        self.itemarg = None
    def handle_tab(self):
        self.write('</TD>\n    <TD>')

1475 1476 1477 1478 1479 1480 1481
    # --- Enumerations, displays, quotations ---
    # XXX Most of these should increase the indentation somehow

    def bgn_quotation(self, args): self.write('<BLOCKQUOTE>')
    def end_quotation(self): self.write('</BLOCKQUOTE>\n')

    def bgn_example(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1482 1483
        self.nofill = self.nofill + 1
        self.write('<PRE>')
1484
    def end_example(self):
Guido van Rossum's avatar
Guido van Rossum committed
1485 1486
        self.write('</PRE>\n')
        self.nofill = self.nofill - 1
1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506

    bgn_lisp = bgn_example # Synonym when contents are executable lisp code
    end_lisp = end_example

    bgn_smallexample = bgn_example # XXX Should use smaller font
    end_smallexample = end_example

    bgn_smalllisp = bgn_lisp # Ditto
    end_smalllisp = end_lisp

    bgn_display = bgn_example
    end_display = end_example

    bgn_format = bgn_display
    end_format = end_display

    def do_exdent(self, args): self.expand(args + '\n')
    # XXX Should really mess with indentation

    def bgn_flushleft(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1507 1508
        self.nofill = self.nofill + 1
        self.write('<PRE>\n')
1509
    def end_flushleft(self):
Guido van Rossum's avatar
Guido van Rossum committed
1510 1511
        self.write('</PRE>\n')
        self.nofill = self.nofill - 1
1512 1513

    def bgn_flushright(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1514 1515
        self.nofill = self.nofill + 1
        self.write('<ADDRESS COMPACT>\n')
1516
    def end_flushright(self):
Guido van Rossum's avatar
Guido van Rossum committed
1517 1518
        self.write('</ADDRESS>\n')
        self.nofill = self.nofill - 1
1519 1520

    def bgn_menu(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1521 1522
        self.write('<DIR>\n')
        self.write('  <STRONG><EM>Menu</EM></STRONG><P>\n')
1523
        self.htmlhelp.beginmenu()
1524
    def end_menu(self):
Guido van Rossum's avatar
Guido van Rossum committed
1525
        self.write('</DIR>\n')
1526
        self.htmlhelp.endmenu()
1527 1528 1529 1530 1531 1532 1533

    def bgn_cartouche(self, args): pass
    def end_cartouche(self): pass

    # --- Indices ---

    def resetindex(self):
Guido van Rossum's avatar
Guido van Rossum committed
1534 1535 1536 1537 1538 1539 1540 1541 1542 1543
        self.noncodeindices = ['cp']
        self.indextitle = {}
        self.indextitle['cp'] = 'Concept'
        self.indextitle['fn'] = 'Function'
        self.indextitle['ky'] = 'Keyword'
        self.indextitle['pg'] = 'Program'
        self.indextitle['tp'] = 'Type'
        self.indextitle['vr'] = 'Variable'
        #
        self.whichindex = {}
1544
        for name in self.indextitle:
Guido van Rossum's avatar
Guido van Rossum committed
1545
            self.whichindex[name] = []
1546 1547

    def user_index(self, name, args):
1548
        if name in self.whichindex:
Guido van Rossum's avatar
Guido van Rossum committed
1549 1550
            self.index(name, args)
        else:
1551
            print('*** No index named', repr(name))
1552 1553 1554 1555 1556 1557 1558 1559 1560

    def do_cindex(self, args): self.index('cp', args)
    def do_findex(self, args): self.index('fn', args)
    def do_kindex(self, args): self.index('ky', args)
    def do_pindex(self, args): self.index('pg', args)
    def do_tindex(self, args): self.index('tp', args)
    def do_vindex(self, args): self.index('vr', args)

    def index(self, name, args):
1561
        self.whichindex[name].append((args, self.nodename))
1562
        self.htmlhelp.index(args, self.nodename)
1563 1564

    def do_synindex(self, args):
1565
        words = args.split()
1566
        if len(words) != 2:
1567
            print('*** bad @synindex', args)
Guido van Rossum's avatar
Guido van Rossum committed
1568 1569
            return
        [old, new] = words
1570 1571
        if old not in self.whichindex or \
                  new not in self.whichindex:
1572
            print('*** bad key(s) in @synindex', args)
Guido van Rossum's avatar
Guido van Rossum committed
1573
            return
1574
        if old != new and \
Guido van Rossum's avatar
Guido van Rossum committed
1575 1576 1577 1578
                  self.whichindex[old] is not self.whichindex[new]:
            inew = self.whichindex[new]
            inew[len(inew):] = self.whichindex[old]
            self.whichindex[old] = inew
1579 1580 1581
    do_syncodeindex = do_synindex # XXX Should use code font

    def do_printindex(self, args):
1582
        words = args.split()
Guido van Rossum's avatar
Guido van Rossum committed
1583
        for name in words:
1584
            if name in self.whichindex:
Guido van Rossum's avatar
Guido van Rossum committed
1585 1586
                self.prindex(name)
            else:
1587
                print('*** No index named', repr(name))
1588 1589

    def prindex(self, name):
Guido van Rossum's avatar
Guido van Rossum committed
1590 1591 1592 1593
        iscodeindex = (name not in self.noncodeindices)
        index = self.whichindex[name]
        if not index: return
        if self.debugging:
1594 1595
            print('!'*self.debugging, '--- Generating', \
                  self.indextitle[name], 'index')
Guido van Rossum's avatar
Guido van Rossum committed
1596 1597
        #  The node already provides a title
        index1 = []
1598
        junkprog = re.compile('^(@[a-z]+)?{')
Guido van Rossum's avatar
Guido van Rossum committed
1599
        for key, node in index:
1600
            sortkey = key.lower()
Guido van Rossum's avatar
Guido van Rossum committed
1601 1602 1603 1604
            # Remove leading `@cmd{' from sort key
            # -- don't bother about the matching `}'
            oldsortkey = sortkey
            while 1:
1605 1606 1607 1608
                mo = junkprog.match(sortkey)
                if not mo:
                    break
                i = mo.end()
Guido van Rossum's avatar
Guido van Rossum committed
1609
                sortkey = sortkey[i:]
1610
            index1.append((sortkey, key, node))
Guido van Rossum's avatar
Guido van Rossum committed
1611 1612 1613 1614 1615 1616 1617
        del index[:]
        index1.sort()
        self.write('<DL COMPACT>\n')
        prevkey = prevnode = None
        for sortkey, key, node in index1:
            if (key, node) == (prevkey, prevnode):
                continue
1618
            if self.debugging > 1: print('!'*self.debugging, key, ':', node)
Guido van Rossum's avatar
Guido van Rossum committed
1619 1620 1621 1622 1623 1624 1625
            self.write('<DT>')
            if iscodeindex: key = '@code{' + key + '}'
            if key != prevkey:
                self.expand(key)
            self.write('\n<DD><A HREF="%s">%s</A>\n' % (makefile(node), node))
            prevkey, prevnode = key, node
        self.write('</DL>\n')
1626 1627 1628 1629

    # --- Final error reports ---

    def report(self):
Guido van Rossum's avatar
Guido van Rossum committed
1630
        if self.unknown:
1631
            print('--- Unrecognized commands ---')
1632
            cmds = sorted(self.unknown.keys())
Guido van Rossum's avatar
Guido van Rossum committed
1633
            for cmd in cmds:
1634
                print(cmd.ljust(20), self.unknown[cmd])
Guido van Rossum's avatar
Guido van Rossum committed
1635 1636


1637 1638 1639 1640 1641
class TexinfoParserHTML3(TexinfoParser):

    COPYRIGHT_SYMBOL = "&copy;"
    FN_ID_PATTERN = "[%(id)s]"
    FN_SOURCE_PATTERN = '<A ID=footnoteref%(id)s ' \
Guido van Rossum's avatar
Guido van Rossum committed
1642
                        'HREF="#footnotetext%(id)s">' + FN_ID_PATTERN + '</A>'
1643
    FN_TARGET_PATTERN = '<FN ID=footnotetext%(id)s>\n' \
Guido van Rossum's avatar
Guido van Rossum committed
1644 1645
                        '<P><A HREF="#footnoteref%(id)s">' + FN_ID_PATTERN \
                        + '</A>\n%(text)s</P></FN>\n'
1646
    FN_HEADER = '<DIV CLASS=footnotes>\n  <HR NOSHADE WIDTH=200>\n' \
Guido van Rossum's avatar
Guido van Rossum committed
1647
                '  <STRONG><EM>Footnotes</EM></STRONG>\n  <P>\n'
1648 1649 1650 1651 1652 1653 1654

    Node = HTML3Node

    def bgn_quotation(self, args): self.write('<BQ>')
    def end_quotation(self): self.write('</BQ>\n')

    def bgn_example(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1655 1656 1657 1658
        # this use of <CODE> would not be legal in HTML 2.0,
        # but is in more recent DTDs.
        self.nofill = self.nofill + 1
        self.write('<PRE CLASS=example><CODE>')
1659
    def end_example(self):
Guido van Rossum's avatar
Guido van Rossum committed
1660 1661
        self.write("</CODE></PRE>\n")
        self.nofill = self.nofill - 1
1662 1663

    def bgn_flushleft(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1664 1665
        self.nofill = self.nofill + 1
        self.write('<PRE CLASS=flushleft>\n')
1666 1667

    def bgn_flushright(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1668 1669
        self.nofill = self.nofill + 1
        self.write('<DIV ALIGN=right CLASS=flushright><ADDRESS COMPACT>\n')
1670
    def end_flushright(self):
Guido van Rossum's avatar
Guido van Rossum committed
1671 1672
        self.write('</ADDRESS></DIV>\n')
        self.nofill = self.nofill - 1
1673 1674

    def bgn_menu(self, args):
Guido van Rossum's avatar
Guido van Rossum committed
1675 1676
        self.write('<UL PLAIN CLASS=menu>\n')
        self.write('  <LH>Menu</LH>\n')
1677
    def end_menu(self):
Guido van Rossum's avatar
Guido van Rossum committed
1678
        self.write('</UL>\n')
1679 1680


1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773
# rpyron 2002-05-07
class HTMLHelp:
    """
    This class encapsulates support for HTML Help. Node names,
    file names, menu items, index items, and image file names are
    accumulated until a call to finalize(). At that time, three
    output files are created in the current directory:

        `helpbase`.hhp  is a HTML Help Workshop project file.
                        It contains various information, some of
                        which I do not understand; I just copied
                        the default project info from a fresh
                        installation.
        `helpbase`.hhc  is the Contents file for the project.
        `helpbase`.hhk  is the Index file for the project.

    When these files are used as input to HTML Help Workshop,
    the resulting file will be named:

        `helpbase`.chm

    If none of the defaults in `helpbase`.hhp are changed,
    the .CHM file will have Contents, Index, Search, and
    Favorites tabs.
    """

    codeprog = re.compile('@code{(.*?)}')

    def __init__(self,helpbase,dirname):
        self.helpbase    = helpbase
        self.dirname     = dirname
        self.projectfile = None
        self.contentfile = None
        self.indexfile   = None
        self.nodelist    = []
        self.nodenames   = {}         # nodename : index
        self.nodeindex   = {}
        self.filenames   = {}         # filename : filename
        self.indexlist   = []         # (args,nodename) == (key,location)
        self.current     = ''
        self.menudict    = {}
        self.dumped      = {}


    def addnode(self,name,next,prev,up,filename):
        node = (name,next,prev,up,filename)
        # add this file to dict
        # retrieve list with self.filenames.values()
        self.filenames[filename] = filename
        # add this node to nodelist
        self.nodeindex[name] = len(self.nodelist)
        self.nodelist.append(node)
        # set 'current' for menu items
        self.current = name
        self.menudict[self.current] = []

    def menuitem(self,nodename):
        menu = self.menudict[self.current]
        menu.append(nodename)


    def addimage(self,imagename):
        self.filenames[imagename] = imagename

    def index(self, args, nodename):
        self.indexlist.append((args,nodename))

    def beginmenu(self):
        pass

    def endmenu(self):
        pass

    def finalize(self):
        if not self.helpbase:
            return

        # generate interesting filenames
        resultfile   = self.helpbase + '.chm'
        projectfile  = self.helpbase + '.hhp'
        contentfile  = self.helpbase + '.hhc'
        indexfile    = self.helpbase + '.hhk'

        # generate a reasonable title
        title        = self.helpbase

        # get the default topic file
        (topname,topnext,topprev,topup,topfile) = self.nodelist[0]
        defaulttopic = topfile

        # PROJECT FILE
        try:
            fp = open(projectfile,'w')
1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790
            print('[OPTIONS]', file=fp)
            print('Auto Index=Yes', file=fp)
            print('Binary TOC=No', file=fp)
            print('Binary Index=Yes', file=fp)
            print('Compatibility=1.1', file=fp)
            print('Compiled file=' + resultfile + '', file=fp)
            print('Contents file=' + contentfile + '', file=fp)
            print('Default topic=' + defaulttopic + '', file=fp)
            print('Error log file=ErrorLog.log', file=fp)
            print('Index file=' + indexfile + '', file=fp)
            print('Title=' + title + '', file=fp)
            print('Display compile progress=Yes', file=fp)
            print('Full-text search=Yes', file=fp)
            print('Default window=main', file=fp)
            print('', file=fp)
            print('[WINDOWS]', file=fp)
            print('main=,"' + contentfile + '","' + indexfile
1791
                        + '","","",,,,,0x23520,222,0x1046,[10,10,780,560],'
1792 1793 1794 1795
                        '0xB0000,,,,,,0', file=fp)
            print('', file=fp)
            print('[FILES]', file=fp)
            print('', file=fp)
1796 1797
            self.dumpfiles(fp)
            fp.close()
1798
        except IOError as msg:
1799
            print(projectfile, ':', msg)
1800 1801 1802 1803 1804
            sys.exit(1)

        # CONTENT FILE
        try:
            fp = open(contentfile,'w')
1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818
            print('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">', file=fp)
            print('<!-- This file defines the table of contents -->', file=fp)
            print('<HTML>', file=fp)
            print('<HEAD>', file=fp)
            print('<meta name="GENERATOR"'
                        'content="Microsoft&reg; HTML Help Workshop 4.1">', file=fp)
            print('<!-- Sitemap 1.0 -->', file=fp)
            print('</HEAD>', file=fp)
            print('<BODY>', file=fp)
            print('   <OBJECT type="text/site properties">', file=fp)
            print('     <param name="Window Styles" value="0x800025">', file=fp)
            print('     <param name="comment" value="title:">', file=fp)
            print('     <param name="comment" value="base:">', file=fp)
            print('   </OBJECT>', file=fp)
1819
            self.dumpnodes(fp)
1820 1821
            print('</BODY>', file=fp)
            print('</HTML>', file=fp)
1822
            fp.close()
1823
        except IOError as msg:
1824
            print(contentfile, ':', msg)
1825 1826 1827 1828 1829
            sys.exit(1)

        # INDEX FILE
        try:
            fp = open(indexfile  ,'w')
1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840
            print('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">', file=fp)
            print('<!-- This file defines the index -->', file=fp)
            print('<HTML>', file=fp)
            print('<HEAD>', file=fp)
            print('<meta name="GENERATOR"'
                        'content="Microsoft&reg; HTML Help Workshop 4.1">', file=fp)
            print('<!-- Sitemap 1.0 -->', file=fp)
            print('</HEAD>', file=fp)
            print('<BODY>', file=fp)
            print('<OBJECT type="text/site properties">', file=fp)
            print('</OBJECT>', file=fp)
1841
            self.dumpindex(fp)
1842 1843
            print('</BODY>', file=fp)
            print('</HTML>', file=fp)
1844
            fp.close()
1845
        except IOError as msg:
1846
            print(indexfile  , ':', msg)
1847 1848 1849
            sys.exit(1)

    def dumpfiles(self, outfile=sys.stdout):
1850
        filelist = sorted(self.filenames.values())
1851
        for filename in filelist:
1852
            print(filename, file=outfile)
1853 1854 1855 1856

    def dumpnodes(self, outfile=sys.stdout):
        self.dumped = {}
        if self.nodelist:
1857
            nodename, dummy, dummy, dummy, dummy = self.nodelist[0]
1858 1859
            self.topnode = nodename

1860
        print('<UL>', file=outfile)
1861 1862
        for node in self.nodelist:
            self.dumpnode(node,0,outfile)
1863
        print('</UL>', file=outfile)
1864 1865 1866 1867 1868 1869 1870 1871

    def dumpnode(self, node, indent=0, outfile=sys.stdout):
        if node:
            # Retrieve info for this node
            (nodename,next,prev,up,filename) = node
            self.current = nodename

            # Have we been dumped already?
1872
            if nodename in self.dumped:
1873 1874 1875 1876
                return
            self.dumped[nodename] = 1

            # Print info for this node
1877 1878 1879 1880 1881
            print(' '*indent, end=' ', file=outfile)
            print('<LI><OBJECT type="text/sitemap">', end=' ', file=outfile)
            print('<param name="Name" value="' + nodename +'">', end=' ', file=outfile)
            print('<param name="Local" value="'+ filename +'">', end=' ', file=outfile)
            print('</OBJECT>', file=outfile)
1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893

            # Does this node have menu items?
            try:
                menu = self.menudict[nodename]
                self.dumpmenu(menu,indent+2,outfile)
            except KeyError:
                pass

    def dumpmenu(self, menu, indent=0, outfile=sys.stdout):
        if menu:
            currentnode = self.current
            if currentnode != self.topnode:    # XXX this is a hack
1894
                print(' '*indent + '<UL>', file=outfile)
1895 1896 1897 1898 1899
                indent += 2
            for item in menu:
                menunode = self.getnode(item)
                self.dumpnode(menunode,indent,outfile)
            if currentnode != self.topnode:    # XXX this is a hack
1900
                print(' '*indent + '</UL>', file=outfile)
1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913
                indent -= 2

    def getnode(self, nodename):
        try:
            index = self.nodeindex[nodename]
            return self.nodelist[index]
        except KeyError:
            return None
        except IndexError:
            return None

    # (args,nodename) == (key,location)
    def dumpindex(self, outfile=sys.stdout):
1914
        print('<UL>', file=outfile)
1915 1916 1917 1918
        for (key,location) in self.indexlist:
            key = self.codeexpand(key)
            location = makefile(location)
            location = self.dirname + '/' + location
1919 1920 1921 1922 1923
            print('<LI><OBJECT type="text/sitemap">', end=' ', file=outfile)
            print('<param name="Name" value="' + key + '">', end=' ', file=outfile)
            print('<param name="Local" value="' + location + '">', end=' ', file=outfile)
            print('</OBJECT>', file=outfile)
        print('</UL>', file=outfile)
1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934

    def codeexpand(self, line):
        co = self.codeprog.match(line)
        if not co:
            return line
        bgn, end = co.span(0)
        a, b = co.span(1)
        line = line[:bgn] + line[a:b] + line[end:]
        return line


Guido van Rossum's avatar
Guido van Rossum committed
1935 1936
# Put @var{} around alphabetic substrings
def makevar(str):
1937
    return '@var{'+str+'}'
Guido van Rossum's avatar
Guido van Rossum committed
1938 1939 1940 1941


# Split a string in "words" according to findwordend
def splitwords(str, minlength):
1942 1943 1944 1945
    words = []
    i = 0
    n = len(str)
    while i < n:
Guido van Rossum's avatar
Guido van Rossum committed
1946 1947 1948 1949 1950
        while i < n and str[i] in ' \t\n': i = i+1
        if i >= n: break
        start = i
        i = findwordend(str, i, n)
        words.append(str[start:i])
1951 1952
    while len(words) < minlength: words.append('')
    return words
Guido van Rossum's avatar
Guido van Rossum committed
1953 1954 1955


# Find the end of a "word", matching braces and interpreting @@ @{ @}
1956
fwprog = re.compile('[@{} ]')
Guido van Rossum's avatar
Guido van Rossum committed
1957
def findwordend(str, i, n):
1958 1959
    level = 0
    while i < n:
1960 1961 1962 1963
        mo = fwprog.search(str, i)
        if not mo:
            break
        i = mo.start()
Guido van Rossum's avatar
Guido van Rossum committed
1964 1965 1966 1967 1968
        c = str[i]; i = i+1
        if c == '@': i = i+1 # Next character is not special
        elif c == '{': level = level+1
        elif c == '}': level = level-1
        elif c == ' ' and level <= 0: return i-1
1969
    return n
Guido van Rossum's avatar
Guido van Rossum committed
1970 1971 1972 1973


# Convert a node name into a file name
def makefile(nodename):
1974
    nodename = nodename.strip()
1975
    return fixfunnychars(nodename) + '.html'
Guido van Rossum's avatar
Guido van Rossum committed
1976 1977 1978


# Characters that are perfectly safe in filenames and hyperlinks
1979
goodchars = string.ascii_letters + string.digits + '!@-=+.'
Guido van Rossum's avatar
Guido van Rossum committed
1980

1981 1982 1983 1984
# Replace characters that aren't perfectly safe by dashes
# Underscores are bad since Cern HTTPD treats them as delimiters for
# encoding times, so you get mismatches if you compress your files:
# a.html.gz will map to a_b.html.gz
Guido van Rossum's avatar
Guido van Rossum committed
1985
def fixfunnychars(addr):
1986 1987
    i = 0
    while i < len(addr):
Guido van Rossum's avatar
Guido van Rossum committed
1988 1989 1990 1991 1992
        c = addr[i]
        if c not in goodchars:
            c = '-'
            addr = addr[:i] + c + addr[i+1:]
        i = i + len(c)
1993
    return addr
Guido van Rossum's avatar
Guido van Rossum committed
1994 1995 1996 1997


# Increment a string used as an enumeration
def increment(s):
1998
    if not s:
Guido van Rossum's avatar
Guido van Rossum committed
1999
        return '1'
2000
    for sequence in string.digits, string.ascii_lowercase, string.ascii_uppercase:
Guido van Rossum's avatar
Guido van Rossum committed
2001 2002
        lastc = s[-1]
        if lastc in sequence:
2003
            i = sequence.index(lastc) + 1
Guido van Rossum's avatar
Guido van Rossum committed
2004 2005 2006 2007 2008 2009 2010 2011 2012 2013
            if i >= len(sequence):
                if len(s) == 1:
                    s = sequence[0]*2
                    if s == '00':
                        s = '10'
                else:
                    s = increment(s[:-1]) + sequence[0]
            else:
                s = s[:-1] + sequence[i]
            return s
2014
    return s # Don't increment
Guido van Rossum's avatar
Guido van Rossum committed
2015 2016 2017


def test():
2018
    import sys
2019 2020 2021 2022
    debugging = 0
    print_headers = 0
    cont = 0
    html3 = 0
2023
    htmlhelp = ''
Tim Peters's avatar
Tim Peters committed
2024

2025
    while sys.argv[1] == ['-d']:
Guido van Rossum's avatar
Guido van Rossum committed
2026
        debugging = debugging + 1
2027
        del sys.argv[1]
2028
    if sys.argv[1] == '-p':
Guido van Rossum's avatar
Guido van Rossum committed
2029 2030
        print_headers = 1
        del sys.argv[1]
2031
    if sys.argv[1] == '-c':
Guido van Rossum's avatar
Guido van Rossum committed
2032 2033
        cont = 1
        del sys.argv[1]
2034
    if sys.argv[1] == '-3':
Guido van Rossum's avatar
Guido van Rossum committed
2035 2036
        html3 = 1
        del sys.argv[1]
2037 2038 2039
    if sys.argv[1] == '-H':
        helpbase = sys.argv[2]
        del sys.argv[1:3]
2040
    if len(sys.argv) != 3:
2041 2042
        print('usage: texi2hh [-d [-d]] [-p] [-c] [-3] [-H htmlhelp]', \
              'inputfile outputdirectory')
Guido van Rossum's avatar
Guido van Rossum committed
2043
        sys.exit(2)
2044 2045

    if html3:
Guido van Rossum's avatar
Guido van Rossum committed
2046
        parser = TexinfoParserHTML3()
2047
    else:
Guido van Rossum's avatar
Guido van Rossum committed
2048
        parser = TexinfoParser()
2049 2050 2051 2052
    parser.cont = cont
    parser.debugging = debugging
    parser.print_headers = print_headers

2053
    file = sys.argv[1]
2054 2055
    dirname  = sys.argv[2]
    parser.setdirname(dirname)
2056
    parser.setincludedir(os.path.dirname(file))
2057 2058 2059 2060

    htmlhelp = HTMLHelp(helpbase, dirname)
    parser.sethtmlhelp(htmlhelp)

2061 2062
    try:
        fp = open(file, 'r')
2063
    except IOError as msg:
2064
        print(file, ':', msg)
2065
        sys.exit(1)
2066

2067 2068 2069 2070
    parser.parse(fp)
    fp.close()
    parser.report()

2071 2072
    htmlhelp.finalize()

2073 2074 2075

if __name__ == "__main__":
    test()