spb/autoscholar
Public
1export interface BibEntry {2 type: string;3 key: string;4 fields: Record<string, string>;5}67/** Minimal BibTeX parser (enough for key extraction and field checks). */8export function parseBib(text: string): BibEntry[] {9 const entries: BibEntry[] = [];10 const re = /@(\w+)\s*\{\s*([^,\s]+)\s*,/g;11 for (const m of text.matchAll(re)) {12 const type = (m[1] ?? "").toLowerCase();13 if (type === "comment" || type === "preamble" || type === "string") continue;14 const start = (m.index ?? 0) + m[0].length;15 let depth = 1;16 let i = start;17 while (i < text.length && depth > 0) {18 if (text[i] === "{") depth++;19 else if (text[i] === "}") depth--;20 i++;21 }22 const body = text.slice(start, i - 1);23 const fields: Record<string, string> = {};24 const fre = /(\w+)\s*=\s*(\{((?:[^{}]|\{[^{}]*\})*)\}|"([^"]*)"|([^,\n]+))/g;25 for (const f of body.matchAll(fre))26 fields[(f[1] ?? "").toLowerCase()] = (f[3] ?? f[4] ?? f[5] ?? "").trim();27 entries.push({ type, key: m[2] ?? "", fields });28 }29 return entries;30}3132export function citeKeysInTex(tex: string): string[] {33 const keys = new Set<string>();34 const re =35 /\\(?:cite|citep|citet|citeauthor|citeyear|parencite|textcite|autocite|citealp|citealt|nocite)\*?(?:\[[^\]]*\]){0,2}\{([^}]+)\}/g;36 for (const m of tex.matchAll(re))37 for (const k of (m[1] ?? "").split(",")) if (k.trim() && k.trim() !== "*") keys.add(k.trim());38 return [...keys];39}40