1 #! /usr/local/bin/python
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.
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
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?)
32 MAGIC
= '\\input texinfo'
34 cmprog
= regex
.compile('^@\([a-z]+\)\([ \t]\|$\)') # Command (line-oriented)
35 blprog
= regex
.compile('^[ \t]*$') # Blank line
36 kwprog
= regex
.compile('@[a-z]+') # Keyword (embedded, usually with {} args)
37 spprog
= regex
.compile('[\n@{}&<>]') # Special characters in running text
38 miprog
= regex
.compile( \
39 '^\* \([^:]*\):\(:\|[ \t]*\([^\t,\n.]+\)\([^ \t\n]*\)\)[ \t\n]*')
44 # Initialize an instance
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
65 # Set (output) directory name
66 def setdirname(self
, dirname
):
67 self
.dirname
= dirname
69 # Set include directory name
70 def setincludedir(self
, includedir
):
71 self
.includedir
= includedir
73 # Parse the contents of an entire file
77 while line
and (line
[0] == '%' or blprog
.match(line
) >= 0):
80 if line
[:len(MAGIC
)] <> MAGIC
:
81 raise SyntaxError, 'file does not begin with '+`MAGIC`
82 self
.parserest(fp
, lineno
)
84 # Parse the contents of a file, not expecting a MAGIC header
85 def parserest(self
, fp
, initial_lineno
):
86 lineno
= initial_lineno
95 if not self
.skip
: self
.process(accu
)
97 if initial_lineno
> 0:
98 print '*** EOF before @bye'
101 if cmprog
.match(line
) >= 0:
102 a
, b
= cmprog
.regs
[1]
104 if cmd
in ('noindent', 'refill'):
112 elif blprog
.match(line
) >= 0:
119 # Append the line including trailing \n!
123 print '*** Still skipping at the end'
125 print '*** Stack not empty at the end'
126 print '***', self
.stack
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'
134 # Return the text saved so far and start writing to file again
135 def collectsavings(self
):
136 savetext
= self
.savetext
138 return savetext
or ''
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
146 self
.nodefp
.write(text
)
148 # Complete the current node -- write footnotes and close file
150 if self
.savetext
<> None:
151 print '*** Still saving text at end of node'
152 dummy
= self
.collectsavings()
154 self
.writefootnotes()
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 '...',
168 if self
.stack
and self
.stack
[-1] == 'menu':
169 # XXX should be done differently
171 if miprog
.match(line
) < 0:
172 line
= string
.strip(line
) + '\n'
175 (bgn
, end
), (a
, b
), (c
, d
), (e
, f
), (g
, h
) = \
179 if nodename
[0] == ':': nodename
= label
180 else: nodename
= line
[e
:f
]
182 self
.write('<DT><A HREF="', \
183 makefile(nodename
), \
184 '" TYPE=Menu>', nodename
, \
185 '</A>', punct
, '\n<DD>')
186 self
.expand(line
[end
:])
188 text
= string
.joinfields(accu
, '')
191 # Write a string, expanding embedded @-commands
192 def expand(self
, text
):
198 i
= spprog
.search(text
, i
)
200 self
.write(text
[start
:])
202 self
.write(text
[start
:i
])
225 print '*** Unmatched }'
231 method
= getattr(self
, 'close_' + cmd
)
232 except AttributeError:
233 self
.unknown_close(cmd
)
238 # Cannot happen unless spprog is changed
239 raise RuntimeError, 'unexpected funny '+`c`
241 while i
< n
and text
[i
] in string
.letters
: i
= i
+1
243 # @ plus non-letter: literal next character
247 # `@:' means no extra space after
248 # preceding `.', `?', `!' or `:'
251 # `@.' means a sentence-ending period;
252 # `@@', `@{', `@}' quote `@', `{', `}'
256 if i
< n
and text
[i
] == '{':
260 method
= getattr(self
, 'open_' + cmd
)
261 except AttributeError:
262 self
.unknown_open(cmd
)
267 method
= getattr(self
, 'handle_' + cmd
)
268 except AttributeError:
269 self
.unknown_handle(cmd
)
273 print '*** Stack not empty at para:', stack
275 # --- Handle unknown embedded @-commands ---
277 def unknown_open(self
, cmd
):
278 print '*** No open func for @' + cmd
+ '{...}'
281 if not self
.unknown
.has_key(cmd
):
282 self
.unknown
[cmd
] = 1
284 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
286 def unknown_close(self
, cmd
):
287 print '*** No close func for @' + cmd
+ '{...}'
290 if not self
.unknown
.has_key(cmd
):
291 self
.unknown
[cmd
] = 1
293 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
295 def unknown_handle(self
, cmd
):
296 print '*** No handler for @' + cmd
298 if not self
.unknown
.has_key(cmd
):
299 self
.unknown
[cmd
] = 1
301 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
303 # XXX The following sections should be ordered as the texinfo docs
305 # --- Embedded @-commands without {} argument list --
307 def handle_noindent(self
): pass
309 def handle_refill(self
): pass
311 # --- Include file handling ---
313 def do_include(self
, args
):
315 file = os
.path
.join(self
.includedir
, file)
319 print '*** Can\'t open include file', `
file`
322 print '--> file', `
file`
323 save_done
= self
.done
324 save_skip
= self
.skip
325 save_stack
= self
.stack
326 self
.parserest(fp
, 0)
328 self
.done
= save_done
329 self
.skip
= save_skip
330 self
.stack
= save_stack
332 print '<-- file', `
file`
334 # --- Special Insertions ---
336 def open_dmn(self
): pass
337 def close_dmn(self
): pass
339 def open_dots(self
): self
.write('...')
340 def close_dots(self
): pass
342 def open_bullet(self
): self
.write('•')
343 def close_bullet(self
): pass
345 def open_TeX(self
): self
.write('TeX')
346 def close_TeX(self
): pass
348 def open_copyright(self
): self
.write('(C)')
349 def close_copyright(self
): pass
351 def open_minus(self
): self
.write('-')
352 def close_minus(self
): pass
354 # --- Special Glyphs for Examples ---
356 def open_result(self
): self
.write('=>')
357 def close_result(self
): pass
359 def open_expansion(self
): self
.write('==>')
360 def close_expansion(self
): pass
362 def open_print(self
): self
.write('-|')
363 def close_print(self
): pass
365 def open_error(self
): self
.write('error-->')
366 def close_error(self
): pass
368 def open_equiv(self
): self
.write('==')
369 def close_equiv(self
): pass
371 def open_point(self
): self
.write('-!-')
372 def close_point(self
): pass
374 # --- Cross References ---
376 def open_pxref(self
):
379 def close_pxref(self
):
385 def close_xref(self
):
393 def open_inforef(self
):
394 self
.write('See info file ')
396 def close_inforef(self
):
397 text
= self
.collectsavings()
398 args
= string
.splitfields(text
, ',')
401 args
[i
] = string
.strip(args
[i
])
402 while len(args
) < 3: args
.append('')
405 self
.write('`', file, '\', node `', node
, '\'')
408 text
= self
.collectsavings()
409 args
= string
.splitfields(text
, ',')
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]
418 href
= makefile(nodename
)
420 href
= '../' + file + '/' + href
421 self
.write('<A HREF="', href
, '">', label
, '</A>')
423 # --- Marking Words and Phrases ---
425 # --- Other @xxx{...} commands ---
427 def open_(self
): pass # Used by {text enclosed in braces}
428 def close_(self
): pass
433 def open_cite(self
): self
.write('<CITE>')
434 def close_cite(self
): self
.write('</CITE>')
436 def open_code(self
): self
.write('<CODE>')
437 def close_code(self
): self
.write('</CODE>')
442 def open_dfn(self
): self
.write('<DFN>')
443 def close_dfn(self
): self
.write('</DFN>')
445 def open_emph(self
): self
.write('<I>')
446 def close_emph(self
): self
.write('</I>')
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>')
459 def close_footnote(self
):
460 id = len(self
.footnotes
) + 1
461 self
.footnotes
.append(`
id`
, self
.savetext
)
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')
472 def open_file(self
): self
.write('<FILE>')
473 def close_file(self
): self
.write('</FILE>')
475 def open_kbd(self
): self
.write('<KBD>')
476 def close_kbd(self
): self
.write('</KBD>')
478 def open_key(self
): self
.write('<KEY>')
479 def close_key(self
): self
.write('</KEY>')
481 def open_r(self
): self
.write('<R>')
482 def close_r(self
): self
.write('</R>')
484 def open_samp(self
): self
.write('`<SAMP>')
485 def close_samp(self
): self
.write('</SAMP>\'')
487 def open_sc(self
): self
.write('<SMALLCAPS>')
488 def close_sc(self
): self
.write('</SMALLCAPS>')
490 def open_strong(self
): self
.write('<B>')
491 def close_strong(self
): self
.write('</B>')
494 close_b
= close_strong
496 def open_var(self
): self
.write('<VAR>')
497 def close_var(self
): self
.write('</VAR>')
499 def open_w(self
): self
.write('<NOBREAK>')
500 def close_w(self
): self
.write('</NOBREAK>')
502 open_titlefont
= open_
503 close_titlefont
= close_
505 def command(self
, line
):
506 a
, b
= cmprog
.regs
[1]
508 args
= string
.strip(line
[b
:])
509 if self
.debugging
> 1:
510 print self
.skip
, self
.stack
, '@' + cmd
, args
512 func
= getattr(self
, 'do_' + cmd
)
513 except AttributeError:
515 func
= getattr(self
, 'bgn_' + cmd
)
516 except AttributeError:
517 self
.unknown_cmd(cmd
, args
)
519 self
.stack
.append(cmd
)
522 if not self
.skip
or cmd
== 'end':
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
530 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
532 def do_end(self
, args
):
533 words
= string
.split(args
)
535 print '*** @end w/o args'
538 if not self
.stack
or self
.stack
[-1] <> cmd
:
539 print '*** @end', cmd
, 'unexpected'
543 func
= getattr(self
, 'end_' + cmd
)
544 except AttributeError:
545 self
.unknown_end(cmd
)
549 def unknown_end(self
, cmd
):
551 print '*** unknown', '@' + cmd
552 if not self
.unknown
.has_key(cmd
):
553 self
.unknown
[cmd
] = 1
555 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
559 def do_comment(self
, args
): pass
562 # --- Conditional processing ---
564 def bgn_ifinfo(self
, args
): pass
565 def end_ifinfo(self
): pass
567 def bgn_iftex(self
, args
): self
.skip
= self
.skip
+ 1
568 def end_iftex(self
): self
.skip
= self
.skip
- 1
570 def bgn_ignore(self
, args
): self
.skip
= self
.skip
+ 1
571 def end_ignore(self
): self
.skip
= self
.skip
- 1
573 def bgn_tex(self
, args
): self
.skip
= self
.skip
+ 1
574 def end_tex(self
): self
.skip
= self
.skip
- 1
576 # --- Beginning a file ---
578 do_finalout
= do_comment
579 do_setchapternewpage
= do_comment
580 do_setfilename
= do_comment
582 def do_settitle(self
, args
):
585 # --- Ending a file ---
587 def do_bye(self
, args
):
592 def bgn_titlepage(self
, args
): self
.skip
= self
.skip
+ 1
593 def end_titlepage(self
): self
.skip
= self
.skip
- 1
595 def do_center(self
, args
):
596 # Actually not used outside title page...
597 self
.write('<H1>', args
, '</H1>\n')
599 do_subtitle
= do_center
600 do_author
= do_center
602 do_vskip
= do_comment
603 do_vfill
= do_comment
604 do_smallbook
= do_comment
606 do_paragraphindent
= do_comment
607 do_setchapternewpage
= do_comment
608 do_headings
= do_comment
609 do_footnotestyle
= do_comment
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
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]
626 file = self
.dirname
+ '/' + makefile(name
)
627 if self
.debugging
: print '--- writing', file
628 self
.nodefp
= open(file, 'w')
630 if not self
.topname
: self
.topname
= 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
)
637 if self
.nodename
<> self
.topname
:
638 self
.link('Top', self
.topname
)
640 def link(self
, label
, nodename
):
642 if string
.lower(nodename
) == '(dir)':
645 addr
= makefile(nodename
)
646 self
.write(label
, ': <A HREF="', addr
, '" TYPE="', \
647 label
, '">', nodename
, '</A> \n')
649 # --- Sectioning commands ---
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)
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)
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)
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)
692 def heading(self
, type, args
, level
):
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
699 for i
in self
.numbering
:
701 args
= x
+ ' ' + args
702 self
.contents
.append(level
, args
, self
.nodename
)
703 self
.write('<', type, '>')
705 self
.write('</', type, '>\n')
709 def do_contents(self
, args
):
711 # self.listcontents('Table of Contents', 999)
713 def do_shortcontents(self
, args
):
715 # self.listcontents('Short Contents', 0)
716 do_summarycontents
= do_shortcontents
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
), '">')
725 self
.write('</A> ', node
, '\n')
726 self
.write('</UL>\n')
728 # --- Page lay-out ---
730 # These commands are only meaningful in printed text
732 def do_page(self
, args
): pass
734 def do_need(self
, args
): pass
736 def bgn_group(self
, args
): pass
737 def end_group(self
): pass
739 # --- Line lay-out ---
741 def do_sp(self
, args
):
742 # Insert <args> blank lines
745 n
= string
.atoi(args
)
746 except string
.atoi_error
:
750 self
.write('<P>\n'*max(n
, 0))
752 # --- Function and variable definitions ---
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
)
762 self
.index('fn', name
)
765 self
.write('</DL>\n')
767 def bgn_defun(self
, args
): self
.bgn_deffn('Function ' + args
)
768 end_defun
= end_deffn
770 def bgn_defmac(self
, args
): self
.bgn_deffn('Macro ' + args
)
771 end_defmac
= end_deffn
773 def bgn_defspec(self
, args
): self
.bgn_deffn('{Special Form} ' + args
)
774 end_defspec
= end_deffn
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
)
785 self
.index('vr', name
)
787 end_defvr
= end_deffn
789 def bgn_defvar(self
, args
): self
.bgn_defvr('Variable ' + args
)
790 end_defvar
= end_defvr
792 def bgn_defopt(self
, args
): self
.bgn_defvr('{User Option} ' + args
)
793 end_defopt
= end_defvr
795 # --- Ditto for typed languages ---
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
)
805 self
.index('fn', name
)
807 end_deftypefn
= end_deffn
809 def bgn_deftypefun(self
, args
): self
.bgn_deftypefn('Function ' + args
)
810 end_deftypefun
= end_deftypefn
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
)
821 self
.index('fn', name
)
823 end_deftypevr
= end_deftypefn
825 def bgn_deftypevar(self
, args
):
826 self
.bgn_deftypevr('Variable ' + args
)
827 end_deftypevar
= end_deftypevr
829 # --- Ditto for object-oriented languages ---
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
)
840 self
.index('vr', name
+ ' @r{of ' + classname
+ '}')
842 end_defcv
= end_deftypevr
844 def bgn_defivar(self
, args
):
845 self
.bgn_defcv('{Instance Variable} ' + args
)
846 end_defivar
= end_defcv
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
)
856 self
.index('fn', name
+ ' @r{on ' + classname
+ '}')
858 end_defop
= end_defcv
860 def bgn_defmethod(self
, args
):
861 self
.bgn_defop('Method ' + args
)
862 end_defmethod
= end_defop
864 # --- Ditto for data types ---
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
)
874 self
.index('tp', name
)
876 end_deftp
= end_defcv
878 # --- Making Lists and Tables
880 def bgn_enumerate(self
, args
):
881 if not args
: args
= '1'
882 self
.itemnumber
= args
884 def end_enumerate(self
):
885 self
.itemnumber
= None
886 self
.write('</UL>\n')
888 def bgn_itemize(self
, args
):
891 def end_itemize(self
):
893 self
.write('</UL>\n')
895 def bgn_table(self
, args
):
900 self
.write('</DL>\n')
902 def bgn_ftable(self
, args
):
903 self
.itemindex
= 'fn'
905 def end_ftable(self
):
906 self
.itemindex
= None
909 def do_item(self
, args
):
910 if self
.itemindex
: self
.index(self
.itemindex
, args
)
912 if self
.itemarg
[0] == '@' and self
.itemarg
[1:2] and \
913 self
.itemarg
[1] in string
.letters
:
914 args
= self
.itemarg
+ '{' + args
+ '}'
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':
929 do_itemx
= do_item
# XXX Should suppress leading blank line
931 # --- Enumerations, displays, quotations ---
932 # XXX Most of these should increase the indentation somehow
934 def bgn_quotation(self
, args
): self
.write('<P>')
935 def end_quotation(self
): self
.write('<P>\n')
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
944 bgn_lisp
= bgn_example
# Synonym when contents are executable lisp code
945 end_lisp
= end_example
947 bgn_smallexample
= bgn_example
# XXX Should use smaller font
948 end_smallexample
= end_example
950 bgn_smalllisp
= bgn_lisp
# Ditto
951 end_smalllisp
= end_lisp
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
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
967 def do_exdent(self
, args
): self
.expand(args
+ '\n')
968 # XXX Should really mess with indentation
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
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
984 def bgn_menu(self
, args
): self
.write('<H2>Menu</H2><DL COMPACT>\n')
985 def end_menu(self
): self
.write('</DL>\n')
987 def bgn_cartouche(self
, args
): pass
988 def end_cartouche(self
): pass
992 def resetindex(self
):
993 self
.noncodeindices
= ['cp']
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'
1002 self
.whichindex
= {}
1003 for name
in self
.indextitle
.keys():
1004 self
.whichindex
[name
] = []
1006 def user_index(self
, name
, args
):
1007 if self
.whichindex
.has_key(name
):
1008 self
.index(name
, args
)
1010 print '*** No index named', `name`
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
)
1019 def index(self
, name
, args
):
1020 self
.whichindex
[name
].append(args
, self
.nodename
)
1022 def do_synindex(self
, args
):
1023 words
= string
.split(args
)
1025 print '*** bad @synindex', args
1028 if not self
.whichindex
.has_key(old
) or \
1029 not self
.whichindex
.has_key(new
):
1030 print '*** bad key(s) in @synindex', args
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
1039 def do_printindex(self
, args
):
1040 words
= string
.split(args
)
1042 if self
.whichindex
.has_key(name
):
1045 print '*** No index named', `name`
1047 def prindex(self
, name
):
1048 iscodeindex
= (name
not in self
.noncodeindices
)
1049 index
= self
.whichindex
[name
]
1050 if not index
: return
1052 print '--- Generating', self
.indextitle
[name
], 'index'
1053 # The node already provides a title
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
1062 i
= junkprog
.match(sortkey
)
1064 sortkey
= sortkey
[i
:]
1065 index1
.append(sortkey
, key
, node
)
1068 self
.write('<DL COMPACT>\n')
1069 for sortkey
, key
, node
in index1
:
1070 if self
.debugging
> 1: print key
, ':', node
1072 if iscodeindex
: key
= '@code{' + key
+ '}'
1074 self
.write('<DD><A HREF="', makefile(node
), \
1075 '">', node
, '</A>\n')
1076 self
.write('</DL>\n')
1078 # --- Final error reports ---
1082 print '--- Unrecognized commands ---'
1083 cmds
= self
.unknown
.keys()
1086 print string
.ljust(cmd
, 20), self
.unknown
[cmd
]
1089 # Put @var{} around alphabetic substrings
1091 # XXX This breaks if str contains @word{...}
1092 return regsub
.gsub('\([a-zA-Z_][a-zA-Z0-9_]*\)', '@var{\\1}', str)
1095 # Split a string in "words" according to findwordend
1096 def splitwords(str, minlength
):
1101 while i
< n
and str[i
] in ' \t\n': i
= i
+1
1104 i
= findwordend(str, i
, n
)
1105 words
.append(str[start
:i
])
1106 while len(words
) < minlength
: words
.append('')
1110 # Find the end of a "word", matching braces and interpreting @@ @{ @}
1111 fwprog
= regex
.compile('[@{} ]')
1112 def findwordend(str, i
, n
):
1115 i
= fwprog
.search(str, i
)
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
1125 # Convert a node name into a file name
1126 def makefile(nodename
):
1127 return string
.lower(fixfunnychars(nodename
)) + '.html'
1130 # Characters that are perfectly safe in filenames and hyperlinks
1131 goodchars
= string
.letters
+ string
.digits
+ '!@-_=+.'
1133 # Replace characters that aren't perfectly safe by underscores
1134 def fixfunnychars(addr
):
1136 while i
< len(addr
):
1138 if c
not in goodchars
:
1140 addr
= addr
[:i
] + c
+ addr
[i
+1:]
1145 # Increment a string used as an enumeration
1149 for sequence
in string
.digits
, string
.lowercase
, string
.uppercase
:
1151 if lastc
in sequence
:
1152 i
= string
.index(sequence
, lastc
) + 1
1153 if i
>= len(sequence
):
1159 s
= increment(s
[:-1]) + sequence
[0]
1161 s
= s
[:-1] + sequence
[i
]
1163 return s
# Don't increment
1168 parser
= TexinfoParser()
1169 while sys
.argv
[1:2] == ['-d']:
1170 parser
.debugging
= parser
.debugging
+ 1
1172 if len(sys
.argv
) <> 3:
1173 print 'usage: texi2html [-d] [-d] inputfile outputdirectory'
1176 parser
.setdirname(sys
.argv
[2])
1180 parser
.setincludedir(os
.path
.dirname(file))
1182 fp
= open(file, 'r')
1183 except IOError, msg
:
1184 print file, ':', msg