#!/usr/local/bin/perl
'di';
'ig00';
#+##############################################################################
# #
# File: texi2html #
# #
# Description: Program to transform most Texinfo documents to HTML. #
# #
#-##############################################################################
# @(#)texi2html 1.26 01/27/94 Written by Lionel Cons, Lionel.Cons@cern.ch
# The man page for this program is included at the end of this file and can be
# viewed using the command 'nroff -man texi2html'.
# Please read the copyright at the end of the man page.
#+++############################################################################
# #
# Constants #
# #
#---############################################################################
$DEBUG_TOC = 1;
$DEBUG_INDEX = 2;
$DEBUG_BIB = 4;
$DEBUG_GLOSS = 8;
$DEBUG_DEF = 16;
$BIBRE = '\[[\w\/]+\]'; # RE for a bibliography reference
$FILERE = '[\/\w.+-]+'; # RE for a file name
$VARRE = '[^\s\{\}]+'; # RE for a variable name
$NODERE = '[^@{}:\'`",]+'; # RE for a node name
$NODESRE = '[^@{}:\'`"]+'; # RE for a list of node names
$XREFRE = '[^@{}]+'; # RE for a xref (should use NODERE)
$THISPROG = "texi2html 1.26"; # program name and version
$TODAY = &pretty_date; # like "20 September 1993"
$SPLITTAG = "\n"; # tag to know where to split
$PROTECTTAG = "_ThisIsProtected_"; # tag to recognize protected sections
#
# texinfo section names to level
#
%sec2level = (
'top', 0,
'chapter', 1,
'unnumbered', 1,
'majorheading', 1,
'chapheading', 1,
'appendix', 1,
'section', 2,
'unnumberedsec', 2,
'heading', 2,
'appendixsec', 2,
'appendixsection', 2,
'subsection', 3,
'unnumberedsubsec', 3,
'subheading', 3,
'appendixsubsec', 3,
'subsubsection', 4,
'unnumberedsubsubsec', 4,
'subsubheading', 4,
'appendixsubsubsec', 4,
);
#
# texinfo "simple things" (@foo) to HTML ones
#
%simple_map = (
# cf. makeinfo.c
"*", "
", # HTML+
" ", " ",
"\n", "\n",
"|", "",
# spacing commands
":", "",
"!", "!",
"?", "?",
".", ".",
);
#
# texinfo "things" (@foo{}) to HTML ones
#
%things_map = (
'TeX', 'TeX',
'br', '
', # paragraph break
'bullet', '*',
'copyright', '(C)',
'dots', '...',
'equiv', '==',
'error', 'error-->',
'expansion', '==>',
'minus', '-',
'point', '-!-',
'print', '-|',
'result', '=>',
'today', $TODAY,
);
#
# texinfo styles (@foo{bar}) to HTML ones
#
%style_map = (
'asis', '',
'b', 'B',
'cite', 'CITE',
'code', 'CODE',
'ctrl', '&do_ctrl', # special case
'dfn', 'DFN',
'dmn', '', # useless
'emph', 'EM',
'file', '"TT', # will put quotes, cf. &apply_style
'i', 'I',
'kbd', 'KBD',
'key', 'KBD',
'r', '', # unsupported
'samp', '"SAMP', # will put quotes, cf. &apply_style
'sc', '&do_sc', # special case
'strong', 'STRONG',
't', 'TT',
'titlefont', '', # useless
'var', 'VAR',
'w', '', # unsupported
);
#
# texinfo format (@foo/@end foo) to HTML ones
#
%format_map = (
'display', 'PRE',
'example', 'PRE',
'format', 'PRE',
'lisp', 'PRE',
'quotation', 'BLOCKQUOTE',
'smallexample', 'PRE',
'smalllisp', 'PRE',
# lists
'itemize', 'UL',
'enumerate', 'OL',
# poorly supported
'flushleft', 'PRE',
'flushright', 'PRE',
);
#
# texinfo definition shortcuts to real ones
#
%def_map = (
# basic commands
'deffn', 0,
'defvr', 0,
'deftypefn', 0,
'deftypevr', 0,
'defcv', 0,
'defop', 0,
'deftp', 0,
# basic x commands
'deffnx', 0,
'defvrx', 0,
'deftypefnx', 0,
'deftypevrx', 0,
'defcvx', 0,
'defopx', 0,
'deftpx', 0,
# shortcuts
'defun', 'deffn Function',
'defmac', 'deffn Macro',
'defspec', 'deffn {Special Form}',
'defvar', 'defvr Variable',
'defopt', 'defvr {User Option}',
'deftypefun', 'deftypefn Function',
'deftypevar', 'deftypevr Variable',
'defivar', 'defcv {Instance Variable}',
'defmethod', 'defop Method',
# x shortcuts
'defunx', 'deffnx Function',
'defmacx', 'deffnx Macro',
'defspecx', 'deffnx {Special Form}',
'defvarx', 'defvrx Variable',
'defoptx', 'defvrx {User Option}',
'deftypefunx', 'deftypefnx Function',
'deftypevarx', 'deftypevrx Variable',
'defivarx', 'defcvx {Instance Variable}',
'defmethodx', 'defopx Method',
);
#
# things to skip
#
%to_skip = (
# comments
'c', 1,
'comment', 1,
# useless
'contents', 1,
'shortcontents', 1,
'summarycontents', 1,
'footnotestyle', 1,
'end ifclear', 1,
'end ifset', 1,
'iftex', 1,
'end iftex', 1,
'titlepage', 1,
'end titlepage', 1,
# unsupported commands (formatting)
'afourpaper', 1,
'cropmarks', 1,
'finalout', 1,
'headings', 1,
'need', 1,
'page', 1,
'setchapternewpage', 1,
'smallbook', 1,
'vskip', 1,
# unsupported formats
'cartouche', 1,
'end cartouche', 1,
'group', 1,
'end group', 1,
# misc unsupported commands
'defindex', 1,
);
#+++############################################################################
# #
# Argument parsing, initialisation #
# #
#---############################################################################
$invisible_mark = '';
$use_bibliography = 1;
$usage = < \n");
next;
} elsif (defined($def_map{$tag})) {
if ($def_map{$tag}) {
s/^\@$tag\s+//;
$tag = $def_map{$tag};
$_ = "\@$tag $_";
$tag =~ s/\s.*//;
}
}
if (defined($def_map{$tag})) {
s/^\@$tag\s+//;
$tag =~ s/x$//;
1 while s/(\{[^\}]*)\s+([^\{]*\})/$1$;9$2/; # protect spaces inside {}
@args = split(/\s+/, $_);
for (@args) {s/$;9/ /g;} # unprotect spaces
$type = shift(@args);
$type =~ s/^\{(.*)\}$/$1/;
print "# def ($tag): {$type} ", join(', ', @args), "\n"
if $debug & $DEBUG_DEF;
$type .= ':'; # it's nicer like this
$name = shift(@args);
$name =~ s/^\{(.*)\}$/$1/;
if ($tag eq 'deffn' || $tag eq 'defvr' || $tag eq 'deftp') {
$_ = "$type $name";
$_ .= " @args" if @args;
$_ .= " \n";
} elsif ($tag eq 'deftypefn' || $tag eq 'deftypevr'
|| $tag eq 'defcv' || $tag eq 'defop') {
$ftype = $name;
$name = shift(@args);
$name =~ s/^\{(.*)\}$/$1/;
$_ = "$type $ftype $name";
$_ .= " @args" if @args;
$_ .= " \n";
} else {
warn "Unknown definition type: $tag\n";
$_ = "$type $name";
$_ .= " @args" if @args;
$_ .= " \n";
}
if ($tag eq 'deffn' || $tag eq 'deftypefn' || $tag eq 'defop') {
unshift(@input_spool, "\@findex $name\n");
} elsif ($tag eq 'defvr' || $tag eq 'deftypevr' || $tag eq 'defcv') {
unshift(@input_spool, "\@vindex $name\n");
} else {
unshift(@input_spool, "\@tindex $name\n");
}
$dont_html = 1;
}
} elsif ($end_tag) {
if ($format_map{$end_tag}) {
$in_pre = 0 if $format_map{$end_tag} eq 'PRE';
push(@lines, "$format_map{$end_tag}>\n");
} elsif ($end_tag eq 'table' ||
$end_tag eq 'ftable' ||
$end_tag eq 'vtable') {
$in_table = 0;
push(@lines, " \n");
} elsif ($end_tag eq 'menu') {
push(@lines, $_); # must keep it for pass 2
}
next;
}
#
# misc things
#
# protect texi and HTML things
&protect_texi;
&protect_html unless $dont_html;
$dont_html = 0;
# non-@ substitutions cf. texinfmt.el
s/``/"/g && study;
s/''/"/g && study;
s/([\w ])---([\w ])/$1--$2/g && study;
# substitution (unsupported things)
s/^\@center\s+//g && study;
s/^\@exdent\s+//g && study;
s/\@noindent\s+//g && study;
s/\@refill\s+//g && study;
# other substitutions
eval($subst_code);
s/\@value{($VARRE)}/$value{$1}/eg;
s/\@footnote\{/\@footnote$docu_doc\{/g; # mark footnotes, cf. pass 4
#
# analyze the tag again
#
if ($tag) {
if ($sec2level{$tag} > 0) {
if (/^\@$tag\s+(.+)$/) {
$name = $1;
$name =~ s/\s+$//;
$level = $sec2level{$tag};
if ($tag =~ /heading$/) {
$_ = " \n" if $_ eq "\n" && ! $in_pre;
# otherwise
push(@lines, $_);
}
# finish TOC
$level = 0;
while ($level < $curlevel) {
$curlevel--;
push(@toc_lines, "\n");
}
print "# end of pass 1\n" if $verbose;
#+++############################################################################
# #
# Pass 2/3: handle style, menu, index, cross-reference #
# #
#---############################################################################
@lines2 = (); # whole document (2nd pass)
@lines3 = (); # whole document (3rd pass)
$in_menu = 0; # am I inside a menu
while (@lines) {
$_ = shift(@lines);
#
# special case (protected sections)
#
if (/^$PROTECTTAG/o) {
push(@lines2, $_);
next;
}
#
# menu
#
$in_menu = 1, push(@lines2, "
while (@lines3) {
$_ = shift(@lines3);
#
# special case (protected sections)
#
if (/^$PROTECTTAG/o) {
push(@doc_lines, $_);
$end_of_para = 0;
next;
}
#
# footnotes
#
while (/\@footnote([^\{\s]+)\{/) {
($before, $d, $after) = ($`, $1, $');
$_ = $after;
$text = '';
$after = '';
$failed = 1;
while (@lines3) {
if (/\}/) {
$text .= $`;
$after = $';
$failed = 0;
last;
} else {
$text .= $_;
$_ = shift(@lines3);
}
}
if ($failed) {
die "* Bad syntax (@footnote) after: $before\n";
} else {
$id = 'FOOT' . ++$foot_num;
$foot = "($foot_num)";
push(@foot_lines, "
#
if ($_ eq " \n") {
next if $end_of_para++;
} else {
$end_of_para = 0;
}
# otherwise
push(@doc_lines, $_);
}
print "# end of pass 4\n" if $verbose;
#+++############################################################################
# #
# Pass 5: print things #
# #
#---############################################################################
$header = <
EOT
&print(*toc_lines, FILE);
close(FILE);
} else {
warn "Can't write to $docu_toc: $!\n";
}
#
# print document
#
if ($split_chapter || $split_node) {
$doc_num = 0;
while (@sections) {
$section = shift(@sections);
&next_doc;
if (open(FILE, "> $docu_doc")) {
print "# creating $docu_doc...\n" if $verbose;
$prev_doc = &doc_name($doc_num - 1);
$prev_doc = '' if $doc_num == 1;
$next_doc = &doc_name($doc_num + 1);
$next_doc = '' unless @sections;
print FILE "$header\n";
print FILE " Go to the " if $prev_doc || $next_doc;
$navigation .= &anchor('', $prev_doc, "previous") if $prev_doc;
$navigation .= ", " if $prev_doc && $next_doc;
$navigation .= &anchor('', $next_doc, "next") if $next_doc;
$navigation .= " section. \n" if $prev_doc || $next_doc;
print FILE $navigation;
# find corresponding lines
@tmp_lines = ();
while (@doc_lines) {
$_ = shift(@doc_lines);
last if ($_ eq $SPLITTAG);
push(@tmp_lines, $_);
}
&print(*tmp_lines, FILE);
print FILE $navigation;
close(FILE);
} else {
warn "Can't write to $docu_doc: $!\n";
}
}
} else {
if (open(FILE, "> $docu_doc")) {
print "# creating $docu_doc...\n" if $verbose;
print FILE <' if $invisible_mark eq 'xbm';
die $usage unless @ARGV == 1;
$docu = shift(@ARGV);
if ($docu =~ /.*\//) {
chop($docu_dir = $&);
$docu_name = $';
} else {
$docu_dir = '.';
$docu_name = $docu;
}
$docu_name =~ s/\.te?x(i|info)?$//; # basename of the document
$docu_toc = $docu_doc = $docu_foot = $docu_name;
$docu_toc .= '_toc.html'; # document's table of contents
$docu_doc .= '.html'; # document's contents
$docu_foot .= '_foot.html'; # document's footnotes
#
# variables
#
%value = (); # hold texinfo variables
$value{'html'} = 1; # predefine html (the output format)
$value{'texi2html'} = '1.26'; # predefine texi2html (the translator)
%node2sec = (); # node to section name
%sec2href = (); # section to HREF
%bib2href = (); # bibliography reference to HREF
%gloss2href = (); # glossary term to HREF
@sections = (); # list of sections
%tag2pro = (); # protected sections
#
# initial indexes
#
$bib_num = 0;
$foot_num = 0;
$gloss_num = 0;
$idx_num = 0;
$sec_num = 0;
$doc_num = 0;
$html_num = 0;
#
# can I use ISO8879 characters? (HTML+)
#
if ($use_iso) {
$things_map{'bullet'} = "•";
$things_map{'copyright'} = "©";
$things_map{'dots'} = "…";
$things_map{'equiv'} = "≡";
$things_map{'expansion'} = "→";
$things_map{'point'} = "∗";
$things_map{'result'} = "⇒";
}
#
# read texi2html extensions (if any)
#
$extensions = 'texi2html.ext'; # extensions in working directory
if (-f $extensions) {
print "# reading extensions from $extensions\n" if $verbose;
require($extensions);
}
($progdir = $0) =~ s/[^\/]+$//;
if ($progdir && ($progdir ne './')) {
$extensions = "${progdir}texi2html.ext"; # extensions in texi2html directory
if (-f $extensions) {
print "# reading extensions from $extensions\n" if $verbose;
require($extensions);
}
}
print "# reading from $docu\n" if $verbose;
#+++############################################################################
# #
# Pass 1: read source, handle command, variable, simple substitution #
# #
#---############################################################################
@lines = (); # whole document
@toc_lines = (); # table of contents
$curlevel = 0; # current level in TOC
$node = ''; # current node name
$in_table = 0; # am I inside a table
$table_type = ''; # type of table ('', 'f', 'v')
$in_bibliography = 0; # am I inside a bibliography
$in_glossary = 0; # am I inside a glossary
$in_top = 0; # am I inside the top node
$in_pre = 0; # am I inside a preformatted section
$in_html = 0; # am I inside an HTML section
$dont_html = 0; # don't protect HTML on this line
$split_num = 0; # split index
# build code for simple substitutions
# the maps used (%simple_map and %things_map) MUST be aware of this
# watch out for regexps, / and escaped characters!
$subst_code = '';
for (keys(%simple_map)) {
($re = $_) =~ s/(\W)/\\$1/g; # protect regexp chars
$subst_code .= "s/\\@$re/$simple_map{$_}/g && study;\n";
}
for (keys(%things_map)) {
$subst_code .= "s/\\@$_\\{\\}/$things_map{$_}/g && study;\n";
}
&init_input;
while ($_ = &next_line) {
#
# parse texinfo tags
#
$tag = '';
$end_tag = '';
if (/^\@end\s+(\w+)\b/) {
$end_tag = $1;
} elsif (/^\@(\w+)\b/) {
$tag = $1;
}
#
# handle @ifhtml / @end ifhtml
#
if ($in_html) {
if ($end_tag eq 'ifhtml') {
$in_html = 0;
} else {
$tag2pro{$in_html} .= $_;
}
next;
} elsif ($tag eq 'ifhtml') {
$in_html = $PROTECTTAG . ++$html_num;
push(@lines, $in_html);
next;
}
#
# try to skip the line
#
if ($end_tag) {
next if $to_skip{"end $end_tag"};
} elsif ($tag) {
next if $to_skip{$tag};
last if $tag eq 'bye';
}
next if $in_top && $tag ne 'node' && $tag ne 'include';
#
# try to remove inlined comments
# syntax from tex-mode.el comment-start-skip
#
s/((^|[^\@])(\@\@)*)\@c(omment)? .*/\1/;
#
# analyze the tag
#
if ($tag) {
# skip lines
&skip_until($tag), next if $tag eq 'ignore';
&skip_until($tag), next if $tag eq 'ifinfo';
&skip_until($tag), next if $tag eq 'tex';
# handle special tables
if ($tag eq 'table') {
$table_type = '';
} elsif ($tag eq 'ftable') {
$tag = 'table';
$table_type = 'f';
} elsif ($tag eq 'vtable') {
$tag = 'table';
$table_type = 'v';
}
# special cases
if ($tag eq 'top' || ($tag eq 'node' && /^\@node\s+top\s*,/i)) {
$in_top = 1;
@lines = (); # ignore all lines before top (title page garbage)
next;
} elsif ($tag eq 'node') {
$in_top = 0;
&protect_html; # if node contains '&' for instance
warn "Bad node line: $_" unless $_ =~ /^\@node\s$NODESRE$/o;
s/^\@node\s+//;
($node) = split(/,/);
$node =~ s/\s+/ /g; # normalize
$node =~ s/ $//;
if ($split_node) {
&next_doc;
push(@lines, $SPLITTAG) if $split_num++;
push(@sections, $node);
}
next;
} elsif ($tag eq 'include') {
if (/^\@include\s+($FILERE)\s*$/o) {
$file = $1;
$file = "$docu_dir/$file" unless -e $file;
if (-e $file) {
&open($file);
print "# including $file\n" if $verbose;
} else {
warn "Can't find $file, skipping";
}
} else {
warn "Bad include line: $_";
}
next;
} elsif ($tag eq 'ifclear') {
if (/^\@ifclear\s+($VARRE)\s*$/o) {
next unless defined($value{$1});
&skip_until($tag);
} else {
warn "Bad ifclear line: $_";
}
next;
} elsif ($tag eq 'ifset') {
if (/^\@ifset\s+($VARRE)\s*$/o) {
next if defined($value{$1});
&skip_until($tag);
} else {
warn "Bad ifset line: $_";
}
next;
} elsif ($tag eq 'menu' && ! $show_menu) {
&skip_until($tag);
next;
} elsif ($format_map{$tag}) {
$in_pre = 1 if $format_map{$tag} eq 'PRE';
push(@lines, "<$format_map{$tag}>\n");
next;
} elsif ($tag eq 'table') {
if (/^\@[fv]?table\s+\@(\w+)\s*$/) {
$in_table = $1;
push(@lines, "
\n");
} else {
warn "Bad table line: $_";
}
next;
} elsif ($tag eq 'synindex' || $tag eq 'syncodeindex') {
if (/^\@$tag\s+(\w)\w\s+(\w)\w\s*$/) {
eval("*${1}index = *${2}index");
} else {
warn "Bad syn*index line: $_";
}
next;
} elsif ($tag eq 'sp') {
push(@lines, "
\n");
} elsif (defined($def_map{$end_tag})) {
push(@lines, "\n");
}
while ($level < $curlevel) {
$curlevel--;
push(@toc_lines, "
\n");
}
$_ = "\n"), next if /^\@menu\b/;
$in_menu = 0, push(@lines2, "
\n"), next if /^\@end\s+menu\b/;
if ($in_menu) {
if (/^\*\s+($NODERE)::/o) {
$descr = $';
chop($descr);
&menu_entry($1, $1, $descr);
} elsif (/^\*\s+(.+):\s+([^\t,\.\n]+)[\t,\.\n]/) {
$descr = $';
chop($descr);
&menu_entry($1, $2, $descr);
} elsif (/^\*/) {
warn "Bad menu line: $_";
} else { # description continued?
push(@lines2, $_);
}
next;
}
#
# printindex
#
if (/^\@printindex\s+(\w)\w\b/) {
eval("*ary = *$1index");
@keys = keys(%ary);
for $key (@keys) {
$_ = $key;
1 while s/<(\w+)>\`(.*)\'<\/\1>/$2/; # remove HTML tags with quotes
1 while s/<(\w+)>(.*)<\/\1>/$2/; # remove HTML tags
&unprotect_html;
&unprotect_texi;
tr/A-Z/a-z/; # lowercase
$key2alpha{$key} = $_;
print "# index $key sorted as $_\n"
if $key ne $_ && $debug & $DEBUG_INDEX;
}
$last_letter = '';
push(@lines2, "$_
\n");
$last_letter = $letter;
}
push(@lines2, "" . &anchor($id, "$d#$id", $foot) . "
\n");
push(@foot_lines, "$text\n");
$_ = $before . &anchor($id, "$docu_foot#$id", $foot) . $after;
}
}
#
# remove unnecessary $value{'title'}
$value{'subtitle'}
$value{'author'}
$value{'title'}
EOT
&print(*doc_lines, FILE);
close(FILE);
} else {
warn "Can't write to $docu_doc: $!\n";
}
}
#
# print footnotes
#
if (@foot_lines) {
if (open(FILE, "> $docu_foot")) {
print "# creating $docu_foot...\n" if $verbose;
print FILE <$value{'title'}
EOT
&print(*foot_lines, FILE);
close(FILE);
} else {
warn "Can't write to $docu_foot: $!\n";
}
}
print "# that's all folks\n" if $verbose;
#+++############################################################################
# #
# Low level functions #
# #
#---############################################################################
sub check {
local($_, %seen, %context, $before, $match, $after);
while (<>) {
if (/\@(\*|\.|\:|\@|\{|\})/) {
$seen{$&}++;
$context{$&} .= "> $_" if $verbose;
$_ = "$`XX$'";
redo;
}
if (/\@(\w+)/) {
($before, $match, $after) = ($`, $&, $');
if ($before =~ /\b[\w-]+$/ && $after =~ /^[\w-.]*\b/) { # e-mail address
$seen{'e-mail address'}++;
$context{'e-mail address'} .= "> $_" if $verbose;
} else {
$seen{$match}++;
$context{$match} .= "> $_" if $verbose;
}
$match =~ s/^\@/X/;
$_ = "$before$match$after";
redo;
}
}
for (sort(keys(%seen))) {
if ($verbose) {
print "$_\n";
print $context{$_};
} else {
print "$_ ($seen{$_})\n";
}
}
}
sub open {
local($name) = @_;
++$fh_name;
if (open($fh_name, $name)) {
unshift(@fhs, $fh_name);
} else {
warn "Can't read file $name: $!\n";
}
}
sub init_input {
@fhs = (); # hold the file handles to read
@input_spool = (); # spooled lines to read
$fh_name = 'FH000';
&open($docu);
}
sub next_line {
local($fh, $line);
if (@input_spool) {
$line = shift(@input_spool);
return($line);
}
while (@fhs) {
$fh = $fhs[0];
$line = <$fh>;
return($line) if $line;
close($fh);
shift(@fhs);
}
return(undef);
}
# used in pass 1, use &next_line
sub skip_until {
local($tag) = @_;
local($_);
while ($_ = &next_line) {
return if /^\@end\s+$tag\s*$/;
}
die "* Failed to find '$tag' after: " . $lines[$#lines];
}
sub menu_entry {
local($entry, $node, $descr) = @_;
local($href);
$sec = $node2sec{$node};
if ($sec) {
$href = $sec2href{$sec};
$descr =~ s/^\s+//;
$descr = ": $descr" if $descr;
push(@lines2, "