/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * Makedoc's texinfo output routines. * * By Shawn Hargreaves. * * Grzegorz Adam Hankiewicz added support for auto types' cross * references generation. * * See readme.txt for copyright information. * * See allegro/docs/src/makedoc/format.txt for a brief description of * the source of _tx files. */ #include #include #include #include #include "maketexi.h" #include "makertf.h" #include "makedoc.h" #include "makemisc.h" int multiwordheaders = 0; static int _no_strip; static int _strip_indent = -1; static char **_build_multi_identifier_nodes_table(void); static void _destroy_multi_identifier_nodes_table(char **table); static void _write_textinfo_xref(FILE *f, char *xref, char **chapter_nodes, char **secondary_nodes); static int _valid_texinfo_node(char *s, char **next, char **prev); static void _output_texinfo_toc(FILE *f, int root, int body, int part); static char *_node_name(int i); static char *_first_word(char *s); static void _html_to_texinfo(FILE *f, char *p); static void _write_auto_types_xrefs(FILE *f, char **auto_types, char *found_auto_types); static int _is_auto_type_starting_letter(char c); static int _is_auto_type_allowed_letter(char c); /* write_texinfo: */ int write_texinfo(char *filename) { char buf[256]; LINE *line = head, *title_line = 0; char *p, *str, *next, *prev; char *xref[256], *chapter_nodes[256], **auto_types; char *found_auto_types, **multi_identifier_nodes; int xrefs = 0; int in_item = 0; int section_number = 0; int toc_waiting = 0; int continue_def = 0; int title_pass = 0; int in_chapter = 0; int i = 0; char up_target[256] = "Top"; FILE *f; /*printf("writing %s\n", filename);*/ f = fopen(filename, "w"); if (!f) return 1; /* First scan all the chapters of the documents and build a lookup table * with their text lines. This lookup table will be used during the * generation of @xref texinfo commands, due to textinfo's restriction * that nodes can't contain spaces, and hence remain as a single word. */ chapter_nodes[0] = 0; while (line) { if (line->flags & TEXINFO_FLAG) { p = line->text; if (line->flags & (HEADING_FLAG | CHAPTER_FLAG)) { chapter_nodes[i++] = p; chapter_nodes[i] = 0; } } line = line->next; } /* Now scan all the definitions and see where are more than one * definition per texi node, building a lookup table for the * identifiers which don't have their own node so we can reference them * correctly from other nodes. */ multi_identifier_nodes = _build_multi_identifier_nodes_table(); /* Build up a table with Allegro's structures' names (spelling?) */ auto_types = build_types_lookup_table(&found_auto_types); /* Now reinit values and let real scanning start */ line = head; while (line) { if (line->flags & TEXINFO_FLAG) { p = line->text; /* end of an ftable? */ if (in_item) { if ((*p) && (*p != ' ') && (*p != '<')) { fputs("@end ftable\n", f); in_item = 0; } } if (!in_item) { /* start a new node? */ str = strstr(p, " p) { str += strlen("flags & NONODE_FLAG) && /* Output a symbol (@@void @func()). */ (_valid_texinfo_node(buf, &next, &prev))) { if (toc_waiting) { _output_texinfo_toc(f, 0, 1, section_number-1); toc_waiting = 0; } if (xrefs > 0) { fputs("See also:@*\n", f); for (i=0; iflags & NO_INDENT_FLAG)) { if (_no_strip) { if (_strip_indent >= 0) { for (i=0; (i<_strip_indent) && (*p) && (myisspace(*p)); i++) p++; } else { if (!is_empty(p)) { _strip_indent = 0; while ((*p) && (myisspace(*p))) { _strip_indent++; p++; } } } } else { while ((*p) && (myisspace(*p))) p++; } } if (line->flags & TEXINFO_CMD_FLAG) { /* raw output of texinfo commands */ fputs(p, f); fputs("\n", f); } else if (line->flags & HTML_CMD_FLAG) { /* process HTML commands */ while (*p) { if (p[0] == '<') { _html_to_texinfo(f, p+1); while ((*p) && (*p != '>')) p++; if (*p) p++; } else p++; } } else if (line->flags & (HEADING_FLAG | CHAPTER_FLAG)) { if (toc_waiting) { _output_texinfo_toc(f, toc_waiting == 2 ? 1 : 0, 1, section_number-1); toc_waiting = 0; } /* output a section heading */ if (section_number > 0) { if (line->flags & CHAPTER_FLAG) { in_chapter = 0; strcpy(up_target, "Top"); } if (in_item) { fputs("@end ftable\n", f); in_item = 0; } if (xrefs > 0) { fputs("See also:@*\n", f); for (i=0; iflags & NOCONTENT_FLAG)) toc_waiting = 1; if (line->flags & CHAPTER_FLAG) { in_chapter = 1; strcpy(up_target, _node_name(section_number)); toc_waiting = 2; } } section_number++; } else { if (line->flags & DEFINITION_FLAG) { /* This will detect all the possible auto types of the line. * The detected types will be marked in the found_auto_types * table for later delayed output. */ int length; char *temp = strstruct(line->text, auto_types, &length, found_auto_types); while(temp) temp = strstruct(temp+length, auto_types, &length, found_auto_types); } if ((line->flags & DEFINITION_FLAG) && (!continue_def)) fputs("@item @t{", f); while (*p) { /* less-than HTML tokens */ if ((p[0] == '&') && (mytolower(p[1]) == 'l') && (mytolower(p[2]) == 't')) { fputc('<', f); p += 3; } /* greater-than HTML tokens */ else if ((p[0] == '&') && (mytolower(p[1]) == 'g') && (mytolower(p[2]) == 't')) { fputc('>', f); p += 3; } /* process other HTML tokens */ else if (p[0] == '<') { _html_to_texinfo(f, p+1); while ((*p) && (*p != '>')) p++; if (*p) p++; } /* output other characters */ else { if ((*p == '{') || (*p == '}') || (*p == '@')) fputc('@', f); fputc((unsigned char)*p, f); p++; } } if (line->flags & CONTINUE_FLAG) fputs(" ", f); else { if (line->flags & DEFINITION_FLAG) fputs("}", f); fputs("\n", f); } } } else if (line->flags & CHAPTER_END_FLAG) { in_chapter = 0; strcpy(up_target, "Top"); } else if (line->flags & NODE_FLAG) { if (in_item) { fputs("@end ftable\n", f); if (toc_waiting) { _output_texinfo_toc(f, 0, 1, section_number-1); toc_waiting = 0; } } if (_valid_texinfo_node(line->text, &next, &prev)) { /* Outputs a @hnode. */ if (xrefs > 0) { fputs("See also:@*\n", f); for (i=0; itext, next, prev, _node_name(section_number-1)); fprintf(f, "%s %s\n", in_chapter ? "@subsection" : "@section", line->text); } fputs("@ftable @asis\n", f); in_item = 1; } else if (line->flags & TOC_FLAG) { _output_texinfo_toc(f, 1, 0, 0); } else if (line->flags & INDEX_FLAG) { _output_texinfo_toc(f, 0, 1, 0); } else if (line->flags & XREF_FLAG) { xref[xrefs++] = line->text; } else if (line->flags & START_TITLE_FLAG) { /* remember where the title starts */ title_line = line; if (!title_pass) fputs("@titlepage\n", f); } else if (line->flags & END_TITLE_FLAG) { if (!title_pass) { title_pass++; fputs("@end titlepage\n@ifinfo\n", f); line = title_line; } else fputs("@end ifinfo\n", f); } continue_def = (line->flags & CONTINUE_FLAG); line = line->next; } fclose(f); _destroy_multi_identifier_nodes_table(multi_identifier_nodes); destroy_types_lookup_table(auto_types, found_auto_types); return 0; } /* _write_textinfo_xref: * Writes the cross references found in the line pointed by xref. * The line will be split with strtok to find individual references. * Before a reference is written, it's compared against the table of * chapter nodes. If it's there, a special more complete xref command is * written, since nodes are internally a single word, but to the user they * look like normal complete sentences. If the word is not there, it's * compared this time agains the secondary_nodes table. If the reference * is in this pair table (see _build_multi_identifier_nodes_table), a * special xref will be written to access the primary node correctly. */ static void _write_textinfo_xref(FILE *f, char *xref, char **chapter_nodes, char **secondary_nodes) { char *tok; assert(f); assert(xref); assert(chapter_nodes); tok = strtok(xref, ",;"); while (tok) { char **p = chapter_nodes; while ((*tok) && (myisspace(*tok))) tok++; while(*p) { if(!strincmp(tok, strip_html(*p))) break; p++; } if(*p) fprintf(f, "@xref{%s, %s}.@*\n", _first_word(tok), strip_html(*p)); else { p = secondary_nodes; while (*p) { if (!strcmp(tok, *p)) break; p += 2; } if (*p) fprintf(f, "@xref{%s, %s}.@*\n", *(p + 1), tok); else fprintf(f, "@xref{%s}.@*\n", tok); } tok = strtok(NULL, ",;"); } } /* _valid_texinfo_node: */ static int _valid_texinfo_node(char *s, char **next, char **prev) { TOC *toc = tochead; *next = *prev = ""; while (toc) { if ((!toc->root) && (toc->texinfoable)) { if (strcmp(toc->text, s) == 0) { do { toc = toc->next; } while ((toc) && ((!toc->texinfoable) || (toc->root))); if (toc) *next = toc->text; return 1; } *prev = toc->text; } toc = toc->next; } return 0; } /* _output_texinfo_toc: */ static void _output_texinfo_toc(FILE *f, int root, int body, int part) { TOC *toc; int section_number; char **ptr; char *s; int i, j; int in_chapter = 0; fprintf(f, "@menu\n"); if (root == 1 && body == 0) { toc = tochead; if (toc) toc = toc->next; while (toc) { if (toc->root == 2 || toc->root == 3) in_chapter = 0; if ((toc->root) && (toc->texinfoable) && !in_chapter) { s = _first_word(toc->text); fprintf(f, "* %s::", s); for (i=strlen(s); i<24; i++) fputc(' ', f); fprintf(f, "%s\n", toc->text); } if (toc->root == 2) in_chapter = 1; toc = toc->next; } } if (body) { toc = tochead; if (toc) toc = toc->next; if (part <= 0) { ptr = m_xmalloc(TOC_SIZE * sizeof(char *)); i = 0; while (toc) { if ((!toc->root) && (toc->texinfoable) && (i < TOC_SIZE)) ptr[i++] = toc->text; toc = toc->next; } if (i > 1) qsort(ptr, i, sizeof(char *), scmp); for (j=0; jroot) && (!toc->otherfile) && (toc->texinfoable)) section_number++; toc = toc->next; } if (root) { while (toc) { if (toc->texinfoable && toc->root) { s = _first_word(toc->text); fprintf(f, "* %s::", s); for (i=strlen(s); i<24; i++) fputc(' ', f); fprintf(f, "%s\n", toc->text); } toc = toc->next; if (toc->root == 2 || toc->root == 3) break; } } else { while ((toc) && (!toc->root)) { if (toc->texinfoable) fprintf(f, "* %s::\n", toc->text); toc = toc->next; } } } } fprintf(f, "@end menu\n"); } /* _node_name: */ static char *_node_name(int i) { TOC *toc = tochead; if (toc) toc = toc->next; while (toc) { if ((toc->root) && (toc->texinfoable)) { i--; if (!i) return _first_word(toc->text); } toc = toc->next; } return ""; } /* _first_word: */ static char *_first_word(char *s) { static char buf[256]; int i; if (multiwordheaders) return strncpy(buf, s, 255); for (i=0; s[i] && s[i] != ' '; i++) buf[i] = s[i]; buf[i] = 0; return buf; } /* _html_to_texinfo: */ static void _html_to_texinfo(FILE *f, char *p) { char buf[256]; int i = 0; while ((*p) && (*p != '>')) buf[i++] = *(p++); buf[i] = 0; if (mystricmp(buf, "pre") == 0) { fputs("@example\n", f); _no_strip = 1; _strip_indent = -1; } else if (mystricmp(buf, "/pre") == 0) { fputs("@end example\n", f); _no_strip = 0; _strip_indent = -1; } else if (mystricmp(buf, "br") == 0) { fputs("@*", f); } else if (mystricmp(buf, "hr") == 0) { fputs("----------------------------------------------------------------------", f); } else if (mystricmp(buf, "p") == 0) { fputs("\n", f); } else if (mystricmp(buf, "ul") == 0) { fputs("\n@itemize @bullet\n", f); } else if (mystricmp(buf, "/ul") == 0) { fputs("@end itemize\n", f); } else if (mystricmp(buf, "li") == 0) { fputs("@item ", f); } } /* build_types_lookup_table: * Automatic function which will scan the document from the beginning in * search of structures/types, which will be returned in a NULL terminated * array. Optionally, if found_table is not NULL, it will contain a simple * array with that many entries like the returned table. This array will * later be used to mark which autotypes were found (useful for texinfo). */ char **build_types_lookup_table(char **found_table) { LINE *line = head; int i = 0; char **table; table = m_xmalloc(sizeof(char*)); table[0] = 0; /* Scan document in memory finding definition lines with upper case types */ while (line) { if ((line->flags & STRUCT_FLAG)) { char *p = strchr(line->text, '\"'); /* Find start of ... part after name */ table[++i] = 0; } line = line->next; } if (found_table) *found_table = memset(m_xmalloc(i + 1), 0, i + 1); return table; } /* _build_multi_identifier_nodes_table: * Automatic function which will scan the document from the beginning in * search of nodes which feature multiple identifiers. Since only the first * identifier of the texinfo node is indexed, all the others have to be * noted down. In the case of referencing them, a special xref has to be * written, which points to the secondary identifier through the first one. * Last lines of _write_texinfo_xref show how this is done. This function * returns a table with pairs of strings in the form (secondary, primary), * where secondary is the secondary identifier name, and primary is the * texinfo indexed identifier. The table has to be freed some time later * along with it's data. It's null terminated. */ static char **_build_multi_identifier_nodes_table(void) { LINE *line = head; int i = 0; char **table; table = m_xmalloc(sizeof(char*)*2); table[0] = 0; table[1] = 0; /* Scan document in memory finding definition lines creating multi * identifier nodes */ while (line) { if ((line->flags & DEFINITION_FLAG)) { char *first_identifier = get_clean_ref_token(line->text); line = line->next; while (line->flags & DEFINITION_FLAG) { if (strstr(line->text, "text); table[i++] = m_strdup(first_identifier); table[i] = table[i+1] = 0; } line = line->next; } free(first_identifier); } line = line->next; } return table; } /* _destroy_multi_identifier_nodes_table: * Given a null terminated table, frees it's string data and later the * table itself. */ static void _destroy_multi_identifier_nodes_table(char **table) { int f; assert(table); for (f = 0; table[f]; f++) free(table[f]); free(table); } /* destroy_types_lookup_table: * Get's rid of the previously created auto_types and found_table. * found_auto_types can be NULL. */ void destroy_types_lookup_table(char **auto_types, char *found_auto_types) { char **p = auto_types; assert(auto_types); if(found_auto_types) free(found_auto_types); while(*p) { free(*p); p++; } free(auto_types); } /* strstruct: * Complex function which replicates the logic behind the strstr function: * It will search in line, any of existant auto_types. If no auto_type is * found, it returns NULL. Otherwise will return a pointer to the beginning * of the auto_type in line, and the length of the auto_type will be stored * at the variable pointed by length. found_auto_types can be NULL, or a * table with as many entries as auto_types. In the latter case, the found * auto_type entry will be marked with a positive number. */ char *strstruct(char *line, char **auto_types, int *length, char *found_auto_types) { assert(line); assert(auto_types); assert(length); while(*line) { if(_is_auto_type_starting_letter(*line)) { char *end = line; char **compare = auto_types; int found = 0; /* Find the end of the presumably found auto_type */ while(_is_auto_type_allowed_letter(*end)) end++; *length = end - line; /* Now compare with the auto_types table */ while(*compare) { if(*length == (signed)strlen(*compare) && !strncmp(line, *compare, *length)) { /* The auto_type must be followed by a blank space */ if(*(line + *length) == ' ') { if(found_auto_types) found_auto_types[found] = 1; return line; /* Found, return it's position. */ } } compare++; found++; } line = end; } else line++; } return 0; } /* _write_auto_types_xrefs: * After normal references have been written, call this with a table of * auto_types, and a table of found_auto_types till the moment. xrefs will * be generated, and all the entries of the found_auto_types will be * zeroed for the next run. */ static void _write_auto_types_xrefs(FILE *f, char **auto_types, char *found_auto_types) { while(*auto_types) { if(*found_auto_types) { *found_auto_types = 0; fprintf(f, "@xref{%s}.@*\n", *auto_types); } auto_types++; found_auto_types++; } } /* _is_auto_type_starting_letter: * Detects if the letter could be the beginning of an auto_type. Note the * ugly hack used for Allegro's fixed and al_ffblk types, which should be * capitalized. */ static int _is_auto_type_starting_letter(char c) { if (c == 'f' || c == 'a' || (c >= 'A' && c <= 'Z')) return 1; return 0; } /* _is_auto_type_starting_letter: * Detects if the letter could be part of an auto_type. Note the ugly hack * used for Allegro's fixed and al_ffblk types, which should be capitalized. */ static int _is_auto_type_allowed_letter(char c) { if (_is_auto_type_starting_letter(c)) return 1; if (c >= '0' && c <= '9') return 1; if (c == '_' || c == 'i' || c == 'x' || c == 'e' || c == 'd' || c == 'l' || c == 'b' || c == 'k') return 1; return 0; }