@cyanheads/pubmed-mcp-server 2.10.21 → 2.10.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/CLAUDE.md +2 -2
- package/Dockerfile +3 -3
- package/README.md +8 -6
- package/changelog/2.10.x/2.10.22.md +44 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/_budget.d.ts +35 -6
- package/dist/mcp-server/tools/definitions/_budget.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/_budget.js +26 -3
- package/dist/mcp-server/tools/definitions/_budget.js.map +1 -1
- package/dist/mcp-server/tools/definitions/_notices.d.ts +33 -0
- package/dist/mcp-server/tools/definitions/_notices.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_notices.js +35 -0
- package/dist/mcp-server/tools/definitions/_notices.js.map +1 -0
- package/dist/mcp-server/tools/definitions/_schemas.d.ts +14 -0
- package/dist/mcp-server/tools/definitions/_schemas.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/_schemas.js +9 -0
- package/dist/mcp-server/tools/definitions/_schemas.js.map +1 -1
- package/dist/mcp-server/tools/definitions/fetch-articles.tool.d.ts +3 -0
- package/dist/mcp-server/tools/definitions/fetch-articles.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/fetch-articles.tool.js +28 -15
- package/dist/mcp-server/tools/definitions/fetch-articles.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.js +107 -48
- package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/format-citations.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/format-citations.tool.js +3 -16
- package/dist/mcp-server/tools/definitions/format-citations.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/lookup-mesh.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/lookup-mesh.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/lookup-mesh.tool.js +66 -76
- package/dist/mcp-server/tools/definitions/lookup-mesh.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-fetch.tool.d.ts +6 -5
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-fetch.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-fetch.tool.js +11 -17
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-fetch.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.d.ts +4 -4
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.js +9 -8
- package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/spell-check.tool.d.ts +4 -4
- package/dist/mcp-server/tools/definitions/spell-check.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/spell-check.tool.js +12 -8
- package/dist/mcp-server/tools/definitions/spell-check.tool.js.map +1 -1
- package/dist/services/error-contracts.d.ts +1 -1
- package/dist/services/error-contracts.d.ts.map +1 -1
- package/dist/services/error-contracts.js +1 -1
- package/dist/services/error-contracts.js.map +1 -1
- package/dist/services/europe-pmc/europe-pmc-service.d.ts +31 -11
- package/dist/services/europe-pmc/europe-pmc-service.d.ts.map +1 -1
- package/dist/services/europe-pmc/europe-pmc-service.js +122 -47
- package/dist/services/europe-pmc/europe-pmc-service.js.map +1 -1
- package/dist/services/europe-pmc/types.d.ts +5 -1
- package/dist/services/europe-pmc/types.d.ts.map +1 -1
- package/dist/services/ncbi/formatting/citation-formatter.d.ts.map +1 -1
- package/dist/services/ncbi/formatting/citation-formatter.js +204 -110
- package/dist/services/ncbi/formatting/citation-formatter.js.map +1 -1
- package/dist/services/ncbi/ncbi-service.d.ts +22 -0
- package/dist/services/ncbi/ncbi-service.d.ts.map +1 -1
- package/dist/services/ncbi/ncbi-service.js +113 -17
- package/dist/services/ncbi/ncbi-service.js.map +1 -1
- package/dist/services/ncbi/parsing/article-parser.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/article-parser.js +11 -4
- package/dist/services/ncbi/parsing/article-parser.js.map +1 -1
- package/dist/services/ncbi/parsing/pmc-article-parser.d.ts +5 -1
- package/dist/services/ncbi/parsing/pmc-article-parser.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/pmc-article-parser.js +85 -31
- package/dist/services/ncbi/parsing/pmc-article-parser.js.map +1 -1
- package/dist/services/ncbi/parsing/pmc-xml-helpers.d.ts +13 -0
- package/dist/services/ncbi/parsing/pmc-xml-helpers.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/pmc-xml-helpers.js +108 -10
- package/dist/services/ncbi/parsing/pmc-xml-helpers.js.map +1 -1
- package/dist/services/ncbi/parsing/text-helpers.d.ts +10 -0
- package/dist/services/ncbi/parsing/text-helpers.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/text-helpers.js +19 -0
- package/dist/services/ncbi/parsing/text-helpers.js.map +1 -1
- package/dist/services/ncbi/response-handler.d.ts +0 -11
- package/dist/services/ncbi/response-handler.d.ts.map +1 -1
- package/dist/services/ncbi/response-handler.js +21 -33
- package/dist/services/ncbi/response-handler.js.map +1 -1
- package/dist/services/ncbi/types.d.ts +30 -30
- package/dist/services/ncbi/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/server.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/europe-pmc/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/europe-pmc/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"citation-formatter.d.ts","sourceRoot":"","sources":["../../../../src/services/ncbi/formatting/citation-formatter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,aAAa,EAIb,wBAAwB,EAEzB,MAAM,aAAa,CAAC;AAErB,yCAAyC;AACzC,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,WAAW,CAAC;AAE3E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,YAChC,cAAc,EACd,2BAA2B,EAC3B,uBAAuB,EACvB,WAAW,EACX,2BAA2B,CACnB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,wBAAwB,GAAG;IAAE,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAQxF,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,EAAE,CAExE;
|
|
1
|
+
{"version":3,"file":"citation-formatter.d.ts","sourceRoot":"","sources":["../../../../src/services/ncbi/formatting/citation-formatter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,aAAa,EAIb,wBAAwB,EAEzB,MAAM,aAAa,CAAC;AAErB,yCAAyC;AACzC,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,WAAW,CAAC;AAE3E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,YAChC,cAAc,EACd,2BAA2B,EAC3B,uBAAuB,EACvB,WAAW,EACX,2BAA2B,CACnB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,wBAAwB,GAAG;IAAE,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAQxF,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,EAAE,CAExE;AAguBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAmExD;AAkDD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAyDxD;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAqH3D;AAmBD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAuGxD;AAqGD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CA2D9D;AAMD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,MAAM,CAanF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,aAAa,EAAE,GACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB"}
|
|
@@ -191,24 +191,6 @@ function stripTrailingPeriod(text) {
|
|
|
191
191
|
function terminate(text) {
|
|
192
192
|
return text.endsWith('.') ? text : `${text}.`;
|
|
193
193
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Split a pages string like "45-67" into start and end components.
|
|
196
|
-
* Handles en-dashes, em-dashes, and hyphens. Expands PubMed's truncated-end
|
|
197
|
-
* convention (e.g., "737-8" → { start: "737", end: "738" }, "1639-41" →
|
|
198
|
-
* "1639"/"1641") so downstream RIS/BibTeX consumers see absolute page numbers.
|
|
199
|
-
*/
|
|
200
|
-
function splitPages(pages) {
|
|
201
|
-
if (!pages)
|
|
202
|
-
return {};
|
|
203
|
-
const parts = pages.split(/[-\u2013\u2014]/).map((p) => p.trim());
|
|
204
|
-
let [start, end] = parts;
|
|
205
|
-
if (start && end && end.length < start.length) {
|
|
206
|
-
end = start.slice(0, start.length - end.length) + end;
|
|
207
|
-
}
|
|
208
|
-
if (start && end)
|
|
209
|
-
return { start, end };
|
|
210
|
-
return start ? { start } : {};
|
|
211
|
-
}
|
|
212
194
|
/**
|
|
213
195
|
* The electronic article locator that Vancouver, BibTeX, and RIS print in place
|
|
214
196
|
* of a page range, or undefined.
|
|
@@ -230,14 +212,31 @@ function articleLocator(journal) {
|
|
|
230
212
|
* `662953`, Springer `42`). A page that matches no locator stays a page, though
|
|
231
213
|
* some are article numbers the record cannot identify (an Elsevier `105471`
|
|
232
214
|
* beside an `S…` pii looks exactly like a Lancet page `2413`). (#217)
|
|
215
|
+
*
|
|
216
|
+
* A dashless `MedlinePgn` also matches the locator with its hyphens removed:
|
|
217
|
+
* American Society for Microbiology journals deposit `e0364621` beside pii
|
|
218
|
+
* `e03646-21`. The locator's form is the one cited, as the publisher numbers
|
|
219
|
+
* the article. Pagination holding a dash is a range and never matches. (#231)
|
|
233
220
|
*/
|
|
234
221
|
function articleNumber(journal) {
|
|
235
|
-
|
|
222
|
+
const locator = journal?.elocationId;
|
|
223
|
+
if (!locator)
|
|
236
224
|
return;
|
|
237
|
-
|
|
225
|
+
const { pages } = journal;
|
|
226
|
+
if (!pages || pages === locator)
|
|
227
|
+
return locator;
|
|
228
|
+
return !PAGE_DASH.test(pages) && pages === locator.replaceAll('-', '') ? locator : undefined;
|
|
238
229
|
}
|
|
239
|
-
/**
|
|
240
|
-
const
|
|
230
|
+
/** A dash NLM's pagination puts between the ends of a range. */
|
|
231
|
+
const PAGE_DASH = /[-\u{2013}\u{2014}]/u;
|
|
232
|
+
/**
|
|
233
|
+
* One page or range inside NLM's pagination: a start, then optionally a dash
|
|
234
|
+
* and an end. An item starts only after a separator, so a long run without a
|
|
235
|
+
* dash is read once rather than retried from each of its characters.
|
|
236
|
+
*/
|
|
237
|
+
const PAGE_ITEM = /(?<![^\s,;\u{2013}\u{2014}-])([^\s,;\u{2013}\u{2014}-]+)(?:\s*[-\u{2013}\u{2014}]\s*([^\s,;\u{2013}\u{2014}-]+))?/gu;
|
|
238
|
+
/** The digits a page ends on, matched from the start of their run only. */
|
|
239
|
+
const TRAILING_DIGITS = /(?<!\d)\d+$/;
|
|
241
240
|
/**
|
|
242
241
|
* The end of a range as NLM's elision means it. An end made of digits is
|
|
243
242
|
* written against the start's digits and takes the start's prefix (`637-41` →
|
|
@@ -245,21 +244,48 @@ const PAGE_RANGE = /([^\s,;\u{2013}\u{2014}-]+)\s*[-\u{2013}\u{2014}]\s*([^\s,;\
|
|
|
245
244
|
* whole (`878.e8`, `e65`, `xv`).
|
|
246
245
|
*/
|
|
247
246
|
function fullPageEnd(start, end) {
|
|
248
|
-
const
|
|
249
|
-
if (!
|
|
247
|
+
const digits = /^\d+$/.test(end) ? TRAILING_DIGITS.exec(start) : null;
|
|
248
|
+
if (!digits)
|
|
250
249
|
return end;
|
|
251
|
-
const [
|
|
252
|
-
const elided = end.length <
|
|
253
|
-
return `${
|
|
250
|
+
const [run] = digits;
|
|
251
|
+
const elided = end.length < run.length;
|
|
252
|
+
return `${start.slice(0, digits.index)}${elided ? run.slice(0, run.length - end.length) : ''}${end}`;
|
|
254
253
|
}
|
|
255
254
|
/**
|
|
256
|
-
*
|
|
257
|
-
* `
|
|
258
|
-
*
|
|
259
|
-
* author reply 1409
|
|
255
|
+
* NLM's pagination with each range written in full (`637-41` → `637-641`,
|
|
256
|
+
* `S5-15` → `S5-S15`), joined by `dash` or by NLM's own when none is given; the
|
|
257
|
+
* rest of NLM's text stays as given — a list's separators and labels too
|
|
258
|
+
* (`1407-8; author reply 1409-10` → `1407-1408; author reply 1409-1410`). APA
|
|
259
|
+
* joins with an en dash (`637–641`); BibTeX keeps NLM's. (#217, #228)
|
|
260
260
|
*/
|
|
261
|
-
function
|
|
262
|
-
return pages.replace(
|
|
261
|
+
function expandPageRanges(pages, dash) {
|
|
262
|
+
return pages.replace(PAGE_ITEM, (item, start, end) => {
|
|
263
|
+
if (end === undefined)
|
|
264
|
+
return item;
|
|
265
|
+
const join = dash ?? item.slice(start.length, item.length - end.length);
|
|
266
|
+
return `${start}${join}${fullPageEnd(start, end)}`;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/** A token that can be a page: it carries a digit or is a Roman numeral, unlike a label (`author reply`). */
|
|
270
|
+
const PAGE_TOKEN = /\d|^[ivxlcdm]+$/i;
|
|
271
|
+
/**
|
|
272
|
+
* The first and last page of NLM's pagination, for RIS `SP` and `EP`: the start
|
|
273
|
+
* of the first page or range, and the end of the last range written in full or
|
|
274
|
+
* the last listed page (`1308, 1310-4` → `1308`/`1314`, `xii-xv` → `xii`/`xv`,
|
|
275
|
+
* `S5-15` → `S5`/`S15`). A single page or article number has no end, and a
|
|
276
|
+
* label in a list (`author reply`) is not a page. NCBI's own `StartPage` and
|
|
277
|
+
* `EndPage` agree on every shape. (#228)
|
|
278
|
+
*/
|
|
279
|
+
function splitPages(pages) {
|
|
280
|
+
const items = [...pages.matchAll(PAGE_ITEM)];
|
|
281
|
+
const [, start] = items[0] ?? [];
|
|
282
|
+
const pageItems = items.filter(([, first = '', last]) => last !== undefined || PAGE_TOKEN.test(first));
|
|
283
|
+
const final = pageItems.at(-1);
|
|
284
|
+
if (!final || (pageItems.length === 1 && final[2] === undefined)) {
|
|
285
|
+
return { start, end: undefined };
|
|
286
|
+
}
|
|
287
|
+
const [, first = '', last] = final;
|
|
288
|
+
return { start, end: last === undefined ? first : fullPageEnd(first, last) };
|
|
263
289
|
}
|
|
264
290
|
/**
|
|
265
291
|
* Words APA 7 keeps lowercase in a title unless one opens it or its subtitle:
|
|
@@ -347,6 +373,63 @@ function titleCaseJournal(title, minorWords) {
|
|
|
347
373
|
})
|
|
348
374
|
.join('');
|
|
349
375
|
}
|
|
376
|
+
/** NLM's year qualifier: `Journal of women's health (2002)`. */
|
|
377
|
+
const YEAR_QUALIFIER = /^\d{4}$/;
|
|
378
|
+
/** An edition or format qualifier: `(Clinical research ed.)`, `(Landmark edition)`, `(Print)`. */
|
|
379
|
+
const EDITION_QUALIFIER = /(?:\bed\.|\bedition)$|(?:^|: )(?:Online|Print)$/i;
|
|
380
|
+
/** An abbreviation naming an edition: `Med Clin (Engl Ed)`, `Angew Chem Int Ed Engl`. */
|
|
381
|
+
const EDITION_ABBREVIATION = /\b(?:Ed|Edn|Edition)\b/i;
|
|
382
|
+
/**
|
|
383
|
+
* A place qualifier, `Place, Region` with an optional year: `London, England`,
|
|
384
|
+
* `New York, N.Y.`, `Chicago, Ill. : 1978`. Neither part holds a parenthesis,
|
|
385
|
+
* semicolon, or digit, and the region absorbs any space before the year's
|
|
386
|
+
* colon, so no two parts compete for a character and a near-miss fails in
|
|
387
|
+
* linear time.
|
|
388
|
+
*/
|
|
389
|
+
const PLACE_QUALIFIER = /^[^,():;\d]+,[^():;\d]+(?::\s*\d{4})?$/;
|
|
390
|
+
/**
|
|
391
|
+
* The parenthetical a title ends on, a nested one read as one unit
|
|
392
|
+
* (`Bulletin (Hospital for Joint Diseases (New York, N.Y.))`), with the text
|
|
393
|
+
* before it; undefined when the title does not end on a balanced one.
|
|
394
|
+
*/
|
|
395
|
+
function trailingParenthetical(title) {
|
|
396
|
+
if (!title.endsWith(')'))
|
|
397
|
+
return;
|
|
398
|
+
let depth = 0;
|
|
399
|
+
for (let i = title.length - 1; i >= 0; i--) {
|
|
400
|
+
if (title[i] === ')')
|
|
401
|
+
depth++;
|
|
402
|
+
else if (title[i] === '(' && --depth === 0) {
|
|
403
|
+
return { base: title.slice(0, i).trimEnd(), inner: title.slice(i + 1, -1) };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The journal name APA and MLA cite: NLM's title less the place, year, or
|
|
410
|
+
* edition NLM appends to tell same-named journals apart (`Lancet (London,
|
|
411
|
+
* England)` → `Lancet`, `Journal of women's health (2002)`, `BMJ (Clinical
|
|
412
|
+
* research ed.)`). The journal prints no such text. An edition stays when the
|
|
413
|
+
* ISO abbreviation names it too, as it is then a journal of its own (`Medicina
|
|
414
|
+
* clinica (English ed.)`, `Med Clin (Engl Ed)`). Any other parenthetical names
|
|
415
|
+
* the journal and stays: an issuing body (`Proceedings (Baylor University.
|
|
416
|
+
* Medical Center)`), a subject, a one-word place. Over NLM's journal list the
|
|
417
|
+
* rule is wrong on 18 of the 2,005 titles NLM's catalog settles — a place-shaped
|
|
418
|
+
* issuing body (`Collegian (Royal College of Nursing, Australia)`) is dropped,
|
|
419
|
+
* and a place with no region (`Medical record (New York : 1934)`) is kept. (#227)
|
|
420
|
+
*/
|
|
421
|
+
function citedJournalName(title, isoAbbreviation) {
|
|
422
|
+
const group = trailingParenthetical(title);
|
|
423
|
+
if (!group?.base)
|
|
424
|
+
return title;
|
|
425
|
+
const { base, inner } = group;
|
|
426
|
+
if (YEAR_QUALIFIER.test(inner))
|
|
427
|
+
return base;
|
|
428
|
+
if (EDITION_QUALIFIER.test(inner)) {
|
|
429
|
+
return EDITION_ABBREVIATION.test(isoAbbreviation ?? '') ? title : base;
|
|
430
|
+
}
|
|
431
|
+
return PLACE_QUALIFIER.test(inner) ? base : title;
|
|
432
|
+
}
|
|
350
433
|
/**
|
|
351
434
|
* Author keywords, then MeSH descriptor names, each term once: a term equal to
|
|
352
435
|
* an earlier one ignoring case is dropped, so the author's spelling and position
|
|
@@ -368,9 +451,11 @@ function mergedKeywords(article) {
|
|
|
368
451
|
return [...terms.values()];
|
|
369
452
|
}
|
|
370
453
|
/**
|
|
371
|
-
* Collapse
|
|
372
|
-
*
|
|
373
|
-
*
|
|
454
|
+
* Collapse each whitespace run to one space and trim the ends. The parser trims
|
|
455
|
+
* only a field's ends, so a line break inside a title, name, or keyword — or
|
|
456
|
+
* between a structured abstract's paragraphs — would otherwise reach a
|
|
457
|
+
* citation: an untagged RIS line, or a blank one that strict parsers read as
|
|
458
|
+
* the end of the record. Every style writes record text through this. (#233)
|
|
374
459
|
*/
|
|
375
460
|
function collapseWhitespace(text) {
|
|
376
461
|
return text.replace(/\s+/g, ' ').trim();
|
|
@@ -420,8 +505,23 @@ function escapeBibtex(text) {
|
|
|
420
505
|
// ---------------------------------------------------------------------------
|
|
421
506
|
// Author formatters
|
|
422
507
|
// ---------------------------------------------------------------------------
|
|
508
|
+
/*
|
|
509
|
+
* A generational suffix (`Jr`, `2nd`) renders verbatim as NCBI writes it, as
|
|
510
|
+
* NCBI's own citation export prints it. It qualifies a surname, so a name with
|
|
511
|
+
* no surname renders without it. Inverted name forms set it off with a comma
|
|
512
|
+
* (`Frenck, R. W., Jr`); natural-order and NLM forms append it after a space
|
|
513
|
+
* (`R. W. Frenck Jr`, `Frenck RW Jr`). (#226)
|
|
514
|
+
*/
|
|
515
|
+
/** `, Jr` for an inverted name form; `''` without a suffix or a surname. */
|
|
516
|
+
function invertedSuffix(author) {
|
|
517
|
+
return author.lastName && author.suffix ? `, ${author.suffix}` : '';
|
|
518
|
+
}
|
|
519
|
+
/** ` Jr` for a natural-order or NLM name form; `''` without a suffix or a surname. */
|
|
520
|
+
function trailingSuffix(author) {
|
|
521
|
+
return author.lastName && author.suffix ? ` ${author.suffix}` : '';
|
|
522
|
+
}
|
|
423
523
|
/**
|
|
424
|
-
* Format a single author in APA style: `Last, F. M
|
|
524
|
+
* Format a single author in APA style: `Last, F. M.`, or `Last, F. M., Jr`.
|
|
425
525
|
* Collective/group authors return the group name directly.
|
|
426
526
|
*/
|
|
427
527
|
function formatAuthorApa(author) {
|
|
@@ -436,7 +536,7 @@ function formatAuthorApa(author) {
|
|
|
436
536
|
.map((part) => `${part[0]}.`)
|
|
437
537
|
.join(' ');
|
|
438
538
|
if (!initials)
|
|
439
|
-
return last
|
|
539
|
+
return `${last}${invertedSuffix(author)}`;
|
|
440
540
|
// Extract only letter characters (Unicode-aware: preserve Á, Ö, É, etc.),
|
|
441
541
|
// format each as "X." separated by spaces.
|
|
442
542
|
const formatted = Array.from(initials.replace(/[^\p{L}]/gu, ''))
|
|
@@ -444,7 +544,7 @@ function formatAuthorApa(author) {
|
|
|
444
544
|
.join(' ');
|
|
445
545
|
if (!last)
|
|
446
546
|
return formatted;
|
|
447
|
-
return `${last}, ${formatted}`;
|
|
547
|
+
return `${last}, ${formatted}${invertedSuffix(author)}`;
|
|
448
548
|
}
|
|
449
549
|
/**
|
|
450
550
|
* Format the full author list for APA 7th edition.
|
|
@@ -471,21 +571,19 @@ function formatAuthorsApa(authors) {
|
|
|
471
571
|
}
|
|
472
572
|
/**
|
|
473
573
|
* Format a single author in MLA style.
|
|
474
|
-
* First listed author: `Last, First Middle
|
|
475
|
-
* Subsequent authors: `First Middle Last`
|
|
574
|
+
* First listed author: `Last, First Middle`, or `Last, First Middle, Jr`.
|
|
575
|
+
* Subsequent authors: `First Middle Last`, or `First Middle Last Jr`.
|
|
476
576
|
*/
|
|
477
577
|
function formatAuthorMla(author, isFirst) {
|
|
478
578
|
if (author.collectiveName)
|
|
479
579
|
return author.collectiveName;
|
|
480
580
|
const last = author.lastName ?? '';
|
|
481
581
|
const first = author.firstName ?? '';
|
|
482
|
-
if (!last && !first)
|
|
483
|
-
return '';
|
|
484
|
-
if (!first)
|
|
485
|
-
return last;
|
|
486
582
|
if (!last)
|
|
487
583
|
return first;
|
|
488
|
-
|
|
584
|
+
if (isFirst)
|
|
585
|
+
return `${first ? `${last}, ${first}` : last}${invertedSuffix(author)}`;
|
|
586
|
+
return `${first ? `${first} ${last}` : last}${trailingSuffix(author)}`;
|
|
489
587
|
}
|
|
490
588
|
/**
|
|
491
589
|
* Format the full author list for MLA 9th edition.
|
|
@@ -508,27 +606,28 @@ function formatAuthorsMla(authors) {
|
|
|
508
606
|
return `${formatAuthorMla(first, true)}, et al.`;
|
|
509
607
|
}
|
|
510
608
|
/**
|
|
511
|
-
* Format a single author in BibTeX style: `{Last},
|
|
609
|
+
* Format a single author in BibTeX style: `{Last}, First`, or the three-part
|
|
610
|
+
* `{Last}, Jr, First` when the name carries a suffix — the comma after `Jr`
|
|
611
|
+
* stays even with no first name, so the suffix is never read as one.
|
|
512
612
|
*/
|
|
513
613
|
function formatAuthorBibtex(author) {
|
|
514
614
|
if (author.collectiveName)
|
|
515
615
|
return `{${escapeBibtex(author.collectiveName)}}`;
|
|
516
616
|
const last = author.lastName ? escapeBibtex(author.lastName) : '';
|
|
517
617
|
const first = author.firstName ? escapeBibtex(author.firstName) : '';
|
|
518
|
-
if (!last && !first)
|
|
519
|
-
return '';
|
|
520
|
-
if (!first)
|
|
521
|
-
return `{${last}}`;
|
|
522
618
|
if (!last)
|
|
523
619
|
return first;
|
|
524
|
-
|
|
620
|
+
if (author.suffix)
|
|
621
|
+
return `{${last}}, ${escapeBibtex(author.suffix)},${first && ` ${first}`}`;
|
|
622
|
+
return first ? `{${last}}, ${first}` : `{${last}}`;
|
|
525
623
|
}
|
|
526
624
|
// ---------------------------------------------------------------------------
|
|
527
625
|
// APA 7th Edition
|
|
528
626
|
// ---------------------------------------------------------------------------
|
|
529
627
|
/**
|
|
530
628
|
* Format an editor in APA's "In E. E. Editor (Ed.)," position: initials first,
|
|
531
|
-
* surname last — the inverse of the author position
|
|
629
|
+
* surname last — the inverse of the author position — and a suffix after the
|
|
630
|
+
* surname (`M. P. Adam Jr`).
|
|
532
631
|
*/
|
|
533
632
|
function formatEditorApa(editor) {
|
|
534
633
|
if (editor.collectiveName)
|
|
@@ -544,7 +643,7 @@ function formatEditorApa(editor) {
|
|
|
544
643
|
.map((c) => `${c}.`)
|
|
545
644
|
.join(' ')
|
|
546
645
|
: '';
|
|
547
|
-
return [initials, editor.lastName].filter(Boolean).join(' ');
|
|
646
|
+
return [initials, editor.lastName].filter(Boolean).join(' ') + trailingSuffix(editor);
|
|
548
647
|
}
|
|
549
648
|
/** APA editor list: comma-separated, `& ` before the last name. */
|
|
550
649
|
function formatEditorsApa(editors) {
|
|
@@ -609,7 +708,7 @@ function formatApaBook(article) {
|
|
|
609
708
|
const url = bookAccessUrl(article);
|
|
610
709
|
if (url)
|
|
611
710
|
parts.push(url);
|
|
612
|
-
return parts.join(' ');
|
|
711
|
+
return collapseWhitespace(parts.join(' '));
|
|
613
712
|
}
|
|
614
713
|
/**
|
|
615
714
|
* Format a PubMed article as an APA 7th edition citation.
|
|
@@ -654,7 +753,8 @@ export function formatApa(article) {
|
|
|
654
753
|
// Journal, volume, issue, pages
|
|
655
754
|
const journal = article.journalInfo;
|
|
656
755
|
if (journal?.title) {
|
|
657
|
-
|
|
756
|
+
const name = citedJournalName(journal.title, journal.isoAbbreviation);
|
|
757
|
+
let journalPart = `*${titleCaseJournal(name, APA_MINOR_WORDS)}*`;
|
|
658
758
|
if (journal.volume) {
|
|
659
759
|
journalPart += `, *${journal.volume}*`;
|
|
660
760
|
if (journal.issue) {
|
|
@@ -667,7 +767,7 @@ export function formatApa(article) {
|
|
|
667
767
|
journalPart += `, Article ${number}`;
|
|
668
768
|
}
|
|
669
769
|
else if (journal.pages) {
|
|
670
|
-
journalPart += `, ${
|
|
770
|
+
journalPart += `, ${expandPageRanges(journal.pages, '\u{2013}')}`;
|
|
671
771
|
}
|
|
672
772
|
journalPart += '.';
|
|
673
773
|
parts.push(journalPart);
|
|
@@ -685,18 +785,14 @@ export function formatApa(article) {
|
|
|
685
785
|
if (retractions.length > 0) {
|
|
686
786
|
parts.push(`(Retraction published ${retractions.join('; ')})`);
|
|
687
787
|
}
|
|
688
|
-
return parts.join(' ');
|
|
788
|
+
return collapseWhitespace(parts.join(' '));
|
|
689
789
|
}
|
|
690
790
|
// ---------------------------------------------------------------------------
|
|
691
791
|
// MLA 9th Edition
|
|
692
792
|
// ---------------------------------------------------------------------------
|
|
693
|
-
/** MLA renders editors first-name-first after `edited by
|
|
793
|
+
/** MLA renders editors first-name-first after `edited by`, as it does a non-first author. */
|
|
694
794
|
function formatEditorsMla(editors) {
|
|
695
|
-
const names = editors
|
|
696
|
-
.map((editor) => editor.collectiveName
|
|
697
|
-
? editor.collectiveName
|
|
698
|
-
: [editor.firstName, editor.lastName].filter(Boolean).join(' '))
|
|
699
|
-
.filter(Boolean);
|
|
795
|
+
const names = editors.map((editor) => formatAuthorMla(editor, false)).filter(Boolean);
|
|
700
796
|
if (names.length === 0)
|
|
701
797
|
return '';
|
|
702
798
|
if (names.length === 1)
|
|
@@ -740,7 +836,7 @@ function formatMlaBook(article) {
|
|
|
740
836
|
detailParts.push(date);
|
|
741
837
|
if (detailParts.length)
|
|
742
838
|
parts.push(terminate(detailParts.join(', ')));
|
|
743
|
-
return parts.join(' ');
|
|
839
|
+
return collapseWhitespace(parts.join(' '));
|
|
744
840
|
}
|
|
745
841
|
/**
|
|
746
842
|
* Format a PubMed article as an MLA 9th edition citation.
|
|
@@ -770,7 +866,8 @@ export function formatMla(article) {
|
|
|
770
866
|
const journal = article.journalInfo;
|
|
771
867
|
if (journal?.title) {
|
|
772
868
|
const detailParts = [];
|
|
773
|
-
|
|
869
|
+
const name = citedJournalName(journal.title, journal.isoAbbreviation);
|
|
870
|
+
detailParts.push(`*${titleCaseJournal(name, MLA_MINOR_WORDS)}*`);
|
|
774
871
|
if (journal.volume) {
|
|
775
872
|
detailParts.push(`vol. ${journal.volume}`);
|
|
776
873
|
}
|
|
@@ -798,7 +895,7 @@ export function formatMla(article) {
|
|
|
798
895
|
if (article.doi) {
|
|
799
896
|
parts.push(`https://doi.org/${article.doi}.`);
|
|
800
897
|
}
|
|
801
|
-
return parts.join(' ');
|
|
898
|
+
return collapseWhitespace(parts.join(' '));
|
|
802
899
|
}
|
|
803
900
|
// ---------------------------------------------------------------------------
|
|
804
901
|
// BibTeX
|
|
@@ -875,11 +972,12 @@ export function formatBibtex(article) {
|
|
|
875
972
|
if (journal?.issue) {
|
|
876
973
|
fields.push(['number', escapeBibtex(journal.issue)]);
|
|
877
974
|
}
|
|
878
|
-
// Pages — or, with no pagination,
|
|
879
|
-
//
|
|
880
|
-
// imprecise; `eid` is broadly
|
|
975
|
+
// Pages, each range written in full (#228) — or, with no pagination,
|
|
976
|
+
// biblatex's `eid` for the article number. Classic BibTeX has no
|
|
977
|
+
// article-number field and overloading `pages` is imprecise; `eid` is broadly
|
|
978
|
+
// supported (acmart included).
|
|
881
979
|
if (journal?.pages) {
|
|
882
|
-
fields.push(['pages', escapeBibtex(journal.pages)]);
|
|
980
|
+
fields.push(['pages', escapeBibtex(expandPageRanges(journal.pages))]);
|
|
883
981
|
}
|
|
884
982
|
else if (!book) {
|
|
885
983
|
const locator = articleLocator(journal);
|
|
@@ -920,14 +1018,30 @@ export function formatBibtex(article) {
|
|
|
920
1018
|
if (keywords.length > 0) {
|
|
921
1019
|
fields.push(['keywords', keywords.map((k) => `{${escapeBibtex(k)}}`).join(', ')]);
|
|
922
1020
|
}
|
|
923
|
-
// Build entry
|
|
1021
|
+
// Build entry — each value on its own line, whitespace runs collapsed (#233)
|
|
924
1022
|
const maxKeyLen = Math.max(...fields.map(([k]) => k.length));
|
|
925
|
-
const fieldLines = fields
|
|
1023
|
+
const fieldLines = fields
|
|
1024
|
+
.map(([k, v]) => ` ${k.padEnd(maxKeyLen)} = {${collapseWhitespace(v)}}`)
|
|
1025
|
+
.join(',\n');
|
|
926
1026
|
return `@${entryType}{${key},\n${fieldLines}\n}`;
|
|
927
1027
|
}
|
|
928
1028
|
// ---------------------------------------------------------------------------
|
|
929
1029
|
// RIS
|
|
930
1030
|
// ---------------------------------------------------------------------------
|
|
1031
|
+
/**
|
|
1032
|
+
* An `AU` or `A2` value: `Lastname, Firstname`, or `Lastname, Firstname, Suffix`.
|
|
1033
|
+
* Positional, so a suffixed name with no first name keeps the empty slot
|
|
1034
|
+
* (`Frenck, , Jr`) rather than reading the suffix as a first name.
|
|
1035
|
+
*/
|
|
1036
|
+
function formatNameRis(person) {
|
|
1037
|
+
if (person.collectiveName)
|
|
1038
|
+
return person.collectiveName;
|
|
1039
|
+
const last = person.lastName ?? '';
|
|
1040
|
+
const first = person.firstName ?? '';
|
|
1041
|
+
if (last && person.suffix)
|
|
1042
|
+
return `${last}, ${first}, ${person.suffix}`;
|
|
1043
|
+
return first ? `${last}, ${first}` : last;
|
|
1044
|
+
}
|
|
931
1045
|
/**
|
|
932
1046
|
* Format a PubMed article as a RIS record.
|
|
933
1047
|
*
|
|
@@ -936,9 +1050,12 @@ export function formatBibtex(article) {
|
|
|
936
1050
|
*/
|
|
937
1051
|
export function formatRis(article) {
|
|
938
1052
|
const lines = [];
|
|
1053
|
+
// Every value on its own tag line: a line break inside one would leave an
|
|
1054
|
+
// untagged line, or a blank one that ends the record for strict parsers (#233)
|
|
939
1055
|
const tag = (code, value) => {
|
|
940
|
-
|
|
941
|
-
|
|
1056
|
+
const text = value && collapseWhitespace(value);
|
|
1057
|
+
if (text)
|
|
1058
|
+
lines.push(`${code} - ${text}`);
|
|
942
1059
|
};
|
|
943
1060
|
const book = isBookRecord(article) ? article.book : undefined;
|
|
944
1061
|
// Type of reference — the record type for a book, else the publication types
|
|
@@ -949,20 +1066,8 @@ export function formatRis(article) {
|
|
|
949
1066
|
: firstMappedType(article.publicationTypes, RIS_REFERENCE_TYPE, 'JOUR');
|
|
950
1067
|
lines.push(`TY - ${refType}`);
|
|
951
1068
|
// Authors — one AU tag per author
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
if (author.collectiveName) {
|
|
955
|
-
tag('AU', author.collectiveName);
|
|
956
|
-
}
|
|
957
|
-
else {
|
|
958
|
-
const last = author.lastName ?? '';
|
|
959
|
-
const first = author.firstName ?? '';
|
|
960
|
-
if (last || first) {
|
|
961
|
-
tag('AU', first ? `${last}, ${first}` : last);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
1069
|
+
for (const author of article.authors ?? [])
|
|
1070
|
+
tag('AU', formatNameRis(author));
|
|
966
1071
|
// Title
|
|
967
1072
|
tag('TI', article.title);
|
|
968
1073
|
// Container — the journal, or the book a chapter sits in. `BT` is the book
|
|
@@ -977,14 +1082,8 @@ export function formatRis(article) {
|
|
|
977
1082
|
if (book) {
|
|
978
1083
|
if (article.recordType === 'book-chapter')
|
|
979
1084
|
tag('BT', book.title);
|
|
980
|
-
for (const editor of book.editors ?? [])
|
|
981
|
-
|
|
982
|
-
const first = editor.firstName ?? '';
|
|
983
|
-
if (editor.collectiveName)
|
|
984
|
-
tag('A2', editor.collectiveName);
|
|
985
|
-
else if (last || first)
|
|
986
|
-
tag('A2', first ? `${last}, ${first}` : last);
|
|
987
|
-
}
|
|
1085
|
+
for (const editor of book.editors ?? [])
|
|
1086
|
+
tag('A2', formatNameRis(editor));
|
|
988
1087
|
tag('PB', book.publisher);
|
|
989
1088
|
tag('CY', book.publisherLocation);
|
|
990
1089
|
tag('ET', book.edition);
|
|
@@ -1001,7 +1100,7 @@ export function formatRis(article) {
|
|
|
1001
1100
|
// Volume & Issue
|
|
1002
1101
|
tag('VL', journal?.volume);
|
|
1003
1102
|
tag('IS', journal?.issue);
|
|
1004
|
-
// Pages —
|
|
1103
|
+
// Pages — the first page and the last, written in full ({@link splitPages}).
|
|
1005
1104
|
// With no pagination, the article number goes on `C7` (the attested RIS
|
|
1006
1105
|
// convention for Article Number), never on SP/EP, which hold absolute pages.
|
|
1007
1106
|
if (journal?.pages) {
|
|
@@ -1037,11 +1136,8 @@ export function formatRis(article) {
|
|
|
1037
1136
|
for (const kw of mergedKeywords(article)) {
|
|
1038
1137
|
tag('KW', kw);
|
|
1039
1138
|
}
|
|
1040
|
-
// Abstract —
|
|
1041
|
-
|
|
1042
|
-
if (article.abstractText) {
|
|
1043
|
-
tag('AB', collapseWhitespace(article.abstractText));
|
|
1044
|
-
}
|
|
1139
|
+
// Abstract — a structured abstract's paragraphs flatten onto the one line
|
|
1140
|
+
tag('AB', article.abstractText);
|
|
1045
1141
|
// End of record (trailing space per RIS spec)
|
|
1046
1142
|
lines.push('ER - ');
|
|
1047
1143
|
return lines.join('\n');
|
|
@@ -1051,8 +1147,8 @@ export function formatRis(article) {
|
|
|
1051
1147
|
// ---------------------------------------------------------------------------
|
|
1052
1148
|
/**
|
|
1053
1149
|
* Format a single author in Vancouver style: `Surname AB` — surname, a space,
|
|
1054
|
-
* then initials with no periods or internal spaces
|
|
1055
|
-
* return their name directly.
|
|
1150
|
+
* then initials with no periods or internal spaces, then any suffix after a
|
|
1151
|
+
* space (`Frenck RW Jr`). Collective/group authors return their name directly.
|
|
1056
1152
|
*/
|
|
1057
1153
|
function formatAuthorVancouver(author) {
|
|
1058
1154
|
if (author.collectiveName)
|
|
@@ -1073,9 +1169,7 @@ function formatAuthorVancouver(author) {
|
|
|
1073
1169
|
: '';
|
|
1074
1170
|
if (!last)
|
|
1075
1171
|
return initials;
|
|
1076
|
-
|
|
1077
|
-
return last;
|
|
1078
|
-
return `${last} ${initials}`;
|
|
1172
|
+
return `${initials ? `${last} ${initials}` : last}${trailingSuffix(author)}`;
|
|
1079
1173
|
}
|
|
1080
1174
|
/**
|
|
1081
1175
|
* Format the full author list for Vancouver: list every author for six or
|
|
@@ -1136,7 +1230,7 @@ function formatVancouverBook(article) {
|
|
|
1136
1230
|
const url = bookshelfUrl(book);
|
|
1137
1231
|
if (url)
|
|
1138
1232
|
segments.push(`Available from: ${url}`);
|
|
1139
|
-
return segments.join(' ');
|
|
1233
|
+
return collapseWhitespace(segments.join(' '));
|
|
1140
1234
|
}
|
|
1141
1235
|
/**
|
|
1142
1236
|
* The notices Vancouver notes after the reference, each with Citing Medicine's
|
|
@@ -1217,7 +1311,7 @@ export function formatVancouver(article) {
|
|
|
1217
1311
|
segments.push(notes.length > 0 ? `doi: ${article.doi}.` : `doi: ${article.doi}`);
|
|
1218
1312
|
}
|
|
1219
1313
|
segments.push(...notes);
|
|
1220
|
-
return segments.join(' ');
|
|
1314
|
+
return collapseWhitespace(segments.join(' '));
|
|
1221
1315
|
}
|
|
1222
1316
|
// ---------------------------------------------------------------------------
|
|
1223
1317
|
// Dispatchers
|