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
42 MAGIC
= '\\input texinfo'
44 cmprog
= re
.compile('^@([a-z]+)([ \t]|$)') # Command (line-oriented)
45 blprog
= re
.compile('^[ \t]*$') # Blank line
46 kwprog
= re
.compile('@[a-z]+') # Keyword (embedded, usually
48 spprog
= re
.compile('[\n@{}&<>]') # Special characters in
52 miprog
= re
.compile('^\* ([^:]*):(:|[ \t]*([^\t,\n.]+)([^ \t\n]*))[ \t\n]*')
57 """Some of the parser's functionality is separated into this class.
59 A Node accumulates its contents, takes care of links to other Nodes
60 and saves itself when it is finished and all links are resolved.
63 DOCTYPE
= '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'
67 epilogue
= '</BODY></HTML>\n'
69 def __init__(self
, dir, name
, topname
, title
, next
, prev
, up
):
73 self
.topname
= topname
82 def write(self
, *lines
):
83 map(self
.lines
.append
, lines
)
86 fp
= open(self
.dirname
+ '/' + makefile(self
.name
), 'w')
87 fp
.write(self
.prologue
)
89 fp
.write(self
.epilogue
)
92 def link(self
, label
, nodename
, rel
=None, rev
=None):
94 if string
.lower(nodename
) == '(dir)':
98 addr
= makefile(nodename
)
99 title
= ' TITLE="%s"' % nodename
100 self
.write(label
, ': <A HREF="', addr
, '"', \
101 rel
and (' REL=' + rel
) or "", \
102 rev
and (' REV=' + rev
) or "", \
103 title
, '>', nodename
, '</A> \n')
106 length
= len(self
.lines
)
107 self
.text
= string
.joinfields(self
.lines
, '')
112 links
= string
.joinfields(self
.lines
, '')
118 ' <!-- Converted with texi2html and Python -->\n'
119 ' <TITLE>' + self
.title
+ '</TITLE>\n'
120 ' <LINK REL=Next HREF="'
121 + makefile(self
.next
) + '" TITLE="' + self
.next
+ '">\n'
122 ' <LINK REL=Previous HREF="'
123 + makefile(self
.prev
) + '" TITLE="' + self
.prev
+ '">\n'
124 ' <LINK REL=Up HREF="'
125 + makefile(self
.up
) + '" TITLE="' + self
.up
+ '">\n'
129 self
.epilogue
= '<P>\n%s</BODY></HTML>\n' % links
131 def open_links(self
):
134 def close_links(self
):
137 def output_links(self
):
138 if self
.cont
!= self
.next
:
139 self
.link(' Cont', self
.cont
)
140 self
.link(' Next', self
.next
, rel
='Next')
141 self
.link(' Prev', self
.prev
, rel
='Previous')
142 self
.link(' Up', self
.up
, rel
='Up')
143 if self
.name
<> self
.topname
:
144 self
.link(' Top', self
.topname
)
147 class HTML3Node(HTMLNode
):
149 DOCTYPE
= '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML Level 3//EN//3.0">'
151 def open_links(self
):
152 self
.write('<DIV CLASS=Navigation>\n <HR>\n')
154 def close_links(self
):
155 self
.write(' <HR>\n</DIV>\n')
160 COPYRIGHT_SYMBOL
= "©"
161 FN_ID_PATTERN
= "(%(id)s)"
162 FN_SOURCE_PATTERN
= '<A NAME=footnoteref%(id)s' \
163 ' HREF="#footnotetext%(id)s">' \
164 + FN_ID_PATTERN
+ '</A>'
165 FN_TARGET_PATTERN
= '<A NAME=footnotetext%(id)s' \
166 ' HREF="#footnoteref%(id)s">' \
167 + FN_ID_PATTERN
+ '</A>\n%(text)s<P>\n'
168 FN_HEADER
= '\n<P>\n<HR NOSHADE SIZE=1 WIDTH=200>\n' \
169 '<STRONG><EM>Footnotes</EM></STRONG>\n<P>'
174 # Initialize an instance
176 self
.unknown
= {} # statistics about unknown @-commands
177 self
.filenames
= {} # Check for identical filenames
178 self
.debugging
= 0 # larger values produce more output
179 self
.print_headers
= 0 # always print headers?
180 self
.nodefp
= None # open file we're writing to
181 self
.nodelineno
= 0 # Linenumber relative to node
182 self
.links
= None # Links from current node
183 self
.savetext
= None # If not None, save text head instead
184 self
.savestack
= [] # If not None, save text head instead
185 self
.dirname
= 'tmp' # directory where files are created
186 self
.includedir
= '.' # directory to search @include files
187 self
.nodename
= '' # name of current node
188 self
.topname
= '' # name of top node (first node seen)
189 self
.title
= '' # title of this whole Texinfo tree
190 self
.resetindex() # Reset all indices
191 self
.contents
= [] # Reset table of contents
192 self
.numbering
= [] # Reset section numbering counters
193 self
.nofill
= 0 # Normal operation: fill paragraphs
194 self
.values
={'html': 1} # Names that should be parsed in ifset
195 self
.stackinfo
={} # Keep track of state in the stack
196 # XXX The following should be reset per node?!
197 self
.footnotes
= [] # Reset list of footnotes
198 self
.itemarg
= None # Reset command used by @item
199 self
.itemnumber
= None # Reset number for @item in @enumerate
200 self
.itemindex
= None # Reset item index name
204 self
.includedepth
= 0
205 # Set (output) directory name
206 def setdirname(self
, dirname
):
207 self
.dirname
= dirname
209 # Set include directory name
210 def setincludedir(self
, includedir
):
211 self
.includedir
= includedir
213 # Parse the contents of an entire file
217 while line
and (line
[0] == '%' or blprog
.match(line
)):
220 if line
[:len(MAGIC
)] <> MAGIC
:
221 raise SyntaxError, 'file does not begin with '+`MAGIC`
222 self
.parserest(fp
, lineno
)
224 # Parse the contents of a file, not expecting a MAGIC header
225 def parserest(self
, fp
, initial_lineno
):
226 lineno
= initial_lineno
233 self
.nodelineno
= self
.nodelineno
+ 1
236 if not self
.skip
: self
.process(accu
)
238 if initial_lineno
> 0:
239 print '*** EOF before @bye'
242 mo
= cmprog
.match(line
)
246 if cmd
in ('noindent', 'refill'):
253 self
.command(line
, mo
)
254 elif blprog
.match(line
) and \
255 'format' not in self
.stack
and \
256 'example' not in self
.stack
:
266 # Append the line including trailing \n!
270 print '*** Still skipping at the end'
272 print '*** Stack not empty at the end'
273 print '***', self
.stack
274 if self
.includedepth
== 0:
275 while self
.nodestack
:
276 self
.nodestack
[-1].finalize()
277 self
.nodestack
[-1].flush()
278 del self
.nodestack
[-1]
280 # Start saving text in a buffer instead of writing it to a file
281 def startsaving(self
):
282 if self
.savetext
<> None:
283 self
.savestack
.append(self
.savetext
)
284 # print '*** Recursively saving text, expect trouble'
287 # Return the text saved so far and start writing to file again
288 def collectsavings(self
):
289 savetext
= self
.savetext
290 if len(self
.savestack
) > 0:
291 self
.savetext
= self
.savestack
[-1]
292 del self
.savestack
[-1]
295 return savetext
or ''
297 # Write text to file, or save it in a buffer, or ignore it
298 def write(self
, *args
):
300 text
= string
.joinfields(args
, '')
304 if self
.savetext
<> None:
305 self
.savetext
= self
.savetext
+ text
307 self
.nodefp
.write(text
)
309 self
.node
.write(text
)
310 # Complete the current node -- write footnotes and close file
312 if self
.savetext
<> None:
313 print '*** Still saving text at end of node'
314 dummy
= self
.collectsavings()
316 self
.writefootnotes()
318 if self
.nodelineno
> 20:
320 [name
, next
, prev
, up
] = self
.nodelinks
[:4]
321 self
.link('Next', next
)
322 self
.link('Prev', prev
)
324 if self
.nodename
<> self
.topname
:
325 self
.link('Top', self
.topname
)
327 self
.write('</BODY>\n')
331 if not self
.cont
and \
332 (not self
.node
.type or \
333 (self
.node
.next
and self
.node
.prev
and self
.node
.up
)):
337 self
.nodestack
.append(self
.node
)
341 # Process a list of lines, expanding embedded @-commands
342 # This mostly distinguishes between menus and normal text
343 def process(self
, accu
):
344 if self
.debugging
> 1:
345 print self
.skip
, self
.stack
,
346 if accu
: print accu
[0][:30],
347 if accu
[0][30:] or accu
[1:]: print '...',
349 if self
.stack
and self
.stack
[-1] == 'menu':
350 # XXX should be done differently
352 mo
= miprog
.match(line
)
354 line
= string
.strip(line
) + '\n'
357 bgn
, end
= mo
.span(0)
364 if nodename
[0] == ':': nodename
= label
365 else: nodename
= line
[e
:f
]
367 self
.write(' <LI><A HREF="',
371 self
.expand(line
[end
:])
373 text
= string
.joinfields(accu
, '')
376 # Write a string, expanding embedded @-commands
377 def expand(self
, text
):
383 mo
= spprog
.search(text
, i
)
387 self
.write(text
[start
:])
389 self
.write(text
[start
:i
])
409 print '*** Unmatched }'
415 method
= getattr(self
, 'close_' + cmd
)
416 except AttributeError:
417 self
.unknown_close(cmd
)
422 # Cannot happen unless spprog is changed
423 raise RuntimeError, 'unexpected funny '+`c`
425 while i
< n
and text
[i
] in string
.letters
: i
= i
+1
427 # @ plus non-letter: literal next character
431 # `@:' means no extra space after
432 # preceding `.', `?', `!' or `:'
435 # `@.' means a sentence-ending period;
436 # `@@', `@{', `@}' quote `@', `{', `}'
440 if i
< n
and text
[i
] == '{':
444 method
= getattr(self
, 'open_' + cmd
)
445 except AttributeError:
446 self
.unknown_open(cmd
)
451 method
= getattr(self
, 'handle_' + cmd
)
452 except AttributeError:
453 self
.unknown_handle(cmd
)
457 print '*** Stack not empty at para:', stack
459 # --- Handle unknown embedded @-commands ---
461 def unknown_open(self
, cmd
):
462 print '*** No open func for @' + cmd
+ '{...}'
465 if not self
.unknown
.has_key(cmd
):
466 self
.unknown
[cmd
] = 1
468 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
470 def unknown_close(self
, cmd
):
471 print '*** No close func for @' + cmd
+ '{...}'
474 if not self
.unknown
.has_key(cmd
):
475 self
.unknown
[cmd
] = 1
477 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
479 def unknown_handle(self
, cmd
):
480 print '*** No handler for @' + cmd
482 if not self
.unknown
.has_key(cmd
):
483 self
.unknown
[cmd
] = 1
485 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
487 # XXX The following sections should be ordered as the texinfo docs
489 # --- Embedded @-commands without {} argument list --
491 def handle_noindent(self
): pass
493 def handle_refill(self
): pass
495 # --- Include file handling ---
497 def do_include(self
, args
):
499 file = os
.path
.join(self
.includedir
, file)
503 print '*** Can\'t open include file', `
file`
506 print '--> file', `
file`
507 save_done
= self
.done
508 save_skip
= self
.skip
509 save_stack
= self
.stack
510 self
.includedepth
= self
.includedepth
+ 1
511 self
.parserest(fp
, 0)
512 self
.includedepth
= self
.includedepth
- 1
514 self
.done
= save_done
515 self
.skip
= save_skip
516 self
.stack
= save_stack
518 print '<-- file', `
file`
520 # --- Special Insertions ---
522 def open_dmn(self
): pass
523 def close_dmn(self
): pass
525 def open_dots(self
): self
.write('...')
526 def close_dots(self
): pass
528 def open_bullet(self
): pass
529 def close_bullet(self
): pass
531 def open_TeX(self
): self
.write('TeX')
532 def close_TeX(self
): pass
534 def handle_copyright(self
): self
.write(self
.COPYRIGHT_SYMBOL
)
535 def open_copyright(self
): self
.write(self
.COPYRIGHT_SYMBOL
)
536 def close_copyright(self
): pass
538 def open_minus(self
): self
.write('-')
539 def close_minus(self
): pass
541 # --- Special Glyphs for Examples ---
543 def open_result(self
): self
.write('=>')
544 def close_result(self
): pass
546 def open_expansion(self
): self
.write('==>')
547 def close_expansion(self
): pass
549 def open_print(self
): self
.write('-|')
550 def close_print(self
): pass
552 def open_error(self
): self
.write('error-->')
553 def close_error(self
): pass
555 def open_equiv(self
): self
.write('==')
556 def close_equiv(self
): pass
558 def open_point(self
): self
.write('-!-')
559 def close_point(self
): pass
561 # --- Cross References ---
563 def open_pxref(self
):
566 def close_pxref(self
):
572 def close_xref(self
):
580 def open_inforef(self
):
581 self
.write('See info file ')
583 def close_inforef(self
):
584 text
= self
.collectsavings()
585 args
= string
.splitfields(text
, ',')
588 args
[i
] = string
.strip(args
[i
])
589 while len(args
) < 3: args
.append('')
592 self
.write('`', file, '\', node `', node
, '\'')
595 text
= self
.collectsavings()
596 args
= string
.splitfields(text
, ',')
599 args
[i
] = string
.strip(args
[i
])
600 while len(args
) < 5: args
.append('')
601 nodename
= label
= args
[0]
602 if args
[2]: label
= args
[2]
605 href
= makefile(nodename
)
607 href
= '../' + file + '/' + href
608 self
.write('<A HREF="', href
, '">', label
, '</A>')
610 # --- Marking Words and Phrases ---
612 # --- Other @xxx{...} commands ---
614 def open_(self
): pass # Used by {text enclosed in braces}
615 def close_(self
): pass
620 def open_cite(self
): self
.write('<CITE>')
621 def close_cite(self
): self
.write('</CITE>')
623 def open_code(self
): self
.write('<CODE>')
624 def close_code(self
): self
.write('</CODE>')
626 def open_t(self
): self
.write('<TT>')
627 def close_t(self
): self
.write('</TT>')
629 def open_dfn(self
): self
.write('<DFN>')
630 def close_dfn(self
): self
.write('</DFN>')
632 def open_emph(self
): self
.write('<EM>')
633 def close_emph(self
): self
.write('</EM>')
635 def open_i(self
): self
.write('<I>')
636 def close_i(self
): self
.write('</I>')
638 def open_footnote(self
):
639 # if self.savetext <> None:
640 # print '*** Recursive footnote -- expect weirdness'
641 id = len(self
.footnotes
) + 1
642 self
.write(self
.FN_SOURCE_PATTERN
% {'id': `
id`
})
645 def close_footnote(self
):
646 id = len(self
.footnotes
) + 1
647 self
.footnotes
.append((id, self
.collectsavings()))
649 def writefootnotes(self
):
650 self
.write(self
.FN_HEADER
)
651 for id, text
in self
.footnotes
:
652 self
.write(self
.FN_TARGET_PATTERN
653 % {'id': `
id`
, 'text': text
})
656 def open_file(self
): self
.write('<CODE>')
657 def close_file(self
): self
.write('</CODE>')
659 def open_kbd(self
): self
.write('<KBD>')
660 def close_kbd(self
): self
.write('</KBD>')
662 def open_key(self
): self
.write('<KEY>')
663 def close_key(self
): self
.write('</KEY>')
665 def open_r(self
): self
.write('<R>')
666 def close_r(self
): self
.write('</R>')
668 def open_samp(self
): self
.write('`<SAMP>')
669 def close_samp(self
): self
.write('</SAMP>\'')
671 def open_sc(self
): self
.write('<SMALLCAPS>')
672 def close_sc(self
): self
.write('</SMALLCAPS>')
674 def open_strong(self
): self
.write('<STRONG>')
675 def close_strong(self
): self
.write('</STRONG>')
677 def open_b(self
): self
.write('<B>')
678 def close_b(self
): self
.write('</B>')
680 def open_var(self
): self
.write('<VAR>')
681 def close_var(self
): self
.write('</VAR>')
683 def open_w(self
): self
.write('<NOBREAK>')
684 def close_w(self
): self
.write('</NOBREAK>')
686 def open_url(self
): self
.startsaving()
688 text
= self
.collectsavings()
689 self
.write('<A HREF="', text
, '">', text
, '</A>')
691 def open_email(self
): self
.startsaving()
692 def close_email(self
):
693 text
= self
.collectsavings()
694 self
.write('<A HREF="mailto:', text
, '">', text
, '</A>')
696 open_titlefont
= open_
697 close_titlefont
= close_
699 def open_small(self
): pass
700 def close_small(self
): pass
702 def command(self
, line
, mo
):
705 args
= string
.strip(line
[b
:])
706 if self
.debugging
> 1:
707 print self
.skip
, self
.stack
, '@' + cmd
, args
709 func
= getattr(self
, 'do_' + cmd
)
710 except AttributeError:
712 func
= getattr(self
, 'bgn_' + cmd
)
713 except AttributeError:
714 # don't complain if we are skipping anyway
716 self
.unknown_cmd(cmd
, args
)
718 self
.stack
.append(cmd
)
721 if not self
.skip
or cmd
== 'end':
724 def unknown_cmd(self
, cmd
, args
):
725 print '*** unknown', '@' + cmd
, args
726 if not self
.unknown
.has_key(cmd
):
727 self
.unknown
[cmd
] = 1
729 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
731 def do_end(self
, args
):
732 words
= string
.split(args
)
734 print '*** @end w/o args'
737 if not self
.stack
or self
.stack
[-1] <> cmd
:
738 print '*** @end', cmd
, 'unexpected'
742 func
= getattr(self
, 'end_' + cmd
)
743 except AttributeError:
744 self
.unknown_end(cmd
)
748 def unknown_end(self
, cmd
):
750 print '*** unknown', '@' + cmd
751 if not self
.unknown
.has_key(cmd
):
752 self
.unknown
[cmd
] = 1
754 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
758 def do_comment(self
, args
): pass
761 # --- Conditional processing ---
763 def bgn_ifinfo(self
, args
): pass
764 def end_ifinfo(self
): pass
766 def bgn_iftex(self
, args
): self
.skip
= self
.skip
+ 1
767 def end_iftex(self
): self
.skip
= self
.skip
- 1
769 def bgn_ignore(self
, args
): self
.skip
= self
.skip
+ 1
770 def end_ignore(self
): self
.skip
= self
.skip
- 1
772 def bgn_tex(self
, args
): self
.skip
= self
.skip
+ 1
773 def end_tex(self
): self
.skip
= self
.skip
- 1
775 def do_set(self
, args
):
776 fields
= string
.splitfields(args
, ' ')
781 value
= string
.joinfields(fields
[1:], ' ')
782 self
.values
[key
] = value
785 def do_clear(self
, args
):
786 self
.values
[args
] = None
788 def bgn_ifset(self
, args
):
789 if args
not in self
.values
.keys() \
790 or self
.values
[args
] is None:
791 self
.skip
= self
.skip
+ 1
792 self
.stackinfo
[len(self
.stack
)] = 1
794 self
.stackinfo
[len(self
.stack
)] = 0
798 if self
.stackinfo
[len(self
.stack
) + 1]:
799 self
.skip
= self
.skip
- 1
800 del self
.stackinfo
[len(self
.stack
) + 1]
802 def bgn_ifclear(self
, args
):
803 if args
in self
.values
.keys() \
804 and self
.values
[args
] is not None:
805 self
.skip
= self
.skip
+ 1
806 self
.stackinfo
[len(self
.stack
)] = 1
808 self
.stackinfo
[len(self
.stack
)] = 0
810 end_ifclear
= end_ifset
812 def open_value(self
):
815 def close_value(self
):
816 key
= self
.collectsavings()
817 if key
in self
.values
.keys():
818 self
.write(self
.values
[key
])
820 print '*** Undefined value: ', key
822 # --- Beginning a file ---
824 do_finalout
= do_comment
825 do_setchapternewpage
= do_comment
826 do_setfilename
= do_comment
828 def do_settitle(self
, args
):
832 self
.title
= self
.collectsavings()
834 def do_parskip(self
, args
): pass
836 # --- Ending a file ---
838 def do_bye(self
, args
):
844 def bgn_titlepage(self
, args
): self
.skip
= self
.skip
+ 1
845 def end_titlepage(self
): self
.skip
= self
.skip
- 1
846 def do_shorttitlepage(self
, args
): pass
848 def do_center(self
, args
):
849 # Actually not used outside title page...
852 self
.write('</H1>\n')
854 do_subtitle
= do_center
855 do_author
= do_center
857 do_vskip
= do_comment
858 do_vfill
= do_comment
859 do_smallbook
= do_comment
861 do_paragraphindent
= do_comment
862 do_setchapternewpage
= do_comment
863 do_headings
= do_comment
864 do_footnotestyle
= do_comment
866 do_evenheading
= do_comment
867 do_evenfooting
= do_comment
868 do_oddheading
= do_comment
869 do_oddfooting
= do_comment
870 do_everyheading
= do_comment
871 do_everyfooting
= do_comment
875 def do_node(self
, args
):
878 parts
= string
.splitfields(args
, ',')
879 while len(parts
) < 4: parts
.append('')
880 for i
in range(4): parts
[i
] = string
.strip(parts
[i
])
881 self
.nodelinks
= parts
882 [name
, next
, prev
, up
] = parts
[:4]
883 file = self
.dirname
+ '/' + makefile(name
)
884 if self
.filenames
.has_key(file):
885 print '*** Filename already in use: ', file
887 if self
.debugging
: print '--- writing', file
888 self
.filenames
[file] = 1
889 # self.nodefp = open(file, 'w')
891 if self
.cont
and self
.nodestack
:
892 self
.nodestack
[-1].cont
= self
.nodename
893 if not self
.topname
: self
.topname
= name
895 if self
.title
: title
= title
+ ' -- ' + self
.title
896 self
.node
= self
.Node(self
.dirname
, self
.nodename
, self
.topname
,
897 title
, next
, prev
, up
)
899 def link(self
, label
, nodename
):
901 if string
.lower(nodename
) == '(dir)':
904 addr
= makefile(nodename
)
905 self
.write(label
, ': <A HREF="', addr
, '" TYPE="',
906 label
, '">', nodename
, '</A> \n')
908 # --- Sectioning commands ---
910 def popstack(self
, type):
912 self
.node
.type = type
913 while self
.nodestack
:
914 if self
.nodestack
[-1].type > type:
915 self
.nodestack
[-1].finalize()
916 self
.nodestack
[-1].flush()
917 del self
.nodestack
[-1]
918 elif self
.nodestack
[-1].type == type:
919 if not self
.nodestack
[-1].next
:
920 self
.nodestack
[-1].next
= self
.node
.name
921 if not self
.node
.prev
:
922 self
.node
.prev
= self
.nodestack
[-1].name
923 self
.nodestack
[-1].finalize()
924 self
.nodestack
[-1].flush()
925 del self
.nodestack
[-1]
927 if type > 1 and not self
.node
.up
:
928 self
.node
.up
= self
.nodestack
[-1].name
931 def do_chapter(self
, args
):
932 self
.heading('H1', args
, 0)
935 def do_unnumbered(self
, args
):
936 self
.heading('H1', args
, -1)
938 def do_appendix(self
, args
):
939 self
.heading('H1', args
, -1)
941 def do_top(self
, args
):
942 self
.heading('H1', args
, -1)
943 def do_chapheading(self
, args
):
944 self
.heading('H1', args
, -1)
945 def do_majorheading(self
, args
):
946 self
.heading('H1', args
, -1)
948 def do_section(self
, args
):
949 self
.heading('H1', args
, 1)
952 def do_unnumberedsec(self
, args
):
953 self
.heading('H1', args
, -1)
955 def do_appendixsec(self
, args
):
956 self
.heading('H1', args
, -1)
958 do_appendixsection
= do_appendixsec
959 def do_heading(self
, args
):
960 self
.heading('H1', args
, -1)
962 def do_subsection(self
, args
):
963 self
.heading('H2', args
, 2)
965 def do_unnumberedsubsec(self
, args
):
966 self
.heading('H2', args
, -1)
968 def do_appendixsubsec(self
, args
):
969 self
.heading('H2', args
, -1)
971 def do_subheading(self
, args
):
972 self
.heading('H2', args
, -1)
974 def do_subsubsection(self
, args
):
975 self
.heading('H3', args
, 3)
977 def do_unnumberedsubsubsec(self
, args
):
978 self
.heading('H3', args
, -1)
980 def do_appendixsubsubsec(self
, args
):
981 self
.heading('H3', args
, -1)
983 def do_subsubheading(self
, args
):
984 self
.heading('H3', args
, -1)
986 def heading(self
, type, args
, level
):
988 while len(self
.numbering
) <= level
:
989 self
.numbering
.append(0)
990 del self
.numbering
[level
+1:]
991 self
.numbering
[level
] = self
.numbering
[level
] + 1
993 for i
in self
.numbering
:
995 args
= x
+ ' ' + args
996 self
.contents
.append((level
, args
, self
.nodename
))
997 self
.write('<', type, '>')
999 self
.write('</', type, '>\n')
1000 if self
.debugging
or self
.print_headers
:
1003 def do_contents(self
, args
):
1005 self
.listcontents('Table of Contents', 999)
1007 def do_shortcontents(self
, args
):
1009 # self.listcontents('Short Contents', 0)
1010 do_summarycontents
= do_shortcontents
1012 def listcontents(self
, title
, maxlevel
):
1013 self
.write('<H1>', title
, '</H1>\n<UL COMPACT PLAIN>\n')
1015 for level
, title
, node
in self
.contents
:
1016 if level
> maxlevel
:
1018 if level
> prevlevels
[-1]:
1019 # can only advance one level at a time
1020 self
.write(' '*prevlevels
[-1], '<UL PLAIN>\n')
1021 prevlevels
.append(level
)
1022 elif level
< prevlevels
[-1]:
1023 # might drop back multiple levels
1024 while level
< prevlevels
[-1]:
1026 self
.write(' '*prevlevels
[-1],
1028 self
.write(' '*level
, '<LI> <A HREF="',
1029 makefile(node
), '">')
1031 self
.write('</A>\n')
1032 self
.write('</UL>\n' * len(prevlevels
))
1034 # --- Page lay-out ---
1036 # These commands are only meaningful in printed text
1038 def do_page(self
, args
): pass
1040 def do_need(self
, args
): pass
1042 def bgn_group(self
, args
): pass
1043 def end_group(self
): pass
1045 # --- Line lay-out ---
1047 def do_sp(self
, args
):
1053 def do_hline(self
, args
):
1056 # --- Function and variable definitions ---
1058 def bgn_deffn(self
, args
):
1060 self
.do_deffnx(args
)
1062 def end_deffn(self
):
1063 self
.write('</DL>\n')
1065 def do_deffnx(self
, args
):
1067 words
= splitwords(args
, 2)
1068 [category
, name
], rest
= words
[:2], words
[2:]
1069 self
.expand('@b{%s}' % name
)
1070 for word
in rest
: self
.expand(' ' + makevar(word
))
1071 #self.expand(' -- ' + category)
1072 self
.write('\n<DD>')
1073 self
.index('fn', name
)
1075 def bgn_defun(self
, args
): self
.bgn_deffn('Function ' + args
)
1076 end_defun
= end_deffn
1077 def do_defunx(self
, args
): self
.do_deffnx('Function ' + args
)
1079 def bgn_defmac(self
, args
): self
.bgn_deffn('Macro ' + args
)
1080 end_defmac
= end_deffn
1081 def do_defmacx(self
, args
): self
.do_deffnx('Macro ' + args
)
1083 def bgn_defspec(self
, args
): self
.bgn_deffn('{Special Form} ' + args
)
1084 end_defspec
= end_deffn
1085 def do_defspecx(self
, args
): self
.do_deffnx('{Special Form} ' + args
)
1087 def bgn_defvr(self
, args
):
1089 self
.do_defvrx(args
)
1091 end_defvr
= end_deffn
1093 def do_defvrx(self
, args
):
1095 words
= splitwords(args
, 2)
1096 [category
, name
], rest
= words
[:2], words
[2:]
1097 self
.expand('@code{%s}' % name
)
1098 # If there are too many arguments, show them
1099 for word
in rest
: self
.expand(' ' + word
)
1100 #self.expand(' -- ' + category)
1101 self
.write('\n<DD>')
1102 self
.index('vr', name
)
1104 def bgn_defvar(self
, args
): self
.bgn_defvr('Variable ' + args
)
1105 end_defvar
= end_defvr
1106 def do_defvarx(self
, args
): self
.do_defvrx('Variable ' + args
)
1108 def bgn_defopt(self
, args
): self
.bgn_defvr('{User Option} ' + args
)
1109 end_defopt
= end_defvr
1110 def do_defoptx(self
, args
): self
.do_defvrx('{User Option} ' + args
)
1112 # --- Ditto for typed languages ---
1114 def bgn_deftypefn(self
, args
):
1116 self
.do_deftypefnx(args
)
1118 end_deftypefn
= end_deffn
1120 def do_deftypefnx(self
, args
):
1122 words
= splitwords(args
, 3)
1123 [category
, datatype
, name
], rest
= words
[:3], words
[3:]
1124 self
.expand('@code{%s} @b{%s}' % (datatype
, name
))
1125 for word
in rest
: self
.expand(' ' + makevar(word
))
1126 #self.expand(' -- ' + category)
1127 self
.write('\n<DD>')
1128 self
.index('fn', name
)
1131 def bgn_deftypefun(self
, args
): self
.bgn_deftypefn('Function ' + args
)
1132 end_deftypefun
= end_deftypefn
1133 def do_deftypefunx(self
, args
): self
.do_deftypefnx('Function ' + args
)
1135 def bgn_deftypevr(self
, args
):
1137 self
.do_deftypevrx(args
)
1139 end_deftypevr
= end_deftypefn
1141 def do_deftypevrx(self
, args
):
1143 words
= splitwords(args
, 3)
1144 [category
, datatype
, name
], rest
= words
[:3], words
[3:]
1145 self
.expand('@code{%s} @b{%s}' % (datatype
, name
))
1146 # If there are too many arguments, show them
1147 for word
in rest
: self
.expand(' ' + word
)
1148 #self.expand(' -- ' + category)
1149 self
.write('\n<DD>')
1150 self
.index('fn', name
)
1152 def bgn_deftypevar(self
, args
):
1153 self
.bgn_deftypevr('Variable ' + args
)
1154 end_deftypevar
= end_deftypevr
1155 def do_deftypevarx(self
, args
):
1156 self
.do_deftypevrx('Variable ' + args
)
1158 # --- Ditto for object-oriented languages ---
1160 def bgn_defcv(self
, args
):
1162 self
.do_defcvx(args
)
1164 end_defcv
= end_deftypevr
1166 def do_defcvx(self
, args
):
1168 words
= splitwords(args
, 3)
1169 [category
, classname
, name
], rest
= words
[:3], words
[3:]
1170 self
.expand('@b{%s}' % name
)
1171 # If there are too many arguments, show them
1172 for word
in rest
: self
.expand(' ' + word
)
1173 #self.expand(' -- %s of @code{%s}' % (category, classname))
1174 self
.write('\n<DD>')
1175 self
.index('vr', '%s @r{on %s}' % (name
, classname
))
1177 def bgn_defivar(self
, args
):
1178 self
.bgn_defcv('{Instance Variable} ' + args
)
1179 end_defivar
= end_defcv
1180 def do_defivarx(self
, args
):
1181 self
.do_defcvx('{Instance Variable} ' + args
)
1183 def bgn_defop(self
, args
):
1185 self
.do_defopx(args
)
1187 end_defop
= end_defcv
1189 def do_defopx(self
, args
):
1191 words
= splitwords(args
, 3)
1192 [category
, classname
, name
], rest
= words
[:3], words
[3:]
1193 self
.expand('@b{%s}' % name
)
1194 for word
in rest
: self
.expand(' ' + makevar(word
))
1195 #self.expand(' -- %s of @code{%s}' % (category, classname))
1196 self
.write('\n<DD>')
1197 self
.index('fn', '%s @r{on %s}' % (name
, classname
))
1199 def bgn_defmethod(self
, args
):
1200 self
.bgn_defop('Method ' + args
)
1201 end_defmethod
= end_defop
1202 def do_defmethodx(self
, args
):
1203 self
.do_defopx('Method ' + args
)
1205 # --- Ditto for data types ---
1207 def bgn_deftp(self
, args
):
1209 self
.do_deftpx(args
)
1211 end_deftp
= end_defcv
1213 def do_deftpx(self
, args
):
1215 words
= splitwords(args
, 2)
1216 [category
, name
], rest
= words
[:2], words
[2:]
1217 self
.expand('@b{%s}' % name
)
1218 for word
in rest
: self
.expand(' ' + word
)
1219 #self.expand(' -- ' + category)
1220 self
.write('\n<DD>')
1221 self
.index('tp', name
)
1223 # --- Making Lists and Tables
1225 def bgn_enumerate(self
, args
):
1227 self
.write('<OL>\n')
1228 self
.stackinfo
[len(self
.stack
)] = '</OL>\n'
1230 self
.itemnumber
= args
1231 self
.write('<UL>\n')
1232 self
.stackinfo
[len(self
.stack
)] = '</UL>\n'
1233 def end_enumerate(self
):
1234 self
.itemnumber
= None
1235 self
.write(self
.stackinfo
[len(self
.stack
) + 1])
1236 del self
.stackinfo
[len(self
.stack
) + 1]
1238 def bgn_itemize(self
, args
):
1240 self
.write('<UL>\n')
1241 def end_itemize(self
):
1243 self
.write('</UL>\n')
1245 def bgn_table(self
, args
):
1247 self
.write('<DL>\n')
1248 def end_table(self
):
1250 self
.write('</DL>\n')
1252 def bgn_ftable(self
, args
):
1253 self
.itemindex
= 'fn'
1254 self
.bgn_table(args
)
1255 def end_ftable(self
):
1256 self
.itemindex
= None
1259 def do_item(self
, args
):
1260 if self
.itemindex
: self
.index(self
.itemindex
, args
)
1262 if self
.itemarg
[0] == '@' and self
.itemarg
[1:2] and \
1263 self
.itemarg
[1] in string
.letters
:
1264 args
= self
.itemarg
+ '{' + args
+ '}'
1266 # some other character, e.g. '-'
1267 args
= self
.itemarg
+ ' ' + args
1268 if self
.itemnumber
<> None:
1269 args
= self
.itemnumber
+ '. ' + args
1270 self
.itemnumber
= increment(self
.itemnumber
)
1271 if self
.stack
and self
.stack
[-1] == 'table':
1274 self
.write('\n<DD>')
1279 do_itemx
= do_item
# XXX Should suppress leading blank line
1281 # --- Enumerations, displays, quotations ---
1282 # XXX Most of these should increase the indentation somehow
1284 def bgn_quotation(self
, args
): self
.write('<BLOCKQUOTE>')
1285 def end_quotation(self
): self
.write('</BLOCKQUOTE>\n')
1287 def bgn_example(self
, args
):
1288 self
.nofill
= self
.nofill
+ 1
1290 def end_example(self
):
1291 self
.write('</PRE>\n')
1292 self
.nofill
= self
.nofill
- 1
1294 bgn_lisp
= bgn_example
# Synonym when contents are executable lisp code
1295 end_lisp
= end_example
1297 bgn_smallexample
= bgn_example
# XXX Should use smaller font
1298 end_smallexample
= end_example
1300 bgn_smalllisp
= bgn_lisp
# Ditto
1301 end_smalllisp
= end_lisp
1303 bgn_display
= bgn_example
1304 end_display
= end_example
1306 bgn_format
= bgn_display
1307 end_format
= end_display
1309 def do_exdent(self
, args
): self
.expand(args
+ '\n')
1310 # XXX Should really mess with indentation
1312 def bgn_flushleft(self
, args
):
1313 self
.nofill
= self
.nofill
+ 1
1314 self
.write('<PRE>\n')
1315 def end_flushleft(self
):
1316 self
.write('</PRE>\n')
1317 self
.nofill
= self
.nofill
- 1
1319 def bgn_flushright(self
, args
):
1320 self
.nofill
= self
.nofill
+ 1
1321 self
.write('<ADDRESS COMPACT>\n')
1322 def end_flushright(self
):
1323 self
.write('</ADDRESS>\n')
1324 self
.nofill
= self
.nofill
- 1
1326 def bgn_menu(self
, args
):
1327 self
.write('<DIR>\n')
1328 self
.write(' <STRONG><EM>Menu</EM></STRONG><P>\n')
1330 self
.write('</DIR>\n')
1332 def bgn_cartouche(self
, args
): pass
1333 def end_cartouche(self
): pass
1337 def resetindex(self
):
1338 self
.noncodeindices
= ['cp']
1339 self
.indextitle
= {}
1340 self
.indextitle
['cp'] = 'Concept'
1341 self
.indextitle
['fn'] = 'Function'
1342 self
.indextitle
['ky'] = 'Keyword'
1343 self
.indextitle
['pg'] = 'Program'
1344 self
.indextitle
['tp'] = 'Type'
1345 self
.indextitle
['vr'] = 'Variable'
1347 self
.whichindex
= {}
1348 for name
in self
.indextitle
.keys():
1349 self
.whichindex
[name
] = []
1351 def user_index(self
, name
, args
):
1352 if self
.whichindex
.has_key(name
):
1353 self
.index(name
, args
)
1355 print '*** No index named', `name`
1357 def do_cindex(self
, args
): self
.index('cp', args
)
1358 def do_findex(self
, args
): self
.index('fn', args
)
1359 def do_kindex(self
, args
): self
.index('ky', args
)
1360 def do_pindex(self
, args
): self
.index('pg', args
)
1361 def do_tindex(self
, args
): self
.index('tp', args
)
1362 def do_vindex(self
, args
): self
.index('vr', args
)
1364 def index(self
, name
, args
):
1365 self
.whichindex
[name
].append((args
, self
.nodename
))
1367 def do_synindex(self
, args
):
1368 words
= string
.split(args
)
1370 print '*** bad @synindex', args
1373 if not self
.whichindex
.has_key(old
) or \
1374 not self
.whichindex
.has_key(new
):
1375 print '*** bad key(s) in @synindex', args
1378 self
.whichindex
[old
] is not self
.whichindex
[new
]:
1379 inew
= self
.whichindex
[new
]
1380 inew
[len(inew
):] = self
.whichindex
[old
]
1381 self
.whichindex
[old
] = inew
1382 do_syncodeindex
= do_synindex
# XXX Should use code font
1384 def do_printindex(self
, args
):
1385 words
= string
.split(args
)
1387 if self
.whichindex
.has_key(name
):
1390 print '*** No index named', `name`
1392 def prindex(self
, name
):
1393 iscodeindex
= (name
not in self
.noncodeindices
)
1394 index
= self
.whichindex
[name
]
1395 if not index
: return
1397 print '--- Generating', self
.indextitle
[name
], 'index'
1398 # The node already provides a title
1400 junkprog
= re
.compile('^(@[a-z]+)?{')
1401 for key
, node
in index
:
1402 sortkey
= string
.lower(key
)
1403 # Remove leading `@cmd{' from sort key
1404 # -- don't bother about the matching `}'
1405 oldsortkey
= sortkey
1407 mo
= junkprog
.match(sortkey
)
1411 sortkey
= sortkey
[i
:]
1412 index1
.append((sortkey
, key
, node
))
1415 self
.write('<DL COMPACT>\n')
1416 prevkey
= prevnode
= None
1417 for sortkey
, key
, node
in index1
:
1418 if (key
, node
) == (prevkey
, prevnode
):
1420 if self
.debugging
> 1: print key
, ':', node
1422 if iscodeindex
: key
= '@code{' + key
+ '}'
1425 self
.write('\n<DD><A HREF="%s">%s</A>\n' % (makefile(node
), node
))
1426 prevkey
, prevnode
= key
, node
1427 self
.write('</DL>\n')
1429 # --- Final error reports ---
1433 print '--- Unrecognized commands ---'
1434 cmds
= self
.unknown
.keys()
1437 print string
.ljust(cmd
, 20), self
.unknown
[cmd
]
1440 class TexinfoParserHTML3(TexinfoParser
):
1442 COPYRIGHT_SYMBOL
= "©"
1443 FN_ID_PATTERN
= "[%(id)s]"
1444 FN_SOURCE_PATTERN
= '<A ID=footnoteref%(id)s ' \
1445 'HREF="#footnotetext%(id)s">' + FN_ID_PATTERN
+ '</A>'
1446 FN_TARGET_PATTERN
= '<FN ID=footnotetext%(id)s>\n' \
1447 '<P><A HREF="#footnoteref%(id)s">' + FN_ID_PATTERN \
1448 + '</A>\n%(text)s</P></FN>\n'
1449 FN_HEADER
= '<DIV CLASS=footnotes>\n <HR NOSHADE WIDTH=200>\n' \
1450 ' <STRONG><EM>Footnotes</EM></STRONG>\n <P>\n'
1454 def bgn_quotation(self
, args
): self
.write('<BQ>')
1455 def end_quotation(self
): self
.write('</BQ>\n')
1457 def bgn_example(self
, args
):
1458 # this use of <CODE> would not be legal in HTML 2.0,
1459 # but is in more recent DTDs.
1460 self
.nofill
= self
.nofill
+ 1
1461 self
.write('<PRE CLASS=example><CODE>')
1462 def end_example(self
):
1463 self
.write("</CODE></PRE>\n")
1464 self
.nofill
= self
.nofill
- 1
1466 def bgn_flushleft(self
, args
):
1467 self
.nofill
= self
.nofill
+ 1
1468 self
.write('<PRE CLASS=flushleft>\n')
1470 def bgn_flushright(self
, args
):
1471 self
.nofill
= self
.nofill
+ 1
1472 self
.write('<DIV ALIGN=right CLASS=flushright><ADDRESS COMPACT>\n')
1473 def end_flushright(self
):
1474 self
.write('</ADDRESS></DIV>\n')
1475 self
.nofill
= self
.nofill
- 1
1477 def bgn_menu(self
, args
):
1478 self
.write('<UL PLAIN CLASS=menu>\n')
1479 self
.write(' <LH>Menu</LH>\n')
1481 self
.write('</UL>\n')
1484 # Put @var{} around alphabetic substrings
1486 return '@var{'+str+'}'
1489 # Split a string in "words" according to findwordend
1490 def splitwords(str, minlength
):
1495 while i
< n
and str[i
] in ' \t\n': i
= i
+1
1498 i
= findwordend(str, i
, n
)
1499 words
.append(str[start
:i
])
1500 while len(words
) < minlength
: words
.append('')
1504 # Find the end of a "word", matching braces and interpreting @@ @{ @}
1505 fwprog
= re
.compile('[@{} ]')
1506 def findwordend(str, i
, n
):
1509 mo
= fwprog
.search(str, i
)
1514 if c
== '@': i
= i
+1 # Next character is not special
1515 elif c
== '{': level
= level
+1
1516 elif c
== '}': level
= level
-1
1517 elif c
== ' ' and level
<= 0: return i
-1
1521 # Convert a node name into a file name
1522 def makefile(nodename
):
1523 return fixfunnychars(nodename
) + '.html'
1526 # Characters that are perfectly safe in filenames and hyperlinks
1527 goodchars
= string
.letters
+ string
.digits
+ '!@-=+.'
1529 # Replace characters that aren't perfectly safe by dashes
1530 # Underscores are bad since Cern HTTPD treats them as delimiters for
1531 # encoding times, so you get mismatches if you compress your files:
1532 # a.html.gz will map to a_b.html.gz
1533 def fixfunnychars(addr
):
1535 while i
< len(addr
):
1537 if c
not in goodchars
:
1539 addr
= addr
[:i
] + c
+ addr
[i
+1:]
1544 # Increment a string used as an enumeration
1548 for sequence
in string
.digits
, string
.lowercase
, string
.uppercase
:
1550 if lastc
in sequence
:
1551 i
= string
.index(sequence
, lastc
) + 1
1552 if i
>= len(sequence
):
1558 s
= increment(s
[:-1]) + sequence
[0]
1560 s
= s
[:-1] + sequence
[i
]
1562 return s
# Don't increment
1572 while sys
.argv
[1:2] == ['-d']:
1573 debugging
= debugging
+ 1
1575 if sys
.argv
[1] == '-p':
1578 if sys
.argv
[1] == '-c':
1581 if sys
.argv
[1] == '-3':
1584 if len(sys
.argv
) <> 3:
1585 print 'usage: texi2html [-d [-d]] [-p] [-c] inputfile outputdirectory'
1589 parser
= TexinfoParserHTML3()
1591 parser
= TexinfoParser()
1593 parser
.debugging
= debugging
1594 parser
.print_headers
= print_headers
1597 parser
.setdirname(sys
.argv
[2])
1601 parser
.setincludedir(os
.path
.dirname(file))
1603 fp
= open(file, 'r')
1604 except IOError, msg
:
1605 print file, ':', msg
1612 if __name__
== "__main__":