1/* BibTeX -> the existing publication card data. No generated JSON to maintain. */ 2(function (root) { 3 function text(value = "") { 4 return String(value) 5 .replace(/\\(?:textit|textbf|emph|textrm|textnormal)\s*\{([^{}]*)\}/g, "$1") 6 .replace(/\\([&%_#$])/g, "$1") 7 .replace(/\\(?:LaTeX|TeX)\b/g, (s) => s.slice(1)) 8 .replace(/[{}]/g, "") 9 .replace(/\s+/g, " ").trim(); 10 } 11 12 // Split only outside braces, so a braced organization name remains one author. 13 function splitAuthors(value) { 14 const result = []; 15 let depth = 0, start = 0; 16 for (let i = 0; i < value.length; i++) { 17 if (value[i] === "\\") { i++; continue; } 18 if (value[i] === "{") depth++; 19 if (value[i] === "}") depth--; 20 const separator = depth === 0 && value.slice(i).match(/^\s+and\s+/i); 21 if (separator) { 22 result.push(value.slice(start, i)); 23 i += separator[0].length - 1; 24 start = i + 1; 25 } 26 } 27 result.push(value.slice(start)); 28 return result.filter((name) => name.trim()).map((name) => { 29 const trimmed = name.trim(); 30 if (trimmed.startsWith("{") && trimmed.endsWith("}")) return text(trimmed); 31 const parts = trimmed.split(",").map(text); 32 return parts.length === 1 ? parts[0] 33 : parts.length === 2 ? `${parts[1]} ${parts[0]}` 34 : `${parts[2]} ${parts[0]}, ${parts[1]}`; 35 }); 36 } 37 38 function httpURL(value) { 39 try { 40 const url = new URL(value); 41 return ["https:", "http:"].includes(url.protocol) ? url.href : ""; 42 } catch { return ""; } 43 } 44 45 function parse(source, authorProfiles = {}) { 46 const keys = new Set(); 47 return root.bibtexParse.toJSON(source).filter((entry) => entry.entryTags).map((entry) => { 48 const f = Object.fromEntries(Object.entries(entry.entryTags).map(([k, v]) => [k.toLowerCase(), v])); 49 const id = entry.citationKey; 50 if (!id || keys.has(id)) throw new Error(`Missing or duplicate BibTeX key: ${id}`); 51 keys.add(id); 52 if (!f.title || !f.author || !/^\d{4}$/.test(text(f.year))) { 53 throw new Error(`${id}: title, author and a four-digit year are required`); 54 } 55 const doi = text(f.doi).replace(/^(?:https?:\/\/(?:dx\.)?doi\.org\/|doi:\s*)/i, ""); 56 if (doi && !/^10\.\d{4,9}\/\S+$/i.test(doi)) throw new Error(`${id}: invalid DOI`); 57 const href = doi ? `https://doi.org/${doi}` : httpURL(text(f.url)); 58 const venue = [text(f.journal || f.booktitle), text(f.volume), text(f.pages).replace(/--/g, "â")] 59 .filter(Boolean).join(", "); 60 return { 61 id, title: text(f.title), year: Number(text(f.year)), venue, 62 authors: splitAuthors(f.author).map((name) => { 63 const profile = Object.hasOwn(authorProfiles, name) ? authorProfiles[name] : {}; 64 return { name, ...(profile.me ? { me: true } : {}), 65 ...(httpURL(profile.url) ? { url: httpURL(profile.url) } : {}) }; 66 }), 67 image: text(f.image), category: text(f.category), summary: text(f.abstract), 68 notes: f.award ? text(f.award).split(/\s*;\s*/).filter(Boolean) : [], 69 links: href ? [{ label: doi ? "doi" : "paper", url: href }] : [], 70 }; 71 }); 72 } 73 74 async function load(path) { 75 const [bib, authors] = await Promise.all([ 76 fetch(path, { cache: "no-cache" }), fetch("data/authors.json", { cache: "no-cache" }), 77 ]); 78 if (!bib.ok || !authors.ok) throw new Error("Could not load publication data"); 79 return parse(await bib.text(), await authors.json()); 80 } 81 root.PublicationData = { parse, load }; 82})(globalThis);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.