spb/wp-spboucher-ai
Public
1export type CitablePaper = {2 identifier: string; slug: string; title: string; subtitle?: string | null; year?: number | null;3 authors: { givenName: string; familyName: string }[];4 seriesName: string; institution: string; url: string; versionLabel?: string | null;5 doi?: string | null; ssrnId?: string | null; keywords?: string[]; abstract?: string[]; releasedOn?: string | null; releasedPrecision?: string | null; pages?: number | null; jel?: string[];6};7const esc = (s: string) => s.replace(/[{}]/g, "").replace(/&/g, "\\&").replace(/%/g, "\\%");8function monthOf(d?: string | null) { if (!d) return null; const m = Number(d.slice(5, 7)); return Number.isFinite(m) && m >= 1 && m <= 12 ? m : null; }9const MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];10function fullTitle(p: CitablePaper) { return p.subtitle ? `${p.title}: ${p.subtitle}` : p.title; }11function authorsApa(a: CitablePaper["authors"]) {12 const parts = a.map((x) => `${x.familyName}, ${x.givenName.split(/[\s-]+/).map((g) => g[0] + ".").join("-")}`);13 if (parts.length <= 1) return parts[0] ?? "";14 if (parts.length === 2) return `${parts[0]}, & ${parts[1]}`;15 return parts.slice(0, -1).join(", ") + ", & " + parts[parts.length - 1];16}17function authorsChicago(a: CitablePaper["authors"]) {18 const first = a[0] ? `${a[0].familyName}, ${a[0].givenName}` : ""; const rest = a.slice(1).map((x) => `${x.givenName} ${x.familyName}`);19 if (!rest.length) return first; if (rest.length === 1) return `${first}, and ${rest[0]}`; return `${first}, ${rest.slice(0, -1).join(", ")}, and ${rest[rest.length - 1]}`;20}21export function citationApa(p: CitablePaper) {22 const y = p.year ?? "n.d.";23 return `${authorsApa(p.authors)} (${y}). ${fullTitle(p)} (${p.seriesName} ${p.identifier}${p.versionLabel ? ", " + p.versionLabel : ""}). ${p.institution}. ${p.doi ? `https://doi.org/${p.doi}` : p.url}`;24}25export function citationChicago(p: CitablePaper) {26 const y = p.year ?? "n.d.";27 return `${authorsChicago(p.authors)}. ${y}. “${fullTitle(p)}.” ${p.seriesName} ${p.identifier}. ${p.institution}. ${p.doi ? `https://doi.org/${p.doi}` : p.url}.`;28}29export function citationKey(p: CitablePaper) {30 const fam = (p.authors[0]?.familyName ?? "anon").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z]/g, "").toLowerCase();31 return `${fam}${p.year ?? ""}${p.slug}`;32}33export function bibtex(p: CitablePaper) {34 const m = monthOf(p.releasedOn); const lines = [35 `@techreport{${citationKey(p)},`,36 ` title = {${esc(fullTitle(p))}},`,37 ` author = {${p.authors.map((a) => `${esc(a.familyName)}, ${esc(a.givenName)}`).join(" and ")}},`,38 p.year ? ` year = {${p.year}},` : null,39 m && p.releasedPrecision !== "year" ? ` month = ${MONTHS[m - 1]},` : null,40 ` institution = {${esc(p.institution)}},`,41 ` type = {${esc(p.seriesName)}},`,42 ` number = {${p.identifier}},`,43 p.versionLabel ? ` note = {Version ${p.versionLabel}},` : null,44 p.doi ? ` doi = {${p.doi}},` : null,45 ` url = {${p.url}},`,46 p.keywords?.length ? ` keywords = {${esc(p.keywords.join(", "))}},` : null,47 p.abstract?.length ? ` abstract = {${esc(p.abstract.join(" "))}},` : null,48 `}`,49 ].filter(Boolean);50 return lines.join("\n");51}52export function ris(p: CitablePaper) {53 const lines = ["TY - RPRT", `TI - ${fullTitle(p)}`, ...p.authors.map((a) => `AU - ${a.familyName}, ${a.givenName}`), p.year ? `PY - ${p.year}` : null,54 p.releasedOn ? `DA - ${p.releasedOn.slice(0, 4)}/${p.releasedPrecision === "year" ? "" : p.releasedOn.slice(5, 7)}/${p.releasedPrecision === "day" ? p.releasedOn.slice(8, 10) : ""}/` : null,55 `PB - ${p.institution}`, `T2 - ${p.seriesName}`, `M1 - ${p.identifier}`, p.versionLabel ? `ET - ${p.versionLabel}` : null,56 p.doi ? `DO - ${p.doi}` : null, `UR - ${p.url}`, ...(p.keywords ?? []).map((k) => `KW - ${k}`), p.abstract?.length ? `AB - ${p.abstract.join(" ")}` : null, "ER - "].filter(Boolean);57 return lines.join("\r\n");58}59