Ticket #1648: implemented single-line boxes.
[kaloumi3.git] / src / man2hlp.c
blobe7066fbc7c5b2e8d0105413920f981bb95751303
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
5 2002 Pavel Roskin
6 2010 Andrew Borodin
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. */
22 /** \file man2hlp.c
23 * \brief Source: man page to help file converter
26 #include <config.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
33 #include <glib.h>
35 #include "help.h"
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.
44 0 = don't skip,
45 1 = skipping title,
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;
62 struct node
64 char *node; /* Section name */
65 char *lname; /* Translated .SH, NULL if not translated */
66 struct node *next;
67 int heading_level;
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.
79 static size_t
80 persistent_fread (void *data, size_t len, FILE * stream)
82 size_t count;
83 size_t bytes_done = 0;
84 char *ptr = (char *) data;
86 if (len <= 0)
87 return 0;
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);
97 if (count <= 0)
98 return 0;
100 bytes_done += count;
101 ptr += count;
104 return bytes_done;
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.
111 static size_t
112 persistent_fwrite (const void *data, size_t len, FILE * stream)
114 size_t count;
115 size_t bytes_done = 0;
116 const char *ptr = (const char *) data;
118 if (len <= 0)
119 return 0;
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);
129 if (count <= 0)
130 return 0;
132 bytes_done += count;
133 ptr += count;
136 return bytes_done;
139 /* Report error in input */
140 static void
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 */
147 static FILE *
148 fopen_check (const char *filename, const char *flags)
150 char tmp[BUFFER_SIZE];
151 FILE *f;
153 f = fopen (filename, flags);
154 if (f == NULL)
156 g_snprintf (tmp, sizeof (tmp), "man2hlp: Cannot open file \"%s\"", filename);
157 perror (tmp);
158 exit (3);
161 return f;
164 /* Do fclose(), exit if it fails */
165 static void
166 fclose_check (FILE * f)
168 if (ferror (f))
170 perror ("man2hlp: File error");
171 exit (3);
174 if (fclose (f))
176 perror ("man2hlp: Cannot close file");
177 exit (3);
181 /* Change output line */
182 static void
183 newline (void)
185 out_row++;
186 col = 0;
187 fprintf (f_out, "\n");
190 /* Calculate the length of string */
191 static int
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 */
200 while (*(buffer))
202 c = *buffer++;
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 */
210 anchor_flag = 1;
211 /* Ugly hack to prevent loss of one space */
212 len++;
214 /* Don't add control characters to the length */
215 if (c >= 0 && c < 32)
216 continue;
217 /* Attempt to handle backslash quoting */
218 if (c == '\\' && !backslash_flag)
220 backslash_flag = 1;
221 continue;
223 backslash_flag = 0;
224 /* Increase length if not inside anchor name or link target name */
225 if (!anchor_flag && !lc_link_flag)
226 len++;
227 if (anchor_flag && c == ']')
229 /* Node anchor name ends */
230 anchor_flag = 0;
233 return len;
236 /* Output the string */
237 static void
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? */
245 if (skip_flag)
246 return;
247 /* Copying verbatim? */
248 if (verbatim_flag)
250 /* Attempt to handle backslash quoting */
251 while (*(buffer))
253 c = *buffer++;
254 if (c == '\\' && !backslash_flag)
256 backslash_flag = 1;
257 continue;
259 backslash_flag = 0;
260 fputc (c, f_out);
263 else
265 /* Split into words */
266 buffer = strtok (buffer, " \t\n");
267 /* Repeat for each word */
268 while (buffer)
270 /* Skip empty strings */
271 if (*(buffer))
273 len = string_len (buffer);
274 /* Words are separated by spaces */
275 if (col > 0)
277 fputc (' ', f_out);
278 col++;
280 else if (indentation)
282 while (col++ < indentation)
283 fputc (' ', f_out);
285 /* Attempt to handle backslash quoting */
286 while (*(buffer))
288 c = *buffer++;
289 if (c == '\\' && !backslash_flag)
291 backslash_flag = 1;
292 continue;
294 backslash_flag = 0;
295 fputc (c, f_out);
297 /* Increase column */
298 col += len;
300 /* Get the next word */
301 buffer = strtok (NULL, " \t\n");
302 } /* while */
306 /* Like print_string but with printf-like syntax */
307 static void
308 printf_string (const char *format, ...)
310 va_list args;
311 char buffer[BUFFER_SIZE];
313 va_start (args, format);
314 g_vsnprintf (buffer, sizeof (buffer), format, args);
315 va_end (args);
316 print_string (buffer);
319 /* Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE */
320 static void
321 handle_node (char *buffer, int is_sh)
323 int len, heading_level;
325 /* If we already skipped a section, don't skip another */
326 if (skip_flag == 2)
328 skip_flag = 0;
330 /* Get the command parameters */
331 buffer = strtok (NULL, "");
332 if (buffer == NULL)
334 print_error ("Syntax error: .SH: no title");
335 return;
337 else
339 /* Remove quotes */
340 if (buffer[0] == '"')
342 buffer++;
343 len = strlen (buffer);
344 if (buffer[len - 1] == '"')
346 len--;
347 buffer[len] = 0;
350 /* Calculate heading level */
351 heading_level = 0;
352 while (buffer[heading_level] == ' ')
353 heading_level++;
354 /* Heading level must be even */
355 if (heading_level & 1)
356 print_error ("Syntax error: .SH: odd heading level");
357 if (no_split_flag)
359 /* Don't start a new section */
360 newline ();
361 print_string (buffer);
362 newline ();
363 newline ();
364 no_split_flag = 0;
366 else if (skip_flag)
368 /* Skipping title and marking text for skipping */
369 skip_flag = 2;
371 else
373 buffer += heading_level;
374 if (!is_sh || !node)
376 /* Start a new section, but omit empty section names */
377 if (*buffer)
379 fprintf (f_out, "%c[%s]", CHAR_NODE_END, buffer);
380 newline ();
383 /* Add section to the linked list */
384 if (!cnode)
386 cnode = &nodes;
388 else
390 cnode->next = malloc (sizeof (nodes));
391 cnode = cnode->next;
393 cnode->node = strdup (buffer);
394 cnode->lname = NULL;
395 cnode->next = NULL;
396 cnode->heading_level = heading_level;
398 if (is_sh)
400 /* print_string() strtok()es buffer, so */
401 cnode->lname = strdup (buffer);
402 print_string (buffer);
403 newline ();
404 newline ();
406 } /* Start new section */
407 } /* Has parameters */
408 node = !is_sh;
411 /* Convert character from the macro name to the font marker */
412 static inline char
413 char_to_font (char c)
415 switch (c)
417 case 'R':
418 return CHAR_FONT_NORMAL;
419 case 'B':
420 return CHAR_FONT_BOLD;
421 case 'I':
422 return CHAR_FONT_ITALIC;
423 default:
424 return 0;
429 * Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB)
430 * Return 0 if the command wasn't recognized, 1 otherwise
432 static int
433 handle_alt_font (char *buffer)
435 char *p;
436 char *w;
437 char font[2];
438 int in_quotes = 0;
439 int alt_state = 0;
441 if (strlen (buffer) != 3)
442 return 0;
444 if (buffer[0] != '.')
445 return 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])
452 return 0;
454 p = strtok (NULL, "");
455 if (p == NULL)
457 return 1;
460 w = buffer;
461 *w++ = font[0];
463 while (*p)
466 if (*p == '"')
468 in_quotes = !in_quotes;
469 p++;
470 continue;
473 if (*p == ' ' && !in_quotes)
475 p++;
476 /* Don't change font if we are at the end */
477 if (*p != 0)
479 alt_state = !alt_state;
480 *w++ = font[alt_state];
483 /* Skip more spaces */
484 while (*p == ' ')
485 p++;
487 continue;
490 *w++ = *p++;
493 /* Turn off attributes if necessary */
494 if (font[alt_state] != CHAR_FONT_NORMAL)
495 *w++ = CHAR_FONT_NORMAL;
497 *w = 0;
498 print_string (buffer);
500 return 1;
503 /* Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP */
504 static void
505 handle_tp_ip (int is_tp)
507 if (col > 0)
508 newline ();
509 newline ();
510 if (is_tp)
512 tp_flag = 1;
513 indentation = 0;
515 else
516 indentation = 8;
519 /* Handle all the roff dot commands. See man groff_man for details */
520 static void
521 handle_command (char *buffer)
523 int len;
525 /* Get the command name */
526 strtok (buffer, " \t");
528 if (strcmp (buffer, ".SH") == 0)
530 indentation = 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)
539 no_split_flag = 1;
541 else if (strcmp (buffer, ".\\\"SKIP_SECTION\"") == 0)
543 skip_flag = 1;
545 else if (strcmp (buffer, ".\\\"LINK2\"") == 0)
547 /* Next two input lines form a link */
548 link_flag = 2;
550 else if ((strcmp (buffer, ".PP") == 0)
551 || (strcmp (buffer, ".P") == 0) || (strcmp (buffer, ".LP") == 0))
553 indentation = 0;
554 /* End of paragraph */
555 if (col > 0)
556 newline ();
557 newline ();
559 else if (strcmp (buffer, ".nf") == 0)
561 /* Following input lines are to be handled verbatim */
562 verbatim_flag = 1;
563 if (col > 0)
564 newline ();
566 else if (strcmp (buffer, ".I") == 0 || strcmp (buffer, ".B") == 0
567 || strcmp (buffer, ".SB") == 0)
569 /* Bold text or italics text */
570 char *p;
571 char *w;
572 int backslash_flag = 0;
574 /* .SB [text]
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, "");
582 if (p == NULL)
584 print_error ("Syntax error: .I | .B | .SB : no text");
585 return;
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)
595 backslash_flag = 1;
596 continue;
598 backslash_flag = 0;
599 *w++ = *p;
602 *w++ = CHAR_FONT_NORMAL;
603 *w = 0;
604 print_string (buffer);
606 else if (strcmp (buffer, ".TP") == 0)
608 handle_tp_ip (1);
610 else if (strcmp (buffer, ".IP") == 0)
612 handle_tp_ip (0);
614 else if (strcmp (buffer, ".\\\"TOPICS") == 0)
616 if (out_row > 1)
618 print_error ("Syntax error: .\\\"TOPICS must be first command");
619 return;
621 buffer = strtok (NULL, "");
622 if (buffer == NULL)
624 print_error ("Syntax error: .\\\"TOPICS: no text");
625 return;
627 /* Remove quotes */
628 if (buffer[0] == '"')
630 buffer++;
631 len = strlen (buffer);
632 if (buffer[len - 1] == '"')
634 len--;
635 buffer[len] = 0;
638 topics = strdup (buffer);
640 else if (strcmp (buffer, ".br") == 0)
642 if (col)
643 newline ();
645 else if (strncmp (buffer, ".\\\"", 3) == 0)
647 /* Comment */
649 else if (strcmp (buffer, ".TH") == 0)
651 /* Title header */
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, "");
659 if (buffer)
660 print_string (buffer);
662 else if (handle_alt_font (buffer) == 1)
664 return;
666 else
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);
672 return;
676 static struct links
678 char *linkname; /* Section name */
679 int line; /* Input line in ... */
680 const char *filename;
681 struct links *next;
682 } links, *current_link;
684 static void
685 handle_link (char *buffer)
687 static char old[80];
688 int len;
689 char *amp;
690 const char *amp_arg;
692 switch (link_flag)
694 case 1:
695 /* Old format link, not supported */
696 break;
697 case 2:
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));
703 link_flag = 3;
704 break;
705 case 3:
706 /* Second part of new format link */
707 if (buffer[0] == '.')
708 buffer++;
709 if (buffer[0] == '\\')
710 buffer++;
711 if (buffer[0] == '"')
712 buffer++;
713 len = strlen (buffer);
714 if (len && buffer[len - 1] == '"')
716 buffer[--len] = 0;
719 /* "Layout\&)," -- "Layout" should be highlighted, but not ")," */
720 amp = strstr (old, "\\&");
721 if (amp)
723 *amp = 0;
724 amp += 2;
725 amp_arg = amp;
727 else
729 amp_arg = "";
732 printf_string ("%c%s%c%s%c%s\n", CHAR_LINK_START, old,
733 CHAR_LINK_POINTER, buffer, CHAR_LINK_END, amp_arg);
734 link_flag = 0;
735 /* Add to the linked list */
736 if (current_link)
738 current_link->next = malloc (sizeof (links));
739 current_link = current_link->next;
740 current_link->next = NULL;
742 else
744 current_link = &links;
746 current_link->linkname = strdup (buffer);
747 current_link->filename = c_in;
748 current_link->line = in_row;
749 break;
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 */
768 if (argc != 4)
770 fprintf (stderr, "Usage: man2hlp file.man template_file helpfile\n");
771 return 3;
774 c_man = argv[1];
775 c_tmpl = argv[2];
776 c_out = argv[3];
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");
781 c_in = c_man;
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;
790 else
791 input_line = buffer;
793 in_row++;
794 len = strlen (input_line);
795 /* Remove terminating newline */
796 if (input_line[len - 1] == '\n')
798 len--;
799 input_line[len] = 0;
802 if (verbatim_flag)
804 /* Copy the line verbatim */
805 if (strcmp (input_line, ".fi") == 0)
807 verbatim_flag = 0;
809 else
811 print_string (input_line);
812 newline ();
815 else if (link_flag)
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);
825 else
827 /* A normal line, just output it */
828 print_string (input_line);
830 /* .TP label processed as usual line */
831 if (tp_flag)
833 if (tp_flag == 1)
835 tp_flag = 2;
837 else
839 tp_flag = 0;
840 indentation = 8;
841 if (col >= indentation)
842 newline ();
843 else
844 while (++col < indentation)
845 fputc (' ', f_out);
850 newline ();
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");
856 c_in = c_tmpl;
858 /* Repeat for each input line */
859 /* Read a line */
860 while (fgets (buffer, BUFFER_SIZE, f_tmpl))
862 if (lc_node)
864 if (*buffer && *buffer != '\n')
866 cnode->lname = strdup (buffer);
867 lc_node = strchr (cnode->lname, '\n');
868 if (lc_node)
869 *lc_node = 0;
871 lc_node = NULL;
873 else
875 lc_node = strchr (buffer, CHAR_NODE_END);
876 if (lc_node && (lc_node[1] == '['))
878 char *p = strchr (lc_node, ']');
879 if (p)
881 if (strncmp (lc_node + 1, "[main]", 6) == 0)
883 lc_node = NULL;
885 else
887 if (!cnode)
889 cnode = &nodes;
891 else
893 cnode->next = malloc (sizeof (nodes));
894 cnode = cnode->next;
896 cnode->node = strdup (lc_node + 2);
897 cnode->node[p - lc_node - 2] = 0;
898 cnode->lname = NULL;
899 cnode->next = NULL;
900 cnode->heading_level = 0;
903 else
904 lc_node = NULL;
906 else
907 lc_node = NULL;
909 fputs (buffer, f_out);
912 cont_start = ftell (f_out);
913 if (cont_start <= 0)
915 perror (c_out);
916 return 1;
919 if (topics)
920 fprintf (f_out, "\004[Contents]\n%s\n\n", topics);
921 else
922 fprintf (f_out, "\004[Contents]\n");
924 for (current_link = &links; current_link && current_link->linkname;)
926 int found = 0;
927 struct links *next = current_link->next;
929 if (strcmp (current_link->linkname, "Contents") == 0)
931 found = 1;
933 else
935 for (cnode = &nodes; cnode && cnode->node; cnode = cnode->next)
937 if (strcmp (cnode->node, current_link->linkname) == 0)
939 found = 1;
940 break;
944 if (!found)
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)
953 free (current_link);
954 current_link = next;
957 for (cnode = &nodes; cnode && cnode->node;)
959 struct node *next = cnode->next;
960 lc_node = cnode->node;
962 if (*lc_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");
967 free (cnode->node);
968 if (cnode->lname)
969 free (cnode->lname);
970 if (cnode != &nodes)
971 free (cnode);
972 cnode = next;
975 file_end = ftell (f_out);
977 /* Sanity check */
978 if ((file_end <= 0) || (file_end - cont_start <= 0))
980 perror (c_out);
981 return 1;
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);
995 if (!outfile_buffer)
996 return 1;
998 if (!persistent_fread (outfile_buffer, file_end, f_out))
1000 perror (c_out);
1001 return 1;
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))
1013 perror (c_out);
1014 return 1;
1017 /* Write part before the "Contents" node */
1018 if (!persistent_fwrite (outfile_buffer, cont_start, f_out))
1020 perror (c_out);
1021 return 1;
1024 free (outfile_buffer);
1025 fclose_check (f_out);
1026 /* Closing everything */
1028 return 0;