core/analyzers/diagram.analyzer.ts

core/analyzers/diagram.analyzer.ts is a file in Bane's Lab Site. 86 lines of code and 26 definitions.

import {
    DIAGRAM_EDGE_SELECTOR,
    DIAGRAM_NODE_SELECTOR,
    DIAGRAM_WALK_SEPARATOR,
    EDGE_PART_MARK,
    EDGE_PREFIX,
    FLOWCHART_MARK,
    NODE_COUNT_MARK,
} from "#configuration/constants/diagram.constants";
import type { WalkStep } from "#types/diagram.types";

const DIGEST_ALGORITHM = "SHA-256";
const HEX_RADIX = 16;
const HEX_WIDTH = 2;
const HEX_PAD = "0";

const hexOf = function hexOf(bytes: ArrayBuffer): string {
    return [...new Uint8Array(bytes)].map((byte) => byte.toString(HEX_RADIX).padStart(HEX_WIDTH, HEX_PAD)).join("");
};

export const diagramDigest = async function diagramDigest(source: string): Promise<string> {
    return hexOf(await crypto.subtle.digest(DIGEST_ALGORITHM, new TextEncoder().encode(source)));
};

export const nodeNameOf = function nodeNameOf(id: string): string | null {
    const at = id.indexOf(FLOWCHART_MARK);
    if (at === -1) {
        return null;
    }
    const rest = id.slice(at + FLOWCHART_MARK.length);
    const cut = rest.lastIndexOf(NODE_COUNT_MARK);
    return cut === -1 ? rest : rest.slice(0, cut);
};

export const edgeEndsOf = function edgeEndsOf(
    id: string,
    names: ReadonlySet<string>,
): readonly [string, string] | null {
    if (!id.startsWith(EDGE_PREFIX)) {
        return null;
    }
    const body = id.slice(EDGE_PREFIX.length, id.lastIndexOf(EDGE_PART_MARK));
    for (let at = body.indexOf(EDGE_PART_MARK); at !== -1; at = body.indexOf(EDGE_PART_MARK, at + 1)) {
        const from = body.slice(0, at);
        const to = body.slice(at + EDGE_PART_MARK.length);
        if (names.has(from) && names.has(to)) {
            return [from, to];
        }
    }
    return null;
};

export const walkOrder = function walkOrder(
    nodeIds: readonly string[],
    edgeIds: readonly string[],
): readonly WalkStep[] {
    const byName = new Map(
        nodeIds.flatMap((id) => {
            const name = nodeNameOf(id);
            return name === null ? [] : [[name, id] as const];
        }),
    );
    const names = new Set(byName.keys());
    const steps: WalkStep[] = [];
    const seen = new Set<string>();
    const visit = function visit(name: string): void {
        const id = byName.get(name);
        if (id !== undefined && !seen.has(id)) {
            seen.add(id);
            steps.push({ id, kind: "node" });
        }
    };
    for (const edge of edgeIds) {
        const ends = edgeEndsOf(edge, names);
        if (ends !== null) {
            visit(ends[0]);
            steps.push({ id: edge, kind: "edge" });
            visit(ends[1]);
        }
    }
    for (const name of names) {
        visit(name);
    }
    return steps;
};

export const walkAttributeOf = function walkAttributeOf(svg: ParentNode): string {
    const ids = function ids(selector: string): string[] {
        return [...svg.querySelectorAll(selector)].map((element) => element.id);
    };
    const order = walkOrder(ids(DIAGRAM_NODE_SELECTOR), ids(DIAGRAM_EDGE_SELECTOR)).map((step) => step.id);
    return order.join(DIAGRAM_WALK_SEPARATOR);
};