1 /* Man page to help file converter
2 Copyright (C) 1994, 1995, 1998, 2000, 2001, 2002, 2003, 2004, 2005,
3 2007 Free Software Foundation, Inc.
4 2002 Andrew V. Samoilov
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
23 * \brief Source: man page to help file converter
37 #define BUFFER_SIZE 256
39 static int col
= 0; /* Current output column */
40 static int out_row
= 1; /* Current output row */
41 static int in_row
= 0; /* Current input row */
42 static int no_split_flag
= 0; /* Flag: Don't split section on next ".SH" */
43 static int skip_flag
= 0; /* Flag: Skip this section.
46 2 = title skipped, skipping text */
47 static int link_flag
= 0; /* Flag: Next line is a link */
48 static int verbatim_flag
= 0; /* Flag: Copy input to output verbatim */
49 static int node
= 0; /* Flag: This line is an original ".SH" */
51 static const char *c_out
; /* Output filename */
52 static FILE *f_out
; /* Output file */
54 static const char *c_in
; /* Current input filename */
56 static int indentation
; /* Indentation level, n spaces */
57 static int tp_flag
; /* Flag: .TP paragraph
58 1 = this line is .TP label,
59 2 = first line of label description. */
60 static char *topics
= NULL
;
64 char *node
; /* Section name */
65 char *lname
; /* Translated .SH, NULL if not translated */
70 static struct node nodes
;
71 static struct node
*cnode
; /* Current node */
73 #define MAX_STREAM_BLOCK 8192
76 * Read in blocks of reasonable size and make sure we read everything.
77 * Failure to read everything is an error, indicated by returning 0.
80 persistent_fread (void *data
, size_t len
, FILE * stream
)
83 size_t bytes_done
= 0;
84 char *ptr
= (char *) data
;
89 while (bytes_done
< len
)
91 count
= len
- bytes_done
;
92 if (count
> MAX_STREAM_BLOCK
)
93 count
= MAX_STREAM_BLOCK
;
95 count
= fread (ptr
, 1, count
, stream
);
108 * Write in blocks of reasonable size and make sure we write everything.
109 * Failure to write everything is an error, indicated by returning 0.
112 persistent_fwrite (const void *data
, size_t len
, FILE * stream
)
115 size_t bytes_done
= 0;
116 const char *ptr
= (const char *) data
;
121 while (bytes_done
< len
)
123 count
= len
- bytes_done
;
124 if (count
> MAX_STREAM_BLOCK
)
125 count
= MAX_STREAM_BLOCK
;
127 count
= fwrite (ptr
, 1, count
, stream
);
139 /* Report error in input */
141 print_error (const char *message
)
143 fprintf (stderr
, "man2hlp: %s in file \"%s\" on line %d\n", message
, c_in
, in_row
);
146 /* Do fopen(), exit if it fails */
148 fopen_check (const char *filename
, const char *flags
)
150 char tmp
[BUFFER_SIZE
];
153 f
= fopen (filename
, flags
);
156 g_snprintf (tmp
, sizeof (tmp
), "man2hlp: Cannot open file \"%s\"", filename
);
164 /* Do fclose(), exit if it fails */
166 fclose_check (FILE * f
)
170 perror ("man2hlp: File error");
176 perror ("man2hlp: Cannot close file");
181 /* Change output line */
187 fprintf (f_out
, "\n");
190 /* Calculate the length of string */
192 string_len (const char *buffer
)
194 static int anchor_flag
= 0; /* Flag: Inside hypertext anchor name */
195 static int lc_link_flag
= 0; /* Flag: Inside hypertext link target name */
196 int backslash_flag
= 0; /* Flag: Backslash quoting */
197 int c
; /* Current character */
198 int len
= 0; /* Result: the length of the string */
203 if (c
== CHAR_LINK_POINTER
)
204 lc_link_flag
= 1; /* Link target name starts */
205 else if (c
== CHAR_LINK_END
)
206 lc_link_flag
= 0; /* Link target name ends */
207 else if (c
== CHAR_NODE_END
)
209 /* Node anchor name starts */
211 /* Ugly hack to prevent loss of one space */
214 /* Don't add control characters to the length */
215 if (c
>= 0 && c
< 32)
217 /* Attempt to handle backslash quoting */
218 if (c
== '\\' && !backslash_flag
)
224 /* Increase length if not inside anchor name or link target name */
225 if (!anchor_flag
&& !lc_link_flag
)
227 if (anchor_flag
&& c
== ']')
229 /* Node anchor name ends */
236 /* Output the string */
238 print_string (char *buffer
)
240 int len
; /* The length of current word */
241 int c
; /* Current character */
242 int backslash_flag
= 0;
244 /* Skipping lines? */
247 /* Copying verbatim? */
250 /* Attempt to handle backslash quoting */
254 if (c
== '\\' && !backslash_flag
)
265 /* Split into words */
266 buffer
= strtok (buffer
, " \t\n");
267 /* Repeat for each word */
270 /* Skip empty strings */
273 len
= string_len (buffer
);
274 /* Words are separated by spaces */
280 else if (indentation
)
282 while (col
++ < indentation
)
285 /* Attempt to handle backslash quoting */
289 if (c
== '\\' && !backslash_flag
)
297 /* Increase column */
300 /* Get the next word */
301 buffer
= strtok (NULL
, " \t\n");
306 /* Like print_string but with printf-like syntax */
308 printf_string (const char *format
, ...)
311 char buffer
[BUFFER_SIZE
];
313 va_start (args
, format
);
314 g_vsnprintf (buffer
, sizeof (buffer
), format
, args
);
316 print_string (buffer
);
319 /* Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE */
321 handle_node (char *buffer
, int is_sh
)
323 int len
, heading_level
;
325 /* If we already skipped a section, don't skip another */
330 /* Get the command parameters */
331 buffer
= strtok (NULL
, "");
334 print_error ("Syntax error: .SH: no title");
340 if (buffer
[0] == '"')
343 len
= strlen (buffer
);
344 if (buffer
[len
- 1] == '"')
350 /* Calculate heading level */
352 while (buffer
[heading_level
] == ' ')
354 /* Heading level must be even */
355 if (heading_level
& 1)
356 print_error ("Syntax error: .SH: odd heading level");
359 /* Don't start a new section */
361 print_string (buffer
);
368 /* Skipping title and marking text for skipping */
373 buffer
+= heading_level
;
376 /* Start a new section, but omit empty section names */
379 fprintf (f_out
, "%c[%s]", CHAR_NODE_END
, buffer
);
383 /* Add section to the linked list */
390 cnode
->next
= malloc (sizeof (nodes
));
393 cnode
->node
= strdup (buffer
);
396 cnode
->heading_level
= heading_level
;
400 /* print_string() strtok()es buffer, so */
401 cnode
->lname
= strdup (buffer
);
402 print_string (buffer
);
406 } /* Start new section */
407 } /* Has parameters */
411 /* Convert character from the macro name to the font marker */
413 char_to_font (char c
)
418 return CHAR_FONT_NORMAL
;
420 return CHAR_FONT_BOLD
;
422 return CHAR_FONT_ITALIC
;
429 * Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB)
430 * Return 0 if the command wasn't recognized, 1 otherwise
433 handle_alt_font (char *buffer
)
441 if (strlen (buffer
) != 3)
444 if (buffer
[0] != '.')
447 font
[0] = char_to_font (buffer
[1]);
448 font
[1] = char_to_font (buffer
[2]);
450 /* Exclude names with unknown characters, .BB, .II and .RR */
451 if (font
[0] == 0 || font
[1] == 0 || font
[0] == font
[1])
454 p
= strtok (NULL
, "");
468 in_quotes
= !in_quotes
;
473 if (*p
== ' ' && !in_quotes
)
476 /* Don't change font if we are at the end */
479 alt_state
= !alt_state
;
480 *w
++ = font
[alt_state
];
483 /* Skip more spaces */
493 /* Turn off attributes if necessary */
494 if (font
[alt_state
] != CHAR_FONT_NORMAL
)
495 *w
++ = CHAR_FONT_NORMAL
;
498 print_string (buffer
);
503 /* Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP */
505 handle_tp_ip (int is_tp
)
519 /* Handle all the roff dot commands. See man groff_man for details */
521 handle_command (char *buffer
)
525 /* Get the command name */
526 strtok (buffer
, " \t");
528 if (strcmp (buffer
, ".SH") == 0)
531 handle_node (buffer
, 1);
533 else if (strcmp (buffer
, ".\\\"NODE") == 0)
535 handle_node (buffer
, 0);
537 else if (strcmp (buffer
, ".\\\"DONT_SPLIT\"") == 0)
541 else if (strcmp (buffer
, ".\\\"SKIP_SECTION\"") == 0)
545 else if (strcmp (buffer
, ".\\\"LINK2\"") == 0)
547 /* Next two input lines form a link */
550 else if ((strcmp (buffer
, ".PP") == 0)
551 || (strcmp (buffer
, ".P") == 0) || (strcmp (buffer
, ".LP") == 0))
554 /* End of paragraph */
559 else if (strcmp (buffer
, ".nf") == 0)
561 /* Following input lines are to be handled verbatim */
566 else if (strcmp (buffer
, ".I") == 0 || strcmp (buffer
, ".B") == 0
567 || strcmp (buffer
, ".SB") == 0)
569 /* Bold text or italics text */
572 int backslash_flag
= 0;
575 * Causes the text on the same line or the text on the
576 * next line to appear in boldface font, one point
577 * size smaller than the default font.
580 /* FIXME: text is optional, so there is no error */
581 p
= strtok (NULL
, "");
584 print_error ("Syntax error: .I | .B | .SB : no text");
588 *buffer
= (buffer
[1] == 'I') ? CHAR_FONT_ITALIC
: CHAR_FONT_BOLD
;
590 /* Attempt to handle backslash quoting */
591 for (w
= &buffer
[1]; *p
; p
++)
593 if (*p
== '\\' && !backslash_flag
)
602 *w
++ = CHAR_FONT_NORMAL
;
604 print_string (buffer
);
606 else if (strcmp (buffer
, ".TP") == 0)
610 else if (strcmp (buffer
, ".IP") == 0)
614 else if (strcmp (buffer
, ".\\\"TOPICS") == 0)
618 print_error ("Syntax error: .\\\"TOPICS must be first command");
621 buffer
= strtok (NULL
, "");
624 print_error ("Syntax error: .\\\"TOPICS: no text");
628 if (buffer
[0] == '"')
631 len
= strlen (buffer
);
632 if (buffer
[len
- 1] == '"')
638 topics
= strdup (buffer
);
640 else if (strcmp (buffer
, ".br") == 0)
645 else if (strncmp (buffer
, ".\\\"", 3) == 0)
649 else if (strcmp (buffer
, ".TH") == 0)
653 else if (strcmp (buffer
, ".SM") == 0)
655 /* Causes the text on the same line or the text on the
656 * next line to appear in a font that is one point
657 * size smaller than the default font. */
658 buffer
= strtok (NULL
, "");
660 print_string (buffer
);
662 else if (handle_alt_font (buffer
) == 1)
668 /* Other commands are ignored */
669 char warn_str
[BUFFER_SIZE
];
670 g_snprintf (warn_str
, sizeof (warn_str
), "Warning: unsupported command %s", buffer
);
671 print_error (warn_str
);
678 char *linkname
; /* Section name */
679 int line
; /* Input line in ... */
680 const char *filename
;
682 } links
, *current_link
;
685 handle_link (char *buffer
)
695 /* Old format link, not supported */
698 /* First part of new format link */
699 /* Bold text or italics text */
700 if (buffer
[0] == '.' && (buffer
[1] == 'I' || buffer
[1] == 'B'))
701 for (buffer
+= 2; *buffer
== ' ' || *buffer
== '\t'; buffer
++);
702 g_strlcpy (old
, buffer
, sizeof (old
));
706 /* Second part of new format link */
707 if (buffer
[0] == '.')
709 if (buffer
[0] == '\\')
711 if (buffer
[0] == '"')
713 len
= strlen (buffer
);
714 if (len
&& buffer
[len
- 1] == '"')
719 /* "Layout\&)," -- "Layout" should be highlighted, but not ")," */
720 amp
= strstr (old
, "\\&");
732 printf_string ("%c%s%c%s%c%s\n", CHAR_LINK_START
, old
,
733 CHAR_LINK_POINTER
, buffer
, CHAR_LINK_END
, amp_arg
);
735 /* Add to the linked list */
738 current_link
->next
= malloc (sizeof (links
));
739 current_link
= current_link
->next
;
740 current_link
->next
= NULL
;
744 current_link
= &links
;
746 current_link
->linkname
= strdup (buffer
);
747 current_link
->filename
= c_in
;
748 current_link
->line
= in_row
;
754 main (int argc
, char **argv
)
756 int len
; /* Length of input line */
757 const char *c_man
; /* Manual filename */
758 const char *c_tmpl
; /* Template filename */
759 FILE *f_man
; /* Manual file */
760 FILE *f_tmpl
; /* Template file */
761 char buffer
[BUFFER_SIZE
]; /* Full input line */
762 char *lc_node
= NULL
;
763 char *outfile_buffer
; /* Large buffer to keep the output file */
764 long cont_start
; /* Start of [Contents] */
765 long file_end
; /* Length of the output file */
767 /* Validity check for arguments */
770 fprintf (stderr
, "Usage: man2hlp file.man template_file helpfile\n");
778 /* First stage - process the manual, write to the output file */
779 f_man
= fopen_check (c_man
, "r");
780 f_out
= fopen_check (c_out
, "w");
783 /* Repeat for each input line */
784 while (fgets (buffer
, BUFFER_SIZE
, f_man
))
786 char *input_line
; /* Input line without initial "\&" */
788 if (buffer
[0] == '\\' && buffer
[1] == '&')
789 input_line
= buffer
+ 2;
794 len
= strlen (input_line
);
795 /* Remove terminating newline */
796 if (input_line
[len
- 1] == '\n')
804 /* Copy the line verbatim */
805 if (strcmp (input_line
, ".fi") == 0)
811 print_string (input_line
);
817 /* The line is a link */
818 handle_link (input_line
);
820 else if (buffer
[0] == '.')
822 /* The line is a roff command */
823 handle_command (input_line
);
827 /* A normal line, just output it */
828 print_string (input_line
);
830 /* .TP label processed as usual line */
841 if (col
>= indentation
)
844 while (++col
< indentation
)
851 fclose_check (f_man
);
852 /* First stage ends here, closing the manual */
854 /* Second stage - process the template file */
855 f_tmpl
= fopen_check (c_tmpl
, "r");
858 /* Repeat for each input line */
860 while (fgets (buffer
, BUFFER_SIZE
, f_tmpl
))
864 if (*buffer
&& *buffer
!= '\n')
866 cnode
->lname
= strdup (buffer
);
867 lc_node
= strchr (cnode
->lname
, '\n');
875 lc_node
= strchr (buffer
, CHAR_NODE_END
);
876 if (lc_node
&& (lc_node
[1] == '['))
878 char *p
= strchr (lc_node
, ']');
881 if (strncmp (lc_node
+ 1, "[main]", 6) == 0)
893 cnode
->next
= malloc (sizeof (nodes
));
896 cnode
->node
= strdup (lc_node
+ 2);
897 cnode
->node
[p
- lc_node
- 2] = 0;
900 cnode
->heading_level
= 0;
909 fputs (buffer
, f_out
);
912 cont_start
= ftell (f_out
);
920 fprintf (f_out
, "\004[Contents]\n%s\n\n", topics
);
922 fprintf (f_out
, "\004[Contents]\n");
924 for (current_link
= &links
; current_link
&& current_link
->linkname
;)
927 struct links
*next
= current_link
->next
;
929 if (strcmp (current_link
->linkname
, "Contents") == 0)
935 for (cnode
= &nodes
; cnode
&& cnode
->node
; cnode
= cnode
->next
)
937 if (strcmp (cnode
->node
, current_link
->linkname
) == 0)
946 g_snprintf (buffer
, sizeof (buffer
), "Stale link \"%s\"", current_link
->linkname
);
947 c_in
= current_link
->filename
;
948 in_row
= current_link
->line
;
949 print_error (buffer
);
951 free (current_link
->linkname
);
952 if (current_link
!= &links
)
957 for (cnode
= &nodes
; cnode
&& cnode
->node
;)
959 struct node
*next
= cnode
->next
;
960 lc_node
= cnode
->node
;
963 fprintf (f_out
, " %*s\001%s\002%s\003", cnode
->heading_level
,
964 "", cnode
->lname
? cnode
->lname
: lc_node
, lc_node
);
965 fprintf (f_out
, "\n");
975 file_end
= ftell (f_out
);
978 if ((file_end
<= 0) || (file_end
- cont_start
<= 0))
984 fclose_check (f_out
);
985 fclose_check (f_tmpl
);
986 /* Second stage ends here, closing all files, note the end of output */
989 * Third stage - swap two parts of the output file.
990 * First, open the output file for reading and load it into the memory.
992 f_out
= fopen_check (c_out
, "r");
994 outfile_buffer
= malloc (file_end
);
998 if (!persistent_fread (outfile_buffer
, file_end
, f_out
))
1004 fclose_check (f_out
);
1005 /* Now the output file is in the memory */
1007 /* Again open output file for writing */
1008 f_out
= fopen_check (c_out
, "w");
1010 /* Write part after the "Contents" node */
1011 if (!persistent_fwrite (outfile_buffer
+ cont_start
, file_end
- cont_start
, f_out
))
1017 /* Write part before the "Contents" node */
1018 if (!persistent_fwrite (outfile_buffer
, cont_start
, f_out
))
1024 free (outfile_buffer
);
1025 fclose_check (f_out
);
1026 /* Closing everything */