Finding out how a volume divides up into chapters and sections.


§1. Projects are divided into volumes, which are divided into chapters, which in turn are divided into sections.

Volumes count from 0, in order of creation, so these are arrays.

define MAX_VOLUMES 100
define MAX_CHAPTERS_PER_VOLUME 100
define MAX_SECTIONS_PER_VOLUME 1000
define MAX_EXAMPLES_PER_VOLUME MAX_EXAMPLES
typedef struct volume {
    struct text_stream *vol_title;  e.g., "What Katy Did Next"
    struct text_stream *vol_abbrev;  e.g., "WKDN"
    struct text_stream *vol_prefix;  e.g., "K"
    struct filename *vol_rawtext_filename;  source file
    struct text_stream *vol_CSS_leafname;   CSS file used for pages in this volume
    struct text_stream *vol_URL;   link to start of volume
    int vol_chapter_count;
    int vol_section_count;
    struct chapter *chapters[MAX_CHAPTERS_PER_VOLUME];  these count from 1
    struct section *sections[MAX_SECTIONS_PER_VOLUME];  but these count from 0
    struct example *examples_sequence[MAX_EXAMPLES_PER_VOLUME];  also these
    struct dictionary *sections_by_name;
    CLASS_DEFINITION
} volume;

volume *volumes[MAX_VOLUMES];

§2. Chapters:

typedef struct chapter {
    struct text_stream *chapter_title;  e.g., "The Pension Suisse"
    int chapter_number;  counting from 1
    struct text_stream *chapter_full_title;  e.g., "Chapter 7: The Pension Suisse"
    struct section *begins_at_section;  e.g., section 51 might be the first of chapter 7
    struct text_stream *chapter_anchor;  HTML anchor to place at front of chapter, if any
    struct text_stream *chapter_URL;  link to start of chapter
    struct chapter *next_chapter;
    struct chapter *previous_chapter;
    struct ebook_chapter *ebook_ref;
    CLASS_DEFINITION
} chapter;

§3. Sections:

define MAX_DRS_PER_SECTION 100
typedef struct section {
    struct chapter *in_which_chapter;  e.g., 7
    struct chapter *begins_which_chapter;  e.g., 7, but -1 if it doesn't open a chapter
    struct text_stream *unlabelled_title;  e.g., "Corsica"
    struct text_stream *label;  e.g., "7.1"
    struct text_stream *title;  e.g, "7.1. Corsica"
    struct text_stream *sort_code;  a formatted version of the label for alphabetic sorting
    struct filename *section_filename;  filename to write this section (and perhaps others) into
    struct text_stream *section_file_title;  title the whole file will have
    struct text_stream *section_anchor;  HTML anchor to place at front of section, if any
    struct text_stream *section_URL;  link to start of section
    struct text_stream *unanchored_URL;  link to start of file holding this
    int no_doc_reference_symbols;
    struct text_stream *doc_reference_symbols[MAX_DRS_PER_SECTION];
    struct section *next_section;
    struct section *previous_section;
    int number_within_volume;
    CLASS_DEFINITION
} section;

§4. Volumes. These are created when we scan the instructions file.

void Scanner::create_volume(pathname *book_path, text_stream *leaf, text_stream *title, text_stream *abbrev_supplied) {
    TEMPORARY_TEXT(pre)
    TEMPORARY_TEXT(abbrev)
    Str::copy(abbrev, abbrev_supplied);

    Work out title and abbreviation if these aren't supplied4.1;

    if (no_volumes > 0) PUT_TO(pre, Str::get_first_char(abbrev));

    Ensure that no two volumes have the same abbreviation or the same prefix4.2;

    if (no_volumes >= MAX_VOLUMES) Errors::fatal("too many volumes");

    volume *V = CREATE(volume);
    volumes[no_volumes++] = V;
    V->vol_title = Str::duplicate(title);
    V->vol_prefix = Str::duplicate(pre);
    V->vol_abbrev = Str::duplicate(abbrev);
    V->vol_rawtext_filename = Filenames::in(book_path, leaf);
    V->vol_CSS_leafname = NULL;
    V->vol_URL = NULL;
    V->vol_chapter_count = 0;
    V->vol_section_count = 0;
    V->sections_by_name = Dictionaries::new(100, FALSE);

    PRINT("Volume %d: %S  %S %S  %f\n", no_volumes-1, title, abbrev, pre,
        V->vol_rawtext_filename);
    DISCARD_TEXT(pre)
    DISCARD_TEXT(abbrev)
}

§4.1. Work out title and abbreviation if these aren't supplied4.1 =

    if (Str::len(title) == 0) title = I"Untitled";

    if (Str::len(abbrev) == 0) {
        int f = 0;
        if (Str::begins_with_wide_string(title, U"A ")) f = 2;
        else if (Str::begins_with_wide_string(title, U"An ")) f = 3;
        else if (Str::begins_with_wide_string(title, U"The ")) f = 4;
        for (int i=f; i<Str::len(title); i++) {
            inchar32_t c = Str::get_at(title, i);
            if (Characters::is_whitespace(c)) continue;
            if ((c >= 'a') && (c <= 'z')) continue;
            PUT_TO(abbrev, c);
        }
    }

§4.2. Ensure that no two volumes have the same abbreviation or the same prefix4.2 =

    for (int i = 0; i < no_volumes; i++) {
        if ((Str::eq(abbrev, volumes[i]->vol_abbrev)) || (Str::len(abbrev) == 0))
            WRITE_TO(abbrev, "_%d", i);
        if ((Str::eq(pre, volumes[i]->vol_prefix)) || (Str::len(pre) == 0))
            WRITE_TO(pre, "_%d", i);
    }

§5. Section title scanning. This is a much skimpier first-pass-only scan which looks for section titles, marrying them up with block numbers.

void Scanner::scan_rawtext_for_section_titles(volume *V) {
    filename *rawtext_filename = V->vol_rawtext_filename;
    sr_helper_state sr;
    sr.s = 0;
    sr.ch = 0;
    sr.chs = 0;
    sr.v = V->allocation_id;
    sr.owner = V;
    TextFiles::read(rawtext_filename, FALSE, "can't open instructions file",
        TRUE, Scanner::scan_rawtext_helper, NULL, &sr);
    V->vol_section_count = sr.s;
    V->vol_chapter_count = sr.ch;
    V->vol_URL = Str::new();
    if (sr.s > 0) Str::copy(V->vol_URL, V->sections[0]->unanchored_URL);

    for (int i=0; i<V->vol_section_count; i++) {
        if (i>0) V->sections[i]->previous_section = V->sections[i-1];
        if (i<V->vol_section_count-1) V->sections[i]->next_section = V->sections[i+1];
    }
    for (int i=0; i<V->vol_chapter_count; i++) {
        V->chapters[i]->chapter_number = i+1;
        if (i>0) V->chapters[i]->previous_chapter = V->chapters[i-1];
        if (i<V->vol_chapter_count-1) V->chapters[i]->next_chapter = V->chapters[i+1];
    }
}

typedef struct sr_helper_state {
    int v;  volume number
    int s;  section number within the volume, starting from 0
    int ch;  chapter number within the volume, starting from 1
    int chs;  section number within current chapter, starting from 1
    struct volume *owner;
} sr_helper_state;

void Scanner::scan_rawtext_helper(text_stream *nl, text_file_position *tfp,
    void *v_sr) {
    sr_helper_state *sr = (sr_helper_state *) v_sr;
    match_results mr = Regexp::create_mr();
    if (Regexp::match(&mr, nl, U"(%c*?) ")) Str::copy(nl, mr.exp[0]);
    if (Regexp::match(&mr, nl, U"%[(%c*?)%] (%c*)")) {
        section *S = CREATE(section);
        S->next_section = NULL; S->previous_section = NULL;
        S->begins_which_chapter = NULL;
        S->no_doc_reference_symbols = 0;
        S->number_within_volume = sr->s++;
        sr->owner->sections[S->number_within_volume] = S;

        text_stream *chi = mr.exp[0];
        text_stream *stitle = mr.exp[1];
        Strip away heading tags, but act on those filtering out the section5.1;
        Deal with this as a chapter heading5.2;
        Deal with this as a section heading5.3;
    }
    Regexp::dispose_of(&mr);
}

§5.1. Strip away heading tags, but act on those filtering out the section5.1 =

    match_results mr2 = Regexp::create_mr();
    while (Regexp::match(&mr2, chi, U"{(%c*?:}(%c*)")) {
        Str::copy(chi, mr2.exp[1]);
        if (Symbols::perform_ifdef(mr2.exp[0]) == FALSE) {
            Regexp::dispose_of(&mr);
            Regexp::dispose_of(&mr2);
            return;
        }
    }
    while (Regexp::match(&mr2, stitle, U"(%c*) {%c*?} *")) Str::copy(stitle, mr2.exp[0]);
    if (Regexp::match(&mr2, stitle, U"(%c*?) ")) Str::copy(stitle, mr2.exp[0]);
    Regexp::dispose_of(&mr2);

§5.2. Deal with this as a chapter heading5.2 =

    match_results mr2 = Regexp::create_mr();
    if (Regexp::match(&mr2, chi, U"Chapter: (%c*)")) {
        chapter *C = CREATE(chapter);
        sr->owner->chapters[sr->ch++] = C;
        sr->chs = 0;
        C->chapter_title = Str::duplicate(mr2.exp[0]);
        C->chapter_full_title = Str::new();
        WRITE_TO(C->chapter_full_title, "Chapter %d: %S", sr->ch, C->chapter_title);
        S->begins_which_chapter = C;
        C->begins_at_section = S;
        C->next_chapter = NULL; C->previous_chapter = NULL;
        C->ebook_ref = NULL;
    }
    Regexp::dispose_of(&mr2);

§5.3. Deal with this as a section heading5.3 =

    chapter *C = (sr->ch > 0)?sr->owner->chapters[sr->ch-1]: NULL;
    S->in_which_chapter = C;

    sr->chs++;
    S->label = Str::new();
    WRITE_TO(S->label, "%d.%d", sr->ch, sr->chs);
    S->sort_code = Str::new();
    WRITE_TO(S->sort_code, "%03d-%03d-%03d-000", sr->v, sr->ch, sr->chs);

    S->title = Str::new();
    WRITE_TO(S->title, "%S. %S", S->label, stitle);
    S->unlabelled_title = Str::duplicate(stitle);

    Dictionaries::create(sr->owner->sections_by_name, stitle);
    Dictionaries::write_value(
        sr->owner->sections_by_name, stitle, (void *) S);
    LOOP_THROUGH_TEXT(pos, stitle)
        Str::put(pos, Characters::toupper(Str::get(pos)));
    Dictionaries::create(sr->owner->sections_by_name, stitle);
    Dictionaries::write_value(
        sr->owner->sections_by_name, stitle, (void *) S);

    Work out section URLs and anchors, depending on granularity5.3.1;

    if (S->begins_which_chapter)
        Work out chapter URLs and anchors, depending on granularity5.3.2;

§5.3.1. This is relevant only to HTML, of course. The idea is that each section has some linkable location, in the form of either a file URL, or a file plus an anchor name: for example, it might be WKDN_7.html#s4. If the anchor is blank, the filename alone is used.

Work out section URLs and anchors, depending on granularity5.3.1 =

    char *extension = "txt";
    if (indoc_settings->format == HTML_FORMAT) extension = "html";
    TEMPORARY_TEXT(leaf)
    if (indoc_settings->granularity == SECTION_GRANULARITY) {
        if (indoc_settings->html_for_Inform_application)
            WRITE_TO(leaf, "%Sdoc%d.%s", sr->owner->vol_prefix, sr->s, extension);
        else
            WRITE_TO(leaf, "%S_%d_%d.%s", sr->owner->vol_abbrev, sr->ch, sr->chs, extension);
        S->section_anchor = Str::new();
        S->section_file_title = Str::duplicate(S->title);
    } else if (indoc_settings->granularity == CHAPTER_GRANULARITY) {
        WRITE_TO(leaf, "%S_%d.%s", sr->owner->vol_abbrev, sr->ch, extension);
        S->section_anchor = Str::new();
        WRITE_TO(S->section_anchor, "s%d", sr->chs);
        S->section_file_title = Str::duplicate(C->chapter_full_title);
    } else {
        WRITE_TO(leaf, "%S.%s", sr->owner->vol_abbrev, extension);
        S->section_anchor = Str::new();
        WRITE_TO(S->section_anchor, "c%d_s%d", sr->ch, sr->chs);
        S->section_file_title = Str::duplicate(sr->owner->vol_title);
    }
    S->section_filename = Filenames::in(indoc_settings->destination, leaf);
    S->section_URL = Str::duplicate(leaf);
    S->unanchored_URL = Str::duplicate(leaf);
    DISCARD_TEXT(leaf)
    if (Str::len(S->section_anchor) > 0) WRITE_TO(S->section_URL, "#%S", S->section_anchor);

§5.3.2. And similarly for chapters.

Work out chapter URLs and anchors, depending on granularity5.3.2 =

    C->chapter_anchor = Str::new();
    if (indoc_settings->granularity == SECTION_GRANULARITY) {
    } else if (indoc_settings->granularity == CHAPTER_GRANULARITY) {
        if (sr->ch == 1) WRITE_TO(C->chapter_anchor, "chapter_%d_%d", sr->v, sr->ch);
    } else {
        WRITE_TO(C->chapter_anchor, "chapter_%d_%d", sr->v, sr->ch);
    }
    C->chapter_URL = Str::duplicate(S->unanchored_URL);
    if (Str::len(C->chapter_anchor) > 0) WRITE_TO(C->chapter_URL, "#%S", C->chapter_anchor);

§6. The manifest file. Its destiny is to hold a simple machine-readable contents listing, and it helps the Inform application in searching the online documentation.

void Scanner::write_manifest_file(volume *V) {
    filename *M = Filenames::in(
        indoc_settings->destination, indoc_settings->manifest_leafname);
    text_stream M_struct;
    text_stream *OUT = &M_struct;
    if (Streams::open_to_file(OUT, M, UTF8_ENC) == FALSE)
        Errors::fatal_with_file("can't write manifest file", M);
    for (int i=0; i<V->vol_section_count; i++) {
        section *S = V->sections[i];
        WRITE("doc%d.html: %S  %S\n", i+1, S->label, S->title);
    }
    Streams::close(OUT);
}

§7. Ebook markup. This places internal markup within files in the EPUB version.

void Scanner::mark_up_ebook(void) {
    section *S;
    LOOP_OVER(S, section) {
        chapter *C = S->in_which_chapter;
        if (C) Epub::set_mark_in_chapter(C->ebook_ref, S->title, S->section_URL);
    }
}