To render a documentation index into HTML form.
§1. Having accumulated the lemmas, it's time to sort them and write the index as it will be seen by the reader.
void Indexes::write_example_index(OUTPUT_STREAM, compiled_documentation *cd) { Indexes::write_general_index_inner(OUT, cd, TRUE); } void Indexes::write_general_index(OUTPUT_STREAM, compiled_documentation *cd) { Indexes::write_general_index_inner(OUT, cd, FALSE); } void Indexes::write_general_index_inner(OUTPUT_STREAM, compiled_documentation *cd, int just_examples) { int NL = 0; index_lemma **lemma_list = IndexingData::sort(cd, &NL); Condense single-line categories1.1 HTML_OPEN_WITH("div", "class=\"generalindex\""); Render the index in sorted order1.2; HTML_CLOSE("div"); }
§1.1. At this point we rely on two facts: the lemma list is sorted, and we've created the ancestors of every term. So "X" (or "X: I") cannot be followed by "Y: J" without a "Y" in between. Given that, we can find condensable lines by looking for peaks: a lemma whose term-length is longer than both the one before and the one after. In that case, the preceding lemma can be skipped *if* it has no references of its own to display.
Condense single-line categories1.1 =
for (int i=0; i<NL; i++) { index_lemma *il = lemma_list[i]; if (il->lemma_source != BODY_LEMMASOURCE) continue; int depth = IndexTerms::subterms(il->term); int nextdepth = 0; if (i+1<NL) nextdepth = IndexTerms::subterms(lemma_list[i+1]->term); if (nextdepth >= depth) { continue; next line is in the same category } int excount = 1; while (i-excount >= 0) { index_lemma *previl = lemma_list[i-excount]; if (previl->lemma_source != BODY_LEMMASOURCE) break; don't squash a category line with references if (IndexLemmas::has_references(previl)) break; int prevdepth = IndexTerms::subterms(previl->term); don't squash a line that isn't a category ancestor of this one if (prevdepth != depth-excount) break; don't squash a category line if the upcoming line is in the same category if (prevdepth < nextdepth) break; okay, squash it, and increase the show count of the current line excount++; previl->categories_to_show = 0; il->categories_to_show = excount; } }
- This code is used in §1.
§1.2. Render the index in sorted order1.2 =
Indexes::alphabet_row(OUT, cd, 1); HTML_OPEN_WITH("table", "class=\"indextable\""); inchar32_t current_incipit = 0; for (int i=0; i<NL; i++) { index_lemma *il = lemma_list[i]; if ((just_examples) && (il->lemma_source == BODY_LEMMASOURCE)) continue; if ((!just_examples) && (il->lemma_source == EG_ALT_LEMMASOURCE)) continue; inchar32_t incipit = Str::get_first_char(il->sorting_key); if (Characters::isalpha(incipit)) incipit = Characters::toupper(incipit); else incipit = '#'; if (incipit != current_incipit) { if (current_incipit != 0) End a block of the index1.2.2; current_incipit = incipit; Indexes::note_letter(cd, current_incipit); Start a block of the index1.2.1; } Place an anchor for the index entry1.2.3; Render an index entry1.2.4; } if (current_incipit != 0) End a block of the index1.2.2; HTML_CLOSE("table"); Indexes::alphabet_row(OUT, cd, 2);
- This code is used in §1.
§1.2.1. Start a block of the index1.2.1 =
HTML_OPEN("tr"); HTML_OPEN_WITH("td", "class=\"letterblock\""); TEMPORARY_TEXT(inc) if (current_incipit == '#') WRITE_TO(inc, "NN"); else PUT_TO(inc, current_incipit); HTML::anchor(OUT, inc); Indexes::majuscule_heading(OUT, cd, inc, TRUE); DISCARD_TEXT(inc) HTML_CLOSE("td"); HTML_OPEN("td");
- This code is used in §1.2.
§1.2.2. End a block of the index1.2.2 =
HTML_CLOSE("td"); HTML_CLOSE("tr");
- This code is used in §1.2 (twice).
§1.2.3. Place an anchor for the index entry1.2.3 =
TEMPORARY_TEXT(anc) WRITE_TO(anc, "l%d", il->allocation_id); HTML::anchor(OUT, anc); DISCARD_TEXT(anc)
- This code is used in §1.2.
§1.2.4. Render an index entry1.2.4 =
if (il->categories_to_show == 0) continue; int depth = IndexTerms::subterms(il->term); if (depth == 0) internal_error("no indexing categories"); IFM_example *EG = NULL; Find example relevant to this entry1.2.4.1; int indent = 4*(depth - il->categories_to_show); measured in em-spaces HTML_OPEN_WITH("p", "class=\"indexentry\" style=\"margin-left: %dem;\"", indent); int ccount = 0; for (int catnum = depth - il->categories_to_show; catnum < depth; catnum++) { if (ccount) WRITE(", "); Render one category1.2.4.2; ccount++; } WRITE(" "); int lc = 0; Render the references1.2.4.3; Render the cross-references1.2.4.4; HTML_CLOSE("p");
- This code is used in §1.2.
§1.2.4.1. Find example relevant to this entry1.2.4.1 =
if (il->lemma_source != BODY_LEMMASOURCE) { index_reference *ref; LOOP_OVER_LINKED_LIST(ref, index_reference, il->references) if (ref->posn.example) EG = ref->posn.example; }
- This code is used in §1.2.4.
§1.2.4.2. Render one category1.2.4.2 =
indexing_category *ic = il->term.categories[catnum]; if (ic == NULL) internal_error("no indexing category"); text_stream *itext = il->term.texts[catnum]; if (itext == NULL) internal_error("no indexing text"); TEMPORARY_TEXT(lemma_wording) Resolve backslash escapes in plain text1.2.4.2.1; if (ic->cat_bracketed) Deal with unescaped brackets if the category makes them significant1.2.4.2.2; Restore any escaped round brackets1.2.4.2.3; Render the lemma text1.2.4.2.4; Render the category gloss1.2.4.2.5; DISCARD_TEXT(lemma_wording)
- This code is used in §1.2.4.
§1.2.4.2.1. Backslash before a character makes it literal. In particular we reassign escaped open and close brackets to make them impossible to confuse with unescaped ones:
define SAVED_OPEN_BRACKET 0x0086 Unicode "start of selected area" define SAVED_CLOSE_BRACKET 0x0087 Unicode "end of selected area"
Resolve backslash escapes in plain text1.2.4.2.1 =
text_stream *plain_text = itext; for (int i=0, L = Str::len(plain_text); i<L; i++) { inchar32_t c = Str::get_at(plain_text, i); if (c == '\\') { inchar32_t n = Str::get_at(plain_text, ++i); if (n == '(') n = SAVED_OPEN_BRACKET; if (n == ')') n = SAVED_CLOSE_BRACKET; PUT_TO(lemma_wording, n); } else PUT_TO(lemma_wording, c); } Indexes::escape_HTML_characters_in(lemma_wording);
- This code is used in §1.2.4.2.
§1.2.4.2.2. Deal with unescaped brackets if the category makes them significant1.2.4.2.2 =
match_results mr = Regexp::create_mr(); while (Regexp::match(&mr, lemma_wording, U"(%c*?)%(%(%+ %+%)%)(%c*)")) { Str::clear(lemma_wording); WRITE_TO(lemma_wording, "%S<span class=\"index%Sbracketed\">%c+ +%c</span>%S", mr.exp[0], ic->cat_name, SAVED_OPEN_BRACKET, SAVED_CLOSE_BRACKET, mr.exp[1]); } while (Regexp::match(&mr, lemma_wording, U"(%c*?)%(%(%- %-%)%)(%c*)")) { Str::clear(lemma_wording); WRITE_TO(lemma_wording, "%S<span class=\"index%Sbracketed\">%c- -%c</span>%S", mr.exp[0], ic->cat_name, SAVED_OPEN_BRACKET, SAVED_CLOSE_BRACKET, mr.exp[1]); } TEMPORARY_TEXT(L) TEMPORARY_TEXT(R) if (ic->cat_unbracketed == FALSE) PUT_TO(L, SAVED_OPEN_BRACKET); if (ic->cat_unbracketed == FALSE) PUT_TO(R, SAVED_CLOSE_BRACKET); while (Regexp::match(&mr, lemma_wording, U"(%c*?)%((%c*?)%)(%c*)")) { Str::clear(lemma_wording); WRITE_TO(lemma_wording, "%S<span class=\"index%Sbracketed\">%S%S%S</span>%S", mr.exp[0], ic->cat_name, L, mr.exp[1], R, mr.exp[2]); } DISCARD_TEXT(L) DISCARD_TEXT(R) Regexp::dispose_of(&mr);
- This code is used in §1.2.4.2.
§1.2.4.2.3. Restore any escaped round brackets1.2.4.2.3 =
LOOP_THROUGH_TEXT(pos, lemma_wording) { inchar32_t d = Str::get(pos); if (d == SAVED_OPEN_BRACKET) Str::put(pos, '('); if (d == SAVED_CLOSE_BRACKET) Str::put(pos, ')'); }
- This code is used in §1.2.4.2.
§1.2.4.2.4. Render the lemma text1.2.4.2.4 =
if (il->lemma_source == EG_NAME_LEMMASOURCE) { HTML_OPEN("b"); if (EG) HTML_OPEN_WITH("a", "href=\"%S\"", EG->URL); } HTML_OPEN_WITH("span", "class=\"index%S\"", ic->cat_name); WRITE("%S", lemma_wording); HTML_CLOSE("span"); if (il->lemma_source == EG_NAME_LEMMASOURCE) { if (EG) HTML_CLOSE("a"); HTML_CLOSE("b"); }
- This code is used in §1.2.4.2.
§1.2.4.2.5. Render the category gloss1.2.4.2.5 =
if (Str::len(ic->cat_glossed) > 0) WRITE(" <span class=\"indexgloss\">%S</span>", ic->cat_glossed);
- This code is used in §1.2.4.2.
§1.2.4.3. Render the references1.2.4.3 =
index_reference *ref; LOOP_OVER_LINKED_LIST(ref, index_reference, il->references) { if (lc++ > 0) WRITE(", "); int volume_number = ref->posn.volume_number; markdown_item *S = ref->posn.latest; IFM_example *E = ref->posn.example; if ((E) && (S == NULL)) S = E->cue; if ((S == NULL) && (E == NULL)) internal_error("unknown destination in index reference"); text_stream *link_class = I"indexlink"; if (volume_number > 0) link_class = I"indexlinkalt"; TEMPORARY_TEXT(link) text_stream *A = NULL; if (S) { for (int i=0; i<Str::len(S->stashed); i++) { inchar32_t c = Str::get_at(S->stashed, i); if (c == ':') break; if ((Characters::isdigit(c)) || (c == '.')) PUT_TO(link, c); } A = MarkdownVariations::URL_for_heading(S); } if (E) { if (S) WRITE_TO(link, " "); WRITE_TO(link, "ex %S", E->insignia); if (EG == NULL) A = E->URL; } Indexes::general_link(OUT, link_class, A, link); DISCARD_TEXT(link) }
- This code is used in §1.2.4.
§1.2.4.4. Render the cross-references1.2.4.4 =
if (LinkedLists::len(il->cross_references) > 0) { if (lc > 0) WRITE("; "); HTML_OPEN_WITH("span", "class=\"indexsee\""); WRITE("see "); if (lc > 0) WRITE("also "); HTML_CLOSE("span"); int c = 0; index_cross_reference *xref; LOOP_OVER_LINKED_LIST(xref, index_cross_reference, il->cross_references) { if (c++ > 0) WRITE("; "); index_lemma *ils = IndexingData::retrieve_lemma(cd, xref->P); if (ils == NULL) internal_error("no such xref"); TEMPORARY_TEXT(url) WRITE_TO(url, "#l%d", ils->allocation_id); TEMPORARY_TEXT(see) IndexTerms::paraphrase(see, cd, xref->P); Indexes::general_link(OUT, I"indexseelink", url, see); DISCARD_TEXT(url) DISCARD_TEXT(see) } }
- This code is used in §1.2.4.
void Indexes::general_link(OUTPUT_STREAM, text_stream *cl, text_stream *to, text_stream *text) { HTML::begin_link_with_class(OUT, cl, to); WRITE("%S", text); HTML::end_link(OUT); }
void Indexes::escape_HTML_characters_in(text_stream *text) { TEMPORARY_TEXT(modified) for (int i=0, L=Str::len(text); i<L; i++) { inchar32_t c = Str::get_at(text, i); switch (c) { case '\"': WRITE_TO(modified, """); break; case '<': WRITE_TO(modified, "<"); break; case '>': WRITE_TO(modified, ">"); break; case '&': if (Str::get_at(text, i+1) == '#') { PUT_TO(modified, c); break; } int j = i+1; while (Characters::isalnum(Str::get_at(text, j))) j++; if ((j > i+1) && (Str::get_at(text, j) == ';')) { PUT_TO(modified, c); break; } WRITE_TO(modified, "&"); break; default: PUT_TO(modified, c); break; } } Str::copy(text, modified); DISCARD_TEXT(modified) }
void Indexes::note_letter(compiled_documentation *cd, inchar32_t c) { inchar32_t i = c - (inchar32_t) 'A'; if (i<26) cd->id.letters_taken[i] = TRUE; } void Indexes::alphabet_row(OUTPUT_STREAM, compiled_documentation *cd, int sequence) { switch (sequence) { case 1: for (int i=0; i<26; i++) cd->id.letters_taken[i] = FALSE; break; case 2: { int faked = FALSE; for (int i=0; i<26; i++) if (cd->id.letters_taken[i] == FALSE) { if (faked == FALSE) { faked = TRUE; HTML_OPEN("p"); } TEMPORARY_TEXT(singleton) PUT_TO(singleton, (inchar32_t) ('A'+i)); HTML::anchor(OUT, singleton); DISCARD_TEXT(singleton) } if (faked) { HTML_CLOSE("p"); } break; } } if (cd->id.use_simplified_letter_rows) { HTML_OPEN("p"); } else { HTML_OPEN_WITH("table", "class=\"fullwidth\""); HTML_OPEN("tr"); HTML_OPEN_WITH("td", "class=\"letterinrow\""); } Indexes::general_link(OUT, I"letterlink", I"#A", I"A"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#B", I"B"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#C", I"C"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#D", I"D"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#E", I"E"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#F", I"F"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#G", I"G"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#H", I"H"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#I", I"I"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#J", I"J"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#K", I"K"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#L", I"L"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#M", I"M"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#N", I"N"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#O", I"O"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#P", I"P"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#Q", I"Q"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#R", I"R"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#S", I"S"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#T", I"T"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#U", I"U"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#V", I"V"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#W", I"W"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#X", I"X"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#Y", I"Y"); Between5.1; Indexes::general_link(OUT, I"letterlink", I"#Z", I"Z"); if (cd->id.use_simplified_letter_rows) { HTML_CLOSE("p"); } else { HTML_CLOSE("td"); HTML_CLOSE("tr"); HTML_CLOSE("table"); } }
if (cd->id.use_simplified_letter_rows) WRITE(" / "); else { HTML_CLOSE("td"); HTML_OPEN_WITH("td", "class=\"letterinrow\""); }
- This code is used in §5 (25 times).
§6. This is mainly used for the typographically dramatic link letters A, B, C, ... but can also make fatter typographically dramatic headings, if it's stretched in width and a longer text is supplied.
void Indexes::majuscule_heading(OUTPUT_STREAM, compiled_documentation *cd, text_stream *display_text, int single_letter) { if (cd->id.use_simplified_letter_rows) { if (single_letter == 1) { HTML::begin_div_with_class_S(OUT, I"majuscule", __FILE__, __LINE__); } else { HTML::begin_div_with_class_S(OUT, I"stretchymajuscule", __FILE__, __LINE__); } HTML_OPEN_WITH("span", "class=\"majusculelettering\""); WRITE("%S", display_text); HTML_CLOSE("span"); HTML::end_div(OUT); } else { WRITE("<b>%S</b>", display_text); } }