To collate material generated by the weaver into finished, fully-woven files.
§1. Collation. This is the process of reading a template file, substituting material into placeholders in it, and writing the result.
The collater needs to operate as a little processor interpreting a meta-language all of its very own, with a stack for holding nested repeat loops, and a program counter and — well, and nothing else to speak of, in fact, except for the slightly unusual way that loop variables provide context by changing the subject of what is discussed rather than by being accessed directly.
Collation always takes place under the umbrella of a specific weave order, and
begins with a call to this front-end: the content in template_filename is
collated into OUT in the context of wv. The output material will eventually
be used in the file wv->current_weave_file, and we need that to be known so
that any links used can correctly be directed.
void Collater::collate(text_stream *OUT, weave_order *wv, filename *template_filename) { if (wv == NULL) internal_error("collation without weave order"); if (wv->current_weave_file == NULL) internal_error("weave order has no file context"); Collater::collate_r(OUT, wv, template_filename, NULL); }
§2. That then makes recursive use of this function, which should not otherwise be called. Note that it can take input either from a template file, or from a WCL declaration (an ability we need in order to collate Navigation links).
void Collater::collate_r(text_stream *OUT, weave_order *wv, filename *template_filename, wcl_declaration *template_wcl) { int inps = 0; if (template_filename) inps++; if (template_wcl) inps++; if (inps != 1) internal_error("exactly one input source should be supplied"); collater_state state = Collater::initial_state(template_filename, template_wcl, wv); Collater::process(OUT, &state); }
define TRACE_COLLATER_EXECUTION FALSE /* set true for debugging */
define MAX_TEMPLATE_LINES 8192 /* maximum number of lines in template */
define CI_STACK_CAPACITY 8 /* maximum recursion of chapter/section iteration */
classdef collater_state {
struct weave_order *wv;
struct text_stream *tlines[MAX_TEMPLATE_LINES];
int no_tlines;
int repeat_stack_level[CI_STACK_CAPACITY];
struct linked_list_item *repeat_stack_variable[CI_STACK_CAPACITY];
struct linked_list_item *repeat_stack_threshold[CI_STACK_CAPACITY];
int repeat_stack_startpos[CI_STACK_CAPACITY];
int sp; /* And this is our stack pointer for tracking of loops */
int inside_navigation_submenu;
struct filename *errors_at;
struct filename *into_file;
struct linked_list *modules; /* of ls_module */
} collater_state;
- The structure collater_state is accessed in 5/twot, 5/wt, 5/fm, 5/ptf, 5/tf, 5/hf, 5/df and here.
§4. Note the unfortunate maximum size limit on the template file. It means that really humungous Javascript files in plugins might have trouble, though if so, they can always be subdivided.
collater_state Collater::initial_state(filename *template_filename, wcl_declaration *template_wcl, weave_order *wv) { collater_state cls; cls.wv = wv; cls.no_tlines = 0; cls.sp = 0; cls.inside_navigation_submenu = FALSE; cls.errors_at = template_filename; cls.modules = NEW_LINKED_LIST(ls_module); if (wv->weave_web) { int c = LinkedLists::len(wv->weave_web->main_module->dependencies); if (c > 0) Form the list of imported modules4.1; } if (template_wcl) Read in the WCL declaration as lines4.2 else Read in the source file containing the contents page template4.3; return cls; }
§4.1. Form the list of imported modules4.1 =
ls_module **module_array = Memory::calloc(c, sizeof(ls_module *), ARRAY_SORTING_MREASON); ls_module *M; int d=0; LOOP_OVER_LINKED_LIST(M, ls_module, wv->weave_web->main_module->dependencies) module_array[d++] = M; Collater::sort_web(wv->weave_web); qsort(module_array, (size_t) c, sizeof(ls_module *), Collater::sort_comparison); for (int d=0; d<c; d++) ADD_TO_LINKED_LIST(module_array[d], ls_module, cls.modules); Memory::I7_free(module_array, ARRAY_SORTING_MREASON, c*((int) sizeof(ls_module *)));
- This code is used in §4.
§4.2. Read in the WCL declaration as lines4.2 =
text_stream *line; LOOP_OVER_LINKED_LIST(line, text_stream, template_wcl->declaration_lines) if (cls.no_tlines < MAX_TEMPLATE_LINES) cls.tlines[cls.no_tlines++] = Str::duplicate(line); if (cls.no_tlines >= MAX_TEMPLATE_LINES) PRINT("Warning: template in declaration truncated after %d line(s)\n", cls.no_tlines);
- This code is used in §4.
§4.3. Read in the source file containing the contents page template4.3 =
TextFiles::read(template_filename, FALSE, "can't find contents template", TRUE, Collater::temp_line, NULL, &cls); if (TRACE_COLLATER_EXECUTION) PRINT("Read template <%f>: %d line(s)\n", template_filename, cls.no_tlines); if (cls.no_tlines >= MAX_TEMPLATE_LINES) PRINT("Warning: template <%f> truncated after %d line(s)\n", template_filename, cls.no_tlines);
- This code is used in §4.
void Collater::temp_line(text_stream *line, text_file_position *tfp, void *v_ies) { collater_state *cls = (collater_state *) v_ies; if (cls->no_tlines < MAX_TEMPLATE_LINES) cls->tlines[cls->no_tlines++] = Str::duplicate(line); }
void Collater::process(text_stream *OUT, collater_state *cls) { int lpos = 0; /* This is our program counter: a line number in the template */ while (lpos < cls->no_tlines) { match_results mr = Regexp::create_mr(); TEMPORARY_TEXT(tl) Str::copy(tl, cls->tlines[lpos++]); /* Fetch the line at the program counter and advance */ Make any necessary substitutions to turn tl into final output6.1; WRITE("%S\n", tl); /* Copy the now finished line to the output */ DISCARD_TEXT(tl) CYCLE: ; Regexp::dispose_of(&mr); } if (cls->inside_navigation_submenu) WRITE("</ul>"); cls->inside_navigation_submenu = FALSE; }
§6.1. Make any necessary substitutions to turn tl into final output6.1 =
if (Regexp::match(&mr, tl, U"(%c*?) ")) Str::copy(tl, mr.exp[0]); /* Strip trailing spaces */ if (TRACE_COLLATER_EXECUTION) Print line and contents of repeat stack6.1.1; if ((Regexp::match(&mr, tl, U"%[%[(%c+)%]%]")) || (Regexp::match(&mr, tl, U" %[%[(%c+)%]%]"))) { TEMPORARY_TEXT(command) Str::copy(command, mr.exp[0]); Deal with a Select command6.1.2; Deal with an If command6.1.3; Deal with an Else command6.1.4; Deal with a Repeat command6.1.5; Deal with a Repeat End command6.1.6; DISCARD_TEXT(command) } Skip line if inside a failed conditional6.1.8; Skip line if inside an empty loop6.1.7; Make substitutions of square-bracketed variables in line6.1.11;
- This code is used in §6.
Print line and contents of repeat stack6.1.1 =
PRINT("%04d: %S\nStack:", lpos-1, tl); for (int j=0; j<cls->sp; j++) { if (cls->repeat_stack_level[j] == CHAPTER_LEVEL) PRINT(" %d: %S/%S", j, ((ls_chapter *) CONTENT_IN_ITEM(cls->repeat_stack_variable[j], ls_chapter))->ch_range, ((ls_chapter *) CONTENT_IN_ITEM(cls->repeat_stack_threshold[j], ls_chapter))->ch_range); else if (cls->repeat_stack_level[j] == SECTION_LEVEL) PRINT(" %d: %S/%S", j, WebRanges::of(((ls_section *) CONTENT_IN_ITEM(cls->repeat_stack_variable[j], ls_section))), WebRanges::of(((ls_section *) CONTENT_IN_ITEM(cls->repeat_stack_threshold[j], ls_section)))); } PRINT("\n");
- This code is used in §6.1.
§6.1.2. We start the direct commands with Select, which is implemented as a one-iteration loop in which the loop variable has the given section or chapter as its value during the sole iteration.
Deal with a Select command6.1.2 =
match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, command, U"Select (%c*)")) { ls_chapter *C; ls_section *S; LOOP_OVER_LINKED_LIST(C, ls_chapter, cls->wv->weave_web->chapters) LOOP_OVER_LINKED_LIST(S, ls_section, C->sections) if (Str::eq(WebRanges::of(S), mr.exp[0])) { Collater::start_CI_loop(cls, SECTION_LEVEL, S_item, S_item, lpos); Regexp::dispose_of(&mr); goto CYCLE; } LOOP_OVER_LINKED_LIST(C, ls_chapter, cls->wv->weave_web->chapters) if (Str::eq(C->ch_range, mr.exp[0])) { Collater::start_CI_loop(cls, CHAPTER_LEVEL, C_item, C_item, lpos); Regexp::dispose_of(&mr); goto CYCLE; } Errors::at_position("don't recognise the chapter or section abbreviation range", cls->errors_at, lpos); Regexp::dispose_of(&mr); goto CYCLE; }
- This code is used in §6.1.
Deal with an If command6.1.3 =
if (Regexp::match(&mr, command, U"If (%c*)")) { text_stream *condition = mr.exp[0]; int level = IF_FALSE_LEVEL; if (Str::eq(condition, I"Chapters")) { if (cls->wv->weave_web->chaptered) level = IF_TRUE_LEVEL; } else if (Str::eq(condition, I"Modules")) { if (LinkedLists::len(cls->modules) > 0) level = IF_TRUE_LEVEL; } else if (Str::eq(condition, I"Module Page")) { ls_module *M = CONTENT_IN_ITEM( Collater::heading_topmost_on_stack(cls, MODULE_LEVEL), ls_module); if ((M) && (Colonies::find(cls->wv->weave_colony, M->module_name))) level = IF_TRUE_LEVEL; } else if (Str::eq(condition, I"Module Purpose")) { ls_module *M = CONTENT_IN_ITEM( Collater::heading_topmost_on_stack(cls, MODULE_LEVEL), ls_module); if (M) { TEMPORARY_TEXT(url) TEMPORARY_TEXT(purpose) WRITE_TO(url, "%p", M->module_location); Readme::write_var(purpose, url, I"Purpose"); if (Str::len(purpose) > 0) level = IF_TRUE_LEVEL; DISCARD_TEXT(url) DISCARD_TEXT(purpose) } } else if (Str::eq(condition, I"Chapter Purpose")) { ls_chapter *C = CONTENT_IN_ITEM( Collater::heading_topmost_on_stack(cls, CHAPTER_LEVEL), ls_chapter); if ((C) && (Str::len(C->rubric) > 0)) level = IF_TRUE_LEVEL; } else if (Str::eq(condition, I"Section Purpose")) { ls_section *S = CONTENT_IN_ITEM( Collater::heading_topmost_on_stack(cls, SECTION_LEVEL), ls_section); if (Str::len(LiterateSource::unit_purpose(S->literate_source)) > 0) level = IF_TRUE_LEVEL; } else if (Str::eq(condition, I"Navigation")) { if (cls->wv->navigation) level = IF_TRUE_LEVEL; } else { Errors::at_position("don't recognise the condition", cls->errors_at, lpos); } Collater::start_CI_loop(cls, level, NULL, NULL, lpos); Regexp::dispose_of(&mr); goto CYCLE; }
- This code is used in §6.1.
§6.1.4. Deal with an Else command6.1.4 =
if (Regexp::match(&mr, command, U"Else")) { if (cls->sp <= 0) { Errors::at_position("Else without If", cls->errors_at, lpos); goto CYCLE; } switch (cls->repeat_stack_level[cls->sp-1]) { case SECTION_LEVEL: case CHAPTER_LEVEL: Errors::at_position("Else not matched with If", cls->errors_at, lpos); break; case IF_TRUE_LEVEL: cls->repeat_stack_level[cls->sp-1] = IF_FALSE_LEVEL; break; case IF_FALSE_LEVEL: cls->repeat_stack_level[cls->sp-1] = IF_TRUE_LEVEL; break; } Regexp::dispose_of(&mr); goto CYCLE; }
- This code is used in §6.1.
Deal with a Repeat command6.1.5 =
int loop_level = 0; if (Regexp::match(&mr, command, U"Repeat Module")) loop_level = MODULE_LEVEL; if (Regexp::match(&mr, command, U"Repeat Chapter")) loop_level = CHAPTER_LEVEL; if (Regexp::match(&mr, command, U"Repeat Section")) loop_level = SECTION_LEVEL; if (loop_level != 0) { linked_list_item *from = NULL, *to = NULL; linked_list_item *CI = FIRST_ITEM_IN_LINKED_LIST(chapter, cls->wv->weave_web->chapters); while ((CI) && (CONTENT_IN_ITEM(CI, ls_chapter)->imported)) CI = NEXT_ITEM_IN_LINKED_LIST(CI, ls_chapter); if (loop_level == MODULE_LEVEL) Begin a module repeat6.1.5.1; if (loop_level == CHAPTER_LEVEL) Begin a chapter repeat6.1.5.2; if (loop_level == SECTION_LEVEL) Begin a section repeat6.1.5.3; Collater::start_CI_loop(cls, loop_level, from, to, lpos); goto CYCLE; }
- This code is used in §6.1.
§6.1.5.1. Begin a module repeat6.1.5.1 =
from = FIRST_ITEM_IN_LINKED_LIST(ls_module, cls->modules); to = LAST_ITEM_IN_LINKED_LIST(ls_module, cls->modules);
- This code is used in §6.1.5.
§6.1.5.2. Begin a chapter repeat6.1.5.2 =
from = CI; to = LAST_ITEM_IN_LINKED_LIST(chapter, cls->wv->weave_web->chapters); if (Str::eq(cls->wv->weave_range, I"0") == FALSE) { ls_chapter *C; LOOP_OVER_LINKED_LIST(C, ls_chapter, cls->wv->weave_web->chapters) if (Str::eq(C->ch_range, cls->wv->weave_range)) { from = C_item; to = from; break; } }
- This code is used in §6.1.5.
§6.1.5.3. Begin a section repeat6.1.5.3 =
ls_chapter *within_chapter = CONTENT_IN_ITEM(Collater::heading_topmost_on_stack(cls, CHAPTER_LEVEL), ls_chapter); if (within_chapter == NULL) { if (CI) { ls_chapter *C = CONTENT_IN_ITEM(CI, ls_chapter); from = FIRST_ITEM_IN_LINKED_LIST(ls_section, C->sections); } ls_chapter *LC = LAST_IN_LINKED_LIST(ls_chapter, cls->wv->weave_web->chapters); if (LC) to = LAST_ITEM_IN_LINKED_LIST(ls_section, LC->sections); } else { from = FIRST_ITEM_IN_LINKED_LIST(ls_section, within_chapter->sections); to = LAST_ITEM_IN_LINKED_LIST(ls_section, within_chapter->sections); }
- This code is used in §6.1.5.
Deal with a Repeat End command6.1.6 =
int end_form = -1; if (Regexp::match(&mr, command, U"End Repeat")) end_form = 1; if (Regexp::match(&mr, command, U"End Select")) end_form = 2; if (Regexp::match(&mr, command, U"End If")) end_form = 3; if (end_form > 0) { if (cls->sp <= 0) { Errors::at_position("stack underflow on contents template", cls->errors_at, lpos); goto CYCLE; } switch (cls->repeat_stack_level[cls->sp-1]) { case MODULE_LEVEL: case CHAPTER_LEVEL: case SECTION_LEVEL: if (end_form == 3) { Errors::at_position("End If not matched with If", cls->errors_at, lpos); goto CYCLE; } break; case IF_TRUE_LEVEL: case IF_FALSE_LEVEL: if (end_form != 3) { Errors::at_position("If not matched with End If", cls->errors_at, lpos); goto CYCLE; } break; } switch (cls->repeat_stack_level[cls->sp-1]) { case MODULE_LEVEL: End a module repeat6.1.6.1; break; case CHAPTER_LEVEL: End a chapter repeat6.1.6.2; break; case SECTION_LEVEL: End a section repeat6.1.6.3; break; case IF_TRUE_LEVEL: End an If6.1.6.4; break; case IF_FALSE_LEVEL: End an If6.1.6.4; break; } goto CYCLE; }
- This code is used in §6.1.
§6.1.6.1. End a module repeat6.1.6.1 =
linked_list_item *CI = cls->repeat_stack_variable[cls->sp-1]; if (CI == cls->repeat_stack_threshold[cls->sp-1]) Collater::end_CI_loop(cls); else { cls->repeat_stack_variable[cls->sp-1] = NEXT_ITEM_IN_LINKED_LIST(CI, chapter); lpos = cls->repeat_stack_startpos[cls->sp-1]; /* Back round loop */ }
- This code is used in §6.1.6.
§6.1.6.2. End a chapter repeat6.1.6.2 =
linked_list_item *CI = cls->repeat_stack_variable[cls->sp-1]; if (CI == cls->repeat_stack_threshold[cls->sp-1]) Collater::end_CI_loop(cls); else { cls->repeat_stack_variable[cls->sp-1] = NEXT_ITEM_IN_LINKED_LIST(CI, chapter); lpos = cls->repeat_stack_startpos[cls->sp-1]; /* Back round loop */ }
- This code is used in §6.1.6.
§6.1.6.3. End a section repeat6.1.6.3 =
linked_list_item *SI = cls->repeat_stack_variable[cls->sp-1]; if ((SI == cls->repeat_stack_threshold[cls->sp-1]) || (NEXT_ITEM_IN_LINKED_LIST(SI, ls_section) == NULL)) Collater::end_CI_loop(cls); else { cls->repeat_stack_variable[cls->sp-1] = NEXT_ITEM_IN_LINKED_LIST(SI, ls_section); lpos = cls->repeat_stack_startpos[cls->sp-1]; /* Back round loop */ }
- This code is used in §6.1.6.
Collater::end_CI_loop(cls);
- This code is used in §6.1.6 (twice).
Skip line if inside an empty loop6.1.7 =
for (int rstl = cls->sp-1; rstl >= 0; rstl--) if (cls->repeat_stack_level[cls->sp-1] == SECTION_LEVEL) { linked_list_item *SI = cls->repeat_stack_threshold[cls->sp-1]; if (NEXT_ITEM_IN_LINKED_LIST(SI, ls_section) == cls->repeat_stack_variable[cls->sp-1]) goto CYCLE; }
- This code is used in §6.1.
§6.1.8. Skip line if inside a failed conditional6.1.8 =
for (int j=cls->sp-1; j>=0; j--) if (cls->repeat_stack_level[j] == IF_FALSE_LEVEL) goto CYCLE;
- This code is used in §6.1.
§6.1.9. If called with the non-conditional levels, the following function returns
the topmost item. It's never called for IF_TRUE_LEVEL or IF_FALSE_LEVEL.
linked_list_item *Collater::heading_topmost_on_stack(collater_state *cls, int level) { for (int rstl = cls->sp-1; rstl >= 0; rstl--) if (cls->repeat_stack_level[rstl] == level) return cls->repeat_stack_variable[rstl]; return NULL; }
§6.1.10. This is the function for starting a loop or code block, which stacks up the details, and similarly for ending it by popping them again:
define MODULE_LEVEL 1
define CHAPTER_LEVEL 2
define SECTION_LEVEL 3
define IF_TRUE_LEVEL 4
define IF_FALSE_LEVEL 5
void Collater::start_CI_loop(collater_state *cls, int level, linked_list_item *from, linked_list_item *to, int pos) { if (cls->sp < CI_STACK_CAPACITY) { cls->repeat_stack_level[cls->sp] = level; cls->repeat_stack_variable[cls->sp] = from; cls->repeat_stack_threshold[cls->sp] = to; cls->repeat_stack_startpos[cls->sp++] = pos; } } void Collater::end_CI_loop(collater_state *cls) { cls->sp--; }
§6.1.11. Variable substitutions. We can now forget about this tiny stack machine: the one task left is to take a line from the template, and make substitutions of variables into its square-bracketed parts.
Note that we do not allow this to recurse, i.e., if [[X]] substitutes into
text which itself contains a [[...]] notation, then we do not expand that
inner one. If we did, then the value of the bibliographic variable [[Code]],
used by the HTML renderer, would cause a modest-sized explosion on some pages.
Make substitutions of square-bracketed variables in line6.1.11 =
TEMPORARY_TEXT(rewritten) int slen, spos; while ((spos = Regexp::find_expansion(tl, '[', '[', ']', ']', &slen)) >= 0) { TEMPORARY_TEXT(varname) TEMPORARY_TEXT(substituted) TEMPORARY_TEXT(tail) Str::substr(rewritten, Str::start(tl), Str::at(tl, spos)); Str::substr(varname, Str::at(tl, spos+2), Str::at(tl, spos+slen-2)); Str::substr(tail, Str::at(tl, spos+slen), Str::end(tl)); match_results mr = Regexp::create_mr(); if (Bibliographic::data_exists(cls->wv->weave_web, varname)) { if (Str::eq(varname, I"Weave Content")) cls->wv->weave_content_position = Str::len(rewritten) + Str::len(OUT); WRITE_TO(substituted, "%S", Bibliographic::get_datum(cls->wv->weave_web, varname)); } else if (Regexp::match(&mr, varname, U"Navigation")) { Substitute Navigation6.1.11.1; } else if (Regexp::match(&mr, varname, U"Breadcrumbs")) { Substitute Breadcrumbs6.1.11.2; } else if (Regexp::match(&mr, varname, U"Search Box")) { Substitute Search Box6.1.11.3; } else if (Str::eq_wide_string(varname, U"Plugins")) { Substitute Plugins6.1.11.4; } else if (Regexp::match(&mr, varname, U"Complete (%c+)")) { text_stream *detail = mr.exp[0]; Substitute a detail about the complete PDF6.1.11.5; } else if (Regexp::match(&mr, varname, U"Module (%c+)")) { text_stream *detail = mr.exp[0]; Substitute a Module6.1.11.6; } else if (Regexp::match(&mr, varname, U"Chapter (%c+)")) { text_stream *detail = mr.exp[0]; Substitute a Chapter6.1.11.7; } else if (Regexp::match(&mr, varname, U"Section (%c+)")) { text_stream *detail = mr.exp[0]; Substitute a Section6.1.11.8; } else if (Regexp::match(&mr, varname, U"Docs")) { Substitute a Docs6.1.11.9; } else if (Regexp::match(&mr, varname, U"Assets")) { Substitute an Assets6.1.11.10; } else if (Regexp::match(&mr, varname, U"URL \"(%c+)\"")) { text_stream *link_text = mr.exp[0]; Substitute a URL6.1.11.11; } else if (Regexp::match(&mr, varname, U"Link \"(%c+)\"")) { text_stream *link_text = mr.exp[0]; Substitute a Link6.1.11.12; } else if (Regexp::match(&mr, varname, U"Menu \"(%c+)\"")) { text_stream *menu_name = mr.exp[0]; Substitute a Menu6.1.11.13; } else if (Regexp::match(&mr, varname, U"Item \"(%c+)\"")) { text_stream *item_name = mr.exp[0]; int icon_size = 18; text_stream *icon_text = NULL; Look for icon text6.1.11.14; text_stream *link_text = item_name; Substitute a member Item6.1.11.15; } else if (Regexp::match(&mr, varname, U"Item \"(%c+)\" -> (%c+)")) { text_stream *item_name = mr.exp[0]; text_stream *link_text = mr.exp[1]; int icon_size = 18; text_stream *icon_text = NULL; Look for icon text6.1.11.14; Substitute a general Item6.1.11.16; } else if (Regexp::match(&mr, varname, U"Home \"(%c+)\" -> (%c+)")) { text_stream *item_name = mr.exp[0]; text_stream *link_text = mr.exp[1]; int icon_size = 18; text_stream *icon_text = NULL; Look for icon text6.1.11.14; Substitute a home Item6.1.11.17; } else if (Regexp::match(&mr, varname, U"Optional (%c+)")) { if (Bibliographic::data_exists(cls->wv->weave_web, mr.exp[0])) WRITE_TO(substituted, "%S", Bibliographic::get_datum(cls->wv->weave_web, mr.exp[0])); } else { WRITE_TO(substituted, "%S", varname); if (Regexp::match(&mr, varname, U"%i+%c*")) PRINT("Warning: unable to resolve command '%S'\n", varname); } Regexp::dispose_of(&mr); Str::clear(tl); WRITE_TO(rewritten, "%S", substituted); WRITE_TO(tl, "%S", tail); DISCARD_TEXT(tail) DISCARD_TEXT(varname) DISCARD_TEXT(substituted) } WRITE_TO(rewritten, "%S", tl); Str::clear(tl); Str::copy(tl, rewritten); DISCARD_TEXT(rewritten)
- This code is used in §6.1.
§6.1.11.1. [[Navigation]] substitutes to the content of the sidebar navigation file;
this will recursively call The Collater, in fact.
Substitute Navigation6.1.11.1 =
if (cls->wv->navigation) { Collater::collate_r(substituted, cls->wv, NULL, cls->wv->navigation); } else { PRINT("Warning: no sidebar links will be generated, as -navigation is unset"); }
- This code is used in §6.1.11.
Substitute Breadcrumbs6.1.11.2 =
Colonies::drop_initial_breadcrumbs(substituted, cls->wv->weave_colony, cls->wv->current_weave_file, cls->wv->breadcrumbs);
- This code is used in §6.1.11.
§6.1.11.3. Substitute Search Box6.1.11.3 =
WRITE("%S", cls->wv->current_inscriptions[SEARCH_BOX_WEAVEINSCRIPTION]);
- This code is used in §6.1.11.
§6.1.11.4. Substitute Plugins6.1.11.4 =
Assets::include_relevant_plugins(substituted, cls->wv);
- This code is used in §6.1.11.
Substitute a detail about the complete PDF6.1.11.5 =
if (swarm_leader) if (WeavingFormats::substitute_post_processing_data(substituted, swarm_leader, detail, cls->wv->pattern) == FALSE) WRITE_TO(substituted, "%S for complete web", detail);
- This code is used in §6.1.11.
Substitute a Module6.1.11.6 =
ls_module *M = CONTENT_IN_ITEM( Collater::heading_topmost_on_stack(cls, MODULE_LEVEL), ls_module); if (M == NULL) Errors::at_position("no module is currently selected", cls->errors_at, lpos); else Substitute a detail about the currently selected Module6.1.11.6.1;
- This code is used in §6.1.11.
§6.1.11.6.1. Substitute a detail about the currently selected Module6.1.11.6.1 =
if (Str::eq_wide_string(detail, U"Title")) { text_stream *owner = Collater::module_owner(M, cls->wv->weave_web); if (Str::len(owner) > 0) WRITE_TO(substituted, "%S/", owner); WRITE_TO(substituted, "%S", M->module_name); } else if (Str::eq_wide_string(detail, U"Page")) { if (Colonies::find(cls->wv->weave_colony, M->module_name)) Colonies::reference_URL(substituted, cls->wv->weave_colony, M->module_name, cls->wv->current_weave_file); } else if (Str::eq_wide_string(detail, U"Purpose")) { TEMPORARY_TEXT(url) WRITE_TO(url, "%p", M->module_location); Readme::write_var(substituted, url, I"Purpose"); DISCARD_TEXT(url) } else { WRITE_TO(substituted, "%S for %S", varname, M->module_name); }
- This code is used in §6.1.11.6.
Substitute a Chapter6.1.11.7 =
ls_chapter *C = CONTENT_IN_ITEM( Collater::heading_topmost_on_stack(cls, CHAPTER_LEVEL), ls_chapter); if (C == NULL) Errors::at_position("no chapter is currently selected", cls->errors_at, lpos); else Substitute a detail about the currently selected Chapter6.1.11.7.1;
- This code is used in §6.1.11.
§6.1.11.7.1. Substitute a detail about the currently selected Chapter6.1.11.7.1 =
if (Str::eq_wide_string(detail, U"Title")) { Str::copy(substituted, C->ch_title); } else if (Str::eq_wide_string(detail, U"Code")) { Str::copy(substituted, C->ch_range); } else if (Str::eq_wide_string(detail, U"Purpose")) { Str::copy(substituted, C->rubric); } else if (WeavingFormats::substitute_post_processing_data(substituted, WeavingDetails::get_ch_weave(C), detail, cls->wv->pattern)) { ; } else { WRITE_TO(substituted, "%S for %S", varname, C->ch_title); }
- This code is used in §6.1.11.7.
Substitute a Section6.1.11.8 =
ls_section *S = CONTENT_IN_ITEM( Collater::heading_topmost_on_stack(cls, SECTION_LEVEL), ls_section); if (S == NULL) Errors::at_position("no section is currently selected", cls->errors_at, lpos); else Substitute a detail about the currently selected Section6.1.11.8.1;
- This code is used in §6.1.11.
§6.1.11.8.1. Substitute a detail about the currently selected Section6.1.11.8.1 =
if (Str::eq_wide_string(detail, U"Title")) { Str::copy(substituted, S->sect_title); } else if (Str::eq_wide_string(detail, U"Purpose")) { WRITE_TO(substituted, "%S", LiterateSource::unit_purpose(S->literate_source)); } else if (Str::eq_wide_string(detail, U"Code")) { Str::copy(substituted, WebRanges::of(S)); } else if (Str::eq_wide_string(detail, U"Lines")) { WRITE_TO(substituted, "%d", S->sect_extent); } else if (Str::eq_wide_string(detail, U"Source")) { WRITE_TO(substituted, "%f", S->source_file_for_section); } else if (Str::eq_wide_string(detail, U"Page")) { Colonies::section_URL(substituted, S); } else if (Str::eq_wide_string(detail, U"Paragraphs")) { WRITE_TO(substituted, "%d", WebStructure::paragraph_count_within_section(S)); } else if (Str::eq_wide_string(detail, U"Mean")) { int denom = WebStructure::paragraph_count_within_section(S); if (denom == 0) denom = 1; WRITE_TO(substituted, "%d", S->sect_extent/denom); } else if (WeavingFormats::substitute_post_processing_data(substituted, WeavingDetails::get_sect_weave(S), detail, cls->wv->pattern)) { ; } else { WRITE_TO(substituted, "%S for %S", varname, S->sect_title); }
- This code is used in §6.1.11.8.
§6.1.11.9. These commands are all used in constructing relative URLs, especially for navigation purposes.
Substitute a Docs6.1.11.9 =
Pathnames::relative_URL(substituted, Filenames::up(cls->wv->current_weave_file), Colonies::home(cls->wv->weave_colony));
- This code is used in §6.1.11.
§6.1.11.10. Substitute an Assets6.1.11.10 =
pathname *P = Colonies::assets_path(cls->wv->weave_colony, cls->wv->weave_web); if (P == NULL) P = Filenames::up(cls->wv->current_weave_file); Pathnames::relative_URL(substituted, Filenames::up(cls->wv->current_weave_file), P);
- This code is used in §6.1.11.
§6.1.11.11. Substitute a URL6.1.11.11 =
Pathnames::relative_URL(substituted, Filenames::up(cls->wv->current_weave_file), Pathnames::from_text(link_text));
- This code is used in §6.1.11.
§6.1.11.12. Substitute a Link6.1.11.12 =
WRITE_TO(substituted, "<a href=\""); Colonies::reference_URL(substituted, cls->wv->weave_colony, link_text, cls->wv->current_weave_file); WRITE_TO(substituted, "\">");
- This code is used in §6.1.11.
§6.1.11.13. Substitute a Menu6.1.11.13 =
if (cls->inside_navigation_submenu) WRITE_TO(substituted, "</ul>"); WRITE_TO(substituted, "<h2>%S</h2><ul>", menu_name); cls->inside_navigation_submenu = TRUE;
- This code is used in §6.1.11.
§6.1.11.14. Look for icon text6.1.11.14 =
match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, item_name, U"<(%i+.%i+)@*(%d*)> *(%c*)")) { icon_text = Str::duplicate(mr.exp[0]); icon_size = Str::atoi(mr.exp[1], 0); item_name = Str::duplicate(mr.exp[2]); } else if (Regexp::match(&mr, item_name, U"(%c*?) *<(%i+.%i+)@*(%d*)>")) { icon_text = Str::duplicate(mr.exp[1]); icon_size = Str::atoi(mr.exp[2], 0); item_name = Str::duplicate(mr.exp[0]); } Regexp::dispose_of(&mr);
- This code is used in §6.1.11 (three times).
§6.1.11.15. Substitute a member Item6.1.11.15 =
TEMPORARY_TEXT(url) Colonies::reference_URL(url, cls->wv->weave_colony, link_text, cls->wv->current_weave_file); Substitute an item at this URL6.1.11.15.1; DISCARD_TEXT(url)
- This code is used in §6.1.11.
§6.1.11.16. Substitute a general Item6.1.11.16 =
TEMPORARY_TEXT(url) Vet the link text6.1.11.16.1; Colonies::link_URL(url, cls->wv->weave_colony, link_text, cls->wv->current_weave_file); Substitute an item at this URL6.1.11.15.1; DISCARD_TEXT(url)
- This code is used in §6.1.11.
§6.1.11.17. Substitute a home Item6.1.11.17 =
WRITE_TO(substituted, "<h1>"); WRITE_TO(substituted, "<a href=\""); Vet the link text6.1.11.16.1; Colonies::link_URL(substituted, cls->wv->weave_colony, link_text, cls->wv->current_weave_file); WRITE_TO(substituted, "\">"); Substitute icon and name6.1.11.17.1; WRITE_TO(substituted, "</a>"); WRITE_TO(substituted, "</h1>");
- This code is used in §6.1.11.
§6.1.11.16.1. Vet the link text6.1.11.16.1 =
int vaguely_URL_like = FALSE, slashed = FALSE; for (int i=0; i<Str::len(link_text); i++) { inchar32_t c = Str::get_at(link_text, i); if ((c == '.') || (c == '/')) vaguely_URL_like = TRUE; } match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, link_text, U" *//%c+// *")) slashed = TRUE; Regexp::dispose_of(&mr); if ((slashed == FALSE) && (vaguely_URL_like == FALSE)) { TEMPORARY_TEXT(err) WRITE_TO(err, "the link '%S' is not in '//...//' slashes, and does not look like a URL", link_text); Errors::at_position_S(err, cls->errors_at, lpos); }
- This code is used in §6.1.11.16 and §6.1.11.17.
§6.1.11.15.1. Substitute an item at this URL6.1.11.15.1 =
if (cls->inside_navigation_submenu == FALSE) WRITE_TO(substituted, "<ul>"); cls->inside_navigation_submenu = TRUE; WRITE_TO(substituted, "<li>"); if (Str::eq(url, Filenames::get_leafname(cls->wv->current_weave_file))) { WRITE_TO(substituted, "<span class=\"unlink\">"); Substitute icon and name6.1.11.17.1; WRITE_TO(substituted, "</span>"); } else if (Str::eq(url, I"index.html")) { WRITE_TO(substituted, "<a href=\"%S\">", url); WRITE_TO(substituted, "<span class=\"selectedlink\">"); Substitute icon and name6.1.11.17.1; WRITE_TO(substituted, "</span>"); WRITE_TO(substituted, "</a>"); } else { WRITE_TO(substituted, "<a href=\"%S\">", url); Substitute icon and name6.1.11.17.1; WRITE_TO(substituted, "</a>"); } WRITE_TO(substituted, "</li>");
- This code is used in §6.1.11.15 and §6.1.11.16.
§6.1.11.17.1. Substitute icon and name6.1.11.17.1 =
if (Str::len(icon_text) > 0) { WRITE_TO(substituted, "<img src=\""); pathname *I = Colonies::assets_path(cls->wv->weave_colony, cls->wv->weave_web); Pathnames::relative_URL(substituted, Filenames::up(cls->wv->current_weave_file), I); WRITE_TO(substituted, "%S\" height=%d> ", icon_text, icon_size); } WRITE_TO(substituted, "%S", item_name);
- This code is used in §6.1.11.17 and §6.1.11.15.1 (three times).
§7. This is a utility for finding the owner of a module, returning NULL (the
empty text) if it appears to belong to the current web W.
text_stream *Collater::module_owner(const ls_module *M, ls_web *W) { text_stream *owner = Pathnames::directory_name(Pathnames::up(M->module_location)); text_stream *me = NULL; if ((W) && (W->path_to_web)) me = Pathnames::directory_name(W->path_to_web); if (Str::ne_insensitive(me, owner)) return owner; return NULL; }
§8. This enables us to sort them. The empty owner (i.e., the current web) comes top, then all other owners, in alphabetical order, and then last of all Inweb, so that foundation will always be at the bottom.
ls_web *sorting_web = NULL; void Collater::sort_web(ls_web *W) { sorting_web = W; } int Collater::sort_comparison(const void *ent1, const void *ent2) { const ls_module *M1 = *((const ls_module **) ent1); const ls_module *M2 = *((const ls_module **) ent2); text_stream *O1 = Collater::module_owner(M1, sorting_web); text_stream *O2 = Collater::module_owner(M2, sorting_web); int r = Collater::cmp_owners(O1, O2); if (r != 0) return r; return Str::cmp_insensitive(M1->module_name, M2->module_name); } int Collater::cmp_owners(text_stream *O1, text_stream *O2) { if (Str::len(O1) == 0) { if (Str::len(O2) > 0) return -1; return 0; } if (Str::len(O2) == 0) return 1; if (Str::eq_insensitive(O1, I"inweb")) { if (Str::eq_insensitive(O2, I"inweb") == FALSE) return 1; return 0; } if (Str::eq_insensitive(O2, I"inweb")) return -1; return Str::cmp_insensitive(O1, O2); }