import { htmlAttributes } from './html-attributes';
export interface IndexedHtmlElement {
tagName: string;
start: number;
bodyStart: number;
selfClosing: boolean;
}
/** Import-local index over already protected/sanitized lexical markup. No DOM
* or persistent cache: offsets belong only to this exact immutable string. */
export function indexHtmlElements(markup: string) {
const byId = new Map();
const namedAnchors = new Map();
const definitions = new Map();
const pattern = /<([a-zA-Z][\w:-]*)\b([^>]*)>/g;
let match: RegExpExecArray | null;
while ((match = pattern.exec(markup))) {
let id: string | undefined;
let name: string | undefined;
for (const attribute of htmlAttributes(match[2] ?? '')) {
if (attribute.name === 'id' && id === undefined) id = attribute.value;
if (attribute.name === 'name' && name === undefined) name = attribute.value;
}
if (!id && !name) continue;
const element = { tagName: match[1], start: match.index, bodyStart: pattern.lastIndex, selfClosing: /\/\s*>$/.test(match[0]) };
if (id && !byId.has(id)) byId.set(id, element);
if (name && element.tagName.toLowerCase() === 'a' && !namedAnchors.has(name)) namedAnchors.set(name, element);
for (const target of new Set([id, name])) {
if (!target) continue;
const previous = definitions.get(target);
if (previous) previous.count++;
else definitions.set(target, { count: 1, start: element.start });
}
}
return { byId, namedAnchors, definitions };
}
export type HtmlElementIndex = ReturnType;