1 /* Man page to help file converter
2 Copyright (C) 1994, 1995 Janne Kukonlehto
3 2002 Andrew V. Samoilov
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
29 #define BUFFER_SIZE 256
31 static int col
= 0; /* Current output column */
32 static int out_row
= 1; /* Current output row */
33 static int in_row
= 0; /* Current input row */
34 static int no_split_flag
= 0; /* Flag: Don't split section on next ".SH" */
35 static int skip_flag
= 0; /* Flag: Skip this section.
38 2 = title skipped, skipping text */
39 static int link_flag
= 0; /* Flag: Next line is a link */
40 static int verbatim_flag
= 0; /* Flag: Copy input to output verbatim */
41 static int node
= 0; /* Flag: This line is an original ".SH" */
43 static const char *c_out
; /* Output filename */
44 static FILE *f_out
; /* Output file */
46 static const char *c_in
; /* Current input filename */
48 static int indentation
; /* Indentation level, n spaces */
49 static int tp_flag
; /* Flag: .TP paragraph
50 1 = this line is .TP label,
51 2 = first line of label description. */
52 static char *topics
= NULL
;
55 char *node
; /* Section name */
56 char *lname
; /* Translated .SH, NULL if not translated */
61 static struct node nodes
;
62 static struct node
*cnode
; /* Current node */
64 #define MAX_STREAM_BLOCK 8192
67 * Read in blocks of reasonable size and make sure we read everything.
68 * Failure to read everything is an error, indicated by returning 0.
71 persistent_fread (void *data
, size_t len
, FILE *stream
)
74 size_t bytes_done
= 0;
75 char *ptr
= (char *) data
;
80 while (bytes_done
< len
) {
81 count
= len
- bytes_done
;
82 if (count
> MAX_STREAM_BLOCK
)
83 count
= MAX_STREAM_BLOCK
;
85 count
= fread (ptr
, 1, count
, stream
);
98 * Write in blocks of reasonable size and make sure we write everything.
99 * Failure to write everything is an error, indicated by returning 0.
102 persistent_fwrite (const void *data
, size_t len
, FILE *stream
)
105 size_t bytes_done
= 0;
106 const char *ptr
= (const char *) data
;
111 while (bytes_done
< len
) {
112 count
= len
- bytes_done
;
113 if (count
> MAX_STREAM_BLOCK
)
114 count
= MAX_STREAM_BLOCK
;
116 count
= fwrite (ptr
, 1, count
, stream
);
128 /* Report error in input */
130 print_error (const char *message
)
132 fprintf (stderr
, "man2hlp: %s in file \"%s\" on line %d\n", message
,
136 /* Do fopen(), exit if it fails */
138 fopen_check (const char *filename
, const char *flags
)
140 char tmp
[BUFFER_SIZE
];
143 f
= fopen (filename
, flags
);
145 g_snprintf (tmp
, sizeof (tmp
), "man2hlp: Cannot open file \"%s\"",
154 /* Do fclose(), exit if it fails */
156 fclose_check (FILE *f
)
159 perror ("man2hlp: File error");
164 perror ("man2hlp: Cannot close file");
169 /* Change output line */
175 fprintf (f_out
, "\n");
178 /* Calculate the length of string */
180 string_len (const char *buffer
)
182 static int anchor_flag
= 0; /* Flag: Inside hypertext anchor name */
183 static int link_flag
= 0; /* Flag: Inside hypertext link target name */
184 int backslash_flag
= 0; /* Flag: Backslash quoting */
185 int c
; /* Current character */
186 int len
= 0; /* Result: the length of the string */
190 if (c
== CHAR_LINK_POINTER
)
191 link_flag
= 1; /* Link target name starts */
192 else if (c
== CHAR_LINK_END
)
193 link_flag
= 0; /* Link target name ends */
194 else if (c
== CHAR_NODE_END
) {
195 /* Node anchor name starts */
197 /* Ugly hack to prevent loss of one space */
200 /* Don't add control characters to the length */
201 if (c
>= 0 && c
< 32)
203 /* Attempt to handle backslash quoting */
204 if (c
== '\\' && !backslash_flag
) {
209 /* Increase length if not inside anchor name or link target name */
210 if (!anchor_flag
&& !link_flag
)
212 if (anchor_flag
&& c
== ']') {
213 /* Node anchor name ends */
220 /* Output the string */
222 print_string (char *buffer
)
224 int len
; /* The length of current word */
225 int c
; /* Current character */
226 int backslash_flag
= 0;
228 /* Skipping lines? */
231 /* Copying verbatim? */
233 /* Attempt to handle backslash quoting */
236 if (c
== '\\' && !backslash_flag
) {
244 /* Split into words */
245 buffer
= strtok (buffer
, " \t\n");
246 /* Repeat for each word */
248 /* Skip empty strings */
250 len
= string_len (buffer
);
251 /* Change the line if about to break the right margin */
252 if (col
+ len
>= HELP_TEXT_WIDTH
)
254 /* Words are separated by spaces */
258 } else if (indentation
) {
259 while (col
++ < indentation
)
262 /* Attempt to handle backslash quoting */
265 if (c
== '\\' && !backslash_flag
) {
272 /* Increase column */
275 /* Get the next word */
276 buffer
= strtok (NULL
, " \t\n");
281 /* Like print_string but with printf-like syntax */
283 printf_string (const char *format
, ...)
286 char buffer
[BUFFER_SIZE
];
288 va_start (args
, format
);
289 g_vsnprintf (buffer
, sizeof (buffer
), format
, args
);
291 print_string (buffer
);
294 /* Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE */
296 handle_node (char *buffer
, int is_sh
)
298 int len
, heading_level
;
300 /* If we already skipped a section, don't skip another */
301 if (skip_flag
== 2) {
304 /* Get the command parameters */
305 buffer
= strtok (NULL
, "");
306 if (buffer
== NULL
) {
307 print_error ("Syntax error: .SH: no title");
311 if (buffer
[0] == '"') {
313 len
= strlen (buffer
);
314 if (buffer
[len
- 1] == '"') {
319 /* Calculate heading level */
321 while (buffer
[heading_level
] == ' ')
323 /* Heading level must be even */
324 if (heading_level
& 1)
325 print_error ("Syntax error: .SH: odd heading level");
327 /* Don't start a new section */
329 print_string (buffer
);
333 } else if (skip_flag
) {
334 /* Skipping title and marking text for skipping */
337 buffer
+= heading_level
;
338 if (!is_sh
|| !node
) {
339 /* Start a new section, but omit empty section names */
341 fprintf (f_out
, "%c[%s]", CHAR_NODE_END
, buffer
);
346 /* Add section to the linked list */
350 cnode
->next
= malloc (sizeof (nodes
));
353 cnode
->node
= strdup (buffer
);
356 cnode
->heading_level
= heading_level
;
359 /* print_string() strtok()es buffer, so */
360 cnode
->lname
= strdup (buffer
);
361 print_string (buffer
);
365 } /* Start new section */
366 } /* Has parameters */
370 /* Convert character from the macro name to the font marker */
372 char_to_font (char c
)
376 return CHAR_FONT_NORMAL
;
378 return CHAR_FONT_BOLD
;
380 return CHAR_FONT_ITALIC
;
387 * Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB)
388 * Return 0 if the command wasn't recognized, 1 otherwise
391 handle_alt_font (char *buffer
)
399 if (strlen (buffer
) != 3)
402 if (buffer
[0] != '.')
405 font
[0] = char_to_font (buffer
[1]);
406 font
[1] = char_to_font (buffer
[2]);
408 /* Exclude names with unknown characters, .BB, .II and .RR */
409 if (font
[0] == 0 || font
[1] == 0 || font
[0] == font
[1])
412 p
= strtok (NULL
, "");
423 in_quotes
= !in_quotes
;
428 if (*p
== ' ' && !in_quotes
) {
430 /* Don't change font if we are at the end */
432 alt_state
= !alt_state
;
433 *w
++ = font
[alt_state
];
436 /* Skip more spaces */
446 /* Turn off attributes if necessary */
447 if (font
[alt_state
] != CHAR_FONT_NORMAL
)
448 *w
++ = CHAR_FONT_NORMAL
;
451 print_string (buffer
);
456 /* Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP */
457 /* buffer is not used now */
459 handle_tp_ip (char *buffer
, int is_tp
)
471 /* Handle all the roff dot commands. See man groff_man for details */
473 handle_command (char *buffer
)
477 /* Get the command name */
478 strtok (buffer
, " \t");
480 if (strcmp (buffer
, ".SH") == 0) {
482 handle_node (buffer
, 1);
483 } else if (strcmp (buffer
, ".\\\"NODE") == 0) {
484 handle_node (buffer
, 0);
485 } else if (strcmp (buffer
, ".\\\"DONT_SPLIT\"") == 0) {
487 } else if (strcmp (buffer
, ".\\\"SKIP_SECTION\"") == 0) {
489 } else if (strcmp (buffer
, ".\\\"LINK2\"") == 0) {
490 /* Next two input lines form a link */
492 } else if ((strcmp (buffer
, ".PP") == 0)
493 || (strcmp (buffer
, ".P") == 0)
494 || (strcmp (buffer
, ".LP") == 0)) {
496 /* End of paragraph */
500 } else if (strcmp (buffer
, ".nf") == 0) {
501 /* Following input lines are to be handled verbatim */
505 } else if (strcmp (buffer
, ".I") == 0 || strcmp (buffer
, ".B") == 0
506 || strcmp (buffer
, ".SB") == 0) {
507 /* Bold text or italics text */
510 int backslash_flag
= 0;
513 * Causes the text on the same line or the text on the
514 * next line to appear in boldface font, one point
515 * size smaller than the default font.
518 /* FIXME: text is optional, so there is no error */
519 p
= strtok (NULL
, "");
521 print_error ("Syntax error: .I | .B | .SB : no text");
525 *buffer
= (buffer
[1] == 'I') ? CHAR_FONT_ITALIC
: CHAR_FONT_BOLD
;
527 /* Attempt to handle backslash quoting */
528 for (w
= &buffer
[1]; *p
; p
++) {
529 if (*p
== '\\' && !backslash_flag
) {
537 *w
++ = CHAR_FONT_NORMAL
;
539 print_string (buffer
);
540 } else if (strcmp (buffer
, ".TP") == 0) {
541 handle_tp_ip (buffer
, 1);
542 } else if (strcmp (buffer
, ".IP") == 0) {
543 handle_tp_ip (buffer
, 0);
544 } else if (strcmp (buffer
, ".\\\"TOPICS") == 0) {
547 ("Syntax error: .\\\"TOPICS must be first command");
550 buffer
= strtok (NULL
, "");
551 if (buffer
== NULL
) {
552 print_error ("Syntax error: .\\\"TOPICS: no text");
556 if (buffer
[0] == '"') {
558 len
= strlen (buffer
);
559 if (buffer
[len
- 1] == '"') {
564 topics
= strdup (buffer
);
565 } else if (strcmp (buffer
, ".br") == 0) {
568 } else if (strncmp (buffer
, ".\\\"", 3) == 0) {
570 } else if (strcmp (buffer
, ".TH") == 0) {
572 } else if (strcmp (buffer
, ".SM") == 0) {
573 /* Causes the text on the same line or the text on the
574 * next line to appear in a font that is one point
575 * size smaller than the default font. */
576 buffer
= strtok (NULL
, "");
578 print_string (buffer
);
579 } else if (handle_alt_font (buffer
) == 1) {
582 /* Other commands are ignored */
583 char warn_str
[BUFFER_SIZE
];
584 g_snprintf (warn_str
, sizeof (warn_str
),
585 "Warning: unsupported command %s", buffer
);
586 print_error (warn_str
);
591 static struct links
{
592 char *linkname
; /* Section name */
593 int line
; /* Input line in ... */
594 const char *filename
;
596 } links
, *current_link
;
599 handle_link (char *buffer
)
607 /* Old format link, not supported */
610 /* First part of new format link */
611 /* Bold text or italics text */
612 if (buffer
[0] == '.' && (buffer
[1] == 'I' || buffer
[1] == 'B'))
613 for (buffer
+= 2; *buffer
== ' ' || *buffer
== '\t'; buffer
++);
614 strncpy (old
, buffer
, sizeof (old
) - 1);
615 old
[sizeof (old
) - 1] = 0;
619 /* Second part of new format link */
620 if (buffer
[0] == '.')
622 if (buffer
[0] == '\\')
624 if (buffer
[0] == '"')
626 len
= strlen (buffer
);
627 if (len
&& buffer
[len
- 1] == '"') {
631 /* "Layout\&)," -- "Layout" should be highlighted, but not ")," */
632 amp
= strstr (old
, "\\&");
640 printf_string ("%c%s%c%s%c%s\n", CHAR_LINK_START
, old
,
641 CHAR_LINK_POINTER
, buffer
, CHAR_LINK_END
, amp
);
643 /* Add to the linked list */
645 current_link
->next
= malloc (sizeof (links
));
646 current_link
= current_link
->next
;
647 current_link
->next
= NULL
;
649 current_link
= &links
;
651 current_link
->linkname
= strdup (buffer
);
652 current_link
->filename
= c_in
;
653 current_link
->line
= in_row
;
659 main (int argc
, char **argv
)
661 int len
; /* Length of input line */
662 const char *c_man
; /* Manual filename */
663 const char *c_tmpl
; /* Template filename */
664 FILE *f_man
; /* Manual file */
665 FILE *f_tmpl
; /* Template file */
666 char buffer
[BUFFER_SIZE
]; /* Full input line */
668 char *outfile_buffer
; /* Large buffer to keep the output file */
669 long cont_start
; /* Start of [Contents] */
670 long file_end
; /* Length of the output file */
672 /* Validity check for arguments */
675 "Usage: man2hlp file.man template_file helpfile\n");
683 /* First stage - process the manual, write to the output file */
684 f_man
= fopen_check (c_man
, "r");
685 f_out
= fopen_check (c_out
, "w");
688 /* Repeat for each input line */
689 while (fgets (buffer
, BUFFER_SIZE
, f_man
)) {
690 char *input_line
; /* Input line without initial "\&" */
692 if (buffer
[0] == '\\' && buffer
[1] == '&')
693 input_line
= buffer
+ 2;
698 len
= strlen (input_line
);
699 /* Remove terminating newline */
700 if (input_line
[len
- 1] == '\n') {
706 /* Copy the line verbatim */
707 if (strcmp (input_line
, ".fi") == 0) {
710 print_string (input_line
);
713 } else if (link_flag
) {
714 /* The line is a link */
715 handle_link (input_line
);
716 } else if (buffer
[0] == '.') {
717 /* The line is a roff command */
718 handle_command (input_line
);
720 /* A normal line, just output it */
721 print_string (input_line
);
723 /* .TP label processed as usual line */
730 if (col
>= indentation
)
733 while (++col
< indentation
)
740 fclose_check (f_man
);
741 /* First stage ends here, closing the manual */
743 /* Second stage - process the template file */
744 f_tmpl
= fopen_check (c_tmpl
, "r");
747 /* Repeat for each input line */
749 while (fgets (buffer
, BUFFER_SIZE
, f_tmpl
)) {
751 if (*buffer
&& *buffer
!= '\n') {
752 cnode
->lname
= strdup (buffer
);
753 node
= strchr (cnode
->lname
, '\n');
759 node
= strchr (buffer
, CHAR_NODE_END
);
760 if (node
&& (node
[1] == '[')) {
761 char *p
= strchr (node
, ']');
763 if (strncmp (node
+ 1, "[main]", 6) == 0) {
769 cnode
->next
= malloc (sizeof (nodes
));
772 cnode
->node
= strdup (node
+ 2);
773 cnode
->node
[p
- node
- 2] = 0;
776 cnode
->heading_level
= 0;
783 fputs (buffer
, f_out
);
786 cont_start
= ftell (f_out
);
787 if (cont_start
<= 0) {
793 fprintf (f_out
, "\004[Contents]\n%s\n\n", topics
);
795 fprintf (f_out
, "\004[Contents]\n");
797 for (current_link
= &links
; current_link
&& current_link
->linkname
;) {
799 struct links
*next
= current_link
->next
;
801 if (strcmp (current_link
->linkname
, "Contents") == 0) {
804 for (cnode
= &nodes
; cnode
&& cnode
->node
; cnode
= cnode
->next
) {
805 if (strcmp (cnode
->node
, current_link
->linkname
) == 0) {
812 g_snprintf (buffer
, sizeof (buffer
), "Stale link \"%s\"",
813 current_link
->linkname
);
814 c_in
= current_link
->filename
;
815 in_row
= current_link
->line
;
816 print_error (buffer
);
818 free (current_link
->linkname
);
819 if (current_link
!= &links
)
824 for (cnode
= &nodes
; cnode
&& cnode
->node
;) {
825 char *node
= cnode
->node
;
826 struct node
*next
= cnode
->next
;
829 fprintf (f_out
, " %*s\001%s\002%s\003", cnode
->heading_level
,
830 "", cnode
->lname
? cnode
->lname
: node
, node
);
831 fprintf (f_out
, "\n");
841 file_end
= ftell (f_out
);
844 if ((file_end
<= 0) || (file_end
- cont_start
<= 0)) {
849 fclose_check (f_out
);
850 fclose_check (f_tmpl
);
851 /* Second stage ends here, closing all files, note the end of output */
854 * Third stage - swap two parts of the output file.
855 * First, open the output file for reading and load it into the memory.
857 f_out
= fopen_check (c_out
, "r");
859 outfile_buffer
= malloc (file_end
);
863 if (!persistent_fread (outfile_buffer
, file_end
, f_out
)) {
868 fclose_check (f_out
);
869 /* Now the output file is in the memory */
871 /* Again open output file for writing */
872 f_out
= fopen_check (c_out
, "w");
874 /* Write part after the "Contents" node */
875 if (!persistent_fwrite
876 (outfile_buffer
+ cont_start
, file_end
- cont_start
, f_out
)) {
881 /* Write part before the "Contents" node */
882 if (!persistent_fwrite (outfile_buffer
, cont_start
, f_out
)) {
887 free (outfile_buffer
);
888 fclose_check (f_out
);
889 /* Closing everything */