1#!/usr/local/bin/perl
2'di ';
3'ig 00 ';
4#+##############################################################################
5#                                                                              #
6# File: texi2html                                                              #
7#                                                                              #
8# Description: Program to transform most Texinfo documents to HTML             #
9#                                                                              #
10#-##############################################################################
11
12# @(#)texi2html	1.52a 01/05/98	Written (mainly) by Lionel Cons, Lionel.Cons@cern.ch
13# 1.52a: Use acute accent instead of apostrophe. Add support for ISO-8859-1
14#        characters with cedilla, circumflex etc.
15
16# The man page for this program is included at the end of this file and can be
17# viewed using the command 'nroff -man texi2html'.
18# Please read the copyright at the end of the man page.
19
20#+++############################################################################
21#                                                                              #
22# Constants                                                                    #
23#                                                                              #
24#---############################################################################
25
26$DEBUG_TOC   =  1;
27$DEBUG_INDEX =  2;
28$DEBUG_BIB   =  4;
29$DEBUG_GLOSS =  8;
30$DEBUG_DEF   = 16;
31$DEBUG_HTML  = 32;
32$DEBUG_USER  = 64;
33
34$BIBRE = '\[[\w\/-]+\]';		# RE for a bibliography reference
35$FILERE = '[\/\w.+-]+';			# RE for a file name
36$VARRE = '[^\s\{\}]+';			# RE for a variable name
37$NODERE = '[^@{}:\'`",]+';		# RE for a node name
38$NODESRE = '[^@{}:\'`"]+';		# RE for a list of node names
39$XREFRE = '[^@{}]+';			# RE for a xref (should use NODERE)
40
41$ERROR = "***";			        # prefix for errors and warnings
42$THISPROG = "texi2html 1.52a";			# program name and version
43$HOMEPAGE = "http://wwwinfo.cern.ch/dis/texi2html/"; # program home page
44$TODAY = &pretty_date;			# like "20 September 1993"
45$SPLITTAG = "<!-- SPLIT HERE -->\n";	# tag to know where to split
46$PROTECTTAG = "_ThisIsProtected_";	# tag to recognize protected sections
47$html2_doctype = '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0 Strict Level 2//EN">';
48
49#
50# language dependent constants
51#
52#$LDC_SEE = 'see';
53#$LDC_SECTION = 'section';
54#$LDC_IN = 'in';
55#$LDC_TOC = 'Table of Contents';
56#$LDC_GOTO = 'Go to the';
57#$LDC_FOOT = 'Footnotes';
58# TODO: @def* shortcuts
59
60#
61# pre-defined indices
62#
63%predefined_index = (
64		    'cp', 'c',
65		    'fn', 'f',
66		    'vr', 'v',
67		    'ky', 'k',
68		    'pg', 'p',
69		    'tp', 't',
70	            );
71
72#
73# valid indices
74#
75%valid_index = (
76		    'c', 1,
77		    'f', 1,
78		    'v', 1,
79		    'k', 1,
80		    'p', 1,
81		    't', 1,
82		);
83
84#
85# texinfo section names to level
86#
87%sec2level = (
88	      'top', 0,
89	      'chapter', 1,
90	      'unnumbered', 1,
91	      'majorheading', 1,
92	      'chapheading', 1,
93	      'appendix', 1,
94	      'section', 2,
95	      'unnumberedsec', 2,
96	      'heading', 2,
97	      'appendixsec', 2,
98	      'appendixsection', 2,
99	      'subsection', 3,
100	      'unnumberedsubsec', 3,
101	      'subheading', 3,
102	      'appendixsubsec', 3,
103	      'subsubsection', 4,
104	      'unnumberedsubsubsec', 4,
105	      'subsubheading', 4,
106	      'appendixsubsubsec', 4,
107	      );
108
109#
110# accent map, TeX command to ISO name
111#
112%accent_map = (
113	       '"',  'uml',
114	       '~',  'tilde',
115	       '^',  'circ',
116	       '`',  'grave',
117	       '\'', 'acute',
118	       );
119
120#
121# texinfo "simple things" (@foo) to HTML ones
122#
123%simple_map = (
124	       # cf. makeinfo.c
125	       "*", "<BR>",		# HTML+
126	       " ", " ",
127	       "\n", "\n",
128	       "|", "",
129	       # spacing commands
130	       ":", "",
131	       "!", "!",
132	       "?", "?",
133	       ".", ".",
134	       "-", "",
135	       );
136
137#
138# texinfo "things" (@foo{}) to HTML ones
139#
140%things_map = (
141	       'TeX', 'TeX',
142	       'br', '<P>',		# paragraph break
143	       'bullet', '*',
144	       'copyright', '(C)',
145	       'dots', '...',
146	       'equiv', '==',
147	       'error', 'error-->',
148	       'expansion', '==>',
149	       'minus', '-',
150	       'point', '-!-',
151	       'print', '-|',
152	       'result', '=>',
153	       'today', $TODAY,
154	       'aa', '&aring;',
155	       'AA', '&Aring;',
156	       'ae', '&aelig;',
157	       'AE', '&AElig;',
158	       'o',  '&oslash;',
159	       'O',  '&Oslash;',
160	       'ss', '&szlig;',
161	       'exclamdown', '&iexcl;',
162	       'questiondown', '&iquest;',
163	       'pounds', '&pound;'
164	       );
165
166#
167# texinfo styles (@foo{bar}) to HTML ones
168#
169%style_map = (
170	      'asis', '',
171	      'b', 'B',
172	      'cite', 'CITE',
173	      'code', 'CODE',
174	      'ctrl', '&do_ctrl',	# special case
175	      'dfn', 'EM',		# DFN tag is illegal in the standard
176	      'dmn', '',		# useless
177	      'email', '&do_email',     # insert a clickable email address
178	      'emph', 'EM',
179	      'file', '"TT',		# will put quotes, cf. &apply_style
180	      'i', 'I',
181	      'kbd', 'KBD',
182	      'key', 'KBD',
183	      'math', 'EM',
184	      'r', '',			# unsupported
185	      'samp', '"SAMP',		# will put quotes, cf. &apply_style
186	      'sc', '&do_sc',		# special case
187	      'strong', 'STRONG',
188	      't', 'TT',
189	      'titlefont', '',		# useless
190	      'uref', '&do_uref',       # insert a clickable URL
191	      'url', '&do_url',         # insert a clickable URL
192	      'var', 'VAR',
193	      'w', '',			# unsupported
194	      '"', '&do_diaeresis',
195	      '\'', '&do_acuteaccent',	# doesn't work??
196	      '\`', '&do_graveaccent',	# doesn't work??
197	      '~', '&do_tildeaccent',
198	      ',', '&do_cedilla',
199	      '^', '&do_circumflex',
200	      );
201
202#
203# texinfo format (@foo/@end foo) to HTML ones
204#
205%format_map = (
206	       'display', 'PRE',
207	       'example', 'PRE',
208	       'format', 'PRE',
209	       'lisp', 'PRE',
210	       'quotation', 'BLOCKQUOTE',
211	       'smallexample', 'PRE',
212	       'smalllisp', 'PRE',
213	       # lists
214	       'itemize', 'UL',
215	       'enumerate', 'OL',
216	       # poorly supported
217	       'flushleft', 'PRE',
218	       'flushright', 'PRE',
219	       );
220
221#
222# texinfo definition shortcuts to real ones
223#
224%def_map = (
225	    # basic commands
226	    'deffn', 0,
227	    'defvr', 0,
228	    'deftypefn', 0,
229	    'deftypevr', 0,
230	    'defcv', 0,
231	    'defop', 0,
232	    'deftp', 0,
233	    # basic x commands
234	    'deffnx', 0,
235	    'defvrx', 0,
236	    'deftypefnx', 0,
237	    'deftypevrx', 0,
238	    'defcvx', 0,
239	    'defopx', 0,
240	    'deftpx', 0,
241	    # shortcuts
242	    'defun', 'deffn Function',
243	    'defmac', 'deffn Macro',
244	    'defspec', 'deffn {Special Form}',
245	    'defvar', 'defvr Variable',
246	    'defopt', 'defvr {User Option}',
247	    'deftypefun', 'deftypefn Function',
248	    'deftypevar', 'deftypevr Variable',
249	    'defivar', 'defcv {Instance Variable}',
250	    'defmethod', 'defop Method',
251	    # x shortcuts
252	    'defunx', 'deffnx Function',
253	    'defmacx', 'deffnx Macro',
254	    'defspecx', 'deffnx {Special Form}',
255	    'defvarx', 'defvrx Variable',
256	    'defoptx', 'defvrx {User Option}',
257	    'deftypefunx', 'deftypefnx Function',
258	    'deftypevarx', 'deftypevrx Variable',
259	    'defivarx', 'defcvx {Instance Variable}',
260	    'defmethodx', 'defopx Method',
261	    );
262
263#
264# things to skip
265#
266%to_skip = (
267	    # comments
268	    'c', 1,
269	    'comment', 1,
270	    # useless
271	    'contents', 1,
272	    'shortcontents', 1,
273	    'summarycontents', 1,
274	    'footnotestyle', 1,
275	    'end ifclear', 1,
276	    'end ifset', 1,
277	    'titlepage', 1,
278	    'end titlepage', 1,
279	    # unsupported commands (formatting)
280	    'afourpaper', 1,
281	    'cropmarks', 1,
282	    'finalout', 1,
283	    'headings', 1,
284	    'need', 1,
285	    'page', 1,
286	    'setchapternewpage', 1,
287	    'everyheading', 1,
288	    'everyfooting', 1,
289	    'evenheading', 1,
290	    'evenfooting', 1,
291	    'oddheading', 1,
292	    'oddfooting', 1,
293	    'smallbook', 1,
294	    'vskip', 1,
295	    'filbreak', 1,
296	    'paragraphindent', 1,
297	    # unsupported formats
298	    'cartouche', 1,
299	    'end cartouche', 1,
300	    'group', 1,
301	    'end group', 1,
302	    );
303
304#+++############################################################################
305#                                                                              #
306# Argument parsing, initialisation                                             #
307#                                                                              #
308#---############################################################################
309
310%value = ();				# hold texinfo variables, see also -D
311
312$use_bibliography = 1;
313$use_acc = 0;
314$debug = 0;
315$doctype = '';
316$check = 0;
317$expandinfo = 0;
318$use_glossary = 0;
319$invisible_mark = '';
320$use_iso = 0;
321@include_dirs = ();
322$show_menu = 0;
323$number_sections = 0;
324$split_node = 0;
325$split_chapter = 0;
326$monolithic = 0;
327$verbose = 0;
328$usage = <<EOT;
329This is $THISPROG
330To convert a Texinfo file to HMTL: $0 [options] file
331  where options can be:
332    -expandinfo    : use \@ifinfo sections, not \@iftex
333    -glossary      : handle a glossary
334    -invisible name: use 'name' as an invisible anchor
335    -Dname         : define name like with \@set
336    -I dir         : search also for files in 'dir'
337    -menu          : handle menus
338    -monolithic    : output only one file including ToC
339    -number        : number sections
340    -split_chapter : split on main sections
341    -split_node    : split on nodes
342    -usage         : print usage instructions
343    -verbose       : verbose output
344To check converted files: $0 -check [-verbose] files
345EOT
346
347while (@ARGV && $ARGV[0] =~ /^-/) {
348    $_ = shift(@ARGV);
349    if (/^-acc$/)            { $use_acc = 1; next; }
350    if (/^-d(ebug)?(\d+)?$/) { $debug = $2 || shift(@ARGV); next; }
351    if (/^-doctype$/)        { $doctype = shift(@ARGV); next; }
352    if (/^-c(heck)?$/)       { $check = 1; next; }
353    if (/^-e(xpandinfo)?$/)  { $expandinfo = 1; next; }
354    if (/^-g(lossary)?$/)    { $use_glossary = 1; next; }
355    if (/^-i(nvisible)?$/)   { $invisible_mark = shift(@ARGV); next; }
356    if (/^-iso$/)            { $use_iso = 1; next; }
357    if (/^-D(.+)?$/)         { $value{$1 || shift(@ARGV)} = 1; next; }
358    if (/^-I(.+)?$/)         { push(@include_dirs, $1 || shift(@ARGV)); next; }
359    if (/^-m(enu)?$/)        { $show_menu = 1; next; }
360    if (/^-mono(lithic)?$/)  { $monolithic = 1; next; }
361    if (/^-n(umber)?$/)      { $number_sections = 1; next; }
362    if (/^-s(plit)?_?(n(ode)?|c(hapter)?)?$/) {
363	if ($2 =~ /^n/) {
364	    $split_node = 1;
365	} else {
366	    $split_chapter = 1;
367	}
368	next;
369    }
370    if (/^-v(erbose)?$/)     { $verbose = 1; next; }
371    die $usage;
372}
373if ($check) {
374    die $usage unless @ARGV > 0;
375    &check;
376    exit;
377}
378
379if (($split_node || $split_chapter) && $monolithic) {
380    warn "Can't use -monolithic with -split, -monolithic ignored.\n";
381    $monolithic = 0;
382}
383if ($expandinfo) {
384    $to_skip{'ifinfo'}++;
385    $to_skip{'end ifinfo'}++;
386} else {
387    $to_skip{'iftex'}++;
388    $to_skip{'end iftex'}++;
389}
390$invisible_mark = '<IMG SRC="invisible.xbm">' if $invisible_mark eq 'xbm';
391die $usage unless @ARGV == 1;
392$docu = shift(@ARGV);
393if ($docu =~ /.*\//) {
394    chop($docu_dir = $&);
395    $docu_name = $';
396} else {
397    $docu_dir = '.';
398    $docu_name = $docu;
399}
400unshift(@include_dirs, $docu_dir);
401$docu_name =~ s/\.te?x(i|info)?$//;	# basename of the document
402
403$docu_doc = "$docu_name.html";		# document's contents
404if ($monolithic) {
405    $docu_toc = $docu_foot = $docu_doc;
406} else {
407    $docu_toc  = "${docu_name}_toc.html";  # document's table of contents
408    $docu_foot = "${docu_name}_foot.html"; # document's footnotes
409}
410
411#
412# variables
413#
414$value{'html'} = 1;			# predefine html (the output format)
415$value{'texi2html'} = '1.52a';		# predefine texi2html (the translator)
416# _foo: internal to track @foo
417foreach ('_author', '_title', '_subtitle',
418	 '_settitle', '_setfilename') {
419    $value{$_} = '';		        # prevent -w warnings
420}
421%node2sec = ();				# node to section name
422%node2href = ();			# node to HREF
423%bib2href = ();				# bibliography reference to HREF
424%gloss2href = ();			# glossary term to HREF
425@sections = ();				# list of sections
426%tag2pro = ();				# protected sections
427
428#
429# initial indexes
430#
431$bib_num = 0;
432$foot_num = 0;
433$gloss_num = 0;
434$idx_num = 0;
435$sec_num = 0;
436$doc_num = 0;
437$html_num = 0;
438
439#
440# can I use ISO8879 characters? (HTML+)
441#
442if ($use_iso) {
443    $things_map{'bullet'} = "&bull;";
444    $things_map{'copyright'} = "&copy;";
445    $things_map{'dots'} = "&hellip;";
446    $things_map{'equiv'} = "&equiv;";
447    $things_map{'expansion'} = "&rarr;";
448    $things_map{'point'} = "&lowast;";
449    $things_map{'result'} = "&rArr;";
450}
451
452#
453# read texi2html extensions (if any)
454#
455$extensions = 'texi2html.ext'; # extensions in working directory
456if (-f $extensions) {
457    print "# reading extensions from $extensions\n" if $verbose;
458    require($extensions);
459}
460($progdir = $0) =~ s/[^\/]+$//;
461if ($progdir && ($progdir ne './')) {
462    $extensions = "${progdir}texi2html.ext"; # extensions in texi2html directory
463    if (-f $extensions) {
464	print "# reading extensions from $extensions\n" if $verbose;
465	require($extensions);
466    }
467}
468
469print "# reading from $docu\n" if $verbose;
470
471#+++############################################################################
472#                                                                              #
473# Pass 1: read source, handle command, variable, simple substitution           #
474#                                                                              #
475#---############################################################################
476
477@lines = ();				# whole document
478@toc_lines = ();			# table of contents
479$toplevel = 0;			        # top level seen in hierarchy
480$curlevel = 0;				# current level in TOC
481$node = '';				# current node name
482$in_table = 0;				# am I inside a table
483$table_type = '';			# type of table ('', 'f', 'v', 'multi')
484@tables = ();			        # nested table support
485$in_bibliography = 0;			# am I inside a bibliography
486$in_glossary = 0;			# am I inside a glossary
487$in_top = 0;				# am I inside the top node
488$in_pre = 0;				# am I inside a preformatted section
489$in_list = 0;				# am I inside a list
490$in_html = 0;				# am I inside an HTML section
491$first_line = 1;		        # is it the first line
492$dont_html = 0;				# don't protect HTML on this line
493$split_num = 0;				# split index
494$deferred_ref = '';			# deferred reference for indexes
495@html_stack = ();			# HTML elements stack
496$html_element = '';			# current HTML element
497&html_reset;
498
499# build code for simple substitutions
500# the maps used (%simple_map and %things_map) MUST be aware of this
501# watch out for regexps, / and escaped characters!
502$subst_code = '';
503foreach (keys(%simple_map)) {
504    ($re = $_) =~ s/(\W)/\\$1/g; # protect regexp chars
505    $subst_code .= "s/\\\@$re/$simple_map{$_}/g;\n";
506}
507foreach (keys(%things_map)) {
508    $subst_code .= "s/\\\@$_\\{\\}/$things_map{$_}/g;\n";
509}
510if ($use_acc) {
511    # accentuated characters
512    foreach (keys(%accent_map)) {
513	if ($_ eq "`") {
514	    $subst_code .= "s/$;3";
515	} elsif ($_ eq "'") {
516	    $subst_code .= "s/$;4";
517	} else {
518	    $subst_code .= "s/\\\@\\$_";
519	}
520	$subst_code .= "([aeiou])/&\${1}$accent_map{$_};/gi;\n";
521    }
522}
523eval("sub simple_substitutions { $subst_code }");
524
525&init_input;
526while ($_ = &next_line) {
527    #
528    # remove \input on the first lines only
529    #
530    if ($first_line) {
531	next if /^\\input/;
532	$first_line = 0;
533    }
534    #
535    # parse texinfo tags
536    #
537    $tag = '';
538    $end_tag = '';
539    if (/^\@end\s+(\w+)\b/) {
540	$end_tag = $1;
541    } elsif (/^\@(\w+)\b/) {
542	$tag = $1;
543    }
544    #
545    # handle @ifhtml / @end ifhtml
546    #
547    if ($in_html) {
548	if ($end_tag eq 'ifhtml') {
549	    $in_html = 0;
550	} else {
551	    $tag2pro{$in_html} .= $_;
552	}
553	next;
554    } elsif ($tag eq 'ifhtml') {
555	$in_html = $PROTECTTAG . ++$html_num;
556	push(@lines, $in_html);
557	next;
558    }
559    #
560    # try to skip the line
561    #
562    if ($end_tag) {
563	next if $to_skip{"end $end_tag"};
564    } elsif ($tag) {
565	next if $to_skip{$tag};
566	last if $tag eq 'bye';
567    }
568    if ($in_top) {
569	# parsing the top node
570	if ($tag eq 'node' || $tag eq 'include' || $sec2level{$tag}) {
571	    # no more in top
572	    $in_top = 0;
573	} else {
574	    # skip it
575	    next;
576	}
577    }
578    #
579    # try to remove inlined comments
580    # syntax from tex-mode.el comment-start-skip
581    #
582    s/((^|[^\@])(\@\@)*)\@c(omment)? .*/$1/;
583    # non-@ substitutions cf. texinfmt.el
584    unless ($in_pre) {
585	s/``/\"/g;
586	s/''/\"/g;
587	s/([\w ])---([\w ])/$1--$2/g;
588    }
589    #
590    # analyze the tag
591    #
592    if ($tag) {
593	# skip lines
594	&skip_until($tag), next if $tag eq 'ignore';
595	if ($expandinfo) {
596	    &skip_until($tag), next if $tag eq 'iftex';
597	} else {
598	    &skip_until($tag), next if $tag eq 'ifinfo';
599	}
600	&skip_until($tag), next if $tag eq 'tex';
601	# handle special tables
602	if ($tag =~ /^(|f|v|multi)table$/) {
603	    $table_type = $1;
604	    $tag = 'table';
605	}
606	# special cases
607	if ($tag eq 'top' || ($tag eq 'node' && /^\@node\s+top\s*,/i)) {
608	    $in_top = 1;
609	    @lines = (); # ignore all lines before top (title page garbage)
610	    next;
611	} elsif ($tag eq 'node') {
612	    $in_top = 0;
613	    warn "$ERROR Bad node line: $_" unless $_ =~ /^\@node\s$NODESRE$/o;
614	    $_ = &protect_html($_); # if node contains '&' for instance
615	    s/^\@node\s+//;
616	    ($node) = split(/,/);
617	    &normalise_node($node);
618	    if ($split_node) {
619		&next_doc;
620		push(@lines, $SPLITTAG) if $split_num++;
621		push(@sections, $node);
622	    }
623	    next;
624	} elsif ($tag eq 'include') {
625	    if (/^\@include\s+($FILERE)\s*$/o) {
626		$file = $1;
627		unless (-e $file) {
628		    foreach $dir (@include_dirs) {
629			$file = "$dir/$1";
630			last if -e $file;
631		    }
632		}
633		if (-e $file) {
634		    &open($file);
635		    print "# including $file\n" if $verbose;
636		} else {
637		    warn "$ERROR Can't find $file, skipping";
638		}
639	    } else {
640		warn "$ERROR Bad include line: $_";
641	    }
642	    next;
643	} elsif ($tag eq 'ifclear') {
644	    if (/^\@ifclear\s+($VARRE)\s*$/o) {
645		next unless defined($value{$1});
646		&skip_until($tag);
647	    } else {
648		warn "$ERROR Bad ifclear line: $_";
649	    }
650	    next;
651	} elsif ($tag eq 'ifset') {
652	    if (/^\@ifset\s+($VARRE)\s*$/o) {
653		next if defined($value{$1});
654		&skip_until($tag);
655	    } else {
656		warn "$ERROR Bad ifset line: $_";
657	    }
658	    next;
659	} elsif ($tag eq 'menu') {
660	    unless ($show_menu) {
661		&skip_until($tag);
662		next;
663	    }
664	    &html_push_if($tag);
665	    push(@lines, &html_debug("\n", __LINE__));
666	} elsif ($format_map{$tag}) {
667	    $in_pre = 1 if $format_map{$tag} eq 'PRE';
668	    &html_push_if($format_map{$tag});
669	    push(@lines, &html_debug("\n", __LINE__));
670	    $in_list++ if $format_map{$tag} eq 'UL' || $format_map{$tag} eq 'OL' ;
671	    push(@lines, &debug("<$format_map{$tag}>\n", __LINE__));
672	    next;
673	} elsif ($tag eq 'table') {
674	    if (/^\@(|f|v|multi)table\s+\@(\w+)/) {
675		$in_table = $2;
676		unshift(@tables, join($;, $table_type, $in_table));
677		if ($table_type eq "multi") {
678		    push(@lines, &debug("<TABLE BORDER>\n", __LINE__));
679		    &html_push_if('TABLE');
680		} else {
681		    push(@lines, &debug("<DL COMPACT>\n", __LINE__));
682		    &html_push_if('DL');
683		}
684		push(@lines, &html_debug("\n", __LINE__));
685	    } else {
686		warn "$ERROR Bad table line: $_";
687	    }
688	    next;
689	} elsif ($tag eq 'synindex' || $tag eq 'syncodeindex') {
690	    if (/^\@$tag\s+(\w)\w\s+(\w)\w\s*$/) {
691		eval("*${1}index = *${2}index");
692	    } else {
693		warn "$ERROR Bad syn*index line: $_";
694	    }
695	    next;
696	} elsif ($tag eq 'sp') {
697	    push(@lines, &debug("<P>\n", __LINE__));
698	    next;
699	} elsif ($tag eq 'setref') {
700	    &protect_html; # if setref contains '&' for instance
701	    if (/^\@$tag\s*{($NODERE)}\s*$/) {
702		$setref = $1;
703		$setref =~ s/\s+/ /g; # normalize
704		$setref =~ s/ $//;
705		$node2sec{$setref} = $name;
706		$node2href{$setref} = "$docu_doc#$docid";
707	    } else {
708		warn "$ERROR Bad setref line: $_";
709	    }
710	    next;
711	} elsif ($tag eq 'defindex' || $tag eq 'defcodeindex') {
712	    if (/^\@$tag\s+(\w\w)\s*$/) {
713		$valid_index{$1} = 1;
714	    } else {
715		warn "$ERROR Bad defindex line: $_";
716	    }
717	    next;
718	} elsif (defined($def_map{$tag})) {
719	    if ($def_map{$tag}) {
720		s/^\@$tag\s+//;
721		$tag = $def_map{$tag};
722		$_ = "\@$tag $_";
723		$tag =~ s/\s.*//;
724	    }
725	} elsif (defined($user_sub{$tag})) {
726	    s/^\@$tag\s+//;
727	    $sub = $user_sub{$tag};
728	    print "# user $tag = $sub, arg: $_" if $debug & $DEBUG_USER;
729	    if (defined(&$sub)) {
730		chop($_);
731		&$sub($_);
732	    } else {
733		warn "$ERROR Bad user sub for $tag: $sub\n";
734	    }
735	    next;
736	}
737	if (defined($def_map{$tag})) {
738	    s/^\@$tag\s+//;
739	    if ($tag =~ /x$/) {
740		# extra definition line
741		$tag = $`;
742		$is_extra = 1;
743	    } else {
744		$is_extra = 0;
745	    }
746	    while (/\{([^\{\}]*)\}/) {
747		# this is a {} construct
748		($before, $contents, $after) = ($`, $1, $');
749		# protect spaces
750		$contents =~ s/\s+/$;9/g;
751		# restore $_ protecting {}
752		$_ = "$before$;7$contents$;8$after";
753	    }
754	    @args = split(/\s+/, &protect_html($_));
755	    foreach (@args) {
756		s/$;9/ /g;	# unprotect spaces
757		s/$;7/\{/g;	# ... {
758		s/$;8/\}/g;	# ... }
759	    }
760	    $type = shift(@args);
761	    $type =~ s/^\{(.*)\}$/$1/;
762	    print "# def ($tag): {$type} ", join(', ', @args), "\n"
763		if $debug & $DEBUG_DEF;
764	    $type .= ':'; # it's nicer like this
765	    $name = shift(@args);
766	    $name =~ s/^\{(.*)\}$/$1/;
767	    if ($is_extra) {
768		$_ = &debug("<DT>", __LINE__);
769	    } else {
770		$_ = &debug("<DL>\n<DT>", __LINE__);
771	    }
772	    if ($tag eq 'deffn' || $tag eq 'defvr' || $tag eq 'deftp') {
773		$_ .= "<U>$type</U> <B>$name</B>";
774		$_ .= " <I>@args</I>" if @args;
775	    } elsif ($tag eq 'deftypefn' || $tag eq 'deftypevr'
776		     || $tag eq 'defcv' || $tag eq 'defop') {
777		$ftype = $name;
778		$name = shift(@args);
779		$name =~ s/^\{(.*)\}$/$1/;
780		$_ .= "<U>$type</U> $ftype <B>$name</B>";
781		$_ .= " <I>@args</I>" if @args;
782	    } else {
783		warn "$ERROR Unknown definition type: $tag\n";
784		$_ .= "<U>$type</U> <B>$name</B>";
785		$_ .= " <I>@args</I>" if @args;
786	    }
787 	    $_ .= &debug("\n<DD>", __LINE__);
788	    $name = &unprotect_html($name);
789	    if ($tag eq 'deffn' || $tag eq 'deftypefn') {
790		unshift(@input_spool, "\@findex $name\n");
791	    } elsif ($tag eq 'defop') {
792		unshift(@input_spool, "\@findex $name on $ftype\n");
793	    } elsif ($tag eq 'defvr' || $tag eq 'deftypevr' || $tag eq 'defcv') {
794		unshift(@input_spool, "\@vindex $name\n");
795	    } else {
796		unshift(@input_spool, "\@tindex $name\n");
797	    }
798	    $dont_html = 1;
799	}
800    } elsif ($end_tag) {
801	if ($format_map{$end_tag}) {
802	    $in_pre = 0 if $format_map{$end_tag} eq 'PRE';
803	    $in_list-- if $format_map{$end_tag} eq 'UL' || $format_map{$end_tag} eq 'OL' ;
804	    &html_pop_if('LI', 'P');
805	    &html_pop_if();
806	    push(@lines, &debug("</$format_map{$end_tag}>\n", __LINE__));
807	    push(@lines, &html_debug("\n", __LINE__));
808	} elsif ($end_tag =~ /^(|f|v|multi)table$/) {
809	    unless (@tables) {
810		warn "$ERROR \@end $end_tag without \@*table\n";
811		next;
812	    }
813	    ($table_type, $in_table) = split($;, shift(@tables));
814	    unless ($1 eq $table_type) {
815		warn "$ERROR \@end $end_tag without matching \@$end_tag\n";
816		next;
817	    }
818	    if ($table_type eq "multi") {
819		push(@lines, "</TR></TABLE>\n");
820		&html_pop_if('TR');
821	    } else {
822		push(@lines, "</DL>\n");
823		&html_pop_if('DD');
824	    }
825	    &html_pop_if();
826	    if (@tables) {
827		($table_type, $in_table) = split($;, $tables[0]);
828	    } else {
829		$in_table = 0;
830	    }
831	} elsif (defined($def_map{$end_tag})) {
832 	    push(@lines, &debug("</DL>\n", __LINE__));
833	} elsif ($end_tag eq 'menu') {
834	    &html_pop_if();
835	    push(@lines, $_); # must keep it for pass 2
836	}
837	next;
838    }
839    #
840    # misc things
841    #
842    # protect texi and HTML things
843    &protect_texi;
844    $_ = &protect_html($_) unless $dont_html;
845    $dont_html = 0;
846    # substitution (unsupported things)
847    s/^\@center\s+//g;
848    s/^\@exdent\s+//g;
849    s/\@noindent\s+//g;
850    s/\@refill\s+//g;
851    # other substitutions
852    &simple_substitutions;
853    s/\@value{($VARRE)}/$value{$1}/eg;
854    s/\@footnote\{/\@footnote$docu_doc\{/g; # mark footnotes, cf. pass 4
855    #
856    # analyze the tag again
857    #
858    if ($tag) {
859	if (defined($sec2level{$tag}) && $sec2level{$tag} > 0) {
860	    if (/^\@$tag\s+(.+)$/) {
861		$name = $1;
862		$name =~ s/\s+$//;
863		$level = $sec2level{$tag};
864		$name = &update_sec_num($tag, $level) . "  $name"
865		    if $number_sections && $tag !~ /^unnumbered/;
866		if ($tag =~ /heading$/) {
867		    push(@lines, &html_debug("\n", __LINE__));
868		    if ($html_element ne 'body') {
869			# We are in a nice pickle here. We are trying to get a H? heading
870			# even though we are not in the body level. So, we convert it to a
871			# nice, bold, line by itself.
872			$_ = &debug("\n\n<P><STRONG>$name</STRONG></P>\n\n", __LINE__);
873		    } else {
874			$_ = &debug("<H$level>$name</H$level>\n", __LINE__);
875			&html_push_if('body');
876		    }
877		    print "# heading, section $name, level $level\n"
878			if $debug & $DEBUG_TOC;
879		} else {
880		    if ($split_chapter) {
881			unless ($toplevel) {
882			    # first time we see a "section"
883			    unless ($level == 1) {
884				warn "$ERROR The first section found is not of level 1: $_";
885				warn "$ERROR I'll split on sections of level $level...\n";
886			    }
887			    $toplevel = $level;
888			}
889			if ($level == $toplevel) {
890			    &next_doc;
891			    push(@lines, $SPLITTAG) if $split_num++;
892			    push(@sections, $name);
893			}
894		    }
895		    $sec_num++;
896		    $docid = "SEC$sec_num";
897		    $tocid = "TOC$sec_num";
898		    # check biblio and glossary
899		    $in_bibliography = ($name =~ /^([A-Z]|\d+)?(\.\d+)*\s*bibliography$/i);
900		    $in_glossary = ($name =~ /^([A-Z]|\d+)?(\.\d+)*\s*glossary$/i);
901		    # check node
902		    if ($node) {
903			if ($node2sec{$node}) {
904			    warn "$ERROR Duplicate node found: $node\n";
905			} else {
906			    $node2sec{$node} = $name;
907			    $node2href{$node} = "$docu_doc#$docid";
908			    print "# node $node, section $name, level $level\n"
909				if $debug & $DEBUG_TOC;
910			}
911			$node = '';
912		    } else {
913			print "# no node, section $name, level $level\n"
914			    if $debug & $DEBUG_TOC;
915		    }
916		    # update TOC
917		    while ($level > $curlevel) {
918			$curlevel++;
919			push(@toc_lines, "<UL>\n");
920		    }
921		    while ($level < $curlevel) {
922			$curlevel--;
923			push(@toc_lines, "</UL>\n");
924		    }
925		    $_ = "<LI>" . &anchor($tocid, "$docu_doc#$docid", $name, 1);
926		    push(@toc_lines, &substitute_style($_));
927		    # update DOC
928		    push(@lines, &html_debug("\n", __LINE__));
929		    &html_reset;
930		    $_ =  "<H$level>".&anchor($docid, "$docu_toc#$tocid", $name)."</H$level>\n";
931		    $_ = &debug($_, __LINE__);
932		    push(@lines, &html_debug("\n", __LINE__));
933		}
934		# update DOC
935		foreach $line (split(/\n+/, $_)) {
936		    push(@lines, "$line\n");
937		}
938		next;
939	    } else {
940		warn "$ERROR Bad section line: $_";
941	    }
942	} else {
943	    # track variables
944	    $value{$1} = $2, next if /^\@set\s+($VARRE)\s+(.*)$/o;
945	    delete $value{$1}, next if /^\@clear\s+($VARRE)\s*$/o;
946	    # store things
947	    $value{'_setfilename'}   = $1, next if /^\@setfilename\s+(.*)$/;
948	    $value{'_settitle'}      = $1, next if /^\@settitle\s+(.*)$/;
949	    $value{'_author'}   .= "$1\n", next if /^\@author\s+(.*)$/;
950	    $value{'_subtitle'} .= "$1\n", next if /^\@subtitle\s+(.*)$/;
951	    $value{'_title'}    .= "$1\n", next if /^\@title\s+(.*)$/;
952	    # index
953	    if (/^\@(..?)index\s+/) {
954		unless ($valid_index{$1}) {
955		    warn "$ERROR Undefined index command: $_";
956		    next;
957		}
958		$id = 'IDX' . ++$idx_num;
959		$index = $1 . 'index';
960		$what = &substitute_style($');
961		$what =~ s/\s+$//;
962		print "# found $index for '$what' id $id\n"
963		    if $debug & $DEBUG_INDEX;
964		eval(<<EOC);
965		if (defined(\$$index\{\$what\})) {
966		    \$$index\{\$what\} .= "$;$docu_doc#$id";
967		} else {
968		    \$$index\{\$what\} = "$docu_doc#$id";
969		}
970EOC
971		#
972		# dirty hack to see if I can put an invisible anchor...
973		#
974		if ($html_element eq 'P' ||
975		    $html_element eq 'LI' ||
976		    $html_element eq 'DT' ||
977		    $html_element eq 'DD' ||
978		    $html_element eq 'ADDRESS' ||
979		    $html_element eq 'B' ||
980		    $html_element eq 'BLOCKQUOTE' ||
981		    $html_element eq 'PRE' ||
982		    $html_element eq 'SAMP') {
983                    push(@lines, &anchor($id, '', $invisible_mark, !$in_pre));
984                } elsif ($html_element eq 'body') {
985		    push(@lines, &debug("<P>\n", __LINE__));
986                    push(@lines, &anchor($id, '', $invisible_mark, !$in_pre));
987		    &html_push('P');
988		} elsif ($html_element eq 'DL' ||
989			 $html_element eq 'UL' ||
990			 $html_element eq 'OL' ) {
991		    $deferred_ref .= &anchor($id, '', $invisible_mark, !$in_pre) . " ";
992		}
993		next;
994	    }
995	    # list item
996	    if (/^\@itemx?\s+/) {
997		$what = $';
998		$what =~ s/\s+$//;
999		if ($in_bibliography && $use_bibliography) {
1000		    if ($what =~ /^$BIBRE$/o) {
1001			$id = 'BIB' . ++$bib_num;
1002			$bib2href{$what} = "$docu_doc#$id";
1003			print "# found bibliography for '$what' id $id\n"
1004			    if $debug & $DEBUG_BIB;
1005			$what = &anchor($id, '', $what);
1006		    }
1007		} elsif ($in_glossary && $use_glossary) {
1008		    $id = 'GLOSS' . ++$gloss_num;
1009		    $entry = $what;
1010		    $entry =~ tr/A-Z/a-z/ unless $entry =~ /^[A-Z\s]+$/;
1011		    $gloss2href{$entry} = "$docu_doc#$id";
1012		    print "# found glossary for '$entry' id $id\n"
1013			if $debug & $DEBUG_GLOSS;
1014		    $what = &anchor($id, '', $what);
1015		}
1016		&html_pop_if('P');
1017		if ($html_element eq 'DL' || $html_element eq 'DD') {
1018		    if ($things_map{$in_table} && !$what) {
1019			# special case to allow @table @bullet for instance
1020			push(@lines, &debug("<DT>$things_map{$in_table}\n", __LINE__));
1021		    } else {
1022			push(@lines, &debug("<DT>\@$in_table\{$what\}\n", __LINE__));
1023		    }
1024		    push(@lines, "<DD>");
1025		    &html_push('DD') unless $html_element eq 'DD';
1026		    if ($table_type) { # add also an index
1027			unshift(@input_spool, "\@${table_type}index $what\n");
1028		    }
1029		} elsif ($html_element eq 'TABLE') {
1030		    push(@lines, &debug("<TR><TD>$what</TD>\n", __LINE__));
1031		    &html_push('TR');
1032		} elsif ($html_element eq 'TR') {
1033		    push(@lines, &debug("</TR>\n", __LINE__));
1034		    push(@lines, &debug("<TR><TD>$what</TD>\n", __LINE__));
1035		} else {
1036		    push(@lines, &debug("<LI>$what\n", __LINE__));
1037		    &html_push('LI') unless $html_element eq 'LI';
1038		}
1039		push(@lines, &html_debug("\n", __LINE__));
1040		if ($deferred_ref) {
1041		    push(@lines, &debug("$deferred_ref\n", __LINE__));
1042		    $deferred_ref = '';
1043		}
1044		next;
1045	    } elsif (/^\@tab\s+(.*)$/) {
1046		push(@lines, "<TD>$1</TD>\n");
1047		next;
1048	    }
1049	}
1050    }
1051    # paragraph separator
1052    if ($_ eq "\n") {
1053	next if $#lines >= 0 && $lines[$#lines] eq "\n";
1054	if ($html_element eq 'P') {
1055	    push(@lines, "\n");
1056	    $_ = &debug("</P>\n", __LINE__);
1057	    &html_pop;
1058	}
1059    } elsif ($html_element eq 'body' || $html_element eq 'BLOCKQUOTE') {
1060	push(@lines, "<P>\n");
1061	&html_push('P');
1062	$_ = &debug($_, __LINE__);
1063    }
1064    # otherwise
1065    push(@lines, $_);
1066}
1067
1068# finish TOC
1069$level = 0;
1070while ($level < $curlevel) {
1071    $curlevel--;
1072    push(@toc_lines, "</UL>\n");
1073}
1074
1075print "# end of pass 1\n" if $verbose;
1076
1077#+++############################################################################
1078#                                                                              #
1079# Pass 2/3: handle style, menu, index, cross-reference                         #
1080#                                                                              #
1081#---############################################################################
1082
1083@lines2 = ();				# whole document (2nd pass)
1084@lines3 = ();				# whole document (3rd pass)
1085$in_menu = 0;				# am I inside a menu
1086
1087while (@lines) {
1088    $_ = shift(@lines);
1089    #
1090    # special case (protected sections)
1091    #
1092    if (/^$PROTECTTAG/o) {
1093	push(@lines2, $_);
1094	next;
1095    }
1096    #
1097    # menu
1098    #
1099    $in_menu = 1, push(@lines2, &debug("<UL>\n", __LINE__)), next if /^\@menu\b/;
1100    $in_menu = 0, push(@lines2, &debug("</UL>\n", __LINE__)), next if /^\@end\s+menu\b/;
1101    if ($in_menu) {
1102	if (/^\*\s+($NODERE)::/o) {
1103	    $descr = $';
1104	    chop($descr);
1105	    &menu_entry($1, $1, $descr);
1106	} elsif (/^\*\s+(.+):\s+([^\t,\.\n]+)[\t,\.\n]/) {
1107	    $descr = $';
1108	    chop($descr);
1109	    &menu_entry($1, $2, $descr);
1110	} elsif (/^\*/) {
1111	    warn "$ERROR Bad menu line: $_";
1112	} else { # description continued?
1113	    push(@lines2, $_);
1114	}
1115	next;
1116    }
1117    #
1118    # printindex
1119    #
1120    if (/^\@printindex\s+(\w\w)\b/) {
1121	local($index, *ary, @keys, $key, $letter, $last_letter, @refs);
1122	if ($predefined_index{$1}) {
1123	    $index = $predefined_index{$1} . 'index';
1124	} else {
1125	    $index = $1 . 'index';
1126	}
1127	eval("*ary = *$index");
1128	@keys = keys(%ary);
1129	foreach $key (@keys) {
1130	    $_ = $key;
1131	    1 while s/<(\w+)>\`(.*)\&acute;<\/\1>/$2/; # remove HTML tags with quotes
1132	    1 while s/<(\w+)>(.*)<\/\1>/$2/;     # remove HTML tags
1133	    $_ = &unprotect_html($_);
1134	    &unprotect_texi;
1135	    tr/A-Z/a-z/; # lowercase
1136	    $key2alpha{$key} = $_;
1137	    print "# index $key sorted as $_\n"
1138		if $key ne $_ && $debug & $DEBUG_INDEX;
1139	}
1140	push(@lines2, "Jump to:\n");
1141	$last_letter = undef;
1142	foreach $key (sort byalpha @keys) {
1143	    $letter = substr($key2alpha{$key}, 0, 1);
1144	    $letter = substr($key2alpha{$key}, 0, 2) if $letter eq $;;
1145	    if (!defined($last_letter) || $letter ne $last_letter) {
1146		push(@lines2, "-\n") if defined($last_letter);
1147		push(@lines2, "<A HREF=\"#$index\_$letter\">" . &protect_html($letter) . "</A>\n");
1148		$last_letter = $letter;
1149	    }
1150	}
1151	push(@lines2, "<P>\n");
1152	$last_letter = undef;
1153	foreach $key (sort byalpha @keys) {
1154	    $letter = substr($key2alpha{$key}, 0, 1);
1155	    $letter = substr($key2alpha{$key}, 0, 2) if $letter eq $;;
1156	    if (!defined($last_letter) || $letter ne $last_letter) {
1157		push(@lines2, "</DIR>\n") if defined($last_letter);
1158		push(@lines2, "<H2><A NAME=\"$index\_$letter\">" . &protect_html($letter) . "</A></H2>\n");
1159		push(@lines2, "<DIR>\n");
1160		$last_letter = $letter;
1161	    }
1162	    @refs = ();
1163	    foreach (split(/$;/, $ary{$key})) {
1164		push(@refs, &anchor('', $_, $key, 0));
1165	    }
1166	    push(@lines2, "<LI>" . join(", ", @refs) . "\n");
1167	}
1168	push(@lines2, "</DIR>\n") if defined($last_letter);
1169	next;
1170    }
1171    #
1172    # simple style substitutions
1173    #
1174    $_ = &substitute_style($_);
1175    #
1176    # xref
1177    #
1178    while (/\@(x|px|info|)ref{($XREFRE)(}?)/o) {
1179	# note: Texinfo may accept other characters
1180	($type, $nodes, $full) = ($1, $2, $3);
1181	($before, $after) = ($`, $');
1182	if (! $full && $after) {
1183	    warn "$ERROR Bad xref (no ending } on line): $_";
1184	    $_ = "$before$;0${type}ref\{$nodes$after";
1185	    next; # while xref
1186	}
1187	if ($type eq 'x') {
1188	    $type = 'See ';
1189	} elsif ($type eq 'px') {
1190	    $type = 'see ';
1191	} elsif ($type eq 'info') {
1192	    $type = 'See Info';
1193	} else {
1194	    $type = '';
1195	}
1196	unless ($full) {
1197	    $next = shift(@lines);
1198	    $next = &substitute_style($next);
1199	    chop($nodes); # remove final newline
1200	    if ($next =~ /\}/) { # split on 2 lines
1201		$nodes .= " $`";
1202		$after = $';
1203	    } else {
1204		$nodes .= " $next";
1205		$next = shift(@lines);
1206		$next = &substitute_style($next);
1207		chop($nodes);
1208		if ($next =~ /\}/) { # split on 3 lines
1209		    $nodes .= " $`";
1210		    $after = $';
1211		} else {
1212		    warn "$ERROR Bad xref (no ending }): $_";
1213		    $_ = "$before$;0xref\{$nodes$after";
1214		    unshift(@lines, $next);
1215		    next; # while xref
1216		}
1217	    }
1218	}
1219	$nodes =~ s/\s+/ /g; # remove useless spaces
1220	@args = split(/\s*,\s*/, $nodes);
1221	$node = $args[0]; # the node is always the first arg
1222	&normalise_node($node);
1223	$sec = $node2sec{$node};
1224	if (@args == 5) { # reference to another manual
1225	    $sec = $args[2] || $node;
1226	    $man = $args[4] || $args[3];
1227	    $_ = "${before}${type}section `$sec' in \@cite{$man}$after";
1228	} elsif ($type =~ /Info/) { # inforef
1229	    warn "$ERROR Wrong number of arguments: $_" unless @args == 3;
1230	    ($nn, $_, $in) = @args;
1231	    $_ = "${before}${type} file `$in', node `$nn'$after";
1232	} elsif ($sec) {
1233	    $href = $node2href{$node};
1234	    $_ = "${before}${type}section " . &anchor('', $href, $sec) . $after;
1235	} else {
1236	    warn "$ERROR Undefined node ($node): $_";
1237	    $_ = "$before$;0xref{$nodes}$after";
1238	}
1239    }
1240    #
1241    # try to guess bibliography references or glossary terms
1242    #
1243    unless (/^<H\d><A NAME=\"SEC\d/) {
1244	if ($use_bibliography) {
1245	    $done = '';
1246	    while (/$BIBRE/o) {
1247		($pre, $what, $post) = ($`, $&, $');
1248		$href = $bib2href{$what};
1249		if (defined($href) && $post !~ /^[^<]*<\/A>/) {
1250		    $done .= $pre . &anchor('', $href, $what);
1251		} else {
1252		    $done .= "$pre$what";
1253		}
1254		$_ = $post;
1255	    }
1256	    $_ = $done . $_;
1257	}
1258	if ($use_glossary) {
1259	    $done = '';
1260	    while (/\b\w+\b/) {
1261		($pre, $what, $post) = ($`, $&, $');
1262		$entry = $what;
1263		$entry =~ tr/A-Z/a-z/ unless $entry =~ /^[A-Z\s]+$/;
1264		$href = $gloss2href{$entry};
1265		if (defined($href) && $post !~ /^[^<]*<\/A>/) {
1266		    $done .= $pre . &anchor('', $href, $what);
1267		} else {
1268		    $done .= "$pre$what";
1269		}
1270		$_ = $post;
1271	    }
1272	    $_ = $done . $_;
1273	}
1274    }
1275    # otherwise
1276    push(@lines2, $_);
1277}
1278print "# end of pass 2\n" if $verbose;
1279
1280#
1281# split style substitutions
1282#
1283while (@lines2) {
1284    $_ = shift(@lines2);
1285    #
1286    # special case (protected sections)
1287    #
1288    if (/^$PROTECTTAG/o) {
1289	push(@lines3, $_);
1290	next;
1291    }
1292    #
1293    # split style substitutions
1294    #
1295    $old = '';
1296    while ($old ne $_) {
1297        $old = $_;
1298	if (/\@(\w+|"|\~|,|\^)\{/) {
1299	    ($before, $style, $after) = ($`, $1, $');
1300	    if (defined($style_map{$style})) {
1301		$_ = $after;
1302		$text = '';
1303		$after = '';
1304		$failed = 1;
1305		while (@lines2) {
1306		    if (/\}/) {
1307			$text .= $`;
1308			$after = $';
1309			$failed = 0;
1310			last;
1311		    } else {
1312			$text .= $_;
1313			$_ = shift(@lines2);
1314		    }
1315		}
1316		if ($failed) {
1317		    die "* Bad syntax (\@$style) after: $before\n";
1318		} else {
1319		    $text = &apply_style($style, $text);
1320		    $_ = "$before$text$after";
1321		}
1322	    }
1323	}
1324    }
1325    # otherwise
1326    push(@lines3, $_);
1327}
1328print "# end of pass 3\n" if $verbose;
1329
1330#+++############################################################################
1331#                                                                              #
1332# Pass 4: foot notes, final cleanup                                            #
1333#                                                                              #
1334#---############################################################################
1335
1336@foot_lines = ();			# footnotes
1337@doc_lines = ();			# final document
1338$end_of_para = 0;			# true if last line is <P>
1339
1340while (@lines3) {
1341    $_ = shift(@lines3);
1342    #
1343    # special case (protected sections)
1344    #
1345    if (/^$PROTECTTAG/o) {
1346	push(@doc_lines, $_);
1347	$end_of_para = 0;
1348	next;
1349    }
1350    #
1351    # footnotes
1352    #
1353    while (/\@footnote([^\{\s]+)\{/) {
1354	($before, $d, $after) = ($`, $1, $');
1355	$_ = $after;
1356	$text = '';
1357	$after = '';
1358	$failed = 1;
1359	while (@lines3) {
1360	    if (/\}/) {
1361		$text .= $`;
1362		$after = $';
1363		$failed = 0;
1364		last;
1365	    } else {
1366		$text .= $_;
1367		$_ = shift(@lines3);
1368	    }
1369	}
1370	if ($failed) {
1371	    die "* Bad syntax (\@footnote) after: $before\n";
1372	} else {
1373	    $foot_num++;
1374	    $docid  = "DOCF$foot_num";
1375	    $footid = "FOOT$foot_num";
1376	    $foot = "($foot_num)";
1377	    push(@foot_lines, "<H3>" . &anchor($footid, "$d#$docid", $foot) . "</H3>\n");
1378	    $text = "<P>$text" unless $text =~ /^\s*<P>/;
1379	    push(@foot_lines, "$text\n");
1380	    $_ = $before . &anchor($docid, "$docu_foot#$footid", $foot) . $after;
1381	}
1382    }
1383    #
1384    # remove unnecessary <P>
1385    #
1386    if (/^\s*<P>\s*$/) {
1387	next if $end_of_para++;
1388    } else {
1389	$end_of_para = 0;
1390    }
1391    # otherwise
1392    push(@doc_lines, $_);
1393}
1394print "# end of pass 4\n" if $verbose;
1395
1396#+++############################################################################
1397#                                                                              #
1398# Pass 5: print things                                                         #
1399#                                                                              #
1400#---############################################################################
1401
1402$header = <<EOT;
1403<!-- This HTML file has been created by $THISPROG
1404     from $docu on $TODAY -->
1405EOT
1406
1407$full_title = $value{'_title'} || $value{'_settitle'} || "Untitled Document";
1408$title = $value{'_settitle'} || $full_title;
1409$_ = &substitute_style($full_title);
1410&unprotect_texi;
1411s/\n$//; # rmv last \n (if any)
1412$full_title = "<H1>" . join("</H1>\n<H1>", split(/\n/, $_)) . "</H1>\n";
1413
1414#
1415# print ToC
1416#
1417if (!$monolithic && @toc_lines) {
1418    if (open(FILE, "> $docu_toc")) {
1419	print "# creating $docu_toc...\n" if $verbose;
1420	&print_toplevel_header("$title - Table of Contents");
1421	&print_ruler;
1422	&print(*toc_lines, FILE);
1423	&print_toplevel_footer;
1424	close(FILE);
1425    } else {
1426	warn "$ERROR Can't write to $docu_toc: $!\n";
1427    }
1428}
1429
1430#
1431# print footnotes
1432#
1433if (!$monolithic && @foot_lines) {
1434    if (open(FILE, "> $docu_foot")) {
1435	print "# creating $docu_foot...\n" if $verbose;
1436	&print_toplevel_header("$title - Footnotes");
1437	&print_ruler;
1438        &print(*foot_lines, FILE);
1439	&print_toplevel_footer;
1440	close(FILE);
1441    } else {
1442	warn "$ERROR Can't write to $docu_foot: $!\n";
1443    }
1444}
1445
1446#
1447# print document
1448#
1449if ($split_chapter || $split_node) { # split
1450    $doc_num = 0;
1451    $last_num = scalar(@sections);
1452    $first_doc = &doc_name(1);
1453    $last_doc = &doc_name($last_num);
1454    while (@sections) {
1455	$section = shift(@sections);
1456	&next_doc;
1457	if (open(FILE, "> $docu_doc")) {
1458	    print "# creating $docu_doc...\n" if $verbose;
1459	    &print_header("$title - $section");
1460	    $prev_doc = ($doc_num == 1 ? undef : &doc_name($doc_num - 1));
1461	    $next_doc = ($doc_num == $last_num ? undef : &doc_name($doc_num + 1));
1462	    $navigation = "Go to the ";
1463	    $navigation .= ($prev_doc ? &anchor('', $first_doc, "first") : "first");
1464	    $navigation .= ", ";
1465	    $navigation .= ($prev_doc ? &anchor('', $prev_doc, "previous") : "previous");
1466	    $navigation .= ", ";
1467	    $navigation .= ($next_doc ? &anchor('', $next_doc, "next") : "next");
1468	    $navigation .= ", ";
1469	    $navigation .= ($next_doc ? &anchor('', $last_doc, "last") : "last");
1470	    $navigation .= " section, " . &anchor('', $docu_toc, "table of contents") . ".\n";
1471	    print FILE $navigation;
1472	    &print_ruler;
1473	    # find corresponding lines
1474            @tmp_lines = ();
1475            while (@doc_lines) {
1476		$_ = shift(@doc_lines);
1477		last if ($_ eq $SPLITTAG);
1478		push(@tmp_lines, $_);
1479	    }
1480            &print(*tmp_lines, FILE);
1481	    &print_ruler;
1482	    print FILE $navigation;
1483	    &print_footer;
1484	    close(FILE);
1485	} else {
1486	    warn "$ERROR Can't write to $docu_doc: $!\n";
1487	}
1488    }
1489} else { # not split
1490    if (open(FILE, "> $docu_doc")) {
1491	print "# creating $docu_doc...\n" if $verbose;
1492	if ($monolithic || !@toc_lines) {
1493	    &print_toplevel_header($title);
1494	} else {
1495	    &print_header($title);
1496	    print FILE $full_title;
1497	}
1498	if ($monolithic && @toc_lines) {
1499	    &print_ruler;
1500 	    print FILE "<H1>Table of Contents</H1>\n";
1501 	    &print(*toc_lines, FILE);
1502	}
1503	&print_ruler;
1504        &print(*doc_lines, FILE);
1505	if ($monolithic && @foot_lines) {
1506	    &print_ruler;
1507 	    print FILE "<H1>Footnotes</H1>\n";
1508 	    &print(*foot_lines, FILE);
1509	}
1510	if ($monolithic || !@toc_lines) {
1511	    &print_toplevel_footer;
1512	} else {
1513	    &print_footer;
1514	}
1515	close(FILE);
1516    } else {
1517	warn "$ERROR Can't write to $docu_doc: $!\n";
1518    }
1519}
1520
1521print "# that's all folks\n" if $verbose;
1522
1523#+++############################################################################
1524#                                                                              #
1525# Low level functions                                                          #
1526#                                                                              #
1527#---############################################################################
1528
1529sub update_sec_num {
1530    local($name, $level) = @_;
1531
1532    $level--; # here we start at 0
1533    if ($name =~ /^appendix/) {
1534	# appendix style
1535	if (defined(@appendix_sec_num)) {
1536	    &incr_sec_num($level, @appendix_sec_num);
1537	} else {
1538	    @appendix_sec_num = ('A', 0, 0, 0);
1539	}
1540	return(join('.', @appendix_sec_num[0..$level]));
1541    } else {
1542	# normal style
1543	if (defined(@normal_sec_num)) {
1544	    &incr_sec_num($level, @normal_sec_num);
1545	} else {
1546	    @normal_sec_num = (1, 0, 0, 0);
1547	}
1548	return(join('.', @normal_sec_num[0..$level]));
1549    }
1550}
1551
1552sub incr_sec_num {
1553    local($level, $l);
1554    $level = shift(@_);
1555    $_[$level]++;
1556    foreach $l ($level+1 .. 3) {
1557	$_[$l] = 0;
1558    }
1559}
1560
1561sub check {
1562    local($_, %seen, %context, $before, $match, $after);
1563
1564    while (<>) {
1565	if (/\@(\*|\.|\:|\@|\{|\})/) {
1566	    $seen{$&}++;
1567	    $context{$&} .= "> $_" if $verbose;
1568	    $_ = "$`XX$'";
1569	    redo;
1570	}
1571	if (/\@(\w+)/) {
1572	    ($before, $match, $after) = ($`, $&, $');
1573	    if ($before =~ /\b[\w-]+$/ && $after =~ /^[\w-.]*\b/) { # e-mail address
1574		$seen{'e-mail address'}++;
1575		$context{'e-mail address'} .= "> $_" if $verbose;
1576	    } else {
1577		$seen{$match}++;
1578		$context{$match} .= "> $_" if $verbose;
1579	    }
1580	    $match =~ s/^\@/X/;
1581	    $_ = "$before$match$after";
1582	    redo;
1583	}
1584    }
1585    
1586    foreach (sort(keys(%seen))) {
1587	if ($verbose) {
1588	    print "$_\n";
1589	    print $context{$_};
1590	} else {
1591	    print "$_ ($seen{$_})\n";
1592	}
1593    }
1594}
1595
1596sub open {
1597    local($name) = @_;
1598
1599    ++$fh_name;
1600    if (open($fh_name, $name)) {
1601	unshift(@fhs, $fh_name);
1602    } else {
1603	warn "$ERROR Can't read file $name: $!\n";
1604    }
1605}
1606
1607sub init_input {
1608    @fhs = ();			# hold the file handles to read
1609    @input_spool = ();		# spooled lines to read
1610    $fh_name = 'FH000';
1611    &open($docu);
1612}
1613
1614sub next_line {
1615    local($fh, $line);
1616
1617    if (@input_spool) {
1618	$line = shift(@input_spool);
1619	return($line);
1620    }
1621    while (@fhs) {
1622	$fh = $fhs[0];
1623	$line = <$fh>;
1624	return($line) if $line;
1625	close($fh);
1626	shift(@fhs);
1627    }
1628    return(undef);
1629}
1630
1631# used in pass 1, use &next_line
1632sub skip_until {
1633    local($tag) = @_;
1634    local($_);
1635
1636    while ($_ = &next_line) {
1637	return if /^\@end\s+$tag\s*$/;
1638    }
1639    die "* Failed to find '$tag' after: " . $lines[$#lines];
1640}
1641
1642#
1643# HTML stacking to have a better HTML output
1644#
1645
1646sub html_reset {
1647    @html_stack = ('html');
1648    $html_element = 'body';
1649}
1650
1651sub html_push {
1652    local($what) = @_;
1653    push(@html_stack, $html_element);
1654    $html_element = $what;
1655}
1656
1657sub html_push_if {
1658    local($what) = @_;
1659    push(@html_stack, $html_element)
1660	if ($html_element && $html_element ne 'P');
1661    $html_element = $what;
1662}
1663
1664sub html_pop {
1665    $html_element = pop(@html_stack);
1666}
1667
1668sub html_pop_if {
1669    local($elt);
1670
1671    if (@_) {
1672	foreach $elt (@_) {
1673	    if ($elt eq $html_element) {
1674		$html_element = pop(@html_stack) if @html_stack;
1675		last;
1676	    }
1677	}
1678    } else {
1679	$html_element = pop(@html_stack) if @html_stack;
1680    }
1681}
1682
1683sub html_debug {
1684    local($what, $line) = @_;
1685    return("<!-- $line @html_stack, $html_element -->$what")
1686	if $debug & $DEBUG_HTML;
1687    return($what);
1688}
1689
1690# to debug the output...
1691sub debug {
1692    local($what, $line) = @_;
1693    return("<!-- $line -->$what")
1694	if $debug & $DEBUG_HTML;
1695    return($what);
1696}
1697
1698sub normalise_node {
1699    $_[0] =~ s/\s+/ /g;
1700    $_[0] =~ s/ $//;
1701    $_[0] =~ s/^ //;
1702}
1703
1704sub menu_entry {
1705    local($entry, $node, $descr) = @_;
1706    local($href);
1707
1708    &normalise_node($node);
1709    $href = $node2href{$node};
1710    if ($href) {
1711	$descr =~ s/^\s+//;
1712	$descr = ": $descr" if $descr;
1713	push(@lines2, "<LI>" . &anchor('', $href, $entry) . "$descr\n");
1714    } else {
1715	warn "$ERROR Undefined node ($node): $_";
1716    }
1717}
1718
1719sub do_ctrl { "^$_[0]" }
1720
1721sub do_email {
1722    local($addr, $text) = split(/,\s*/, $_[0]);
1723
1724    $text = $addr unless $text;
1725    &anchor('', "mailto:$addr", $text);
1726}
1727
1728sub do_sc { "\U$_[0]\E" }
1729
1730sub do_uref {
1731    local($url, $text) = split(/,\s*/, $_[0]);
1732
1733    $text = $url unless $text;
1734    &anchor('', $url, $text);
1735}
1736
1737sub do_url { &anchor('', $_[0], $_[0]) }
1738
1739sub do_diaeresis { return "&$_[0]uml;"; }
1740sub do_acuteaccent { return "&$_[0]acute;"; }
1741sub do_graveaccent { return "&$_[0]grave;"; }
1742sub do_tildeaccent { return "&$_[0]tilde;"; }
1743sub do_cedilla { return "&$_[0]cedil;"; }
1744sub do_circumflex { return "&$_[0]circ;"; }
1745
1746sub apply_style {
1747    local($texi_style, $text) = @_;
1748    local($style);
1749
1750    $style = $style_map{$texi_style};
1751    if (defined($style)) { # known style
1752	if ($style =~ /^\"/) { # add quotes
1753	    $style = $';
1754	    $text = "\`$text\&acute;";
1755	}
1756	if ($style =~ /^\&/) { # custom
1757	    $style = $';
1758	    $text = &$style($text);
1759	} elsif ($style) { # good style
1760	    $text = "<$style>$text</$style>";
1761	} else { # no style
1762	}
1763    } else { # unknown style
1764	$text = undef;
1765    }
1766    return($text);
1767}
1768
1769# remove Texinfo styles
1770sub remove_style {
1771    local($_) = @_;
1772    s/\@\w+{([^\{\}]+)}/$1/g;
1773    return($_);
1774}
1775
1776sub substitute_style {
1777    local($_) = @_;
1778    local($changed, $done, $style, $text);
1779
1780    $changed = 1;
1781    while ($changed) {
1782	$changed = 0;
1783	$done = '';
1784	while (/\@(\w+|"|\~|,|\^){([^\{\}]+)}/) {
1785	    $text = &apply_style($1, $2);
1786	    if ($text) {
1787		$_ = "$`$text$'";
1788		$changed = 1;
1789	    } else {
1790		$done .= "$`\@$1";
1791		$_ = "{$2}$'";
1792	    }
1793	}
1794        $_ = $done . $_;
1795    }
1796    return($_);
1797}
1798
1799sub anchor {
1800    local($name, $href, $text, $newline) = @_;
1801    local($result);
1802
1803    $result = "<A";
1804    $result .= " NAME=\"$name\"" if $name;
1805    $result .= " HREF=\"$href\"" if $href;
1806    $result .= ">$text</A>";
1807    $result .= "\n" if $newline;
1808    return($result);
1809}
1810
1811sub pretty_date {
1812    local(@MoY, $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
1813
1814    @MoY = ('January', 'Febuary', 'March', 'April', 'May', 'June',
1815	    'July', 'August', 'September', 'October', 'November', 'December');
1816    ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
1817    $year += ($year < 70) ? 2000 : 1900;
1818    return("$mday $MoY[$mon] $year");
1819}
1820
1821sub doc_name {
1822    local($num) = @_;
1823
1824    return("${docu_name}_$num.html");
1825}
1826
1827sub next_doc {
1828    $docu_doc = &doc_name(++$doc_num);
1829}
1830
1831sub print {
1832    local(*lines, $fh) = @_;
1833    local($_);
1834
1835    while (@lines) {
1836	$_ = shift(@lines);
1837	if (/^$PROTECTTAG/o) {
1838	    $_ = $tag2pro{$_};
1839	} else {
1840	    &unprotect_texi;
1841	}
1842	print $fh $_;
1843    }
1844}
1845
1846sub print_ruler {
1847    print FILE "<P><HR><P>\n";
1848}
1849
1850sub print_header {
1851    local($_);
1852
1853    # clean the title
1854    $_ = &remove_style($_[0]);
1855    &unprotect_texi;
1856    # print the header
1857    if ($doctype eq 'html2') {
1858	print FILE $html2_doctype;
1859    } elsif ($doctype) {
1860	print FILE $doctype;
1861    }
1862    print FILE <<EOT;
1863<HTML>
1864<HEAD>
1865$header
1866<TITLE>$_</TITLE>
1867</HEAD>
1868<BODY>
1869EOT
1870}
1871
1872sub print_toplevel_header {
1873    local($_);
1874
1875    &print_header; # pass given arg...
1876    print FILE $full_title;
1877    if ($value{'_subtitle'}) {
1878	$value{'_subtitle'} =~ s/\n+$//;
1879	foreach (split(/\n/, $value{'_subtitle'})) {
1880	    $_ = &substitute_style($_);
1881	    &unprotect_texi;
1882	    print FILE "<H2>$_</H2>\n";
1883	}
1884    }
1885    if ($value{'_author'}) {
1886	$value{'_author'} =~ s/\n+$//;
1887	foreach (split(/\n/, $value{'_author'})) {
1888	    $_ = &substitute_style($_);
1889	    &unprotect_texi;
1890	    s/[\w.-]+\@[\w.-]+/<A HREF="mailto:$&">$&<\/A>/g;
1891	    print FILE "<ADDRESS>$_</ADDRESS>\n";
1892	}
1893    }
1894    print FILE "<P>\n";
1895}
1896
1897sub print_footer {
1898    print FILE <<EOT;
1899</BODY>
1900</HTML>
1901EOT
1902}
1903
1904sub print_toplevel_footer {
1905    &print_ruler;
1906    print FILE <<EOT;
1907This document was generated on $TODAY using the
1908<A HREF=\"$HOMEPAGE\">texi2html</A>
1909translator version 1.52a.</P>
1910EOT
1911    &print_footer;
1912}
1913
1914sub protect_texi {
1915    # protect @ { } ` '
1916    s/\@\@/$;0/go;
1917    s/\@\{/$;1/go;
1918    s/\@\}/$;2/go;
1919    s/\@\`/$;3/go;
1920    s/\@\'/$;4/go;
1921}
1922
1923sub protect_html {
1924    local($what) = @_;
1925    # protect & < >
1926    $what =~ s/\&/\&\#38;/g;
1927    $what =~ s/\</\&\#60;/g;
1928    $what =~ s/\>/\&\#62;/g;
1929    # but recognize some HTML things
1930    $what =~ s/\&\#60;\/A\&\#62;/<\/A>/g;	      # </A>
1931    $what =~ s/\&\#60;A ([^\&]+)\&\#62;/<A $1>/g;     # <A [^&]+>
1932    $what =~ s/\&\#60;IMG ([^\&]+)\&\#62;/<IMG $1>/g; # <IMG [^&]+>
1933    return($what);
1934}
1935
1936sub unprotect_texi {
1937    s/$;0/\@/go;
1938    s/$;1/\{/go;
1939    s/$;2/\}/go;
1940    s/$;3/\`/go;
1941    s/$;4/\'/go;
1942}
1943
1944sub unprotect_html {
1945    local($what) = @_;
1946    $what =~ s/\&\#38;/\&/g;
1947    $what =~ s/\&\#60;/\</g;
1948    $what =~ s/\&\#62;/\>/g;
1949    return($what);
1950}
1951
1952sub byalpha {
1953    $key2alpha{$a} cmp $key2alpha{$b};
1954}
1955
1956##############################################################################
1957
1958	# These next few lines are legal in both Perl and nroff.
1959
1960.00 ;			# finish .ig
1961 
1962'di			\" finish diversion--previous line must be blank
1963.nr nl 0-1		\" fake up transition to first page again
1964.nr % 0			\" start at page 1
1965'; __END__ ############# From here on it's a standard manual page ############
1966.TH TEXI2HTML 1 "01/05/98"
1967.AT 3
1968.SH NAME
1969texi2html \- a Texinfo to HTML converter
1970.SH SYNOPSIS
1971.B texi2html [options] file
1972.PP
1973.B texi2html -check [-verbose] files
1974.SH DESCRIPTION
1975.I Texi2html
1976converts the given Texinfo file to a set of HTML files. It tries to handle
1977most of the Texinfo commands. It creates hypertext links for cross-references,
1978footnotes...
1979.PP
1980It also tries to add links from a reference to its corresponding entry in the
1981bibliography (if any). It may also handle a glossary (see the
1982.B \-glossary
1983option).
1984.PP
1985.I Texi2html
1986creates several files depending on the contents of the Texinfo file and on
1987the chosen options (see FILES).
1988.PP
1989The HTML files created by
1990.I texi2html
1991are closer to TeX than to Info, that's why
1992.I texi2html
1993converts @iftex sections and not @ifinfo ones by default. You can reverse
1994this with the \-expandinfo option.
1995.SH OPTIONS
1996.TP 12
1997.B \-check
1998Check the given file and give the list of all things that may be Texinfo commands.
1999This may be used to check the output of
2000.I texi2html
2001to find the Texinfo commands that have been left in the HTML file.
2002.TP
2003.B \-expandinfo
2004Expand @ifinfo sections, not @iftex ones.
2005.TP
2006.B \-glossary
2007Use the section named 'Glossary' to build a list of terms and put links in the HTML
2008document from each term toward its definition.
2009.TP
2010.B \-invisible \fIname\fP
2011Use \fIname\fP to create invisible destination anchors for index links
2012(you can for instance use the invisible.xbm file shipped with this program).
2013This is a workaround for a known bug of many WWW browsers, including netscape.
2014.TP
2015.B \-I \fIdir\fP
2016Look also in \fIdir\fP to find included files.
2017.TP
2018.B \-menu
2019Show the Texinfo menus; by default they are ignored.
2020.TP
2021.B \-monolithic
2022Output only one file, including the table of contents and footnotes.
2023.TP
2024.B \-number
2025Number the sections.
2026.TP
2027.B \-split_chapter
2028Split the output into several HTML files (one per main section:
2029chapter, appendix...).
2030.TP
2031.B \-split_node
2032Split the output into several HTML files (one per node).
2033.TP
2034.B \-usage
2035Print usage instructions, listing the current available command-line options.
2036.TP
2037.B \-verbose
2038Give a verbose output. Can be used with the
2039.B \-check
2040option.
2041.PP
2042.SH FILES
2043By default
2044.I texi2html
2045creates the following files (foo being the name of the Texinfo file):
2046.TP 16
2047.B foo_toc.html
2048The table of contents.
2049.TP
2050.B foo.html
2051The document's contents.
2052.TP
2053.B foo_foot.html
2054The footnotes (if any).
2055.PP
2056When used with the
2057.B \-split
2058option, it creates several files (one per chapter or node), named
2059.B foo_n.html
2060(n being the indice of the chapter or node), instead of the single
2061.B foo.html
2062file.
2063.PP
2064When used with the
2065.B \-monolithic
2066option, it creates only one file:
2067.B foo.html
2068.SH VARIABLES
2069.I texi2html
2070predefines the following variables: \fBhtml\fP, \fBtexi2html\fP.
2071.SH ADDITIONAL COMMANDS
2072.I texi2html
2073implements the following non-Texinfo commands (maybe they are in Texinfo now...):
2074.TP 16
2075.B @ifhtml
2076This indicates the start of an HTML section, this section will passed through
2077without any modification.
2078.TP
2079.B @end ifhtml
2080This indicates the end of an HTML section.
2081.SH VERSION
2082This is \fItexi2html\fP version 1.52a, 01/05/98.
2083.PP
2084The latest version of \fItexi2html\fP can be found in WWW, cf. URL
2085http://wwwinfo.cern.ch/dis/texi2html/
2086.SH AUTHOR
2087The main author is Lionel Cons, CERN IT/DIS/OSE, Lionel.Cons@cern.ch.
2088Many other people around the net contributed to this program.
2089.SH COPYRIGHT
2090This program is the intellectual property of the European
2091Laboratory for Particle Physics (known as CERN). No guarantee whatsoever is
2092provided by CERN. No liability whatsoever is accepted for any loss or damage
2093of any kind resulting from any defect or inaccuracy in this information or
2094code.
2095.PP
2096CERN, 1211 Geneva 23, Switzerland
2097.SH "SEE ALSO"
2098GNU Texinfo Documentation Format,
2099HyperText Markup Language (HTML),
2100World Wide Web (WWW).
2101.SH BUGS
2102This program does not understand all Texinfo commands (yet).
2103.PP
2104TeX specific commands (normally enclosed in @iftex) will be
2105passed unmodified.
2106.ex
2107