# domain/converters/ontology.converter.ts

> 193 lines of code and 47 definitions.

Tree: Site tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/tree#file-domain-converters-ontology-converter-ts
Source text: https://banes-lab.com/assets/sources/source.fee68819a30ff150ce313a21cae56a5fec35ed29bb7a89803b271f2334da9374.generated.txt

## Definitions

- `glossary` (lexical_declaration, line 144, exported)
- `linkList` (lexical_declaration, line 116, exported)
- `codeOf` (lexical_declaration, line 156, exported)
- `diagram` (lexical_declaration, line 202, exported)
- `plainList` (lexical_declaration, line 120, exported)
- `edgeLine` (lexical_declaration, line 206, exported)
- `faceLink` (lexical_declaration, line 112, exported)
- `fields` (lexical_declaration, line 132, exported)
- `nodeId` (lexical_declaration, line 172, exported)
- `exemplarBlocks` (lexical_declaration, line 160, exported)
- `linkOf` (lexical_declaration, line 107, exported)
- `slugOf` (lexical_declaration, line 180, exported)
- `labelled` (lexical_declaration, line 124, exported)
- `row` (lexical_declaration, line 136, exported)
- `nodeLabel` (lexical_declaration, line 194, exported)
- `linked` (lexical_declaration, line 128, exported)
- `linkRow` (lexical_declaration, line 140, exported)
- `node` (lexical_declaration, line 198, exported)
- `hrefOf` (lexical_declaration, line 96, exported)
- `TYPESCRIPT_LANG` (lexical_declaration, line 48)
- `QUOTE` (lexical_declaration, line 49)
- `APOSTROPHE` (lexical_declaration, line 50)
- `NODE_OPEN` (lexical_declaration, line 51)
- `NODE_CLOSE` (lexical_declaration, line 52)
- `LINE_END` (lexical_declaration, line 53)
- `INDENT` (lexical_declaration, line 54)
- `SAFE` (lexical_declaration, line 55)
- `NODE_PREFIX` (lexical_declaration, line 56)
- `UNDERSCORE` (lexical_declaration, line 57)
- `HYPHEN` (lexical_declaration, line 58)
- `TAB_BY_FACE` (lexical_declaration, line 60)
- `ORDER` (lexical_declaration, line 76)
- `anchorOf` (lexical_declaration, line 78, exported)
- `refOfAnchor` (lexical_declaration, line 84, exported)
- `found` (lexical_declaration, line 85, exported)
- `width` (lexical_declaration, line 86, exported)
- `split` (lexical_declaration, line 97, exported)
- `face` (lexical_declaration, line 101, exported)
- `href` (lexical_declaration, line 108, exported)
- `text` (lexical_declaration, line 148, exported)
- `chips` (lexical_declaration, line 152, exported)
- `language` (lexical_declaration, line 168, exported)
- `out` (lexical_declaration, line 181, exported)
- `gap` (lexical_declaration, line 182, exported)
- `localId` (lexical_declaration, line 210, exported)
- `prefix` (lexical_declaration, line 214, exported)
- `id` (lexical_declaration, line 218, exported)

## Uses

- [core/assets/link.assets.ts](https://banes-lab.com/source/tree/core/assets/link.assets.ts.md)

## Used by

- [domain/converters/anatomy.converter.ts](https://banes-lab.com/source/tree/domain/converters/anatomy.converter.ts.md)
- [domain/converters/anatomy.fragment.converter.ts](https://banes-lab.com/source/tree/domain/converters/anatomy.fragment.converter.ts.md)
- [domain/converters/catalog.converter.ts](https://banes-lab.com/source/tree/domain/converters/catalog.converter.ts.md)
- [domain/converters/contract.converter.ts](https://banes-lab.com/source/tree/domain/converters/contract.converter.ts.md)
- [domain/converters/document.converter.ts](https://banes-lab.com/source/tree/domain/converters/document.converter.ts.md)
- [domain/converters/graph.converter.ts](https://banes-lab.com/source/tree/domain/converters/graph.converter.ts.md)
- [domain/converters/kind.converter.ts](https://banes-lab.com/source/tree/domain/converters/kind.converter.ts.md)
- [domain/converters/lexicon.converter.ts](https://banes-lab.com/source/tree/domain/converters/lexicon.converter.ts.md)
- [domain/converters/link.converter.ts](https://banes-lab.com/source/tree/domain/converters/link.converter.ts.md)
- [domain/converters/loop.converter.ts](https://banes-lab.com/source/tree/domain/converters/loop.converter.ts.md)
- [domain/converters/principle.converter.ts](https://banes-lab.com/source/tree/domain/converters/principle.converter.ts.md)
- [domain/converters/reason.converter.ts](https://banes-lab.com/source/tree/domain/converters/reason.converter.ts.md)
- [domain/converters/reason.fragment.converter.ts](https://banes-lab.com/source/tree/domain/converters/reason.fragment.converter.ts.md)
- [domain/converters/source.converter.ts](https://banes-lab.com/source/tree/domain/converters/source.converter.ts.md)
- [domain/converters/tension.converter.ts](https://banes-lab.com/source/tree/domain/converters/tension.converter.ts.md)

## Source

```typescript
import {
    ALGORITHMS_TAB,
    ALGO_ANCHOR,
    ALGO_DOMAIN_ANCHOR,
    ARCH_ANCHOR,
    ARCH_CATEGORY_ANCHOR,
    FORCE_ANCHOR,
    KIND_ANCHOR,
    LAYER_ANCHOR,
    LEXICON_TAB,
    LEX_ANCHOR,
    LEX_CATEGORY_ANCHOR,
    ONTOLOGY_TAB_ORDER,
    PRINCIPLES_TAB,
    REASONING_TAB,
    REASON_ANCHOR,
    RELATION_ANCHOR,
    SCHEMA_TAB,
    STAGE_ANCHOR,
    TENSION_ANCHOR,
} from "#core/ids/ontology.ids";
import {
    ALGO_DOMAIN_FACE,
    ALGO_FACE,
    ARCH_CATEGORY_FACE,
    ARCH_FACE,
    FACE_SEPARATOR,
    FORCE_FACE,
    KIND_FACE,
    LAYER_FACE,
    LEX_CATEGORY_FACE,
    LEX_FACE,
    REASON_FACE,
    RELATION_FACE,
    STAGE_FACE,
    TENSION_FACE,
} from "@govlab/constants";
import type { Block, CodeBlock, GlossaryEntry } from "#types/block.types";
import { FIELD_SEPARATOR, LABEL_SEPARATOR, LIST_SEPARATOR, NONE_LABEL } from "#configuration/strings/ontology.strings";
import { TEXT_LANGUAGE, TYPESCRIPT_LANGUAGE } from "#configuration/constants/code.constants";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import { CHAPTER_FACE } from "#configuration/constants/vocabulary.constants";
import type { EdgeRef } from "#types/link.types";
import type { ExemplarView } from "#types/ontology.types";
import { ONTOLOGY_PAGE } from "#core/ids/page.ids";
import { tabPath } from "#assets/link.assets";

const TYPESCRIPT_LANG = "ts";
const QUOTE = '"';
const APOSTROPHE = "'";
const NODE_OPEN = '["';
const NODE_CLOSE = '"]';
const LINE_END = "\n";
const INDENT = "    ";
const SAFE = "abcdefghijklmnopqrstuvwxyz0123456789";
const NODE_PREFIX = "n_";
const UNDERSCORE = "_";
const HYPHEN = "-";

const TAB_BY_FACE: ReadonlyMap<string, { readonly anchor: string; readonly tab: string }> = new Map([
    [ARCH_FACE, { anchor: ARCH_ANCHOR, tab: PRINCIPLES_TAB }],
    [ARCH_CATEGORY_FACE, { anchor: ARCH_CATEGORY_ANCHOR, tab: PRINCIPLES_TAB }],
    [LEX_FACE, { anchor: LEX_ANCHOR, tab: LEXICON_TAB }],
    [LEX_CATEGORY_FACE, { anchor: LEX_CATEGORY_ANCHOR, tab: LEXICON_TAB }],
    [ALGO_FACE, { anchor: ALGO_ANCHOR, tab: ALGORITHMS_TAB }],
    [ALGO_DOMAIN_FACE, { anchor: ALGO_DOMAIN_ANCHOR, tab: ALGORITHMS_TAB }],
    [REASON_FACE, { anchor: REASON_ANCHOR, tab: REASONING_TAB }],
    [STAGE_FACE, { anchor: STAGE_ANCHOR, tab: REASONING_TAB }],
    [TENSION_FACE, { anchor: TENSION_ANCHOR, tab: SCHEMA_TAB }],
    [LAYER_FACE, { anchor: LAYER_ANCHOR, tab: SCHEMA_TAB }],
    [FORCE_FACE, { anchor: FORCE_ANCHOR, tab: SCHEMA_TAB }],
    [KIND_FACE, { anchor: KIND_ANCHOR, tab: SCHEMA_TAB }],
    [RELATION_FACE, { anchor: RELATION_ANCHOR, tab: SCHEMA_TAB }],
]);

const ORDER = ONTOLOGY_TAB_ORDER.map((id) => ({ id }));

export const anchorOf = function anchorOf(ref: string): string | null {
    const split = ref.indexOf(FACE_SEPARATOR);
    const face = TAB_BY_FACE.get(ref.slice(0, split));
    return face === undefined ? null : face.anchor + ref.slice(split + 1);
};

export const refOfAnchor = function refOfAnchor(fragment: string): string | null {
    let found: string | null = null;
    let width = 0;
    for (const [face, { anchor }] of TAB_BY_FACE) {
        if (fragment.startsWith(anchor) && anchor.length > width) {
            found = face + FACE_SEPARATOR + fragment.slice(anchor.length);
            width = anchor.length;
        }
    }
    return found;
};

export const hrefOf = function hrefOf(ref: string): string | null {
    const split = ref.indexOf(FACE_SEPARATOR);
    if (ref.slice(0, split) === CHAPTER_FACE) {
        return ref.slice(split + 1);
    }
    const face = TAB_BY_FACE.get(ref.slice(0, split));
    return face === undefined
        ? null
        : tabPath(ONTOLOGY_PAGE, ORDER, face.tab) + ANCHOR_PREFIX + face.anchor + ref.slice(split + 1);
};

export const linkOf = function linkOf(edge: EdgeRef): string {
    const href = edge.ref === null ? null : hrefOf(edge.ref);
    return href === null ? edge.label : `<a href="${href}">${edge.label}</a>`;
};

export const faceLink = function faceLink(face: string, id: string, label: string): string {
    return linkOf({ label, ref: face + FACE_SEPARATOR + id });
};

export const linkList = function linkList(edges: readonly EdgeRef[]): string {
    return edges.length === 0 ? NONE_LABEL : edges.map(linkOf).join(LIST_SEPARATOR);
};

export const plainList = function plainList(items: readonly string[]): string {
    return items.length === 0 ? NONE_LABEL : items.join(LIST_SEPARATOR);
};

export const labelled = function labelled(label: string, value: string | null): string | null {
    return value === null || value.length === 0 ? null : label + LABEL_SEPARATOR + value;
};

export const linked = function linked(label: string, edge: EdgeRef | null): string | null {
    return edge === null ? null : labelled(label, linkOf(edge));
};

export const fields = function fields(parts: readonly (string | null)[]): string {
    return parts.filter((part): part is string => part !== null && part.length > 0).join(FIELD_SEPARATOR);
};

export const row = function row(term: string, description: string): GlossaryEntry {
    return { description, term };
};

export const linkRow = function linkRow(term: string, edges: readonly EdgeRef[]): readonly GlossaryEntry[] {
    return edges.length === 0 ? [] : [row(term, linkList(edges))];
};

export const glossary = function glossary(entries: readonly GlossaryEntry[]): Block {
    return { entries, kind: "glossary" };
};

export const text = function text(value: string): Block {
    return { kind: "text", text: value };
};

export const chips = function chips(parts: readonly (string | null)[]): Block {
    return { items: parts.filter((part): part is string => part !== null && part.length > 0), kind: "list" };
};

export const codeOf = function codeOf(title: string, code: string, language: string): CodeBlock {
    return { code, kind: "code", language, title };
};

export const exemplarBlocks = function exemplarBlocks(
    exemplar: ExemplarView | null,
    beforeTitle: string,
    afterTitle: string,
): readonly Block[] {
    if (exemplar === null) {
        return [];
    }
    const language = exemplar.lang === TYPESCRIPT_LANG ? TYPESCRIPT_LANGUAGE : TEXT_LANGUAGE;
    return [codeOf(beforeTitle, exemplar.before, language), codeOf(afterTitle, exemplar.after, language)];
};

export const nodeId = function nodeId(id: string): string {
    let out = NODE_PREFIX;
    for (const char of id.toLowerCase()) {
        out += SAFE.includes(char) ? char : UNDERSCORE;
    }
    return out;
};

export const slugOf = function slugOf(label: string): string {
    let out = "";
    let gap = false;
    for (const char of label.toLowerCase()) {
        if (SAFE.includes(char)) {
            out += gap && out.length > 0 ? HYPHEN + char : char;
            gap = false;
        } else {
            gap = true;
        }
    }
    return out;
};

export const nodeLabel = function nodeLabel(label: string): string {
    return label.replaceAll(QUOTE, APOSTROPHE);
};

export const node = function node(id: string, label: string): string {
    return INDENT + nodeId(id) + NODE_OPEN + nodeLabel(label) + NODE_CLOSE;
};

export const diagram = function diagram(header: string, lines: readonly string[]): string {
    return [header, ...lines].join(LINE_END);
};

export const edgeLine = function edgeLine(from: string, arrow: string, to: string): string {
    return INDENT + nodeId(from) + arrow + nodeId(to);
};

export const localId = function localId(edge: EdgeRef, face: string, members: ReadonlySet<string>): string | null {
    if (edge.ref === null) {
        return null;
    }
    const prefix = face + FACE_SEPARATOR;
    if (!edge.ref.startsWith(prefix)) {
        return null;
    }
    const id = edge.ref.slice(prefix.length);
    return members.has(id) ? id : null;
};
```
