/*****************************************************************************/ /* navig8.c Navigate through the wasDOC document. This includes generating a primary Table of Content from the rendered HTML. Secondary TOCs for each major section. Navigation icon/links with each major section. An "Index" for the document. VERSION HISTORY --------------- 28-FEB-2019 MGD initial */ /*****************************************************************************/ #include "wasdoc.h" extern int dbug; /*****************************************************************************/ /* Generate fragment references from the text of all document headings. */ int navig8AnchorHeadings (struct wasdoc_st *docptr) { int at, length; char *cptr, *hptr, *sptr, *zptr; char anchor [512], text [256]; if (dbug>0) dbugThis (FI_LI, "navig8AnchorHeadings()"); if (!(hptr = docptr->html)) RETURN_FI_LI (docptr, EILSEQ) for (; *hptr; hptr++) { if (*hptr != '<') continue; if (MATCH4 (hptr, "", 14)) break; } if (!*atptr) return (0); tocptr = calloc (1, sizeof(struct wasdoc_st)); if (!tocptr) exit (vaxc$errno); wasDocHtmlPrint (tocptr, "
\n\ \n", docptr->setTocCols); while (*hptr) { /* search for fragid); if (!hptr) goto EILSEQ_error; if (navig8Heading0 (docptr->fragid)) continue; hptr = navig8HeadingText (hptr, text, sizeof(text)); if (!hptr) goto EILSEQ_error; /* scan over any TOC numbering */ period = 0; for (cptr = text; *cptr == '.' || isdigit(*cptr); cptr++) if (*cptr == '.') period = *cptr; if (period) { *cptr++ = '\0'; iptr = text; } else { cptr = text; iptr = ""; } if (docptr->setTocForm[0] >= '1' && docptr->setTocForm[0] <= '9') { if (docptr->setTocForm[1]) /* numbering and heading separated by repeated characters */ wasDocHtmlPrint (tocptr, "
%s %s\ %s\n", navig8ChunkPath(docptr,-1), docptr->fragid, iptr, docptr->setTocForm+1, ch == '1' ? " TOCmajor" : "", navig8ChunkPath(docptr,-1), docptr->fragid, cptr); else /* just empty space */ wasDocHtmlPrint (tocptr, "
%s\ %s\n", navig8ChunkPath(docptr,-1), ch == '1' ? " TOCmajor" : "", docptr->fragid, iptr, navig8ChunkPath(docptr,-1), docptr->fragid, cptr); } else /* anything else (e.g. HTML entity) is just used as-is */ wasDocHtmlPrint (tocptr, "
%s%s\n", ch == '1' ? " class=\"TOCmajor\"" : "", navig8ChunkPath(docptr,-1), docptr->fragid, docptr->setTocForm, text); } if (!tocptr->hlength) goto EILSEQ_error; wasDocHtmlPrint (tocptr, "
\n
\n"); wasDocInsertAt (docptr, atptr - docptr->html, sizeof("")-1, tocptr->html, tocptr->hlength); goto exit_now; EILSEQ_error: docptr->hparse = hptr - docptr->html; retval = EILSEQ; SET_FI_LI (docptr) goto exit_now; exit_now: if (tocptr) { if (tocptr->html) free (tocptr->html); free (tocptr); } return (retval); } /*****************************************************************************/ /* Generate a secondary (sub-)TOC after each

from the generated HTML. ~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~ Secondary TOCs can include secondary headings not included in a primary TOC. Secondary heading entries can be excluded by prefixing their |0...| with an additional zero, i.e. |00..|. See renderInsertHeading(). */ int navig8SubTOC (struct wasdoc_st *docptr) { int cnt, error = 0, retval = 0; char *cptr, *hptr, *h1ptr, *sptr; char ch; char fragnum [FRAGID_SIZE], text [256]; struct wasdoc_st *tocptr; if (dbug>0) dbugThis (FI_LI, "navig8SubTOC()"); if (!docptr->setToc2) return (0); if (!(hptr = docptr->html)) RETURN_FI_LI (docptr, EILSEQ) tocptr = calloc (1, sizeof(struct wasdoc_st)); if (!tocptr) exit (vaxc$errno); while (*hptr) { for (; *hptr; hptr++) { if (*hptr != '<') continue; if (MATCH4 (hptr, " if the reference description should be substituted, or if the text following is the description. See renderInsertUrl(). */ int navig8ReferAll (struct wasdoc_st *docptr) { int at, error; char *hptr; if (dbug>0) dbugThis (FI_LI, "navig8ReferAll()"); if (!(hptr = docptr->html)) RETURN_FI_LI (docptr, EILSEQ) for (; *hptr; hptr++) { if (*hptr != '<') continue; if (MATCH4 (hptr, " which itself is inside the quotes of an . If the fragment ID is prefixed by an asterisk then leave the current link description as-is, if a plus symbol then replace with the next heading description or following text. See renderInsertLink(). |9some helpful reference| may be located in the middle of some text and so not have an associated (nearby) heading. For these cases keep track of the most recent heading before the link. Then extract a representative leading portion of the text and quote that along with the section heading it occurs in. */ int navig8ReferAt ( struct wasdoc_st *docptr, int at ) { int alen, error, excise, hit = 0, idlen, inplace; char *aptr, *cptr, *soaptr, *hptr, *sptr, *zptr; char heading [256], id1 [64], id2 [64], refer [512], text [128]; if (!(hptr = docptr->html)) RETURN_FI_LI (docptr, EILSEQ) hptr += at; if (dbug>0) dbugThis (FI_LI, "navig8referAt() {%s}", dbugMax(hptr)); /* should now point to this */ if (!MATCH7 (soaptr = hptr, "\">"))) { if (sptr < zptr) *sptr++ = *hptr; hptr++; } *sptr = '\0'; if (!MATCH8 (hptr,"-- -->\">")) RETURN_FI_LI (docptr, EILSEQ) hptr += 8; if (inplace) { /* excising only the href= */ excise = hptr - soaptr; } else { /* excising all the href= up to end of link description */ for (; *hptr; hptr++) { if (*hptr != '<') hptr++; if (MATCH4 (hptr, "")) break; } if (!*hptr) RETURN_FI_LI (docptr, EILSEQ) excise = hptr - soaptr; } /**********/ /* search */ /**********/ /* search for the reference */ for (hptr = docptr->html; *hptr; hptr++) { if (*hptr != '<') continue; if (!MATCH7 (hptr, "fragid); if (!hptr) RETURN_FI_LI (docptr, EILSEQ) /* use the already in-place description */ alen = sprintf (refer, "%s#%s\">", navig8ChunkPath(docptr,-1), id1); } else { /* generate reference description */ error = navig8ReferText (docptr, hptr, text, sizeof(text)); if (error) return (error); alen = sprintf (refer, "%s#%s\">%s", navig8ChunkPath(docptr,-1), id1, text+1); } error = wasDocInsertAt (docptr, at, excise, refer, alen); if (error) RETURN_FI_LI (docptr, error) return (0); } /*****************************************************************************/ /* Generate a document index (of sorts) based on all fragment IDs. These are collated alaphetically but then listed as occur in the text. */ int navig8Index (struct wasdoc_st *docptr) { int at, error = 0, len, maxch = 0, retval = 0; char ch, ch2; char *cptr, *hptr, *sptr, *zptr; char buf [64], fragid [FRAGID_SIZE], prevtext [256], text [256]; struct wasdoc_st *tocptr[256]; if (dbug>0) dbugThis (FI_LI, "navig8Index()"); if (!docptr->html) RETURN_FI_LI (docptr, EILSEQ) /* find where the INDEX should be inserted */ for (hptr = docptr->html; *hptr; hptr++) { if (*hptr != '<') continue; if (!MATCH4 (hptr, "", 14)) break; } if (!*hptr) return (0); at = hptr - docptr->html; /* initialise the array of pointers */ memset (tocptr, 0, sizeof(tocptr)); hptr = docptr->html; while (*hptr) { /* search for next fragment ID */ for (; *hptr; hptr++) { if (*hptr != '<') continue; if (MATCH2 (hptr, " */ if (MATCH7 (hptr, "fragid, fragid); /*************/ /* the entry */ /*************/ /* generate reference description */ error = navig8ReferText (docptr, hptr, text, sizeof(text)); if (error) goto exit_now; /* which alphabetic group */ if (isalpha (text[0])) ch = toupper (text[0]); else ch = '+'; /* try and avoid obviously duplicate entries */ if (!strcmp (text, prevtext)) continue; ch2 = ' '; if (!tocptr[ch]) { /* allocate a structure for this character */ tocptr[ch2 = ch] = calloc (1, sizeof(struct wasdoc_st)); if (!tocptr[ch]) exit (vaxc$errno); if (ch > maxch) maxch = ch; } error = wasDocHtmlPrint (tocptr[ch], "%c\ %s\n", ch2, navig8ChunkPath(docptr,-1), fragid, text+1); if (error) { SET_FI_LI(docptr); goto exit_now; } zptr = (sptr = prevtext) + sizeof(prevtext)-1; for (cptr = text; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } /****************/ /* insert index */ /****************/ len = sprintf (buf, "
\n\ \n", docptr->setIdxCols); wasDocInsertAt (docptr, at, 14, /* */ buf, len); at += len; for (ch = 'A'; ch <= maxch; ch++) { if (!tocptr[ch]) continue; wasDocInsertAt (docptr, at, 0, tocptr[ch]->html, tocptr[ch]->hlength); at += tocptr[ch]->hlength; if (ch == 'Z') ch = 0x7f; } if (tocptr[ch = '+']) { wasDocInsertAt (docptr, at, 0, tocptr[ch]->html, tocptr[ch]->hlength); at += tocptr[ch]->hlength; } wasDocInsertAt (docptr, at, 0, "
\n
\n", 16); goto exit_now; EILSEQ_error: docptr->hparse = hptr - docptr->html; error = EILSEQ; goto exit_now; exit_now: if (error) retval = error; for (ch = '+'; ch <= maxch; ch++) { if (!tocptr[ch]) continue; if (tocptr[ch]->html) free (tocptr[ch]->html); free (tocptr[ch]); if (ch == '+') ch = 'A'; } return (retval); } /*****************************************************************************/ /* Generate text for use as the description for an internal reference. Scan forward from the current reference looking for a visible heading or some other tag to indicate there is no next heading before intervening text. If a heading then check if it has a section number (1.2.3.4). If it does then that is suitable. If not then it needs to placed into context (i.e. associated with a section-numbered heading). If some other tag (i.e. text) then extract that text and return that. */ int navig8ReferText ( struct wasdoc_st *docptr, char *hptr, char *buf, int size ) { char ch; char *aptr, *cptr, *sptr, *zptr; char heading [256], text [128]; if (dbug>0) dbugThis (FI_LI, "navig8referText() {%s}", dbugMax(hptr)); /* look for any following heading allowing |9 references */ hptr = aptr = navig8ReferForward (docptr, hptr, 1); if (!hptr) RETURN_FI_LI (docptr, EILSEQ) hptr = navig8GetHeadingId (hptr, docptr->fragid); if (!hptr) RETURN_FI_LI (docptr, EILSEQ) hptr = navig8HeadingText (hptr, text, sizeof(text)); if (!hptr) RETURN_FI_LI (docptr, EILSEQ) /* if a |9...| then use the document narrative following */ if (MATCH4 (text, "...") || MATCH4 (text+1, "...")) { /* get collating character following the '9' (i.e. 'A', 'B', ... 'C') */ ch = text[0]; /* and retrieve that narative ignoring markup HTML tags */ aptr = navig8JustText (hptr, text, sizeof(text)/2); if (!aptr) RETURN_FI_LI (docptr, EILSEQ) zptr = (sptr = buf) + size - 1; *sptr++ = ch; for (cptr = """; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = text; *cptr && sptr < zptr; *sptr++ = *cptr++); /* put the text into the preceding numbered heading context */ hptr = navig8ReferBackward (docptr, hptr); if (hptr) { hptr = navig8GetHeadingId (hptr, docptr->fragid); if (!hptr) RETURN_FI_LI (docptr, EILSEQ) hptr = navig8HeadingText (hptr, heading, sizeof(heading)); if (!hptr) RETURN_FI_LI (docptr, EILSEQ) for (cptr = ""  in  "; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = heading; *cptr && sptr < zptr; *sptr++ = *cptr++); } else for (cptr = """; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } else { /* skip over white space and section numbering if heading */ for (cptr = text; *cptr; cptr++) if (!(isdigit(*cptr) || *cptr == '.' || isspace(*cptr))) break; /* and retrieve the collating character of that heading */ ch = *cptr; /* if no section number */ if (navig8Heading0 (docptr->fragid)) { /* look forward then backward for a heading excluding |9s */ hptr = navig8ReferForward (docptr, aptr, 0); if (!hptr) hptr = navig8ReferBackward (docptr, aptr); if (hptr) { /* use this heading for the context */ hptr = navig8SkipHeadingId (hptr); if (!hptr) RETURN_FI_LI (docptr, EILSEQ) hptr = navig8HeadingText (hptr, heading, sizeof(heading)); if (!hptr) RETURN_FI_LI (docptr, EILSEQ) zptr = (sptr = buf) + size - 1; *sptr++ = ch; for (cptr = "‘"; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = text; *cptr && isspace(*cptr); cptr++); while (*cptr && sptr < zptr) *sptr++ = *cptr++; for (cptr = "’  in  "; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = heading; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } else { /* no preceding heading so use just the text */ zptr = (sptr = buf) + size - 1; *sptr++ = ch; for (cptr = text; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } } else { /* has section number so has implicit context */ zptr = (sptr = buf) + size - 1; *sptr++ = ch; for (cptr = text; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } } if (sptr > zptr) { sptr = buf; for (cptr = "[OVERFLOW]"; *cptr; *sptr++ = *cptr++); } return (0); } /*****************************************************************************/ /* Look forward for the next visible heading. If allow9 then allow |9 entires to be used as a target. Otherwise not. */ char* navig8ReferForward ( struct wasdoc_st *docptr, char *hptr, int allow9 ) { char *aptr; char fragid [FRAGID_SIZE]; /* if called already at heading then just scan over that */ if (MATCH2 (hptr, " docptr->html) { if (MATCH2 (hptr, " buf && *(sptr-1) != ' ') *sptr++ = ' '; } while (sptr > buf && isspace(*(sptr-1))) sptr--; *sptr = '\0'; /* trim potentially hanging partial words and sentences */ for (sptr = buf; *sptr; sptr++); while (sptr > buf && !isspace(*(sptr-1))) sptr--; while (sptr > buf && isspace(*(sptr-1))) sptr--; if (sptr == buf) return (NULL); *sptr = '\0'; while (sptr > buf && !(*sptr == '.' && isspace(*(sptr+1)))) sptr--; if (sptr > buf) *(sptr+1) = '\0'; else { /* indicate there is more to the sentence */ while (*sptr) sptr++; for (cptr = "..."; *cptr; *sptr++ = *cptr++); *sptr = '\0'; } return (cptr); } /*****************************************************************************/ /* Locate where each section begin. Before that location insert either a page break (horizontal rule) or if chunked, optional navigation arrows. At the new section insert optional navigation arrows. */ int navig8Paginate (struct wasdoc_st *docptr) { int at, error, plusat, number, size; char *cptr, *hptr; char buf [1024], fragid [FRAGID_SIZE], text [256]; if (dbug>0) dbugThis (FI_LI, "navig8Paginate()\n"); if (!(docptr->setNavigate || docptr->setPaginate)) return (0); if (!(hptr = docptr->html)) RETURN_FI_LI (docptr, EILSEQ) while (*hptr) { /* look for next fragment ID */ for (; *hptr; hptr++) { if (*hptr != '<') continue; if (MATCH4 (hptr, "")) break; *sptr++ = *hptr++; } *sptr = '\0'; if (sptr >= zptr) return (NULL); return (hptr + 4); } /*****************************************************************************/ /* Skip over the heading ID string. */ char* navig8SkipHeadingId (char *hptr) { if (dbug>0) dbugThis (FI_LI, "navig8SkipHeadingId() {%s}", dbugMax(hptr)); if (!hptr) return (NULL); if (!MATCH2 (hptr, "")) return (hptr + 4); return (NULL); } /*****************************************************************************/ /* Return true if the ID indicates |0 or |9. */ int navig8Heading0 (char *idptr) { if (dbug>0) dbugThis (FI_LI, "navig8Heading0() {%s}", dbugMax(idptr)); for (int cnt = 4; cnt; cnt--) { while (isdigit(*idptr)) idptr++; if (*idptr++ != '.') return (0); } while (*idptr == '0' || *idptr == '.') idptr++; return (isdigit(*idptr)); } /*****************************************************************************/ /* Return true if the ID is a cross-reference (i.e. |9). */ int navig8Heading9 (char *idptr) { if (dbug>0) dbugThis (FI_LI, "navig8Heading9() {%s}", dbugMax(idptr)); for (int cnt = 5; cnt; cnt--) { while (isdigit(*idptr)) idptr++; if (*idptr++ != '.') return (0); } return (*idptr != '0'); } /*****************************************************************************/ /* Parse out and buffer the heading text. Return a pointer to the next character following that text, or NULL to indicate the document HTML was not understood. Must be called pointing to the character immediately following the commented fragment string that follows the heading "".. */ char* navig8HeadingText ( char *hptr, char *buf, int size ) { char *cptr, *sptr, *zptr; if (dbug>0) dbugThis (FI_LI, "navig8HeadingText()"); zptr = (sptr = buf) + size - 1; if (sptr >= zptr) return (NULL); *sptr = '\0'; if (!hptr) return (hptr); if (memcmp (hptr, "", 21)) return (NULL); for (hptr += 21; *hptr && *hptr != '<' && sptr < zptr; *sptr++ = *hptr++); *sptr = '\0'; if (sptr >= zptr) return (NULL); if (!MATCH7 (hptr, "")) return (NULL); hptr += 7; if (memcmp (hptr, "", 21)) return (NULL); hptr += 21; if (sptr > buf) *sptr++ = ' '; while (*hptr && sptr < zptr) { if (*hptr == '<') { /* don't be fooled by any internal highlight spans */ if (MATCH10 (hptr, "= zptr) { sptr = buf; for (cptr = "[OVERFLOW]"; *cptr && sptr < zptr; *sptr++ = *cptr++); } /* in case it overflowed just ensure were'e at the end of the heading */ for (; *hptr; hptr++) { if (*hptr != '<') continue; if (MATCH10 (hptr, "0) dbugThis (FI_LI, "%d {%s}", strlen(buf), buf); return (hptr + 7); } /*****************************************************************************/ /* Grab the full heading. */ char* navig8HeadingMain ( char *hptr, char *buf, int size ) { char *cptr, *sptr, *zptr; if (dbug>0) dbugThis (FI_LI, "navig8HeadingMain() {%s}", dbugMax(hptr)); zptr = (sptr = buf) + size - 1; if (sptr >= zptr) return (NULL); *sptr = '\0'; if (!hptr) return (hptr); if (!MATCH2 (hptr, "= zptr) { sptr = buf; for (cptr = "[OVERFLOW]"; *cptr && sptr < zptr; *sptr++ = *cptr++); } for (; *hptr; hptr++) if (*hptr == '<' && MATCH3 (hptr, "0) dbugThis (FI_LI, "%d {%s}", strlen(buf), buf); return (hptr + 5); } /*****************************************************************************/ /* When parsing the HTML retrieve the source file name from an appropriate comment. Used for error reporting and what-have-you. Return a pointer to the next character after the comment. */ char* navig8Source ( struct wasdoc_st *docptr, char *hptr ) { char *cptr, *sptr, *zptr; if (!MATCH4 (cptr = hptr, "")) cptr++; if (*cptr) cptr += 4; return (cptr); } /*****************************************************************************/