texi2html.py revision 26a9d37e5c473f1489553ec9e0e8c0444a88ae9f
1#! /usr/local/bin/python 2 3# Convert GNU texinfo files into HTML, one file per node. 4# Based on Texinfo 2.14. 5# Usage: texi2html [-d] [-d] 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# - @set, @clear, @ifset, @ifclear 21# - @defindex (hard) 22# - @c(omment) in the middle of a line (rarely used) 23# - @this* (not really needed, only used in headers anyway) 24# - @today{} (ever used outside title page?) 25 26 27import os 28import regex 29import regsub 30import string 31 32MAGIC = '\\input texinfo' 33 34cmprog = regex.compile('^@\([a-z]+\)\([ \t]\|$\)') # Command (line-oriented) 35blprog = regex.compile('^[ \t]*$') # Blank line 36kwprog = regex.compile('@[a-z]+') # Keyword (embedded, usually with {} args) 37spprog = regex.compile('[\n@{}&<>]') # Special characters in running text 38miprog = regex.compile( \ 39 '^\* \([^:]*\):\(:\|[ \t]*\([^\t,\n.]+\)\([^ \t\n]*\)\)[ \t\n]*') 40 # menu item (Yuck!) 41 42class TexinfoParser: 43 44 # Initialize an instance 45 def __init__(self): 46 self.unknown = {} # statistics about unknown @-commands 47 self.debugging = 0 # larger values produce more output 48 self.nodefp = None # open file we're writing to 49 self.savetext = None # If not None, save text head instead 50 self.dirname = 'tmp' # directory where files are created 51 self.includedir = '.' # directory to search @include files 52 self.nodename = '' # name of current node 53 self.topname = '' # name of top node (first node seen) 54 self.title = '' # title of this whole Texinfo tree 55 self.resetindex() # Reset all indices 56 self.contents = [] # Reset table of contents 57 self.numbering = [] # Reset section numbering counters 58 self.nofill = 0 # Normal operation: fill paragraphs 59 # XXX The following should be reset per node?! 60 self.footnotes = [] # Reset list of footnotes 61 self.itemarg = None # Reset command used by @item 62 self.itemnumber = None # Reset number for @item in @enumerate 63 self.itemindex = None # Reset item index name 64 65 # Set (output) directory name 66 def setdirname(self, dirname): 67 self.dirname = dirname 68 69 # Set include directory name 70 def setincludedir(self, includedir): 71 self.includedir = includedir 72 73 # Parse the contents of an entire file 74 def parse(self, fp): 75 line = fp.readline() 76 lineno = 1 77 while line and (line[0] == '%' or blprog.match(line) >= 0): 78 line = fp.readline() 79 lineno = lineno + 1 80 if line[:len(MAGIC)] <> MAGIC: 81 raise SyntaxError, 'file does not begin with '+`MAGIC` 82 self.parserest(fp, lineno) 83 84 # Parse the contents of a file, not expecting a MAGIC header 85 def parserest(self, fp, initial_lineno): 86 lineno = initial_lineno 87 self.done = 0 88 self.skip = 0 89 self.stack = [] 90 accu = [] 91 while not self.done: 92 line = fp.readline() 93 if not line: 94 if accu: 95 if not self.skip: self.process(accu) 96 accu = [] 97 if initial_lineno > 0: 98 print '*** EOF before @bye' 99 break 100 lineno = lineno + 1 101 if cmprog.match(line) >= 0: 102 a, b = cmprog.regs[1] 103 cmd = line[a:b] 104 if cmd in ('noindent', 'refill'): 105 accu.append(line) 106 else: 107 if accu: 108 if not self.skip: 109 self.process(accu) 110 accu = [] 111 self.command(line) 112 elif blprog.match(line) >= 0: 113 if accu: 114 if not self.skip: 115 self.process(accu) 116 self.write('<P>\n') 117 accu = [] 118 else: 119 # Append the line including trailing \n! 120 accu.append(line) 121 # 122 if self.skip: 123 print '*** Still skipping at the end' 124 if self.stack: 125 print '*** Stack not empty at the end' 126 print '***', self.stack 127 128 # Start saving text in a buffer instead of writing it to a file 129 def startsaving(self): 130 if self.savetext <> None: 131 print '*** Recursively saving text, expect trouble' 132 self.savetext = '' 133 134 # Return the text saved so far and start writing to file again 135 def collectsavings(self): 136 savetext = self.savetext 137 self.savetext = None 138 return savetext or '' 139 140 # Write text to file, or save it in a buffer, or ignore it 141 def write(self, *args): 142 text = string.joinfields(args, '') 143 if self.savetext <> None: 144 self.savetext = self.savetext + text 145 elif self.nodefp: 146 self.nodefp.write(text) 147 148 # Complete the current node -- write footnotes and close file 149 def endnode(self): 150 if self.savetext <> None: 151 print '*** Still saving text at end of node' 152 dummy = self.collectsavings() 153 if self.footnotes: 154 self.writefootnotes() 155 if self.nodefp: 156 self.nodefp.close() 157 self.nodefp = None 158 self.nodename = '' 159 160 # Process a list of lines, expanding embedded @-commands 161 # This mostly distinguishes between menus and normal text 162 def process(self, accu): 163 if self.debugging > 1: 164 print self.skip, self.stack, 165 if accu: print accu[0][:30], 166 if accu[0][30:] or accu[1:]: print '...', 167 print 168 if self.stack and self.stack[-1] == 'menu': 169 # XXX should be done differently 170 for line in accu: 171 if miprog.match(line) < 0: 172 line = string.strip(line) + '\n' 173 self.expand(line) 174 continue 175 (bgn, end), (a, b), (c, d), (e, f), (g, h) = \ 176 miprog.regs[:5] 177 label = line[a:b] 178 nodename = line[c:d] 179 if nodename[0] == ':': nodename = label 180 else: nodename = line[e:f] 181 punct = line[g:h] 182 self.write('<DT><A HREF="', \ 183 makefile(nodename), \ 184 '" TYPE=Menu>', nodename, \ 185 '</A>', punct, '\n<DD>') 186 self.expand(line[end:]) 187 else: 188 text = string.joinfields(accu, '') 189 self.expand(text) 190 191 # Write a string, expanding embedded @-commands 192 def expand(self, text): 193 stack = [] 194 i = 0 195 n = len(text) 196 while i < n: 197 start = i 198 i = spprog.search(text, i) 199 if i < 0: 200 self.write(text[start:]) 201 break 202 self.write(text[start:i]) 203 c = text[i] 204 i = i+1 205 if c == '\n': 206 if self.nofill > 0: 207 self.write('<P>\n') 208 else: 209 self.write('\n') 210 continue 211 if c == '<': 212 self.write('<') 213 continue 214 if c == '>': 215 self.write('>') 216 continue 217 if c == '&': 218 self.write('&') 219 continue 220 if c == '{': 221 stack.append('') 222 continue 223 if c == '}': 224 if not stack: 225 print '*** Unmatched }' 226 self.write('}') 227 continue 228 cmd = stack[-1] 229 del stack[-1] 230 try: 231 method = getattr(self, 'close_' + cmd) 232 except AttributeError: 233 self.unknown_close(cmd) 234 continue 235 method() 236 continue 237 if c <> '@': 238 # Cannot happen unless spprog is changed 239 raise RuntimeError, 'unexpected funny '+`c` 240 start = i 241 while i < n and text[i] in string.letters: i = i+1 242 if i == start: 243 # @ plus non-letter: literal next character 244 i = i+1 245 c = text[start:i] 246 if c == ':': 247 # `@:' means no extra space after 248 # preceding `.', `?', `!' or `:' 249 pass 250 else: 251 # `@.' means a sentence-ending period; 252 # `@@', `@{', `@}' quote `@', `{', `}' 253 self.write(c) 254 continue 255 cmd = text[start:i] 256 if i < n and text[i] == '{': 257 i = i+1 258 stack.append(cmd) 259 try: 260 method = getattr(self, 'open_' + cmd) 261 except AttributeError: 262 self.unknown_open(cmd) 263 continue 264 method() 265 continue 266 try: 267 method = getattr(self, 'handle_' + cmd) 268 except AttributeError: 269 self.unknown_handle(cmd) 270 continue 271 method() 272 if stack: 273 print '*** Stack not empty at para:', stack 274 275 # --- Handle unknown embedded @-commands --- 276 277 def unknown_open(self, cmd): 278 print '*** No open func for @' + cmd + '{...}' 279 cmd = cmd + '{' 280 self.write('@', cmd) 281 if not self.unknown.has_key(cmd): 282 self.unknown[cmd] = 1 283 else: 284 self.unknown[cmd] = self.unknown[cmd] + 1 285 286 def unknown_close(self, cmd): 287 print '*** No close func for @' + cmd + '{...}' 288 cmd = '}' + cmd 289 self.write('}') 290 if not self.unknown.has_key(cmd): 291 self.unknown[cmd] = 1 292 else: 293 self.unknown[cmd] = self.unknown[cmd] + 1 294 295 def unknown_handle(self, cmd): 296 print '*** No handler for @' + cmd 297 self.write('@', cmd) 298 if not self.unknown.has_key(cmd): 299 self.unknown[cmd] = 1 300 else: 301 self.unknown[cmd] = self.unknown[cmd] + 1 302 303 # XXX The following sections should be ordered as the texinfo docs 304 305 # --- Embedded @-commands without {} argument list -- 306 307 def handle_noindent(self): pass 308 309 def handle_refill(self): pass 310 311 # --- Include file handling --- 312 313 def do_include(self, args): 314 file = args 315 file = os.path.join(self.includedir, file) 316 try: 317 fp = open(file, 'r') 318 except IOError, msg: 319 print '*** Can\'t open include file', `file` 320 return 321 if self.debugging: 322 print '--> file', `file` 323 save_done = self.done 324 save_skip = self.skip 325 save_stack = self.stack 326 self.parserest(fp, 0) 327 fp.close() 328 self.done = save_done 329 self.skip = save_skip 330 self.stack = save_stack 331 if self.debugging: 332 print '<-- file', `file` 333 334 # --- Special Insertions --- 335 336 def open_dmn(self): pass 337 def close_dmn(self): pass 338 339 def open_dots(self): self.write('...') 340 def close_dots(self): pass 341 342 def open_bullet(self): self.write('•') 343 def close_bullet(self): pass 344 345 def open_TeX(self): self.write('TeX') 346 def close_TeX(self): pass 347 348 def open_copyright(self): self.write('(C)') 349 def close_copyright(self): pass 350 351 def open_minus(self): self.write('-') 352 def close_minus(self): pass 353 354 # --- Special Glyphs for Examples --- 355 356 def open_result(self): self.write('=>') 357 def close_result(self): pass 358 359 def open_expansion(self): self.write('==>') 360 def close_expansion(self): pass 361 362 def open_print(self): self.write('-|') 363 def close_print(self): pass 364 365 def open_error(self): self.write('error-->') 366 def close_error(self): pass 367 368 def open_equiv(self): self.write('==') 369 def close_equiv(self): pass 370 371 def open_point(self): self.write('-!-') 372 def close_point(self): pass 373 374 # --- Cross References --- 375 376 def open_pxref(self): 377 self.write('see ') 378 self.startsaving() 379 def close_pxref(self): 380 self.makeref() 381 382 def open_xref(self): 383 self.write('See ') 384 self.startsaving() 385 def close_xref(self): 386 self.makeref() 387 388 def open_ref(self): 389 self.startsaving() 390 def close_ref(self): 391 self.makeref() 392 393 def open_inforef(self): 394 self.write('See info file ') 395 self.startsaving() 396 def close_inforef(self): 397 text = self.collectsavings() 398 args = string.splitfields(text, ',') 399 n = len(args) 400 for i in range(n): 401 args[i] = string.strip(args[i]) 402 while len(args) < 3: args.append('') 403 node = args[0] 404 file = args[2] 405 self.write('`', file, '\', node `', node, '\'') 406 407 def makeref(self): 408 text = self.collectsavings() 409 args = string.splitfields(text, ',') 410 n = len(args) 411 for i in range(n): 412 args[i] = string.strip(args[i]) 413 while len(args) < 5: args.append('') 414 nodename = label = args[0] 415 if args[2]: label = args[2] 416 file = args[3] 417 title = args[4] 418 href = makefile(nodename) 419 if file: 420 href = '../' + file + '/' + href 421 self.write('<A HREF="', href, '">', label, '</A>') 422 423 # --- Marking Words and Phrases --- 424 425 # --- Other @xxx{...} commands --- 426 427 def open_(self): pass # Used by {text enclosed in braces} 428 def close_(self): pass 429 430 open_asis = open_ 431 close_asis = close_ 432 433 def open_cite(self): self.write('<CITE>') 434 def close_cite(self): self.write('</CITE>') 435 436 def open_code(self): self.write('<CODE>') 437 def close_code(self): self.write('</CODE>') 438 439 open_t = open_code 440 close_t = close_code 441 442 def open_dfn(self): self.write('<DFN>') 443 def close_dfn(self): self.write('</DFN>') 444 445 def open_emph(self): self.write('<I>') 446 def close_emph(self): self.write('</I>') 447 448 open_i = open_emph 449 close_i = close_emph 450 451 def open_footnote(self): 452 if self.savetext <> None: 453 print '*** Recursive footnote -- expect weirdness' 454 id = len(self.footnotes) + 1 455 self.write('<A NAME="footnoteref', `id`, \ 456 '" HREF="#footnotetext', `id`, '">(', `id`, ')</A>') 457 self.savetext = '' 458 459 def close_footnote(self): 460 id = len(self.footnotes) + 1 461 self.footnotes.append(`id`, self.savetext) 462 self.savetext = None 463 464 def writefootnotes(self): 465 self.write('<H2>---------- Footnotes ----------</H2>\n') 466 for id, text in self.footnotes: 467 self.write('<A NAME="footnotetext', id, \ 468 '" HREF="#footnoteref', id, '">(', \ 469 id, ')</A>\n', text, '<P>\n') 470 self.footnotes = [] 471 472 def open_file(self): self.write('<FILE>') 473 def close_file(self): self.write('</FILE>') 474 475 def open_kbd(self): self.write('<KBD>') 476 def close_kbd(self): self.write('</KBD>') 477 478 def open_key(self): self.write('<KEY>') 479 def close_key(self): self.write('</KEY>') 480 481 def open_r(self): self.write('<R>') 482 def close_r(self): self.write('</R>') 483 484 def open_samp(self): self.write('`<SAMP>') 485 def close_samp(self): self.write('</SAMP>\'') 486 487 def open_sc(self): self.write('<SMALLCAPS>') 488 def close_sc(self): self.write('</SMALLCAPS>') 489 490 def open_strong(self): self.write('<B>') 491 def close_strong(self): self.write('</B>') 492 493 open_b = open_strong 494 close_b = close_strong 495 496 def open_var(self): self.write('<VAR>') 497 def close_var(self): self.write('</VAR>') 498 499 def open_w(self): self.write('<NOBREAK>') 500 def close_w(self): self.write('</NOBREAK>') 501 502 open_titlefont = open_ 503 close_titlefont = close_ 504 505 def command(self, line): 506 a, b = cmprog.regs[1] 507 cmd = line[a:b] 508 args = string.strip(line[b:]) 509 if self.debugging > 1: 510 print self.skip, self.stack, '@' + cmd, args 511 try: 512 func = getattr(self, 'do_' + cmd) 513 except AttributeError: 514 try: 515 func = getattr(self, 'bgn_' + cmd) 516 except AttributeError: 517 self.unknown_cmd(cmd, args) 518 return 519 self.stack.append(cmd) 520 func(args) 521 return 522 if not self.skip or cmd == 'end': 523 func(args) 524 525 def unknown_cmd(self, cmd, args): 526 print '*** unknown', '@' + cmd, args 527 if not self.unknown.has_key(cmd): 528 self.unknown[cmd] = 1 529 else: 530 self.unknown[cmd] = self.unknown[cmd] + 1 531 532 def do_end(self, args): 533 words = string.split(args) 534 if not words: 535 print '*** @end w/o args' 536 else: 537 cmd = words[0] 538 if not self.stack or self.stack[-1] <> cmd: 539 print '*** @end', cmd, 'unexpected' 540 else: 541 del self.stack[-1] 542 try: 543 func = getattr(self, 'end_' + cmd) 544 except AttributeError: 545 self.unknown_end(cmd) 546 return 547 func() 548 549 def unknown_end(self, cmd): 550 cmd = 'end ' + cmd 551 print '*** unknown', '@' + cmd 552 if not self.unknown.has_key(cmd): 553 self.unknown[cmd] = 1 554 else: 555 self.unknown[cmd] = self.unknown[cmd] + 1 556 557 # --- Comments --- 558 559 def do_comment(self, args): pass 560 do_c = do_comment 561 562 # --- Conditional processing --- 563 564 def bgn_ifinfo(self, args): pass 565 def end_ifinfo(self): pass 566 567 def bgn_iftex(self, args): self.skip = self.skip + 1 568 def end_iftex(self): self.skip = self.skip - 1 569 570 def bgn_ignore(self, args): self.skip = self.skip + 1 571 def end_ignore(self): self.skip = self.skip - 1 572 573 def bgn_tex(self, args): self.skip = self.skip + 1 574 def end_tex(self): self.skip = self.skip - 1 575 576 # --- Beginning a file --- 577 578 do_finalout = do_comment 579 do_setchapternewpage = do_comment 580 do_setfilename = do_comment 581 582 def do_settitle(self, args): 583 self.title = args 584 585 # --- Ending a file --- 586 587 def do_bye(self, args): 588 self.done = 1 589 590 # --- Title page --- 591 592 def bgn_titlepage(self, args): self.skip = self.skip + 1 593 def end_titlepage(self): self.skip = self.skip - 1 594 595 def do_center(self, args): 596 # Actually not used outside title page... 597 self.write('<H1>', args, '</H1>\n') 598 do_title = do_center 599 do_subtitle = do_center 600 do_author = do_center 601 602 do_vskip = do_comment 603 do_vfill = do_comment 604 do_smallbook = do_comment 605 606 do_paragraphindent = do_comment 607 do_setchapternewpage = do_comment 608 do_headings = do_comment 609 do_footnotestyle = do_comment 610 611 do_evenheading = do_comment 612 do_evenfooting = do_comment 613 do_oddheading = do_comment 614 do_oddfooting = do_comment 615 do_everyheading = do_comment 616 do_everyfooting = do_comment 617 618 # --- Nodes --- 619 620 def do_node(self, args): 621 parts = string.splitfields(args, ',') 622 while len(parts) < 4: parts.append('') 623 for i in range(4): parts[i] = string.strip(parts[i]) 624 [name, next, prev, up] = parts[:4] 625 self.endnode() 626 file = self.dirname + '/' + makefile(name) 627 if self.debugging: print '--- writing', file 628 self.nodefp = open(file, 'w') 629 self.nodename = name 630 if not self.topname: self.topname = name 631 title = name 632 if self.title: title = title + ' -- ' + self.title 633 self.write('<TITLE>', title, '</TITLE>\n') 634 self.link('Next', next) 635 self.link('Prev', prev) 636 self.link('Up', up) 637 if self.nodename <> self.topname: 638 self.link('Top', self.topname) 639 640 def link(self, label, nodename): 641 if nodename: 642 if string.lower(nodename) == '(dir)': 643 addr = '../dir.html' 644 else: 645 addr = makefile(nodename) 646 self.write(label, ': <A HREF="', addr, '" TYPE="', \ 647 label, '">', nodename, '</A> \n') 648 649 # --- Sectioning commands --- 650 651 def do_chapter(self, args): 652 self.heading('H1', args, 0) 653 def do_unnumbered(self, args): 654 self.heading('H1', args, -1) 655 def do_appendix(self, args): 656 self.heading('H1', args, -1) 657 def do_top(self, args): 658 self.heading('H1', args, -1) 659 def do_chapheading(self, args): 660 self.heading('H1', args, -1) 661 def do_majorheading(self, args): 662 self.heading('H1', args, -1) 663 664 def do_section(self, args): 665 self.heading('H1', args, 1) 666 def do_unnumberedsec(self, args): 667 self.heading('H1', args, -1) 668 def do_appendixsec(self, args): 669 self.heading('H1', args, -1) 670 do_appendixsection = do_appendixsec 671 def do_heading(self, args): 672 self.heading('H1', args, -1) 673 674 def do_subsection(self, args): 675 self.heading('H2', args, 2) 676 def do_unnumberedsubsec(self, args): 677 self.heading('H2', args, -1) 678 def do_appendixsubsec(self, args): 679 self.heading('H2', args, -1) 680 def do_subheading(self, args): 681 self.heading('H2', args, -1) 682 683 def do_subsubsection(self, args): 684 self.heading('H3', args, 3) 685 def do_unnumberedsubsubsec(self, args): 686 self.heading('H3', args, -1) 687 def do_appendixsubsubsec(self, args): 688 self.heading('H3', args, -1) 689 def do_subsubheading(self, args): 690 self.heading('H3', args, -1) 691 692 def heading(self, type, args, level): 693 if level >= 0: 694 while len(self.numbering) <= level: 695 self.numbering.append(0) 696 del self.numbering[level+1:] 697 self.numbering[level] = self.numbering[level] + 1 698 x = '' 699 for i in self.numbering: 700 x = x + `i` + '.' 701 args = x + ' ' + args 702 self.contents.append(level, args, self.nodename) 703 self.write('<', type, '>') 704 self.expand(args) 705 self.write('</', type, '>\n') 706 if self.debugging: 707 print '---', args 708 709 def do_contents(self, args): 710 pass 711 # self.listcontents('Table of Contents', 999) 712 713 def do_shortcontents(self, args): 714 pass 715 # self.listcontents('Short Contents', 0) 716 do_summarycontents = do_shortcontents 717 718 def listcontents(self, title, maxlevel): 719 self.write('<H1>', title, '</H1>\n<UL COMPACT>\n') 720 for level, title, node in self.contents: 721 if level <= maxlevel: 722 self.write('<LI>', '. '*level, '<A HREF="', \ 723 makefile(node), '">') 724 self.expand(title) 725 self.write('</A> ', node, '\n') 726 self.write('</UL>\n') 727 728 # --- Page lay-out --- 729 730 # These commands are only meaningful in printed text 731 732 def do_page(self, args): pass 733 734 def do_need(self, args): pass 735 736 def bgn_group(self, args): pass 737 def end_group(self): pass 738 739 # --- Line lay-out --- 740 741 def do_sp(self, args): 742 # Insert <args> blank lines 743 if args: 744 try: 745 n = string.atoi(args) 746 except string.atoi_error: 747 n = 1 748 else: 749 n = 1 750 self.write('<P>\n'*max(n, 0)) 751 752 # --- Function and variable definitions --- 753 754 def bgn_deffn(self, args): 755 self.write('<DL><DT>') 756 words = splitwords(args, 2) 757 [category, name], rest = words[:2], words[2:] 758 self.expand('@b{' + name + '}') 759 for word in rest: self.expand(' ' + makevar(word)) 760 self.expand(' -- ' + category) 761 self.write('<DD>\n') 762 self.index('fn', name) 763 764 def end_deffn(self): 765 self.write('</DL>\n') 766 767 def bgn_defun(self, args): self.bgn_deffn('Function ' + args) 768 end_defun = end_deffn 769 770 def bgn_defmac(self, args): self.bgn_deffn('Macro ' + args) 771 end_defmac = end_deffn 772 773 def bgn_defspec(self, args): self.bgn_deffn('{Special Form} ' + args) 774 end_defspec = end_deffn 775 776 def bgn_defvr(self, args): 777 self.write('<DL><DT>') 778 words = splitwords(args, 2) 779 [category, name], rest = words[:2], words[2:] 780 self.expand('@code{' + name + '}') 781 # If there are too many arguments, show them 782 for word in rest: self.expand(' ' + word) 783 self.expand(' -- ' + category) 784 self.write('<DD>\n') 785 self.index('vr', name) 786 787 end_defvr = end_deffn 788 789 def bgn_defvar(self, args): self.bgn_defvr('Variable ' + args) 790 end_defvar = end_defvr 791 792 def bgn_defopt(self, args): self.bgn_defvr('{User Option} ' + args) 793 end_defopt = end_defvr 794 795 # --- Ditto for typed languages --- 796 797 def bgn_deftypefn(self, args): 798 self.write('<DL><DT>') 799 words = splitwords(args, 3) 800 [category, datatype, name], rest = words[:3], words[3:] 801 self.expand('@code{' + datatype + '} @b{' + name + '}') 802 for word in rest: self.expand(' ' + makevar(word)) 803 self.expand(' -- ' + category) 804 self.write('<DD>\n') 805 self.index('fn', name) 806 807 end_deftypefn = end_deffn 808 809 def bgn_deftypefun(self, args): self.bgn_deftypefn('Function ' + args) 810 end_deftypefun = end_deftypefn 811 812 def bgn_deftypevr(self, args): 813 words = splitwords(args, 3) 814 [category, datatype, name], rest = words[:3], words[3:] 815 self.write('<DL><DT>') 816 self.expand('@code{' + datatype + '} @b{' + name + '}') 817 # If there are too many arguments, show them 818 for word in rest: self.expand(' ' + word) 819 self.expand(' -- ' + category) 820 self.write('<DD>\n') 821 self.index('fn', name) 822 823 end_deftypevr = end_deftypefn 824 825 def bgn_deftypevar(self, args): 826 self.bgn_deftypevr('Variable ' + args) 827 end_deftypevar = end_deftypevr 828 829 # --- Ditto for object-oriented languages --- 830 831 def bgn_defcv(self, args): 832 words = splitwords(args, 3) 833 [category, classname, name], rest = words[:3], words[3:] 834 self.write('<DL><DT>') 835 self.expand('@b{' + name + '}') 836 # If there are too many arguments, show them 837 for word in rest: self.expand(' ' + word) 838 self.expand(' -- ' + category + ' of ' + classname) 839 self.write('<DD>\n') 840 self.index('vr', name + ' @r{of ' + classname + '}') 841 842 end_defcv = end_deftypevr 843 844 def bgn_defivar(self, args): 845 self.bgn_defcv('{Instance Variable} ' + args) 846 end_defivar = end_defcv 847 848 def bgn_defop(self, args): 849 self.write('<DL><DT>') 850 words = splitwords(args, 3) 851 [category, classname, name], rest = words[:3], words[3:] 852 self.expand('@b{' + name + '}') 853 for word in rest: self.expand(' ' + makevar(word)) 854 self.expand(' -- ' + category + ' on ' + classname) 855 self.write('<DD>\n') 856 self.index('fn', name + ' @r{on ' + classname + '}') 857 858 end_defop = end_defcv 859 860 def bgn_defmethod(self, args): 861 self.bgn_defop('Method ' + args) 862 end_defmethod = end_defop 863 864 # --- Ditto for data types --- 865 866 def bgn_deftp(self, args): 867 self.write('<DL><DT>') 868 words = splitwords(args, 2) 869 [category, name], rest = words[:2], words[2:] 870 self.expand('@b{' + name + '}') 871 for word in rest: self.expand(' ' + word) 872 self.expand(' -- ' + category) 873 self.write('<DD>\n') 874 self.index('tp', name) 875 876 end_deftp = end_defcv 877 878 # --- Making Lists and Tables 879 880 def bgn_enumerate(self, args): 881 if not args: args = '1' 882 self.itemnumber = args 883 self.write('<UL>\n') 884 def end_enumerate(self): 885 self.itemnumber = None 886 self.write('</UL>\n') 887 888 def bgn_itemize(self, args): 889 self.itemarg = args 890 self.write('<UL>\n') 891 def end_itemize(self): 892 self.itemarg = None 893 self.write('</UL>\n') 894 895 def bgn_table(self, args): 896 self.itemarg = args 897 self.write('<DL>\n') 898 def end_table(self): 899 self.itemarg = None 900 self.write('</DL>\n') 901 902 def bgn_ftable(self, args): 903 self.itemindex = 'fn' 904 self.bgn_table(args) 905 def end_ftable(self): 906 self.itemindex = None 907 self.end_table() 908 909 def do_item(self, args): 910 if self.itemindex: self.index(self.itemindex, args) 911 if self.itemarg: 912 if self.itemarg[0] == '@' and self.itemarg[1:2] and \ 913 self.itemarg[1] in string.letters: 914 args = self.itemarg + '{' + args + '}' 915 else: 916 # some other character, e.g. '-' 917 args = self.itemarg + ' ' + args 918 if self.itemnumber <> None: 919 args = self.itemnumber + '. ' + args 920 self.itemnumber = increment(self.itemnumber) 921 if self.stack and self.stack[-1] == 'table': 922 self.write('<DT>') 923 self.expand(args) 924 self.write('<DD>') 925 else: 926 self.write('<LI>') 927 self.expand(args) 928 self.write(' ') 929 do_itemx = do_item # XXX Should suppress leading blank line 930 931 # --- Enumerations, displays, quotations --- 932 # XXX Most of these should increase the indentation somehow 933 934 def bgn_quotation(self, args): self.write('<P>') 935 def end_quotation(self): self.write('<P>\n') 936 937 def bgn_example(self, args): 938 self.nofill = self.nofill + 1 939 self.write('<UL COMPACT><CODE>') 940 def end_example(self): 941 self.write('</CODE></UL>\n') 942 self.nofill = self.nofill - 1 943 944 bgn_lisp = bgn_example # Synonym when contents are executable lisp code 945 end_lisp = end_example 946 947 bgn_smallexample = bgn_example # XXX Should use smaller font 948 end_smallexample = end_example 949 950 bgn_smalllisp = bgn_lisp # Ditto 951 end_smalllisp = end_lisp 952 953 def bgn_display(self, args): 954 self.nofill = self.nofill + 1 955 self.write('<UL COMPACT>\n') 956 def end_display(self): 957 self.write('</UL>\n') 958 self.nofill = self.nofill - 1 959 960 def bgn_format(self, args): 961 self.nofill = self.nofill + 1 962 self.write('<UL COMPACT>\n') 963 def end_format(self): 964 self.write('</UL>\n') 965 self.nofill = self.nofill - 1 966 967 def do_exdent(self, args): self.expand(args + '\n') 968 # XXX Should really mess with indentation 969 970 def bgn_flushleft(self, args): 971 self.nofill = self.nofill + 1 972 self.write('<UL COMPACT>\n') 973 def end_flushleft(self): 974 self.write('</UL>\n') 975 self.nofill = self.nofill - 1 976 977 def bgn_flushright(self, args): 978 self.nofill = self.nofill + 1 979 self.write('<ADDRESS COMPACT>\n') 980 def end_flushright(self): 981 self.write('</ADDRESS>\n') 982 self.nofill = self.nofill - 1 983 984 def bgn_menu(self, args): self.write('<H2>Menu</H2><DL COMPACT>\n') 985 def end_menu(self): self.write('</DL>\n') 986 987 def bgn_cartouche(self, args): pass 988 def end_cartouche(self): pass 989 990 # --- Indices --- 991 992 def resetindex(self): 993 self.noncodeindices = ['cp'] 994 self.indextitle = {} 995 self.indextitle['cp'] = 'Concept' 996 self.indextitle['fn'] = 'Function' 997 self.indextitle['ky'] = 'Keyword' 998 self.indextitle['pg'] = 'Program' 999 self.indextitle['tp'] = 'Type' 1000 self.indextitle['vr'] = 'Variable' 1001 # 1002 self.whichindex = {} 1003 for name in self.indextitle.keys(): 1004 self.whichindex[name] = [] 1005 1006 def user_index(self, name, args): 1007 if self.whichindex.has_key(name): 1008 self.index(name, args) 1009 else: 1010 print '*** No index named', `name` 1011 1012 def do_cindex(self, args): self.index('cp', args) 1013 def do_findex(self, args): self.index('fn', args) 1014 def do_kindex(self, args): self.index('ky', args) 1015 def do_pindex(self, args): self.index('pg', args) 1016 def do_tindex(self, args): self.index('tp', args) 1017 def do_vindex(self, args): self.index('vr', args) 1018 1019 def index(self, name, args): 1020 self.whichindex[name].append(args, self.nodename) 1021 1022 def do_synindex(self, args): 1023 words = string.split(args) 1024 if len(words) <> 2: 1025 print '*** bad @synindex', args 1026 return 1027 [old, new] = words 1028 if not self.whichindex.has_key(old) or \ 1029 not self.whichindex.has_key(new): 1030 print '*** bad key(s) in @synindex', args 1031 return 1032 if old <> new and \ 1033 self.whichindex[old] is not self.whichindex[new]: 1034 inew = self.whichindex[new] 1035 inew[len(inew):] = self.whichindex[old] 1036 self.whichindex[old] = inew 1037 do_syncodeindex = do_synindex # XXX Should use code font 1038 1039 def do_printindex(self, args): 1040 words = string.split(args) 1041 for name in words: 1042 if self.whichindex.has_key(name): 1043 self.prindex(name) 1044 else: 1045 print '*** No index named', `name` 1046 1047 def prindex(self, name): 1048 iscodeindex = (name not in self.noncodeindices) 1049 index = self.whichindex[name] 1050 if not index: return 1051 if self.debugging: 1052 print '--- Generating', self.indextitle[name], 'index' 1053 # The node already provides a title 1054 index1 = [] 1055 junkprog = regex.compile('^\(@[a-z]+\)?{') 1056 for key, node in index: 1057 sortkey = string.lower(key) 1058 # Remove leading `@cmd{' from sort key 1059 # -- don't bother about the matching `}' 1060 oldsortkey = sortkey 1061 while 1: 1062 i = junkprog.match(sortkey) 1063 if i < 0: break 1064 sortkey = sortkey[i:] 1065 index1.append(sortkey, key, node) 1066 del index[:] 1067 index1.sort() 1068 self.write('<DL COMPACT>\n') 1069 for sortkey, key, node in index1: 1070 if self.debugging > 1: print key, ':', node 1071 self.write('<DT>') 1072 if iscodeindex: key = '@code{' + key + '}' 1073 self.expand(key) 1074 self.write('<DD><A HREF="', makefile(node), \ 1075 '">', node, '</A>\n') 1076 self.write('</DL>\n') 1077 1078 # --- Final error reports --- 1079 1080 def report(self): 1081 if self.unknown: 1082 print '--- Unrecognized commands ---' 1083 cmds = self.unknown.keys() 1084 cmds.sort() 1085 for cmd in cmds: 1086 print string.ljust(cmd, 20), self.unknown[cmd] 1087 1088 1089# Put @var{} around alphabetic substrings 1090def makevar(str): 1091 # XXX This breaks if str contains @word{...} 1092 return regsub.gsub('\([a-zA-Z_][a-zA-Z0-9_]*\)', '@var{\\1}', str) 1093 1094 1095# Split a string in "words" according to findwordend 1096def splitwords(str, minlength): 1097 words = [] 1098 i = 0 1099 n = len(str) 1100 while i < n: 1101 while i < n and str[i] in ' \t\n': i = i+1 1102 if i >= n: break 1103 start = i 1104 i = findwordend(str, i, n) 1105 words.append(str[start:i]) 1106 while len(words) < minlength: words.append('') 1107 return words 1108 1109 1110# Find the end of a "word", matching braces and interpreting @@ @{ @} 1111fwprog = regex.compile('[@{} ]') 1112def findwordend(str, i, n): 1113 level = 0 1114 while i < n: 1115 i = fwprog.search(str, i) 1116 if i < 0: break 1117 c = str[i]; i = i+1 1118 if c == '@': i = i+1 # Next character is not special 1119 elif c == '{': level = level+1 1120 elif c == '}': level = level-1 1121 elif c == ' ' and level <= 0: return i-1 1122 return n 1123 1124 1125# Convert a node name into a file name 1126def makefile(nodename): 1127 return string.lower(fixfunnychars(nodename)) + '.html' 1128 1129 1130# Characters that are perfectly safe in filenames and hyperlinks 1131goodchars = string.letters + string.digits + '!@-_=+.' 1132 1133# Replace characters that aren't perfectly safe by underscores 1134def fixfunnychars(addr): 1135 i = 0 1136 while i < len(addr): 1137 c = addr[i] 1138 if c not in goodchars: 1139 c = '_' 1140 addr = addr[:i] + c + addr[i+1:] 1141 i = i + len(c) 1142 return addr 1143 1144 1145# Increment a string used as an enumeration 1146def increment(s): 1147 if not s: 1148 return '1' 1149 for sequence in string.digits, string.lowercase, string.uppercase: 1150 lastc = s[-1] 1151 if lastc in sequence: 1152 i = string.index(sequence, lastc) + 1 1153 if i >= len(sequence): 1154 if len(s) == 1: 1155 s = sequence[0]*2 1156 if s == '00': 1157 s = '10' 1158 else: 1159 s = increment(s[:-1]) + sequence[0] 1160 else: 1161 s = s[:-1] + sequence[i] 1162 return s 1163 return s # Don't increment 1164 1165 1166def test(): 1167 import sys 1168 parser = TexinfoParser() 1169 while sys.argv[1:2] == ['-d']: 1170 parser.debugging = parser.debugging + 1 1171 del sys.argv[1:2] 1172 if len(sys.argv) <> 3: 1173 print 'usage: texi2html [-d] [-d] inputfile outputdirectory' 1174 sys.exit(2) 1175 file = sys.argv[1] 1176 parser.setdirname(sys.argv[2]) 1177 if file == '-': 1178 fp = sys.stdin 1179 else: 1180 parser.setincludedir(os.path.dirname(file)) 1181 try: 1182 fp = open(file, 'r') 1183 except IOError, msg: 1184 print file, ':', msg 1185 sys.exit(1) 1186 parser.parse(fp) 1187 fp.close() 1188 parser.report() 1189 1190 1191test() 1192