/* Man page to help file converter Copyright (C) 1994, 1995 Janne Kukonlehto 2002 Andrew V. Samoilov 2002 Pavel Roskin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "help.h" #include "glibcompat.h" #define BUFFER_SIZE 256 static int col = 0; /* Current output column */ static int out_row = 1; /* Current output row */ static int in_row = 0; /* Current input row */ static int no_split_flag = 0; /* Flag: Don't split section on next ".SH" */ static int skip_flag = 0; /* Flag: Skip this section. 0 = don't skip, 1 = skipping title, 2 = title skipped, skipping text */ static int link_flag = 0; /* Flag: Next line is a link */ static int verbatim_flag = 0; /* Flag: Copy input to output verbatim */ static int node = 0; /* Flag: This line is an original ".SH" */ static const char *c_out; /* Output filename */ static FILE *f_out; /* Output file */ static const char *c_in; /* Current input filename */ static int indentation; /* Indentation level, n spaces */ static int tp_flag; /* Flag: .TP paragraph 1 = this line is .TP label, 2 = first line of label description. */ static char *topics = NULL; struct node { char *node; /* Section name */ char *lname; /* Translated .SH, NULL if not translated */ struct node *next; int heading_level; }; static struct node nodes; static struct node *cnode; /* Current node */ #define MAX_STREAM_BLOCK 8192 /* * Read in blocks of reasonable size and make sure we read everything. * Failure to read everything is an error, indicated by returning 0. */ static size_t persistent_fread (void *data, size_t len, FILE *stream) { size_t count; size_t bytes_done = 0; char *ptr = (char *) data; if (len <= 0) return 0; while (bytes_done < len) { count = len - bytes_done; if (count > MAX_STREAM_BLOCK) count = MAX_STREAM_BLOCK; count = fread (ptr, 1, count, stream); if (count <= 0) return 0; bytes_done += count; ptr += count; } return bytes_done; } /* * Write in blocks of reasonable size and make sure we write everything. * Failure to write everything is an error, indicated by returning 0. */ static size_t persistent_fwrite (const void *data, size_t len, FILE *stream) { size_t count; size_t bytes_done = 0; const char *ptr = (const char *) data; if (len <= 0) return 0; while (bytes_done < len) { count = len - bytes_done; if (count > MAX_STREAM_BLOCK) count = MAX_STREAM_BLOCK; count = fwrite (ptr, 1, count, stream); if (count <= 0) return 0; bytes_done += count; ptr += count; } return bytes_done; } /* Report error in input */ static void print_error (const char *message) { fprintf (stderr, "man2hlp: %s in file \"%s\" on line %d\n", message, c_in, in_row); } /* Do fopen(), exit if it fails */ static FILE * fopen_check (const char *filename, const char *flags) { char tmp[BUFFER_SIZE]; FILE *f; f = fopen (filename, flags); if (f == NULL) { g_snprintf (tmp, sizeof (tmp), "man2hlp: Cannot open file \"%s\"", filename); perror (tmp); exit (3); } return f; } /* Do fclose(), exit if it fails */ static void fclose_check (FILE *f) { if (ferror (f)) { perror ("man2hlp: File error"); exit (3); } if (fclose (f)) { perror ("man2hlp: Cannot close file"); exit (3); } } /* Change output line */ static void newline (void) { out_row++; col = 0; fprintf (f_out, "\n"); } /* Calculate the length of string */ static int string_len (const char *buffer) { static int anchor_flag = 0; /* Flag: Inside hypertext anchor name */ static int link_flag = 0; /* Flag: Inside hypertext link target name */ int backslash_flag = 0; /* Flag: Backslash quoting */ int c; /* Current character */ int len = 0; /* Result: the length of the string */ while (*(buffer)) { c = *buffer++; if (c == CHAR_LINK_POINTER) link_flag = 1; /* Link target name starts */ else if (c == CHAR_LINK_END) link_flag = 0; /* Link target name ends */ else if (c == CHAR_NODE_END) { /* Node anchor name starts */ anchor_flag = 1; /* Ugly hack to prevent loss of one space */ len++; } /* Don't add control characters to the length */ if (c >= 0 && c < 32) continue; /* Attempt to handle backslash quoting */ if (c == '\\' && !backslash_flag) { backslash_flag = 1; continue; } backslash_flag = 0; /* Increase length if not inside anchor name or link target name */ if (!anchor_flag && !link_flag) len++; if (anchor_flag && c == ']') { /* Node anchor name ends */ anchor_flag = 0; } } return len; } /* Output the string */ static void print_string (char *buffer) { int len; /* The length of current word */ int c; /* Current character */ int backslash_flag = 0; /* Skipping lines? */ if (skip_flag) return; /* Copying verbatim? */ if (verbatim_flag) { /* Attempt to handle backslash quoting */ while (*(buffer)) { c = *buffer++; if (c == '\\' && !backslash_flag) { backslash_flag = 1; continue; } backslash_flag = 0; fputc (c, f_out); } } else { /* Split into words */ buffer = strtok (buffer, " \t\n"); /* Repeat for each word */ while (buffer) { /* Skip empty strings */ if (*(buffer)) { len = string_len (buffer); /* Change the line if about to break the right margin */ if (col + len >= HELP_TEXT_WIDTH) newline (); /* Words are separated by spaces */ if (col > 0) { fputc (' ', f_out); col++; } else if (indentation) { while (col++ < indentation) fputc (' ', f_out); } /* Attempt to handle backslash quoting */ while (*(buffer)) { c = *buffer++; if (c == '\\' && !backslash_flag) { backslash_flag = 1; continue; } backslash_flag = 0; fputc (c, f_out); } /* Increase column */ col += len; } /* Get the next word */ buffer = strtok (NULL, " \t\n"); } /* while */ } } /* Like print_string but with printf-like syntax */ static void printf_string (const char *format, ...) { va_list args; char buffer[BUFFER_SIZE]; va_start (args, format); g_vsnprintf (buffer, sizeof (buffer), format, args); va_end (args); print_string (buffer); } /* Handle NODE and .SH commands. is_sh is 1 for .SH, 0 for NODE */ static void handle_node (char *buffer, int is_sh) { int len, heading_level; /* If we already skipped a section, don't skip another */ if (skip_flag == 2) { skip_flag = 0; } /* Get the command parameters */ buffer = strtok (NULL, ""); if (buffer == NULL) { print_error ("Syntax error: .SH: no title"); return; } else { /* Remove quotes */ if (buffer[0] == '"') { buffer++; len = strlen (buffer); if (buffer[len - 1] == '"') { len--; buffer[len] = 0; } } /* Calculate heading level */ heading_level = 0; while (buffer[heading_level] == ' ') heading_level++; /* Heading level must be even */ if (heading_level & 1) print_error ("Syntax error: .SH: odd heading level"); if (no_split_flag) { /* Don't start a new section */ newline (); print_string (buffer); newline (); newline (); no_split_flag = 0; } else if (skip_flag) { /* Skipping title and marking text for skipping */ skip_flag = 2; } else { buffer += heading_level; if (!is_sh || !node) { /* Start a new section, but omit empty section names */ if (*buffer) { fprintf (f_out, "%c[%s]", CHAR_NODE_END, buffer); col++; newline (); } /* Add section to the linked list */ if (!cnode) { cnode = &nodes; } else { cnode->next = malloc (sizeof (nodes)); cnode = cnode->next; } cnode->node = strdup (buffer); cnode->lname = NULL; cnode->next = NULL; cnode->heading_level = heading_level; } if (is_sh) { /* print_string() strtok()es buffer, so */ cnode->lname = strdup (buffer); print_string (buffer); newline (); newline (); } } /* Start new section */ } /* Has parameters */ node = !is_sh; } /* Convert character from the macro name to the font marker */ static inline char char_to_font (char c) { switch (c) { case 'R': return CHAR_FONT_NORMAL; case 'B': return CHAR_FONT_BOLD; case 'I': return CHAR_FONT_ITALIC; default: return 0; } } /* * Handle alternate font commands (.BR, .IR, .RB, .RI, .BI, .IB) * Return 0 if the command wasn't recognized, 1 otherwise */ static int handle_alt_font (char *buffer) { char *p; char *w; char font[2]; int in_quotes = 0; int alt_state = 0; if (strlen (buffer) != 3) return 0; if (buffer[0] != '.') return 0; font[0] = char_to_font (buffer[1]); font[1] = char_to_font (buffer[2]); /* Exclude names with unknown characters, .BB, .II and .RR */ if (font[0] == 0 || font[1] == 0 || font[0] == font[1]) return 0; p = strtok (NULL, ""); if (p == NULL) { return 1; } w = buffer; *w++ = font[0]; while (*p) { if (*p == '"') { in_quotes = !in_quotes; p++; continue; } if (*p == ' ' && !in_quotes) { p++; /* Don't change font if we are at the end */ if (*p != 0) { alt_state = !alt_state; *w++ = font[alt_state]; } /* Skip more spaces */ while (*p == ' ') p++; continue; } *w++ = *p++; } /* Turn off attributes if necessary */ if (font[alt_state] != CHAR_FONT_NORMAL) *w++ = CHAR_FONT_NORMAL; *w = 0; print_string (buffer); return 1; } /* Handle .IP and .TP commands. is_tp is 1 for .TP, 0 for .IP */ static void handle_tp_ip (int is_tp) { if (col > 0) newline (); newline (); if (is_tp) { tp_flag = 1; indentation = 0; } else indentation = 8; } /* Handle all the roff dot commands. See man groff_man for details */ static void handle_command (char *buffer) { int len; /* Get the command name */ strtok (buffer, " \t"); if (strcmp (buffer, ".SH") == 0) { indentation = 0; handle_node (buffer, 1); } else if (strcmp (buffer, ".\\\"NODE") == 0) { handle_node (buffer, 0); } else if (strcmp (buffer, ".\\\"DONT_SPLIT\"") == 0) { no_split_flag = 1; } else if (strcmp (buffer, ".\\\"SKIP_SECTION\"") == 0) { skip_flag = 1; } else if (strcmp (buffer, ".\\\"LINK2\"") == 0) { /* Next two input lines form a link */ link_flag = 2; } else if ((strcmp (buffer, ".PP") == 0) || (strcmp (buffer, ".P") == 0) || (strcmp (buffer, ".LP") == 0)) { indentation = 0; /* End of paragraph */ if (col > 0) newline (); newline (); } else if (strcmp (buffer, ".nf") == 0) { /* Following input lines are to be handled verbatim */ verbatim_flag = 1; if (col > 0) newline (); } else if (strcmp (buffer, ".I") == 0 || strcmp (buffer, ".B") == 0 || strcmp (buffer, ".SB") == 0) { /* Bold text or italics text */ char *p; char *w; int backslash_flag = 0; /* .SB [text] * Causes the text on the same line or the text on the * next line to appear in boldface font, one point * size smaller than the default font. */ /* FIXME: text is optional, so there is no error */ p = strtok (NULL, ""); if (p == NULL) { print_error ("Syntax error: .I | .B | .SB : no text"); return; } *buffer = (buffer[1] == 'I') ? CHAR_FONT_ITALIC : CHAR_FONT_BOLD; /* Attempt to handle backslash quoting */ for (w = &buffer[1]; *p; p++) { if (*p == '\\' && !backslash_flag) { backslash_flag = 1; continue; } backslash_flag = 0; *w++ = *p; } *w++ = CHAR_FONT_NORMAL; *w = 0; print_string (buffer); } else if (strcmp (buffer, ".TP") == 0) { handle_tp_ip (1); } else if (strcmp (buffer, ".IP") == 0) { handle_tp_ip (0); } else if (strcmp (buffer, ".\\\"TOPICS") == 0) { if (out_row > 1) { print_error ("Syntax error: .\\\"TOPICS must be first command"); return; } buffer = strtok (NULL, ""); if (buffer == NULL) { print_error ("Syntax error: .\\\"TOPICS: no text"); return; } /* Remove quotes */ if (buffer[0] == '"') { buffer++; len = strlen (buffer); if (buffer[len - 1] == '"') { len--; buffer[len] = 0; } } topics = strdup (buffer); } else if (strcmp (buffer, ".br") == 0) { if (col) newline (); } else if (strncmp (buffer, ".\\\"", 3) == 0) { /* Comment */ } else if (strcmp (buffer, ".TH") == 0) { /* Title header */ } else if (strcmp (buffer, ".SM") == 0) { /* Causes the text on the same line or the text on the * next line to appear in a font that is one point * size smaller than the default font. */ buffer = strtok (NULL, ""); if (buffer) print_string (buffer); } else if (handle_alt_font (buffer) == 1) { return; } else { /* Other commands are ignored */ char warn_str[BUFFER_SIZE]; g_snprintf (warn_str, sizeof (warn_str), "Warning: unsupported command %s", buffer); print_error (warn_str); return; } } static struct links { char *linkname; /* Section name */ int line; /* Input line in ... */ const char *filename; struct links *next; } links, *current_link; static void handle_link (char *buffer) { static char old[80]; int len; char *amp; const char *amp_arg; switch (link_flag) { case 1: /* Old format link, not supported */ break; case 2: /* First part of new format link */ /* Bold text or italics text */ if (buffer[0] == '.' && (buffer[1] == 'I' || buffer[1] == 'B')) for (buffer += 2; *buffer == ' ' || *buffer == '\t'; buffer++); g_strlcpy (old, buffer, sizeof (old)); link_flag = 3; break; case 3: /* Second part of new format link */ if (buffer[0] == '.') buffer++; if (buffer[0] == '\\') buffer++; if (buffer[0] == '"') buffer++; len = strlen (buffer); if (len && buffer[len - 1] == '"') { buffer[--len] = 0; } /* "Layout\&)," -- "Layout" should be highlighted, but not ")," */ amp = strstr (old, "\\&"); if (amp) { *amp = 0; amp += 2; amp_arg = amp; } else { amp_arg = ""; } printf_string ("%c%s%c%s%c%s\n", CHAR_LINK_START, old, CHAR_LINK_POINTER, buffer, CHAR_LINK_END, amp_arg); link_flag = 0; /* Add to the linked list */ if (current_link) { current_link->next = malloc (sizeof (links)); current_link = current_link->next; current_link->next = NULL; } else { current_link = &links; } current_link->linkname = strdup (buffer); current_link->filename = c_in; current_link->line = in_row; break; } } int main (int argc, char **argv) { int len; /* Length of input line */ const char *c_man; /* Manual filename */ const char *c_tmpl; /* Template filename */ FILE *f_man; /* Manual file */ FILE *f_tmpl; /* Template file */ char buffer[BUFFER_SIZE]; /* Full input line */ char *node = NULL; char *outfile_buffer; /* Large buffer to keep the output file */ long cont_start; /* Start of [Contents] */ long file_end; /* Length of the output file */ /* Validity check for arguments */ if (argc != 4) { fprintf (stderr, "Usage: man2hlp file.man template_file helpfile\n"); return 3; } c_man = argv[1]; c_tmpl = argv[2]; c_out = argv[3]; /* First stage - process the manual, write to the output file */ f_man = fopen_check (c_man, "r"); f_out = fopen_check (c_out, "w"); c_in = c_man; /* Repeat for each input line */ while (fgets (buffer, BUFFER_SIZE, f_man)) { char *input_line; /* Input line without initial "\&" */ if (buffer[0] == '\\' && buffer[1] == '&') input_line = buffer + 2; else input_line = buffer; in_row++; len = strlen (input_line); /* Remove terminating newline */ if (input_line[len - 1] == '\n') { len--; input_line[len] = 0; } if (verbatim_flag) { /* Copy the line verbatim */ if (strcmp (input_line, ".fi") == 0) { verbatim_flag = 0; } else { print_string (input_line); newline (); } } else if (link_flag) { /* The line is a link */ handle_link (input_line); } else if (buffer[0] == '.') { /* The line is a roff command */ handle_command (input_line); } else { /* A normal line, just output it */ print_string (input_line); } /* .TP label processed as usual line */ if (tp_flag) { if (tp_flag == 1) { tp_flag = 2; } else { tp_flag = 0; indentation = 8; if (col >= indentation) newline (); else while (++col < indentation) fputc (' ', f_out); } } } newline (); fclose_check (f_man); /* First stage ends here, closing the manual */ /* Second stage - process the template file */ f_tmpl = fopen_check (c_tmpl, "r"); c_in = c_tmpl; /* Repeat for each input line */ /* Read a line */ while (fgets (buffer, BUFFER_SIZE, f_tmpl)) { if (node) { if (*buffer && *buffer != '\n') { cnode->lname = strdup (buffer); node = strchr (cnode->lname, '\n'); if (node) *node = 0; } node = NULL; } else { node = strchr (buffer, CHAR_NODE_END); if (node && (node[1] == '[')) { char *p = strchr (node, ']'); if (p) { if (strncmp (node + 1, "[main]", 6) == 0) { node = 0; } else { if (!cnode) { cnode = &nodes; } else { cnode->next = malloc (sizeof (nodes)); cnode = cnode->next; } cnode->node = strdup (node + 2); cnode->node[p - node - 2] = 0; cnode->lname = NULL; cnode->next = NULL; cnode->heading_level = 0; } } else node = NULL; } else node = NULL; } fputs (buffer, f_out); } cont_start = ftell (f_out); if (cont_start <= 0) { perror (c_out); return 1; } if (topics) fprintf (f_out, "\004[Contents]\n%s\n\n", topics); else fprintf (f_out, "\004[Contents]\n"); for (current_link = &links; current_link && current_link->linkname;) { int found = 0; struct links *next = current_link->next; if (strcmp (current_link->linkname, "Contents") == 0) { found = 1; } else { for (cnode = &nodes; cnode && cnode->node; cnode = cnode->next) { if (strcmp (cnode->node, current_link->linkname) == 0) { found = 1; break; } } } if (!found) { g_snprintf (buffer, sizeof (buffer), "Stale link \"%s\"", current_link->linkname); c_in = current_link->filename; in_row = current_link->line; print_error (buffer); } free (current_link->linkname); if (current_link != &links) free (current_link); current_link = next; } for (cnode = &nodes; cnode && cnode->node;) { char *node = cnode->node; struct node *next = cnode->next; if (*node) fprintf (f_out, " %*s\001%s\002%s\003", cnode->heading_level, "", cnode->lname ? cnode->lname : node, node); fprintf (f_out, "\n"); free (cnode->node); if (cnode->lname) free (cnode->lname); if (cnode != &nodes) free (cnode); cnode = next; } file_end = ftell (f_out); /* Sanity check */ if ((file_end <= 0) || (file_end - cont_start <= 0)) { perror (c_out); return 1; } fclose_check (f_out); fclose_check (f_tmpl); /* Second stage ends here, closing all files, note the end of output */ /* * Third stage - swap two parts of the output file. * First, open the output file for reading and load it into the memory. */ f_out = fopen_check (c_out, "r"); outfile_buffer = malloc (file_end); if (!outfile_buffer) return 1; if (!persistent_fread (outfile_buffer, file_end, f_out)) { perror (c_out); return 1; } fclose_check (f_out); /* Now the output file is in the memory */ /* Again open output file for writing */ f_out = fopen_check (c_out, "w"); /* Write part after the "Contents" node */ if (!persistent_fwrite (outfile_buffer + cont_start, file_end - cont_start, f_out)) { perror (c_out); return 1; } /* Write part before the "Contents" node */ if (!persistent_fwrite (outfile_buffer, cont_start, f_out)) { perror (c_out); return 1; } free (outfile_buffer); fclose_check (f_out); /* Closing everything */ return 0; }