# core/normalizers/markdown.normalizer.ts

> 78 lines of code and 16 definitions.

Tree: Site tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/tree#file-core-normalizers-markdown-normalizer-ts
Source text: https://banes-lab.com/assets/sources/source.a1b0b3b2a9bffbb35da11369e3b08fff582fc4ebbbc81b7aa1b0199b4b06980b.generated.txt

## Definitions

- `inlineMarkup` (lexical_declaration, line 80, exported)
- `escaped` (lexical_declaration, line 23, exported)
- `linkAt` (lexical_declaration, line 31)
- `codeAt` (lexical_declaration, line 42)
- `step` (lexical_declaration, line 55)
- `spanAt` (lexical_declaration, line 47)
- `Span` (interface_declaration, line 11)
- `Scan` (interface_declaration, line 16)
- `out` (lexical_declaration, line 24, exported)
- `middle` (lexical_declaration, line 32)
- `label` (lexical_declaration, line 37)
- `href` (lexical_declaration, line 38)
- `close` (lexical_declaration, line 43)
- `span` (lexical_declaration, line 56)
- `char` (lexical_declaration, line 68)
- `scan` (lexical_declaration, line 81, exported)

## Used by

- [presentation/renderers/markdown.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/markdown.renderer.ts.md)

## Source

```typescript
import {
    CODE_MARK,
    EMPHASIS_MARKS,
    ESCAPES,
    LINK_CLOSE,
    LINK_MIDDLE,
    LINK_OPEN,
    STRONG_MARK,
} from "#configuration/constants/markdown.constants";

interface Span {
    readonly end: number;
    readonly markup: string;
}

interface Scan {
    readonly at: number;
    readonly emphasis: boolean;
    readonly out: string;
    readonly strong: boolean;
}

export const escaped = function escaped(text: string): string {
    let out = "";
    for (const char of text) {
        out += ESCAPES.get(char) ?? char;
    }
    return out;
};

const linkAt = function linkAt(text: string, at: number): Span | null {
    const middle = text.indexOf(LINK_MIDDLE, at);
    const close = middle === -1 ? -1 : text.indexOf(LINK_CLOSE, middle);
    if (middle === -1 || close === -1) {
        return null;
    }
    const label = inlineMarkup(text.slice(at + 1, middle));
    const href = text.slice(middle + LINK_MIDDLE.length, close);
    return { end: close + 1, markup: `<a href="${escaped(href)}">${label}</a>` };
};

const codeAt = function codeAt(text: string, at: number): Span | null {
    const close = text.indexOf(CODE_MARK, at + 1);
    return close === -1 ? null : { end: close + 1, markup: `<code>${escaped(text.slice(at + 1, close))}</code>` };
};

const spanAt = function spanAt(text: string, at: number): Span | null {
    const char = text.charAt(at);
    if (char === LINK_OPEN) {
        return linkAt(text, at);
    }
    return char === CODE_MARK ? codeAt(text, at) : null;
};

const step = function step(text: string, scan: Scan): Scan {
    const span = spanAt(text, scan.at);
    if (span !== null) {
        return { ...scan, at: span.end, out: scan.out + span.markup };
    }
    if (text.startsWith(STRONG_MARK, scan.at)) {
        return {
            ...scan,
            at: scan.at + STRONG_MARK.length,
            out: scan.out + (scan.strong ? "</strong>" : "<strong>"),
            strong: !scan.strong,
        };
    }
    const char = text.charAt(scan.at);
    if (EMPHASIS_MARKS.has(char)) {
        return {
            ...scan,
            at: scan.at + 1,
            emphasis: !scan.emphasis,
            out: scan.out + (scan.emphasis ? "</em>" : "<em>"),
        };
    }
    return { ...scan, at: scan.at + 1, out: scan.out + escaped(char) };
};

export const inlineMarkup = function inlineMarkup(text: string): string {
    let scan: Scan = { at: 0, emphasis: false, out: "", strong: false };
    while (scan.at < text.length) {
        scan = step(text, scan);
    }
    return scan.out;
};
```
