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] [-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.
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):
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?)
25 # More consistent handling of chapters/sections/etc.
26 # Lots of documentation
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
34 # Support the most recent texinfo version and take a good look at HTML 3.0
35 # More debugging output (customizable) and more fexible error handling
43 MAGIC
= '\\input texinfo'
45 cmprog
= regex
.compile('^@\([a-z]+\)\([ \t]\|$\)') # Command (line-oriented)
46 blprog
= regex
.compile('^[ \t]*$') # Blank line
47 kwprog
= regex
.compile('@[a-z]+') # Keyword (embedded, usually with {} args)
48 spprog
= regex
.compile('[\n@{}&<>]') # Special characters in running text
49 miprog
= regex
.compile( \
50 '^\* \([^:]*\):\(:\|[ \t]*\([^\t,\n.]+\)\([^ \t\n]*\)\)[ \t\n]*')
56 Some of the parser's functionality is separated into this class.
58 A Node accumulates its contents, takes care of links to other Nodes
59 and saves itself when it is finished and all links are resolved. """
61 def __init__ (self
, dir, name
, topname
, title
, next
, prev
, up
):
65 self
.topname
= topname
76 def write (self
, *lines
):
78 self
.lines
.append (line
)
81 fp
= open (self
.dirname
+ '/' + makefile(self
.name
), 'w')
82 fp
.write (self
.prologue
)
84 fp
.write (self
.epilogue
)
88 def link(self
, label
, nodename
):
90 if string
.lower(nodename
) == '(dir)':
93 addr
= makefile(nodename
)
94 self
.write(label
, ': <A HREF="', addr
, '" TYPE="', \
95 label
, '">', nodename
, '</A> \n')
99 length
= len (self
.lines
)
100 self
.text
= string
.joinfields (self
.lines
, '')
102 self
.write ('<BR> <HR>\n')
103 if self
.cont
!= self
.next
:
104 self
.link('Cont', self
.cont
)
105 self
.link('Next', self
.next
)
106 self
.link('Prev', self
.prev
)
107 self
.link('Up', self
.up
)
108 if self
.name
<> self
.topname
:
109 self
.link('Top', self
.topname
)
110 self
.write ('<BR> <HR> <P>\n')
111 links
= string
.joinfields (self
.lines
, '')
115 '<!DOCTYPE HTML PUBLIC "-//W3O//DTD W3 HTML 2.0//EN">\n' + \
116 '<!- Converted with texi2html and Python>\n' + \
118 '<TITLE>' + self
.title
+ '</TITLE>\n' + \
119 '</HEAD>\n<BODY>\n<P>\n' + \
123 self
.epilogue
= links
+ '</BODY>\n'
125 self
.epilogue
= '</BODY>\n'
130 # Initialize an instance
132 self
.unknown
= {} # statistics about unknown @-commands
133 self
.filenames
= {} # Check for identical filenames
134 self
.debugging
= 0 # larger values produce more output
135 self
.nodefp
= None # open file we're writing to
136 self
.nodelineno
= 0 # Linenumber relative to node
137 self
.links
= None # Links from current node
138 self
.savetext
= None # If not None, save text head instead
139 self
.savestack
= [] # If not None, save text head instead
140 self
.dirname
= 'tmp' # directory where files are created
141 self
.includedir
= '.' # directory to search @include files
142 self
.nodename
= '' # name of current node
143 self
.topname
= '' # name of top node (first node seen)
144 self
.title
= '' # title of this whole Texinfo tree
145 self
.resetindex() # Reset all indices
146 self
.contents
= [] # Reset table of contents
147 self
.numbering
= [] # Reset section numbering counters
148 self
.nofill
= 0 # Normal operation: fill paragraphs
149 self
.values
={'html': 1} # Names that should be parsed in ifset
150 self
.stackinfo
={} # Keep track of state in the stack
151 # XXX The following should be reset per node?!
152 self
.footnotes
= [] # Reset list of footnotes
153 self
.itemarg
= None # Reset command used by @item
154 self
.itemnumber
= None # Reset number for @item in @enumerate
155 self
.itemindex
= None # Reset item index name
159 self
.includedepth
= 0
160 # Set (output) directory name
161 def setdirname(self
, dirname
):
162 self
.dirname
= dirname
164 # Set include directory name
165 def setincludedir(self
, includedir
):
166 self
.includedir
= includedir
168 # Parse the contents of an entire file
172 while line
and (line
[0] == '%' or blprog
.match(line
) >= 0):
175 if line
[:len(MAGIC
)] <> MAGIC
:
176 raise SyntaxError, 'file does not begin with '+`MAGIC`
177 self
.parserest(fp
, lineno
)
179 # Parse the contents of a file, not expecting a MAGIC header
180 def parserest(self
, fp
, initial_lineno
):
181 lineno
= initial_lineno
188 self
.nodelineno
= self
.nodelineno
+ 1
191 if not self
.skip
: self
.process(accu
)
193 if initial_lineno
> 0:
194 print '*** EOF before @bye'
197 if cmprog
.match(line
) >= 0:
198 a
, b
= cmprog
.regs
[1]
200 if cmd
in ('noindent', 'refill'):
208 elif blprog
.match(line
) >= 0 and \
209 'format' not in self
.stack
and \
210 'example' not in self
.stack
:
217 # Append the line including trailing \n!
221 print '*** Still skipping at the end'
223 print '*** Stack not empty at the end'
224 print '***', self
.stack
225 if self
.includedepth
== 0:
226 while self
.nodestack
:
227 self
.nodestack
[-1].finalize ()
228 self
.nodestack
[-1].flush ()
229 del self
.nodestack
[-1]
231 # Start saving text in a buffer instead of writing it to a file
232 def startsaving(self
):
233 if self
.savetext
<> None:
234 self
.savestack
.append (self
.savetext
)
235 # print '*** Recursively saving text, expect trouble'
238 # Return the text saved so far and start writing to file again
239 def collectsavings(self
):
240 savetext
= self
.savetext
241 if len (self
.savestack
) > 0:
242 self
.savetext
= self
.savestack
[-1]
243 del self
.savestack
[-1]
246 return savetext
or ''
248 # Write text to file, or save it in a buffer, or ignore it
249 def write(self
, *args
):
251 text
= string
.joinfields(args
, '')
255 if self
.savetext
<> None:
256 self
.savetext
= self
.savetext
+ text
258 self
.nodefp
.write(text
)
260 self
.node
.write (text
)
261 # Complete the current node -- write footnotes and close file
263 if self
.savetext
<> None:
264 print '*** Still saving text at end of node'
265 dummy
= self
.collectsavings()
267 self
.writefootnotes()
269 if self
.nodelineno
> 20:
270 self
.write ('<HR>\n')
271 [name
, next
, prev
, up
] = self
.nodelinks
[:4]
272 self
.link('Next', next
)
273 self
.link('Prev', prev
)
275 if self
.nodename
<> self
.topname
:
276 self
.link('Top', self
.topname
)
277 self
.write ('<HR>\n')
278 self
.write('</BODY>\n')
282 if not self
.cont
and \
283 (not self
.node
.type or \
284 (self
.node
.next
and self
.node
.prev
and self
.node
.up
)):
285 self
.node
.finalize ()
288 self
.nodestack
.append (self
.node
)
292 # Process a list of lines, expanding embedded @-commands
293 # This mostly distinguishes between menus and normal text
294 def process(self
, accu
):
295 if self
.debugging
> 1:
296 print self
.skip
, self
.stack
,
297 if accu
: print accu
[0][:30],
298 if accu
[0][30:] or accu
[1:]: print '...',
300 if self
.stack
and self
.stack
[-1] == 'menu':
301 # XXX should be done differently
303 if miprog
.match(line
) < 0:
304 line
= string
.strip(line
) + '\n'
307 (bgn
, end
), (a
, b
), (c
, d
), (e
, f
), (g
, h
) = \
311 if nodename
[0] == ':': nodename
= label
312 else: nodename
= line
[e
:f
]
314 self
.write('<DT><A HREF="', \
315 makefile(nodename
), \
316 '" TYPE=Menu>', nodename
, \
317 '</A>', punct
, '\n<DD>')
318 self
.expand(line
[end
:])
320 text
= string
.joinfields(accu
, '')
323 # Write a string, expanding embedded @-commands
324 def expand(self
, text
):
330 i
= spprog
.search(text
, i
)
332 self
.write(text
[start
:])
334 self
.write(text
[start
:i
])
357 print '*** Unmatched }'
363 method
= getattr(self
, 'close_' + cmd
)
364 except AttributeError:
365 self
.unknown_close(cmd
)
370 # Cannot happen unless spprog is changed
371 raise RuntimeError, 'unexpected funny '+`c`
373 while i
< n
and text
[i
] in string
.letters
: i
= i
+1
375 # @ plus non-letter: literal next character
379 # `@:' means no extra space after
380 # preceding `.', `?', `!' or `:'
383 # `@.' means a sentence-ending period;
384 # `@@', `@{', `@}' quote `@', `{', `}'
388 if i
< n
and text
[i
] == '{':
392 method
= getattr(self
, 'open_' + cmd
)
393 except AttributeError:
394 self
.unknown_open(cmd
)
399 method
= getattr(self
, 'handle_' + cmd
)
400 except AttributeError:
401 self
.unknown_handle(cmd
)
405 print '*** Stack not empty at para:', stack
407 # --- Handle unknown embedded @-commands ---
409 def unknown_open(self
, cmd
):
410 print '*** No open func for @' + cmd
+ '{...}'
413 if not self
.unknown
.has_key(cmd
):
414 self
.unknown
[cmd
] = 1
416 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
418 def unknown_close(self
, cmd
):
419 print '*** No close func for @' + cmd
+ '{...}'
422 if not self
.unknown
.has_key(cmd
):
423 self
.unknown
[cmd
] = 1
425 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
427 def unknown_handle(self
, cmd
):
428 print '*** No handler for @' + cmd
430 if not self
.unknown
.has_key(cmd
):
431 self
.unknown
[cmd
] = 1
433 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
435 # XXX The following sections should be ordered as the texinfo docs
437 # --- Embedded @-commands without {} argument list --
439 def handle_noindent(self
): pass
441 def handle_refill(self
): pass
443 # --- Include file handling ---
445 def do_include(self
, args
):
447 file = os
.path
.join(self
.includedir
, file)
451 print '*** Can\'t open include file', `
file`
454 print '--> file', `
file`
455 save_done
= self
.done
456 save_skip
= self
.skip
457 save_stack
= self
.stack
458 self
.includedepth
= self
.includedepth
+ 1
459 self
.parserest(fp
, 0)
460 self
.includedepth
= self
.includedepth
- 1
462 self
.done
= save_done
463 self
.skip
= save_skip
464 self
.stack
= save_stack
466 print '<-- file', `
file`
468 # --- Special Insertions ---
470 def open_dmn(self
): pass
471 def close_dmn(self
): pass
473 def open_dots(self
): self
.write('...')
474 def close_dots(self
): pass
476 def open_bullet(self
): pass
477 def close_bullet(self
): pass
479 def open_TeX(self
): self
.write('TeX')
480 def close_TeX(self
): pass
482 def handle_copyright(self
): self
.write('(C)')
483 def open_copyright(self
): self
.write('(C)')
484 def close_copyright(self
): pass
486 def open_minus(self
): self
.write('-')
487 def close_minus(self
): pass
489 # --- Special Glyphs for Examples ---
491 def open_result(self
): self
.write('=>')
492 def close_result(self
): pass
494 def open_expansion(self
): self
.write('==>')
495 def close_expansion(self
): pass
497 def open_print(self
): self
.write('-|')
498 def close_print(self
): pass
500 def open_error(self
): self
.write('error-->')
501 def close_error(self
): pass
503 def open_equiv(self
): self
.write('==')
504 def close_equiv(self
): pass
506 def open_point(self
): self
.write('-!-')
507 def close_point(self
): pass
509 # --- Cross References ---
511 def open_pxref(self
):
514 def close_pxref(self
):
520 def close_xref(self
):
528 def open_inforef(self
):
529 self
.write('See info file ')
531 def close_inforef(self
):
532 text
= self
.collectsavings()
533 args
= string
.splitfields(text
, ',')
536 args
[i
] = string
.strip(args
[i
])
537 while len(args
) < 3: args
.append('')
540 self
.write('`', file, '\', node `', node
, '\'')
543 text
= self
.collectsavings()
544 args
= string
.splitfields(text
, ',')
547 args
[i
] = string
.strip(args
[i
])
548 while len(args
) < 5: args
.append('')
549 nodename
= label
= args
[0]
550 if args
[2]: label
= args
[2]
553 href
= makefile(nodename
)
555 href
= '../' + file + '/' + href
556 self
.write('<A HREF="', href
, '">', label
, '</A>')
558 # --- Marking Words and Phrases ---
560 # --- Other @xxx{...} commands ---
562 def open_(self
): pass # Used by {text enclosed in braces}
563 def close_(self
): pass
568 def open_cite(self
): self
.write('<CITE>')
569 def close_cite(self
): self
.write('</CITE>')
571 def open_code(self
): self
.write('<CODE>')
572 def close_code(self
): self
.write('</CODE>')
577 def open_dfn(self
): self
.write('<DFN>')
578 def close_dfn(self
): self
.write('</DFN>')
580 def open_emph(self
): self
.write('<I>')
581 def close_emph(self
): self
.write('</I>')
586 def open_footnote(self
):
587 # if self.savetext <> None:
588 # print '*** Recursive footnote -- expect weirdness'
589 id = len(self
.footnotes
) + 1
590 self
.write('<A NAME="footnoteref', `
id`
, \
591 '" HREF="#footnotetext', `
id`
, '">(', `
id`
, ')</A>')
595 def close_footnote(self
):
596 id = len(self
.footnotes
) + 1
597 # self.footnotes.append(`id`, self.savetext)
598 self
.footnotes
.append(`
id`
, self
.collectsavings())
599 # self.savetext = None
601 def writefootnotes(self
):
602 self
.write('<H2>---------- Footnotes ----------</H2>\n')
603 for id, text
in self
.footnotes
:
604 self
.write('<A NAME="footnotetext', id, \
605 '" HREF="#footnoteref', id, '">(', \
606 id, ')</A>\n', text
, '<P>\n')
609 def open_file(self
): self
.write('<FILE>')
610 def close_file(self
): self
.write('</FILE>')
612 def open_kbd(self
): self
.write('<KBD>')
613 def close_kbd(self
): self
.write('</KBD>')
615 def open_key(self
): self
.write('<KEY>')
616 def close_key(self
): self
.write('</KEY>')
618 def open_r(self
): self
.write('<R>')
619 def close_r(self
): self
.write('</R>')
621 def open_samp(self
): self
.write('`<SAMP>')
622 def close_samp(self
): self
.write('</SAMP>\'')
624 def open_sc(self
): self
.write('<SMALLCAPS>')
625 def close_sc(self
): self
.write('</SMALLCAPS>')
627 def open_strong(self
): self
.write('<B>')
628 def close_strong(self
): self
.write('</B>')
631 close_b
= close_strong
633 def open_var(self
): self
.write('<VAR>')
634 def close_var(self
): self
.write('</VAR>')
636 def open_w(self
): self
.write('<NOBREAK>')
637 def close_w(self
): self
.write('</NOBREAK>')
639 open_titlefont
= open_
640 close_titlefont
= close_
642 def open_small(self
): pass
643 def close_small(self
): pass
645 def command(self
, line
):
646 a
, b
= cmprog
.regs
[1]
648 args
= string
.strip(line
[b
:])
649 if self
.debugging
> 1:
650 print self
.skip
, self
.stack
, '@' + cmd
, args
652 func
= getattr(self
, 'do_' + cmd
)
653 except AttributeError:
655 func
= getattr(self
, 'bgn_' + cmd
)
656 except AttributeError:
657 # don't complain if we are skipping anyway
659 self
.unknown_cmd(cmd
, args
)
661 self
.stack
.append(cmd
)
664 if not self
.skip
or cmd
== 'end':
667 def unknown_cmd(self
, cmd
, args
):
668 print '*** unknown', '@' + cmd
, args
669 if not self
.unknown
.has_key(cmd
):
670 self
.unknown
[cmd
] = 1
672 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
674 def do_end(self
, args
):
675 words
= string
.split(args
)
677 print '*** @end w/o args'
680 if not self
.stack
or self
.stack
[-1] <> cmd
:
681 print '*** @end', cmd
, 'unexpected'
685 func
= getattr(self
, 'end_' + cmd
)
686 except AttributeError:
687 self
.unknown_end(cmd
)
691 def unknown_end(self
, cmd
):
693 print '*** unknown', '@' + cmd
694 if not self
.unknown
.has_key(cmd
):
695 self
.unknown
[cmd
] = 1
697 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
701 def do_comment(self
, args
): pass
704 # --- Conditional processing ---
706 def bgn_ifinfo(self
, args
): pass
707 def end_ifinfo(self
): pass
709 def bgn_iftex(self
, args
): self
.skip
= self
.skip
+ 1
710 def end_iftex(self
): self
.skip
= self
.skip
- 1
712 def bgn_ignore(self
, args
): self
.skip
= self
.skip
+ 1
713 def end_ignore(self
): self
.skip
= self
.skip
- 1
715 def bgn_tex(self
, args
): self
.skip
= self
.skip
+ 1
716 def end_tex(self
): self
.skip
= self
.skip
- 1
718 def do_set(self
, args
):
719 fields
= string
.splitfields (args
, ' ')
724 value
= string
.joinfields (fields
[1:], ' ')
725 self
.values
[key
]=value
728 def do_clear(self
, args
):
729 self
.values
[args
] = None
731 def bgn_ifset(self
, args
):
732 if args
not in self
.values
.keys() \
733 or self
.values
[args
] is None:
734 self
.skip
= self
.skip
+ 1
735 self
.stackinfo
[len(self
.stack
)] = 1
737 self
.stackinfo
[len(self
.stack
)] = 0
741 if self
.stackinfo
[len(self
.stack
) + 1]:
742 self
.skip
= self
.skip
- 1
743 del self
.stackinfo
[len(self
.stack
) + 1]
745 def bgn_ifclear(self
, args
):
746 if args
in self
.values
.keys() \
747 and self
.values
[args
] is not None:
748 self
.skip
= self
.skip
+ 1
749 self
.stackinfo
[len(self
.stack
)] = 1
751 self
.stackinfo
[len(self
.stack
)] = 0
753 end_ifclear
= end_ifset
755 def open_value(self
):
758 def close_value(self
):
759 key
= self
.collectsavings ()
760 if key
in self
.values
.keys():
761 self
.write (self
.values
[key
])
763 print '*** Undefined value: ', key
765 # --- Beginning a file ---
767 do_finalout
= do_comment
768 do_setchapternewpage
= do_comment
769 do_setfilename
= do_comment
771 def do_settitle(self
, args
):
775 self
.title
= self
.collectsavings ()
777 def do_parskip(self
, args
): pass
779 # --- Ending a file ---
781 def do_bye(self
, args
):
787 def bgn_titlepage(self
, args
): self
.skip
= self
.skip
+ 1
788 def end_titlepage(self
): self
.skip
= self
.skip
- 1
789 def do_shorttitlepage(self
, args
): pass
791 def do_center(self
, args
):
792 # Actually not used outside title page...
795 self
.write ('</H1>\n')
797 do_subtitle
= do_center
798 do_author
= do_center
800 do_vskip
= do_comment
801 do_vfill
= do_comment
802 do_smallbook
= do_comment
804 do_paragraphindent
= do_comment
805 do_setchapternewpage
= do_comment
806 do_headings
= do_comment
807 do_footnotestyle
= do_comment
809 do_evenheading
= do_comment
810 do_evenfooting
= do_comment
811 do_oddheading
= do_comment
812 do_oddfooting
= do_comment
813 do_everyheading
= do_comment
814 do_everyfooting
= do_comment
818 def do_node(self
, args
):
821 parts
= string
.splitfields(args
, ',')
822 while len(parts
) < 4: parts
.append('')
823 for i
in range(4): parts
[i
] = string
.strip(parts
[i
])
824 self
.nodelinks
= parts
825 [name
, next
, prev
, up
] = parts
[:4]
826 file = self
.dirname
+ '/' + makefile(name
)
827 if self
.filenames
.has_key(file):
828 print '*** Filename already in use: ', file
830 if self
.debugging
: print '--- writing', file
831 self
.filenames
[file] = 1
832 # self.nodefp = open(file, 'w')
834 if self
.cont
and self
.nodestack
:
835 self
.nodestack
[-1].cont
= self
.nodename
836 if not self
.topname
: self
.topname
= name
838 if self
.title
: title
= title
+ ' -- ' + self
.title
839 # No idea what this means, but this is what latex2html writes
840 # self.write('<!DOCTYPE HTML PUBLIC "-//W3O//DTD W3 HTML 2.0//EN">\n')
841 # self.write('<!- Converted with texi2html and Python>\n')
842 # self.write ('<P>\n<HEAD>\n')
843 # self.write('<TITLE>', title, '</TITLE>\n')
844 # self.write ('</HEAD>\n<BODY>\n<P>\n<BR> <HR>\n')
845 # self.link('Next', next)
846 # self.link('Prev', prev)
847 # self.link('Up', up)
848 # if self.nodename <> self.topname:
849 # self.link('Top', self.topname)
850 # self.write ('<BR> <HR> <P>\n')
851 self
.node
= Node (self
.dirname
, self
.nodename
, self
.topname
, \
852 title
, next
, prev
, up
)
854 def link(self
, label
, nodename
):
856 if string
.lower(nodename
) == '(dir)':
859 addr
= makefile(nodename
)
860 self
.write(label
, ': <A HREF="', addr
, '" TYPE="', \
861 label
, '">', nodename
, '</A> \n')
863 # --- Sectioning commands ---
865 def popstack (self
, type):
867 self
.node
.type = type
868 while self
.nodestack
:
869 if self
.nodestack
[-1].type > type:
870 self
.nodestack
[-1].finalize ()
871 self
.nodestack
[-1].flush ()
872 del self
.nodestack
[-1]
873 elif self
.nodestack
[-1].type == type:
874 if not self
.nodestack
[-1].next
:
875 self
.nodestack
[-1].next
= self
.node
.name
876 if not self
.node
.prev
:
877 self
.node
.prev
= self
.nodestack
[-1].name
878 self
.nodestack
[-1].finalize ()
879 self
.nodestack
[-1].flush ()
880 del self
.nodestack
[-1]
882 if type > 1 and not self
.node
.up
:
883 self
.node
.up
= self
.nodestack
[-1].name
886 def do_chapter(self
, args
):
887 self
.heading('H1', args
, 0)
890 def do_unnumbered(self
, args
):
891 self
.heading('H1', args
, -1)
893 def do_appendix(self
, args
):
894 self
.heading('H1', args
, -1)
896 def do_top(self
, args
):
897 self
.heading('H1', args
, -1)
898 def do_chapheading(self
, args
):
899 self
.heading('H1', args
, -1)
900 def do_majorheading(self
, args
):
901 self
.heading('H1', args
, -1)
903 def do_section(self
, args
):
904 self
.heading('H1', args
, 1)
907 def do_unnumberedsec(self
, args
):
908 self
.heading('H1', args
, -1)
910 def do_appendixsec(self
, args
):
911 self
.heading('H1', args
, -1)
913 do_appendixsection
= do_appendixsec
914 def do_heading(self
, args
):
915 self
.heading('H1', args
, -1)
917 def do_subsection(self
, args
):
918 self
.heading('H2', args
, 2)
920 def do_unnumberedsubsec(self
, args
):
921 self
.heading('H2', args
, -1)
923 def do_appendixsubsec(self
, args
):
924 self
.heading('H2', args
, -1)
926 def do_subheading(self
, args
):
927 self
.heading('H2', args
, -1)
929 def do_subsubsection(self
, args
):
930 self
.heading('H3', args
, 3)
932 def do_unnumberedsubsubsec(self
, args
):
933 self
.heading('H3', args
, -1)
935 def do_appendixsubsubsec(self
, args
):
936 self
.heading('H3', args
, -1)
938 def do_subsubheading(self
, args
):
939 self
.heading('H3', args
, -1)
941 def heading(self
, type, args
, level
):
943 while len(self
.numbering
) <= level
:
944 self
.numbering
.append(0)
945 del self
.numbering
[level
+1:]
946 self
.numbering
[level
] = self
.numbering
[level
] + 1
948 for i
in self
.numbering
:
950 args
= x
+ ' ' + args
951 self
.contents
.append(level
, args
, self
.nodename
)
952 self
.write('<', type, '>')
954 self
.write('</', type, '>\n')
958 def do_contents(self
, args
):
960 self
.listcontents('Table of Contents', 999)
962 def do_shortcontents(self
, args
):
964 # self.listcontents('Short Contents', 0)
965 do_summarycontents
= do_shortcontents
967 def listcontents(self
, title
, maxlevel
):
968 self
.write('<H1>', title
, '</H1>\n<UL COMPACT>\n')
969 for level
, title
, node
in self
.contents
:
970 if level
<= maxlevel
:
971 self
.write('<LI>', '. '*level
, '<A HREF="', \
972 makefile(node
), '">')
974 self
.write('</A> ', node
, '\n')
975 self
.write('</UL>\n')
977 # --- Page lay-out ---
979 # These commands are only meaningful in printed text
981 def do_page(self
, args
): pass
983 def do_need(self
, args
): pass
985 def bgn_group(self
, args
): pass
986 def end_group(self
): pass
988 # --- Line lay-out ---
990 def do_sp(self
, args
):
991 # Insert <args> blank lines
994 n
= string
.atoi(args
)
995 except string
.atoi_error
:
999 self
.write('<P>\n'*max(n
, 0))
1001 def do_hline(self
, args
):
1004 # --- Function and variable definitions ---
1006 def bgn_deffn(self
, args
):
1008 self
.do_deffnx (args
)
1010 def end_deffn(self
):
1011 self
.write('</DL>\n')
1013 def do_deffnx(self
, args
):
1015 words
= splitwords(args
, 2)
1016 [category
, name
], rest
= words
[:2], words
[2:]
1017 self
.expand('@b{' + name
+ '}')
1018 for word
in rest
: self
.expand(' ' + makevar(word
))
1019 self
.expand(' -- ' + category
)
1020 self
.write('<DD>\n')
1021 self
.index('fn', name
)
1023 def bgn_defun(self
, args
): self
.bgn_deffn('Function ' + args
)
1024 end_defun
= end_deffn
1025 def do_defunx(self
, args
): self
.do_deffnx('Function ' + args
)
1027 def bgn_defmac(self
, args
): self
.bgn_deffn('Macro ' + args
)
1028 end_defmac
= end_deffn
1029 def do_defmacx(self
, args
): self
.do_deffnx('Macro ' + args
)
1031 def bgn_defspec(self
, args
): self
.bgn_deffn('{Special Form} ' + args
)
1032 end_defspec
= end_deffn
1033 def do_defspecx(self
, args
): self
.do_deffnx('{Special Form} ' + args
)
1035 def bgn_defvr(self
, args
):
1037 self
.do_defvrx (args
)
1039 end_defvr
= end_deffn
1041 def do_defvrx(self
, args
):
1043 words
= splitwords(args
, 2)
1044 [category
, name
], rest
= words
[:2], words
[2:]
1045 self
.expand('@code{' + name
+ '}')
1046 # If there are too many arguments, show them
1047 for word
in rest
: self
.expand(' ' + word
)
1048 self
.expand(' -- ' + category
)
1049 self
.write('<DD>\n')
1050 self
.index('vr', name
)
1052 def bgn_defvar(self
, args
): self
.bgn_defvr('Variable ' + args
)
1053 end_defvar
= end_defvr
1054 def do_defvarx(self
, args
): self
.do_defvrx('Variable ' + args
)
1056 def bgn_defopt(self
, args
): self
.bgn_defvr('{User Option} ' + args
)
1057 end_defopt
= end_defvr
1058 def do_defoptx(self
, args
): self
.do_defvrx('{User Option} ' + args
)
1060 # --- Ditto for typed languages ---
1062 def bgn_deftypefn(self
, args
):
1064 self
.do_deftypefnx (args
)
1066 end_deftypefn
= end_deffn
1068 def do_deftypefnx(self
, args
):
1070 words
= splitwords(args
, 3)
1071 [category
, datatype
, name
], rest
= words
[:3], words
[3:]
1072 self
.expand('@code{' + datatype
+ '} @b{' + name
+ '}')
1073 for word
in rest
: self
.expand(' ' + makevar(word
))
1074 self
.expand(' -- ' + category
)
1075 self
.write('<DD>\n')
1076 self
.index('fn', name
)
1079 def bgn_deftypefun(self
, args
): self
.bgn_deftypefn('Function ' + args
)
1080 end_deftypefun
= end_deftypefn
1081 def do_deftypefunx(self
, args
): self
.do_deftypefnx('Function ' + args
)
1083 def bgn_deftypevr(self
, args
):
1085 self
.do_deftypevrx (args
)
1087 end_deftypevr
= end_deftypefn
1089 def do_deftypevrx(self
, args
):
1091 words
= splitwords(args
, 3)
1092 [category
, datatype
, name
], rest
= words
[:3], words
[3:]
1093 self
.expand('@code{' + datatype
+ '} @b{' + name
+ '}')
1094 # If there are too many arguments, show them
1095 for word
in rest
: self
.expand(' ' + word
)
1096 self
.expand(' -- ' + category
)
1097 self
.write('<DD>\n')
1098 self
.index('fn', name
)
1100 def bgn_deftypevar(self
, args
):
1101 self
.bgn_deftypevr('Variable ' + args
)
1102 end_deftypevar
= end_deftypevr
1103 def do_deftypevarx(self
, args
):
1104 self
.do_deftypevrx('Variable ' + args
)
1106 # --- Ditto for object-oriented languages ---
1108 def bgn_defcv(self
, args
):
1110 self
.do_defcvx(args
)
1112 end_defcv
= end_deftypevr
1114 def do_defcvx(self
, args
):
1116 words
= splitwords(args
, 3)
1117 [category
, classname
, name
], rest
= words
[:3], words
[3:]
1118 self
.expand('@b{' + name
+ '}')
1119 # If there are too many arguments, show them
1120 for word
in rest
: self
.expand(' ' + word
)
1121 self
.expand(' -- ' + category
+ ' of ' + classname
)
1122 self
.write('<DD>\n')
1123 self
.index('vr', name
+ ' @r{of ' + classname
+ '}')
1125 def bgn_defivar(self
, args
):
1126 self
.bgn_defcv('{Instance Variable} ' + args
)
1127 end_defivar
= end_defcv
1128 def do_defivarx(self
, args
):
1129 self
.do_defcvx('{Instance Variable} ' + args
)
1131 def bgn_defop(self
, args
):
1133 self
.do_defopx (args
)
1135 end_defop
= end_defcv
1137 def do_defopx(self
, args
):
1139 words
= splitwords(args
, 3)
1140 [category
, classname
, name
], rest
= words
[:3], words
[3:]
1141 self
.expand('@b{' + name
+ '}')
1142 for word
in rest
: self
.expand(' ' + makevar(word
))
1143 self
.expand(' -- ' + category
+ ' on ' + classname
)
1144 self
.write('<DD>\n')
1145 self
.index('fn', name
+ ' @r{on ' + classname
+ '}')
1147 def bgn_defmethod(self
, args
):
1148 self
.bgn_defop('Method ' + args
)
1149 end_defmethod
= end_defop
1150 def do_defmethodx(self
, args
):
1151 self
.do_defopx('Method ' + args
)
1153 # --- Ditto for data types ---
1155 def bgn_deftp(self
, args
):
1157 self
.do_deftpx(args
)
1159 end_deftp
= end_defcv
1161 def do_deftpx(self
, args
):
1163 words
= splitwords(args
, 2)
1164 [category
, name
], rest
= words
[:2], words
[2:]
1165 self
.expand('@b{' + name
+ '}')
1166 for word
in rest
: self
.expand(' ' + word
)
1167 self
.expand(' -- ' + category
)
1168 self
.write('<DD>\n')
1169 self
.index('tp', name
)
1171 # --- Making Lists and Tables
1173 def bgn_enumerate(self
, args
):
1175 self
.write('<OL>\n')
1176 self
.stackinfo
[len(self
.stack
)] = '</OL>\n'
1178 self
.itemnumber
= args
1179 self
.write('<UL>\n')
1180 self
.stackinfo
[len(self
.stack
)] = '</UL>\n'
1181 def end_enumerate(self
):
1182 self
.itemnumber
= None
1183 self
.write(self
.stackinfo
[len(self
.stack
) + 1])
1184 del self
.stackinfo
[len(self
.stack
) + 1]
1186 def bgn_itemize(self
, args
):
1188 self
.write('<UL>\n')
1189 def end_itemize(self
):
1191 self
.write('</UL>\n')
1193 def bgn_table(self
, args
):
1195 self
.write('<DL>\n')
1196 def end_table(self
):
1198 self
.write('</DL>\n')
1200 def bgn_ftable(self
, args
):
1201 self
.itemindex
= 'fn'
1202 self
.bgn_table(args
)
1203 def end_ftable(self
):
1204 self
.itemindex
= None
1207 def do_item(self
, args
):
1208 if self
.itemindex
: self
.index(self
.itemindex
, args
)
1210 if self
.itemarg
[0] == '@' and self
.itemarg
[1:2] and \
1211 self
.itemarg
[1] in string
.letters
:
1212 args
= self
.itemarg
+ '{' + args
+ '}'
1214 # some other character, e.g. '-'
1215 args
= self
.itemarg
+ ' ' + args
1216 if self
.itemnumber
<> None:
1217 args
= self
.itemnumber
+ '. ' + args
1218 self
.itemnumber
= increment(self
.itemnumber
)
1219 if self
.stack
and self
.stack
[-1] == 'table':
1227 do_itemx
= do_item
# XXX Should suppress leading blank line
1229 # --- Enumerations, displays, quotations ---
1230 # XXX Most of these should increase the indentation somehow
1232 def bgn_quotation(self
, args
): self
.write('<P>')
1233 def end_quotation(self
): self
.write('<P>\n')
1235 def bgn_example(self
, args
):
1236 self
.nofill
= self
.nofill
+ 1
1237 self
.write('<PRE><CODE>')
1238 def end_example(self
):
1239 self
.write('</CODE></PRE>')
1240 self
.nofill
= self
.nofill
- 1
1242 bgn_lisp
= bgn_example
# Synonym when contents are executable lisp code
1243 end_lisp
= end_example
1245 bgn_smallexample
= bgn_example
# XXX Should use smaller font
1246 end_smallexample
= end_example
1248 bgn_smalllisp
= bgn_lisp
# Ditto
1249 end_smalllisp
= end_lisp
1251 def bgn_display(self
, args
):
1252 self
.nofill
= self
.nofill
+ 1
1253 self
.write('<PRE>\n')
1254 def end_display(self
):
1255 self
.write('</PRE>\n')
1256 self
.nofill
= self
.nofill
- 1
1258 def bgn_format(self
, args
):
1259 self
.nofill
= self
.nofill
+ 1
1260 self
.write('<PRE><CODE>\n')
1261 def end_format(self
):
1262 self
.write('</CODE></PRE>\n')
1263 self
.nofill
= self
.nofill
- 1
1265 def do_exdent(self
, args
): self
.expand(args
+ '\n')
1266 # XXX Should really mess with indentation
1268 def bgn_flushleft(self
, args
):
1269 self
.nofill
= self
.nofill
+ 1
1270 self
.write('<PRE>\n')
1271 def end_flushleft(self
):
1272 self
.write('</PRE>\n')
1273 self
.nofill
= self
.nofill
- 1
1275 def bgn_flushright(self
, args
):
1276 self
.nofill
= self
.nofill
+ 1
1277 self
.write('<ADDRESS COMPACT>\n')
1278 def end_flushright(self
):
1279 self
.write('</ADDRESS>\n')
1280 self
.nofill
= self
.nofill
- 1
1282 def bgn_menu(self
, args
): self
.write('<H2>Menu</H2><DL COMPACT>\n')
1283 def end_menu(self
): self
.write('</DL>\n')
1285 def bgn_cartouche(self
, args
): pass
1286 def end_cartouche(self
): pass
1290 def resetindex(self
):
1291 self
.noncodeindices
= ['cp']
1292 self
.indextitle
= {}
1293 self
.indextitle
['cp'] = 'Concept'
1294 self
.indextitle
['fn'] = 'Function'
1295 self
.indextitle
['ky'] = 'Keyword'
1296 self
.indextitle
['pg'] = 'Program'
1297 self
.indextitle
['tp'] = 'Type'
1298 self
.indextitle
['vr'] = 'Variable'
1300 self
.whichindex
= {}
1301 for name
in self
.indextitle
.keys():
1302 self
.whichindex
[name
] = []
1304 def user_index(self
, name
, args
):
1305 if self
.whichindex
.has_key(name
):
1306 self
.index(name
, args
)
1308 print '*** No index named', `name`
1310 def do_cindex(self
, args
): self
.index('cp', args
)
1311 def do_findex(self
, args
): self
.index('fn', args
)
1312 def do_kindex(self
, args
): self
.index('ky', args
)
1313 def do_pindex(self
, args
): self
.index('pg', args
)
1314 def do_tindex(self
, args
): self
.index('tp', args
)
1315 def do_vindex(self
, args
): self
.index('vr', args
)
1317 def index(self
, name
, args
):
1318 self
.whichindex
[name
].append(args
, self
.nodename
)
1320 def do_synindex(self
, args
):
1321 words
= string
.split(args
)
1323 print '*** bad @synindex', args
1326 if not self
.whichindex
.has_key(old
) or \
1327 not self
.whichindex
.has_key(new
):
1328 print '*** bad key(s) in @synindex', args
1331 self
.whichindex
[old
] is not self
.whichindex
[new
]:
1332 inew
= self
.whichindex
[new
]
1333 inew
[len(inew
):] = self
.whichindex
[old
]
1334 self
.whichindex
[old
] = inew
1335 do_syncodeindex
= do_synindex
# XXX Should use code font
1337 def do_printindex(self
, args
):
1338 words
= string
.split(args
)
1340 if self
.whichindex
.has_key(name
):
1343 print '*** No index named', `name`
1345 def prindex(self
, name
):
1346 iscodeindex
= (name
not in self
.noncodeindices
)
1347 index
= self
.whichindex
[name
]
1348 if not index
: return
1350 print '--- Generating', self
.indextitle
[name
], 'index'
1351 # The node already provides a title
1353 junkprog
= regex
.compile('^\(@[a-z]+\)?{')
1354 for key
, node
in index
:
1355 sortkey
= string
.lower(key
)
1356 # Remove leading `@cmd{' from sort key
1357 # -- don't bother about the matching `}'
1358 oldsortkey
= sortkey
1360 i
= junkprog
.match(sortkey
)
1362 sortkey
= sortkey
[i
:]
1363 index1
.append(sortkey
, key
, node
)
1366 self
.write('<DL COMPACT>\n')
1367 for sortkey
, key
, node
in index1
:
1368 if self
.debugging
> 1: print key
, ':', node
1370 if iscodeindex
: key
= '@code{' + key
+ '}'
1372 self
.write('<DD><A HREF="', makefile(node
), \
1373 '">', node
, '</A>\n')
1374 self
.write('</DL>\n')
1376 # --- Final error reports ---
1380 print '--- Unrecognized commands ---'
1381 cmds
= self
.unknown
.keys()
1384 print string
.ljust(cmd
, 20), self
.unknown
[cmd
]
1387 # Put @var{} around alphabetic substrings
1389 return '@var{'+str+'}'
1392 # Split a string in "words" according to findwordend
1393 def splitwords(str, minlength
):
1398 while i
< n
and str[i
] in ' \t\n': i
= i
+1
1401 i
= findwordend(str, i
, n
)
1402 words
.append(str[start
:i
])
1403 while len(words
) < minlength
: words
.append('')
1407 # Find the end of a "word", matching braces and interpreting @@ @{ @}
1408 fwprog
= regex
.compile('[@{} ]')
1409 def findwordend(str, i
, n
):
1412 i
= fwprog
.search(str, i
)
1415 if c
== '@': i
= i
+1 # Next character is not special
1416 elif c
== '{': level
= level
+1
1417 elif c
== '}': level
= level
-1
1418 elif c
== ' ' and level
<= 0: return i
-1
1422 # Convert a node name into a file name
1423 def makefile(nodename
):
1424 return fixfunnychars(nodename
) + '.html'
1427 # Characters that are perfectly safe in filenames and hyperlinks
1428 goodchars
= string
.letters
+ string
.digits
+ '!@-=+.'
1430 # Replace characters that aren't perfectly safe by dashes
1431 # Underscores are bad since Cern HTTPD treats them as delimiters for
1432 # encoding times, so you get mismatches if you compress your files:
1433 # a.html.gz will map to a_b.html.gz
1434 def fixfunnychars(addr
):
1436 while i
< len(addr
):
1438 if c
not in goodchars
:
1440 addr
= addr
[:i
] + c
+ addr
[i
+1:]
1445 # Increment a string used as an enumeration
1449 for sequence
in string
.digits
, string
.lowercase
, string
.uppercase
:
1451 if lastc
in sequence
:
1452 i
= string
.index(sequence
, lastc
) + 1
1453 if i
>= len(sequence
):
1459 s
= increment(s
[:-1]) + sequence
[0]
1461 s
= s
[:-1] + sequence
[i
]
1463 return s
# Don't increment
1468 parser
= TexinfoParser()
1469 while sys
.argv
[1:2] == ['-d']:
1470 parser
.debugging
= parser
.debugging
+ 1
1472 if sys
.argv
[1] == '-c':
1475 if len(sys
.argv
) <> 3:
1476 print 'usage: texi2html [-d] [-d] [-c] inputfile outputdirectory'
1479 parser
.setdirname(sys
.argv
[2])
1483 parser
.setincludedir(os
.path
.dirname(file))
1485 fp
= open(file, 'r')
1486 except IOError, msg
:
1487 print file, ':', msg
1496 # Emacs local variables
1499 # py-indent-offset: 8