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


§1. Definitions.

§2. 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;
        MEMORY_MANAGEMENT
    } volume;

    volume *volumes[MAX_VOLUMES];

The structure volume is accessed in 1/mn, 1/ins, 2/exm, 2/rnd, 2/rr, 2/haj, 2/css, 3/cai, 3/ei, 4/nd, 4/cm, 4/ct, 4/cr, 4/cu and here.

§3. 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;
        MEMORY_MANAGEMENT
    } chapter;

The structure chapter is accessed in 5/ee, 2/rnd, 3/ei, 4/nd, 4/cl, 4/cm, 4/ca, 4/ct, 4/cr, 4/cu and here.

§4. 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;
        MEMORY_MANAGEMENT
    } section;

The structure section is accessed in 2/exm, 2/rnd, 2/utsr, 3/cai, 3/ei, 4/nd, 4/cl, 4/cm, 4/ca, 4/ct, 4/cr, 4/cu and here.

§5. 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 supplied 5.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 prefix 5.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_folder(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);
    }

The function Scanner::create_volume is used in 1/ins (§5.1.2).

§5.1. <Work out title and abbreviation if these aren't supplied 5.1> =

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

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

This code is used in §5.

§5.2. <Ensure that no two volumes have the same abbreviation or the same prefix 5.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);
        }

This code is used in §5.

§6. 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, L"(%c*?) ")) Str::copy(nl, mr.exp[0]);
        if (Regexp::match(&mr, nl, L"%[(%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 section 6.1>;
            <Deal with this as a chapter heading 6.2>;
            <Deal with this as a section heading 6.3>;
        }
        Regexp::dispose_of(&mr);
    }

The function Scanner::scan_rawtext_for_section_titles is used in 1/mn (§1.2).

The function Scanner::scan_rawtext_helper appears nowhere else.

The structure sr_helper_state is private to this section.

§6.1. <Strip away heading tags, but act on those filtering out the section 6.1> =

        match_results mr2 = Regexp::create_mr();
        while (Regexp::match(&mr2, chi, L"{(%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, L"(%c*) {%c*?} *")) Str::copy(stitle, mr2.exp[0]);
        if (Regexp::match(&mr2, stitle, L"(%c*?) ")) Str::copy(stitle, mr2.exp[0]);
        Regexp::dispose_of(&mr2);

This code is used in §6.

§6.2. <Deal with this as a chapter heading 6.2> =

        match_results mr2 = Regexp::create_mr();
        if (Regexp::match(&mr2, chi, L"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);

This code is used in §6.

§6.3. <Deal with this as a section heading 6.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 granularity 6.3.1>;

        if (S->begins_which_chapter)
            <Work out chapter URLs and anchors, depending on granularity 6.3.2>;

This code is used in §6.

§6.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 granularity 6.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_folder(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);

This code is used in §6.3.

§6.3.2. And similarly for chapters.

<Work out chapter URLs and anchors, depending on granularity 6.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);

This code is used in §6.3.

§7. 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_folder(
            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);
    }

The function Scanner::write_manifest_file is used in 1/mn (§1.4).

§8. 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);
        }
    }

The function Scanner::mark_up_ebook is used in 1/mn (§1).