1#! /usr/bin/env python3 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(r'^\* ([^:]*):(:|[ \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 for line in lines: 118 self.lines.append(line) 119 120 def flush(self): 121 with open(self.dirname + '/' + makefile(self.name), 'w') as fp: 122 fp.write(self.prologue) 123 fp.write(self.text) 124 fp.write(self.epilogue) 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 is not 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 is not 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 is not 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, end=' ') 386 if accu: print(accu[0][:30], end=' ') 387 if accu[0][30:] or accu[1:]: print('...', end=' ') 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 cmd not in self.unknown: 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 cmd not in self.unknown: 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 cmd not in self.unknown: 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 as msg: 558 print('*** Can\'t open include file', repr(file)) 559 return 560 with fp: 561 print('!'*self.debugging, '--> file', repr(file)) 562 save_done = self.done 563 save_skip = self.skip 564 save_stack = self.stack 565 self.includedepth = self.includedepth + 1 566 self.parserest(fp, 0) 567 self.includedepth = self.includedepth - 1 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 is not 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 cmd not in self.unknown: 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 cmd not in self.unknown: 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 or self.values[args] is None: 957 self.skip = self.skip + 1 958 self.stackinfo[len(self.stack)] = 1 959 else: 960 self.stackinfo[len(self.stack)] = 0 961 def end_ifset(self): 962 try: 963 if self.stackinfo[len(self.stack) + 1]: 964 self.skip = self.skip - 1 965 del self.stackinfo[len(self.stack) + 1] 966 except KeyError: 967 print('*** end_ifset: KeyError :', len(self.stack) + 1) 968 969 def bgn_ifclear(self, args): 970 if args in self.values and self.values[args] is not None: 971 self.skip = self.skip + 1 972 self.stackinfo[len(self.stack)] = 1 973 else: 974 self.stackinfo[len(self.stack)] = 0 975 def end_ifclear(self): 976 try: 977 if self.stackinfo[len(self.stack) + 1]: 978 self.skip = self.skip - 1 979 del self.stackinfo[len(self.stack) + 1] 980 except KeyError: 981 print('*** end_ifclear: KeyError :', len(self.stack) + 1) 982 983 def open_value(self): 984 self.startsaving() 985 986 def close_value(self): 987 key = self.collectsavings() 988 if key in self.values: 989 self.write(self.values[key]) 990 else: 991 print('*** Undefined value: ', key) 992 993 # --- Beginning a file --- 994 995 do_finalout = do_comment 996 do_setchapternewpage = do_comment 997 do_setfilename = do_comment 998 999 def do_settitle(self, args): 1000 self.startsaving() 1001 self.expand(args) 1002 self.title = self.collectsavings() 1003 def do_parskip(self, args): pass 1004 1005 # --- Ending a file --- 1006 1007 def do_bye(self, args): 1008 self.endnode() 1009 self.done = 1 1010 1011 # --- Title page --- 1012 1013 def bgn_titlepage(self, args): self.skip = self.skip + 1 1014 def end_titlepage(self): self.skip = self.skip - 1 1015 def do_shorttitlepage(self, args): pass 1016 1017 def do_center(self, args): 1018 # Actually not used outside title page... 1019 self.write('<H1>') 1020 self.expand(args) 1021 self.write('</H1>\n') 1022 do_title = do_center 1023 do_subtitle = do_center 1024 do_author = do_center 1025 1026 do_vskip = do_comment 1027 do_vfill = do_comment 1028 do_smallbook = do_comment 1029 1030 do_paragraphindent = do_comment 1031 do_setchapternewpage = do_comment 1032 do_headings = do_comment 1033 do_footnotestyle = do_comment 1034 1035 do_evenheading = do_comment 1036 do_evenfooting = do_comment 1037 do_oddheading = do_comment 1038 do_oddfooting = do_comment 1039 do_everyheading = do_comment 1040 do_everyfooting = do_comment 1041 1042 # --- Nodes --- 1043 1044 def do_node(self, args): 1045 self.endnode() 1046 self.nodelineno = 0 1047 parts = [s.strip() for s in args.split(',')] 1048 while len(parts) < 4: parts.append('') 1049 self.nodelinks = parts 1050 [name, next, prev, up] = parts[:4] 1051 file = self.dirname + '/' + makefile(name) 1052 if file in self.filenames: 1053 print('*** Filename already in use: ', file) 1054 else: 1055 if self.debugging: print('!'*self.debugging, '--- writing', file) 1056 self.filenames[file] = 1 1057 # self.nodefp = open(file, 'w') 1058 self.nodename = name 1059 if self.cont and self.nodestack: 1060 self.nodestack[-1].cont = self.nodename 1061 if not self.topname: self.topname = name 1062 title = name 1063 if self.title: title = title + ' -- ' + self.title 1064 self.node = self.Node(self.dirname, self.nodename, self.topname, 1065 title, next, prev, up) 1066 self.htmlhelp.addnode(self.nodename,next,prev,up,file) 1067 1068 def link(self, label, nodename): 1069 if nodename: 1070 if nodename.lower() == '(dir)': 1071 addr = '../dir.html' 1072 else: 1073 addr = makefile(nodename) 1074 self.write(label, ': <A HREF="', addr, '" TYPE="', 1075 label, '">', nodename, '</A> \n') 1076 1077 # --- Sectioning commands --- 1078 1079 def popstack(self, type): 1080 if (self.node): 1081 self.node.type = type 1082 while self.nodestack: 1083 if self.nodestack[-1].type > type: 1084 self.nodestack[-1].finalize() 1085 self.nodestack[-1].flush() 1086 del self.nodestack[-1] 1087 elif self.nodestack[-1].type == type: 1088 if not self.nodestack[-1].next: 1089 self.nodestack[-1].next = self.node.name 1090 if not self.node.prev: 1091 self.node.prev = self.nodestack[-1].name 1092 self.nodestack[-1].finalize() 1093 self.nodestack[-1].flush() 1094 del self.nodestack[-1] 1095 else: 1096 if type > 1 and not self.node.up: 1097 self.node.up = self.nodestack[-1].name 1098 break 1099 1100 def do_chapter(self, args): 1101 self.heading('H1', args, 0) 1102 self.popstack(1) 1103 1104 def do_unnumbered(self, args): 1105 self.heading('H1', args, -1) 1106 self.popstack(1) 1107 def do_appendix(self, args): 1108 self.heading('H1', args, -1) 1109 self.popstack(1) 1110 def do_top(self, args): 1111 self.heading('H1', args, -1) 1112 def do_chapheading(self, args): 1113 self.heading('H1', args, -1) 1114 def do_majorheading(self, args): 1115 self.heading('H1', args, -1) 1116 1117 def do_section(self, args): 1118 self.heading('H1', args, 1) 1119 self.popstack(2) 1120 1121 def do_unnumberedsec(self, args): 1122 self.heading('H1', args, -1) 1123 self.popstack(2) 1124 def do_appendixsec(self, args): 1125 self.heading('H1', args, -1) 1126 self.popstack(2) 1127 do_appendixsection = do_appendixsec 1128 def do_heading(self, args): 1129 self.heading('H1', args, -1) 1130 1131 def do_subsection(self, args): 1132 self.heading('H2', args, 2) 1133 self.popstack(3) 1134 def do_unnumberedsubsec(self, args): 1135 self.heading('H2', args, -1) 1136 self.popstack(3) 1137 def do_appendixsubsec(self, args): 1138 self.heading('H2', args, -1) 1139 self.popstack(3) 1140 def do_subheading(self, args): 1141 self.heading('H2', args, -1) 1142 1143 def do_subsubsection(self, args): 1144 self.heading('H3', args, 3) 1145 self.popstack(4) 1146 def do_unnumberedsubsubsec(self, args): 1147 self.heading('H3', args, -1) 1148 self.popstack(4) 1149 def do_appendixsubsubsec(self, args): 1150 self.heading('H3', args, -1) 1151 self.popstack(4) 1152 def do_subsubheading(self, args): 1153 self.heading('H3', args, -1) 1154 1155 def heading(self, type, args, level): 1156 if level >= 0: 1157 while len(self.numbering) <= level: 1158 self.numbering.append(0) 1159 del self.numbering[level+1:] 1160 self.numbering[level] = self.numbering[level] + 1 1161 x = '' 1162 for i in self.numbering: 1163 x = x + repr(i) + '.' 1164 args = x + ' ' + args 1165 self.contents.append((level, args, self.nodename)) 1166 self.write('<', type, '>') 1167 self.expand(args) 1168 self.write('</', type, '>\n') 1169 if self.debugging or self.print_headers: 1170 print('---', args) 1171 1172 def do_contents(self, args): 1173 # pass 1174 self.listcontents('Table of Contents', 999) 1175 1176 def do_shortcontents(self, args): 1177 pass 1178 # self.listcontents('Short Contents', 0) 1179 do_summarycontents = do_shortcontents 1180 1181 def listcontents(self, title, maxlevel): 1182 self.write('<H1>', title, '</H1>\n<UL COMPACT PLAIN>\n') 1183 prevlevels = [0] 1184 for level, title, node in self.contents: 1185 if level > maxlevel: 1186 continue 1187 if level > prevlevels[-1]: 1188 # can only advance one level at a time 1189 self.write(' '*prevlevels[-1], '<UL PLAIN>\n') 1190 prevlevels.append(level) 1191 elif level < prevlevels[-1]: 1192 # might drop back multiple levels 1193 while level < prevlevels[-1]: 1194 del prevlevels[-1] 1195 self.write(' '*prevlevels[-1], 1196 '</UL>\n') 1197 self.write(' '*level, '<LI> <A HREF="', 1198 makefile(node), '">') 1199 self.expand(title) 1200 self.write('</A>\n') 1201 self.write('</UL>\n' * len(prevlevels)) 1202 1203 # --- Page lay-out --- 1204 1205 # These commands are only meaningful in printed text 1206 1207 def do_page(self, args): pass 1208 1209 def do_need(self, args): pass 1210 1211 def bgn_group(self, args): pass 1212 def end_group(self): pass 1213 1214 # --- Line lay-out --- 1215 1216 def do_sp(self, args): 1217 if self.nofill: 1218 self.write('\n') 1219 else: 1220 self.write('<P>\n') 1221 1222 def do_hline(self, args): 1223 self.write('<HR>') 1224 1225 # --- Function and variable definitions --- 1226 1227 def bgn_deffn(self, args): 1228 self.write('<DL>') 1229 self.do_deffnx(args) 1230 1231 def end_deffn(self): 1232 self.write('</DL>\n') 1233 1234 def do_deffnx(self, args): 1235 self.write('<DT>') 1236 words = splitwords(args, 2) 1237 [category, name], rest = words[:2], words[2:] 1238 self.expand('@b{%s}' % name) 1239 for word in rest: self.expand(' ' + makevar(word)) 1240 #self.expand(' -- ' + category) 1241 self.write('\n<DD>') 1242 self.index('fn', name) 1243 1244 def bgn_defun(self, args): self.bgn_deffn('Function ' + args) 1245 end_defun = end_deffn 1246 def do_defunx(self, args): self.do_deffnx('Function ' + args) 1247 1248 def bgn_defmac(self, args): self.bgn_deffn('Macro ' + args) 1249 end_defmac = end_deffn 1250 def do_defmacx(self, args): self.do_deffnx('Macro ' + args) 1251 1252 def bgn_defspec(self, args): self.bgn_deffn('{Special Form} ' + args) 1253 end_defspec = end_deffn 1254 def do_defspecx(self, args): self.do_deffnx('{Special Form} ' + args) 1255 1256 def bgn_defvr(self, args): 1257 self.write('<DL>') 1258 self.do_defvrx(args) 1259 1260 end_defvr = end_deffn 1261 1262 def do_defvrx(self, args): 1263 self.write('<DT>') 1264 words = splitwords(args, 2) 1265 [category, name], rest = words[:2], words[2:] 1266 self.expand('@code{%s}' % name) 1267 # If there are too many arguments, show them 1268 for word in rest: self.expand(' ' + word) 1269 #self.expand(' -- ' + category) 1270 self.write('\n<DD>') 1271 self.index('vr', name) 1272 1273 def bgn_defvar(self, args): self.bgn_defvr('Variable ' + args) 1274 end_defvar = end_defvr 1275 def do_defvarx(self, args): self.do_defvrx('Variable ' + args) 1276 1277 def bgn_defopt(self, args): self.bgn_defvr('{User Option} ' + args) 1278 end_defopt = end_defvr 1279 def do_defoptx(self, args): self.do_defvrx('{User Option} ' + args) 1280 1281 # --- Ditto for typed languages --- 1282 1283 def bgn_deftypefn(self, args): 1284 self.write('<DL>') 1285 self.do_deftypefnx(args) 1286 1287 end_deftypefn = end_deffn 1288 1289 def do_deftypefnx(self, args): 1290 self.write('<DT>') 1291 words = splitwords(args, 3) 1292 [category, datatype, name], rest = words[:3], words[3:] 1293 self.expand('@code{%s} @b{%s}' % (datatype, name)) 1294 for word in rest: self.expand(' ' + makevar(word)) 1295 #self.expand(' -- ' + category) 1296 self.write('\n<DD>') 1297 self.index('fn', name) 1298 1299 1300 def bgn_deftypefun(self, args): self.bgn_deftypefn('Function ' + args) 1301 end_deftypefun = end_deftypefn 1302 def do_deftypefunx(self, args): self.do_deftypefnx('Function ' + args) 1303 1304 def bgn_deftypevr(self, args): 1305 self.write('<DL>') 1306 self.do_deftypevrx(args) 1307 1308 end_deftypevr = end_deftypefn 1309 1310 def do_deftypevrx(self, args): 1311 self.write('<DT>') 1312 words = splitwords(args, 3) 1313 [category, datatype, name], rest = words[:3], words[3:] 1314 self.expand('@code{%s} @b{%s}' % (datatype, name)) 1315 # If there are too many arguments, show them 1316 for word in rest: self.expand(' ' + word) 1317 #self.expand(' -- ' + category) 1318 self.write('\n<DD>') 1319 self.index('fn', name) 1320 1321 def bgn_deftypevar(self, args): 1322 self.bgn_deftypevr('Variable ' + args) 1323 end_deftypevar = end_deftypevr 1324 def do_deftypevarx(self, args): 1325 self.do_deftypevrx('Variable ' + args) 1326 1327 # --- Ditto for object-oriented languages --- 1328 1329 def bgn_defcv(self, args): 1330 self.write('<DL>') 1331 self.do_defcvx(args) 1332 1333 end_defcv = end_deftypevr 1334 1335 def do_defcvx(self, args): 1336 self.write('<DT>') 1337 words = splitwords(args, 3) 1338 [category, classname, name], rest = words[:3], words[3:] 1339 self.expand('@b{%s}' % name) 1340 # If there are too many arguments, show them 1341 for word in rest: self.expand(' ' + word) 1342 #self.expand(' -- %s of @code{%s}' % (category, classname)) 1343 self.write('\n<DD>') 1344 self.index('vr', '%s @r{on %s}' % (name, classname)) 1345 1346 def bgn_defivar(self, args): 1347 self.bgn_defcv('{Instance Variable} ' + args) 1348 end_defivar = end_defcv 1349 def do_defivarx(self, args): 1350 self.do_defcvx('{Instance Variable} ' + args) 1351 1352 def bgn_defop(self, args): 1353 self.write('<DL>') 1354 self.do_defopx(args) 1355 1356 end_defop = end_defcv 1357 1358 def do_defopx(self, args): 1359 self.write('<DT>') 1360 words = splitwords(args, 3) 1361 [category, classname, name], rest = words[:3], words[3:] 1362 self.expand('@b{%s}' % name) 1363 for word in rest: self.expand(' ' + makevar(word)) 1364 #self.expand(' -- %s of @code{%s}' % (category, classname)) 1365 self.write('\n<DD>') 1366 self.index('fn', '%s @r{on %s}' % (name, classname)) 1367 1368 def bgn_defmethod(self, args): 1369 self.bgn_defop('Method ' + args) 1370 end_defmethod = end_defop 1371 def do_defmethodx(self, args): 1372 self.do_defopx('Method ' + args) 1373 1374 # --- Ditto for data types --- 1375 1376 def bgn_deftp(self, args): 1377 self.write('<DL>') 1378 self.do_deftpx(args) 1379 1380 end_deftp = end_defcv 1381 1382 def do_deftpx(self, args): 1383 self.write('<DT>') 1384 words = splitwords(args, 2) 1385 [category, name], rest = words[:2], words[2:] 1386 self.expand('@b{%s}' % name) 1387 for word in rest: self.expand(' ' + word) 1388 #self.expand(' -- ' + category) 1389 self.write('\n<DD>') 1390 self.index('tp', name) 1391 1392 # --- Making Lists and Tables 1393 1394 def bgn_enumerate(self, args): 1395 if not args: 1396 self.write('<OL>\n') 1397 self.stackinfo[len(self.stack)] = '</OL>\n' 1398 else: 1399 self.itemnumber = args 1400 self.write('<UL>\n') 1401 self.stackinfo[len(self.stack)] = '</UL>\n' 1402 def end_enumerate(self): 1403 self.itemnumber = None 1404 self.write(self.stackinfo[len(self.stack) + 1]) 1405 del self.stackinfo[len(self.stack) + 1] 1406 1407 def bgn_itemize(self, args): 1408 self.itemarg = args 1409 self.write('<UL>\n') 1410 def end_itemize(self): 1411 self.itemarg = None 1412 self.write('</UL>\n') 1413 1414 def bgn_table(self, args): 1415 self.itemarg = args 1416 self.write('<DL>\n') 1417 def end_table(self): 1418 self.itemarg = None 1419 self.write('</DL>\n') 1420 1421 def bgn_ftable(self, args): 1422 self.itemindex = 'fn' 1423 self.bgn_table(args) 1424 def end_ftable(self): 1425 self.itemindex = None 1426 self.end_table() 1427 1428 def bgn_vtable(self, args): 1429 self.itemindex = 'vr' 1430 self.bgn_table(args) 1431 def end_vtable(self): 1432 self.itemindex = None 1433 self.end_table() 1434 1435 def do_item(self, args): 1436 if self.itemindex: self.index(self.itemindex, args) 1437 if self.itemarg: 1438 if self.itemarg[0] == '@' and self.itemarg[1] and \ 1439 self.itemarg[1] in string.ascii_letters: 1440 args = self.itemarg + '{' + args + '}' 1441 else: 1442 # some other character, e.g. '-' 1443 args = self.itemarg + ' ' + args 1444 if self.itemnumber is not None: 1445 args = self.itemnumber + '. ' + args 1446 self.itemnumber = increment(self.itemnumber) 1447 if self.stack and self.stack[-1] == 'table': 1448 self.write('<DT>') 1449 self.expand(args) 1450 self.write('\n<DD>') 1451 elif self.stack and self.stack[-1] == 'multitable': 1452 self.write('<TR><TD>') 1453 self.expand(args) 1454 self.write('</TD>\n</TR>\n') 1455 else: 1456 self.write('<LI>') 1457 self.expand(args) 1458 self.write(' ') 1459 do_itemx = do_item # XXX Should suppress leading blank line 1460 1461 # rpyron 2002-05-07 multitable support 1462 def bgn_multitable(self, args): 1463 self.itemarg = None # should be handled by columnfractions 1464 self.write('<TABLE BORDER="">\n') 1465 def end_multitable(self): 1466 self.itemarg = None 1467 self.write('</TABLE>\n<BR>\n') 1468 def handle_columnfractions(self): 1469 # It would be better to handle this, but for now it's in the way... 1470 self.itemarg = None 1471 def handle_tab(self): 1472 self.write('</TD>\n <TD>') 1473 1474 # --- Enumerations, displays, quotations --- 1475 # XXX Most of these should increase the indentation somehow 1476 1477 def bgn_quotation(self, args): self.write('<BLOCKQUOTE>') 1478 def end_quotation(self): self.write('</BLOCKQUOTE>\n') 1479 1480 def bgn_example(self, args): 1481 self.nofill = self.nofill + 1 1482 self.write('<PRE>') 1483 def end_example(self): 1484 self.write('</PRE>\n') 1485 self.nofill = self.nofill - 1 1486 1487 bgn_lisp = bgn_example # Synonym when contents are executable lisp code 1488 end_lisp = end_example 1489 1490 bgn_smallexample = bgn_example # XXX Should use smaller font 1491 end_smallexample = end_example 1492 1493 bgn_smalllisp = bgn_lisp # Ditto 1494 end_smalllisp = end_lisp 1495 1496 bgn_display = bgn_example 1497 end_display = end_example 1498 1499 bgn_format = bgn_display 1500 end_format = end_display 1501 1502 def do_exdent(self, args): self.expand(args + '\n') 1503 # XXX Should really mess with indentation 1504 1505 def bgn_flushleft(self, args): 1506 self.nofill = self.nofill + 1 1507 self.write('<PRE>\n') 1508 def end_flushleft(self): 1509 self.write('</PRE>\n') 1510 self.nofill = self.nofill - 1 1511 1512 def bgn_flushright(self, args): 1513 self.nofill = self.nofill + 1 1514 self.write('<ADDRESS COMPACT>\n') 1515 def end_flushright(self): 1516 self.write('</ADDRESS>\n') 1517 self.nofill = self.nofill - 1 1518 1519 def bgn_menu(self, args): 1520 self.write('<DIR>\n') 1521 self.write(' <STRONG><EM>Menu</EM></STRONG><P>\n') 1522 self.htmlhelp.beginmenu() 1523 def end_menu(self): 1524 self.write('</DIR>\n') 1525 self.htmlhelp.endmenu() 1526 1527 def bgn_cartouche(self, args): pass 1528 def end_cartouche(self): pass 1529 1530 # --- Indices --- 1531 1532 def resetindex(self): 1533 self.noncodeindices = ['cp'] 1534 self.indextitle = {} 1535 self.indextitle['cp'] = 'Concept' 1536 self.indextitle['fn'] = 'Function' 1537 self.indextitle['ky'] = 'Keyword' 1538 self.indextitle['pg'] = 'Program' 1539 self.indextitle['tp'] = 'Type' 1540 self.indextitle['vr'] = 'Variable' 1541 # 1542 self.whichindex = {} 1543 for name in self.indextitle: 1544 self.whichindex[name] = [] 1545 1546 def user_index(self, name, args): 1547 if name in self.whichindex: 1548 self.index(name, args) 1549 else: 1550 print('*** No index named', repr(name)) 1551 1552 def do_cindex(self, args): self.index('cp', args) 1553 def do_findex(self, args): self.index('fn', args) 1554 def do_kindex(self, args): self.index('ky', args) 1555 def do_pindex(self, args): self.index('pg', args) 1556 def do_tindex(self, args): self.index('tp', args) 1557 def do_vindex(self, args): self.index('vr', args) 1558 1559 def index(self, name, args): 1560 self.whichindex[name].append((args, self.nodename)) 1561 self.htmlhelp.index(args, self.nodename) 1562 1563 def do_synindex(self, args): 1564 words = args.split() 1565 if len(words) != 2: 1566 print('*** bad @synindex', args) 1567 return 1568 [old, new] = words 1569 if old not in self.whichindex or \ 1570 new not in self.whichindex: 1571 print('*** bad key(s) in @synindex', args) 1572 return 1573 if old != new and \ 1574 self.whichindex[old] is not self.whichindex[new]: 1575 inew = self.whichindex[new] 1576 inew[len(inew):] = self.whichindex[old] 1577 self.whichindex[old] = inew 1578 do_syncodeindex = do_synindex # XXX Should use code font 1579 1580 def do_printindex(self, args): 1581 words = args.split() 1582 for name in words: 1583 if name in self.whichindex: 1584 self.prindex(name) 1585 else: 1586 print('*** No index named', repr(name)) 1587 1588 def prindex(self, name): 1589 iscodeindex = (name not in self.noncodeindices) 1590 index = self.whichindex[name] 1591 if not index: return 1592 if self.debugging: 1593 print('!'*self.debugging, '--- Generating', \ 1594 self.indextitle[name], 'index') 1595 # The node already provides a title 1596 index1 = [] 1597 junkprog = re.compile('^(@[a-z]+)?{') 1598 for key, node in index: 1599 sortkey = key.lower() 1600 # Remove leading `@cmd{' from sort key 1601 # -- don't bother about the matching `}' 1602 oldsortkey = sortkey 1603 while 1: 1604 mo = junkprog.match(sortkey) 1605 if not mo: 1606 break 1607 i = mo.end() 1608 sortkey = sortkey[i:] 1609 index1.append((sortkey, key, node)) 1610 del index[:] 1611 index1.sort() 1612 self.write('<DL COMPACT>\n') 1613 prevkey = prevnode = None 1614 for sortkey, key, node in index1: 1615 if (key, node) == (prevkey, prevnode): 1616 continue 1617 if self.debugging > 1: print('!'*self.debugging, key, ':', node) 1618 self.write('<DT>') 1619 if iscodeindex: key = '@code{' + key + '}' 1620 if key != prevkey: 1621 self.expand(key) 1622 self.write('\n<DD><A HREF="%s">%s</A>\n' % (makefile(node), node)) 1623 prevkey, prevnode = key, node 1624 self.write('</DL>\n') 1625 1626 # --- Final error reports --- 1627 1628 def report(self): 1629 if self.unknown: 1630 print('--- Unrecognized commands ---') 1631 cmds = sorted(self.unknown.keys()) 1632 for cmd in cmds: 1633 print(cmd.ljust(20), self.unknown[cmd]) 1634 1635 1636class TexinfoParserHTML3(TexinfoParser): 1637 1638 COPYRIGHT_SYMBOL = "©" 1639 FN_ID_PATTERN = "[%(id)s]" 1640 FN_SOURCE_PATTERN = '<A ID=footnoteref%(id)s ' \ 1641 'HREF="#footnotetext%(id)s">' + FN_ID_PATTERN + '</A>' 1642 FN_TARGET_PATTERN = '<FN ID=footnotetext%(id)s>\n' \ 1643 '<P><A HREF="#footnoteref%(id)s">' + FN_ID_PATTERN \ 1644 + '</A>\n%(text)s</P></FN>\n' 1645 FN_HEADER = '<DIV CLASS=footnotes>\n <HR NOSHADE WIDTH=200>\n' \ 1646 ' <STRONG><EM>Footnotes</EM></STRONG>\n <P>\n' 1647 1648 Node = HTML3Node 1649 1650 def bgn_quotation(self, args): self.write('<BQ>') 1651 def end_quotation(self): self.write('</BQ>\n') 1652 1653 def bgn_example(self, args): 1654 # this use of <CODE> would not be legal in HTML 2.0, 1655 # but is in more recent DTDs. 1656 self.nofill = self.nofill + 1 1657 self.write('<PRE CLASS=example><CODE>') 1658 def end_example(self): 1659 self.write("</CODE></PRE>\n") 1660 self.nofill = self.nofill - 1 1661 1662 def bgn_flushleft(self, args): 1663 self.nofill = self.nofill + 1 1664 self.write('<PRE CLASS=flushleft>\n') 1665 1666 def bgn_flushright(self, args): 1667 self.nofill = self.nofill + 1 1668 self.write('<DIV ALIGN=right CLASS=flushright><ADDRESS COMPACT>\n') 1669 def end_flushright(self): 1670 self.write('</ADDRESS></DIV>\n') 1671 self.nofill = self.nofill - 1 1672 1673 def bgn_menu(self, args): 1674 self.write('<UL PLAIN CLASS=menu>\n') 1675 self.write(' <LH>Menu</LH>\n') 1676 def end_menu(self): 1677 self.write('</UL>\n') 1678 1679 1680# rpyron 2002-05-07 1681class HTMLHelp: 1682 """ 1683 This class encapsulates support for HTML Help. Node names, 1684 file names, menu items, index items, and image file names are 1685 accumulated until a call to finalize(). At that time, three 1686 output files are created in the current directory: 1687 1688 `helpbase`.hhp is a HTML Help Workshop project file. 1689 It contains various information, some of 1690 which I do not understand; I just copied 1691 the default project info from a fresh 1692 installation. 1693 `helpbase`.hhc is the Contents file for the project. 1694 `helpbase`.hhk is the Index file for the project. 1695 1696 When these files are used as input to HTML Help Workshop, 1697 the resulting file will be named: 1698 1699 `helpbase`.chm 1700 1701 If none of the defaults in `helpbase`.hhp are changed, 1702 the .CHM file will have Contents, Index, Search, and 1703 Favorites tabs. 1704 """ 1705 1706 codeprog = re.compile('@code{(.*?)}') 1707 1708 def __init__(self,helpbase,dirname): 1709 self.helpbase = helpbase 1710 self.dirname = dirname 1711 self.projectfile = None 1712 self.contentfile = None 1713 self.indexfile = None 1714 self.nodelist = [] 1715 self.nodenames = {} # nodename : index 1716 self.nodeindex = {} 1717 self.filenames = {} # filename : filename 1718 self.indexlist = [] # (args,nodename) == (key,location) 1719 self.current = '' 1720 self.menudict = {} 1721 self.dumped = {} 1722 1723 1724 def addnode(self,name,next,prev,up,filename): 1725 node = (name,next,prev,up,filename) 1726 # add this file to dict 1727 # retrieve list with self.filenames.values() 1728 self.filenames[filename] = filename 1729 # add this node to nodelist 1730 self.nodeindex[name] = len(self.nodelist) 1731 self.nodelist.append(node) 1732 # set 'current' for menu items 1733 self.current = name 1734 self.menudict[self.current] = [] 1735 1736 def menuitem(self,nodename): 1737 menu = self.menudict[self.current] 1738 menu.append(nodename) 1739 1740 1741 def addimage(self,imagename): 1742 self.filenames[imagename] = imagename 1743 1744 def index(self, args, nodename): 1745 self.indexlist.append((args,nodename)) 1746 1747 def beginmenu(self): 1748 pass 1749 1750 def endmenu(self): 1751 pass 1752 1753 def finalize(self): 1754 if not self.helpbase: 1755 return 1756 1757 # generate interesting filenames 1758 resultfile = self.helpbase + '.chm' 1759 projectfile = self.helpbase + '.hhp' 1760 contentfile = self.helpbase + '.hhc' 1761 indexfile = self.helpbase + '.hhk' 1762 1763 # generate a reasonable title 1764 title = self.helpbase 1765 1766 # get the default topic file 1767 (topname,topnext,topprev,topup,topfile) = self.nodelist[0] 1768 defaulttopic = topfile 1769 1770 # PROJECT FILE 1771 try: 1772 with open(projectfile, 'w') as fp: 1773 print('[OPTIONS]', file=fp) 1774 print('Auto Index=Yes', file=fp) 1775 print('Binary TOC=No', file=fp) 1776 print('Binary Index=Yes', file=fp) 1777 print('Compatibility=1.1', file=fp) 1778 print('Compiled file=' + resultfile + '', file=fp) 1779 print('Contents file=' + contentfile + '', file=fp) 1780 print('Default topic=' + defaulttopic + '', file=fp) 1781 print('Error log file=ErrorLog.log', file=fp) 1782 print('Index file=' + indexfile + '', file=fp) 1783 print('Title=' + title + '', file=fp) 1784 print('Display compile progress=Yes', file=fp) 1785 print('Full-text search=Yes', file=fp) 1786 print('Default window=main', file=fp) 1787 print('', file=fp) 1788 print('[WINDOWS]', file=fp) 1789 print('main=,"' + contentfile + '","' + indexfile 1790 + '","","",,,,,0x23520,222,0x1046,[10,10,780,560],' 1791 '0xB0000,,,,,,0', file=fp) 1792 print('', file=fp) 1793 print('[FILES]', file=fp) 1794 print('', file=fp) 1795 self.dumpfiles(fp) 1796 except IOError as msg: 1797 print(projectfile, ':', msg) 1798 sys.exit(1) 1799 1800 # CONTENT FILE 1801 try: 1802 with open(contentfile, 'w') as fp: 1803 print('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">', file=fp) 1804 print('<!-- This file defines the table of contents -->', file=fp) 1805 print('<HTML>', file=fp) 1806 print('<HEAD>', file=fp) 1807 print('<meta name="GENERATOR" ' 1808 'content="Microsoft® HTML Help Workshop 4.1">', file=fp) 1809 print('<!-- Sitemap 1.0 -->', file=fp) 1810 print('</HEAD>', file=fp) 1811 print('<BODY>', file=fp) 1812 print(' <OBJECT type="text/site properties">', file=fp) 1813 print(' <param name="Window Styles" value="0x800025">', file=fp) 1814 print(' <param name="comment" value="title:">', file=fp) 1815 print(' <param name="comment" value="base:">', file=fp) 1816 print(' </OBJECT>', file=fp) 1817 self.dumpnodes(fp) 1818 print('</BODY>', file=fp) 1819 print('</HTML>', file=fp) 1820 except IOError as msg: 1821 print(contentfile, ':', msg) 1822 sys.exit(1) 1823 1824 # INDEX FILE 1825 try: 1826 with open(indexfile, 'w') as fp: 1827 print('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">', file=fp) 1828 print('<!-- This file defines the index -->', file=fp) 1829 print('<HTML>', file=fp) 1830 print('<HEAD>', file=fp) 1831 print('<meta name="GENERATOR" ' 1832 'content="Microsoft® HTML Help Workshop 4.1">', file=fp) 1833 print('<!-- Sitemap 1.0 -->', file=fp) 1834 print('</HEAD>', file=fp) 1835 print('<BODY>', file=fp) 1836 print('<OBJECT type="text/site properties">', file=fp) 1837 print('</OBJECT>', file=fp) 1838 self.dumpindex(fp) 1839 print('</BODY>', file=fp) 1840 print('</HTML>', file=fp) 1841 except IOError as msg: 1842 print(indexfile , ':', msg) 1843 sys.exit(1) 1844 1845 def dumpfiles(self, outfile=sys.stdout): 1846 filelist = sorted(self.filenames.values()) 1847 for filename in filelist: 1848 print(filename, file=outfile) 1849 1850 def dumpnodes(self, outfile=sys.stdout): 1851 self.dumped = {} 1852 if self.nodelist: 1853 nodename, dummy, dummy, dummy, dummy = self.nodelist[0] 1854 self.topnode = nodename 1855 1856 print('<UL>', file=outfile) 1857 for node in self.nodelist: 1858 self.dumpnode(node,0,outfile) 1859 print('</UL>', file=outfile) 1860 1861 def dumpnode(self, node, indent=0, outfile=sys.stdout): 1862 if node: 1863 # Retrieve info for this node 1864 (nodename,next,prev,up,filename) = node 1865 self.current = nodename 1866 1867 # Have we been dumped already? 1868 if nodename in self.dumped: 1869 return 1870 self.dumped[nodename] = 1 1871 1872 # Print info for this node 1873 print(' '*indent, end=' ', file=outfile) 1874 print('<LI><OBJECT type="text/sitemap">', end=' ', file=outfile) 1875 print('<param name="Name" value="' + nodename +'">', end=' ', file=outfile) 1876 print('<param name="Local" value="'+ filename +'">', end=' ', file=outfile) 1877 print('</OBJECT>', file=outfile) 1878 1879 # Does this node have menu items? 1880 try: 1881 menu = self.menudict[nodename] 1882 self.dumpmenu(menu,indent+2,outfile) 1883 except KeyError: 1884 pass 1885 1886 def dumpmenu(self, menu, indent=0, outfile=sys.stdout): 1887 if menu: 1888 currentnode = self.current 1889 if currentnode != self.topnode: # XXX this is a hack 1890 print(' '*indent + '<UL>', file=outfile) 1891 indent += 2 1892 for item in menu: 1893 menunode = self.getnode(item) 1894 self.dumpnode(menunode,indent,outfile) 1895 if currentnode != self.topnode: # XXX this is a hack 1896 print(' '*indent + '</UL>', file=outfile) 1897 indent -= 2 1898 1899 def getnode(self, nodename): 1900 try: 1901 index = self.nodeindex[nodename] 1902 return self.nodelist[index] 1903 except KeyError: 1904 return None 1905 except IndexError: 1906 return None 1907 1908 # (args,nodename) == (key,location) 1909 def dumpindex(self, outfile=sys.stdout): 1910 print('<UL>', file=outfile) 1911 for (key,location) in self.indexlist: 1912 key = self.codeexpand(key) 1913 location = makefile(location) 1914 location = self.dirname + '/' + location 1915 print('<LI><OBJECT type="text/sitemap">', end=' ', file=outfile) 1916 print('<param name="Name" value="' + key + '">', end=' ', file=outfile) 1917 print('<param name="Local" value="' + location + '">', end=' ', file=outfile) 1918 print('</OBJECT>', file=outfile) 1919 print('</UL>', file=outfile) 1920 1921 def codeexpand(self, line): 1922 co = self.codeprog.match(line) 1923 if not co: 1924 return line 1925 bgn, end = co.span(0) 1926 a, b = co.span(1) 1927 line = line[:bgn] + line[a:b] + line[end:] 1928 return line 1929 1930 1931# Put @var{} around alphabetic substrings 1932def makevar(str): 1933 return '@var{'+str+'}' 1934 1935 1936# Split a string in "words" according to findwordend 1937def splitwords(str, minlength): 1938 words = [] 1939 i = 0 1940 n = len(str) 1941 while i < n: 1942 while i < n and str[i] in ' \t\n': i = i+1 1943 if i >= n: break 1944 start = i 1945 i = findwordend(str, i, n) 1946 words.append(str[start:i]) 1947 while len(words) < minlength: words.append('') 1948 return words 1949 1950 1951# Find the end of a "word", matching braces and interpreting @@ @{ @} 1952fwprog = re.compile('[@{} ]') 1953def findwordend(str, i, n): 1954 level = 0 1955 while i < n: 1956 mo = fwprog.search(str, i) 1957 if not mo: 1958 break 1959 i = mo.start() 1960 c = str[i]; i = i+1 1961 if c == '@': i = i+1 # Next character is not special 1962 elif c == '{': level = level+1 1963 elif c == '}': level = level-1 1964 elif c == ' ' and level <= 0: return i-1 1965 return n 1966 1967 1968# Convert a node name into a file name 1969def makefile(nodename): 1970 nodename = nodename.strip() 1971 return fixfunnychars(nodename) + '.html' 1972 1973 1974# Characters that are perfectly safe in filenames and hyperlinks 1975goodchars = string.ascii_letters + string.digits + '!@-=+.' 1976 1977# Replace characters that aren't perfectly safe by dashes 1978# Underscores are bad since Cern HTTPD treats them as delimiters for 1979# encoding times, so you get mismatches if you compress your files: 1980# a.html.gz will map to a_b.html.gz 1981def fixfunnychars(addr): 1982 i = 0 1983 while i < len(addr): 1984 c = addr[i] 1985 if c not in goodchars: 1986 c = '-' 1987 addr = addr[:i] + c + addr[i+1:] 1988 i = i + len(c) 1989 return addr 1990 1991 1992# Increment a string used as an enumeration 1993def increment(s): 1994 if not s: 1995 return '1' 1996 for sequence in string.digits, string.ascii_lowercase, string.ascii_uppercase: 1997 lastc = s[-1] 1998 if lastc in sequence: 1999 i = sequence.index(lastc) + 1 2000 if i >= len(sequence): 2001 if len(s) == 1: 2002 s = sequence[0]*2 2003 if s == '00': 2004 s = '10' 2005 else: 2006 s = increment(s[:-1]) + sequence[0] 2007 else: 2008 s = s[:-1] + sequence[i] 2009 return s 2010 return s # Don't increment 2011 2012 2013def test(): 2014 import sys 2015 debugging = 0 2016 print_headers = 0 2017 cont = 0 2018 html3 = 0 2019 htmlhelp = '' 2020 2021 while sys.argv[1] == ['-d']: 2022 debugging = debugging + 1 2023 del sys.argv[1] 2024 if sys.argv[1] == '-p': 2025 print_headers = 1 2026 del sys.argv[1] 2027 if sys.argv[1] == '-c': 2028 cont = 1 2029 del sys.argv[1] 2030 if sys.argv[1] == '-3': 2031 html3 = 1 2032 del sys.argv[1] 2033 if sys.argv[1] == '-H': 2034 helpbase = sys.argv[2] 2035 del sys.argv[1:3] 2036 if len(sys.argv) != 3: 2037 print('usage: texi2hh [-d [-d]] [-p] [-c] [-3] [-H htmlhelp]', \ 2038 'inputfile outputdirectory') 2039 sys.exit(2) 2040 2041 if html3: 2042 parser = TexinfoParserHTML3() 2043 else: 2044 parser = TexinfoParser() 2045 parser.cont = cont 2046 parser.debugging = debugging 2047 parser.print_headers = print_headers 2048 2049 file = sys.argv[1] 2050 dirname = sys.argv[2] 2051 parser.setdirname(dirname) 2052 parser.setincludedir(os.path.dirname(file)) 2053 2054 htmlhelp = HTMLHelp(helpbase, dirname) 2055 parser.sethtmlhelp(htmlhelp) 2056 2057 try: 2058 fp = open(file, 'r') 2059 except IOError as msg: 2060 print(file, ':', msg) 2061 sys.exit(1) 2062 2063 with fp: 2064 parser.parse(fp) 2065 parser.report() 2066 2067 htmlhelp.finalize() 2068 2069 2070if __name__ == "__main__": 2071 test() 2072