3 # manServer - Unix man page to HTML converter
4 # Rolf Howarth, rolf@squarebox.co.uk
5 # Version 1.07 16 July 2001
6 # Version 1.07+ma1 2006-03-31 Matthias Andree
7 # add trailing slash of URLs
10 $version = "1.07+ma1";
11 $manServerUrl = "<A HREF=\"http://www.squarebox.co.uk/users/rolf/download/manServer.shtml\">manServer $version</A>";
15 $ENV{'PATH'} = "/bin:/usr/bin";
18 $request = shift @ARGV;
19 # Usage: manServer [-dn] filename | manServer [-s port]
23 $bodyTag = "BODY bgcolor=#F0F0F0 text=#000000 link=#0000ff vlink=#C000C0 alink=#ff0000";
25 if ($ENV{'GATEWAY_INTERFACE'} ne "")
28 open(LOG, ">>/tmp/manServer.log");
29 chmod(0666, '/tmp/manServer.log');
30 $root = $ENV{'SCRIPT_NAME'};
31 $url = $ENV{'PATH_INFO'};
32 if ($ENV{'REQUEST_METHOD'} eq "POST")
33 { $args = <STDIN>; chop $args; }
35 { $args = $ENV{'QUERY_STRING'}; }
36 $url .= "?".$args if ($args);
38 $date = &fmtTime(time);
39 $remoteHost = $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'};
40 $referer = $ENV{'HTTP_REFERER'};
41 $userAgent = $ENV{'HTTP_USER_AGENT'};
42 print LOG "$date\t$remoteHost\t$url\t$referer\t$userAgent\n";
45 elsif ($request eq "-s" || $request eq "")
53 if ($request =~ m/^-d(\d)/)
56 $request = shift @ARGV;
60 $file = findPage($request);
67 ##### Mini HTTP Server ####
72 $port = 8888 unless $port;
74 $sockaddr = 'S n a4 x8';
76 ($name, $aliases, $proto) = getprotobyname('tcp');
77 ($name, $aliases, $port) = getservbyname($port, 'tcp')
78 unless $port =~ /^\d+$/;
82 $this = pack($sockaddr, AF_INET, $port, "\0\0\0\0");
84 select(NS); $| = 1; select(stdout);
86 socket(S, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
93 print STDERR "Failed to bind to port $port: $!\n";
98 listen(S, 5) || die "connect: $!";
100 select(S); $| = 1; select(stdout);
104 print LOG "Waiting for connection on port $port\n";
105 ($addr = accept(NS,S)) || die $!;
106 #print "accept ok\n";
108 ($af,$rport,$inetaddr) = unpack($sockaddr,$addr);
109 @inetaddr = unpack('C4',$inetaddr);
110 print LOG "Got connection from ", join(".",@inetaddr), "\n";
114 if (m/^GET (\S+)/) { $url = $1; }
118 processRequest($url);
127 print LOG "Request = $url, root = $root\n";
129 if ( ($url =~ m/^([^?]*)\?(.*)$/) || ($url =~ m/^([^&]*)&(.*)$/) )
140 @params = split(/[=&]/, $args);
141 for ($i=0; $i<=$#params; ++$i)
143 $params[$i] =~ tr/+/ /;
144 $params[$i] =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C",hex($1))/eg;
148 $request = $params{'q'} if ($params{'q'});
149 $searchType = $params{'t'};
150 $debug = $params{'d'};
157 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
158 print OUT "Content-type: text/html\n\n";
159 print OUT "<H1>Searching not yet implemented</H1>\n";
160 print LOG "Searching not implemented\n";
163 elsif ($request eq "/" || $request eq "")
165 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
166 print OUT "Content-type: text/html\n\n";
167 print LOG "Home page\n";
171 elsif ($request =~ m,^/.*/$,)
173 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
174 print OUT "Content-type: text/html\n\n";
175 print LOG "List directory\n";
179 elsif (-f $request || -f "$request.gz" || -f "$request.bz2")
181 # Only allow fully specified files if they're in our manpath
182 foreach $md (@manpath)
185 if (substr($request,0,length($dir)) eq $dir)
187 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
188 print OUT "Content-type: text/html\n\n";
197 $file = findPage($request);
198 if (@multipleMatches)
200 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
201 print OUT "Content-type: text/html\n\n";
202 print LOG "Multiple matches\n";
208 print OUT "HTTP/1.0 301 Redirected\n" unless ($cgiMode);
209 $file .= "&d=$debug" if ($debug);
210 print OUT "Location: $root$file\n\n";
211 print LOG "Redirect to $root$file\n";
218 print OUT "HTTP/1.0 404 Not Found\n" unless ($cgiMode);
219 print OUT "Content-type: text/html\n\n";
220 print OUT "<HTML><HEAD>\n<TITLE>Not Found</TITLE>\n<$bodyTag>\n";
221 print OUT "<CENTER><H1><HR>Not Found<HR></H1></CENTER>\nFailed to find man page /$request\n";
222 print OUT "<P><HR><P><A HREF=\"$root/\">Main Index</A>\n</HTML>\n";
223 print STDERR "Failed to find /$request\n" unless ($cgiMode);
229 print OUT "<HTML><HEAD><TITLE>Manual Pages - Main Index</TITLE>
230 </HEAD><$bodyTag><CENTER><H1><HR><I>Manual Reference Pages</I> - Main Index<HR></H1></CENTER>
231 <FORM ACTION=\"$root/\" METHOD=get>\n";
232 $uname = `uname -s -r`;
235 $hostname = `hostname`;
236 print OUT "<B>$uname pages on $hostname</B><P>\n";
238 # print OUT "<SELECT name=t> <OPTION selected value=0>Command name
239 # <OPTION value=1>Keyword search <OPTION value=2>Full text search</SELECT>\n";
240 print OUT "Command name: <INPUT name=q size=20> <INPUT type=submit value=\"Show Page\"> </FORM><P>\n";
242 foreach $dir (@mandirs)
244 ($section) = ($dir =~ m/man([0-9A-Za-z]+)$/);
245 print OUT "<A HREF=\"$root$dir/\">$dir" ;
246 print OUT "- <I>$sectionName{$section}</I>" if ($sectionName{$section});
247 print OUT "</A><BR>\n";
249 print OUT "<P><HR><P><FONT SIZE=-1>Generated by $manServerUrl from local unix man pages.</FONT>\n</BODY></HTML>\n";
254 foreach $md (@manpath)
257 if (substr($request,0,length($dir)) eq $dir)
260 ($section) = ($request =~ m/man([0-9A-Za-z]+)$/);
261 $sectionName = $sectionName{$section};
262 $sectionName = "Manual Reference Pages" unless ($sectionName);
263 print OUT "<HTML><HEAD><TITLE>Contents of $request</TITLE></HEAD>\n<$bodyTag>\n";
264 print OUT "<CENTER><H1><HR><NOBR><I>$sectionName</I></NOBR> - <NOBR>Index of $request</NOBR><HR></H1></CENTER>\n";
265 print OUT "<FORM ACTION=\"$root/\" METHOD=get>\n";
266 print OUT "Command name: <INPUT name=q size=20> <INPUT type=submit value=\"Show Page\"> </FORM><P>\n";
268 if (opendir(DIR, $request))
270 @files = sort readdir DIR;
273 next if ($f eq "." || $f eq ".." || $f !~ m/\./);
274 $f =~ s/\.(gz|bz2)$//;
275 # ($name) = ($f =~ m,/([^/]*)$,);
276 print OUT "<A HREF=\"$root$request/$f\">$f</A> \n";
280 print OUT "<P><A HREF=\"$root/\">Main Index</A>\n</HTML>\n";
281 print OUT "<P><HR><P><FONT SIZE=-1>Generated by $manServerUrl from local unix man pages.</FONT>\n</BODY></HTML>\n";
285 print OUT "<H1>Directory $request not known</H1>\n";
290 print OUT "<HTML><HEAD><TITLE>Ambiguous Request '$request'</TITLE></HEAD>\n<$bodyTag>\n";
291 print OUT "<CENTER><H1><HR>Ambiguous Request '$request'<HR></H1></CENTER>\nPlease select one of the following pages:<P><BLOCKQUOTE>";
292 foreach $f (@multipleMatches)
294 print OUT "<A HREF=\"$root$f\">$f</A><BR>\n";
296 print OUT "</BLOCKQUOTE><HR><P><A HREF=\"$root/\">Main Index</A>\n</HTML>\n";
300 ##### Process troff input using man macros into HTML #####
312 $zcat = "/usr/bin/zcat";
313 $zcat = "/bin/zcat" unless (-x $zcat);
314 $srcfile = "$zcat $zfile |";
315 $srcfile =~ m/^(.*)$/;
316 $srcfile = $1; # untaint
318 elsif (-f "$file.bz2")
320 $zfile = "$file.bz2";
321 $srcfile = "/usr/bin/bzcat $zfile |";
322 $srcfile =~ m/^(.*)$/;
323 $srcfile = $1; # untaint
326 print LOG "man2html $file\n";
329 unless (open(SRC, $srcfile))
331 print OUT "<H1>Failed to open $file</H1>\n";
332 print STDERR "Failed to open $srcfile\n";
335 ($dir,$page,$sect) = ($file =~ m,^(.*)/([^/]+)\.([^.]+)$,);
343 $prevailingIndent = 6;
354 $title = "Manual Page - $page($sect)" if ($page && $sect);
357 if (m/^.so (man.*)$/)
359 # An .so include on the first line only is replaced by the referenced page.
360 # (See elsewhere for processing of included sections that occur later in document.)
361 man2html("$dir/../$1");
366 if ($file =~ m/perl/)
369 $perlPattern = join('|', grep($_ ne $page, keys %perlPages));
372 print OUT "<HTML><HEAD>\n<TITLE>$title</TITLE>\n<$bodyTag><A NAME=top></A>\n";
387 # Special case where input is not nroff at all but is preformatted text
388 $sectionName = "Manual Reference Pages";
389 $sectionNumber = $sect;
390 $left = "Manual Page";
391 $right = "Manual Page";
392 $macroPackage = "(preformatted text)";
393 $pageName = "$page($sect)";
394 $saveCurrentLine = $_;
396 $_ = $saveCurrentLine;
403 print OUT "</PRE>\n";
410 plainOutput( "<CENTER>\n" );
411 outputLine( "<H1><HR><I>$sectionName - </I><NOBR>$pageName</NOBR><HR></H1>\n" );
412 plainOutput( "</CENTER>\n" );
419 unless ($cmdLineMode)
421 plainOutput( "<FORM ACTION=\"$root/\" METHOD=get>\n" );
422 plainOutput( "Jump to page <INPUT name=q size=12> or go to <A HREF=#top>Top of page</A> | \n" );
423 plainOutput( "<A HREF=\"$root$dir/\">Section $sectionNumber</A> | \n" );
424 plainOutput( "<A HREF=\"$root/\">Main Index</A>.\n" );
425 plainOutput( "<FORM>\n" );
428 outputLine("<P><HR>\n<TABLE width=100%><TR> <TD width=33%><I>$left</I></TD> <TD width=33% align=center>$pageName</TD> <TD align=right width=33%><I>$right</I></TD> </TR></TABLE>");
430 plainOutput("<FONT SIZE=-1>Generated by $manServerUrl from $zfile $macroPackage.</FONT>\n</BODY></HTML>\n");
435 print OUT "<A name=contents></A><H3>CONTENTS</H3></A>\n";
437 for ($id=1; $id<=$#contents; ++$id)
439 $name = $contents[$id];
441 $pre = " " if ($name =~ m/^ /);
442 $pre .= " " if ($name =~ m/^ /);
444 next if ($name eq "" || $name =~ m,^/,);
445 unless ($name =~ m/[a-z]/)
448 $name =~ s/ (.)/ \u\1/g;
450 outputLine("$pre<A HREF=#$id>$name</A><BR>\n");
455 # First pass to extract table of contents
460 # print STDERR "SRCFILE = $srcfile\n";
461 open(SRC, $srcfile) || return;
465 $foundNroffTag = $foundNroffTag || (m/^\.(\\\"|TH|so) /);
466 if (m/^\.(S[HShs]) ([A-Z].*)\s*$/)
473 if ($c eq "SH" || $c eq "Sh")
480 push(@contents, " $t");
484 push(@contents, " $t");
486 $contents{"\U$t"} = $id;
495 # Remove spurious white space to canonicise the input
499 s,^',.,; # treat non breaking requests as if there was a dot
506 s,^(.*?)$eqnEnd,&processEqnd($1),e;
514 if ($eqnStart && $eqnMode==0)
516 s,$eqnStart(.*?)$eqnEnd,&processEqnd($1),ge;
519 s,$eqnStart(.*)$,&processEqns($1),e;
524 # XXX Note: multiple levels of escaping aren't handled properly, eg. \\*.. as a macro argument
525 # should get interpolated as string but ends up with a literal '\' being copied through to output.
526 s,\\\\\*q,",g; # treat mdoc \\*q as special case
533 # Then apply any variable substitutions and escape < and >
534 # (which has to be done before we start inserting tags...)
535 s,\\\*\((..),$vars{$1},ge;
536 s/\\\*([*'`,^,:~].)/$vars{$1}||"\\*$1"/ge;
537 s,\\\*(.),$vars{$1},ge;
538 # Expand special characters for the first time (eg. \(<-
539 s,\\\((..),$special{$1}||"\\($1",ge;
543 # Interpolate width and number registers
544 s,\\w(.)(.*?)\1,&width($2),ge;
545 s,\\n\((..),&numreg($1),ge;
546 s,\\n(.),&numreg($1),ge;
549 # Undo slash escaping, normally done at output stage, also in macro defn
556 # Rewrite the line, expanding escapes such as font styles, and output it.
557 # The line may be a plain text troff line, or it might be the expanded output of a
558 # macro in which case some HTML tags may already have been inserted into the text.
563 print OUT "<!-- Output: \"$_\" -->\n" if ($debug>1);
567 plainOutput("<!-- Need break --><BR>\n");
570 if ($textSinceBreak && !$noFill && $_ =~ m/^\s/)
572 plainOutput("<BR>\n");
576 s,\\&\.,.,g; # \&. often used to escape dot at start of line
592 # Can't implement local motion tags
596 # Font changes, super/sub-scripts and font size changes
597 s,\\(f[^(]|f\(..|u|d|s[-+]?\d),&inlineStyle($1),ge;
602 # handle a few special accent cases we know how to deal with
603 s,\\o(.)([aouAOU])"\1,\\o\1\2:\1,g;
604 s,\\o(.)(.)\\(.)\1,\\o\1\2\3\1,g;
605 s;\\o(.)([A-Za-z])(['`:,^~])\1;\\o\1\3\2\1;g;
606 #s,\\o(.)(.*?)\1,"<BLINK>".($vars{$2}||$2)."</BLINK>",ge;
607 s,\\o(.)(.*?)\1,$vars{$2}||$2,ge;
609 # Bracket building (ignore)
610 s,\\b(.)(.*?)\1,\2,g;
617 # Expand special characters introduced by eqn
618 s,\\\((..),$special{$1}||"\\($1",ge;
619 s,\\\((..),<BLINK>\\($1</BLINK>,g unless (m,^\.,);
621 # Don't know how to handle other escapes
622 s,(\\[^&]),<BLINK>\1</BLINK>,g unless (m,^\.,);
626 # Insert links for http, ftp and mailto URLs
627 # Recognised URLs are sequence of alphanumerics and special chars like / and ~
628 # but must finish with an alphanumeric rather than punctuation like "."
629 s,\b(https?://[-\w/~:@.%#+$?=]+[\w/]),<A HREF=\"\1\">\1</A>,g;
630 s,\b(ftp://[-\w/~:@.%#+$?=]+),<A HREF=\"\1\">\1</A>,g;
631 s,([-_A-Za-z0-9.]+@[A-Za-z][-_A-Za-z0-9]*\.[-_A-Za-z0-9.]+),<A HREF=\"mailto:\1\">\1</A>,g;
633 # special case for things like 'perlre' as it's so useful but the
634 # pod-generated pages aren't very parser friendly...
635 if ($perlPattern && ! m/<A HREF/i)
637 s,\b($perlPattern)\b,<A HREF=\"$root$perlPages{$1}\">\1</A>,g;
640 # Do this late so \& can be used to suppress conversion of URLs etc.
643 # replace tabs with spaces to next multiple of 8
647 $tmp =~ s/<[^>]*>//g;
648 $tmp =~ s/&[^;]*;/@/g;
649 @tmp = split(/\t/, $tmp);
651 for ($i=0; $i<=$#tmp; ++$i)
653 $pos += length($tmp[$i]);
655 $tab[$i] = 8 - $pos%8 unless (@tabstops);
656 foreach $ts (@tabstops)
668 s,\t," " x (shift @tab),e;
672 $textSinceBreak = $_ unless ($textSinceBreak);
676 # Output a line consisting purely of HTML tags which shouldn't be regarded as
677 # a troff output line.
684 # Output the original line for debugging
687 print OUT "<!-- $origLine -->\n";
690 # Use this to read the next input line (buffered to implement lookahead)
696 $_ = shift @lookahead;
702 # Look ahead to peek at the next input line
705 # set lookaheadPtr to 0 to re-read the lines we've looked ahead at
706 if ($lookaheadPtr>=0 && $lookaheadPtr <= $#lookahead)
708 return $lookahead[$lookaheadPtr++];
712 push(@lookahead, $ll);
716 # Consume the last line that was returned by lookahead
720 if ($lookaheadPtr>=0 && $lookaheadPtr <= $#lookahead)
722 $removed = $lookahead[$lookaheadPtr];
723 @lookahead = (@lookahead[0..$lookaheadPtr-1],@lookahead[$lookaheadPtr+1..$#lookahead]);
727 $removed = pop @lookahead;
730 plainOutput("<!-- Consumed $removed -->\n");
733 # Look ahead skipping comments and other common non-text tags
737 while ($ll =~ m/^\.(\\"|PD|IX|ns)/)
744 # Process $_, expaning any macros into HTML and calling outputLine().
745 # If necessary, this method can read more lines of input from <SRC> (.ig & .de)
746 # The following state variables are used:
750 $doneLine = 1; # By default, this counts as a line for trap purposes
753 s,^\.el ,,; # conditions assumed to evaluate false, so else must be true...
759 elsif ($eqnMode == 2)
761 plainOutput("<!-- $_ -->\n");
774 # Called after processing (most) input lines to decrement trapLine. This is needed
775 # to implement the .it 1 trap after one line for .TP, where the first line is outdented
788 # Process plain text lines
794 plainOutput("<P>\n");
798 s,(\\f[23BI])([A-Z].*?)(\\f.),$1.($contents{"\U$2"}?"<A HREF=#".$contents{"\U$2"}.">$2</A>":$2).$3,ge;
800 if ($currentSection eq "SEE ALSO" && ! $cmdLineMode)
802 # Some people don't use BR or IR for see also refs
803 s,(^|\s)([-.A-Za-z_0-9]+)\s?\(([0-9lL][0-9a-zA-Z]*)\),\1<A HREF=\"$root/$2.$3\">$2($3)</A>,g;
809 # Process macros and built-in directives
814 # Place macro arguments (space delimited unless within ") into @p
815 # Remove " from $_, place command in $c, remainder in $joined
817 @p = grep($_ !~ m/^\s*$/, split(/("[^"]*"|\s+)/) );
822 $joined = join(" ", @p[1..$#p]);
823 $joined2 = join(" ", @p[2..$#p]);
824 $joined3 = join(" ", @p[3..$#p]);
826 if ($macro{$c}) # Expand macro
828 # Get full macro text
830 # Interpolate arguments
831 $macro =~ s,\\\$(\d),$p[$1],ge;
832 #print OUT "<!-- Expanding $c to\n$macro-->\n";
833 foreach $_ (split(/\n/, $macro))
842 elsif ($renamedMacro{$c})
844 $c = $renamedMacro{$c};
847 if ($c eq "ds") # Define string
849 $vars{$p[1]} = $joined2;
852 elsif ($c eq "nr") # Define number register
854 $number{$p[1]} = evalnum($joined2);
857 elsif ($c eq "ti") # Temporary indent
859 plainOutput(" ");
864 if ($macro{$macroName})
866 delete $macro{$macroName};
870 $deletedMacro{$macroName} = 1;
877 $macro = $macro{$oldName};
880 if ($newName =~ $reservedMacros && ! $deletedMacro{$newName})
882 plainOutput("<!-- Not overwriting reserved macro '$newName' -->\n");
886 $macro{$newName} = $macro;
887 delete $deletedMacro{$newName};
889 delete $macro{$oldName};
893 # Support renaming of reserved macros by mapping occurrences of new name
894 # to old name after macro expansion so that built in definition is still
895 # available, also mark the name as deleted to override reservedMacro checks.
896 plainOutput("<!-- Fake renaming reserved macro '$oldName' -->\n");
897 $renamedMacro{$newName} = $oldName;
898 $deletedMacro{$oldName} = 1;
901 elsif ($c eq "de" || $c eq "ig") # Define macro or ignore
905 { $delim = ".$p[1]"; }
907 { $delim = ".$p[2]"; }
908 $delim = ".." if ($delim eq ".");
909 # plainOutput("<!-- Scanning for delimiter $delim -->\n");
924 # plainOutput("<!-- Found delimiter -->\n");
927 if ($macroName =~ $reservedMacros && ! $deletedMacro{$macroName})
929 plainOutput("<!-- Not defining reserved macro '$macroName' ! -->\n");
933 $macro{$macroName} = $macro;
934 delete $deletedMacro{$macroName};
938 elsif ($c eq "so") # Source
940 plainOutput("<P>[<A HREF=\"$root$dir/../$p[1]\">Include document $p[1]</A>]<P>\n");
942 elsif ($c eq "TH" || $c eq "Dt") # Man page title
945 $sectionNumber = $p[2];
946 $sectionName = $sectionName{"\L$sectionNumber"};
947 $sectionName = "Manual Reference Pages" unless ($sectionName);
948 $pageName = "$p[1] ($sectionNumber)";
954 $left = $osver unless ($left);
955 $macroPackage = "using man macros";
959 $macroPackage = "using doc macros";
964 outputLine("- $joined\n");
966 elsif ($c eq "SH" || $c eq "SS" || $c eq "Sh" || $c eq "Ss") # Section/subsection
971 $id = $contents{"\U$joined"};
972 $currentSection = $joined;
974 if ($c eq "SH" || $c eq "Sh")
977 if ($firstSection++==1) # after first 'Name' section
981 outputLine( "<A name=$id>\n\n <H3>$joined</H3>\n\n</A>\n" );
984 elsif ($joined =~ m/\\f/)
986 $joined =~ s/\\f.//g;
987 $id = $contents{"\U$joined"};
988 outputLine( "<A name=$id>\n<H4><I>$joined</I></H4></A>\n" );
993 outputLine( "<A name=$id>\n\n <H4> $joined</H4>\n</A>\n" );
998 elsif ($c eq "TX" || $c eq "TZ") # Document reference
1000 $title = $title{$p[1]};
1001 $title = "Document [$p[1]]" unless ($title);
1002 outputLine( "\\fI$title\\fP$joined2\n" );
1004 elsif ($c eq "PD") # Line spacing
1006 $noSpace = ($p[1] eq "0");
1009 elsif ($c eq "TS") # Table start
1011 unless ($macroPackage =~ /tbl/)
1013 if ($macroPackage =~ /eqn/)
1014 { $macroPackage =~ s/eqn/eqn & tbl/; }
1016 { $macroPackage .= " with tbl support"; }
1021 $troffSeparator = "\t";
1022 plainOutput( "<P><BLOCKQUOTE><TABLE bgcolor=#E0E0E0 border=1 cellspacing=0 cellpadding=3>\n" );
1024 elsif ($c eq "EQ") # Eqn start
1026 unless ($macroPackage =~ /eqn/)
1028 if ($macroPackage =~ /tbl/)
1029 { $macroPackage =~ s/tbl/tbl & eqn/; }
1031 { $macroPackage .= " with eqn support"; }
1035 elsif ($c eq "ps") # Point size
1037 plainOutput(&sizeChange($p[1]));
1039 elsif ($c eq "ft") # Font change
1041 plainOutput(&fontChange($p[1]));
1043 elsif ($c eq "I" || $c eq "B") # Single word font change
1045 $id = $contents{"\U$joined"};
1046 if ($id && $joined =~ m/^[A-Z]/)
1047 { $joined = "<A HREF=#$id>$joined</A>"; }
1048 outputLine( "\\f$c$joined\\fP " );
1049 plainOutput("\n") if ($noFill);
1051 elsif ($c eq "SM") # Single word smaller
1053 outputLine("\\s-1$joined\\s0 ");
1054 $doneLine = 0 unless ($joined);
1056 elsif ($c eq "SB") # Single word bold and small
1058 outputLine("\\fB\\s-1$joined\\s0\\fP ");
1060 elsif (m/^\.[BI]R (\S+)\s?\(\s?([0-9lL][0-9a-zA-Z]*)\s?\)(.*)$/)
1062 # Special form, .BR is generally used for references to other pages
1063 # Annoyingly, some people have more than one per line...
1064 # Also, some people use .IR ...
1065 for ($i=1; $i<=$#p; $i+=2)
1067 $pair = $p[$i]." ".$p[$i+1];
1068 if ($p[$i+1] eq "(")
1070 $pair .= $p[$i+2].$p[$i+3];
1073 if ($pair =~ m/^(\S+)\s?\(\s?([0-9lL][0-9a-zA-Z]*)\s?\)(.*)$/)
1076 { outputLine( "\\fB$1\\fR($2)$3\n" ); }
1078 { outputLine( "<A HREF=\"$root/$1.$2\">$1($2)</A>$3\n" ); }
1081 { outputLine( "$pair\n" ); }
1084 elsif ($c eq "BR" || $c eq "BI" || $c eq "IB" ||
1085 $c eq "IR" || $c eq "RI" || $c eq "RB")
1087 $f1 = (substr($c ,0,1));
1088 $f2 = (substr($c,1,1));
1090 # Check if first param happens to be a section name
1091 $id = $contents{"\U$p[1]"};
1092 if ($id && $p[1] =~ m/^[A-Z]/)
1094 $p[1] = "<A HREF=#$id>$p[1]</A>";
1097 for ($i=1; $i<=$#p; ++$i)
1099 $f = ($i%2 == 1) ? $f1 : $f2;
1100 outputLine("\\f$f$p[$i]");
1102 outputLine("\\fP ");
1103 plainOutput("\n") if ($noFill);
1105 elsif ($c eq "nf" || $c eq "Bd") # No fill
1109 elsif ($c eq "fi" || $c eq "Ed") # Fill
1115 $indent = evalnum($p[1]);
1118 plainOutput("<BR>\n");
1122 # Outdent first line, ie. until next break
1124 $trapAction = *trapHP;
1125 newParagraph($indent);
1126 plainOutput( "<TD colspan=2>\n" );
1134 $indent = evalnum($p[2]);
1135 newParagraph($indent);
1136 outputLine("<TD$width>\n$tag\n</TD><TD>\n");
1143 $trapLine = 1; # Next line is tag, then next column
1144 $doneLine = 0; # (But don't count this line)
1145 $trapAction = *trapTP;
1146 $indent = evalnum($p[1]);
1149 $i = ($indent ? $indent : $prevailingIndent) ;
1153 plainOutput("<!-- Length of tag '$tag' ($w) > indent ($i) -->\n") if ($debug);
1154 newParagraph($indent);
1155 $trapAction = *trapHP;
1156 plainOutput( "<TD colspan=2>\n" );
1161 newParagraph($indent);
1162 plainOutput( "<TD$width nowrap>\n" );
1165 $body = lookahead();
1167 if ($body =~ m/^\.[HILP]?P/)
1170 plainOutput("<!-- Suppressing TP body due to $body -->\n");
1174 elsif ($c eq "LP" || $c eq "PP" || $c eq "P" || $c eq "Pp") # Paragraph
1177 $prevailingIndent = 6;
1178 if ($indent[$indentLevel] > 0 && $docListStyle eq "")
1180 $line = lookahead();
1181 if ($line =~ m/^\.(TP|IP|HP)/)
1183 plainOutput("<!-- suppressed $c before $1 -->\n");
1185 elsif ($line =~ m/^\.RS/)
1187 plainOutput("<P>\n");
1196 $line = lookahead();
1197 if ($line =~ m/^\.(TP|HP|IP|RS)( \d+)?/)
1200 $indent = $prevailingIndent unless ($2);
1201 if ($indent == $indent[$indentLevel])
1206 while ($line ne "" && $line !~ m/^\.(RE|SH|SS|PD)/);
1210 plainOutput("<!-- Found tag $foundTag -->\n");
1211 plainOutput("<TR><TD colspan=2>\n");
1216 plainOutput("<!-- $c ends table -->\n");
1223 plainOutput("<P>\n");
1227 elsif ($c eq "br") # Break
1231 # Should this apply to all macros that cause a break?
1235 $needBreak = 1 if ($textSinceBreak);
1237 elsif ($c eq "sp") # Space
1240 plainOutput("<P>\n");
1242 elsif ($c eq "RS") # Block indent start
1244 if ($indentLevel==0 && $indent[0]==0)
1251 $indent = $prevailingIndent unless ($indent);
1252 if ($indent > $indent[$indentLevel] && !$extraIndent)
1256 $indent[$indentLevel] = 0;
1257 setIndent($indent-$indent[$indentLevel-1]);
1258 plainOutput("<TR><TD$width> </TD><TD>\n");
1261 elsif ($indent < $indent[$indentLevel] || $colState==2)
1265 plainOutput("<TR><TD$width> </TD><TD>\n");
1269 $indent[$indentLevel] = 0;
1271 $prevailingIndent = 6;
1273 elsif ($c eq "RE") # Block indent end
1282 if ($indentLevel==0)
1287 plainOutput("</BLOCKQUOTE>\n");
1297 $prevailingIndent = $indent[$indentLevel];
1298 $prevailingIndent = 6 unless($prevailingIndent);
1300 elsif ($c eq "DT") # default tabs
1304 elsif ($c eq "ta") # Tab stops
1307 for ($i=0; $i<$#p; ++$i)
1313 $tb = $tabstops[$i-1];
1317 $tabstops[$i] = $tb + $ts;
1319 plainOutput("<!-- Tabstops set at ".join(",", @tabstops)." -->\n") if ($debug);
1321 elsif ($c eq "It") # List item (mdoc)
1324 if ($docListStyle eq "-tag")
1326 endRow() unless($multilineIt);
1329 setIndent($tagWidth);
1334 $width = ""; # let table take care of own width
1338 plainOutput("<TR valign=top><TD colspan=2>");
1342 $tag = &mdocStyle(@p[1..$#p]);
1343 $body = lookahead();
1344 if ($body =~ m/^\.It/)
1345 { $multilineItNext = 1; }
1347 { $multilineItNext = 0; }
1350 outputLine("<BR>\n$tag\n");
1352 elsif ($multilineItNext || $tagWidth>0 && width($tag)>$tagWidth)
1354 outputLine("<TR valign=top><TD colspan=2>$tag\n");
1359 outputLine("<TR valign=top><TD>$tag\n");
1362 if ($multilineItNext)
1370 { plainOutput("</TD></TR><TR><TD> </TD><TD>\n"); }
1372 { plainOutput("</TD><TD>\n"); }
1378 plainOutput("<LI>");
1384 if ($docListStyle eq "-tag")
1386 plainOutput("</TD></TR><TR><TD> </TD><TD>\n");
1389 elsif ($c eq "Bl") # Begin list (mdoc)
1391 push @docListStyles, $docListStyle;
1392 if ($p[1] eq "-enum")
1394 plainOutput("<OL>\n");
1395 $docListStyle = $p[1];
1397 elsif($p[1] eq "-bullet")
1399 plainOutput("<UL>\n");
1400 $docListStyle = $p[1];
1404 $docListStyle = "-tag";
1405 if ($p[2] eq "-width")
1407 $tagWidth = width($p[3]);
1408 if ($tagWidth < 6) { $tagWidth = 6; }
1417 elsif ($c eq "El") # End list
1419 if ($docListStyle eq "-tag")
1424 elsif ($docListStyle eq "-bullet")
1426 plainOutput("</UL>\n");
1430 plainOutput("</OL>\n");
1432 $docListStyle = pop @docListStyles;
1442 elsif ($c eq "Sx") # See section
1444 $id = $contents{"\U$joined"};
1445 if ($id && $joined =~ m/^[A-Z]/)
1447 outputLine("<A HREF=#$id>".&mdocStyle(@p[1..$#p])."</A>\n");
1451 my $x = &mdocStyle(@p[1..$#p]);
1453 outputLine($x."\n");
1456 elsif (&mdocCallable($c))
1458 my $x = &mdocStyle(@p);
1460 outputLine($x."\n");
1464 outputLine("<I>BSD $joined</I>\n");
1468 outputLine("<I>Unix $joined</I>\n");
1472 outputLine("<I>AT&T $joined</I>\n");
1474 elsif ($c =~ m/[A-Z][a-z]/) # Unsupported doc directive
1476 outputLine("<BR>.$c $joined\n");
1478 elsif ($c eq "") # Empty line (eg. troff comment)
1482 else # Unsupported directive
1484 # Unknown macros are ignored, and don't count as a line as far as trapLine goes
1486 plainOutput("<!-- ignored unsupported tag .$c -->\n");
1493 $body = lookahead();
1494 if ($body =~ m/^\.TP/)
1497 $trapLine = 1; # restore TP trap
1498 $doneLine = 0; # don't count this line
1499 plainOutput("<BR>\n");
1503 plainOutput("</TD><TD valign=bottom>\n");
1512 $body = lookahead();
1513 if ($body =~ m/^\.([TH]P)/)
1516 # Restore appropriate type of trap
1520 $doneLine = 0; # don't count this line
1526 plainOutput("<BR>\n");
1530 plainOutput("</TD></TR><TR valign=top><TD$width> </TD><TD>\n");
1546 $indent = $prevailingIndent unless ($indent);
1547 $prevailingIndent = $indent;
1549 plainOutput( "<TR valign=top>" );
1552 # End an existing HP/TP/IP/RS row
1555 if ($indent[$indentLevel] > 0)
1558 plainOutput( "</TD></TR>\n" );
1562 # Called when we output a line break tag. Only needs to be called once if
1563 # calling plainOutput, but should call before and after if using outputLine.
1567 $textSinceBreak = 0;
1570 # Called to reset all indents and pending paragraphs (eg. at the start of
1571 # a new top level section).
1575 while ($indentLevel > 0)
1578 if ($indent[$indentLevel] > 0)
1586 # Interpolate a number register (possibly autoincrementing)
1589 return 0 + $number{$_[0]};
1592 # Evaluate a numeric expression
1596 return "" if ($n eq "");
1597 if ($n =~ m/i$/) # inches
1607 $tsb = $textSinceBreak;
1608 $indent = evalnum($_[0]);
1609 if ($indent==0 && $_[0] !~ m/^0/)
1613 plainOutput("<!-- setIndent $indent, indent[$indentLevel] = $indent[$indentLevel] -->\n") if ($debug);
1614 if ($indent[$indentLevel] != $indent)
1617 if ($indent[$indentLevel] > 0)
1619 plainOutput("<TR></TR>") unless ($noSpace);
1620 plainOutput("</TABLE>");
1626 $border = " border=1" if ($debug>2);
1627 #plainOutput("<P>") unless ($indent[$indentLevel] > 0);
1628 plainOutput("<TABLE$border");
1629 # Netscape bug, makes 2 cols same width? : plainOutput("<TABLE$border COLS=2");
1630 # Overcome some of the vagaries of Netscape tables
1631 plainOutput(" width=100%") if ($indentLevel>0);
1634 plainOutput(" cellpadding=0 cellspacing=0>\n");
1638 plainOutput(" cellpadding=3>".($tsb ? "<!-- tsb: $tsb -->\n<TR></TR><TR></TR>\n" : "\n") );
1640 #$width = " width=".($indent*5); # causes text to be chopped if too big
1642 if ($indentLevel > 0)
1643 { $percent = $indent * 100 / (100-$indentLevel[0]); }
1644 $width = " width=$percent%";
1646 $indent[$indentLevel] = $indent;
1650 # Process mdoc style macros recursively, as one of the macro arguments
1651 # may itself be the name of another macro to invoke.
1654 return "" unless @_;
1655 my ($tag, @param) = @_;
1658 # Don't format trailing punctuation
1659 if ($param[$#param] =~ m/^[.,;:]$/)
1663 if ($param[$#param] =~ m/^[)\]]$/)
1665 $term = (pop @param).$term;
1668 if ($param[0] =~ m,\\\\,)
1670 print STDERR "$tag: ",join(",", @param),"\n";
1672 $rest = &mdocStyle(@param);
1676 $rest =~ s/ //; # remove first space
1677 return " \\fP[$rest]$term";
1679 elsif ($tag eq "Xr") # cross reference
1681 my $p = shift @param;
1685 $url .= ".".$param[0];
1686 $rest = "(".$param[0].")";
1690 $rest = &mdocStyle(@param);
1694 return " <B>".$p."</B>".$rest.$term;
1698 return " <A HREF=\"".$root."/".$url."\">".$p."</A>".$rest.$term;
1701 elsif ($tag eq "Fl")
1707 if ($f eq "Ns") # no space
1711 elsif (&mdocCallable($f))
1714 return $sofar.&mdocStyle(@param).$term;
1718 $sofar .= "-<B>$f</B> "
1721 return $sofar.$term;
1723 elsif ($tag eq "Pa" || $tag eq "Er" || $tag eq "Fn" || $tag eq "Dv")
1725 return "\\fC$rest\\fP$term";
1727 elsif ($tag eq "Ad" || $tag eq "Ar" || $tag eq "Em" || $tag eq "Fa" || $tag eq "St" ||
1728 $tag eq "Ft" || $tag eq "Va" || $tag eq "Ev" || $tag eq "Tn" || $tag eq "%T")
1730 return "\\fI$rest\\fP$term";
1732 elsif ($tag eq "Nm")
1734 $defaultNm = $param[0] unless ($defaultNm);
1735 $rest = $defaultNm unless ($param[0]);
1736 return "\\fB$rest\\fP$term";
1738 elsif ($tag eq "Ic" || $tag eq "Cm" || $tag eq "Sy")
1740 return "\\fB$rest\\fP$term";
1742 elsif ($tag eq "Ta") # Tab
1744 # Tabs are used inconsistently so this is the best we can do. Columns won't line up. Tough.
1745 return " $rest$term";
1747 elsif ($tag eq "Ql")
1750 return "`<TT>$rest</TT>'$term";
1752 elsif ($tag eq "Dl")
1754 return "<P> <TT>$rest</TT>$term<P>\n";
1756 elsif ($tag =~ m/^[ABDEOPQS][qoc]$/)
1761 { $lq = "<"; $rq = ">"; }
1762 elsif ($tag =~ m/^B/)
1763 { $lq = "["; $rq = "]"; }
1764 elsif ($tag =~ m/^D/)
1765 { $lq = "\""; $rq = "\""; }
1766 elsif ($tag =~ m/^P/)
1767 { $lq = "("; $rq = ")"; }
1768 elsif ($tag =~ m/^Q/)
1769 { $lq = "\""; $rq = "\""; }
1770 elsif ($tag =~ m/^S/)
1771 { $lq = "\\'"; $rq = "\\'"; }
1772 elsif ($tag =~ m/^O/)
1773 { $lq = "["; $rq = "]"; }
1779 return $lq.$rest.$rq.$term ;
1781 elsif (&mdocCallable($tag)) # but not in list above...
1785 elsif ($tag =~ m/^[.,;:()\[\]]$/) # punctuation
1787 return $tag.$rest.$term;
1789 elsif ($tag eq "Ns")
1795 return " ".$tag.$rest.$term;
1799 # Determine if a macro is mdoc parseable/callable
1802 return ($_[0] =~ m/^(Op|Fl|Pa|Er|Fn|Ns|No|Ad|Ar|Xr|Em|Fa|Ft|St|Ic|Cm|Va|Sy|Nm|Li|Dv|Ev|Tn|Pf|Dl|%T|Ta|Ql|[ABDEOPQS][qoc])$/);
1806 # Estimate the output width of a string
1809 local($word) = $_[0];
1810 $word =~ s,<[/A-Z][^>]*>,,g; # remove any html tags
1811 $word =~ s/^\.\S+\s//;
1814 $word =~ s/[ ()|.,!;:"']//g; # width of punctuation is about half a character
1815 return ($x + length($word)) / 2;
1818 # Process a tbl table (between TS/TE tags)
1821 if ($troffTable == "1")
1831 $troffSeparator = quotemeta($1) if (m/tab\s*\((.)\)/);
1837 s/^[^lrcan^t]*//; # remove any 'modifiers' coming before tag
1838 # delimit on tags excluding s (viewed as modifier of previous column)
1839 s/([lrcan^t])/\t$1/g;
1841 push @troffRowDefs, $_;
1842 last if ($origLine =~ m/\.\s*$/);
1851 s/$troffSeparator/\t/g;
1857 plainOutput("</TABLE></BLOCKQUOTE>\n");
1865 elsif (m/[_=]/ && m/^[_=\t]*$/ && $troffCol==0)
1870 plainOutput("<TR></TR><TR></TR>\n");
1873 elsif ($troffCol==0 && @troffRowDefs)
1875 # Don't output a row, but this counts as a row as far as row defs go
1876 $rowDef = shift @troffRowDefs;
1877 @troffColDefs = split(/\t/, $rowDef);
1880 elsif (m/^\.sp/ && $troffCol==0 && !$hadUnderscore)
1883 plainOutput("<TR></TR><TR></TR>\n");
1885 elsif ($_ eq ".br" && $troffMultiline)
1887 $rowref->[$troffCol] .= "<BR>\n";
1889 elsif ($_ !~ m/^\./)
1891 $rowref = $tableRows[$#tableRows]; # reference to current row (last row in array)
1892 if ($troffCol==0 && @troffRowDefs)
1894 $rowDef = shift @troffRowDefs;
1895 if ($rowDef =~ m/^[_=]/)
1899 plainOutput("<TR></TR><TR></TR>\n");
1902 $rowDef = shift @troffRowDefs;
1904 @troffColDefs = split(/\t/, $rowDef);
1907 if ($troffCol == 0 && !$troffMultiline)
1910 push(@tableRows, $rowref);
1911 #plainOutput("<TR valign=top>");
1917 $troffMultiline = 0;
1919 if ($troffMultiline)
1921 $rowref->[$troffCol] .= "$_\n";
1925 @columns = split(/\t/, $_);
1926 plainOutput("<!-- Adding (".join(",", @columns)."), type (".join(",", @troffColDefs).") -->\n") if ($debug);
1927 while ($troffCol <= $#troffColDefs && @columns > 0)
1929 $def = $troffColDefs[$troffCol];
1930 $col = shift @columns;
1933 $col = "\\^" if ($col eq "" && $def =~ m/\^/);
1934 $col = " " if ($col eq "");
1939 if ($def =~ m/[bB]/ || $def =~ m/f3/)
1940 { $style1 = "\\fB"; $style2 = "\\fP"; }
1941 if ($def =~ m/I/ || $def =~ m/f2/)
1942 { $style1 = "\\fI"; $style2 = "\\fP"; }
1944 if ($def =~ m/c/) { $align = " align=center"; }
1945 if ($def =~ m/[rn]/) { $align = " align=right"; }
1948 if ($span) { $align.= " colspan=".(length($span)+1); }
1953 $rowref->[$troffCol] .= "$style2</TD>";
1956 elsif ($col =~ m/T{/) #}
1959 $rowref->[$troffCol] = "<TD$align>$style1$col";
1960 $troffMultiline = 1;
1964 $rowref->[$troffCol] = "<TD$align>$style1$col$style2</TD>";
1969 endTblRow() unless ($troffMultiline);
1975 return if ($troffCol == 0);
1976 while ($troffCol <= $#troffColDefs)
1978 $rowref->[$troffCol] = "<TD> </TD>";
1979 #print OUT "<TD> </TD>";
1983 #print OUT "</TR>\n"
1988 plainOutput("<!-- flushTable $#tableRows rows -->\n") if ($debug);
1990 # Treat rows with first cell blank or with more than one vertically
1991 # spanned row as a continuation of the previous line.
1992 # Note this is frequently a useful heuristic but isn't foolproof.
1993 for($r=0; $r<$#tableRows; ++$r)
1996 for ($c=0; $c<=$#{$tableRows[$r+1]}; ++$c)
1997 {++$vspans if ($tableRows[$r+1][$c] =~ m,<TD.*?>\\\^</TD>,);}
1998 if ((($vspans>1) || ($tableRows[$r+1][0] =~ m,<TD.*?> </TD>,)) &&
1999 $#{$tableRows[$r]} == $#{$tableRows[$r+1]} && 0)
2003 plainOutput("<!-- merging row $r+1 into previous -->\n");
2004 plainOutput("<!-- row $r: (".join(",", @{$tableRows[$r]}).") -->\n");
2005 plainOutput("<!-- row $r+1: (".join(",", @{$tableRows[$r+1]}).") -->\n");
2007 for ($c=0; $c<=$#{$tableRows[$r]}; ++$c)
2009 $tableRows[$r][$c] .= $tableRows[$r+1][$c];
2010 $tableRows[$r][$c] =~ s,\\\^,,g; # merging is stronger than spanning!
2011 $tableRows[$r][$c] =~ s,</TD><TD.*?>,<BR>,;
2013 @tableRows = (@tableRows[0..$r], @tableRows[$r+2 .. $#tableRows]);
2014 --$r; # process again
2018 # Turn \^ vertical span requests into rowspan tags
2019 for($r=0; $r<$#tableRows; ++$r)
2021 for ($c=0; $c<=$#{$tableRows[$r]}; ++$c)
2024 while ( $r2<=$#tableRows && ($tableRows[$r2][$c] =~ m,<TD.*?>\\\^</TD>,) )
2031 plainOutput("<!-- spanning from $r,$c -->\n") if ($debug);
2032 $tableRows[$r][$c] =~ s/<TD/<TD rowspan=$rs/;
2037 # As tbl and html differ in whether they expect spanned cells to be
2038 # supplied, remove any cells that are 'rowspanned into'.
2039 for($r=0; $r<=$#tableRows; ++$r)
2041 for ($c=$#{$tableRows[$r]}; $c>=0; --$c)
2043 if ($tableRows[$r][$c] =~ m/<TD rowspan=(\d+)/)
2045 for ($r2=$r+1; $r2<$r+$1; ++$r2)
2047 $rowref = $tableRows[$r2];
2048 plainOutput("<!-- removing $r2,$c: ".$rowref->[$c]." -->\n") if ($debug);
2049 @$rowref = (@{$rowref}[0..$c-1], @{$rowref}[$c+1..$#$rowref]);
2055 # Finally, output the cells that are left
2056 for($r=0; $r<=$#tableRows; ++$r)
2058 plainOutput("<TR valign=top>\n");
2059 for ($c=0; $c <= $#{$tableRows[$r]}; ++$c)
2061 outputLine($tableRows[$r][$c]);
2063 plainOutput("</TR>\n");
2067 plainOutput("<!-- flushTable done -->\n") if ($debug);
2071 # Use these for all font changes, including .ft, .ps, .B, .BI, .SM etc.
2072 # Need to add a mechanism to stack up these changes so tags match: <X> <Y> ... </Y> </X> etc.
2079 print OUT "<!-- pushStyle $type($tag) [".join(",", @styleStack)."] " if ($debug>1);
2081 if (grep(m/^$type/, @styleStack))
2083 print OUT "undoing up to old $type " if ($debug>1);
2086 # search back, undoing intervening tags in reverse order
2087 $oldItem = pop @styleStack;
2088 ($oldTag) = ($oldItem =~ m/^.(\S+)/);
2089 $result .= "</$oldTag>";
2090 if (substr($oldItem,0,1) eq $type)
2092 print OUT "found $oldItem " if ($debug>1);
2095 # restore the intermediates again
2096 $oldItem = shift @oldItems;
2097 push(@styleStack, $oldItem);
2098 $result .= "<".substr($oldItem,1).">";
2104 unshift(@oldItems, $oldItem);
2108 print OUT "oldItems=(@oldItems) " if ($debug>1);
2109 push(@styleStack, @oldItems); # if we didn't find anything of type
2112 $result .= "<$tag>";
2113 push(@styleStack, $type.$tag);
2115 print OUT "-> '$result' -->\n" if ($debug>1);
2123 print OUT "<!-- resetStyles [".join(",", @styleStack)."] -->\n";
2124 print OUT "<HR> resetStyles [".join(",", @styleStack)."] <HR>\n" if ($debug);
2128 $oldItem = pop @styleStack;
2129 ($oldTag) = ($oldItem =~ m/^.(\S+)/);
2130 print OUT "</$oldTag>";
2138 print OUT "<BLOCKQUOTE>\n";
2145 while ($blockquote > 0)
2147 print OUT "</BLOCKQUOTE>\n";
2154 plainOutput(pushStyle("I", "TABLE"));
2156 $width = " width=$width%" if ($width);
2157 plainOutput("<TR><TD$width> </TD><TD>\n");
2162 plainOutput("</TD></TR>\n");
2163 plainOutput(pushStyle("I"));
2168 $_[0] =~ m/^(.)(.*)$/;
2171 elsif ($1 eq "s" && ! $noFill)
2182 if ($fnt eq "P" || $fnt eq "R" || $fnt eq "1" || $fnt eq "")
2184 elsif ($fnt eq "B" || $fnt eq "3")
2186 elsif ($fnt eq "I" || $fnt eq "2")
2190 return pushStyle("F", $font);
2196 if ($size =~ m/^[+-]/)
2197 { $currentSize += $size; }
2199 { $currentSize = $size-10; }
2200 $currentSize = 0 if (! $size);
2203 $sz = -2 if ($sz < -2);
2204 $sz = 2 if ($sz > 2);
2206 if ($currentSize eq "0")
2209 { $size = "FONT size=$sz"; }
2210 return pushStyle("S", $size);
2216 ++$currentShift if ($sub eq "u");
2217 --$currentShift if ($sub eq "d");
2219 $tag = "SUP" if ($currentShift > 0);
2220 $tag = "SUB" if ($currentShift < 0);
2221 return pushStyle("D", $tag);
2226 print OUT "<PRE>\n" unless($noFill);
2232 print OUT "</PRE>\n" if ($noFill);
2239 if ($eqnMode==2 && $_[0] =~ m/^\.EN/)
2242 outputLine(flushEqn());
2246 $eqnBuffer .= $_[0]." ";
2257 @p = grep($_ !~ m/^ *$/, split(/("[^"]*"|\s+|[{}~^])/, $eqnBuffer) );
2259 #return "[".join(',', @p)." -> ".&doEqn(@p)."]\n";
2261 #$res =~ s,\\\((..),$special{$1}||"\\($1",ge;
2262 #$res =~ s,<,<,g;
2263 #$res =~ s,>,>,g;
2275 ($res, @p) = doEqn1(@p);
2290 @x = split(/\0/, $eqndefs{$c});
2294 if ($c =~ m/^"(.*)"$/)
2298 elsif ($c eq "delim")
2309 $eqnStart = quotemeta($1);
2310 $eqnEnd = quotemeta($2);
2313 elsif ($c eq "define" || $c eq "tdefine" || $c eq "ndefine")
2318 if (length($d) != 1)
2321 $def =~ s/^.(.*)./\1/;
2325 while (@p && $p[0] ne $d)
2333 $eqndefs{$t} = $def unless ($c eq "ndefine");
2339 for ($i=0; $i<=$#p; ++$i)
2341 ++$level if ($p[$i] eq "{");
2342 --$level if ($p[$i] eq "}");
2343 last if ($level==0);
2345 $res = doEqn(@p[0..$i-1]);
2350 ($c,@p) = &doEqn1(@p);
2355 ($c,@p) = &doEqn1(@p);
2358 elsif ($c eq "sub" || $c eq "from")
2360 ($c,@p) = &doEqn1(@p);
2363 elsif ($c eq "matrix")
2365 ($c,@p) = &doEqn1(@p);
2366 $res = "matrix ( $c )";
2368 elsif ($c eq "bold")
2370 ($c,@p) = &doEqn1(@p);
2371 $res = "\\fB$c\\fP";
2373 elsif ($c eq "italic")
2375 ($c,@p) = &doEqn1(@p);
2376 $res = "\\fI$c\\fP";
2378 elsif ($c eq "roman")
2381 elsif ($c eq "font" || $c eq "gfont" || $c eq "size" || $c eq "gsize")
2385 elsif ($c eq "mark" || $c eq "lineup")
2388 elsif ($c eq "~" || $c eq "^")
2392 elsif ($c eq "over")
2396 elsif ($c eq "half")
2400 elsif ($c eq "prime")
2408 elsif ($c eq "dotdot")
2412 elsif ($c eq "tilde")
2420 elsif ($c eq "bar" || $c eq "vec")
2424 elsif ($c eq "under")
2428 elsif ( $c eq "sqrt" || $c eq "lim" || $c eq "sum" || $c eq "pile" || $c eq "lpile" ||
2429 $c eq "rpile" || $c eq "cpile" || $c eq "int" || $c eq "prod" )
2433 elsif ($c eq "cdot")
2441 elsif ($c eq "above" || $c eq "lcol" || $c eq "ccol")
2445 elsif ($c eq "sin" || $c eq "cos" || $c eq "tan" || $c eq "log" || $c eq "ln" )
2449 elsif ($c eq "left" || $c eq "right" || $c eq "nothing")
2452 elsif ($c =~ m/^[A-Za-z]/)
2454 $res = "\\fI$c\\fP";
2464 ##### Search manpath and initialise special char array #####
2468 # Determine groff version if possible
2469 my $groffver = `groff -v`;
2470 $groffver =~ /^GNU groff version (\S+)/;
2473 # Parse the macro definition file for section names
2474 if (open(MACRO, "/usr/lib/tmac/tmac.an") ||
2475 open(MACRO, "/usr/lib/tmac/an") ||
2476 open(MACRO, "/usr/lib/groff/tmac/tmac.an") ||
2477 open(MACRO, "/usr/lib/groff/tmac/an.tmac") ||
2478 open(MACRO, "/usr/share/tmac/tmac.an") ||
2479 open(MACRO, "/usr/share/tmac/an.tmac") ||
2480 open(MACRO, "/usr/share/groff/tmac/tmac.an") ||
2481 open(MACRO, "/usr/share/groff/tmac/an.tmac") ||
2482 open(MACRO, "/usr/share/groff/$groffver/tmac/an.tmac") )
2487 if (m/\$2'([0-9a-zA-Z]+)' .ds ]D (.*)$/)
2490 unless ($sn =~ m/[a-z]/)
2493 $sn =~ s/ (.)/ \u\1/g;
2495 $sectionName{"\L$1"} = $sn;
2497 if (m/\$1'([^']+)' .ds Tx "?(.*)$/)
2501 if (m/^.ds ]W (.*)$/)
2509 print STDERR "Failed to read tmac.an definitions\n" unless ($cgiMode);
2511 if (open(MACRO, "/usr/lib/tmac/tz.map"))
2516 if (m/\$1'([^']+)' .ds Tz "?(.*)$/)
2523 # Prevent redefinition of macros that have special meaning to us
2524 $reservedMacros = '^(SH|SS|Sh|Ss)$';
2526 # Predefine special number registers
2529 # String variables defined by man package
2530 $vars{'lq'} = '“';
2531 $vars{'rq'} = '”';
2532 $vars{'R'} = '\\(rg';
2533 $vars{'S'} = '\\s0';
2535 # String variables defined by mdoc package
2536 $vars{'Le'} = '\\(<=';
2537 $vars{'<='} = '\\(<=';
2538 $vars{'Ge'} = '\\(>=';
2541 $vars{'Ne'} = '\\(!=';
2542 $vars{'>='} = '\\(>=';
2543 $vars{'q'} = '"'; # see also special case in preProcessLine
2544 $vars{'Lq'} = '“';
2545 $vars{'Rq'} = '”';
2546 $vars{'ua'} = '\\(ua';
2547 $vars{'ga'} = '\\(ga';
2548 $vars{'Pi'} = '\\(*p';
2549 $vars{'Pm'} = '\\(+-';
2550 $vars{'Na'} = 'NaN';
2551 $vars{'If'} = '\\(if';
2554 # String variables defined by ms package (access to accented characters)
2555 $vars{'bu'} = '»';
2556 $vars{'66'} = '“';
2557 $vars{'99'} = '”';
2558 $vars{'*!'} = '¡';
2559 $vars{'ct'} = '¢';
2560 $vars{'po'} = '£';
2561 $vars{'gc'} = '¤';
2562 $vars{'ye'} = '¥';
2563 #$vars{'??'} = '¦';
2564 $vars{'sc'} = '§';
2565 $vars{'*:'} = '¨';
2566 $vars{'co'} = '©';
2567 $vars{'_a'} = 'ª';
2568 $vars{'<<'} = '«';
2569 $vars{'no'} = '¬';
2570 $vars{'hy'} = '­';
2571 $vars{'rg'} = '®';
2572 $vars{'ba'} = '¯';
2573 $vars{'de'} = '°';
2574 $vars{'pm'} = '±';
2575 #$vars{'??'} = '²';
2576 #$vars{'??'} = '³';
2577 $vars{'aa'} = '´';
2578 $vars{'mu'} = 'µ';
2579 $vars{'pg'} = '¶';
2580 $vars{'c.'} = '·';
2581 $vars{'cd'} = '¸';
2582 #$vars{'??'} = '¹';
2583 $vars{'_o'} = 'º';
2584 $vars{'>>'} = '»';
2585 $vars{'14'} = '¼';
2586 $vars{'12'} = '½';
2587 #$vars{'??'} = '¾';
2588 $vars{'*?'} = '¿';
2589 $vars{'`A'} = 'À';
2590 $vars{"'A"} = 'Á';
2591 $vars{'^A'} = 'Â';
2592 $vars{'~A'} = 'Ã';
2593 $vars{':A'} = 'Ä';
2594 $vars{'oA'} = 'Å';
2595 $vars{'AE'} = 'Æ';
2596 $vars{',C'} = 'Ç';
2597 $vars{'`E'} = 'È';
2598 $vars{"'E"} = 'É';
2599 $vars{'^E'} = 'Ê';
2600 $vars{':E'} = 'Ë';
2601 $vars{'`I'} = 'Ì';
2602 $vars{"'I"} = 'Í';
2603 $vars{'^I'} = 'Î';
2604 $vars{':I'} = 'Ï';
2605 $vars{'-D'} = 'Ð';
2606 $vars{'~N'} = 'Ñ';
2607 $vars{'`O'} = 'Ò';
2608 $vars{"'O"} = 'Ó';
2609 $vars{'^O'} = 'Ô';
2610 $vars{'~O'} = 'Õ';
2611 $vars{':O'} = 'Ö';
2612 #$vars{'mu'} = '×';
2613 $vars{'NU'} = 'Ø';
2614 $vars{'`U'} = 'Ù';
2615 $vars{"'U"} = 'Ú';
2616 $vars{'^U'} = 'Û';
2617 $vars{':U'} = 'Ü';
2618 #$vars{'??'} = 'Ý';
2619 $vars{'Th'} = 'Þ';
2620 $vars{'*b'} = 'ß';
2621 $vars{'`a'} = 'à';
2622 $vars{"'a"} = 'á';
2623 $vars{'^a'} = 'â';
2624 $vars{'~a'} = 'ã';
2625 $vars{':a'} = 'ä';
2626 $vars{'oa'} = 'å';
2627 $vars{'ae'} = 'æ';
2628 $vars{',c'} = 'ç';
2629 $vars{'`e'} = 'è';
2630 $vars{"'e"} = 'é';
2631 $vars{'^e'} = 'ê';
2632 $vars{':e'} = 'ë';
2633 $vars{'`i'} = 'ì';
2634 $vars{"'i"} = 'í';
2635 $vars{'^i'} = 'î';
2636 $vars{':i'} = 'ï';
2637 #$vars{'??'} = 'ð';
2638 $vars{'~n'} = 'ñ';
2639 $vars{'`o'} = 'ò';
2640 $vars{"'o"} = 'ó';
2641 $vars{'^o'} = 'ô';
2642 $vars{'~o'} = 'õ';
2643 $vars{':o'} = 'ö';
2644 $vars{'di'} = '÷';
2645 $vars{'nu'} = 'ø';
2646 $vars{'`u'} = 'ù';
2647 $vars{"'u"} = 'ú';
2648 $vars{'^u'} = 'û';
2649 $vars{':u'} = 'ü';
2650 #$vars{'??'} = 'ý';
2651 $vars{'th'} = 'þ';
2652 $vars{':y'} = 'ÿ';
2654 # troff special characters and their closest equivalent
2656 $special{'em'} = '—';
2657 $special{'hy'} = '-';
2658 $special{'\-'} = '–'; # was -
2659 $special{'bu'} = 'o';
2660 $special{'sq'} = '[]';
2661 $special{'ru'} = '_';
2662 $special{'14'} = '¼';
2663 $special{'12'} = '½';
2664 $special{'34'} = '¾';
2665 $special{'fi'} = 'fi';
2666 $special{'fl'} = 'fl';
2667 $special{'ff'} = 'ff';
2668 $special{'Fi'} = 'ffi';
2669 $special{'Fl'} = 'ffl';
2670 $special{'de'} = '°';
2671 $special{'dg'} = '†'; # was 182, para symbol
2672 $special{'fm'} = "\\'";
2673 $special{'ct'} = '¢';
2674 $special{'rg'} = '®';
2675 $special{'co'} = '©';
2676 $special{'pl'} = '+';
2677 $special{'mi'} = '-';
2678 $special{'eq'} = '=';
2679 $special{'**'} = '*';
2680 $special{'sc'} = '§';
2681 $special{'aa'} = '´'; # was '
2682 $special{'ga'} = '`'; # was `
2683 $special{'ul'} = '_';
2684 $special{'sl'} = '/';
2685 $special{'*a'} = 'a';
2686 $special{'*b'} = 'ß';
2687 $special{'*g'} = 'y';
2688 $special{'*d'} = 'd';
2689 $special{'*e'} = 'e';
2690 $special{'*z'} = 'z';
2691 $special{'*y'} = 'n';
2692 $special{'*h'} = 'th';
2693 $special{'*i'} = 'i';
2694 $special{'*k'} = 'k';
2695 $special{'*l'} = 'l';
2696 $special{'*m'} = 'µ';
2697 $special{'*n'} = 'v';
2698 $special{'*c'} = '3';
2699 $special{'*o'} = 'o';
2700 $special{'*p'} = 'pi';
2701 $special{'*r'} = 'p';
2702 $special{'*s'} = 's';
2703 $special{'*t'} = 't';
2704 $special{'*u'} = 'u';
2705 $special{'*f'} = 'ph';
2706 $special{'*x'} = 'x';
2707 $special{'*q'} = 'ps';
2708 $special{'*w'} = 'o';
2709 $special{'*A'} = 'A';
2710 $special{'*B'} = 'B';
2711 $special{'*G'} = '|\\u_\\d';
2712 $special{'*D'} = '/\';
2713 $special{'*E'} = 'E';
2714 $special{'*Z'} = 'Z';
2715 $special{'*Y'} = 'H';
2716 $special{'*H'} = 'TH';
2717 $special{'*I'} = 'I';
2718 $special{'*K'} = 'K';
2719 $special{'*L'} = 'L';
2720 $special{'*M'} = 'M';
2721 $special{'*N'} = 'N';
2722 $special{'*C'} = 'Z';
2723 $special{'*O'} = 'O';
2724 $special{'*P'} = '||';
2725 $special{'*R'} = 'P';
2726 $special{'*S'} = 'S';
2727 $special{'*T'} = 'T';
2728 $special{'*U'} = 'Y';
2729 $special{'*F'} = 'PH';
2730 $special{'*X'} = 'X';
2731 $special{'*Q'} = 'PS';
2732 $special{'*W'} = 'O';
2733 $special{'ts'} = 's';
2734 $special{'sr'} = 'v/';
2735 $special{'rn'} = '\\u–\\d'; # was 175
2736 $special{'>='} = '>=';
2737 $special{'<='} = '<=';
2738 $special{'=='} = '==';
2739 $special{'~='} = '~=';
2740 $special{'ap'} = '~'; # was ~
2741 $special{'!='} = '!=';
2742 $special{'->'} = '->';
2743 $special{'<-'} = '<-';
2744 $special{'ua'} = '^';
2745 $special{'da'} = 'v';
2746 $special{'mu'} = '×';
2747 $special{'di'} = '÷';
2748 $special{'+-'} = '±';
2749 $special{'cu'} = 'U';
2750 $special{'ca'} = '^';
2751 $special{'sb'} = '(';
2752 $special{'sp'} = ')';
2753 $special{'ib'} = '(=';
2754 $special{'ip'} = '=)';
2755 $special{'if'} = 'oo';
2756 $special{'pd'} = '6';
2757 $special{'gr'} = 'V';
2758 $special{'no'} = '¬';
2759 $special{'is'} = 'I';
2760 $special{'pt'} = '~';
2761 $special{'es'} = 'Ø';
2762 $special{'mo'} = 'e';
2763 $special{'br'} = '|';
2764 $special{'dd'} = '‡'; # was 165, yen
2765 $special{'rh'} = '=>';
2766 $special{'lh'} = '<=';
2767 $special{'or'} = '|';
2768 $special{'ci'} = 'O';
2769 $special{'lt'} = '(';
2770 $special{'lb'} = '(';
2771 $special{'rt'} = ')';
2772 $special{'rb'} = ')';
2773 $special{'lk'} = '|';
2774 $special{'rk'} = '|';
2775 $special{'bv'} = '|';
2776 $special{'lf'} = '|';
2777 $special{'rf'} = '|';
2778 $special{'lc'} = '|';
2779 $special{'rc'} = '|';
2781 # Not true troff characters but very common typos
2782 $special{'cp'} = '©';
2783 $special{'tm'} = '®';
2784 $special{'en'} = '-';
2786 # Build a list of directories containing man pages
2788 if (open(MPC, "/etc/manpath.config") || open(MPC, "/etc/man.config"))
2792 if (m/^(MANDB_MAP|MANPATH)\s+(\S+)/)
2798 @manpath = split(/:/, $ENV{'MANPATH'}) unless (@manpath);
2799 @manpath = ("/usr/man") unless (@manpath);
2802 # Search through @manpath and construct @mandirs (non-empty subsections)
2805 return if (@mandirs);
2806 print STDERR "Searching ",join(":", @manpath)," for mandirs\n" unless($cgiMode);
2807 foreach $tld (@manpath)
2810 $tld = $1; # untaint manpath
2811 if (opendir(DIR, $tld))
2813 # foreach $d (<$tld/man[0-9a-z]*>)
2814 foreach $d (sort readdir(DIR))
2816 if ($d =~ m/^man\w/ && -d "$tld/$d")
2818 push (@mandirs, "$tld/$d");
2826 ##### Utility to search manpath for a given command #####
2832 @multipleMatches = ();
2835 return $file if (-f $file || -f "$file.gz" || -f "$file.bz2");
2837 # Search the path for the requested man page, which may be of the form:
2838 # "/usr/man/man1/ls.1", "ls.1" or "ls".
2839 ($page,$sect) = ($request =~ m/^(.+)\.([^.]+)$/);
2842 # Search the specified section first (if specified)
2845 foreach $md (@manpath)
2848 $file = "$dir/man$sect/$page.$sect";
2849 push(@multipleMatches, $file) if (-f $file || -f "$file.gz" || -f "$file.bz2");
2856 if (@multipleMatches == 1)
2858 return pop @multipleMatches;
2861 # If not found need to search through each directory
2863 foreach $dir (@mandirs)
2865 ($s) = ($dir =~ m/man([0-9A-Za-z]+)$/);
2866 $file = "$dir/$page.$s";
2867 push(@multipleMatches, $file) if (-f $file || -f "$file.gz" || -f "$file.bz2");
2868 $file = "$dir/$request";
2869 push(@multipleMatches, $file) if (-f $file || -f "$file.gz" || -f "$file.bz2");
2870 if ($sect && "$page.$sect" ne $request)
2872 $file = "$dir/$page.$sect";
2873 push(@multipleMatches, $file) if (-f $file || -f "$file.gz" || -f "$file.bz2");
2876 if (@multipleMatches == 1)
2878 return pop @multipleMatches;
2880 if (@multipleMatches > 1)
2884 # Ok, didn't find it using section numbers. Perhaps there's a page with the
2885 # right name but wrong section number lurking there somewhere. (This search is slow)
2886 # eg. page.1x in man1 (not man1x) directory
2887 foreach $dir (@mandirs)
2890 foreach $f (readdir DIR)
2892 if ($f =~ m/^$page\./)
2894 $f =~ s/\.(gz|bz2)$//;
2895 push(@multipleMatches, "$dir/$f");
2899 if (@multipleMatches == 1)
2901 return pop @multipleMatches;
2908 my ($dir,$f,$name,@files);
2910 return if (%perlPages);
2911 foreach $dir (@mandirs)
2913 if (opendir(DIR, $dir))
2915 @files = sort readdir DIR;
2918 next if ($f eq "." || $f eq ".." || $f !~ m/\./);
2919 next unless ("$dir/$f" =~ m/perl/);
2920 $f =~ s/\.(gz|bz2)$//;
2921 ($name) = ($f =~ m,(.+)\.[^.]*$,);
2922 $perlPages{$name} = "$dir/$f";
2927 delete $perlPages{'perl'}; # too ubiquitous to be useful
2933 my @days = qw (Sun Mon Tue Wed Thu Fri Sat);
2934 my @months = qw (Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
2935 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$istdst) = localtime($time);
2936 return sprintf ("%s, %02d %s %4d %02d:%02d:%02d GMT",
2937 $days[$wday],$mday,$months[$mon],1900+$year,$hour,$min,$sec);