# core/converters/section.converter.ts

> 77 lines of code and 20 definitions.

Tree: Build tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/build#file-build-core-converters-section-converter-ts
Source text: https://banes-lab.com/assets/sources/source.a05819d7e51ee1159b9cddd244c069f0cfba7f44b3aaaec2c7ba851e066508fd.generated.txt

## Definitions

- `isRecord` (lexical_declaration, line 7)
- `sectionOf` (lexical_declaration, line 74)
- `sectionsOf` (lexical_declaration, line 80, exported)
- `stringsOf` (lexical_declaration, line 11)
- `splitSections` (lexical_declaration, line 53)
- `linkTargetsOf` (lexical_declaration, line 21, exported)
- `targets` (lexical_declaration, line 22, exported)
- `at` (lexical_declaration, line 23, exported)
- `start` (lexical_declaration, line 25, exported)
- `close` (lexical_declaration, line 26, exported)
- `sectionIdOf` (lexical_declaration, line 36)
- `Split` (interface_declaration, line 41)
- `keyedSections` (lexical_declaration, line 46)
- `id` (lexical_declaration, line 48)
- `parts` (lexical_declaration, line 55)
- `entries` (lexical_declaration, line 61)
- `rest` (lexical_declaration, line 65)
- `strings` (lexical_declaration, line 75)
- `links` (lexical_declaration, line 76)
- `{ body, found }` (lexical_declaration, line 81, exported)

## Source

```typescript
import { LINK_CLOSE, LINK_MIDDLE } from "#configuration/constants/chapter.constants";
import { PAYLOAD_ID_KEY, PAYLOAD_SECTIONS_KEY } from "#configuration/constants/leak.constants";
import type { PageSection } from "#types/writing.types";
import { SECTION_MARK } from "#configuration/constants/writing.constants";
import { fingerprintOf } from "@govlab/content-fingerprint";

const isRecord = function isRecord(value: unknown): value is Record<string, unknown> {
    return typeof value === "object" && value !== null;
};

const stringsOf = function stringsOf(value: unknown): string[] {
    if (typeof value === "string") {
        return [value];
    }
    if (Array.isArray(value)) {
        return value.flatMap(stringsOf);
    }
    return isRecord(value) ? Object.values(value).flatMap(stringsOf) : [];
};

export const linkTargetsOf = function linkTargetsOf(text: string): string[] {
    const targets: string[] = [];
    let at = text.indexOf(LINK_MIDDLE);
    while (at !== -1) {
        const start = at + LINK_MIDDLE.length;
        const close = text.indexOf(LINK_CLOSE, start);
        if (close === -1) {
            return targets;
        }
        targets.push(text.slice(start, close));
        at = text.indexOf(LINK_MIDDLE, close);
    }
    return targets;
};

const sectionIdOf = function sectionIdOf(value: unknown): string | null {
    const id = isRecord(value) ? value[PAYLOAD_ID_KEY] : undefined;
    return typeof id === "string" ? id : null;
};

interface Split {
    readonly body: unknown;
    readonly found: readonly (readonly [string, unknown])[];
}

const keyedSections = function keyedSections(list: readonly unknown[]): (readonly [string, unknown])[] {
    return list.flatMap((section) => {
        const id = sectionIdOf(section);
        return id === null ? [] : [[id, section] as const];
    });
};

const splitSections = function splitSections(value: unknown): Split {
    if (Array.isArray(value)) {
        const parts = value.map(splitSections);
        return { body: parts.map((part) => part.body), found: parts.flatMap((part) => part.found) };
    }
    if (!isRecord(value)) {
        return { body: value, found: [] };
    }
    const entries = Object.entries(value).map(([key, entry]) => {
        if (key !== PAYLOAD_SECTIONS_KEY || !Array.isArray(entry)) {
            return { key, ...splitSections(entry) };
        }
        const rest = splitSections(entry.filter((section: unknown) => sectionIdOf(section) === null));
        return { body: rest.body, found: [...keyedSections(entry), ...rest.found], key };
    });
    return {
        body: Object.fromEntries(entries.map((entry) => [entry.key, entry.body])),
        found: entries.flatMap((entry) => entry.found),
    };
};

const sectionOf = function sectionOf(page: string, key: string, value: unknown): PageSection {
    const strings = stringsOf(value);
    const links = [...new Set(strings.flatMap(linkTargetsOf))].sort((a, b) => a.localeCompare(b));
    return { fingerprint: fingerprintOf(strings), key, links, page };
};

export const sectionsOf = function sectionsOf(page: string, content: unknown): PageSection[] {
    const { body, found } = splitSections(content);
    return [
        sectionOf(page, page, body),
        ...found.map(([id, section]) => sectionOf(page, page + SECTION_MARK + id, section)),
    ];
};
```
