Skip to content
Snippets Groups Projects
splitAtDelimiters.ts 2.59 KiB
// Adapted from https://github.com/KaTeX/KaTeX/blob/main/contrib/auto-render/splitAtDelimiters.js

interface Delimiter {
    left: string;
    right: string;
    display: boolean;
}

interface TextElement {
    type: string;
    data: string;
    rawData?: string;
    display?: boolean;
}

const findEndOfMath = function (
    delimiter: string,
    text: string,
    startIndex: number
) {
    let index = startIndex;
    let braceLevel = 0;

    const delimLength = delimiter.length;

    while (index < text.length) {
        const character = text[index];

        if (
            braceLevel <= 0 &&
            text.slice(index, index + delimLength) === delimiter
        ) {
            return index;
        } else if (character === "\\") {
            index++;
        } else if (character === "{") {
            braceLevel++;
        } else if (character === "}") {
            braceLevel--;
        }

        index++;
    }

    return -1;
};

const escapeRegex = function (string: string) {
    return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
};

const amsRegex = /^\\begin{/;

const splitAtDelimiters = function (
    text: string,
    delimiters: Array<Delimiter>
): Array<TextElement> {
    let index;
    const data = [];

    const regexLeft = new RegExp(
        "(" + delimiters.map((x) => escapeRegex(x.left)).join("|") + ")"
    );

    // eslint-disable-next-line no-constant-condition
    while (true) {
        index = text.search(regexLeft);
        if (index === -1) {
            break;
        }
        if (index > 0) {
            data.push({
                type: "text",
                data: text.slice(0, index),
            });
            text = text.slice(index); // now text starts with delimiter
        }
        // ... so this always succeeds:
        const i = delimiters.findIndex((delim) => text.startsWith(delim.left));
        index = findEndOfMath(
            delimiters[i].right,
            text,
            delimiters[i].left.length
        );
        if (index === -1) {
            break;
        }
        const rawData = text.slice(0, index + delimiters[i].right.length);
        const math = amsRegex.test(rawData)
            ? rawData
            : text.slice(delimiters[i].left.length, index);
        data.push({
            type: "math",
            data: math,
            rawData,
            display: delimiters[i].display,
        });
        text = text.slice(index + delimiters[i].right.length);
    }

    if (text !== "") {
        data.push({
            type: "text",
            data: text,
        });
    }

    return data;
};

export default splitAtDelimiters;