3 # Man page to help file converter
4 # Copyright (C) 1994, 1995, 1998, 2000, 2001, 2002, 2003, 2004, 2005,
5 # 2007, 2010 Free Software Foundation, Inc.
7 # Originally written by:
8 # 2002 Andrew V. Samoilov
11 # Completely rewriten on perl by:
12 # 2010 Alexandr Prenko
14 # This program is free software; you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation; either version 2 of the License, or
17 # (at your option) any later version.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
29 # \brief Source: man page to help file converter
32 # end of include "help.h"
37 # Perl have no static variables, so this hash emulates them
39 "string_len anchor_flag" => 0,
40 "string_len lc_link_flag" => 0,
41 "handle_link old" => undef
45 my $CHAR_LINK_START = chr(01); # Ctrl-A
46 my $CHAR_LINK_POINTER = chr(02); # Ctrl-B
47 my $CHAR_LINK_END = chr(03); # Ctrl-C
48 my $CHAR_NODE_END = chr(04); # Ctrl-D
49 my $CHAR_ALTERNATE = chr(05); # Ctrl-E
50 my $CHAR_NORMAL = chr(06); # Ctrl-F
51 my $CHAR_VERSION = chr(07); # Ctrl-G
52 my $CHAR_FONT_BOLD = chr(010); # Ctrl-H
53 my $CHAR_FONT_NORMAL = chr(013); # Ctrl-K
54 my $CHAR_FONT_ITALIC = chr(024); # Ctrl-T
57 my $col = 0; # Current output column
58 my $out_row = 1; # Current output row
59 my $in_row = 0; # Current input row
60 my $no_split_flag = 0; # Flag: Don't split section on next ".SH"
61 my $skip_flag = 0; # Flag: Skip this section.
64 # 2 = title skipped, skipping text
65 my $link_flag = 0; # Flag: Next line is a link
66 my $verbatim_flag = 0; # Flag: Copy input to output verbatim
67 my $node = 0; # Flag: This line is an original ".SH"
69 my $c_out; # Output filename
70 my $f_out; # Output file
72 my $c_in; # Current input filename
74 my $indentation; # Indentation level, n spaces
75 my $tp_flag; # Flag: .TP paragraph
76 # 1 = this line is .TP label,
77 # 2 = first line of label description.
84 my ($str, $chars) = @_;
86 if (! defined $chars || $chars eq "")
93 $str = $strtok unless defined $str;
94 return undef unless defined $str;
97 $str =~ s/^[$chars]+//;
98 ($result, $strtok) = split /[$chars]+/, $str, 2;
99 ($result, $strtok) = split /[$chars]+/, $strtok, 2 if defined $result && $result eq "";
100 $strtok = undef if ! defined $strtok || $strtok eq "";
104 # Attempt to handle backslash quoting
105 sub handle_backslash($)
108 my $backslash_flag = 0;
110 foreach my $c (split //, $s)
112 if ($c eq '\\' && ! $backslash_flag)
125 "node" => undef, # Section name
126 "lname" => undef, # Translated .SH, undef if not translated
128 "heading_level" => undef
132 my $nodes = struct_node();
133 my $cnode; # Current node
135 # Report error in input
139 warn sprintf "man2hlp: %s in file \"%s\" on line %d\n", $message, $c_in, $in_row;
142 # Do open, exit if it fails
145 my ($mode, $filename) = @_;
148 unless (open $f, $mode, $filename)
150 warn sprintf("man2hlp: Cannot open file \"%s\" ($!)\n", $filename);
156 # Do close, exit if it fails
162 warn "man2hlp: Cannot close file ($!)\n";
175 # Calculate the length of string
179 my $anchor_flag = \$static{"string_len anchor_flag"}; # Flag: Inside hypertext anchor name ho4u_v_Ariom
180 my $lc_link_flag = \$static{"string_len lc_link_flag"}; # Flag: Inside hypertext link target name
181 my $backslash_flag = 0; # Flag: Backslash quoting
182 my $len = 0; # Result: the length of the string
185 foreach my $c (split //, $buffer)
187 if ($c eq $CHAR_LINK_POINTER)
189 $$lc_link_flag = 1; # Link target name starts
191 elsif ($c eq $CHAR_LINK_END)
193 $$lc_link_flag = 0; # Link target name ends
195 elsif ($c eq $CHAR_NODE_END)
197 # Node anchor name starts
199 # Ugly hack to prevent loss of one space
202 # Don't add control characters to the length
203 next if ord($c) >= 0 && ord($c) < 32;
204 # Attempt to handle backslash quoting
205 if ($c eq '\\' && !$backslash_flag)
211 # Increase length if not inside anchor name or link target name
212 $len++ if !$$anchor_flag && !$$lc_link_flag;
213 if ($$anchor_flag && $c eq ']')
215 # Node anchor name ends
226 my $len; # The length of current word
227 my $c; # Current character
228 my $backslash_flag = 0;
231 return if $skip_flag;
235 # Attempt to handle backslash quoting
236 $buffer = handle_backslash $buffer;
237 print $f_out $buffer;
242 $buffer = strtok($buffer, " \t\n");
243 # Repeat for each word
244 while (defined $buffer)
249 $len = length($buffer);
250 # Words are separated by spaces
258 print $f_out ' ' while $col++ < $indentation;
260 # Attempt to handle backslash quoting
261 $buffer = handle_backslash $buffer;
262 print $f_out $buffer;
267 $buffer = strtok(undef, " \t\n");
272 # Like print_string but with printf-like syntax
275 print_string sprintf shift, @_;
278 # Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE
279 # FIXME: Consider to remove first parameter
282 my ($buffer, $is_sh) = @_;
283 my ($len, $heading_level);
285 # If we already skipped a section, don't skip another
286 $skip_flag = 0 if $skip_flag == 2;
288 # Get the command parameters
289 $buffer = strtok(undef, "");
290 if (! defined $buffer)
292 print_error "Syntax error: .SH: no title";
298 $buffer =~ s/^"// and $buffer =~ s/"$//;
299 # Calculate heading level
301 $heading_level++ while substr($buffer, $heading_level, 1) eq ' ';
302 # Heading level must be even
303 unless ($heading_level % 2)
305 print_error "Syntax error: .SH: odd heading level";
309 # Don't start a new section
311 print_string $buffer;
318 # Skipping title and marking text for skipping
323 $buffer = substr($buffer, $heading_level);
324 if (! $is_sh || ! $node)
326 # Start a new section, but omit empty section names
329 printf $f_out "%s[%s]", $CHAR_NODE_END, $buffer;
333 # Add section to the linked list
334 if (! defined $cnode)
340 $cnode->{'next'} = struct_node();
341 $cnode = $cnode->{'next'};
343 $cnode->{'node'} = $buffer;
344 $cnode->{'lname'} = undef;
345 $cnode->{'next'} = undef;
346 $cnode->{'heading_level'} = $heading_level;
350 $cnode->{'lname'} = $buffer;
351 print_string $buffer;
355 } # Start new section
360 # Convert character from the macro name to the font marker
365 'R' => $CHAR_FONT_NORMAL,
366 'B' => $CHAR_FONT_BOLD,
367 'I' => $CHAR_FONT_ITALIC
369 return exists $font{$c} ? $font{$c} : chr(0);
373 # Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB)
374 # Return 0 if the command wasn't recognized, 1 otherwise
376 sub handle_alt_font($)
382 return 0 if length($buffer) != 3;
383 return 0 if substr($buffer, 0, 1) ne '.';
386 char_to_font substr($buffer, 1, 1),
387 char_to_font substr($buffer, 2, 1)
390 # Exclude names with unknown characters, .BB, .II and .RR
391 if ($font[0] eq chr(0) || $font[1] eq chr(0) || $font[0] eq $font[1])
396 my $p = strtok(undef, "");
397 return 1 unless defined $p;
401 my @p = split //, $p;
407 $in_quotes = !$in_quotes;
412 if ($p[0] eq ' ' && !$in_quotes)
415 # Don't change font if we are at the end
418 $alt_state = $alt_state ? 0 : 1;
419 $buffer .= $font[$alt_state];
423 shift @p while $p[0] eq ' ';
431 # Turn off attributes if necessary
432 if ($font[$alt_state] ne $CHAR_FONT_NORMAL)
434 $buffer .= $CHAR_FONT_NORMAL;
437 print_string $buffer;
442 # Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP */
459 # Handle all the roff dot commands. See man groff_man for details
460 sub handle_command($)
465 # Get the command name
466 $buffer = strtok($buffer, " \t");
468 if ($buffer eq ".SH")
471 handle_node $buffer, 1;
473 elsif ($buffer eq ".\\\"NODE")
475 handle_node $buffer, 0;
477 elsif ($buffer eq ".\\\"DONT_SPLIT\"")
481 elsif ($buffer eq ".\\\"SKIP_SECTION\"")
485 elsif ($buffer eq ".\\\"LINK2\"")
487 # Next two input lines form a link
490 elsif ($buffer eq ".PP" || $buffer eq ".P" || $buffer eq ".LP")
497 elsif ($buffer eq ".nf")
499 # Following input lines are to be handled verbatim
503 elsif ($buffer eq ".I" || $buffer eq ".B" || $buffer eq ".SB")
505 # Bold text or italics text
508 # Causes the text on the same line or the text on the
509 # next line to appear in boldface font, one point
510 # size smaller than the default font.
513 # FIXME: text is optional, so there is no error
514 my $p = strtok(undef, "");
517 print_error "Syntax error: .I | .B | .SB : no text";
521 $buffer = substr($buffer, 1, 1) eq 'I' ? $CHAR_FONT_ITALIC : $CHAR_FONT_BOLD;
523 # Attempt to handle backslash quoting
524 $p = handle_backslash $p;
525 print_string $buffer . $p . $CHAR_FONT_NORMAL;
527 elsif ($buffer eq ".TP")
531 elsif ($buffer eq ".IP")
535 elsif ($buffer eq ".\\\"TOPICS")
539 print_error "Syntax error: .\\\"TOPICS must be first command";
542 $buffer = strtok(undef, "");
543 if (! defined $buffer)
545 print_error "Syntax error: .\\\"TOPICS: no text";
549 $buffer =~ s/^"// and $buffer =~ s/"$//;
552 elsif ($buffer eq ".br")
556 elsif ($buffer =~ /^\.\\"/)
558 # Comment { Hello from K.O. ;-) }
560 elsif ($buffer eq ".TH")
564 elsif ($buffer eq ".SM")
566 # Causes the text on the same line or the text on the
567 # next line to appear in a font that is one point
568 # size smaller than the default font.
569 $buffer = strtok(undef, "");
570 print_string $buffer if defined $buffer;
572 elsif (handle_alt_font($buffer) == 1)
578 # Other commands are ignored
579 print_error sprintf "Warning: unsupported command %s", $buffer;
587 'linkname' => undef, # Section name
588 'line' => undef, # Input line in ...
594 my $links = struct_links();
601 my $old = \$static{"handle_link old"};
608 # Old format link, not supported
610 elsif ($link_flag == 2)
612 # First part of new format link
613 # Bold text or italics text
614 if (substr($buffer, 0, 2) eq '.I' || substr($buffer, 0, 2) eq '.B')
616 $buffer =~ s/^..[\s\t]*//;
622 elsif ($link_flag == 3)
624 # Second part of new format link
630 # "Layout\&)," -- "Layout" should be highlighted, but not "),"
631 ($$old, $amp_arg) = split /\\&/, $$old, 2;
632 printf_string "%s%s%s%s%s%s\n", $CHAR_LINK_START, $$old,
633 $CHAR_LINK_POINTER, $buffer, $CHAR_LINK_END, $amp_arg;
635 # Add to the linked list
636 if (defined $current_link)
638 $current_link->{'next'} = struct_links();
639 $current_link = $current_link->{'next'};
640 $current_link->{'next'} = undef;
644 $current_link = $links;
646 $current_link->{'linkname'} = $buffer;
647 $current_link->{'filename'} = $c_in;
648 $current_link->{'line'} = $in_row;
654 my $len; # Length of input line
655 my $c_man; # Manual filename
656 my $c_tmpl; # Template filename
657 my $f_man; # Manual file
658 my $f_tmpl; # Template file
659 my $buffer; # Full input line
661 my $outfile_buffer; # Large buffer to keep the output file
662 my $cont_start; # Start of [Contents]
663 my $file_end; # Length of the output file
665 # Validity check for arguments
668 warn "Usage: man2hlp file.man template_file helpfile\n";
676 # First stage - process the manual, write to the output file
678 $f_man = fopen_check "<", $c_man;
679 $f_out = fopen_check ">", $c_out;
682 # Repeat for each input line
685 # Remove terminating newline
688 my $input_line; # Input line without initial "\&"
690 if (substr($buffer, 0, 2) eq '\\&')
692 $input_line = substr($buffer, 2);
696 $input_line = $buffer;
700 $len = length($input_line);
704 # Copy the line verbatim
705 if ($input_line eq ".fi")
711 print_string $input_line;
718 handle_link $input_line;
720 elsif (substr($buffer, 0, 1) eq '.')
722 # The line is a roff command
723 handle_command $input_line;
727 #A normal line, just output it
728 print_string $input_line;
730 # .TP label processed as usual line
741 if ($col >= $indentation)
747 print $f_out " " while ++$col < $indentation;
755 # First stage ends here, closing the manual
757 # Second stage - process the template file
758 $f_tmpl = fopen_check "<", $c_tmpl;
761 # Repeat for each input line
766 if (defined $lc_node)
770 $cnode->{'lname'} = $buffer;
771 chomp $cnode->{'lname'};
777 my $char_node_end = index($buffer, $CHAR_NODE_END);
778 $lc_node = $char_node_end < 0 ? undef : substr($buffer, $char_node_end);
780 if (defined $lc_node && substr($lc_node, 1, 1) eq '[')
782 my $p = index($lc_node, ']');
784 if (substr($lc_node, 1, 6) eq '[main]')
790 if (! defined $cnode)
796 $cnode->{'next'} = struct_node();
797 $cnode = $cnode->{'next'};
799 $cnode->{'node'} = substr($lc_node, 2, $p-2);
800 $cnode->{'lname'} = undef;
801 $cnode->{'next'} = undef;
802 $cnode->{'heading_level'} = 0;
815 print $f_out $buffer;
818 $cont_start = tell $f_out;
819 if ($cont_start <= 0)
827 printf $f_out "\004[Contents]\n%s\n\n", $topics;
831 print $f_out "\004[Contents]\n";
834 for ($current_link = $links; defined $current_link && defined $current_link->{'linkname'};)
837 my $next = $current_link->{'next'};
839 if ($current_link->{'linkname'} eq "Contents")
845 for ($cnode = $nodes; defined $cnode && defined $cnode->{'node'}; $cnode = $cnode->{'next'})
847 if ($cnode->{'node'} eq $current_link->{'linkname'})
856 $buffer = sprintf "Stale link \"%s\"", $current_link->{'linkname'};
857 $c_in = $current_link->{'filename'};
858 $in_row = $current_link->{'line'};
862 $current_link = $next;
865 for ($cnode = $nodes; defined $cnode && defined $cnode->{'node'};)
867 my $next = $cnode->{'next'};
868 $lc_node = $cnode->{'node'};
870 if (defined $lc_node && $lc_node ne '') {
871 printf $f_out " %*s\001%s\002%s\003", $cnode->{'heading_level'},
872 "", $cnode->{'lname'} ? $cnode->{'lname'} : $lc_node, $lc_node;
878 $file_end = tell $f_out;
881 if (($file_end <= 0) || ($file_end - $cont_start <= 0))
888 fclose_check $f_tmpl;
889 # Second stage ends here, closing all files, note the end of output
892 # Third stage - swap two parts of the output file.
893 # First, open the output file for reading and load it into the memory.
895 ## TODO: replace writing to f_out by writing to a string
896 $outfile_buffer = '';
897 $f_out = fopen_check '<', $c_out;
898 $outfile_buffer .= $_ while <$f_out>;
900 # Now the output file is in the memory
902 # Again open output file for writing
903 $f_out = fopen_check '>', $c_out;
905 # Write part after the "Contents" node
906 print $f_out substr($outfile_buffer, $cont_start, $file_end - $cont_start);
908 # Write part before the "Contents" node
909 print $f_out substr($outfile_buffer, 0, $cont_start-1);