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 flexible error handling
39 # Robert Pyron <rpyron@alum.mit.edu>
40 # 1. BUGFIX: In function makefile(), strip blanks from the nodename.
41 # This is necessary to match the behavior of parser.makeref() and
43 # 2. BUGFIX fixed KeyError in end_ifset (well, I may have just made
44 # it go away, rather than fix it)
45 # 3. BUGFIX allow @menu and menu items inside @ifset or @ifclear
46 # 4. Support added for:
48 # @image image file reference (see note below)
49 # @multitable output an HTML table
51 # 5. Partial support for accents, to match MAKEINFO output
52 # 6. I added a new command-line option, '-H basename', to specify
53 # HTML Help output. This will cause three files to be created
54 # in the current directory:
55 # `basename`.hhp HTML Help Workshop project file
56 # `basename`.hhc Contents file for the project
57 # `basename`.hhk Index file for the project
58 # When fed into HTML Help Workshop, the resulting file will be
59 # named `basename`.chm.
60 # 7. A new class, HTMLHelp, to accomplish item 6.
61 # 8. Various calls to HTMLHelp functions.
62 # A NOTE ON IMAGES: Just as 'outputdirectory' must exist before
63 # running this program, all referenced images must already exist
71 MAGIC
= '\\input texinfo'
73 cmprog
= re
.compile('^@([a-z]+)([ \t]|$)') # Command (line-oriented)
74 blprog
= re
.compile('^[ \t]*$') # Blank line
75 kwprog
= re
.compile('@[a-z]+') # Keyword (embedded, usually
77 spprog
= re
.compile('[\n@{}&<>]') # Special characters in
81 miprog
= re
.compile('^\* ([^:]*):(:|[ \t]*([^\t,\n.]+)([^ \t\n]*))[ \t\n]*')
83 # ----- ---------- ---------
84 # -|-----------------------------
85 # -----------------------------------------------------
91 """Some of the parser's functionality is separated into this class.
93 A Node accumulates its contents, takes care of links to other Nodes
94 and saves itself when it is finished and all links are resolved.
97 DOCTYPE
= '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'
101 epilogue
= '</BODY></HTML>\n'
103 def __init__(self
, dir, name
, topname
, title
, next
, prev
, up
):
107 self
.topname
= topname
116 def write(self
, *lines
):
117 map(self
.lines
.append
, lines
)
120 fp
= open(self
.dirname
+ '/' + makefile(self
.name
), 'w')
121 fp
.write(self
.prologue
)
123 fp
.write(self
.epilogue
)
126 def link(self
, label
, nodename
, rel
=None, rev
=None):
128 if nodename
.lower() == '(dir)':
132 addr
= makefile(nodename
)
133 title
= ' TITLE="%s"' % nodename
134 self
.write(label
, ': <A HREF="', addr
, '"', \
135 rel
and (' REL=' + rel
) or "", \
136 rev
and (' REV=' + rev
) or "", \
137 title
, '>', nodename
, '</A> \n')
140 length
= len(self
.lines
)
141 self
.text
= ''.join(self
.lines
)
146 links
= ''.join(self
.lines
)
151 ' <!-- Converted with texi2html and Python -->\n'
152 ' <TITLE>' + self
.title
+ '</TITLE>\n'
153 ' <LINK REL=Next HREF="'
154 + makefile(self
.next
) + '" TITLE="' + self
.next
+ '">\n'
155 ' <LINK REL=Previous HREF="'
156 + makefile(self
.prev
) + '" TITLE="' + self
.prev
+ '">\n'
157 ' <LINK REL=Up HREF="'
158 + makefile(self
.up
) + '" TITLE="' + self
.up
+ '">\n'
162 self
.epilogue
= '<P>\n%s</BODY></HTML>\n' % links
164 def open_links(self
):
167 def close_links(self
):
170 def output_links(self
):
171 if self
.cont
!= self
.next
:
172 self
.link(' Cont', self
.cont
)
173 self
.link(' Next', self
.next
, rel
='Next')
174 self
.link(' Prev', self
.prev
, rel
='Previous')
175 self
.link(' Up', self
.up
, rel
='Up')
176 if self
.name
<> self
.topname
:
177 self
.link(' Top', self
.topname
)
180 class HTML3Node(HTMLNode
):
182 DOCTYPE
= '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML Level 3//EN//3.0">'
184 def open_links(self
):
185 self
.write('<DIV CLASS=Navigation>\n <HR>\n')
187 def close_links(self
):
188 self
.write(' <HR>\n</DIV>\n')
193 COPYRIGHT_SYMBOL
= "©"
194 FN_ID_PATTERN
= "(%(id)s)"
195 FN_SOURCE_PATTERN
= '<A NAME=footnoteref%(id)s' \
196 ' HREF="#footnotetext%(id)s">' \
197 + FN_ID_PATTERN
+ '</A>'
198 FN_TARGET_PATTERN
= '<A NAME=footnotetext%(id)s' \
199 ' HREF="#footnoteref%(id)s">' \
200 + FN_ID_PATTERN
+ '</A>\n%(text)s<P>\n'
201 FN_HEADER
= '\n<P>\n<HR NOSHADE SIZE=1 WIDTH=200>\n' \
202 '<STRONG><EM>Footnotes</EM></STRONG>\n<P>'
207 # Initialize an instance
209 self
.unknown
= {} # statistics about unknown @-commands
210 self
.filenames
= {} # Check for identical filenames
211 self
.debugging
= 0 # larger values produce more output
212 self
.print_headers
= 0 # always print headers?
213 self
.nodefp
= None # open file we're writing to
214 self
.nodelineno
= 0 # Linenumber relative to node
215 self
.links
= None # Links from current node
216 self
.savetext
= None # If not None, save text head instead
217 self
.savestack
= [] # If not None, save text head instead
218 self
.htmlhelp
= None # html help data
219 self
.dirname
= 'tmp' # directory where files are created
220 self
.includedir
= '.' # directory to search @include files
221 self
.nodename
= '' # name of current node
222 self
.topname
= '' # name of top node (first node seen)
223 self
.title
= '' # title of this whole Texinfo tree
224 self
.resetindex() # Reset all indices
225 self
.contents
= [] # Reset table of contents
226 self
.numbering
= [] # Reset section numbering counters
227 self
.nofill
= 0 # Normal operation: fill paragraphs
228 self
.values
={'html': 1} # Names that should be parsed in ifset
229 self
.stackinfo
={} # Keep track of state in the stack
230 # XXX The following should be reset per node?!
231 self
.footnotes
= [] # Reset list of footnotes
232 self
.itemarg
= None # Reset command used by @item
233 self
.itemnumber
= None # Reset number for @item in @enumerate
234 self
.itemindex
= None # Reset item index name
238 self
.includedepth
= 0
240 # Set htmlhelp helper class
241 def sethtmlhelp(self
, htmlhelp
):
242 self
.htmlhelp
= htmlhelp
244 # Set (output) directory name
245 def setdirname(self
, dirname
):
246 self
.dirname
= dirname
248 # Set include directory name
249 def setincludedir(self
, includedir
):
250 self
.includedir
= includedir
252 # Parse the contents of an entire file
256 while line
and (line
[0] == '%' or blprog
.match(line
)):
259 if line
[:len(MAGIC
)] <> MAGIC
:
260 raise SyntaxError, 'file does not begin with %r' % (MAGIC
,)
261 self
.parserest(fp
, lineno
)
263 # Parse the contents of a file, not expecting a MAGIC header
264 def parserest(self
, fp
, initial_lineno
):
265 lineno
= initial_lineno
272 self
.nodelineno
= self
.nodelineno
+ 1
275 if not self
.skip
: self
.process(accu
)
277 if initial_lineno
> 0:
278 print '*** EOF before @bye'
281 mo
= cmprog
.match(line
)
285 if cmd
in ('noindent', 'refill'):
292 self
.command(line
, mo
)
293 elif blprog
.match(line
) and \
294 'format' not in self
.stack
and \
295 'example' not in self
.stack
:
305 # Append the line including trailing \n!
309 print '*** Still skipping at the end'
311 print '*** Stack not empty at the end'
312 print '***', self
.stack
313 if self
.includedepth
== 0:
314 while self
.nodestack
:
315 self
.nodestack
[-1].finalize()
316 self
.nodestack
[-1].flush()
317 del self
.nodestack
[-1]
319 # Start saving text in a buffer instead of writing it to a file
320 def startsaving(self
):
321 if self
.savetext
<> None:
322 self
.savestack
.append(self
.savetext
)
323 # print '*** Recursively saving text, expect trouble'
326 # Return the text saved so far and start writing to file again
327 def collectsavings(self
):
328 savetext
= self
.savetext
329 if len(self
.savestack
) > 0:
330 self
.savetext
= self
.savestack
[-1]
331 del self
.savestack
[-1]
334 return savetext
or ''
336 # Write text to file, or save it in a buffer, or ignore it
337 def write(self
, *args
):
343 if self
.savetext
<> None:
344 self
.savetext
= self
.savetext
+ text
346 self
.nodefp
.write(text
)
348 self
.node
.write(text
)
350 # Complete the current node -- write footnotes and close file
352 if self
.savetext
<> None:
353 print '*** Still saving text at end of node'
354 dummy
= self
.collectsavings()
356 self
.writefootnotes()
358 if self
.nodelineno
> 20:
360 [name
, next
, prev
, up
] = self
.nodelinks
[:4]
361 self
.link('Next', next
)
362 self
.link('Prev', prev
)
364 if self
.nodename
<> self
.topname
:
365 self
.link('Top', self
.topname
)
367 self
.write('</BODY>\n')
371 if not self
.cont
and \
372 (not self
.node
.type or \
373 (self
.node
.next
and self
.node
.prev
and self
.node
.up
)):
377 self
.nodestack
.append(self
.node
)
381 # Process a list of lines, expanding embedded @-commands
382 # This mostly distinguishes between menus and normal text
383 def process(self
, accu
):
384 if self
.debugging
> 1:
385 print '!'*self
.debugging
, 'process:', self
.skip
, self
.stack
,
386 if accu
: print accu
[0][:30],
387 if accu
[0][30:] or accu
[1:]: print '...',
390 # XXX should be done differently
392 mo
= miprog
.match(line
)
394 line
= line
.strip() + '\n'
397 bgn
, end
= mo
.span(0)
404 if nodename
[0] == ':': nodename
= label
405 else: nodename
= line
[e
:f
]
407 self
.write(' <LI><A HREF="',
411 self
.htmlhelp
.menuitem(nodename
)
412 self
.expand(line
[end
:])
417 # find 'menu' (we might be inside 'ifset' or 'ifclear')
419 #if 'menu' in self.stack:
420 # print 'inmenu :', self.skip, self.stack, self.stackinfo
422 while stack
and stack
[-1] in ('ifset','ifclear'):
424 if self
.stackinfo
[len(stack
)]:
429 return (stack
and stack
[-1] == 'menu')
431 # Write a string, expanding embedded @-commands
432 def expand(self
, text
):
438 mo
= spprog
.search(text
, i
)
442 self
.write(text
[start
:])
444 self
.write(text
[start
:i
])
464 print '*** Unmatched }'
470 method
= getattr(self
, 'close_' + cmd
)
471 except AttributeError:
472 self
.unknown_close(cmd
)
477 # Cannot happen unless spprog is changed
478 raise RuntimeError, 'unexpected funny %r' % c
480 while i
< n
and text
[i
] in string
.ascii_letters
: i
= i
+1
482 # @ plus non-letter: literal next character
486 # `@:' means no extra space after
487 # preceding `.', `?', `!' or `:'
490 # `@.' means a sentence-ending period;
491 # `@@', `@{', `@}' quote `@', `{', `}'
495 if i
< n
and text
[i
] == '{':
499 method
= getattr(self
, 'open_' + cmd
)
500 except AttributeError:
501 self
.unknown_open(cmd
)
506 method
= getattr(self
, 'handle_' + cmd
)
507 except AttributeError:
508 self
.unknown_handle(cmd
)
512 print '*** Stack not empty at para:', stack
514 # --- Handle unknown embedded @-commands ---
516 def unknown_open(self
, cmd
):
517 print '*** No open func for @' + cmd
+ '{...}'
520 if not self
.unknown
.has_key(cmd
):
521 self
.unknown
[cmd
] = 1
523 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
525 def unknown_close(self
, cmd
):
526 print '*** No close func for @' + cmd
+ '{...}'
529 if not self
.unknown
.has_key(cmd
):
530 self
.unknown
[cmd
] = 1
532 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
534 def unknown_handle(self
, cmd
):
535 print '*** No handler for @' + cmd
537 if not self
.unknown
.has_key(cmd
):
538 self
.unknown
[cmd
] = 1
540 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
542 # XXX The following sections should be ordered as the texinfo docs
544 # --- Embedded @-commands without {} argument list --
546 def handle_noindent(self
): pass
548 def handle_refill(self
): pass
550 # --- Include file handling ---
552 def do_include(self
, args
):
554 file = os
.path
.join(self
.includedir
, file)
558 print '*** Can\'t open include file', repr(file)
560 print '!'*self
.debugging
, '--> file', repr(file)
561 save_done
= self
.done
562 save_skip
= self
.skip
563 save_stack
= self
.stack
564 self
.includedepth
= self
.includedepth
+ 1
565 self
.parserest(fp
, 0)
566 self
.includedepth
= self
.includedepth
- 1
568 self
.done
= save_done
569 self
.skip
= save_skip
570 self
.stack
= save_stack
571 print '!'*self
.debugging
, '<-- file', repr(file)
573 # --- Special Insertions ---
575 def open_dmn(self
): pass
576 def close_dmn(self
): pass
578 def open_dots(self
): self
.write('...')
579 def close_dots(self
): pass
581 def open_bullet(self
): pass
582 def close_bullet(self
): pass
584 def open_TeX(self
): self
.write('TeX')
585 def close_TeX(self
): pass
587 def handle_copyright(self
): self
.write(self
.COPYRIGHT_SYMBOL
)
588 def open_copyright(self
): self
.write(self
.COPYRIGHT_SYMBOL
)
589 def close_copyright(self
): pass
591 def open_minus(self
): self
.write('-')
592 def close_minus(self
): pass
597 # I would like to do at least as well as makeinfo when
598 # it is producing HTML output:
601 # @"o @"o umlaut accent
602 # @'o 'o acute accent
603 # @,{c} @,{c} cedilla accent
604 # @=o @=o macron/overbar accent
605 # @^o @^o circumflex accent
606 # @`o `o grave accent
607 # @~o @~o tilde accent
608 # @dotaccent{o} @dotaccent{o} overdot accent
609 # @H{o} @H{o} long Hungarian umlaut
610 # @ringaccent{o} @ringaccent{o} ring accent
611 # @tieaccent{oo} @tieaccent{oo} tie-after accent
612 # @u{o} @u{o} breve accent
613 # @ubaraccent{o} @ubaraccent{o} underbar accent
614 # @udotaccent{o} @udotaccent{o} underdot accent
615 # @v{o} @v{o} hacek or check accent
616 # @exclamdown{} ¡ upside-down !
617 # @questiondown{} ¿ upside-down ?
618 # @aa{},@AA{} å,Å a,A with circle
619 # @ae{},@AE{} æ,Æ ae,AE ligatures
620 # @dotless{i} @dotless{i} dotless i
621 # @dotless{j} @dotless{j} dotless j
622 # @l{},@L{} l/,L/ suppressed-L,l
623 # @o{},@O{} ø,Ø O,o with slash
624 # @oe{},@OE{} oe,OE oe,OE ligatures
625 # @ss{} ß es-zet or sharp S
627 # The following character codes and approximations have been
628 # copied from makeinfo's HTML output.
630 def open_exclamdown(self
): self
.write('¡') # upside-down !
631 def close_exclamdown(self
): pass
632 def open_questiondown(self
): self
.write('¿') # upside-down ?
633 def close_questiondown(self
): pass
634 def open_aa(self
): self
.write('å') # a with circle
635 def close_aa(self
): pass
636 def open_AA(self
): self
.write('Å') # A with circle
637 def close_AA(self
): pass
638 def open_ae(self
): self
.write('æ') # ae ligatures
639 def close_ae(self
): pass
640 def open_AE(self
): self
.write('Æ') # AE ligatures
641 def close_AE(self
): pass
642 def open_o(self
): self
.write('ø') # o with slash
643 def close_o(self
): pass
644 def open_O(self
): self
.write('Ø') # O with slash
645 def close_O(self
): pass
646 def open_ss(self
): self
.write('ß') # es-zet or sharp S
647 def close_ss(self
): pass
648 def open_oe(self
): self
.write('oe') # oe ligatures
649 def close_oe(self
): pass
650 def open_OE(self
): self
.write('OE') # OE ligatures
651 def close_OE(self
): pass
652 def open_l(self
): self
.write('l/') # suppressed-l
653 def close_l(self
): pass
654 def open_L(self
): self
.write('L/') # suppressed-L
655 def close_L(self
): pass
657 # --- Special Glyphs for Examples ---
659 def open_result(self
): self
.write('=>')
660 def close_result(self
): pass
662 def open_expansion(self
): self
.write('==>')
663 def close_expansion(self
): pass
665 def open_print(self
): self
.write('-|')
666 def close_print(self
): pass
668 def open_error(self
): self
.write('error-->')
669 def close_error(self
): pass
671 def open_equiv(self
): self
.write('==')
672 def close_equiv(self
): pass
674 def open_point(self
): self
.write('-!-')
675 def close_point(self
): pass
677 # --- Cross References ---
679 def open_pxref(self
):
682 def close_pxref(self
):
688 def close_xref(self
):
696 def open_inforef(self
):
697 self
.write('See info file ')
699 def close_inforef(self
):
700 text
= self
.collectsavings()
701 args
= [s
.strip() for s
in text
.split(',')]
702 while len(args
) < 3: args
.append('')
705 self
.write('`', file, '\', node `', node
, '\'')
708 text
= self
.collectsavings()
709 args
= [s
.strip() for s
in text
.split(',')]
710 while len(args
) < 5: args
.append('')
711 nodename
= label
= args
[0]
712 if args
[2]: label
= args
[2]
715 href
= makefile(nodename
)
717 href
= '../' + file + '/' + href
718 self
.write('<A HREF="', href
, '">', label
, '</A>')
720 # rpyron 2002-05-07 uref support
723 def close_uref(self
):
724 text
= self
.collectsavings()
725 args
= [s
.strip() for s
in text
.split(',')]
726 while len(args
) < 2: args
.append('')
729 if not label
: label
= href
730 self
.write('<A HREF="', href
, '">', label
, '</A>')
732 # rpyron 2002-05-07 image support
733 # GNU makeinfo producing HTML output tries `filename.png'; if
734 # that does not exist, it tries `filename.jpg'. If that does
735 # not exist either, it complains. GNU makeinfo does not handle
736 # GIF files; however, I include GIF support here because
737 # MySQL documentation uses GIF files.
739 def open_image(self
):
741 def close_image(self
):
744 text
= self
.collectsavings()
745 args
= [s
.strip() for s
in text
.split(',')]
746 while len(args
) < 5: args
.append('')
753 # The HTML output will have a reference to the image
754 # that is relative to the HTML output directory,
755 # which is what 'filename' gives us. However, we need
756 # to find it relative to our own current directory,
757 # so we construct 'imagename'.
758 imagelocation
= self
.dirname
+ '/' + filename
760 if os
.path
.exists(imagelocation
+'.png'):
762 elif os
.path
.exists(imagelocation
+'.jpg'):
764 elif os
.path
.exists(imagelocation
+'.gif'): # MySQL uses GIF files
767 print "*** Cannot find image " + imagelocation
768 #TODO: what is 'ext'?
769 self
.write('<IMG SRC="', filename
, '"', \
770 width
and (' WIDTH="' + width
+ '"') or "", \
771 height
and (' HEIGHT="' + height
+ '"') or "", \
772 alt
and (' ALT="' + alt
+ '"') or "", \
774 self
.htmlhelp
.addimage(imagelocation
)
777 # --- Marking Words and Phrases ---
779 # --- Other @xxx{...} commands ---
781 def open_(self
): pass # Used by {text enclosed in braces}
782 def close_(self
): pass
787 def open_cite(self
): self
.write('<CITE>')
788 def close_cite(self
): self
.write('</CITE>')
790 def open_code(self
): self
.write('<CODE>')
791 def close_code(self
): self
.write('</CODE>')
793 def open_t(self
): self
.write('<TT>')
794 def close_t(self
): self
.write('</TT>')
796 def open_dfn(self
): self
.write('<DFN>')
797 def close_dfn(self
): self
.write('</DFN>')
799 def open_emph(self
): self
.write('<EM>')
800 def close_emph(self
): self
.write('</EM>')
802 def open_i(self
): self
.write('<I>')
803 def close_i(self
): self
.write('</I>')
805 def open_footnote(self
):
806 # if self.savetext <> None:
807 # print '*** Recursive footnote -- expect weirdness'
808 id = len(self
.footnotes
) + 1
809 self
.write(self
.FN_SOURCE_PATTERN
% {'id': repr(id)})
812 def close_footnote(self
):
813 id = len(self
.footnotes
) + 1
814 self
.footnotes
.append((id, self
.collectsavings()))
816 def writefootnotes(self
):
817 self
.write(self
.FN_HEADER
)
818 for id, text
in self
.footnotes
:
819 self
.write(self
.FN_TARGET_PATTERN
820 % {'id': repr(id), 'text': text
})
823 def open_file(self
): self
.write('<CODE>')
824 def close_file(self
): self
.write('</CODE>')
826 def open_kbd(self
): self
.write('<KBD>')
827 def close_kbd(self
): self
.write('</KBD>')
829 def open_key(self
): self
.write('<KEY>')
830 def close_key(self
): self
.write('</KEY>')
832 def open_r(self
): self
.write('<R>')
833 def close_r(self
): self
.write('</R>')
835 def open_samp(self
): self
.write('`<SAMP>')
836 def close_samp(self
): self
.write('</SAMP>\'')
838 def open_sc(self
): self
.write('<SMALLCAPS>')
839 def close_sc(self
): self
.write('</SMALLCAPS>')
841 def open_strong(self
): self
.write('<STRONG>')
842 def close_strong(self
): self
.write('</STRONG>')
844 def open_b(self
): self
.write('<B>')
845 def close_b(self
): self
.write('</B>')
847 def open_var(self
): self
.write('<VAR>')
848 def close_var(self
): self
.write('</VAR>')
850 def open_w(self
): self
.write('<NOBREAK>')
851 def close_w(self
): self
.write('</NOBREAK>')
853 def open_url(self
): self
.startsaving()
855 text
= self
.collectsavings()
856 self
.write('<A HREF="', text
, '">', text
, '</A>')
858 def open_email(self
): self
.startsaving()
859 def close_email(self
):
860 text
= self
.collectsavings()
861 self
.write('<A HREF="mailto:', text
, '">', text
, '</A>')
863 open_titlefont
= open_
864 close_titlefont
= close_
866 def open_small(self
): pass
867 def close_small(self
): pass
869 def command(self
, line
, mo
):
872 args
= line
[b
:].strip()
873 if self
.debugging
> 1:
874 print '!'*self
.debugging
, 'command:', self
.skip
, self
.stack
, \
877 func
= getattr(self
, 'do_' + cmd
)
878 except AttributeError:
880 func
= getattr(self
, 'bgn_' + cmd
)
881 except AttributeError:
882 # don't complain if we are skipping anyway
884 self
.unknown_cmd(cmd
, args
)
886 self
.stack
.append(cmd
)
889 if not self
.skip
or cmd
== 'end':
892 def unknown_cmd(self
, cmd
, args
):
893 print '*** unknown', '@' + cmd
, args
894 if not self
.unknown
.has_key(cmd
):
895 self
.unknown
[cmd
] = 1
897 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
899 def do_end(self
, args
):
902 print '*** @end w/o args'
905 if not self
.stack
or self
.stack
[-1] <> cmd
:
906 print '*** @end', cmd
, 'unexpected'
910 func
= getattr(self
, 'end_' + cmd
)
911 except AttributeError:
912 self
.unknown_end(cmd
)
916 def unknown_end(self
, cmd
):
918 print '*** unknown', '@' + cmd
919 if not self
.unknown
.has_key(cmd
):
920 self
.unknown
[cmd
] = 1
922 self
.unknown
[cmd
] = self
.unknown
[cmd
] + 1
926 def do_comment(self
, args
): pass
929 # --- Conditional processing ---
931 def bgn_ifinfo(self
, args
): pass
932 def end_ifinfo(self
): pass
934 def bgn_iftex(self
, args
): self
.skip
= self
.skip
+ 1
935 def end_iftex(self
): self
.skip
= self
.skip
- 1
937 def bgn_ignore(self
, args
): self
.skip
= self
.skip
+ 1
938 def end_ignore(self
): self
.skip
= self
.skip
- 1
940 def bgn_tex(self
, args
): self
.skip
= self
.skip
+ 1
941 def end_tex(self
): self
.skip
= self
.skip
- 1
943 def do_set(self
, args
):
944 fields
= args
.split(' ')
949 value
= ' '.join(fields
[1:])
950 self
.values
[key
] = value
952 def do_clear(self
, args
):
953 self
.values
[args
] = None
955 def bgn_ifset(self
, args
):
956 if args
not in self
.values
.keys() \
957 or self
.values
[args
] is None:
958 self
.skip
= self
.skip
+ 1
959 self
.stackinfo
[len(self
.stack
)] = 1
961 self
.stackinfo
[len(self
.stack
)] = 0
964 if self
.stackinfo
[len(self
.stack
) + 1]:
965 self
.skip
= self
.skip
- 1
966 del self
.stackinfo
[len(self
.stack
) + 1]
968 print '*** end_ifset: KeyError :', len(self
.stack
) + 1
970 def bgn_ifclear(self
, args
):
971 if args
in self
.values
.keys() \
972 and self
.values
[args
] is not None:
973 self
.skip
= self
.skip
+ 1
974 self
.stackinfo
[len(self
.stack
)] = 1
976 self
.stackinfo
[len(self
.stack
)] = 0
977 def end_ifclear(self
):
979 if self
.stackinfo
[len(self
.stack
) + 1]:
980 self
.skip
= self
.skip
- 1
981 del self
.stackinfo
[len(self
.stack
) + 1]
983 print '*** end_ifclear: KeyError :', len(self
.stack
) + 1
985 def open_value(self
):
988 def close_value(self
):
989 key
= self
.collectsavings()
990 if key
in self
.values
.keys():
991 self
.write(self
.values
[key
])
993 print '*** Undefined value: ', key
995 # --- Beginning a file ---
997 do_finalout
= do_comment
998 do_setchapternewpage
= do_comment
999 do_setfilename
= do_comment
1001 def do_settitle(self
, args
):
1004 self
.title
= self
.collectsavings()
1005 def do_parskip(self
, args
): pass
1007 # --- Ending a file ---
1009 def do_bye(self
, args
):
1013 # --- Title page ---
1015 def bgn_titlepage(self
, args
): self
.skip
= self
.skip
+ 1
1016 def end_titlepage(self
): self
.skip
= self
.skip
- 1
1017 def do_shorttitlepage(self
, args
): pass
1019 def do_center(self
, args
):
1020 # Actually not used outside title page...
1023 self
.write('</H1>\n')
1024 do_title
= do_center
1025 do_subtitle
= do_center
1026 do_author
= do_center
1028 do_vskip
= do_comment
1029 do_vfill
= do_comment
1030 do_smallbook
= do_comment
1032 do_paragraphindent
= do_comment
1033 do_setchapternewpage
= do_comment
1034 do_headings
= do_comment
1035 do_footnotestyle
= do_comment
1037 do_evenheading
= do_comment
1038 do_evenfooting
= do_comment
1039 do_oddheading
= do_comment
1040 do_oddfooting
= do_comment
1041 do_everyheading
= do_comment
1042 do_everyfooting
= do_comment
1046 def do_node(self
, args
):
1049 parts
= [s
.strip() for s
in args
.split(',')]
1050 while len(parts
) < 4: parts
.append('')
1051 self
.nodelinks
= parts
1052 [name
, next
, prev
, up
] = parts
[:4]
1053 file = self
.dirname
+ '/' + makefile(name
)
1054 if self
.filenames
.has_key(file):
1055 print '*** Filename already in use: ', file
1057 if self
.debugging
: print '!'*self
.debugging
, '--- writing', file
1058 self
.filenames
[file] = 1
1059 # self.nodefp = open(file, 'w')
1060 self
.nodename
= name
1061 if self
.cont
and self
.nodestack
:
1062 self
.nodestack
[-1].cont
= self
.nodename
1063 if not self
.topname
: self
.topname
= name
1065 if self
.title
: title
= title
+ ' -- ' + self
.title
1066 self
.node
= self
.Node(self
.dirname
, self
.nodename
, self
.topname
,
1067 title
, next
, prev
, up
)
1068 self
.htmlhelp
.addnode(self
.nodename
,next
,prev
,up
,file)
1070 def link(self
, label
, nodename
):
1072 if nodename
.lower() == '(dir)':
1073 addr
= '../dir.html'
1075 addr
= makefile(nodename
)
1076 self
.write(label
, ': <A HREF="', addr
, '" TYPE="',
1077 label
, '">', nodename
, '</A> \n')
1079 # --- Sectioning commands ---
1081 def popstack(self
, type):
1083 self
.node
.type = type
1084 while self
.nodestack
:
1085 if self
.nodestack
[-1].type > type:
1086 self
.nodestack
[-1].finalize()
1087 self
.nodestack
[-1].flush()
1088 del self
.nodestack
[-1]
1089 elif self
.nodestack
[-1].type == type:
1090 if not self
.nodestack
[-1].next
:
1091 self
.nodestack
[-1].next
= self
.node
.name
1092 if not self
.node
.prev
:
1093 self
.node
.prev
= self
.nodestack
[-1].name
1094 self
.nodestack
[-1].finalize()
1095 self
.nodestack
[-1].flush()
1096 del self
.nodestack
[-1]
1098 if type > 1 and not self
.node
.up
:
1099 self
.node
.up
= self
.nodestack
[-1].name
1102 def do_chapter(self
, args
):
1103 self
.heading('H1', args
, 0)
1106 def do_unnumbered(self
, args
):
1107 self
.heading('H1', args
, -1)
1109 def do_appendix(self
, args
):
1110 self
.heading('H1', args
, -1)
1112 def do_top(self
, args
):
1113 self
.heading('H1', args
, -1)
1114 def do_chapheading(self
, args
):
1115 self
.heading('H1', args
, -1)
1116 def do_majorheading(self
, args
):
1117 self
.heading('H1', args
, -1)
1119 def do_section(self
, args
):
1120 self
.heading('H1', args
, 1)
1123 def do_unnumberedsec(self
, args
):
1124 self
.heading('H1', args
, -1)
1126 def do_appendixsec(self
, args
):
1127 self
.heading('H1', args
, -1)
1129 do_appendixsection
= do_appendixsec
1130 def do_heading(self
, args
):
1131 self
.heading('H1', args
, -1)
1133 def do_subsection(self
, args
):
1134 self
.heading('H2', args
, 2)
1136 def do_unnumberedsubsec(self
, args
):
1137 self
.heading('H2', args
, -1)
1139 def do_appendixsubsec(self
, args
):
1140 self
.heading('H2', args
, -1)
1142 def do_subheading(self
, args
):
1143 self
.heading('H2', args
, -1)
1145 def do_subsubsection(self
, args
):
1146 self
.heading('H3', args
, 3)
1148 def do_unnumberedsubsubsec(self
, args
):
1149 self
.heading('H3', args
, -1)
1151 def do_appendixsubsubsec(self
, args
):
1152 self
.heading('H3', args
, -1)
1154 def do_subsubheading(self
, args
):
1155 self
.heading('H3', args
, -1)
1157 def heading(self
, type, args
, level
):
1159 while len(self
.numbering
) <= level
:
1160 self
.numbering
.append(0)
1161 del self
.numbering
[level
+1:]
1162 self
.numbering
[level
] = self
.numbering
[level
] + 1
1164 for i
in self
.numbering
:
1165 x
= x
+ repr(i
) + '.'
1166 args
= x
+ ' ' + args
1167 self
.contents
.append((level
, args
, self
.nodename
))
1168 self
.write('<', type, '>')
1170 self
.write('</', type, '>\n')
1171 if self
.debugging
or self
.print_headers
:
1174 def do_contents(self
, args
):
1176 self
.listcontents('Table of Contents', 999)
1178 def do_shortcontents(self
, args
):
1180 # self.listcontents('Short Contents', 0)
1181 do_summarycontents
= do_shortcontents
1183 def listcontents(self
, title
, maxlevel
):
1184 self
.write('<H1>', title
, '</H1>\n<UL COMPACT PLAIN>\n')
1186 for level
, title
, node
in self
.contents
:
1187 if level
> maxlevel
:
1189 if level
> prevlevels
[-1]:
1190 # can only advance one level at a time
1191 self
.write(' '*prevlevels
[-1], '<UL PLAIN>\n')
1192 prevlevels
.append(level
)
1193 elif level
< prevlevels
[-1]:
1194 # might drop back multiple levels
1195 while level
< prevlevels
[-1]:
1197 self
.write(' '*prevlevels
[-1],
1199 self
.write(' '*level
, '<LI> <A HREF="',
1200 makefile(node
), '">')
1202 self
.write('</A>\n')
1203 self
.write('</UL>\n' * len(prevlevels
))
1205 # --- Page lay-out ---
1207 # These commands are only meaningful in printed text
1209 def do_page(self
, args
): pass
1211 def do_need(self
, args
): pass
1213 def bgn_group(self
, args
): pass
1214 def end_group(self
): pass
1216 # --- Line lay-out ---
1218 def do_sp(self
, args
):
1224 def do_hline(self
, args
):
1227 # --- Function and variable definitions ---
1229 def bgn_deffn(self
, args
):
1231 self
.do_deffnx(args
)
1233 def end_deffn(self
):
1234 self
.write('</DL>\n')
1236 def do_deffnx(self
, args
):
1238 words
= splitwords(args
, 2)
1239 [category
, name
], rest
= words
[:2], words
[2:]
1240 self
.expand('@b{%s}' % name
)
1241 for word
in rest
: self
.expand(' ' + makevar(word
))
1242 #self.expand(' -- ' + category)
1243 self
.write('\n<DD>')
1244 self
.index('fn', name
)
1246 def bgn_defun(self
, args
): self
.bgn_deffn('Function ' + args
)
1247 end_defun
= end_deffn
1248 def do_defunx(self
, args
): self
.do_deffnx('Function ' + args
)
1250 def bgn_defmac(self
, args
): self
.bgn_deffn('Macro ' + args
)
1251 end_defmac
= end_deffn
1252 def do_defmacx(self
, args
): self
.do_deffnx('Macro ' + args
)
1254 def bgn_defspec(self
, args
): self
.bgn_deffn('{Special Form} ' + args
)
1255 end_defspec
= end_deffn
1256 def do_defspecx(self
, args
): self
.do_deffnx('{Special Form} ' + args
)
1258 def bgn_defvr(self
, args
):
1260 self
.do_defvrx(args
)
1262 end_defvr
= end_deffn
1264 def do_defvrx(self
, args
):
1266 words
= splitwords(args
, 2)
1267 [category
, name
], rest
= words
[:2], words
[2:]
1268 self
.expand('@code{%s}' % name
)
1269 # If there are too many arguments, show them
1270 for word
in rest
: self
.expand(' ' + word
)
1271 #self.expand(' -- ' + category)
1272 self
.write('\n<DD>')
1273 self
.index('vr', name
)
1275 def bgn_defvar(self
, args
): self
.bgn_defvr('Variable ' + args
)
1276 end_defvar
= end_defvr
1277 def do_defvarx(self
, args
): self
.do_defvrx('Variable ' + args
)
1279 def bgn_defopt(self
, args
): self
.bgn_defvr('{User Option} ' + args
)
1280 end_defopt
= end_defvr
1281 def do_defoptx(self
, args
): self
.do_defvrx('{User Option} ' + args
)
1283 # --- Ditto for typed languages ---
1285 def bgn_deftypefn(self
, args
):
1287 self
.do_deftypefnx(args
)
1289 end_deftypefn
= end_deffn
1291 def do_deftypefnx(self
, args
):
1293 words
= splitwords(args
, 3)
1294 [category
, datatype
, name
], rest
= words
[:3], words
[3:]
1295 self
.expand('@code{%s} @b{%s}' % (datatype
, name
))
1296 for word
in rest
: self
.expand(' ' + makevar(word
))
1297 #self.expand(' -- ' + category)
1298 self
.write('\n<DD>')
1299 self
.index('fn', name
)
1302 def bgn_deftypefun(self
, args
): self
.bgn_deftypefn('Function ' + args
)
1303 end_deftypefun
= end_deftypefn
1304 def do_deftypefunx(self
, args
): self
.do_deftypefnx('Function ' + args
)
1306 def bgn_deftypevr(self
, args
):
1308 self
.do_deftypevrx(args
)
1310 end_deftypevr
= end_deftypefn
1312 def do_deftypevrx(self
, args
):
1314 words
= splitwords(args
, 3)
1315 [category
, datatype
, name
], rest
= words
[:3], words
[3:]
1316 self
.expand('@code{%s} @b{%s}' % (datatype
, name
))
1317 # If there are too many arguments, show them
1318 for word
in rest
: self
.expand(' ' + word
)
1319 #self.expand(' -- ' + category)
1320 self
.write('\n<DD>')
1321 self
.index('fn', name
)
1323 def bgn_deftypevar(self
, args
):
1324 self
.bgn_deftypevr('Variable ' + args
)
1325 end_deftypevar
= end_deftypevr
1326 def do_deftypevarx(self
, args
):
1327 self
.do_deftypevrx('Variable ' + args
)
1329 # --- Ditto for object-oriented languages ---
1331 def bgn_defcv(self
, args
):
1333 self
.do_defcvx(args
)
1335 end_defcv
= end_deftypevr
1337 def do_defcvx(self
, args
):
1339 words
= splitwords(args
, 3)
1340 [category
, classname
, name
], rest
= words
[:3], words
[3:]
1341 self
.expand('@b{%s}' % name
)
1342 # If there are too many arguments, show them
1343 for word
in rest
: self
.expand(' ' + word
)
1344 #self.expand(' -- %s of @code{%s}' % (category, classname))
1345 self
.write('\n<DD>')
1346 self
.index('vr', '%s @r{on %s}' % (name
, classname
))
1348 def bgn_defivar(self
, args
):
1349 self
.bgn_defcv('{Instance Variable} ' + args
)
1350 end_defivar
= end_defcv
1351 def do_defivarx(self
, args
):
1352 self
.do_defcvx('{Instance Variable} ' + args
)
1354 def bgn_defop(self
, args
):
1356 self
.do_defopx(args
)
1358 end_defop
= end_defcv
1360 def do_defopx(self
, args
):
1362 words
= splitwords(args
, 3)
1363 [category
, classname
, name
], rest
= words
[:3], words
[3:]
1364 self
.expand('@b{%s}' % name
)
1365 for word
in rest
: self
.expand(' ' + makevar(word
))
1366 #self.expand(' -- %s of @code{%s}' % (category, classname))
1367 self
.write('\n<DD>')
1368 self
.index('fn', '%s @r{on %s}' % (name
, classname
))
1370 def bgn_defmethod(self
, args
):
1371 self
.bgn_defop('Method ' + args
)
1372 end_defmethod
= end_defop
1373 def do_defmethodx(self
, args
):
1374 self
.do_defopx('Method ' + args
)
1376 # --- Ditto for data types ---
1378 def bgn_deftp(self
, args
):
1380 self
.do_deftpx(args
)
1382 end_deftp
= end_defcv
1384 def do_deftpx(self
, args
):
1386 words
= splitwords(args
, 2)
1387 [category
, name
], rest
= words
[:2], words
[2:]
1388 self
.expand('@b{%s}' % name
)
1389 for word
in rest
: self
.expand(' ' + word
)
1390 #self.expand(' -- ' + category)
1391 self
.write('\n<DD>')
1392 self
.index('tp', name
)
1394 # --- Making Lists and Tables
1396 def bgn_enumerate(self
, args
):
1398 self
.write('<OL>\n')
1399 self
.stackinfo
[len(self
.stack
)] = '</OL>\n'
1401 self
.itemnumber
= args
1402 self
.write('<UL>\n')
1403 self
.stackinfo
[len(self
.stack
)] = '</UL>\n'
1404 def end_enumerate(self
):
1405 self
.itemnumber
= None
1406 self
.write(self
.stackinfo
[len(self
.stack
) + 1])
1407 del self
.stackinfo
[len(self
.stack
) + 1]
1409 def bgn_itemize(self
, args
):
1411 self
.write('<UL>\n')
1412 def end_itemize(self
):
1414 self
.write('</UL>\n')
1416 def bgn_table(self
, args
):
1418 self
.write('<DL>\n')
1419 def end_table(self
):
1421 self
.write('</DL>\n')
1423 def bgn_ftable(self
, args
):
1424 self
.itemindex
= 'fn'
1425 self
.bgn_table(args
)
1426 def end_ftable(self
):
1427 self
.itemindex
= None
1430 def bgn_vtable(self
, args
):
1431 self
.itemindex
= 'vr'
1432 self
.bgn_table(args
)
1433 def end_vtable(self
):
1434 self
.itemindex
= None
1437 def do_item(self
, args
):
1438 if self
.itemindex
: self
.index(self
.itemindex
, args
)
1440 if self
.itemarg
[0] == '@' and self
.itemarg
[1] and \
1441 self
.itemarg
[1] in string
.ascii_letters
:
1442 args
= self
.itemarg
+ '{' + args
+ '}'
1444 # some other character, e.g. '-'
1445 args
= self
.itemarg
+ ' ' + args
1446 if self
.itemnumber
<> None:
1447 args
= self
.itemnumber
+ '. ' + args
1448 self
.itemnumber
= increment(self
.itemnumber
)
1449 if self
.stack
and self
.stack
[-1] == 'table':
1452 self
.write('\n<DD>')
1453 elif self
.stack
and self
.stack
[-1] == 'multitable':
1454 self
.write('<TR><TD>')
1456 self
.write('</TD>\n</TR>\n')
1461 do_itemx
= do_item
# XXX Should suppress leading blank line
1463 # rpyron 2002-05-07 multitable support
1464 def bgn_multitable(self
, args
):
1465 self
.itemarg
= None # should be handled by columnfractions
1466 self
.write('<TABLE BORDER="">\n')
1467 def end_multitable(self
):
1469 self
.write('</TABLE>\n<BR>\n')
1470 def handle_columnfractions(self
):
1471 # It would be better to handle this, but for now it's in the way...
1473 def handle_tab(self
):
1474 self
.write('</TD>\n <TD>')
1476 # --- Enumerations, displays, quotations ---
1477 # XXX Most of these should increase the indentation somehow
1479 def bgn_quotation(self
, args
): self
.write('<BLOCKQUOTE>')
1480 def end_quotation(self
): self
.write('</BLOCKQUOTE>\n')
1482 def bgn_example(self
, args
):
1483 self
.nofill
= self
.nofill
+ 1
1485 def end_example(self
):
1486 self
.write('</PRE>\n')
1487 self
.nofill
= self
.nofill
- 1
1489 bgn_lisp
= bgn_example
# Synonym when contents are executable lisp code
1490 end_lisp
= end_example
1492 bgn_smallexample
= bgn_example
# XXX Should use smaller font
1493 end_smallexample
= end_example
1495 bgn_smalllisp
= bgn_lisp
# Ditto
1496 end_smalllisp
= end_lisp
1498 bgn_display
= bgn_example
1499 end_display
= end_example
1501 bgn_format
= bgn_display
1502 end_format
= end_display
1504 def do_exdent(self
, args
): self
.expand(args
+ '\n')
1505 # XXX Should really mess with indentation
1507 def bgn_flushleft(self
, args
):
1508 self
.nofill
= self
.nofill
+ 1
1509 self
.write('<PRE>\n')
1510 def end_flushleft(self
):
1511 self
.write('</PRE>\n')
1512 self
.nofill
= self
.nofill
- 1
1514 def bgn_flushright(self
, args
):
1515 self
.nofill
= self
.nofill
+ 1
1516 self
.write('<ADDRESS COMPACT>\n')
1517 def end_flushright(self
):
1518 self
.write('</ADDRESS>\n')
1519 self
.nofill
= self
.nofill
- 1
1521 def bgn_menu(self
, args
):
1522 self
.write('<DIR>\n')
1523 self
.write(' <STRONG><EM>Menu</EM></STRONG><P>\n')
1524 self
.htmlhelp
.beginmenu()
1526 self
.write('</DIR>\n')
1527 self
.htmlhelp
.endmenu()
1529 def bgn_cartouche(self
, args
): pass
1530 def end_cartouche(self
): pass
1534 def resetindex(self
):
1535 self
.noncodeindices
= ['cp']
1536 self
.indextitle
= {}
1537 self
.indextitle
['cp'] = 'Concept'
1538 self
.indextitle
['fn'] = 'Function'
1539 self
.indextitle
['ky'] = 'Keyword'
1540 self
.indextitle
['pg'] = 'Program'
1541 self
.indextitle
['tp'] = 'Type'
1542 self
.indextitle
['vr'] = 'Variable'
1544 self
.whichindex
= {}
1545 for name
in self
.indextitle
.keys():
1546 self
.whichindex
[name
] = []
1548 def user_index(self
, name
, args
):
1549 if self
.whichindex
.has_key(name
):
1550 self
.index(name
, args
)
1552 print '*** No index named', repr(name
)
1554 def do_cindex(self
, args
): self
.index('cp', args
)
1555 def do_findex(self
, args
): self
.index('fn', args
)
1556 def do_kindex(self
, args
): self
.index('ky', args
)
1557 def do_pindex(self
, args
): self
.index('pg', args
)
1558 def do_tindex(self
, args
): self
.index('tp', args
)
1559 def do_vindex(self
, args
): self
.index('vr', args
)
1561 def index(self
, name
, args
):
1562 self
.whichindex
[name
].append((args
, self
.nodename
))
1563 self
.htmlhelp
.index(args
, self
.nodename
)
1565 def do_synindex(self
, args
):
1566 words
= args
.split()
1568 print '*** bad @synindex', args
1571 if not self
.whichindex
.has_key(old
) or \
1572 not self
.whichindex
.has_key(new
):
1573 print '*** bad key(s) in @synindex', args
1576 self
.whichindex
[old
] is not self
.whichindex
[new
]:
1577 inew
= self
.whichindex
[new
]
1578 inew
[len(inew
):] = self
.whichindex
[old
]
1579 self
.whichindex
[old
] = inew
1580 do_syncodeindex
= do_synindex
# XXX Should use code font
1582 def do_printindex(self
, args
):
1583 words
= args
.split()
1585 if self
.whichindex
.has_key(name
):
1588 print '*** No index named', repr(name
)
1590 def prindex(self
, name
):
1591 iscodeindex
= (name
not in self
.noncodeindices
)
1592 index
= self
.whichindex
[name
]
1593 if not index
: return
1595 print '!'*self
.debugging
, '--- Generating', \
1596 self
.indextitle
[name
], 'index'
1597 # The node already provides a title
1599 junkprog
= re
.compile('^(@[a-z]+)?{')
1600 for key
, node
in index
:
1601 sortkey
= key
.lower()
1602 # Remove leading `@cmd{' from sort key
1603 # -- don't bother about the matching `}'
1604 oldsortkey
= sortkey
1606 mo
= junkprog
.match(sortkey
)
1610 sortkey
= sortkey
[i
:]
1611 index1
.append((sortkey
, key
, node
))
1614 self
.write('<DL COMPACT>\n')
1615 prevkey
= prevnode
= None
1616 for sortkey
, key
, node
in index1
:
1617 if (key
, node
) == (prevkey
, prevnode
):
1619 if self
.debugging
> 1: print '!'*self
.debugging
, key
, ':', node
1621 if iscodeindex
: key
= '@code{' + key
+ '}'
1624 self
.write('\n<DD><A HREF="%s">%s</A>\n' % (makefile(node
), node
))
1625 prevkey
, prevnode
= key
, node
1626 self
.write('</DL>\n')
1628 # --- Final error reports ---
1632 print '--- Unrecognized commands ---'
1633 cmds
= self
.unknown
.keys()
1636 print cmd
.ljust(20), self
.unknown
[cmd
]
1639 class TexinfoParserHTML3(TexinfoParser
):
1641 COPYRIGHT_SYMBOL
= "©"
1642 FN_ID_PATTERN
= "[%(id)s]"
1643 FN_SOURCE_PATTERN
= '<A ID=footnoteref%(id)s ' \
1644 'HREF="#footnotetext%(id)s">' + FN_ID_PATTERN
+ '</A>'
1645 FN_TARGET_PATTERN
= '<FN ID=footnotetext%(id)s>\n' \
1646 '<P><A HREF="#footnoteref%(id)s">' + FN_ID_PATTERN \
1647 + '</A>\n%(text)s</P></FN>\n'
1648 FN_HEADER
= '<DIV CLASS=footnotes>\n <HR NOSHADE WIDTH=200>\n' \
1649 ' <STRONG><EM>Footnotes</EM></STRONG>\n <P>\n'
1653 def bgn_quotation(self
, args
): self
.write('<BQ>')
1654 def end_quotation(self
): self
.write('</BQ>\n')
1656 def bgn_example(self
, args
):
1657 # this use of <CODE> would not be legal in HTML 2.0,
1658 # but is in more recent DTDs.
1659 self
.nofill
= self
.nofill
+ 1
1660 self
.write('<PRE CLASS=example><CODE>')
1661 def end_example(self
):
1662 self
.write("</CODE></PRE>\n")
1663 self
.nofill
= self
.nofill
- 1
1665 def bgn_flushleft(self
, args
):
1666 self
.nofill
= self
.nofill
+ 1
1667 self
.write('<PRE CLASS=flushleft>\n')
1669 def bgn_flushright(self
, args
):
1670 self
.nofill
= self
.nofill
+ 1
1671 self
.write('<DIV ALIGN=right CLASS=flushright><ADDRESS COMPACT>\n')
1672 def end_flushright(self
):
1673 self
.write('</ADDRESS></DIV>\n')
1674 self
.nofill
= self
.nofill
- 1
1676 def bgn_menu(self
, args
):
1677 self
.write('<UL PLAIN CLASS=menu>\n')
1678 self
.write(' <LH>Menu</LH>\n')
1680 self
.write('</UL>\n')
1686 This class encapsulates support for HTML Help. Node names,
1687 file names, menu items, index items, and image file names are
1688 accumulated until a call to finalize(). At that time, three
1689 output files are created in the current directory:
1691 `helpbase`.hhp is a HTML Help Workshop project file.
1692 It contains various information, some of
1693 which I do not understand; I just copied
1694 the default project info from a fresh
1696 `helpbase`.hhc is the Contents file for the project.
1697 `helpbase`.hhk is the Index file for the project.
1699 When these files are used as input to HTML Help Workshop,
1700 the resulting file will be named:
1704 If none of the defaults in `helpbase`.hhp are changed,
1705 the .CHM file will have Contents, Index, Search, and
1709 codeprog
= re
.compile('@code{(.*?)}')
1711 def __init__(self
,helpbase
,dirname
):
1712 self
.helpbase
= helpbase
1713 self
.dirname
= dirname
1714 self
.projectfile
= None
1715 self
.contentfile
= None
1716 self
.indexfile
= None
1718 self
.nodenames
= {} # nodename : index
1720 self
.filenames
= {} # filename : filename
1721 self
.indexlist
= [] # (args,nodename) == (key,location)
1727 def addnode(self
,name
,next
,prev
,up
,filename
):
1728 node
= (name
,next
,prev
,up
,filename
)
1729 # add this file to dict
1730 # retrieve list with self.filenames.values()
1731 self
.filenames
[filename
] = filename
1732 # add this node to nodelist
1733 self
.nodeindex
[name
] = len(self
.nodelist
)
1734 self
.nodelist
.append(node
)
1735 # set 'current' for menu items
1737 self
.menudict
[self
.current
] = []
1739 def menuitem(self
,nodename
):
1740 menu
= self
.menudict
[self
.current
]
1741 menu
.append(nodename
)
1744 def addimage(self
,imagename
):
1745 self
.filenames
[imagename
] = imagename
1747 def index(self
, args
, nodename
):
1748 self
.indexlist
.append((args
,nodename
))
1750 def beginmenu(self
):
1757 if not self
.helpbase
:
1760 # generate interesting filenames
1761 resultfile
= self
.helpbase
+ '.chm'
1762 projectfile
= self
.helpbase
+ '.hhp'
1763 contentfile
= self
.helpbase
+ '.hhc'
1764 indexfile
= self
.helpbase
+ '.hhk'
1766 # generate a reasonable title
1767 title
= self
.helpbase
1769 # get the default topic file
1770 (topname
,topnext
,topprev
,topup
,topfile
) = self
.nodelist
[0]
1771 defaulttopic
= topfile
1775 fp
= open(projectfile
,'w')
1776 print>>fp
, '[OPTIONS]'
1777 print>>fp
, 'Auto Index=Yes'
1778 print>>fp
, 'Binary TOC=No'
1779 print>>fp
, 'Binary Index=Yes'
1780 print>>fp
, 'Compatibility=1.1'
1781 print>>fp
, 'Compiled file=' + resultfile
+ ''
1782 print>>fp
, 'Contents file=' + contentfile
+ ''
1783 print>>fp
, 'Default topic=' + defaulttopic
+ ''
1784 print>>fp
, 'Error log file=ErrorLog.log'
1785 print>>fp
, 'Index file=' + indexfile
+ ''
1786 print>>fp
, 'Title=' + title
+ ''
1787 print>>fp
, 'Display compile progress=Yes'
1788 print>>fp
, 'Full-text search=Yes'
1789 print>>fp
, 'Default window=main'
1791 print>>fp
, '[WINDOWS]'
1792 print>>fp
, ('main=,"' + contentfile
+ '","' + indexfile
1793 + '","","",,,,,0x23520,222,0x1046,[10,10,780,560],'
1796 print>>fp
, '[FILES]'
1800 except IOError, msg
:
1801 print projectfile
, ':', msg
1806 fp
= open(contentfile
,'w')
1807 print>>fp
, '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'
1808 print>>fp
, '<!-- This file defines the table of contents -->'
1811 print>>fp
, ('<meta name="GENERATOR"'
1812 'content="Microsoft® HTML Help Workshop 4.1">')
1813 print>>fp
, '<!-- Sitemap 1.0 -->'
1814 print>>fp
, '</HEAD>'
1816 print>>fp
, ' <OBJECT type="text/site properties">'
1817 print>>fp
, ' <param name="Window Styles" value="0x800025">'
1818 print>>fp
, ' <param name="comment" value="title:">'
1819 print>>fp
, ' <param name="comment" value="base:">'
1820 print>>fp
, ' </OBJECT>'
1822 print>>fp
, '</BODY>'
1823 print>>fp
, '</HTML>'
1825 except IOError, msg
:
1826 print contentfile
, ':', msg
1831 fp
= open(indexfile
,'w')
1832 print>>fp
, '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'
1833 print>>fp
, '<!-- This file defines the index -->'
1836 print>>fp
, ('<meta name="GENERATOR"'
1837 'content="Microsoft® HTML Help Workshop 4.1">')
1838 print>>fp
, '<!-- Sitemap 1.0 -->'
1839 print>>fp
, '</HEAD>'
1841 print>>fp
, '<OBJECT type="text/site properties">'
1842 print>>fp
, '</OBJECT>'
1844 print>>fp
, '</BODY>'
1845 print>>fp
, '</HTML>'
1847 except IOError, msg
:
1848 print indexfile
, ':', msg
1851 def dumpfiles(self
, outfile
=sys
.stdout
):
1852 filelist
= self
.filenames
.values()
1854 for filename
in filelist
:
1855 print>>outfile
, filename
1857 def dumpnodes(self
, outfile
=sys
.stdout
):
1860 nodename
, dummy
, dummy
, dummy
, dummy
= self
.nodelist
[0]
1861 self
.topnode
= nodename
1863 print>>outfile
, '<UL>'
1864 for node
in self
.nodelist
:
1865 self
.dumpnode(node
,0,outfile
)
1866 print>>outfile
, '</UL>'
1868 def dumpnode(self
, node
, indent
=0, outfile
=sys
.stdout
):
1870 # Retrieve info for this node
1871 (nodename
,next
,prev
,up
,filename
) = node
1872 self
.current
= nodename
1874 # Have we been dumped already?
1875 if self
.dumped
.has_key(nodename
):
1877 self
.dumped
[nodename
] = 1
1879 # Print info for this node
1880 print>>outfile
, ' '*indent
,
1881 print>>outfile
, '<LI><OBJECT type="text/sitemap">',
1882 print>>outfile
, '<param name="Name" value="' + nodename
+'">',
1883 print>>outfile
, '<param name="Local" value="'+ filename
+'">',
1884 print>>outfile
, '</OBJECT>'
1886 # Does this node have menu items?
1888 menu
= self
.menudict
[nodename
]
1889 self
.dumpmenu(menu
,indent
+2,outfile
)
1893 def dumpmenu(self
, menu
, indent
=0, outfile
=sys
.stdout
):
1895 currentnode
= self
.current
1896 if currentnode
!= self
.topnode
: # XXX this is a hack
1897 print>>outfile
, ' '*indent
+ '<UL>'
1900 menunode
= self
.getnode(item
)
1901 self
.dumpnode(menunode
,indent
,outfile
)
1902 if currentnode
!= self
.topnode
: # XXX this is a hack
1903 print>>outfile
, ' '*indent
+ '</UL>'
1906 def getnode(self
, nodename
):
1908 index
= self
.nodeindex
[nodename
]
1909 return self
.nodelist
[index
]
1915 # (args,nodename) == (key,location)
1916 def dumpindex(self
, outfile
=sys
.stdout
):
1917 print>>outfile
, '<UL>'
1918 for (key
,location
) in self
.indexlist
:
1919 key
= self
.codeexpand(key
)
1920 location
= makefile(location
)
1921 location
= self
.dirname
+ '/' + location
1922 print>>outfile
, '<LI><OBJECT type="text/sitemap">',
1923 print>>outfile
, '<param name="Name" value="' + key
+ '">',
1924 print>>outfile
, '<param name="Local" value="' + location
+ '">',
1925 print>>outfile
, '</OBJECT>'
1926 print>>outfile
, '</UL>'
1928 def codeexpand(self
, line
):
1929 co
= self
.codeprog
.match(line
)
1932 bgn
, end
= co
.span(0)
1934 line
= line
[:bgn
] + line
[a
:b
] + line
[end
:]
1938 # Put @var{} around alphabetic substrings
1940 return '@var{'+str+'}'
1943 # Split a string in "words" according to findwordend
1944 def splitwords(str, minlength
):
1949 while i
< n
and str[i
] in ' \t\n': i
= i
+1
1952 i
= findwordend(str, i
, n
)
1953 words
.append(str[start
:i
])
1954 while len(words
) < minlength
: words
.append('')
1958 # Find the end of a "word", matching braces and interpreting @@ @{ @}
1959 fwprog
= re
.compile('[@{} ]')
1960 def findwordend(str, i
, n
):
1963 mo
= fwprog
.search(str, i
)
1968 if c
== '@': i
= i
+1 # Next character is not special
1969 elif c
== '{': level
= level
+1
1970 elif c
== '}': level
= level
-1
1971 elif c
== ' ' and level
<= 0: return i
-1
1975 # Convert a node name into a file name
1976 def makefile(nodename
):
1977 nodename
= nodename
.strip()
1978 return fixfunnychars(nodename
) + '.html'
1981 # Characters that are perfectly safe in filenames and hyperlinks
1982 goodchars
= string
.ascii_letters
+ string
.digits
+ '!@-=+.'
1984 # Replace characters that aren't perfectly safe by dashes
1985 # Underscores are bad since Cern HTTPD treats them as delimiters for
1986 # encoding times, so you get mismatches if you compress your files:
1987 # a.html.gz will map to a_b.html.gz
1988 def fixfunnychars(addr
):
1990 while i
< len(addr
):
1992 if c
not in goodchars
:
1994 addr
= addr
[:i
] + c
+ addr
[i
+1:]
1999 # Increment a string used as an enumeration
2003 for sequence
in string
.digits
, string
.lowercase
, string
.uppercase
:
2005 if lastc
in sequence
:
2006 i
= sequence
.index(lastc
) + 1
2007 if i
>= len(sequence
):
2013 s
= increment(s
[:-1]) + sequence
[0]
2015 s
= s
[:-1] + sequence
[i
]
2017 return s
# Don't increment
2028 while sys
.argv
[1] == ['-d']:
2029 debugging
= debugging
+ 1
2031 if sys
.argv
[1] == '-p':
2034 if sys
.argv
[1] == '-c':
2037 if sys
.argv
[1] == '-3':
2040 if sys
.argv
[1] == '-H':
2041 helpbase
= sys
.argv
[2]
2043 if len(sys
.argv
) <> 3:
2044 print 'usage: texi2hh [-d [-d]] [-p] [-c] [-3] [-H htmlhelp]', \
2045 'inputfile outputdirectory'
2049 parser
= TexinfoParserHTML3()
2051 parser
= TexinfoParser()
2053 parser
.debugging
= debugging
2054 parser
.print_headers
= print_headers
2057 dirname
= sys
.argv
[2]
2058 parser
.setdirname(dirname
)
2059 parser
.setincludedir(os
.path
.dirname(file))
2061 htmlhelp
= HTMLHelp(helpbase
, dirname
)
2062 parser
.sethtmlhelp(htmlhelp
)
2065 fp
= open(file, 'r')
2066 except IOError, msg
:
2067 print file, ':', msg
2077 if __name__
== "__main__":