{"_id":"@behackl/citation-js-extras","_rev":"2-52e3da15f040705f80549da969d9da4c","name":"@behackl/citation-js-extras","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@behackl/citation-js-extras","version":"0.1.0","keywords":["citation-js","bibtex","bibliography","csl","custom-fields","academic"],"author":{"name":"Benjamin Hackl"},"license":"MIT","_id":"@behackl/citation-js-extras@0.1.0","maintainers":[{"name":"behackl","email":"devel@benjamin-hackl.at"}],"homepage":"https://github.com/behackl/citation-js-extra#readme","bugs":{"url":"https://github.com/behackl/citation-js-extra/issues"},"dist":{"shasum":"29bc0fd44c8a3509cdf8dae1f156e3dad58b39a7","tarball":"https://registry.npmjs.org/@behackl/citation-js-extras/-/citation-js-extras-0.1.0.tgz","fileCount":7,"integrity":"sha512-QZk/F85wQmXyM8prtARgBfyaAU25PBIdY2RclstmPVC6uKjKU8KfOhrvAbZlT3tfv6E37JRwE44lSWttCAYelA==","signatures":[{"sig":"MEQCIDAnV9Pyaw0xhw6HZZxmMnsqIbF4yTI9hEdtDlfPnc7KAiBPF3LC9KqM/K+aztgKCkcMwTHeoG2vf/0Jqj6Ic4WvaQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":30376},"main":"./dist/index.js","type":"module","_from":"file:behackl-citation-js-extras-0.1.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc","test:watch":"vitest"},"_npmUser":{"name":"behackl","email":"devel@benjamin-hackl.at"},"_resolved":"/private/var/folders/vg/y5mlmw8n7bxbd62gk4h02bgw0000gn/T/e18f5f1c5c9ac54eb6e5c504266aa056/behackl-citation-js-extras-0.1.0.tgz","_integrity":"sha512-QZk/F85wQmXyM8prtARgBfyaAU25PBIdY2RclstmPVC6uKjKU8KfOhrvAbZlT3tfv6E37JRwE44lSWttCAYelA==","repository":{"url":"git+https://github.com/behackl/citation-js-extra.git","type":"git"},"_npmVersion":"11.5.2","description":"Preserve custom BibTeX fields through citation-js and render academic bibliographies with linked titles, badges, and more.","directories":{},"sideEffects":false,"_nodeVersion":"22.15.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.1.4","typescript":"^5.9.3","@types/node":"^25.2.3","citation-js":"^0.7.22"},"peerDependencies":{"citation-js":">=0.7.0"},"_npmOperationalInternal":{"tmp":"tmp/citation-js-extras_0.1.0_1771362728720_0.3430959326443599","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@behackl/citation-js-extras","version":"0.2.0","description":"Preserve custom BibTeX fields through citation-js and render academic bibliographies with linked titles, badges, and more.","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc","test":"vitest run","test:watch":"vitest","test:package":"pnpm build && node scripts/test-package.mjs","prepublishOnly":"tsc"},"keywords":["citation-js","bibtex","bibliography","csl","custom-fields","academic"],"author":{"name":"Benjamin Hackl"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/behackl/citation-js-extras.git"},"bugs":{"url":"https://github.com/behackl/citation-js-extras/issues"},"homepage":"https://github.com/behackl/citation-js-extras#readme","publishConfig":{"access":"public"},"engines":{"node":">=18"},"sideEffects":false,"packageManager":"pnpm@10.29.1","peerDependencies":{"citation-js":">=0.7.0"},"devDependencies":{"@mathjax/src":"4.1.0","@types/node":"^25.2.3","citation-js":"^0.7.22","typescript":"^5.9.3","vitest":"^3.1.4"},"pnpm":{"onlyBuiltDependencies":["esbuild"]},"gitHead":"c9550e7e33de06ba1b82f60fbd4f9b542cf55f9f","_id":"@behackl/citation-js-extras@0.2.0","_nodeVersion":"24.20.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-W3ItkQP/rkDKsc/h35LcM8ZHpApcPnDvMf3LnF41Li17cc3CKBBNMXJKR84dMZSDCFm8DNJKy1TD2uR/o7b3TA==","shasum":"2eba60d4ca06f39d779221624439df6782de82dc","tarball":"https://registry.npmjs.org/@behackl/citation-js-extras/-/citation-js-extras-0.2.0.tgz","fileCount":9,"unpackedSize":39225,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@behackl%2fcitation-js-extras@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCKaHLMcLnMUld6nqRSv5yQHnZqEeTSDLYF+17GbLS07AIgCG3PYmIwzWSyrAucO8gzH8Phx80a7Kkpo6XhVT+VP4g="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8e2d7331-fe27-4ed2-bcfe-d1b4490549f3"}},"directories":{},"maintainers":[{"name":"behackl","email":"devel@benjamin-hackl.at"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/citation-js-extras_0.2.0_1788688596388_0.4865582906704047"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-17T21:12:08.583Z","modified":"2026-09-06T09:56:36.810Z","0.1.0":"2026-02-17T21:12:08.892Z","0.2.0":"2026-09-06T09:56:36.510Z"},"bugs":{"url":"https://github.com/behackl/citation-js-extras/issues"},"author":{"name":"Benjamin Hackl"},"license":"MIT","homepage":"https://github.com/behackl/citation-js-extras#readme","keywords":["citation-js","bibtex","bibliography","csl","custom-fields","academic"],"repository":{"type":"git","url":"git+https://github.com/behackl/citation-js-extras.git"},"description":"Preserve custom BibTeX fields through citation-js and render academic bibliographies with linked titles, badges, and more.","maintainers":[{"name":"behackl","email":"devel@benjamin-hackl.at"}],"readme":"# @behackl/citation-js-extras\n\n![NPM Version](https://img.shields.io/npm/v/%40behackl%2Fcitation-js-extras)\n\nPreserve custom BibTeX fields through [citation-js](https://citation.js.org/) and render academic bibliographies with linked titles, configurable badges, and more.\n\n## The problem\n\ncitation-js converts BibTeX to CSL-JSON, but silently **drops all non-standard fields** during the conversion. There is no plugin hook or configuration option to preserve them. Fields like `arxiv`, `mrnumber`, `publication-status`, or project identifiers are lost.\n\nThis package solves the problem with a two-pass parsing strategy: one pass extracts the raw BibTeX fields, the other produces CSL-JSON for formatting. The results are merged so you get the best of both worlds.\n\n## Install\n\n```bash\nnpm install @behackl/citation-js-extras citation-js\n# or\npnpm add @behackl/citation-js-extras citation-js\n```\n\n`citation-js` is a **peer dependency** — you bring your own version.\n\n## Quick start\n\n```ts\nimport { Bibliography } from \"@behackl/citation-js-extras\";\n\nconst bib = new Bibliography({\n  data: \"./references.bib\", // file path or raw BibTeX string\n  cslStyle: \"./my-style.csl\", // optional: file path, raw XML, or registered template name\n  customFields: [\"publication-status\", \"arxiv\", \"mrnumber\"],\n});\n\n// Filter and sort\nconst published = bib.filter({ \"publication-status\": \"published\" });\nconst sorted = bib.sort(published, { by: \"year\", order: \"desc\" });\n\n// Render HTML\nconst html = bib.formatHtml(sorted, {\n  titleLink: [\"url\", \"doi\", \"arxiv\"],\n  badges: [\n    { field: \"doi\", label: \"doi\", url: \"https://doi.org/$1\", className: \"badge-doi\" },\n    {\n      field: \"arxiv\",\n      label: \"arXiv\",\n      url: \"https://arxiv.org/abs/$1\",\n      match: /^(.+?)(?:v\\d+)?$/,\n      className: \"badge-arxiv\",\n    },\n  ],\n});\n```\n\n## Preserving mathematics\n\nCitation.js normally converts TeX math to text, losing delimiters and potentially\ncomplex expressions. Enable preservation before parsing:\n\n```ts\nconst bib = new Bibliography({\n  data: \"./references.bib\",\n  preserveMath: true,\n});\n\n// Restore HTML-escaped original TeX for client-side MathJax:\nconst html = bib.formatHtml(bib.entries);\n\n// Or typeset at build time with your own synchronous renderer:\nconst rendered = bib.formatHtml(bib.entries, {\n  renderMath: (tex, { display }) => myMathRenderer(tex, display),\n});\n```\n\n`myMathRenderer` is an application-supplied function returning **trusted HTML**\n(e.g. MathJax SVG or KaTeX output). No math renderer is bundled. Configure it for\nuntrusted TeX as appropriate; the callback output is inserted verbatim, not\nsanitized. Exceptions propagate to the caller. `renderMath` also works with\n`formatEntry`; it has no effect unless `preserveMath` was enabled.\n\nSupported delimiters are `$…$`, `$$…$$`, `\\(…\\)`, and `\\[…\\]`.\nEscape literal dollars as `\\$`. Empty or unclosed expressions throw with the\ncitation key and field name. This is a delimiter scanner, not a TeX validator:\nunsupported commands and mathematical validity are the renderer's responsibility.\n\nProtection covers `title`, `subtitle`, `titleaddon`, `shorttitle`, `booktitle`,\n`booksubtitle`, `booktitleaddon`, `maintitle`, `mainsubtitle`, `maintitleaddon`,\n`journaltitle`, `journalsubtitle`, `journal`, `note`, `annote`, `abstract`, and\n`howpublished`. Fields still need to be supported by Citation.js and the chosen\nCSL style to appear in the output. Names, identifiers, URLs, and custom metadata\nare not protected. BibTeX strings and concatenations are resolved before protection;\ninherited cross-reference text is protected during conversion.\n\nOriginal `.raw` and `.custom` values remain unchanged. With preservation enabled,\n`.csl` contains internal placeholders: use the formatting methods for HTML and\nraw fields for original source text, not `.csl` for plain-text exports. Placeholders\nalso mean CSL title-based sorting/disambiguation operates on protected text rather\nthan mathematical meaning. Existing caller-controlled ordering is retained.\n\nRestoration runs after title linking, badges, and URL linkification. Neither\nrenderer output nor restored TeX is fed back through these HTML helpers. Disabling\npreservation (the default) retains the previous behavior.\n\n## Development checks\n\n```sh\npnpm install --frozen-lockfile\npnpm test          # Unit tests and actual MathJax SVG integration (base + AMS)\npnpm test:package  # Build, pack, install into a temporary consumer, and test exports\n```\n\nThe package check validates ESM imports, TypeScript declarations under both\nNodeNext and Bundler resolution, and MathJax rendering through the installed\ntarball. Its temporary consumer is removed afterwards. Installation prefers the\nlocal cache but may need registry access on a fresh machine. CI runs both checks.\nMathJax is a development-only dependency, not a runtime dependency for consumers.\nUse Node 24 LTS for these development checks, matching CI and publishing.\n\n## API\n\n### `new Bibliography(options)`\n\n| Option | Type | Description |\n|---|---|---|\n| `data` | `string` | BibTeX input — a raw string or a file path. |\n| `cslStyle` | `string?` | CSL style — a registered template name, raw XML, or a file path. Defaults to `'apa'`. |\n| `customFields` | `string[]?` | BibTeX field names to preserve. These appear on each entry under `.custom`. |\n| `preserveMath` | `boolean?` | Preserve math in display-text fields through CSL formatting. Defaults to `false`. |\n\n### `bib.entries`\n\nAll parsed entries as `BibEntry[]`:\n\n```ts\ninterface BibEntry {\n  csl: Record<string, any>; // CSL-JSON data (for citation-js)\n  key: string; // BibTeX citation key\n  year: number | null; // extracted from CSL `issued`\n  custom: Record<string, string>; // declared custom fields\n  raw: Record<string, any>; // all raw BibTeX properties\n}\n```\n\n### `bib.filter(criteria)`\n\nFilter entries by custom field values. All criteria must match (AND logic).\n\n```ts\nbib.filter({ \"publication-status\": \"published\" });\nbib.filter({ \"publication-status\": \"published\", project: \"ABC-123\" });\n```\n\n### `bib.sort(entries, options?)`\n\nReturn a sorted **copy** of the entries (the input is not mutated).\n\n```ts\nbib.sort(entries); // by year, descending (default)\nbib.sort(entries, { by: \"year\", order: \"asc\" });\n```\n\n### `bib.formatHtml(entries, options?)`\n\nRender entries as a complete HTML bibliography list.\n\n```ts\nbib.formatHtml(entries, {\n  titleLink: [\"url\", \"doi\", \"arxiv\"],\n  badges: [ /* ... */ ],\n  list: \"ol\", // 'ol', 'ul', or 'div' (div uses <div class=\"csl-entry\"> children)\n  listAttributes: { reversed: true },\n  linkifyUrls: true,\n});\n```\n\nEntries are formatted in one citeproc pass, so style-dependent state (for example numeric labels in Vancouver) remains correct.\n\n### `bib.formatEntry(entry, options?)`\n\nRender a single entry as an HTML string (no list wrapper). The title link targets the actual CSL title text, regardless of italics.\n\nFor citation styles that depend on multi-entry context (numbered labels, ibid behavior, etc.), prefer `formatHtml(...)`.\n\nTitle links are only created for safe URL schemes (`http`, `https`, `mailto`) or normalized DOI/arXiv links.\n\n### Badges\n\nBadges are small inline links appended to each entry. They are configured declaratively:\n\n```ts\ninterface BadgeConfig {\n  field: string; // BibTeX field name to read\n  label: string; // display text (e.g. \"doi\", \"arXiv\")\n  url: string; // URL template — $1 is replaced by the field value\n  match?: RegExp; // optional: validate/transform the field value\n  className?: string; // CSS class(es) for the <a> element\n}\n```\n\nThe `url` template uses `$1` as a placeholder for the field value:\n\n```ts\n{ field: \"doi\", label: \"doi\", url: \"https://doi.org/$1\" }\n// doi: \"10.1234/example\" → href=\"https://doi.org/10.1234/example\"\n```\n\nWhen `match` is provided, the field value is tested against the regex. If it doesn't match, the badge is skipped. If it matches, `$1` in the URL is replaced by the **first capture group** (or the full match if there are no capture groups):\n\n```ts\n// Strip version suffix from arXiv IDs:\n{ field: \"arxiv\", label: \"arXiv\",\n  url: \"https://arxiv.org/abs/$1\",\n  match: /^(.+?)(?:v\\d+)?$/ }\n// \"2301.00001v3\" → capture group \"2301.00001\" → href=\".../2301.00001\"\n\n// Only link if the field looks like a valid identifier:\n{ field: \"zbl\", label: \"zbMATH\",\n  url: \"https://zbmath.org/?q=an:$1\",\n  match: /^(\\d+\\.\\d+)$/ }\n// \"7654.12345\" → match → linked\n// \"not-a-number\" → no match → badge skipped\n```\n\nBadge labels are HTML-escaped before rendering. Generated badge links are emitted only for `http(s)` and `mailto:` URLs; unsafe schemes are skipped.\n\n### `linkifyBareUrls(html)`\n\nStandalone utility: auto-linkify bare `http(s)://` URLs in HTML text nodes that aren't already inside `<a>`, `<script>`, or `<style>` tags. Trailing punctuation is kept outside the link.\n\n```ts\nimport { linkifyBareUrls } from \"@behackl/citation-js-extras\";\n\nlinkifyBareUrls(\"See https://example.com.\");\n// → 'See <a href=\"https://example.com\">https://example.com</a>.'\n```\n\n## Custom CSL styles\n\nPass a file path or raw XML to `cslStyle`. You can also pass the name of any template already registered with citation-js:\n\n```ts\nconst bib = new Bibliography({\n  data: bibtex,\n  cslStyle: \"./styles/my-department.csl\",\n  customFields: [\"publication-status\"],\n});\n```\n\nThe style is registered with citation-js and used for all formatting calls.\n\nRaw CSL XML styles are internally registered under deterministic content-hash names to avoid collisions between multiple `Bibliography` instances.\n\n## How it works\n\ncitation-js has a hardcoded list of ~106 BibTeX → CSL field mappings. Any field not in that list is silently dropped. There is no plugin API to extend this mapping.\n\nThis package works around the limitation with a **two-pass parse**:\n\n1. `Cite.plugins.input.chainLink(bibData)` — returns raw BibTeX entries with **all** fields preserved (but no CSL conversion).\n2. `new Cite(bibData)` — returns CSL-JSON entries (needed for formatted output via citeproc) but with custom fields stripped.\n\nThe results are merged by citation key, giving you CSL-formatted output **and** access to every custom BibTeX field.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}