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