#!/usr/local/bin/perl
#
# rawman2html
#
# Convert man page source to HTML
# From std in to std out
#
# Copyright (c) 2005, J.Andrea
# This code may be freely used and distributed, but may not be sold.
# However this copyright must remain intact.
#
# v0.7, May.29/2004, J.Andrea -do convert escaped dashes
# v0.6, Jun.27/2002, J.Andrea -correct my address
#                             -correct html header
#                             -use two breaks instead of a para
# v0.5, Jun.15/2002, J.Andrea -dtd and meta char header
# v0.4, Dec.5/2001, J.Andrea -spaces around comments
#                            -handle embeded "\-" as a html hyphen
#                            -correct html header
#                            -use inline fB as bold
#                            -try reducing quotes
# v0.3, Mar.26/1999, J.Andrea -handle many special characters
# v0.2, Mar.6/1999, J.Andrea -add comments
# v0.1, Mar.3/1999, J.Andrea -reinvent wheel that works my way
# http://www.johnandrea.ca/rawman2html/
# john@johnandrea.ca
#

#$infile = $ARGV[0];
#$outfile = $ARGV[1];
#
#open( IN, $infile );
#open( OUT, ">$outfile" );

$lineno = 0;       #current line counter
$prev_blank = 0;   #watch for multiple blanks
$curr_blank = 0;

&begin_page;

while ( defined($inline = <STDIN>) ) {
   $lineno++;
   chomp( $inline );
   &fixup_line;

   $prev_blank = $curr_blank;
   if ( $inline =~ /^ *$/ ) {
     $curr_blank = 1;
   } else {
     $curr_blank = 0;
   }
   next if ( $curr_blank && $prev_blank );

   #...If the line begins with a dot then its a formatting
   #...specification.

   if ( $inline =~ /^\./ ) {
     ( $control, $data ) = split( / /, $inline, 2 );

     $control =~ s/^\.//;
     $control =~ tr/a-z/A-Z/;

     if ( defined($data) ) {
       #...Truncate leading and trailing blanks
       $data =~ s/^ *//;
       $data =~ s/ *$//;
     } else {
       $data = "";
     }

     if ( ($control eq "B") || ($control eq "BI") || ($control eq BR) ) {
       &bold;
     } elsif ( ($control eq "I" ) || ($control eq "IB") || ($control eq "IR") ) {
       &italics;
     } elsif ( ($control eq "R") || ($control eq "RB") || ($control eq "RI") ) {
       &roman;
     } elsif ( ($control eq "SB") || ($control eq "SM") ) {
       &small;
     } elsif ( $control eq "DT" ) {
       &default_tabs;
     } elsif ( ($control eq "HP") || ($control eq "TP") ) {
       &hanging_indent;
     } elsif ( $control eq "IP" ) {
       &hanging_indent_this_line;
     } elsif ( ($control eq "LP") || ($control eq "PP") ) {
       &paragraph;
     } elsif ( $control eq "RE" ) {
       &end_relative_indent;
     } elsif ( $control eq "RS" ) {
       &start_relative_indent;
     } elsif ( $control eq "SS" ) {
       &subheading;
     } elsif ( $control eq "SH" ) {
       &section;
     } elsif ( ($control eq "\\") || ($control eq "\\\"") ) {
       &comment;
     } elsif ( $control eq "TH" ) {
       &title;
     } elsif ( $control eq "NF" ) {
       &stop_auto_fill;
     } elsif ( $control eq "FI" ) {
       &start_auto_fill;
     } else {
       print STDERR "unknown control :$control: at $lineno\n";
     }

   } else {
     #...Line didn't begin with a dot
     $data = $inline;

     if ( $data =~ /^\s*$/ ) {
       #...All blank
       &blank_line;

     } elsif ( $data =~ /^\s+\S/ ) {
       #...Begins with blanks, but has some data
       &indented_line;

     } else {
       #....Everything else falls here, just a plain line
       &plain_line;
     }
   }

}

&end_page;

#close( IN );
#close( OUT );

#EOMain

#...I used this output subroutine because on MSWin95 stdin/out
#...doesn't work so well, then just switch to file name mode
#sub output { print OUT $_[0]; }
#sub outputln { print OUT "\n"; }
sub output { print $_[0]; }
sub outputln { print "\n"; }


sub close_pre {
  #...If any HTML pre tags are open, close them all
  while ( $pre_open > 0 ) {
    &output( "</pre>" );
    $pre_open--;
  }
}
sub close_table {
  #...If any tables (dlists that is) are open, close them all
  while ( $table_open > 0 ) {
    &output( "</dl>" );
    $table_open--;
  }
}
sub close_quote {
  #...If any blockquotes are open, close them all
  while ( $quote_open > 0 ) {
    &output( "</blockquote>" );
    $quote_open--;
  }
}

sub begin_page {
  #...We don't yet know the title, so just put in a generic description
  &output( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">" ); &outputln;
  &output( "<html>" ); &outputln;
  &output( "<head>" ); &outputln;
  &output( "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">" ); &outputln;
  &output( "<title>man page</title>" ); &outputln;
  &output( "</head>" ); &outputln;
  &output( "<body bgcolor=\"white\">" ); &outputln;

  $pre_open = 0;
  $dl_open = 0;
  $table_open = 0;
  $quote_open = 0;
  $saw_title = 0;
  $ready_for_tag = 0;
  $table_data_open = 0;
  $firstdt = 1;  #first dt in a dl flag

}
sub end_page {
  &close_pre;
  &close_quote;
  &close_table;
  while ( $dl_open > 0 ) {
    &output( "</dl>" );
    $dl_open--;
  }
  &output( "</body>" ); &outputln;
  &output( "</html>" ); &outputln;
}

sub begin_table_data {
  #...Use a sort of table structure for tagged paragraphs
  #...An HTML definition list work well
  if ( $ready_for_tag > 0 ) {
    #...this line is then the first column
    &output( "<dt>" );
    $table_data_open++;
  }
}
sub end_table_data {
  if ( $ready_for_tag > 0 ) {
    #...ok, column one is done because it is only allowed
    #...to be one input line
    &outputln;
    &output( "<dd>" );
    $ready_for_tag = 0;
    #...the next column may be many input lines though
  }
}

sub fixup_line {
  #...Note: don't truncate blanks
  #...Change all <,> signs to HTML special codes
  $inline =~ s/</\&lt;/g;
  $inline =~ s/>/\&gt;/g;
  #...change all tabs to spaces
  $inline =~ tr/\t/ /;
  #...remove all carriage returns (may have come from dos)
  $inline =~ tr/\r//d;

  #...escaped dash
  #$inline =~ s/\\-/\&#150;/g;
  $inline =~ s/\\-/-/g;

  #...bold code ?
  $inline =~ s|\\fB|<b>|g;
  $inline =~ s|\\fR|</b>|g;

  #...These special characters begin with \(
  #...The greek letters aren't handled because there is no HTML equivalent
  $inline =~ s/\\\(bu/\&#149;/g;   #button
  $inline =~ s/\\\(sq/[]/g;        #square
  $inline =~ s/\\\(dg/\&#134;/g;   #dagger
  $inline =~ s/\\\(ct/\&cent;/g;   #cent
  $inline =~ s/\\\(de/\&deg;/g;    #degree
  $inline =~ s/\\\(em/\&150;/g;    #em dash
  $inline =~ s/\\\(rg/\&reg;/g;    #registered
  $inline =~ s/\\\(co/\&copy;/g;   #copyright
  $inline =~ s/\\\(14/\&frac14;/g; #1/4
  $inline =~ s/\\\(12/\&frac12;/g; #1/2
  $inline =~ s/\\\(34/\&frac34;/g; #3/4
  $inline =~ s/\\\(fi/\&#131;i/g;  #fi
  $inline =~ s/\\\(fl/\&#131;l/g;  #fl
  $inline =~ s/\\\(ff/\&#131;f/g;  #ff
  $inline =~ s/\\\(Fi/\&#131;fi/g; #ffi
  $inline =~ s/\\\(Fl/\&#131;fl/g; #ffl
  $inline =~ s/\\\(sc/\&sect;/g;   #section
  $inline =~ s/\\\(dd/\&135;/g;    #double dagger
  $inline =~ s/\\\(ci/\&deg;/g;    #circle
  $inline =~ s/\\\(aa/\&146;/g;    #backquote
  $inline =~ s/\\\(ga/\&145;/g;    #forward quotq
  $inline =~ s/\\\(->/-\&gt;/g;    #arrow
  $inline =~ s/\\\(<-/\&lt;-/g;    #arrow
  $inline =~ s/\\\(ua/^/g;         #up arrow
  $inline =~ s/\\\(da/v/g;         #down arrow
  $inline =~ s/\\\(pl/+/g;         #plus
  $inline =~ s/\\\(mi/-/g;         #minus
  $inline =~ s/\\\(\*\*/\*/g;      #asterix
  $inline =~ s/\\\(mu/\&times;/g;  #multiply
  $inline =~ s/\\\(di/\&divide;/g; #divide
  $inline =~ s/\\\(eq/=/g;         #equal
  $inline =~ s/\\\(>=/\&gt;=/g;    #greater equal
  $inline =~ s/\\\(<=/\&lt;=/g;    #less equal
  $inline =~ s/\\\(!=/!=/g;        #not equal
  $inline =~ s/\\\(+-/\&plusmn;/g; #plus minus
}

sub plain_line {
  #...A plain line.
  &close_pre;         #...Can't be within a "pre" formatted paragraph
  &begin_table_data;  #...Might be within a tagged paragraph
  &output( $data );
  &end_table_data;
  &outputln;
}

sub blank_line {
  #...A blank line.
  #...Just output an HTML line break. But if within a "pre"
  #...the line end itself is enough.
  &begin_table_data;
  if ( $pre_open < 1 ) {
    &output( "<br>" );
  }
  &end_table_data;
  &outputln;
}

sub indented_line {
  #...An indented line.
  #...Sometimes man page authors indent lines rather than using
  #...the "un-justified paragraph" formatting operators. So this
  #...routine takes care of that be putting all indented lines
  #...within an HTML "pre" structure.
  &begin_table_data;
  if ( $pre_open < 1 ) {
    &output( "<pre>" ); &outputln;
    $pre_open = 1;
  }
  &output( $data );
  &end_table_data;
  &outputln;
}

sub bold {
  &begin_table_data;
  &output( " " );
  &output( "<strong>" );
  &output( $data );
  &output( "</strong>" );
  &end_table_data;
  &outputln;
}
sub italics {
  &begin_table_data;
  &output( " " );
  &output( "<em>" );
  &output( $data );
  &output( "</em>" );
  &end_table_data;
  &outputln;
}
sub roman {
  &begin_table_data;
  &output( " " );
  &output( $data );
  &end_table_data;
  &outputln;
}
sub small {
  &begin_table_data;
  &output( " " );
  &output( "<font size=\"-1\">" );
  &output( $data );
  &output( "</font>" );
  &end_table_data;
  &outputln;
}

sub default_tabs {
  #...No need for tabs in HTML
}

sub hanging_indent {
  #...This is a tagged paragraph.
  #...Use an HTML definition list to show the tags and their
  #...definitions.
  &close_pre;
  if ( $table_open < 1 ) {
    #...start a new table
    &output( "<dl>" );
    $table_open = 1;
  } else {
    #...the second column may be many lines, don't end that
    #...column until a new row is ready
    $table_data_open = 0;
    &output( "<br><br>" ); &outputln;
  }
  #...next line will be the tag
  $ready_for_tag = 1;
}

sub hanging_indent_this_line {
  &hanging_indent;
  #...take the data to output from what was given on the current line
  &bold;
}

sub paragraph {
  #...A new paragraph specifier.
  #...Close any tagged paragraphs.
  &close_pre;
  if ( $table_open > 0 ) {
    if ( $table_data_open > 0 ) {
      $table_data_open = 0;
    }
    &output( "</dl>" ); &outputln;
    $table_open--;
  }
  &output( "<br><br>" ); &outputln;
}

sub end_relative_indent {
  &output( "</blockquote>" ); &outputln;
  $quote_open--;
}

sub start_relative_indent {
  &output( "<blockquote>" ); &outputln;
  $quote_open++;
}

sub subheading {
  #...I'm not sure of how this formatting option is used.
  #...Lets assume that it is the same as a section.
  &section;
}

sub section {
  #...A new section.
  #...Use an HTML definition list to show the parts of the section.
  if ( $dl_open < 1 ) {
    $dl_open = 1;
    &output( "<dl>" ); &outputln;
    $first_dt = 1;
  }
  &close_pre;
  &close_quote;
  &close_table;
  if ( $first_dt ) {
    $first_dt = "0";
  } else {
    &output( "<br><br>" );
  }
  &output( "<dt><strong>" );
  #...reduce too much quoting
  $data =~ tr/"//d;
  &output( $data );
  &output( "</strong>" );
  &outputln;
  &output( "<dd>" );
}

sub comment {
  #...Save all comments in the output file.
  &output( "<!-- " );
  &output( $data );
  &output( " -->" );
  &outputln;
}

sub title {
  #...A title. Should only be seen once in a man page.
  if ( $saw_title < 1 ) {
    &output( "<h1>" );
    #...reduce too much quoting
    $data =~ tr/"//d;
    &output( $data );
    &output( "</h1>" );
    &output( "<dl>" ); &outputln;
    $first_dt = 1;
    $dl_open++;
  } else {
    &plain_line;
  }
  $saw_title = 1;
}

sub start_auto_fill {
  #...Auto fill is the reverse of HTML "pre" sections.
  &close_pre;
}

sub stop_auto_fill {
  if ( $pre_open < 1 ) {
    &output( "<pre>" ); &outputln;
    $pre_open = 1;
  }
}

#eof
