3 # manServer - Unix man page to HTML converter
4 # Rolf Howarth, rolf@squarebox.co.uk
5 # Version 1.07 16 July 2001
8 $manServerUrl = "<A HREF=\"http://www.squarebox.co.uk/download/manServer.shtml\">manServer $version</A>";
12 $ENV{'PATH'} = "/bin:/usr/bin";
15 $request = shift @ARGV;
16 # Usage: manServer [-dn] filename | manServer [-s port]
20 $bodyTag = "BODY bgcolor=#F0F0F0 text=#000000 link=#0000ff vlink=#C000C0 alink=#ff0000";
22 if ($ENV{'GATEWAY_INTERFACE'} ne "")
25 open(LOG, ">>/tmp/manServer.log");
26 chmod(0666, '/tmp/manServer.log');
27 $root = $ENV{'SCRIPT_NAME'};
28 $url = $ENV{'PATH_INFO'};
29 if ($ENV{'REQUEST_METHOD'} eq "POST")
30 { $args = <STDIN>; chop $args; }
32 { $args = $ENV{'QUERY_STRING'}; }
33 $url .= "?".$args if ($args);
35 $date = &fmtTime(time);
36 $remoteHost = $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'};
37 $referer = $ENV{'HTTP_REFERER'};
38 $userAgent = $ENV{'HTTP_USER_AGENT'};
39 print LOG "$date\t$remoteHost\t$url\t$referer\t$userAgent\n";
42 elsif ($request eq "-s" || $request eq "")
50 if ($request =~ m/^-d(\d)/)
53 $request = shift @ARGV;
57 $file = findPage($request);
64 ##### Mini HTTP Server ####
69 $port = 8888 unless $port;
71 $sockaddr = 'S n a4 x8';
73 ($name, $aliases, $proto) = getprotobyname('tcp');
74 ($name, $aliases, $port) = getservbyname($port, 'tcp')
75 unless $port =~ /^\d+$/;
79 $this = pack($sockaddr, AF_INET, $port, "\0\0\0\0");
81 select(NS); $| = 1; select(stdout);
83 socket(S, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
90 print STDERR "Failed to bind to port $port: $!\n";
95 listen(S, 5) || die "connect: $!";
97 select(S); $| = 1; select(stdout);
101 print LOG "Waiting for connection on port $port\n";
102 ($addr = accept(NS,S)) || die $!;
103 #print "accept ok\n";
105 ($af,$rport,$inetaddr) = unpack($sockaddr,$addr);
106 @inetaddr = unpack('C4',$inetaddr);
107 print LOG "Got connection from ", join(".",@inetaddr), "\n";
111 if (m/^GET (\S+)/) { $url = $1; }
115 processRequest($url);
124 print LOG "Request = $url, root = $root\n";
126 if ( ($url =~ m/^([^?]*)\?(.*)$/) || ($url =~ m/^([^&]*)&(.*)$/) )
137 @params = split(/[=&]/, $args);
138 for ($i=0; $i<=$#params; ++$i)
140 $params[$i] =~ tr/+/ /;
141 $params[$i] =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack("C",hex($1))/eg;
145 $request = $params{'q'} if ($params{'q'});
146 $searchType = $params{'t'};
147 $debug = $params{'d'};
154 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
155 print OUT "Content-type: text/html\n\n";
156 print OUT "<H1>Searching not yet implemented</H1>\n";
157 print LOG "Searching not implemented\n";
160 elsif ($request eq "/" || $request eq "")
162 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
163 print OUT "Content-type: text/html\n\n";
164 print LOG "Home page\n";
168 elsif ($request =~ m,^/.*/$,)
170 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
171 print OUT "Content-type: text/html\n\n";
172 print LOG "List directory\n";
176 elsif (-f $request || -f "$request.gz" || -f "$request.bz2")
178 # Only allow fully specified files if they're in our manpath
179 foreach $md (@manpath)
182 if (substr($request,0,length($dir)) eq $dir)
184 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
185 print OUT "Content-type: text/html\n\n";
194 $file = findPage($request);
195 if (@multipleMatches)
197 print OUT "HTTP/1.0 200 Ok\n" unless ($cgiMode);
198 print OUT "Content-type: text/html\n\n";
199 print LOG "Multiple matches\n";
205 print OUT "HTTP/1.0 301 Redirected\n" unless ($cgiMode);
206 $file .= "&d=$debug" if ($debug);
207 print OUT "Location: $root$file\n\n";
208 print LOG "Redirect to $root$file\n";
215 print OUT "HTTP/1.0 404 Not Found\n" unless ($cgiMode);
216 print OUT "Content-type: text/html\n\n";
217 print OUT "<HTML><HEAD>\n<TITLE>Not Found</TITLE>\n<$bodyTag>\n";
218 print OUT "<CENTER><H1><HR>Not Found<HR></H1></CENTER>\nFailed to find man page /$request\n";
219 print OUT "<P><HR><P><A HREF=\"$root/\">Main Index</A>\n</HTML>\n";
220 print STDERR "Failed to find /$request\n" unless ($cgiMode);
226 print OUT "<HTML><HEAD><TITLE>Manual Pages - Main Index</TITLE>
227 </HEAD><$bodyTag><CENTER><H1><HR><I>Manual Reference Pages</I> - Main Index<HR></H1></CENTER>
228 <FORM ACTION=\"$root/\" METHOD=get>\n";
229 $uname = `uname -s -r`;
232 $hostname = `hostname`;
233 print OUT "<B>$uname pages on $hostname</B><P>\n";
235 # print OUT "<SELECT name=t> <OPTION selected value=0>Command name
236 # <OPTION value=1>Keyword search <OPTION value=2>Full text search</SELECT>\n";
237 print OUT "Command name: <INPUT name=q size=20> <INPUT type=submit value=\"Show Page\"> </FORM><P>\n";
239 foreach $dir (@mandirs)
241 ($section) = ($dir =~ m/man([0-9A-Za-z]+)$/);
242 print OUT "<A HREF=\"$root$dir/\">$dir" ;
243 print OUT "- <I>$sectionName{$section}</I>" if ($sectionName{$section});
244 print OUT "</A><BR>\n";
246 print OUT "<P><HR><P><FONT SIZE=-1>Generated by $manServerUrl from local unix man pages.</FONT>\n</BODY></HTML>\n";
251 foreach $md (@manpath)
254 if (substr($request,0,length($dir)) eq $dir)
257 ($section) = ($request =~ m/man([0-9A-Za-z]+)$/);
258 $sectionName = $sectionName{$section};
259 $sectionName = "Manual Reference Pages" unless ($sectionName);
260 print OUT "<HTML><HEAD><TITLE>Contents of $request</TITLE></HEAD>\n<$bodyTag>\n";
261 print OUT "<CENTER><H1><HR><NOBR><I>$sectionName</I></NOBR> - <NOBR>Index of $request</NOBR><HR></H1></CENTER>\n";
262 print OUT "<FORM ACTION=\"$root/\" METHOD=get>\n";
263 print OUT "Command name: <INPUT name=q size=20> <INPUT type=submit value=\"Show Page\"> </FORM><P>\n";
265 if (opendir(DIR, $request))
267 @files = sort readdir DIR;
270 next if ($f eq "." || $f eq ".." || $f !~ m/\./);
271 $f =~ s/\.(gz|bz2)$//;
272 # ($name) = ($f =~ m,/([^/]*)$,);
273 print OUT "<A HREF=\"$root$request/$f\">$f</A> \n";
277 print OUT "<P><A HREF=\"$root/\">Main Index</A>\n</HTML>\n";
278 print OUT "<P><HR><P><FONT SIZE=-1>Generated by $manServerUrl from local unix man pages.</FONT>\n</BODY></HTML>\n";
282 print OUT "<H1>Directory $request not known</H1>\n";
287 print OUT "<HTML><HEAD><TITLE>Ambiguous Request '$request'</TITLE></HEAD>\n<$bodyTag>\n";
288 print OUT "<CENTER><H1><HR>Ambiguous Request '$request'<HR></H1></CENTER>\nPlease select one of the following pages:<P><BLOCKQUOTE>";
289 foreach $f (@multipleMatches)
291 print OUT "<A HREF=\"$root$f\">$f</A><BR>\n";
293 print OUT "</BLOCKQUOTE><HR><P><A HREF=\"$root/\">Main Index</A>\n</HTML>\n";
297 ##### Process troff input using man macros into HTML #####
309 $zcat = "/usr/bin/zcat";
310 $zcat = "/bin/zcat" unless (-x $zcat);
311 $srcfile = "$zcat $zfile |";
312 $srcfile =~ m/^(.*)$/;
313 $srcfile = $1; # untaint
315 elsif (-f "$file.bz2")
317 $zfile = "$file.bz2";
318 $srcfile = "/usr/bin/bzcat $zfile |";
319 $srcfile =~ m/^(.*)$/;
320 $srcfile = $1; # untaint
323 print LOG "man2html $file\n";
326 unless (open(SRC, $srcfile))
328 print OUT "<H1>Failed to open $file</H1>\n";
329 print STDERR "Failed to open $srcfile\n";
332 ($dir,$page,$sect) = ($file =~ m,^(.*)/([^/]+)\.([^.]+)$,);
340 $prevailingIndent = 6;
351 $title = "Manual Page - $page($sect)" if ($page && $sect);
354 if (m/^.so (man.*)$/)
356 # An .so include on the first line only is replaced by the referenced page.
357 # (See elsewhere for processing of included sections that occur later in document.)
358 man2html("$dir/../$1");
363 if ($file =~ m/perl/)
366 $perlPattern = join('|', grep($_ ne $page, keys %perlPages));
369 print OUT "<HTML><HEAD>\n<TITLE>$title</TITLE>\n<$bodyTag><A NAME=top></A>\n";
384 # Special case where input is not nroff at all but is preformatted text
385 $sectionName = "Manual Reference Pages";
386 $sectionNumber = $sect;
387 $left = "Manual Page";
388 $right = "Manual Page";
389 $macroPackage = "(preformatted text)";
390 $pageName = "$page($sect)";
391 $saveCurrentLine = $_;
393 $_ = $saveCurrentLine;
400 print OUT "</PRE>\n";
407 plainOutput( "<CENTER>\n" );
408 outputLine( "<H1><HR><I>$sectionName - </I><NOBR>$pageName</NOBR><HR></H1>\n" );
409 plainOutput( "</CENTER>\n" );
416 unless ($cmdLineMode)
418 plainOutput( "<FORM ACTION=\"$root/\" METHOD=get>\n" );
419 plainOutput( "Jump to page <INPUT name=q size=12> or go to <A HREF=#top>Top of page</A> | \n" );
420 plainOutput( "<A HREF=\"$root$dir/\">Section $sectionNumber</A> | \n" );
421 plainOutput( "<A HREF=\"$root/\">Main Index</A>.\n" );
422 plainOutput( "<FORM>\n" );
425 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>");
427 plainOutput("<FONT SIZE=-1>Generated by $manServerUrl from $zfile $macroPackage.</FONT>\n</BODY></HTML>\n");
432 print OUT "<A name=contents></A><H3>CONTENTS</H3></A>\n";
434 for ($id=1; $id<=$#contents; ++$id)
436 $name = $contents[$id];
438 $pre = " " if ($name =~ m/^ /);
439 $pre .= " " if ($name =~ m/^ /);
441 next if ($name eq "" || $name =~ m,^/,);
442 unless ($name =~ m/[a-z]/)
445 $name =~ s/ (.)/ \u\1/g;
447 outputLine("$pre<A HREF=#$id>$name</A><BR>\n");
452 # First pass to extract table of contents
457 # print STDERR "SRCFILE = $srcfile\n";
458 open(SRC, $srcfile) || return;
462 $foundNroffTag = $foundNroffTag || (m/^\.(\\\"|TH|so) /);
463 if (m/^\.(S[HShs]) ([A-Z].*)\s*$/)
470 if ($c eq "SH" || $c eq "Sh")
477 push(@contents, " $t");
481 push(@contents, " $t");
483 $contents{"\U$t"} = $id;
492 # Remove spurious white space to canonicise the input
496 s,^',.,; # treat non breaking requests as if there was a dot
503 s,^(.*?)$eqnEnd,&processEqnd($1),e;
511 if ($eqnStart && $eqnMode==0)
513 s,$eqnStart(.*?)$eqnEnd,&processEqnd($1),ge;
516 s,$eqnStart(.*)$,&processEqns($1),e;
521 # XXX Note: multiple levels of escaping aren't handled properly, eg. \\*.. as a macro argument
522 # should get interpolated as string but ends up with a literal '\' being copied through to output.
523 s,\\\\\*q,",g; # treat mdoc \\*q as special case
530 # Then apply any variable substitutions and escape < and >
531 # (which has to be done before we start inserting tags...)
532 s,\\\*\((..),$vars{$1},ge;
533 s/\\\*([*'`,^,:~].)/$vars{$1}||"\\*$1"/ge;
534 s,\\\*(.),$vars{$1},ge;
535 # Expand special characters for the first time (eg. \(<-
536 s,\\\((..),$special{$1}||"\\($1",ge;
540 # Interpolate width and number registers
541 s,\\w(.)(.*?)\1,&width($2),ge;
542 s,\\n\((..),&numreg($1),ge;
543 s,\\n(.),&numreg($1),ge;
546 # Undo slash escaping, normally done at output stage, also in macro defn
553 # Rewrite the line, expanding escapes such as font styles, and output it.
554 # The line may be a plain text troff line, or it might be the expanded output of a
555 # macro in which case some HTML tags may already have been inserted into the text.
560 print OUT "<!-- Output: \"$_\" -->\n" if ($debug>1);
564 plainOutput("<!-- Need break --><BR>\n");
567 if ($textSinceBreak && !$noFill && $_ =~ m/^\s/)
569 plainOutput("<BR>\n");
573 s,\\&\.,.,g; # \&. often used to escape dot at start of line
589 # Can't implement local motion tags
593 # Font changes, super/sub-scripts and font size changes
594 s,\\(f[^(]|f\(..|u|d|s[-+]?\d),&inlineStyle($1),ge;
599 # handle a few special accent cases we know how to deal with
600 s,\\o(.)([aouAOU])"\1,\\o\1\2:\1,g;
601 s,\\o(.)(.)\\(.)\1,\\o\1\2\3\1,g;
602 s;\\o(.)([A-Za-z])(['`:,^~])\1;\\o\1\3\2\1;g;
603 #s,\\o(.)(.*?)\1,"<BLINK>".($vars{$2}||$2)."</BLINK>",ge;
604 s,\\o(.)(.*?)\1,$vars{$2}||$2,ge;
606 # Bracket building (ignore)
607 s,\\b(.)(.*?)\1,\2,g;
614 # Expand special characters introduced by eqn
615 s,\\\((..),$special{$1}||"\\($1",ge;
616 s,\\\((..),<BLINK>\\($1</BLINK>,g unless (m,^\.,);
618 # Don't know how to handle other escapes
619 s,(\\[^&]),<BLINK>\1</BLINK>,g unless (m,^\.,);
623 # Insert links for http, ftp and mailto URLs
624 # Recognised URLs are sequence of alphanumerics and special chars like / and ~
625 # but must finish with an alphanumeric rather than punctuation like "."
626 s,\b(http://[-\w/~:@.%#+$?=]+\w),<A HREF=\"\1\">\1</A>,g;
627 s,\b(ftp://[-\w/~:@.%#+$?=]+),<A HREF=\"\1\">\1</A>,g;
628 s,([-_A-Za-z0-9.]+@[A-Za-z][-_A-Za-z0-9]*\.[-_A-Za-z0-9.]+),<A HREF=\"mailto:\1\">\1</A>,g;
630 # special case for things like 'perlre' as it's so useful but the
631 # pod-generated pages aren't very parser friendly...
632 if ($perlPattern && ! m/<A HREF/i)
634 s,\b($perlPattern)\b,<A HREF=\"$root$perlPages{$1}\">\1</A>,g;
637 # Do this late so \& can be used to suppress conversion of URLs etc.
640 # replace tabs with spaces to next multiple of 8
644 $tmp =~ s/<[^>]*>//g;
645 $tmp =~ s/&[^;]*;/@/g;
646 @tmp = split(/\t/, $tmp);
648 for ($i=0; $i<=$#tmp; ++$i)
650 $pos += length($tmp[$i]);
652 $tab[$i] = 8 - $pos%8 unless (@tabstops);
653 foreach $ts (@tabstops)
665 s,\t," " x (shift @tab),e;
669 $textSinceBreak = $_ unless ($textSinceBreak);
673 # Output a line consisting purely of HTML tags which shouldn't be regarded as
674 # a troff output line.
681 # Output the original line for debugging
684 print OUT "<!-- $origLine -->\n";
687 # Use this to read the next input line (buffered to implement lookahead)
693 $_ = shift @lookahead;
699 # Look ahead to peek at the next input line
702 # set lookaheadPtr to 0 to re-read the lines we've looked ahead at
703 if ($lookaheadPtr>=0 && $lookaheadPtr <= $#lookahead)
705 return $lookahead[$lookaheadPtr++];
709 push(@lookahead, $ll);
713 # Consume the last line that was returned by lookahead
717 if ($lookaheadPtr>=0 && $lookaheadPtr <= $#lookahead)
719 $removed = $lookahead[$lookaheadPtr];
720 @lookahead = (@lookahead[0..$lookaheadPtr-1],@lookahead[$lookaheadPtr+1..$#lookahead]);
724 $removed = pop @lookahead;
727 plainOutput("<!-- Consumed $removed -->\n");
730 # Look ahead skipping comments and other common non-text tags
734 while ($ll =~ m/^\.(\\"|PD|IX|ns)/)
741 # Process $_, expaning any macros into HTML and calling outputLine().
742 # If necessary, this method can read more lines of input from <SRC> (.ig & .de)
743 # The following state variables are used:
747 $doneLine = 1; # By default, this counts as a line for trap purposes
750 s,^\.el ,,; # conditions assumed to evaluate false, so else must be true...
756 elsif ($eqnMode == 2)
758 plainOutput("<!-- $_ -->\n");
771 # Called after processing (most) input lines to decrement trapLine. This is needed
772 # to implement the .it 1 trap after one line for .TP, where the first line is outdented
785 # Process plain text lines
791 plainOutput("<P>\n");
795 s,(\\f[23BI])([A-Z].*?)(\\f.),$1.($contents{"\U$2"}?"<A HREF=#".$contents{"\U$2"}.">$2</A>":$2).$3,ge;
797 if ($currentSection eq "SEE ALSO" && ! $cmdLineMode)
799 # Some people don't use BR or IR for see also refs
800 s,(^|\s)([-.A-Za-z_0-9]+)\s?\(([0-9lL][0-9a-zA-Z]*)\),\1<A HREF=\"$root/$2.$3\">$2($3)</A>,g;
806 # Process macros and built-in directives
811 # Place macro arguments (space delimited unless within ") into @p
812 # Remove " from $_, place command in $c, remainder in $joined
814 @p = grep($_ !~ m/^\s*$/, split(/("[^"]*"|\s+)/) );
819 $joined = join(" ", @p[1..$#p]);
820 $joined2 = join(" ", @p[2..$#p]);
821 $joined3 = join(" ", @p[3..$#p]);
823 if ($macro{$c}) # Expand macro
825 # Get full macro text
827 # Interpolate arguments
828 $macro =~ s,\\\$(\d),$p[$1],ge;
829 #print OUT "<!-- Expanding $c to\n$macro-->\n";
830 foreach $_ (split(/\n/, $macro))
839 elsif ($renamedMacro{$c})
841 $c = $renamedMacro{$c};
844 if ($c eq "ds") # Define string
846 $vars{$p[1]} = $joined2;
849 elsif ($c eq "nr") # Define number register
851 $number{$p[1]} = evalnum($joined2);
854 elsif ($c eq "ti") # Temporary indent
856 plainOutput(" ");
861 if ($macro{$macroName})
863 delete $macro{$macroName};
867 $deletedMacro{$macroName} = 1;
874 $macro = $macro{$oldName};
877 if ($newName =~ $reservedMacros && ! $deletedMacro{$newName})
879 plainOutput("<!-- Not overwriting reserved macro '$newName' -->\n");
883 $macro{$newName} = $macro;
884 delete $deletedMacro{$newName};
886 delete $macro{$oldName};
890 # Support renaming of reserved macros by mapping occurrences of new name
891 # to old name after macro expansion so that built in definition is still
892 # available, also mark the name as deleted to override reservedMacro checks.
893 plainOutput("<!-- Fake renaming reserved macro '$oldName' -->\n");
894 $renamedMacro{$newName} = $oldName;
895 $deletedMacro{$oldName} = 1;
898 elsif ($c eq "de" || $c eq "ig") # Define macro or ignore
902 { $delim = ".$p[1]"; }
904 { $delim = ".$p[2]"; }
905 $delim = ".." if ($delim eq ".");
906 # plainOutput("<!-- Scanning for delimiter $delim -->\n");
921 # plainOutput("<!-- Found delimiter -->\n");
924 if ($macroName =~ $reservedMacros && ! $deletedMacro{$macroName})
926 plainOutput("<!-- Not defining reserved macro '$macroName' ! -->\n");
930 $macro{$macroName} = $macro;
931 delete $deletedMacro{$macroName};
935 elsif ($c eq "so") # Source
937 plainOutput("<P>[<A HREF=\"$root$dir/../$p[1]\">Include document $p[1]</A>]<P>\n");
939 elsif ($c eq "TH" || $c eq "Dt") # Man page title
942 $sectionNumber = $p[2];
943 $sectionName = $sectionName{"\L$sectionNumber"};
944 $sectionName = "Manual Reference Pages" unless ($sectionName);
945 $pageName = "$p[1] ($sectionNumber)";
951 $left = $osver unless ($left);
952 $macroPackage = "using man macros";
956 $macroPackage = "using doc macros";
961 outputLine("- $joined\n");
963 elsif ($c eq "SH" || $c eq "SS" || $c eq "Sh" || $c eq "Ss") # Section/subsection
968 $id = $contents{"\U$joined"};
969 $currentSection = $joined;
971 if ($c eq "SH" || $c eq "Sh")
974 if ($firstSection++==1) # after first 'Name' section
978 outputLine( "<A name=$id>\n\n <H3>$joined</H3>\n\n</A>\n" );
981 elsif ($joined =~ m/\\f/)
983 $joined =~ s/\\f.//g;
984 $id = $contents{"\U$joined"};
985 outputLine( "<A name=$id>\n<H4><I>$joined</I></H4></A>\n" );
990 outputLine( "<A name=$id>\n\n <H4> $joined</H4>\n</A>\n" );
995 elsif ($c eq "TX" || $c eq "TZ") # Document reference
997 $title = $title{$p[1]};
998 $title = "Document [$p[1]]" unless ($title);
999 outputLine( "\\fI$title\\fP$joined2\n" );
1001 elsif ($c eq "PD") # Line spacing
1003 $noSpace = ($p[1] eq "0");
1006 elsif ($c eq "TS") # Table start
1008 unless ($macroPackage =~ /tbl/)
1010 if ($macroPackage =~ /eqn/)
1011 { $macroPackage =~ s/eqn/eqn & tbl/; }
1013 { $macroPackage .= " with tbl support"; }
1018 $troffSeparator = "\t";
1019 plainOutput( "<P><BLOCKQUOTE><TABLE bgcolor=#E0E0E0 border=1 cellspacing=0 cellpadding=3>\n" );
1021 elsif ($c eq "EQ") # Eqn start
1023 unless ($macroPackage =~ /eqn/)
1025 if ($macroPackage =~ /tbl/)
1026 { $macroPackage =~ s/tbl/tbl & eqn/; }
1028 { $macroPackage .= " with eqn support"; }
1032 elsif ($c eq "ps") # Point size
1034 plainOutput(&sizeChange($p[1]));
1036 elsif ($c eq "ft") # Font change
1038 plainOutput(&fontChange($p[1]));
1040 elsif ($c eq "I" || $c eq "B") # Single word font change
1042 $id = $contents{"\U$joined"};
1043 if ($id && $joined =~ m/^[A-Z]/)
1044 { $joined = "<A HREF=#$id>$joined</A>"; }
1045 outputLine( "\\f$c$joined\\fP " );
1046 plainOutput("\n") if ($noFill);
1048 elsif ($c eq "SM") # Single word smaller
1050 outputLine("\\s-1$joined\\s0 ");
1051 $doneLine = 0 unless ($joined);
1053 elsif ($c eq "SB") # Single word bold and small
1055 outputLine("\\fB\\s-1$joined\\s0\\fP ");
1057 elsif (m/^\.[BI]R (\S+)\s?\(\s?([0-9lL][0-9a-zA-Z]*)\s?\)(.*)$/)
1059 # Special form, .BR is generally used for references to other pages
1060 # Annoyingly, some people have more than one per line...
1061 # Also, some people use .IR ...
1062 for ($i=1; $i<=$#p; $i+=2)
1064 $pair = $p[$i]." ".$p[$i+1];
1065 if ($p[$i+1] eq "(")
1067 $pair .= $p[$i+2].$p[$i+3];
1070 if ($pair =~ m/^(\S+)\s?\(\s?([0-9lL][0-9a-zA-Z]*)\s?\)(.*)$/)
1073 { outputLine( "\\fB$1\\fR($2)$3\n" ); }
1075 { outputLine( "<A HREF=\"$root/$1.$2\">$1($2)</A>$3\n" ); }
1078 { outputLine( "$pair\n" ); }
1081 elsif ($c eq "BR" || $c eq "BI" || $c eq "IB" ||
1082 $c eq "IR" || $c eq "RI" || $c eq "RB")
1084 $f1 = (substr($c ,0,1));
1085 $f2 = (substr($c,1,1));
1087 # Check if first param happens to be a section name
1088 $id = $contents{"\U$p[1]"};
1089 if ($id && $p[1] =~ m/^[A-Z]/)
1091 $p[1] = "<A HREF=#$id>$p[1]</A>";
1094 for ($i=1; $i<=$#p; ++$i)
1096 $f = ($i%2 == 1) ? $f1 : $f2;
1097 outputLine("\\f$f$p[$i]");
1099 outputLine("\\fP ");
1100 plainOutput("\n") if ($noFill);
1102 elsif ($c eq "nf" || $c eq "Bd") # No fill
1106 elsif ($c eq "fi" || $c eq "Ed") # Fill
1112 $indent = evalnum($p[1]);
1115 plainOutput("<BR>\n");
1119 # Outdent first line, ie. until next break
1121 $trapAction = *trapHP;
1122 newParagraph($indent);
1123 plainOutput( "<TD colspan=2>\n" );
1131 $indent = evalnum($p[2]);
1132 newParagraph($indent);
1133 outputLine("<TD$width>\n$tag\n</TD><TD>\n");
1140 $trapLine = 1; # Next line is tag, then next column
1141 $doneLine = 0; # (But don't count this line)
1142 $trapAction = *trapTP;
1143 $indent = evalnum($p[1]);
1146 $i = ($indent ? $indent : $prevailingIndent) ;
1150 plainOutput("<!-- Length of tag '$tag' ($w) > indent ($i) -->\n") if ($debug);
1151 newParagraph($indent);
1152 $trapAction = *trapHP;
1153 plainOutput( "<TD colspan=2>\n" );
1158 newParagraph($indent);
1159 plainOutput( "<TD$width nowrap>\n" );
1162 $body = lookahead();
1164 if ($body =~ m/^\.[HILP]?P/)
1167 plainOutput("<!-- Suppressing TP body due to $body -->\n");
1171 elsif ($c eq "LP" || $c eq "PP" || $c eq "P" || $c eq "Pp") # Paragraph
1174 $prevailingIndent = 6;
1175 if ($indent[$indentLevel] > 0 && $docListStyle eq "")
1177 $line = lookahead();
1178 if ($line =~ m/^\.(TP|IP|HP)/)
1180 plainOutput("<!-- suppressed $c before $1 -->\n");
1182 elsif ($line =~ m/^\.RS/)
1184 plainOutput("<P>\n");
1193 $line = lookahead();
1194 if ($line =~ m/^\.(TP|HP|IP|RS)( \d+)?/)
1197 $indent = $prevailingIndent unless ($2);
1198 if ($indent == $indent[$indentLevel])
1203 while ($line ne "" && $line !~ m/^\.(RE|SH|SS|PD)/);
1207 plainOutput("<!-- Found tag $foundTag -->\n");
1208 plainOutput("<TR><TD colspan=2>\n");
1213 plainOutput("<!-- $c ends table -->\n");
1220 plainOutput("<P>\n");
1224 elsif ($c eq "br") # Break
1228 # Should this apply to all macros that cause a break?
1232 $needBreak = 1 if ($textSinceBreak);
1234 elsif ($c eq "sp") # Space
1237 plainOutput("<P>\n");
1239 elsif ($c eq "RS") # Block indent start
1241 if ($indentLevel==0 && $indent[0]==0)
1248 $indent = $prevailingIndent unless ($indent);
1249 if ($indent > $indent[$indentLevel] && !$extraIndent)
1253 $indent[$indentLevel] = 0;
1254 setIndent($indent-$indent[$indentLevel-1]);
1255 plainOutput("<TR><TD$width> </TD><TD>\n");
1258 elsif ($indent < $indent[$indentLevel] || $colState==2)
1262 plainOutput("<TR><TD$width> </TD><TD>\n");
1266 $indent[$indentLevel] = 0;
1268 $prevailingIndent = 6;
1270 elsif ($c eq "RE") # Block indent end
1279 if ($indentLevel==0)
1284 plainOutput("</BLOCKQUOTE>\n");
1294 $prevailingIndent = $indent[$indentLevel];
1295 $prevailingIndent = 6 unless($prevailingIndent);
1297 elsif ($c eq "DT") # default tabs
1301 elsif ($c eq "ta") # Tab stops
1304 for ($i=0; $i<$#p; ++$i)
1310 $tb = $tabstops[$i-1];
1314 $tabstops[$i] = $tb + $ts;
1316 plainOutput("<!-- Tabstops set at ".join(",", @tabstops)." -->\n") if ($debug);
1318 elsif ($c eq "It") # List item (mdoc)
1321 if ($docListStyle eq "-tag")
1323 endRow() unless($multilineIt);
1326 setIndent($tagWidth);
1331 $width = ""; # let table take care of own width
1335 plainOutput("<TR valign=top><TD colspan=2>");
1339 $tag = &mdocStyle(@p[1..$#p]);
1340 $body = lookahead();
1341 if ($body =~ m/^\.It/)
1342 { $multilineItNext = 1; }
1344 { $multilineItNext = 0; }
1347 outputLine("<BR>\n$tag\n");
1349 elsif ($multilineItNext || $tagWidth>0 && width($tag)>$tagWidth)
1351 outputLine("<TR valign=top><TD colspan=2>$tag\n");
1356 outputLine("<TR valign=top><TD>$tag\n");
1359 if ($multilineItNext)
1367 { plainOutput("</TD></TR><TR><TD> </TD><TD>\n"); }
1369 { plainOutput("</TD><TD>\n"); }
1375 plainOutput("<LI>");
1381 if ($docListStyle eq "-tag")
1383 plainOutput("</TD></TR><TR><TD> </TD><TD>\n");
1386 elsif ($c eq "Bl") # Begin list (mdoc)
1388 push @docListStyles, $docListStyle;
1389 if ($p[1] eq "-enum")
1391 plainOutput("<OL>\n");
1392 $docListStyle = $p[1];
1394 elsif($p[1] eq "-bullet")
1396 plainOutput("<UL>\n");
1397 $docListStyle = $p[1];
1401 $docListStyle = "-tag";
1402 if ($p[2] eq "-width")
1404 $tagWidth = width($p[3]);
1405 if ($tagWidth < 6) { $tagWidth = 6; }
1414 elsif ($c eq "El") # End list
1416 if ($docListStyle eq "-tag")
1421 elsif ($docListStyle eq "-bullet")
1423 plainOutput("</UL>\n");
1427 plainOutput("</OL>\n");
1429 $docListStyle = pop @docListStyles;
1439 elsif ($c eq "Sx") # See section
1441 $id = $contents{"\U$joined"};
1442 if ($id && $joined =~ m/^[A-Z]/)
1444 outputLine("<A HREF=#$id>".&mdocStyle(@p[1..$#p])."</A>\n");
1448 my $x = &mdocStyle(@p[1..$#p]);
1450 outputLine($x."\n");
1453 elsif (&mdocCallable($c))
1455 my $x = &mdocStyle(@p);
1457 outputLine($x."\n");
1461 outputLine("<I>BSD $joined</I>\n");
1465 outputLine("<I>Unix $joined</I>\n");
1469 outputLine("<I>AT&T $joined</I>\n");
1471 elsif ($c =~ m/[A-Z][a-z]/) # Unsupported doc directive
1473 outputLine("<BR>.$c $joined\n");
1475 elsif ($c eq "") # Empty line (eg. troff comment)
1479 else # Unsupported directive
1481 # Unknown macros are ignored, and don't count as a line as far as trapLine goes
1483 plainOutput("<!-- ignored unsupported tag .$c -->\n");
1490 $body = lookahead();
1491 if ($body =~ m/^\.TP/)
1494 $trapLine = 1; # restore TP trap
1495 $doneLine = 0; # don't count this line
1496 plainOutput("<BR>\n");
1500 plainOutput("</TD><TD valign=bottom>\n");
1509 $body = lookahead();
1510 if ($body =~ m/^\.([TH]P)/)
1513 # Restore appropriate type of trap
1517 $doneLine = 0; # don't count this line
1523 plainOutput("<BR>\n");
1527 plainOutput("</TD></TR><TR valign=top><TD$width> </TD><TD>\n");
1543 $indent = $prevailingIndent unless ($indent);
1544 $prevailingIndent = $indent;
1546 plainOutput( "<TR valign=top>" );
1549 # End an existing HP/TP/IP/RS row
1552 if ($indent[$indentLevel] > 0)
1555 plainOutput( "</TD></TR>\n" );
1559 # Called when we output a line break tag. Only needs to be called once if
1560 # calling plainOutput, but should call before and after if using outputLine.
1564 $textSinceBreak = 0;
1567 # Called to reset all indents and pending paragraphs (eg. at the start of
1568 # a new top level section).
1572 while ($indentLevel > 0)
1575 if ($indent[$indentLevel] > 0)
1583 # Interpolate a number register (possibly autoincrementing)
1586 return 0 + $number{$_[0]};
1589 # Evaluate a numeric expression
1593 return "" if ($n eq "");
1594 if ($n =~ m/i$/) # inches
1604 $tsb = $textSinceBreak;
1605 $indent = evalnum($_[0]);
1606 if ($indent==0 && $_[0] !~ m/^0/)
1610 plainOutput("<!-- setIndent $indent, indent[$indentLevel] = $indent[$indentLevel] -->\n") if ($debug);
1611 if ($indent[$indentLevel] != $indent)
1614 if ($indent[$indentLevel] > 0)
1616 plainOutput("<TR></TR>") unless ($noSpace);
1617 plainOutput("</TABLE>");
1623 $border = " border=1" if ($debug>2);
1624 #plainOutput("<P>") unless ($indent[$indentLevel] > 0);
1625 plainOutput("<TABLE$border");
1626 # Netscape bug, makes 2 cols same width? : plainOutput("<TABLE$border COLS=2");
1627 # Overcome some of the vagaries of Netscape tables
1628 plainOutput(" width=100%") if ($indentLevel>0);
1631 plainOutput(" cellpadding=0 cellspacing=0>\n");
1635 plainOutput(" cellpadding=3>".($tsb ? "<!-- tsb: $tsb -->\n<TR></TR><TR></TR>\n" : "\n") );
1637 #$width = " width=".($indent*5); # causes text to be chopped if too big
1639 if ($indentLevel > 0)
1640 { $percent = $indent * 100 / (100-$indentLevel[0]); }
1641 $width = " width=$percent%";
1643 $indent[$indentLevel] = $indent;
1647 # Process mdoc style macros recursively, as one of the macro arguments
1648 # may itself be the name of another macro to invoke.
1651 return "" unless @_;
1652 my ($tag, @param) = @_;
1655 # Don't format trailing punctuation
1656 if ($param[$#param] =~ m/^[.,;:]$/)
1660 if ($param[$#param] =~ m/^[)\]]$/)
1662 $term = (pop @param).$term;
1665 if ($param[0] =~ m,\\\\,)
1667 print STDERR "$tag: ",join(",", @param),"\n";
1669 $rest = &mdocStyle(@param);
1673 $rest =~ s/ //; # remove first space
1674 return " \\fP[$rest]$term";
1676 elsif ($tag eq "Xr") # cross reference
1678 my $p = shift @param;
1682 $url .= ".".$param[0];
1683 $rest = "(".$param[0].")";
1687 $rest = &mdocStyle(@param);
1691 return " <B>".$p."</B>".$rest.$term;
1695 return " <A HREF=\"".$root."/".$url."\">".$p."</A>".$rest.$term;
1698 elsif ($tag eq "Fl")
1704 if ($f eq "Ns") # no space
1708 elsif (&mdocCallable($f))
1711 return $sofar.&mdocStyle(@param).$term;
1715 $sofar .= "-<B>$f</B> "
1718 return $sofar.$term;
1720 elsif ($tag eq "Pa" || $tag eq "Er" || $tag eq "Fn" || $tag eq "Dv")
1722 return "\\fC$rest\\fP$term";
1724 elsif ($tag eq "Ad" || $tag eq "Ar" || $tag eq "Em" || $tag eq "Fa" || $tag eq "St" ||
1725 $tag eq "Ft" || $tag eq "Va" || $tag eq "Ev" || $tag eq "Tn" || $tag eq "%T")
1727 return "\\fI$rest\\fP$term";
1729 elsif ($tag eq "Nm")
1731 $defaultNm = $param[0] unless ($defaultNm);
1732 $rest = $defaultNm unless ($param[0]);
1733 return "\\fB$rest\\fP$term";
1735 elsif ($tag eq "Ic" || $tag eq "Cm" || $tag eq "Sy")
1737 return "\\fB$rest\\fP$term";
1739 elsif ($tag eq "Ta") # Tab
1741 # Tabs are used inconsistently so this is the best we can do. Columns won't line up. Tough.
1742 return " $rest$term";
1744 elsif ($tag eq "Ql")
1747 return "`<TT>$rest</TT>'$term";
1749 elsif ($tag eq "Dl")
1751 return "<P> <TT>$rest</TT>$term<P>\n";
1753 elsif ($tag =~ m/^[ABDEOPQS][qoc]$/)
1758 { $lq = "<"; $rq = ">"; }
1759 elsif ($tag =~ m/^B/)
1760 { $lq = "["; $rq = "]"; }
1761 elsif ($tag =~ m/^D/)
1762 { $lq = "\""; $rq = "\""; }
1763 elsif ($tag =~ m/^P/)
1764 { $lq = "("; $rq = ")"; }
1765 elsif ($tag =~ m/^Q/)
1766 { $lq = "\""; $rq = "\""; }
1767 elsif ($tag =~ m/^S/)
1768 { $lq = "\\'"; $rq = "\\'"; }
1769 elsif ($tag =~ m/^O/)
1770 { $lq = "["; $rq = "]"; }
1776 return $lq.$rest.$rq.$term ;
1778 elsif (&mdocCallable($tag)) # but not in list above...
1782 elsif ($tag =~ m/^[.,;:()\[\]]$/) # punctuation
1784 return $tag.$rest.$term;
1786 elsif ($tag eq "Ns")
1792 return " ".$tag.$rest.$term;
1796 # Determine if a macro is mdoc parseable/callable
1799 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])$/);
1803 # Estimate the output width of a string
1806 local($word) = $_[0];
1807 $word =~ s,<[/A-Z][^>]*>,,g; # remove any html tags
1808 $word =~ s/^\.\S+\s//;
1811 $word =~ s/[ ()|.,!;:"']//g; # width of punctuation is about half a character
1812 return ($x + length($word)) / 2;
1815 # Process a tbl table (between TS/TE tags)
1818 if ($troffTable == "1")
1828 $troffSeparator = quotemeta($1) if (m/tab\s*\((.)\)/);
1834 s/^[^lrcan^t]*//; # remove any 'modifiers' coming before tag
1835 # delimit on tags excluding s (viewed as modifier of previous column)
1836 s/([lrcan^t])/\t$1/g;
1838 push @troffRowDefs, $_;
1839 last if ($origLine =~ m/\.\s*$/);
1848 s/$troffSeparator/\t/g;
1854 plainOutput("</TABLE></BLOCKQUOTE>\n");
1862 elsif (m/[_=]/ && m/^[_=\t]*$/ && $troffCol==0)
1867 plainOutput("<TR></TR><TR></TR>\n");
1870 elsif ($troffCol==0 && @troffRowDefs)
1872 # Don't output a row, but this counts as a row as far as row defs go
1873 $rowDef = shift @troffRowDefs;
1874 @troffColDefs = split(/\t/, $rowDef);
1877 elsif (m/^\.sp/ && $troffCol==0 && !$hadUnderscore)
1880 plainOutput("<TR></TR><TR></TR>\n");
1882 elsif ($_ eq ".br" && $troffMultiline)
1884 $rowref->[$troffCol] .= "<BR>\n";
1886 elsif ($_ !~ m/^\./)
1888 $rowref = $tableRows[$#tableRows]; # reference to current row (last row in array)
1889 if ($troffCol==0 && @troffRowDefs)
1891 $rowDef = shift @troffRowDefs;
1892 if ($rowDef =~ m/^[_=]/)
1896 plainOutput("<TR></TR><TR></TR>\n");
1899 $rowDef = shift @troffRowDefs;
1901 @troffColDefs = split(/\t/, $rowDef);
1904 if ($troffCol == 0 && !$troffMultiline)
1907 push(@tableRows, $rowref);
1908 #plainOutput("<TR valign=top>");
1914 $troffMultiline = 0;
1916 if ($troffMultiline)
1918 $rowref->[$troffCol] .= "$_\n";
1922 @columns = split(/\t/, $_);
1923 plainOutput("<!-- Adding (".join(",", @columns)."), type (".join(",", @troffColDefs).") -->\n") if ($debug);
1924 while ($troffCol <= $#troffColDefs && @columns > 0)
1926 $def = $troffColDefs[$troffCol];
1927 $col = shift @columns;
1930 $col = "\\^" if ($col eq "" && $def =~ m/\^/);
1931 $col = " " if ($col eq "");
1936 if ($def =~ m/[bB]/ || $def =~ m/f3/)
1937 { $style1 = "\\fB"; $style2 = "\\fP"; }
1938 if ($def =~ m/I/ || $def =~ m/f2/)
1939 { $style1 = "\\fI"; $style2 = "\\fP"; }
1941 if ($def =~ m/c/) { $align = " align=center"; }
1942 if ($def =~ m/[rn]/) { $align = " align=right"; }
1945 if ($span) { $align.= " colspan=".(length($span)+1); }
1950 $rowref->[$troffCol] .= "$style2</TD>";
1953 elsif ($col =~ m/T{/) #}
1956 $rowref->[$troffCol] = "<TD$align>$style1$col";
1957 $troffMultiline = 1;
1961 $rowref->[$troffCol] = "<TD$align>$style1$col$style2</TD>";
1966 endTblRow() unless ($troffMultiline);
1972 return if ($troffCol == 0);
1973 while ($troffCol <= $#troffColDefs)
1975 $rowref->[$troffCol] = "<TD> </TD>";
1976 #print OUT "<TD> </TD>";
1980 #print OUT "</TR>\n"
1985 plainOutput("<!-- flushTable $#tableRows rows -->\n") if ($debug);
1987 # Treat rows with first cell blank or with more than one vertically
1988 # spanned row as a continuation of the previous line.
1989 # Note this is frequently a useful heuristic but isn't foolproof.
1990 for($r=0; $r<$#tableRows; ++$r)
1993 for ($c=0; $c<=$#{$tableRows[$r+1]}; ++$c)
1994 {++$vspans if ($tableRows[$r+1][$c] =~ m,<TD.*?>\\\^</TD>,);}
1995 if ((($vspans>1) || ($tableRows[$r+1][0] =~ m,<TD.*?> </TD>,)) &&
1996 $#{$tableRows[$r]} == $#{$tableRows[$r+1]} && 0)
2000 plainOutput("<!-- merging row $r+1 into previous -->\n");
2001 plainOutput("<!-- row $r: (".join(",", @{$tableRows[$r]}).") -->\n");
2002 plainOutput("<!-- row $r+1: (".join(",", @{$tableRows[$r+1]}).") -->\n");
2004 for ($c=0; $c<=$#{$tableRows[$r]}; ++$c)
2006 $tableRows[$r][$c] .= $tableRows[$r+1][$c];
2007 $tableRows[$r][$c] =~ s,\\\^,,g; # merging is stronger than spanning!
2008 $tableRows[$r][$c] =~ s,</TD><TD.*?>,<BR>,;
2010 @tableRows = (@tableRows[0..$r], @tableRows[$r+2 .. $#tableRows]);
2011 --$r; # process again
2015 # Turn \^ vertical span requests into rowspan tags
2016 for($r=0; $r<$#tableRows; ++$r)
2018 for ($c=0; $c<=$#{$tableRows[$r]}; ++$c)
2021 while ( $r2<=$#tableRows && ($tableRows[$r2][$c] =~ m,<TD.*?>\\\^</TD>,) )
2028 plainOutput("<!-- spanning from $r,$c -->\n") if ($debug);
2029 $tableRows[$r][$c] =~ s/<TD/<TD rowspan=$rs/;
2034 # As tbl and html differ in whether they expect spanned cells to be
2035 # supplied, remove any cells that are 'rowspanned into'.
2036 for($r=0; $r<=$#tableRows; ++$r)
2038 for ($c=$#{$tableRows[$r]}; $c>=0; --$c)
2040 if ($tableRows[$r][$c] =~ m/<TD rowspan=(\d+)/)
2042 for ($r2=$r+1; $r2<$r+$1; ++$r2)
2044 $rowref = $tableRows[$r2];
2045 plainOutput("<!-- removing $r2,$c: ".$rowref->[$c]." -->\n") if ($debug);
2046 @$rowref = (@{$rowref}[0..$c-1], @{$rowref}[$c+1..$#$rowref]);
2052 # Finally, output the cells that are left
2053 for($r=0; $r<=$#tableRows; ++$r)
2055 plainOutput("<TR valign=top>\n");
2056 for ($c=0; $c <= $#{$tableRows[$r]}; ++$c)
2058 outputLine($tableRows[$r][$c]);
2060 plainOutput("</TR>\n");
2064 plainOutput("<!-- flushTable done -->\n") if ($debug);
2068 # Use these for all font changes, including .ft, .ps, .B, .BI, .SM etc.
2069 # Need to add a mechanism to stack up these changes so tags match: <X> <Y> ... </Y> </X> etc.
2076 print OUT "<!-- pushStyle $type($tag) [".join(",", @styleStack)."] " if ($debug>1);
2078 if (grep(m/^$type/, @styleStack))
2080 print OUT "undoing up to old $type " if ($debug>1);
2083 # search back, undoing intervening tags in reverse order
2084 $oldItem = pop @styleStack;
2085 ($oldTag) = ($oldItem =~ m/^.(\S+)/);
2086 $result .= "</$oldTag>";
2087 if (substr($oldItem,0,1) eq $type)
2089 print OUT "found $oldItem " if ($debug>1);
2092 # restore the intermediates again
2093 $oldItem = shift @oldItems;
2094 push(@styleStack, $oldItem);
2095 $result .= "<".substr($oldItem,1).">";
2101 unshift(@oldItems, $oldItem);
2105 print OUT "oldItems=(@oldItems) " if ($debug>1);
2106 push(@styleStack, @oldItems); # if we didn't find anything of type
2109 $result .= "<$tag>";
2110 push(@styleStack, $type.$tag);
2112 print OUT "-> '$result' -->\n" if ($debug>1);
2120 print OUT "<!-- resetStyles [".join(",", @styleStack)."] -->\n";
2121 print OUT "<HR> resetStyles [".join(",", @styleStack)."] <HR>\n" if ($debug);
2125 $oldItem = pop @styleStack;
2126 ($oldTag) = ($oldItem =~ m/^.(\S+)/);
2127 print OUT "</$oldTag>";
2135 print OUT "<BLOCKQUOTE>\n";
2142 while ($blockquote > 0)
2144 print OUT "</BLOCKQUOTE>\n";
2151 plainOutput(pushStyle("I", "TABLE"));
2153 $width = " width=$width%" if ($width);
2154 plainOutput("<TR><TD$width> </TD><TD>\n");
2159 plainOutput("</TD></TR>\n");
2160 plainOutput(pushStyle("I"));
2165 $_[0] =~ m/^(.)(.*)$/;
2168 elsif ($1 eq "s" && ! $noFill)
2179 if ($fnt eq "P" || $fnt eq "R" || $fnt eq "1" || $fnt eq "")
2181 elsif ($fnt eq "B" || $fnt eq "3")
2183 elsif ($fnt eq "I" || $fnt eq "2")
2187 return pushStyle("F", $font);
2193 if ($size =~ m/^[+-]/)
2194 { $currentSize += $size; }
2196 { $currentSize = $size-10; }
2197 $currentSize = 0 if (! $size);
2200 $sz = -2 if ($sz < -2);
2201 $sz = 2 if ($sz > 2);
2203 if ($currentSize eq "0")
2206 { $size = "FONT size=$sz"; }
2207 return pushStyle("S", $size);
2213 ++$currentShift if ($sub eq "u");
2214 --$currentShift if ($sub eq "d");
2216 $tag = "SUP" if ($currentShift > 0);
2217 $tag = "SUB" if ($currentShift < 0);
2218 return pushStyle("D", $tag);
2223 print OUT "<PRE>\n" unless($noFill);
2229 print OUT "</PRE>\n" if ($noFill);
2236 if ($eqnMode==2 && $_[0] =~ m/^\.EN/)
2239 outputLine(flushEqn());
2243 $eqnBuffer .= $_[0]." ";
2254 @p = grep($_ !~ m/^ *$/, split(/("[^"]*"|\s+|[{}~^])/, $eqnBuffer) );
2256 #return "[".join(',', @p)." -> ".&doEqn(@p)."]\n";
2258 #$res =~ s,\\\((..),$special{$1}||"\\($1",ge;
2259 #$res =~ s,<,<,g;
2260 #$res =~ s,>,>,g;
2272 ($res, @p) = doEqn1(@p);
2287 @x = split(/\0/, $eqndefs{$c});
2291 if ($c =~ m/^"(.*)"$/)
2295 elsif ($c eq "delim")
2306 $eqnStart = quotemeta($1);
2307 $eqnEnd = quotemeta($2);
2310 elsif ($c eq "define" || $c eq "tdefine" || $c eq "ndefine")
2315 if (length($d) != 1)
2318 $def =~ s/^.(.*)./\1/;
2322 while (@p && $p[0] ne $d)
2330 $eqndefs{$t} = $def unless ($c eq "ndefine");
2336 for ($i=0; $i<=$#p; ++$i)
2338 ++$level if ($p[$i] eq "{");
2339 --$level if ($p[$i] eq "}");
2340 last if ($level==0);
2342 $res = doEqn(@p[0..$i-1]);
2347 ($c,@p) = &doEqn1(@p);
2352 ($c,@p) = &doEqn1(@p);
2355 elsif ($c eq "sub" || $c eq "from")
2357 ($c,@p) = &doEqn1(@p);
2360 elsif ($c eq "matrix")
2362 ($c,@p) = &doEqn1(@p);
2363 $res = "matrix ( $c )";
2365 elsif ($c eq "bold")
2367 ($c,@p) = &doEqn1(@p);
2368 $res = "\\fB$c\\fP";
2370 elsif ($c eq "italic")
2372 ($c,@p) = &doEqn1(@p);
2373 $res = "\\fI$c\\fP";
2375 elsif ($c eq "roman")
2378 elsif ($c eq "font" || $c eq "gfont" || $c eq "size" || $c eq "gsize")
2382 elsif ($c eq "mark" || $c eq "lineup")
2385 elsif ($c eq "~" || $c eq "^")
2389 elsif ($c eq "over")
2393 elsif ($c eq "half")
2397 elsif ($c eq "prime")
2405 elsif ($c eq "dotdot")
2409 elsif ($c eq "tilde")
2417 elsif ($c eq "bar" || $c eq "vec")
2421 elsif ($c eq "under")
2425 elsif ( $c eq "sqrt" || $c eq "lim" || $c eq "sum" || $c eq "pile" || $c eq "lpile" ||
2426 $c eq "rpile" || $c eq "cpile" || $c eq "int" || $c eq "prod" )
2430 elsif ($c eq "cdot")
2438 elsif ($c eq "above" || $c eq "lcol" || $c eq "ccol")
2442 elsif ($c eq "sin" || $c eq "cos" || $c eq "tan" || $c eq "log" || $c eq "ln" )
2446 elsif ($c eq "left" || $c eq "right" || $c eq "nothing")
2449 elsif ($c =~ m/^[A-Za-z]/)
2451 $res = "\\fI$c\\fP";
2461 ##### Search manpath and initialise special char array #####
2465 # Determine groff version if possible
2466 my $groffver = `groff -v`;
2467 $groffver =~ /^GNU groff version (\S+)/;
2470 # Parse the macro definition file for section names
2471 if (open(MACRO, "/usr/lib/tmac/tmac.an") ||
2472 open(MACRO, "/usr/lib/tmac/an") ||
2473 open(MACRO, "/usr/lib/groff/tmac/tmac.an") ||
2474 open(MACRO, "/usr/lib/groff/tmac/an.tmac") ||
2475 open(MACRO, "/usr/share/tmac/tmac.an") ||
2476 open(MACRO, "/usr/share/groff/tmac/tmac.an") ||
2477 open(MACRO, "/usr/share/groff/tmac/an.tmac") ||
2478 open(MACRO, "/usr/share/groff/$groffver/tmac/an.tmac") )
2483 if (m/\$2'([0-9a-zA-Z]+)' .ds ]D (.*)$/)
2486 unless ($sn =~ m/[a-z]/)
2489 $sn =~ s/ (.)/ \u\1/g;
2491 $sectionName{"\L$1"} = $sn;
2493 if (m/\$1'([^']+)' .ds Tx "?(.*)$/)
2497 if (m/^.ds ]W (.*)$/)
2505 print STDERR "Failed to read tmac.an definitions\n" unless ($cgiMode);
2507 if (open(MACRO, "/usr/lib/tmac/tz.map"))
2512 if (m/\$1'([^']+)' .ds Tz "?(.*)$/)
2519 # Prevent redefinition of macros that have special meaning to us
2520 $reservedMacros = '^(SH|SS|Sh|Ss)$';
2522 # Predefine special number registers
2525 # String variables defined by man package
2526 $vars{'lq'} = '“';
2527 $vars{'rq'} = '”';
2528 $vars{'R'} = '\\(rg';
2529 $vars{'S'} = '\\s0';
2531 # String variables defined by mdoc package
2532 $vars{'Le'} = '\\(<=';
2533 $vars{'<='} = '\\(<=';
2534 $vars{'Ge'} = '\\(>=';
2537 $vars{'Ne'} = '\\(!=';
2538 $vars{'>='} = '\\(>=';
2539 $vars{'q'} = '"'; # see also special case in preProcessLine
2540 $vars{'Lq'} = '“';
2541 $vars{'Rq'} = '”';
2542 $vars{'ua'} = '\\(ua';
2543 $vars{'ga'} = '\\(ga';
2544 $vars{'Pi'} = '\\(*p';
2545 $vars{'Pm'} = '\\(+-';
2546 $vars{'Na'} = 'NaN';
2547 $vars{'If'} = '\\(if';
2550 # String variables defined by ms package (access to accented characters)
2551 $vars{'bu'} = '»';
2552 $vars{'66'} = '“';
2553 $vars{'99'} = '”';
2554 $vars{'*!'} = '¡';
2555 $vars{'ct'} = '¢';
2556 $vars{'po'} = '£';
2557 $vars{'gc'} = '¤';
2558 $vars{'ye'} = '¥';
2559 #$vars{'??'} = '¦';
2560 $vars{'sc'} = '§';
2561 $vars{'*:'} = '¨';
2562 $vars{'co'} = '©';
2563 $vars{'_a'} = 'ª';
2564 $vars{'<<'} = '«';
2565 $vars{'no'} = '¬';
2566 $vars{'hy'} = '­';
2567 $vars{'rg'} = '®';
2568 $vars{'ba'} = '¯';
2569 $vars{'de'} = '°';
2570 $vars{'pm'} = '±';
2571 #$vars{'??'} = '²';
2572 #$vars{'??'} = '³';
2573 $vars{'aa'} = '´';
2574 $vars{'mu'} = 'µ';
2575 $vars{'pg'} = '¶';
2576 $vars{'c.'} = '·';
2577 $vars{'cd'} = '¸';
2578 #$vars{'??'} = '¹';
2579 $vars{'_o'} = 'º';
2580 $vars{'>>'} = '»';
2581 $vars{'14'} = '¼';
2582 $vars{'12'} = '½';
2583 #$vars{'??'} = '¾';
2584 $vars{'*?'} = '¿';
2585 $vars{'`A'} = 'À';
2586 $vars{"'A"} = 'Á';
2587 $vars{'^A'} = 'Â';
2588 $vars{'~A'} = 'Ã';
2589 $vars{':A'} = 'Ä';
2590 $vars{'oA'} = 'Å';
2591 $vars{'AE'} = 'Æ';
2592 $vars{',C'} = 'Ç';
2593 $vars{'`E'} = 'È';
2594 $vars{"'E"} = 'É';
2595 $vars{'^E'} = 'Ê';
2596 $vars{':E'} = 'Ë';
2597 $vars{'`I'} = 'Ì';
2598 $vars{"'I"} = 'Í';
2599 $vars{'^I'} = 'Î';
2600 $vars{':I'} = 'Ï';
2601 $vars{'-D'} = 'Ð';
2602 $vars{'~N'} = 'Ñ';
2603 $vars{'`O'} = 'Ò';
2604 $vars{"'O"} = 'Ó';
2605 $vars{'^O'} = 'Ô';
2606 $vars{'~O'} = 'Õ';
2607 $vars{':O'} = 'Ö';
2608 #$vars{'mu'} = '×';
2609 $vars{'NU'} = 'Ø';
2610 $vars{'`U'} = 'Ù';
2611 $vars{"'U"} = 'Ú';
2612 $vars{'^U'} = 'Û';
2613 $vars{':U'} = 'Ü';
2614 #$vars{'??'} = 'Ý';
2615 $vars{'Th'} = 'Þ';
2616 $vars{'*b'} = 'ß';
2617 $vars{'`a'} = 'à';
2618 $vars{"'a"} = 'á';
2619 $vars{'^a'} = 'â';
2620 $vars{'~a'} = 'ã';
2621 $vars{':a'} = 'ä';
2622 $vars{'oa'} = 'å';
2623 $vars{'ae'} = 'æ';
2624 $vars{',c'} = 'ç';
2625 $vars{'`e'} = 'è';
2626 $vars{"'e"} = 'é';
2627 $vars{'^e'} = 'ê';
2628 $vars{':e'} = 'ë';
2629 $vars{'`i'} = 'ì';
2630 $vars{"'i"} = 'í';
2631 $vars{'^i'} = 'î';
2632 $vars{':i'} = 'ï';
2633 #$vars{'??'} = 'ð';
2634 $vars{'~n'} = 'ñ';
2635 $vars{'`o'} = 'ò';
2636 $vars{"'o"} = 'ó';
2637 $vars{'^o'} = 'ô';
2638 $vars{'~o'} = 'õ';
2639 $vars{':o'} = 'ö';
2640 $vars{'di'} = '÷';
2641 $vars{'nu'} = 'ø';
2642 $vars{'`u'} = 'ù';
2643 $vars{"'u"} = 'ú';
2644 $vars{'^u'} = 'û';
2645 $vars{':u'} = 'ü';
2646 #$vars{'??'} = 'ý';
2647 $vars{'th'} = 'þ';
2648 $vars{':y'} = 'ÿ';
2650 # troff special characters and their closest equivalent
2652 $special{'em'} = '—';
2653 $special{'hy'} = '-';
2654 $special{'\-'} = '–'; # was -
2655 $special{'bu'} = 'o';
2656 $special{'sq'} = '[]';
2657 $special{'ru'} = '_';
2658 $special{'14'} = '¼';
2659 $special{'12'} = '½';
2660 $special{'34'} = '¾';
2661 $special{'fi'} = 'fi';
2662 $special{'fl'} = 'fl';
2663 $special{'ff'} = 'ff';
2664 $special{'Fi'} = 'ffi';
2665 $special{'Fl'} = 'ffl';
2666 $special{'de'} = '°';
2667 $special{'dg'} = '†'; # was 182, para symbol
2668 $special{'fm'} = "\\'";
2669 $special{'ct'} = '¢';
2670 $special{'rg'} = '®';
2671 $special{'co'} = '©';
2672 $special{'pl'} = '+';
2673 $special{'mi'} = '-';
2674 $special{'eq'} = '=';
2675 $special{'**'} = '*';
2676 $special{'sc'} = '§';
2677 $special{'aa'} = '´'; # was '
2678 $special{'ga'} = '`'; # was `
2679 $special{'ul'} = '_';
2680 $special{'sl'} = '/';
2681 $special{'*a'} = 'a';
2682 $special{'*b'} = 'ß';
2683 $special{'*g'} = 'y';
2684 $special{'*d'} = 'd';
2685 $special{'*e'} = 'e';
2686 $special{'*z'} = 'z';
2687 $special{'*y'} = 'n';
2688 $special{'*h'} = 'th';
2689 $special{'*i'} = 'i';
2690 $special{'*k'} = 'k';
2691 $special{'*l'} = 'l';
2692 $special{'*m'} = 'µ';
2693 $special{'*n'} = 'v';
2694 $special{'*c'} = '3';
2695 $special{'*o'} = 'o';
2696 $special{'*p'} = 'pi';
2697 $special{'*r'} = 'p';
2698 $special{'*s'} = 's';
2699 $special{'*t'} = 't';
2700 $special{'*u'} = 'u';
2701 $special{'*f'} = 'ph';
2702 $special{'*x'} = 'x';
2703 $special{'*q'} = 'ps';
2704 $special{'*w'} = 'o';
2705 $special{'*A'} = 'A';
2706 $special{'*B'} = 'B';
2707 $special{'*G'} = '|\\u_\\d';
2708 $special{'*D'} = '/\';
2709 $special{'*E'} = 'E';
2710 $special{'*Z'} = 'Z';
2711 $special{'*Y'} = 'H';
2712 $special{'*H'} = 'TH';
2713 $special{'*I'} = 'I';
2714 $special{'*K'} = 'K';
2715 $special{'*L'} = 'L';
2716 $special{'*M'} = 'M';
2717 $special{'*N'} = 'N';
2718 $special{'*C'} = 'Z';
2719 $special{'*O'} = 'O';
2720 $special{'*P'} = '||';
2721 $special{'*R'} = 'P';
2722 $special{'*S'} = 'S';
2723 $special{'*T'} = 'T';
2724 $special{'*U'} = 'Y';
2725 $special{'*F'} = 'PH';
2726 $special{'*X'} = 'X';
2727 $special{'*Q'} = 'PS';
2728 $special{'*W'} = 'O';
2729 $special{'ts'} = 's';
2730 $special{'sr'} = 'v/';
2731 $special{'rn'} = '\\u–\\d'; # was 175
2732 $special{'>='} = '>=';
2733 $special{'<='} = '<=';
2734 $special{'=='} = '==';
2735 $special{'~='} = '~=';
2736 $special{'ap'} = '~'; # was ~
2737 $special{'!='} = '!=';
2738 $special{'->'} = '->';
2739 $special{'<-'} = '<-';
2740 $special{'ua'} = '^';
2741 $special{'da'} = 'v';
2742 $special{'mu'} = '×';
2743 $special{'di'} = '÷';
2744 $special{'+-'} = '±';
2745 $special{'cu'} = 'U';
2746 $special{'ca'} = '^';
2747 $special{'sb'} = '(';
2748 $special{'sp'} = ')';
2749 $special{'ib'} = '(=';
2750 $special{'ip'} = '=)';
2751 $special{'if'} = 'oo';
2752 $special{'pd'} = '6';
2753 $special{'gr'} = 'V';
2754 $special{'no'} = '¬';
2755 $special{'is'} = 'I';
2756 $special{'pt'} = '~';
2757 $special{'es'} = 'Ø';
2758 $special{'mo'} = 'e';
2759 $special{'br'} = '|';
2760 $special{'dd'} = '‡'; # was 165, yen
2761 $special{'rh'} = '=>';
2762 $special{'lh'} = '<=';
2763 $special{'or'} = '|';
2764 $special{'ci'} = 'O';
2765 $special{'lt'} = '(';
2766 $special{'lb'} = '(';
2767 $special{'rt'} = ')';
2768 $special{'rb'} = ')';
2769 $special{'lk'} = '|';
2770 $special{'rk'} = '|';
2771 $special{'bv'} = '|';
2772 $special{'lf'} = '|';
2773 $special{'rf'} = '|';
2774 $special{'lc'} = '|';
2775 $special{'rc'} = '|';
2777 # Not true troff characters but very common typos
2778 $special{'cp'} = '©';
2779 $special{'tm'} = '®';
2780 $special{'en'} = '-';
2782 # Build a list of directories containing man pages
2784 if (open(MPC, "/etc/manpath.config") || open(MPC, "/etc/man.config"))
2788 if (m/^(MANDB_MAP|MANPATH)\s+(\S+)/)
2794 @manpath = split(/:/, $ENV{'MANPATH'}) unless (@manpath);
2795 @manpath = ("/usr/man") unless (@manpath);
2798 # Search through @manpath and construct @mandirs (non-empty subsections)
2801 return if (@mandirs);
2802 print STDERR "Searching ",join(":", @manpath)," for mandirs\n" unless($cgiMode);
2803 foreach $tld (@manpath)
2806 $tld = $1; # untaint manpath
2807 if (opendir(DIR, $tld))
2809 # foreach $d (<$tld/man[0-9a-z]*>)
2810 foreach $d (sort readdir(DIR))
2812 if ($d =~ m/^man\w/ && -d "$tld/$d")
2814 push (@mandirs, "$tld/$d");
2822 ##### Utility to search manpath for a given command #####
2828 @multipleMatches = ();
2831 return $file if (-f $file || -f "$file.gz" || -f "$file.bz2");
2833 # Search the path for the requested man page, which may be of the form:
2834 # "/usr/man/man1/ls.1", "ls.1" or "ls".
2835 ($page,$sect) = ($request =~ m/^(.+)\.([^.]+)$/);
2838 # Search the specified section first (if specified)
2841 foreach $md (@manpath)
2844 $file = "$dir/man$sect/$page.$sect";
2845 push(@multipleMatches, $file) if (-f $file || -f "$file.gz" || -f "$file.bz2");
2852 if (@multipleMatches == 1)
2854 return pop @multipleMatches;
2857 # If not found need to search through each directory
2859 foreach $dir (@mandirs)
2861 ($s) = ($dir =~ m/man([0-9A-Za-z]+)$/);
2862 $file = "$dir/$page.$s";
2863 push(@multipleMatches, $file) if (-f $file || -f "$file.gz" || -f "$file.bz2");
2864 $file = "$dir/$request";
2865 push(@multipleMatches, $file) if (-f $file || -f "$file.gz" || -f "$file.bz2");
2866 if ($sect && "$page.$sect" ne $request)
2868 $file = "$dir/$page.$sect";
2869 push(@multipleMatches, $file) if (-f $file || -f "$file.gz" || -f "$file.bz2");
2872 if (@multipleMatches == 1)
2874 return pop @multipleMatches;
2876 if (@multipleMatches > 1)
2880 # Ok, didn't find it using section numbers. Perhaps there's a page with the
2881 # right name but wrong section number lurking there somewhere. (This search is slow)
2882 # eg. page.1x in man1 (not man1x) directory
2883 foreach $dir (@mandirs)
2886 foreach $f (readdir DIR)
2888 if ($f =~ m/^$page\./)
2890 $f =~ s/\.(gz|bz2)$//;
2891 push(@multipleMatches, "$dir/$f");
2895 if (@multipleMatches == 1)
2897 return pop @multipleMatches;
2904 my ($dir,$f,$name,@files);
2906 return if (%perlPages);
2907 foreach $dir (@mandirs)
2909 if (opendir(DIR, $dir))
2911 @files = sort readdir DIR;
2914 next if ($f eq "." || $f eq ".." || $f !~ m/\./);
2915 next unless ("$dir/$f" =~ m/perl/);
2916 $f =~ s/\.(gz|bz2)$//;
2917 ($name) = ($f =~ m,(.+)\.[^.]*$,);
2918 $perlPages{$name} = "$dir/$f";
2923 delete $perlPages{'perl'}; # too ubiquitous to be useful
2929 my @days = qw (Sun Mon Tue Wed Thu Fri Sat);
2930 my @months = qw (Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
2931 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$istdst) = localtime($time);
2932 return sprintf ("%s, %02d %s %4d %02d:%02d:%02d GMT",
2933 $days[$wday],$mday,$months[$mon],1900+$year,$hour,$min,$sec);