Just a little correction at the it.po file.
[midnight-commander.git] / src / man2hlp.c
blobbea39fea2f7f42d4190ef4f5b3f676e46d802520
1 /* Man page to help file converter
2 Copyright (C) 1994, 1995 Janne Kukonlehto
3 2002 Andrew V. Samoilov
4 2002 Pavel Roskin
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. */
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <string.h>
26 #include <glib.h>
27 #include "help.h"
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.
36 0 = don't skip,
37 1 = skipping title,
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;
54 struct node {
55 char *node; /* Section name */
56 char *lname; /* Translated .SH, NULL if not translated */
57 struct node *next;
58 int heading_level;
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.
70 static size_t
71 persistent_fread (void *data, size_t len, FILE *stream)
73 size_t count;
74 size_t bytes_done = 0;
75 char *ptr = (char *) data;
77 if (len <= 0)
78 return 0;
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);
87 if (count <= 0)
88 return 0;
90 bytes_done += count;
91 ptr += count;
94 return bytes_done;
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.
101 static size_t
102 persistent_fwrite (const void *data, size_t len, FILE *stream)
104 size_t count;
105 size_t bytes_done = 0;
106 const char *ptr = (const char *) data;
108 if (len <= 0)
109 return 0;
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);
118 if (count <= 0)
119 return 0;
121 bytes_done += count;
122 ptr += count;
125 return bytes_done;
128 /* Report error in input */
129 static void
130 print_error (const char *message)
132 fprintf (stderr, "man2hlp: %s in file \"%s\" on line %d\n", message,
133 c_in, in_row);
136 /* Do fopen(), exit if it fails */
137 static FILE *
138 fopen_check (const char *filename, const char *flags)
140 char tmp[BUFFER_SIZE];
141 FILE *f;
143 f = fopen (filename, flags);
144 if (f == NULL) {
145 g_snprintf (tmp, sizeof (tmp), "man2hlp: Cannot open file \"%s\"",
146 filename);
147 perror (tmp);
148 exit (3);
151 return f;
154 /* Do fclose(), exit if it fails */
155 static void
156 fclose_check (FILE *f)
158 if (ferror (f)) {
159 perror ("man2hlp: File error");
160 exit (3);
163 if (fclose (f)) {
164 perror ("man2hlp: Cannot close file");
165 exit (3);
169 /* Change output line */
170 static void
171 newline (void)
173 out_row++;
174 col = 0;
175 fprintf (f_out, "\n");
178 /* Calculate the length of string */
179 static int
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 */
188 while (*(buffer)) {
189 c = *buffer++;
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 */
196 anchor_flag = 1;
197 /* Ugly hack to prevent loss of one space */
198 len++;
200 /* Don't add control characters to the length */
201 if (c >= 0 && c < 32)
202 continue;
203 /* Attempt to handle backslash quoting */
204 if (c == '\\' && !backslash_flag) {
205 backslash_flag = 1;
206 continue;
208 backslash_flag = 0;
209 /* Increase length if not inside anchor name or link target name */
210 if (!anchor_flag && !link_flag)
211 len++;
212 if (anchor_flag && c == ']') {
213 /* Node anchor name ends */
214 anchor_flag = 0;
217 return len;
220 /* Output the string */
221 static void
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? */
229 if (skip_flag)
230 return;
231 /* Copying verbatim? */
232 if (verbatim_flag) {
233 /* Attempt to handle backslash quoting */
234 while (*(buffer)) {
235 c = *buffer++;
236 if (c == '\\' && !backslash_flag) {
237 backslash_flag = 1;
238 continue;
240 backslash_flag = 0;
241 fputc (c, f_out);
243 } else {
244 /* Split into words */
245 buffer = strtok (buffer, " \t\n");
246 /* Repeat for each word */
247 while (buffer) {
248 /* Skip empty strings */
249 if (*(buffer)) {
250 len = string_len (buffer);
251 /* Change the line if about to break the right margin */
252 if (col + len >= HELP_TEXT_WIDTH)
253 newline ();
254 /* Words are separated by spaces */
255 if (col > 0) {
256 fputc (' ', f_out);
257 col++;
258 } else if (indentation) {
259 while (col++ < indentation)
260 fputc (' ', f_out);
262 /* Attempt to handle backslash quoting */
263 while (*(buffer)) {
264 c = *buffer++;
265 if (c == '\\' && !backslash_flag) {
266 backslash_flag = 1;
267 continue;
269 backslash_flag = 0;
270 fputc (c, f_out);
272 /* Increase column */
273 col += len;
275 /* Get the next word */
276 buffer = strtok (NULL, " \t\n");
277 } /* while */
281 /* Like print_string but with printf-like syntax */
282 static void
283 printf_string (const char *format, ...)
285 va_list args;
286 char buffer[BUFFER_SIZE];
288 va_start (args, format);
289 g_vsnprintf (buffer, sizeof (buffer), format, args);
290 va_end (args);
291 print_string (buffer);
294 /* Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE */
295 static void
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) {
302 skip_flag = 0;
304 /* Get the command parameters */
305 buffer = strtok (NULL, "");
306 if (buffer == NULL) {
307 print_error ("Syntax error: .SH: no title");
308 return;
309 } else {
310 /* Remove quotes */
311 if (buffer[0] == '"') {
312 buffer++;
313 len = strlen (buffer);
314 if (buffer[len - 1] == '"') {
315 len--;
316 buffer[len] = 0;
319 /* Calculate heading level */
320 heading_level = 0;
321 while (buffer[heading_level] == ' ')
322 heading_level++;
323 /* Heading level must be even */
324 if (heading_level & 1)
325 print_error ("Syntax error: .SH: odd heading level");
326 if (no_split_flag) {
327 /* Don't start a new section */
328 newline ();
329 print_string (buffer);
330 newline ();
331 newline ();
332 no_split_flag = 0;
333 } else if (skip_flag) {
334 /* Skipping title and marking text for skipping */
335 skip_flag = 2;
336 } else {
337 buffer += heading_level;
338 if (!is_sh || !node) {
339 /* Start a new section, but omit empty section names */
340 if (*buffer) {
341 fprintf (f_out, "%c[%s]", CHAR_NODE_END, buffer);
342 col++;
343 newline ();
346 /* Add section to the linked list */
347 if (!cnode) {
348 cnode = &nodes;
349 } else {
350 cnode->next = malloc (sizeof (nodes));
351 cnode = cnode->next;
353 cnode->node = strdup (buffer);
354 cnode->lname = NULL;
355 cnode->next = NULL;
356 cnode->heading_level = heading_level;
358 if (is_sh) {
359 /* print_string() strtok()es buffer, so */
360 cnode->lname = strdup (buffer);
361 print_string (buffer);
362 newline ();
363 newline ();
365 } /* Start new section */
366 } /* Has parameters */
367 node = !is_sh;
370 /* Convert character from the macro name to the font marker */
371 static inline char
372 char_to_font (char c)
374 switch (c) {
375 case 'R':
376 return CHAR_FONT_NORMAL;
377 case 'B':
378 return CHAR_FONT_BOLD;
379 case 'I':
380 return CHAR_FONT_ITALIC;
381 default:
382 return 0;
387 * Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB)
388 * Return 0 if the command wasn't recognized, 1 otherwise
390 static int
391 handle_alt_font (char *buffer)
393 char *p;
394 char *w;
395 char font[2];
396 int in_quotes = 0;
397 int alt_state = 0;
399 if (strlen (buffer) != 3)
400 return 0;
402 if (buffer[0] != '.')
403 return 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])
410 return 0;
412 p = strtok (NULL, "");
413 if (p == NULL) {
414 return 1;
417 w = buffer;
418 *w++ = font[0];
420 while (*p) {
422 if (*p == '"') {
423 in_quotes = !in_quotes;
424 p++;
425 continue;
428 if (*p == ' ' && !in_quotes) {
429 p++;
430 /* Don't change font if we are at the end */
431 if (*p != 0) {
432 alt_state = !alt_state;
433 *w++ = font[alt_state];
436 /* Skip more spaces */
437 while (*p == ' ')
438 p++;
440 continue;
443 *w++ = *p++;
446 /* Turn off attributes if necessary */
447 if (font[alt_state] != CHAR_FONT_NORMAL)
448 *w++ = CHAR_FONT_NORMAL;
450 *w = 0;
451 print_string (buffer);
453 return 1;
456 /* Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP */
457 /* buffer is not used now */
458 static void
459 handle_tp_ip (char *buffer, int is_tp)
461 if (col > 0)
462 newline ();
463 newline ();
464 if (is_tp) {
465 tp_flag = 1;
466 indentation = 0;
467 } else
468 indentation = 8;
471 /* Handle all the roff dot commands. See man groff_man for details */
472 static void
473 handle_command (char *buffer)
475 int len;
477 /* Get the command name */
478 strtok (buffer, " \t");
480 if (strcmp (buffer, ".SH") == 0) {
481 indentation = 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) {
486 no_split_flag = 1;
487 } else if (strcmp (buffer, ".\\\"SKIP_SECTION\"") == 0) {
488 skip_flag = 1;
489 } else if (strcmp (buffer, ".\\\"LINK2\"") == 0) {
490 /* Next two input lines form a link */
491 link_flag = 2;
492 } else if ((strcmp (buffer, ".PP") == 0)
493 || (strcmp (buffer, ".P") == 0)
494 || (strcmp (buffer, ".LP") == 0)) {
495 indentation = 0;
496 /* End of paragraph */
497 if (col > 0)
498 newline ();
499 newline ();
500 } else if (strcmp (buffer, ".nf") == 0) {
501 /* Following input lines are to be handled verbatim */
502 verbatim_flag = 1;
503 if (col > 0)
504 newline ();
505 } else if (strcmp (buffer, ".I") == 0 || strcmp (buffer, ".B") == 0
506 || strcmp (buffer, ".SB") == 0) {
507 /* Bold text or italics text */
508 char *p;
509 char *w;
510 int backslash_flag = 0;
512 /* .SB [text]
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, "");
520 if (p == NULL) {
521 print_error ("Syntax error: .I | .B | .SB : no text");
522 return;
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) {
530 backslash_flag = 1;
531 continue;
533 backslash_flag = 0;
534 *w++ = *p;
537 *w++ = CHAR_FONT_NORMAL;
538 *w = 0;
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) {
545 if (out_row > 1) {
546 print_error
547 ("Syntax error: .\\\"TOPICS must be first command");
548 return;
550 buffer = strtok (NULL, "");
551 if (buffer == NULL) {
552 print_error ("Syntax error: .\\\"TOPICS: no text");
553 return;
555 /* Remove quotes */
556 if (buffer[0] == '"') {
557 buffer++;
558 len = strlen (buffer);
559 if (buffer[len - 1] == '"') {
560 len--;
561 buffer[len] = 0;
564 topics = strdup (buffer);
565 } else if (strcmp (buffer, ".br") == 0) {
566 if (col)
567 newline ();
568 } else if (strncmp (buffer, ".\\\"", 3) == 0) {
569 /* Comment */
570 } else if (strcmp (buffer, ".TH") == 0) {
571 /* Title header */
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, "");
577 if (buffer)
578 print_string (buffer);
579 } else if (handle_alt_font (buffer) == 1) {
580 return;
581 } else {
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);
587 return;
591 static struct links {
592 char *linkname; /* Section name */
593 int line; /* Input line in ... */
594 const char *filename;
595 struct links *next;
596 } links, *current_link;
598 static void
599 handle_link (char *buffer)
601 static char old[80];
602 int len;
603 char *amp;
605 switch (link_flag) {
606 case 1:
607 /* Old format link, not supported */
608 break;
609 case 2:
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;
616 link_flag = 3;
617 break;
618 case 3:
619 /* Second part of new format link */
620 if (buffer[0] == '.')
621 buffer++;
622 if (buffer[0] == '\\')
623 buffer++;
624 if (buffer[0] == '"')
625 buffer++;
626 len = strlen (buffer);
627 if (len && buffer[len - 1] == '"') {
628 buffer[--len] = 0;
631 /* "Layout\&)," -- "Layout" should be highlighted, but not ")," */
632 amp = strstr (old, "\\&");
633 if (amp) {
634 *amp = 0;
635 amp += 2;
636 } else {
637 amp = "";
640 printf_string ("%c%s%c%s%c%s\n", CHAR_LINK_START, old,
641 CHAR_LINK_POINTER, buffer, CHAR_LINK_END, amp);
642 link_flag = 0;
643 /* Add to the linked list */
644 if (current_link) {
645 current_link->next = malloc (sizeof (links));
646 current_link = current_link->next;
647 current_link->next = NULL;
648 } else {
649 current_link = &links;
651 current_link->linkname = strdup (buffer);
652 current_link->filename = c_in;
653 current_link->line = in_row;
654 break;
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 */
667 char *node = NULL;
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 */
673 if (argc != 4) {
674 fprintf (stderr,
675 "Usage: man2hlp file.man template_file helpfile\n");
676 return 3;
679 c_man = argv[1];
680 c_tmpl = argv[2];
681 c_out = argv[3];
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");
686 c_in = c_man;
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;
694 else
695 input_line = buffer;
697 in_row++;
698 len = strlen (input_line);
699 /* Remove terminating newline */
700 if (input_line[len - 1] == '\n') {
701 len--;
702 input_line[len] = 0;
705 if (verbatim_flag) {
706 /* Copy the line verbatim */
707 if (strcmp (input_line, ".fi") == 0) {
708 verbatim_flag = 0;
709 } else {
710 print_string (input_line);
711 newline ();
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);
719 } else {
720 /* A normal line, just output it */
721 print_string (input_line);
723 /* .TP label processed as usual line */
724 if (tp_flag) {
725 if (tp_flag == 1) {
726 tp_flag = 2;
727 } else {
728 tp_flag = 0;
729 indentation = 8;
730 if (col >= indentation)
731 newline ();
732 else
733 while (++col < indentation)
734 fputc (' ', f_out);
739 newline ();
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");
745 c_in = c_tmpl;
747 /* Repeat for each input line */
748 /* Read a line */
749 while (fgets (buffer, BUFFER_SIZE, f_tmpl)) {
750 if (node) {
751 if (*buffer && *buffer != '\n') {
752 cnode->lname = strdup (buffer);
753 node = strchr (cnode->lname, '\n');
754 if (node)
755 *node = 0;
757 node = NULL;
758 } else {
759 node = strchr (buffer, CHAR_NODE_END);
760 if (node && (node[1] == '[')) {
761 char *p = strchr (node, ']');
762 if (p) {
763 if (strncmp (node + 1, "[main]", 6) == 0) {
764 node = 0;
765 } else {
766 if (!cnode) {
767 cnode = &nodes;
768 } else {
769 cnode->next = malloc (sizeof (nodes));
770 cnode = cnode->next;
772 cnode->node = strdup (node + 2);
773 cnode->node[p - node - 2] = 0;
774 cnode->lname = NULL;
775 cnode->next = NULL;
776 cnode->heading_level = 0;
778 } else
779 node = NULL;
780 } else
781 node = NULL;
783 fputs (buffer, f_out);
786 cont_start = ftell (f_out);
787 if (cont_start <= 0) {
788 perror (c_out);
789 return 1;
792 if (topics)
793 fprintf (f_out, "\004[Contents]\n%s\n\n", topics);
794 else
795 fprintf (f_out, "\004[Contents]\n");
797 for (current_link = &links; current_link && current_link->linkname;) {
798 int found = 0;
799 struct links *next = current_link->next;
801 if (strcmp (current_link->linkname, "Contents") == 0) {
802 found = 1;
803 } else {
804 for (cnode = &nodes; cnode && cnode->node; cnode = cnode->next) {
805 if (strcmp (cnode->node, current_link->linkname) == 0) {
806 found = 1;
807 break;
811 if (!found) {
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)
820 free (current_link);
821 current_link = next;
824 for (cnode = &nodes; cnode && cnode->node;) {
825 char *node = cnode->node;
826 struct node *next = cnode->next;
828 if (*node)
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");
833 free (cnode->node);
834 if (cnode->lname)
835 free (cnode->lname);
836 if (cnode != &nodes)
837 free (cnode);
838 cnode = next;
841 file_end = ftell (f_out);
843 /* Sanity check */
844 if ((file_end <= 0) || (file_end - cont_start <= 0)) {
845 perror (c_out);
846 return 1;
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);
860 if (!outfile_buffer)
861 return 1;
863 if (!persistent_fread (outfile_buffer, file_end, f_out)) {
864 perror (c_out);
865 return 1;
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)) {
877 perror (c_out);
878 return 1;
881 /* Write part before the "Contents" node */
882 if (!persistent_fwrite (outfile_buffer, cont_start, f_out)) {
883 perror (c_out);
884 return 1;
887 free (outfile_buffer);
888 fclose_check (f_out);
889 /* Closing everything */
891 return 0;