@retorquere/bibtex-parser 9.0.22 → 9.0.25

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.
Files changed (66) hide show
  1. package/dist/cjs/crossref.js +209 -0
  2. package/dist/cjs/crossref.js.map +7 -0
  3. package/dist/cjs/fields.js +6961 -0
  4. package/dist/cjs/fields.js.map +7 -0
  5. package/dist/cjs/index.js +1072 -0
  6. package/dist/cjs/index.js.map +7 -0
  7. package/dist/cjs/jabref.js +111 -0
  8. package/dist/cjs/jabref.js.map +7 -0
  9. package/dist/cjs/merge.js +37 -0
  10. package/dist/cjs/merge.js.map +7 -0
  11. package/dist/cjs/prepositions.js +111 -0
  12. package/dist/cjs/prepositions.js.map +7 -0
  13. package/dist/cjs/sentence-case.js +98 -0
  14. package/dist/cjs/sentence-case.js.map +7 -0
  15. package/dist/cjs/tokenizer.js +194 -0
  16. package/dist/cjs/tokenizer.js.map +7 -0
  17. package/dist/cjs/verbatim.js +403 -0
  18. package/dist/cjs/verbatim.js.map +7 -0
  19. package/dist/cjs/yield.js +26 -0
  20. package/dist/cjs/yield.js.map +7 -0
  21. package/dist/data/crossref.json +186 -0
  22. package/dist/data/fields.json +6938 -0
  23. package/dist/data/prepositions.json +88 -0
  24. package/dist/esm/crossref.js +190 -0
  25. package/dist/esm/crossref.js.map +7 -0
  26. package/dist/esm/fields.js +6942 -0
  27. package/dist/esm/fields.js.map +7 -0
  28. package/dist/esm/index.js +1044 -0
  29. package/dist/esm/index.js.map +7 -0
  30. package/dist/esm/jabref.js +92 -0
  31. package/dist/esm/jabref.js.map +7 -0
  32. package/dist/esm/merge.js +8 -0
  33. package/dist/esm/merge.js.map +7 -0
  34. package/dist/esm/prepositions.js +92 -0
  35. package/dist/esm/prepositions.js.map +7 -0
  36. package/dist/esm/sentence-case.js +79 -0
  37. package/dist/esm/sentence-case.js.map +7 -0
  38. package/dist/esm/tokenizer.js +170 -0
  39. package/dist/esm/tokenizer.js.map +7 -0
  40. package/dist/esm/verbatim.js +387 -0
  41. package/dist/esm/verbatim.js.map +7 -0
  42. package/dist/esm/yield.js +7 -0
  43. package/dist/esm/yield.js.map +7 -0
  44. package/dist/types/crossref.d.ts +188 -0
  45. package/dist/types/crossref.d.ts.map +1 -0
  46. package/dist/types/fields.d.ts +55 -0
  47. package/dist/types/fields.d.ts.map +1 -0
  48. package/dist/types/index.d.ts +194 -0
  49. package/dist/types/index.d.ts.map +1 -0
  50. package/dist/types/jabref.d.ts +58 -0
  51. package/dist/types/jabref.d.ts.map +1 -0
  52. package/dist/types/merge.d.ts +2 -0
  53. package/dist/types/merge.d.ts.map +1 -0
  54. package/dist/types/prepositions.d.ts +3 -0
  55. package/dist/types/prepositions.d.ts.map +1 -0
  56. package/dist/types/sentence-case.d.ts +9 -0
  57. package/dist/types/sentence-case.d.ts.map +1 -0
  58. package/dist/types/tokenizer.d.ts +13 -0
  59. package/dist/types/tokenizer.d.ts.map +1 -0
  60. package/dist/types/verbatim.d.ts +66 -0
  61. package/dist/types/verbatim.d.ts.map +1 -0
  62. package/dist/types/yield.d.ts +2 -0
  63. package/dist/types/yield.d.ts.map +1 -0
  64. package/package.json +4 -31
  65. /package/{strings.bib → dist/data/strings.bib} +0 -0
  66. /package/{unabbrev.json → dist/data/unabbrev.json} +0 -0
@@ -0,0 +1,1044 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { LatexPegParser } from "@unified-latex/unified-latex-util-pegjs";
5
+ import { printRaw } from "@unified-latex/unified-latex-util-print-raw";
6
+ import { replaceNode } from "@unified-latex/unified-latex-util-replace";
7
+ import { visit } from "@unified-latex/unified-latex-util-visit";
8
+ import { combining, latex2unicode as latex2unicodemap } from "unicode2latex";
9
+ import * as JabRef from "./jabref.js";
10
+ import * as bibtex from "./verbatim.js";
11
+ import { toSentenceCase } from "./sentence-case.js";
12
+ import { toSentenceCase as toSentenceCase2 } from "./sentence-case.js";
13
+ import { tokenize } from "./tokenizer.js";
14
+ import { tokenize as tokenize2 } from "./tokenizer.js";
15
+ import { playnice } from "./yield.js";
16
+ import CrossRef from "./crossref.js";
17
+ import allowed from "./fields.js";
18
+ import { merge } from "./merge.js";
19
+ function latexMode(node) {
20
+ return node._renderInfo.mode;
21
+ }
22
+ function latex2unicode(tex, node) {
23
+ const text = latex2unicodemap[tex];
24
+ if (typeof text === "string") return text;
25
+ return text && text[latexMode(node)];
26
+ }
27
+ const open = {};
28
+ const close = {};
29
+ for (const tag of ["i", "b", "sc", "nc", "ncx", "br", "p", "li", "code"]) {
30
+ open[tag] = `${tag}`;
31
+ close[tag] = `/${tag}`;
32
+ }
33
+ const collapsable = /\x0E\/([a-z]+)\x0F(\s*)\x0E\1\x0F/ig;
34
+ const Month = {
35
+ jan: "1",
36
+ january: "1",
37
+ feb: "2",
38
+ february: "2",
39
+ mar: "3",
40
+ march: "3",
41
+ apr: "4",
42
+ april: "4",
43
+ may: "5",
44
+ jun: "6",
45
+ june: "6",
46
+ jul: "7",
47
+ july: "7",
48
+ aug: "8",
49
+ august: "8",
50
+ sep: "9",
51
+ september: "9",
52
+ oct: "10",
53
+ october: "10",
54
+ nov: "11",
55
+ november: "11",
56
+ dec: "12",
57
+ december: "12"
58
+ };
59
+ const FieldMode = {
60
+ creatorlist: [
61
+ "author",
62
+ "bookauthor",
63
+ "collaborator",
64
+ "commentator",
65
+ "director",
66
+ "editor",
67
+ "editora",
68
+ "editorb",
69
+ "editors",
70
+ "holder",
71
+ "scriptwriter",
72
+ "translator"
73
+ ],
74
+ title: [
75
+ "title",
76
+ "subtitle",
77
+ "series",
78
+ "shorttitle",
79
+ "booktitle",
80
+ // 'type',
81
+ "origtitle",
82
+ "maintitle",
83
+ "eventtitle"
84
+ ],
85
+ verbatim: [
86
+ "doi",
87
+ "eprint",
88
+ "file",
89
+ "files",
90
+ "pdf",
91
+ "groups",
92
+ // jabref unilaterally decided to make this non-standard field verbatim
93
+ "ids",
94
+ "url",
95
+ "verba",
96
+ "verbb",
97
+ "verbc",
98
+ /^keywords([+]duplicate-\d+)?$/,
99
+ /^citeulike-linkout-[0-9]+$/,
100
+ /^bdsk-url-[0-9]+$/
101
+ ],
102
+ richtext: [
103
+ "annotation",
104
+ "comment",
105
+ "annote",
106
+ "review",
107
+ "notes",
108
+ "note"
109
+ ],
110
+ literallist: [
111
+ "institution",
112
+ "publisher",
113
+ "origpublisher",
114
+ "organization",
115
+ "location",
116
+ "origlocation"
117
+ ]
118
+ };
119
+ const English = [
120
+ "",
121
+ "american",
122
+ "british",
123
+ "canadian",
124
+ "english",
125
+ "australian",
126
+ "newzealand",
127
+ "usenglish",
128
+ "ukenglish",
129
+ "en",
130
+ "eng",
131
+ "en-au",
132
+ "en-bz",
133
+ "en-ca",
134
+ "en-cb",
135
+ "en-gb",
136
+ "en-ie",
137
+ "en-jm",
138
+ "en-nz",
139
+ "en-ph",
140
+ "en-tt",
141
+ "en-us",
142
+ "en-za",
143
+ "en-zw",
144
+ "anglais"
145
+ // don't do this people
146
+ ];
147
+ const FieldAction = {
148
+ removeOuterBraces: [
149
+ "doi"
150
+ // 'publisher',
151
+ // 'location',
152
+ ],
153
+ parseInt: [
154
+ "year",
155
+ "month"
156
+ ],
157
+ noCrossRef: [
158
+ "file"
159
+ ]
160
+ };
161
+ const narguments = {
162
+ advance: 1,
163
+ ElsevierGlyph: 1,
164
+ bar: 1,
165
+ bibcyr: 1,
166
+ bibstring: 1,
167
+ chsf: 1,
168
+ cite: 1,
169
+ citeauthor: 1,
170
+ cyrchar: 1,
171
+ ding: 1,
172
+ emph: 1,
173
+ enquote: 1,
174
+ frac: 2,
175
+ hbox: 1,
176
+ href: 2,
177
+ hskip: 1,
178
+ hspace: 1,
179
+ ht: 1,
180
+ mathrm: 1,
181
+ mbox: 1,
182
+ mkbibbold: 1,
183
+ mkbibemph: 1,
184
+ mkbibitalic: 1,
185
+ mkbibquote: 1,
186
+ newcommand: 2,
187
+ noopsort: 1,
188
+ ocirc: 1,
189
+ overline: 1,
190
+ ProvideTextCommandDefault: 2,
191
+ rlap: 1,
192
+ sb: 1,
193
+ section: 1,
194
+ sp: 1,
195
+ subsection: 1,
196
+ subsubsection: 1,
197
+ subsubsubsection: 1,
198
+ t: 1,
199
+ textbf: 1,
200
+ textcite: 1,
201
+ textit: 1,
202
+ textrm: 1,
203
+ textsc: 1,
204
+ textsl: 1,
205
+ textsubscript: 1,
206
+ textsuperscript: 1,
207
+ texttt: 1,
208
+ textup: 1,
209
+ url: 1,
210
+ vphantom: 1,
211
+ vspace: 1,
212
+ wd: 1,
213
+ // math
214
+ "math _": 1,
215
+ "math ^": 1
216
+ };
217
+ for (const m in combining.tounicode) {
218
+ narguments[m] = 1;
219
+ }
220
+ class BibTeXParser {
221
+ constructor() {
222
+ __publicField(this, "fallback");
223
+ __publicField(this, "current");
224
+ __publicField(this, "english");
225
+ __publicField(this, "options");
226
+ __publicField(this, "fieldMode");
227
+ __publicField(this, "newcommands", {});
228
+ __publicField(this, "bib");
229
+ __publicField(this, "unhandled", /* @__PURE__ */ new Set());
230
+ }
231
+ split(ast, sep, split) {
232
+ const roots = [];
233
+ const nodes = [...ast.content];
234
+ const types = nodes.map((node) => {
235
+ if (node.type === "whitespace") return " ";
236
+ if (node.type === "string" && node.content.match(sep)) return "&";
237
+ return ".";
238
+ }).join("");
239
+ types.split(split).forEach((match, i) => {
240
+ const content = match.length ? nodes.splice(0, match.length) : [];
241
+ if (i % 2 === 0) roots.push({ type: "root", content });
242
+ });
243
+ return roots;
244
+ }
245
+ trimCreator(cr) {
246
+ return JSON.parse(JSON.stringify(cr, (k, v) => typeof v === "string" ? v.trim() || void 0 : v));
247
+ }
248
+ parseCreator(ast) {
249
+ if (ast.content.length === 1 && ast.content[0].type === "group") return this.trimCreator({ name: this.stringify(ast, { mode: "creatorlist" }) });
250
+ const { parts, extended } = this.commaSeparatedCreator(ast);
251
+ if (extended) {
252
+ const name = {};
253
+ for (const [attr, value] of Object.entries(extended)) {
254
+ const rendered = this.stringify(value, { mode: "creatorlist" }).trim();
255
+ switch (attr) {
256
+ case "":
257
+ break;
258
+ case "given":
259
+ name.firstName = rendered;
260
+ break;
261
+ case "family":
262
+ name.lastName = rendered;
263
+ break;
264
+ case "given-i":
265
+ name.initial = rendered;
266
+ break;
267
+ case "useprefix":
268
+ case "juniorcomma":
269
+ name[attr] = rendered.toLowerCase() === "true";
270
+ break;
271
+ default:
272
+ name[attr] = rendered;
273
+ break;
274
+ }
275
+ }
276
+ return this.trimCreator(name);
277
+ } else if (parts.length) {
278
+ const nameparts = parts.map((part) => this.stringify(part, { mode: "creatorlist" }).trim());
279
+ if (nameparts.length === 3 && nameparts[2] === "") nameparts.pop();
280
+ if (nameparts.length > 3) {
281
+ const key = this.current.key ? `@${this.current.key}: ` : "";
282
+ this.bib.errors.push({
283
+ error: `${key}unexpected ${nameparts.length}-part name "${nameparts.join(", ")}", dropping "${nameparts.slice(3).join(", ")}"`,
284
+ input: nameparts.join(", ")
285
+ });
286
+ }
287
+ let [lastName, suffix, firstName] = nameparts.length === 2 ? [nameparts[0], void 0, nameparts[1]] : nameparts.slice(0, 3);
288
+ let prefix;
289
+ const m = lastName.match(/^([a-z'. ]+) (.+)/);
290
+ if (m) {
291
+ prefix = m[1];
292
+ lastName = m[2];
293
+ }
294
+ return this.trimCreator({
295
+ lastName,
296
+ firstName,
297
+ prefix,
298
+ suffix
299
+ });
300
+ } else {
301
+ const nameparts = this.split(ast, /^$/, /( )/).map((part) => this.stringify(part, { mode: "creatorlist" })).filter((n) => n);
302
+ if (nameparts.length === 1) return this.trimCreator({ lastName: nameparts[0] });
303
+ const prefix = nameparts.findIndex((n) => n.match(/^[a-z]/));
304
+ const postfix = prefix <= 0 ? -1 : nameparts.findIndex((n, i) => i > prefix && !n.match(/^[a-z]/));
305
+ if (postfix === -1) {
306
+ const lastName = nameparts.pop();
307
+ return this.trimCreator({ lastName, firstName: nameparts.join(" ") });
308
+ }
309
+ return this.trimCreator({
310
+ firstName: nameparts.slice(0, prefix).join(" "),
311
+ prefix: nameparts.slice(prefix, postfix).join(" "),
312
+ lastName: nameparts.slice(postfix).join(" ")
313
+ });
314
+ }
315
+ }
316
+ ligature(nodes) {
317
+ if (latexMode(nodes[0]) !== "text") return null;
318
+ const max = 3;
319
+ const slice = nodes.slice(0, max);
320
+ const type = slice.map((n) => n.type === "string" ? "s" : " ").join("");
321
+ if (type[0] !== "s") return null;
322
+ const content = slice.map((n) => n.type === "string" ? n.content : "");
323
+ let latex;
324
+ while (content.length) {
325
+ if (type.startsWith("s".repeat(content.length)) && (latex = latex2unicode(content.join(""), slice[0]))) {
326
+ try {
327
+ return { type: "string", content: latex, _renderInfo: {} };
328
+ } finally {
329
+ nodes.splice(0, content.length);
330
+ }
331
+ }
332
+ content.pop();
333
+ }
334
+ return null;
335
+ }
336
+ wraparg(node, macro) {
337
+ if (macro.content.match(/^(itshape|textsl|textit|emph|mkbibemph)$/)) node._renderInfo.emph = true;
338
+ if (macro.content.match(/^(textbf|mkbibbold|bfseries)$/)) node._renderInfo.bold = true;
339
+ if (macro.content.match(/^(textsc)$/)) node._renderInfo.smallCaps = true;
340
+ if (macro.content.match(/^(texttt)$/)) node._renderInfo.code = true;
341
+ return { type: "argument", content: [node], openMark: "", closeMark: "", _renderInfo: { mode: node._renderInfo.mode } };
342
+ }
343
+ argtogroup(node) {
344
+ if (node.content.length === 1 && node.content[0].type === "group") return node.content[0];
345
+ return { type: "group", content: node.content };
346
+ }
347
+ argument(nodes, macro) {
348
+ if (!nodes.length) return null;
349
+ if (nodes[0].type === "whitespace") nodes.shift();
350
+ if (!nodes.length) return null;
351
+ if (nodes[0].type === "string") {
352
+ const char = nodes[0].content[0];
353
+ nodes[0].content = nodes[0].content.substr(1);
354
+ const arg = { ...nodes[0], content: char };
355
+ if (!nodes[0].content) nodes.shift();
356
+ return this.wraparg(arg, macro);
357
+ }
358
+ return this.wraparg(nodes.shift(), macro);
359
+ }
360
+ unsupported(node) {
361
+ const tex = printRaw(node);
362
+ if (this.fallback) return this.fallback(node, tex, this.current) ?? "";
363
+ let id;
364
+ switch (node.type) {
365
+ case "macro":
366
+ id = `${node.type}.${node.content}`;
367
+ if (!this.unhandled.has(id)) {
368
+ this.unhandled.add(id);
369
+ this.bib.errors.push({ error: `unhandled ${node.type} ${printRaw(node)}`, input: printRaw(node) });
370
+ }
371
+ break;
372
+ case "environment":
373
+ id = `${node.type}.${node.env}`;
374
+ if (!this.unhandled.has(id)) {
375
+ this.unhandled.add(id);
376
+ this.bib.errors.push({ error: `unhandled ${node.type} ${node.env} (${printRaw(node)})`, input: printRaw(node) });
377
+ }
378
+ break;
379
+ default:
380
+ this.bib.errors.push({ error: `unhandled ${node.type} (${printRaw(node)})`, input: printRaw(node) });
381
+ break;
382
+ }
383
+ return tex;
384
+ }
385
+ wrap(text, tag, wrap = true) {
386
+ if (!text || !wrap) return text || "";
387
+ return `${tag}${text}/${tag}`;
388
+ }
389
+ registercommand(node) {
390
+ const types = (nodes) => nodes.map((n) => n.type).join(".");
391
+ const group = (arg, kind) => {
392
+ if (!arg) throw new Error(`missing ${kind} for ${printRaw(node)} @ ${JSON.stringify(node.position)}`);
393
+ if (types(arg.content) !== "group") throw new Error(`Malformed ${kind} for ${printRaw(node)} @ ${JSON.stringify(node.position)}`);
394
+ return arg.content[0].content;
395
+ };
396
+ if (!node.args) throw new Error(`No arguments for ${printRaw(node)} @ ${JSON.stringify(node.position)}`);
397
+ const namearg = group(node.args[0], "name");
398
+ if (types(namearg) !== "macro") throw new Error(`Unexpected name for ${printRaw(node)} @ ${JSON.stringify(node.position)}`);
399
+ this.newcommands[namearg[0].content] = node.args[1];
400
+ return "";
401
+ }
402
+ subp(text, mode) {
403
+ const macro = { sub: "_", sup: "^" }[mode];
404
+ let subp = "";
405
+ for (let char of text) {
406
+ const mapped = latex2unicodemap[`${macro}{${char}}`];
407
+ switch (typeof mapped) {
408
+ case "string":
409
+ subp += mapped;
410
+ break;
411
+ case "undefined":
412
+ return `${mode}${text}/${mode}`;
413
+ default:
414
+ subp += mapped.math;
415
+ }
416
+ }
417
+ return subp;
418
+ }
419
+ macro(node, context) {
420
+ const text = latex2unicode(printRaw(node), node);
421
+ if (text) return text;
422
+ let url;
423
+ let label;
424
+ let arg;
425
+ switch (node.content) {
426
+ case "newcommand":
427
+ case "ProvideTextCommandDefault":
428
+ return this.registercommand(node);
429
+ // too complex to deal with these
430
+ case "raise":
431
+ case "accent":
432
+ case "def":
433
+ case "hss":
434
+ case "ifmmode":
435
+ case "makeatletter":
436
+ case "makeatother":
437
+ case "scriptscriptstyle":
438
+ case "setbox":
439
+ case "dimen":
440
+ case "advance":
441
+ return "";
442
+ case "vphantom":
443
+ case "noopsort":
444
+ case "left":
445
+ case "right":
446
+ case "ensuremath":
447
+ case "wd":
448
+ case "ht":
449
+ return "";
450
+ case "path":
451
+ return "";
452
+ // until https://github.com/siefkenj/unified-latex/issues/94 is fixed
453
+ case "hspace":
454
+ case "hskip":
455
+ if (node.args && node.args.length) {
456
+ if (printRaw(node.args).match(/^[{]?0[a-z]*$/)) return "";
457
+ return " ";
458
+ }
459
+ return "";
460
+ case "overline":
461
+ case "bar":
462
+ return node.args.map((a) => this.stringify(a, context)).join("").replace(/[a-z0-9]/ig, (m) => `${m}\u0305`);
463
+ // accents dealt with by preprocessor
464
+ case "textup":
465
+ case "textsc":
466
+ case "textrm":
467
+ case "texttt":
468
+ case "mathrm":
469
+ case "mbox":
470
+ case "hbox":
471
+ case "rlap":
472
+ return node.args.map((n) => this.stringify(n, context)).join("");
473
+ case "href":
474
+ case "url":
475
+ if (node.args) {
476
+ url = node.args[0];
477
+ label = node.args[node.content === "url" ? 0 : 1];
478
+ }
479
+ return `<a href="${this.stringify(url, context)}">${this.stringify(label, context)}</a>`;
480
+ case "relax":
481
+ case "aftergroup":
482
+ case "ignorespaces":
483
+ case "em":
484
+ case "it":
485
+ case "tt":
486
+ case "sl":
487
+ return "";
488
+ case "rm":
489
+ case "sc":
490
+ return "";
491
+ // bold/emph/smallcaps is handled in the wrapper
492
+ case "textbf":
493
+ case "mkbibbold":
494
+ case "textit":
495
+ case "emph":
496
+ case "mkbibemph":
497
+ return this.stringify(node.args?.[0], context);
498
+ case "textsuperscript":
499
+ return this.subp(this.stringify(node.args?.[0], context), "sup");
500
+ case "textsubscript":
501
+ return this.subp(this.stringify(node.args?.[0], context), "sub");
502
+ case "_":
503
+ case "^":
504
+ switch (latexMode(node)) {
505
+ case "math":
506
+ return this.subp(this.stringify(node.args?.[0], context), node.content === "^" ? "sup" : "sub");
507
+ default:
508
+ return node.content;
509
+ }
510
+ case "LaTeX":
511
+ return this.wrap(`L${this.subp("A", "sup")}T${this.subp("E", "sub")}X`, "ncx");
512
+ case "enquote":
513
+ case "mkbibquote":
514
+ return this.wrap(this.stringify(node.args?.[0], context), "enquote");
515
+ case "\\":
516
+ return context.mode === "richtext" ? open.br : " ";
517
+ case "par":
518
+ return context.mode === "richtext" ? open.p : " ";
519
+ case "item":
520
+ return context.mode === "richtext" ? open.li : " ";
521
+ case "section":
522
+ case "subsection":
523
+ case "subsubsection":
524
+ case "subsubsubsection":
525
+ return this.wrap(this.stringify(node.args?.[0], context), `h${node.content.split("sub").length}`);
526
+ case "frac":
527
+ arg = node.args.map((a) => this.stringify(a, context));
528
+ if (arg.length === 2) {
529
+ const resolved = latex2unicodemap[`\\frac${arg.map((a) => `{${a}}`).join("")}`];
530
+ if (resolved) return typeof resolved === "string" ? resolved : resolved.math || resolved.text;
531
+ }
532
+ return arg.map((part, i) => this.subp(part, i ? "sub" : "sup")).join("\u2044");
533
+ case "chsf":
534
+ case "bibstring":
535
+ case "cite":
536
+ case "textcite":
537
+ case "citeauthor":
538
+ return this.wrap(this.stringify(node.args?.[0], context), "ncx", context.mode === "title");
539
+ default:
540
+ if (this.newcommands[node.content]) return this.stringify(this.newcommands[node.content], context);
541
+ return this.unsupported(node);
542
+ }
543
+ }
544
+ what(node) {
545
+ if (!node) return "";
546
+ switch (node.type) {
547
+ case "macro":
548
+ return `macro:${node._renderInfo.mode ?? "text"}:${node.content}`;
549
+ case "environment":
550
+ return `env:${node.env}`;
551
+ default:
552
+ return node.type;
553
+ }
554
+ }
555
+ environment(node, context) {
556
+ while (node.content.length && this.what(node.content[0]).match(/^parbreak|whitespace|macro:text:par$/)) node.content.shift();
557
+ while (node.content.length && this.what(node.content[node.content.length - 1]).match(/^parbreak|whitespace|macro:text:par$/)) node.content.pop();
558
+ switch (node.env) {
559
+ case "quotation":
560
+ return this.wrap(node.content.map((n) => this.stringify(n, context)).join(""), "blockquote", context.mode === "richtext");
561
+ case "itemize":
562
+ return this.wrap(node.content.map((n) => this.stringify(n, context)).join(""), "ul", context.mode === "richtext");
563
+ case "em":
564
+ return this.wrap(node.content.map((n) => this.stringify(n, context)).join(""), "i", context.mode === "richtext");
565
+ default:
566
+ return this.unsupported(node);
567
+ }
568
+ }
569
+ stringify(node, context) {
570
+ let content = this.stringifyContent(node, context);
571
+ if (content && node._renderInfo) {
572
+ if (node._renderInfo.emph) content = `${open.i}${content}${close.i}`;
573
+ if (node._renderInfo.bold) content = `${open.b}${content}${close.b}`;
574
+ if (node._renderInfo.smallCaps) content = `${open.sc}${content}${close.sc}`;
575
+ if (node._renderInfo.code) content = `${open.code}${content}${close.code}`;
576
+ if (this.english && node._renderInfo.protectCase) content = `${open.nc}${content}${close.nc}`;
577
+ }
578
+ return content;
579
+ }
580
+ stringifyContent(node, context) {
581
+ if (!node) return "";
582
+ switch (node.type) {
583
+ case "root":
584
+ case "argument":
585
+ case "group":
586
+ case "inlinemath":
587
+ return node.content.map((n) => this.stringify(n, context)).join("");
588
+ case "string":
589
+ case "verbatim":
590
+ return node.content;
591
+ case "macro":
592
+ return this.macro(node, context);
593
+ case "parbreak":
594
+ return context.mode === "richtext" ? open.p : " ";
595
+ case "whitespace":
596
+ return node._renderInfo.mode === "math" ? "" : " ";
597
+ case "comment":
598
+ return "";
599
+ case "environment":
600
+ return this.environment(node, context);
601
+ case "verb":
602
+ return node.content;
603
+ default:
604
+ return this.unsupported(node);
605
+ }
606
+ }
607
+ protect(node) {
608
+ if (node.type === "inlinemath") return true;
609
+ if (node.type !== "group") return false;
610
+ if (!node.content.length) return false;
611
+ return node.content[0].type !== "macro";
612
+ }
613
+ mode(field) {
614
+ if (this.options.verbatimFields && this.options.verbatimFields.find((name) => typeof name === "string" ? name === field : field.match(name))) return "verbatim";
615
+ let mode = "literal";
616
+ for (const [selected, fields] of Object.entries(this.fieldMode)) {
617
+ if (fields.find((match) => typeof match === "string" ? field === match : field.match(match))) mode = selected;
618
+ }
619
+ return mode;
620
+ }
621
+ restoreMarkup(s) {
622
+ if (!s.includes("")) return s;
623
+ const restored = [s.replace(/\x0E\/?ncx\x0F/ig, "")];
624
+ while (restored[0] !== restored[1]) {
625
+ restored.unshift(restored[0].replace(collapsable, "$2"));
626
+ }
627
+ return restored[0].replace(/(\x0Ep\x0F\s*){2,}/ig, "p").replace(/\s*(\x0E\/p\x0F){2,}/ig, "/p").replace(/\x0Eenquote\x0F/ig, "\u201C").replace(/\x0E\/enquote\x0F/ig, "\u201D").replace(/\x0Esc\x0F/ig, '<span style="font-variant:small-caps;">').replace(/\x0E\/sc\x0F/ig, "</span>").replace(/\x0Enc\x0F/ig, '<span class="nocase">').replace(/\x0E\/nc\x0F/ig, "</span>").replace(/\x0E/ig, "<").replace(/\x0F/ig, ">");
628
+ }
629
+ stringField(field, value, mode, guessSC) {
630
+ if (field === "crossref") return value;
631
+ if (FieldAction.parseInt.includes(field) && value.trim().match(/^-?\d+$/)) return `${parseInt(value)}`;
632
+ if (this.english && mode === "title") {
633
+ value = toSentenceCase(value, {
634
+ preserveQuoted: this.options.sentenceCase && this.options.sentenceCase.preserveQuoted,
635
+ subSentenceCapitalization: this.options.sentenceCase && this.options.sentenceCase.subSentence,
636
+ markup: /\x0E\/?([a-z]+)\x0F/ig,
637
+ nocase: /\x0E(ncx?)\x0F.*?\x0E\/\1\x0F/ig,
638
+ guess: guessSC
639
+ });
640
+ let cancel = (_match, stripped) => stripped;
641
+ switch (this.options.caseProtection) {
642
+ case "strict":
643
+ cancel = (match, _stripped) => match;
644
+ break;
645
+ case "as-needed":
646
+ cancel = (match, stripped) => {
647
+ const words = tokenize(stripped, /\x0E\/?([a-z]+)\x0F/ig);
648
+ return words.find((w) => w.shape.match(/^(?!.*X).*x.*$/)) ? match : this.wrap(stripped, "ncx");
649
+ };
650
+ break;
651
+ }
652
+ return value.replace(/\x0Enc\x0F(.*?)\x0E\/nc\x0F/ig, cancel);
653
+ }
654
+ return value;
655
+ }
656
+ commaSeparatedCreator(ast) {
657
+ const result = {
658
+ parts: []
659
+ };
660
+ if (!ast.content.find((node) => node.type === "string" && node.content === ",")) return result;
661
+ result.parts = this.split(ast, /^,$/, /(&)/);
662
+ result.extended = {};
663
+ for (const part of result.parts) {
664
+ const start = part.content[0]?.type === "whitespace" ? 1 : 0;
665
+ let signature = part.content.slice(start, start + 4).map((node, i) => node._renderInfo.mode === "text" && node.type === "string" ? node.content.replace(i % 2 ? /[^=-]/g : /[^a-z]/gi, ".") : ".").join("");
666
+ if (signature.match(/^[a-z]+(-[a-z]+)?=/i)) {
667
+ signature = signature.replace(/=.*/, "").toLowerCase();
668
+ result.extended[signature] = { type: "root", content: part.content.slice(signature.includes("-") ? start + 4 : start + 2) };
669
+ } else {
670
+ delete result.extended;
671
+ break;
672
+ }
673
+ }
674
+ return result;
675
+ }
676
+ field(entry, field, value) {
677
+ const mode = entry.mode[field] = this.mode(field);
678
+ const caseProtection = {
679
+ present: false,
680
+ intuitive: 0
681
+ // a lot of people don't realise `some text \textit{in italics}` will protect 'in italics'
682
+ };
683
+ const ast = LatexPegParser.parse(value);
684
+ if (this.options.removeOuterBraces.includes(field) && ast.content.length === 1 && ast.content[0].type === "group") {
685
+ ast.content = ast.content[0].content;
686
+ }
687
+ if (mode === "verbatim") {
688
+ entry.fields[field] = printRaw(ast);
689
+ return;
690
+ }
691
+ if (this.english && mode === "title") {
692
+ let root = [...ast.content];
693
+ while (root.length) {
694
+ const node = root.shift();
695
+ if (this.protect(node)) node._renderInfo = { root: true };
696
+ if (node.type === "environment") root = [...root, ...node.content];
697
+ }
698
+ }
699
+ visit(ast, (node, info) => {
700
+ if (!node._renderInfo) node._renderInfo = {};
701
+ node._renderInfo.mode = info.context.inMathMode ? "math" : "text";
702
+ if (mode === "title" && node.type === "inlinemath" && !info.parents.find((p) => p._renderInfo.protectCase)) node._renderInfo.protectCase = true;
703
+ if (!info.context.inMathMode) {
704
+ if (mode === "title" && node._renderInfo.root && (node.type !== "group" || node.content[0].type !== "macro")) {
705
+ node._renderInfo.protectCase = true;
706
+ if (node.type === "group") {
707
+ caseProtection.present = true;
708
+ caseProtection.intuitive += 1;
709
+ }
710
+ }
711
+ if (node.type === "macro" && typeof node.escapeToken !== "string") node.escapeToken = "\\";
712
+ if (node.type === "environment" && node.env === "em") node._renderInfo.emph = true;
713
+ }
714
+ });
715
+ if (this.options.raw) {
716
+ switch (mode) {
717
+ case "creatorlist":
718
+ entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/).map((cr) => {
719
+ const { parts, extended } = this.commaSeparatedCreator(cr);
720
+ if (parts.length === 2 && !extended) {
721
+ return this.trimCreator({ lastName: printRaw(parts[0]), firstName: printRaw(parts[1]) });
722
+ }
723
+ if (!parts.length) {
724
+ const nameparts = this.split(ast, /^$/, /( )/);
725
+ if (nameparts.length === 2) {
726
+ return this.trimCreator({
727
+ lastName: printRaw(nameparts[1]),
728
+ firstName: printRaw(nameparts[0])
729
+ });
730
+ }
731
+ }
732
+ return { name: printRaw(cr) };
733
+ });
734
+ return;
735
+ case "literallist":
736
+ entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/).map((elt) => printRaw(elt));
737
+ return;
738
+ default:
739
+ entry.fields[field] = printRaw(ast);
740
+ return;
741
+ }
742
+ }
743
+ visit(ast, (nodes, info) => {
744
+ let node;
745
+ const compacted = [];
746
+ let inif = 0;
747
+ while (nodes.length) {
748
+ if (node = this.ligature(nodes)) {
749
+ compacted.push(node);
750
+ continue;
751
+ }
752
+ node = nodes.shift();
753
+ if (node.type === "macro" && node.content === "ifdefined") {
754
+ inif += 1;
755
+ continue;
756
+ } else if (node.type === "macro" && node.content === "else") {
757
+ continue;
758
+ } else if (node.type === "macro" && node.content === "fi") {
759
+ inif = Math.max(inif - 1, 0);
760
+ continue;
761
+ } else if (inif) {
762
+ continue;
763
+ }
764
+ const nargs = node.type === "macro" ? narguments[node.content] || narguments[`${info.context.inMathMode ? "math" : "text"} ${node.content}`] : 0;
765
+ if (node.type === "macro" && nargs) {
766
+ node.args = Array(nargs).fill(void 0).map((_i) => this.argument(nodes, node)).filter((arg) => arg);
767
+ if (node.content.match(/^(url|href)$/) && node.args.length) {
768
+ let url = node.args[0].content;
769
+ if (url.length === 1 && url[0].type === "group") url = url[0].content;
770
+ node.args[0] = this.wraparg({ type: "string", content: printRaw(url), _renderInfo: { mode: url[0]._renderInfo.mode } }, node);
771
+ }
772
+ caseProtection.intuitive -= node.args.filter((arg) => arg.content[0].type === "group" && arg.content[0]._renderInfo.protectCase).length;
773
+ } else if (node.type === "macro" && node.content.match(/^[a-z]+$/i) && nodes[0]?.type === "whitespace") {
774
+ nodes.shift();
775
+ }
776
+ compacted.push(node);
777
+ }
778
+ if (!info.context.inMathMode) {
779
+ for (const [macro, markup] of Object.entries({ em: "emph", it: "emph", sl: "emph", bf: "bold", sc: "smallCaps", tt: "code" })) {
780
+ if (info.parents.find((p) => p._renderInfo[markup])) continue;
781
+ compacted.forEach((markup_node, i) => {
782
+ if (markup_node.type === "macro" && markup_node.content === macro) {
783
+ compacted.slice(i + 1).forEach((n) => n._renderInfo[markup] = true);
784
+ }
785
+ });
786
+ }
787
+ }
788
+ nodes.push(...compacted);
789
+ }, { test: Array.isArray, includeArrays: true });
790
+ replaceNode(ast, (node, _info) => {
791
+ if (node.type !== "macro") return;
792
+ if (node.escapeToken && combining.tounicode[node.content]) {
793
+ let arg;
794
+ if (!node.args || node.args.length === 0 || node.args[0].content.length === 0) {
795
+ arg = { type: "string", content: " ", _renderInfo: {} };
796
+ } else if (node.args.length !== 1 || node.args[0].content.length !== 1) {
797
+ return;
798
+ } else {
799
+ arg = node.args[0].content[0];
800
+ }
801
+ if (arg.type === "group") {
802
+ switch (arg.content.length) {
803
+ case 0:
804
+ arg = { type: "string", content: " ", _renderInfo: {} };
805
+ break;
806
+ case 1:
807
+ arg = arg.content[0];
808
+ break;
809
+ default:
810
+ return;
811
+ }
812
+ }
813
+ switch (arg.type) {
814
+ case "verbatim":
815
+ case "string":
816
+ return { type: "string", content: `${arg.content}${combining.tounicode[node.content]}`, _renderInfo: {} };
817
+ default:
818
+ return;
819
+ }
820
+ }
821
+ let latex = `${node.escapeToken}${node.content}`;
822
+ latex += (node.args || []).map((arg) => printRaw(this.argtogroup(arg))).join("");
823
+ if (latex in latex2unicodemap) return { type: "string", content: latex2unicode(latex, node), _renderInfo: {} };
824
+ });
825
+ switch (mode) {
826
+ case "creatorlist":
827
+ entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/).map((cr) => this.parseCreator(cr)).filter((cr) => Object.keys(cr).length);
828
+ break;
829
+ case "literallist":
830
+ entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/).map((elt) => this.stringify(elt, { mode: "literal" }).trim());
831
+ break;
832
+ default:
833
+ entry.fields[field] = this.stringField(
834
+ field,
835
+ this.stringify(ast, { mode }),
836
+ mode,
837
+ this.options.sentenceCase && this.options.sentenceCase.guess
838
+ // && (!caseProtection.present || caseProtection.intuitive > 0)
839
+ );
840
+ break;
841
+ }
842
+ entry.fields[field] = JSON.parse(JSON.stringify(entry.fields[field], (k, v) => typeof v === "string" ? this.restoreMarkup(v) : v));
843
+ }
844
+ reset(options = {}) {
845
+ if (options.sentenceCase === false) {
846
+ options = {
847
+ ...options,
848
+ english: false,
849
+ sentenceCase: {
850
+ guess: false,
851
+ subSentence: false,
852
+ preserveQuoted: false
853
+ }
854
+ };
855
+ }
856
+ this.options = merge(options, {
857
+ caseProtection: "as-needed",
858
+ applyCrossRef: options.applyCrossRef ?? true,
859
+ fieldMode: {},
860
+ english: English,
861
+ sentenceCase: {
862
+ guess: true,
863
+ preserveQuoted: true
864
+ }
865
+ });
866
+ if (this.options.caseProtection === true) this.options.caseProtection = "as-needed";
867
+ if (this.options.verbatimFields) this.options.verbatimFields = this.options.verbatimFields.map((f) => typeof f === "string" ? f.toLowerCase() : new RegExp(f.source, f.flags + (f.flags.includes("i") ? "" : "i")));
868
+ if (typeof this.options.english === "boolean") this.options.english = this.options.english ? English : [];
869
+ this.options.english = this.options.english.map((langid) => langid.toLowerCase());
870
+ this.fieldMode = Object.entries(FieldMode).reduce((acc, [mode, test]) => {
871
+ const strings = test.filter((fieldname_or_regex) => typeof fieldname_or_regex === "string" && !this.options.fieldMode[fieldname_or_regex]);
872
+ const regexes = test.filter((fieldname_or_regex) => typeof fieldname_or_regex !== "string");
873
+ acc[mode] = [...strings, ...regexes];
874
+ return acc;
875
+ }, {});
876
+ for (const [field, mode] of Object.entries(this.options.fieldMode)) {
877
+ this.fieldMode[mode].unshift(field);
878
+ }
879
+ if (!this.options.removeOuterBraces) {
880
+ this.options.removeOuterBraces = [
881
+ ...FieldAction.removeOuterBraces,
882
+ ...this.fieldMode.title,
883
+ ...this.fieldMode.verbatim
884
+ ].filter((field) => typeof field === "string");
885
+ }
886
+ this.fallback = options.unsupported === "ignore" ? ((_node, _tex) => "") : options.unsupported;
887
+ this.bib = {
888
+ errors: [],
889
+ entries: [],
890
+ comments: [],
891
+ strings: {},
892
+ preamble: [],
893
+ jabref: null
894
+ };
895
+ }
896
+ reparse(verbatim) {
897
+ let langid = (verbatim.fields.langid || verbatim.fields.hyphenation || "").toLowerCase();
898
+ if (!langid && this.options.languageAsLangid && verbatim.fields.language) langid = verbatim.fields.language.toLowerCase();
899
+ this.english = this.options.english.includes(langid);
900
+ const entry = this.current = {
901
+ type: verbatim.type,
902
+ key: verbatim.key,
903
+ fields: {},
904
+ mode: {},
905
+ input: verbatim.input
906
+ };
907
+ let keywords = [];
908
+ try {
909
+ for (let [field, value] of Object.entries(verbatim.fields)) {
910
+ if (!value.trim()) continue;
911
+ if (field.match(/^keywords([+]duplicate-\d+)?$/)) field = "keywords";
912
+ this.field(entry, field, value);
913
+ if (field === "keywords") {
914
+ keywords = [...keywords, ...entry.fields.keywords.split(/\s*[,;]\s*/)].map((k) => k.trim()).filter((k) => k);
915
+ delete entry.fields.keywords;
916
+ }
917
+ if (!this.options.raw && typeof entry.fields[field] === "string") {
918
+ entry.fields[field] = entry.fields[field].trim();
919
+ if (field === "month") entry.fields[field] = Month[entry.fields[field].toLowerCase()] || entry.fields[field];
920
+ }
921
+ }
922
+ if (keywords.length) {
923
+ entry.fields.keywords = [...new Set(keywords)].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
924
+ entry.mode.keywords = "verbatimlist";
925
+ }
926
+ this.bib.entries.push(entry);
927
+ } catch (err) {
928
+ this.bib.errors.push({ error: `${err.message}
929
+ ${entry.input}`, input: entry.input });
930
+ }
931
+ }
932
+ content(tex) {
933
+ const entry = { key: "", type: "", fields: {}, mode: {}, input: tex };
934
+ const english = this.english;
935
+ this.english = false;
936
+ this.field(entry, "tex", tex);
937
+ this.english = english;
938
+ return entry.fields.tex;
939
+ }
940
+ prep(base) {
941
+ for (const preamble of base.preambles) {
942
+ try {
943
+ this.content(preamble);
944
+ } catch {
945
+ }
946
+ }
947
+ for (const [k, v] of Object.entries(base.strings)) {
948
+ this.bib.strings[k] = this.content(v);
949
+ }
950
+ }
951
+ finalize(base) {
952
+ if (this.options.applyCrossRef) {
953
+ const entries = {};
954
+ for (const entry of this.bib.entries) {
955
+ if (entry.key) entries[entry.key.toUpperCase()] = entry;
956
+ }
957
+ const order = [];
958
+ for (const entry of this.bib.entries) {
959
+ if (!entry.key || typeof entry.fields.crossref !== "string") continue;
960
+ const crossref = entry.fields.crossref.toUpperCase();
961
+ if (!entries[crossref]) continue;
962
+ const key = entry.key.toUpperCase();
963
+ if (!order.includes(crossref)) order.unshift(crossref);
964
+ if (!order.includes(key)) order.push(key);
965
+ }
966
+ const add = (obj, kind, field) => {
967
+ obj[kind] = [.../* @__PURE__ */ new Set([...obj[kind], field])].sort();
968
+ };
969
+ for (const key of order) {
970
+ const child = entries[key.toUpperCase()];
971
+ const parent = entries[child.fields.crossref?.toUpperCase()];
972
+ if (!parent) continue;
973
+ child.crossref = child.crossref || { donated: [], inherited: [] };
974
+ parent.crossref = parent.crossref || { donated: [], inherited: [] };
975
+ for (const mappings of [CrossRef[child.type], CrossRef["*"]].filter((m) => m)) {
976
+ for (const mapping of [mappings[parent.type], mappings["*"]].filter((m) => m)) {
977
+ for (const [childfield, parentfield] of Object.entries(mapping)) {
978
+ if (!child.fields[childfield] && parent.fields[parentfield]) {
979
+ child.fields[childfield] = parent.fields[parentfield];
980
+ add(child.crossref, "inherited", childfield);
981
+ add(parent.crossref, "donated", parentfield);
982
+ }
983
+ }
984
+ for (const field of allowed[child.type] || []) {
985
+ if (FieldAction.noCrossRef.includes(field)) continue;
986
+ if (!child.fields[field] && parent.fields[field]) {
987
+ child.fields[field] = parent.fields[field];
988
+ add(child.crossref, "inherited", field);
989
+ add(parent.crossref, "donated", field);
990
+ }
991
+ }
992
+ }
993
+ }
994
+ }
995
+ for (const entry of this.bib.entries) {
996
+ if (entry.crossref && !entry.crossref.donated.length && !entry.crossref.inherited.length) delete entry.crossref;
997
+ }
998
+ }
999
+ const { comments, jabref } = JabRef.parse(base.comments);
1000
+ this.bib.comments = comments;
1001
+ this.bib.jabref = jabref;
1002
+ this.bib.preamble = base.preambles;
1003
+ this.bib.errors = [...base.errors, ...this.bib.errors];
1004
+ }
1005
+ parse(input, options = {}) {
1006
+ this.reset(options);
1007
+ const base = bibtex.parse(input, { strings: options.strings });
1008
+ this.prep(base);
1009
+ for (const entry of base.entries) {
1010
+ this.reparse(entry);
1011
+ }
1012
+ this.finalize(base);
1013
+ return this.bib;
1014
+ }
1015
+ async parseAsync(input, options = {}) {
1016
+ this.reset(options);
1017
+ const base = await bibtex.promises.parse(input, { strings: options.strings });
1018
+ this.prep(base);
1019
+ let n = 1;
1020
+ for (const entry of base.entries) {
1021
+ this.reparse(entry);
1022
+ if (n++ % 1e3 === 0) await playnice();
1023
+ }
1024
+ this.finalize(base);
1025
+ return this.bib;
1026
+ }
1027
+ }
1028
+ function parse(input, options = {}) {
1029
+ const parser = new BibTeXParser();
1030
+ return parser.parse(input, options);
1031
+ }
1032
+ async function parseAsync(input, options = {}) {
1033
+ const parser = new BibTeXParser();
1034
+ return await parser.parseAsync(input, options);
1035
+ }
1036
+ export {
1037
+ English,
1038
+ FieldMode,
1039
+ parse,
1040
+ parseAsync,
1041
+ toSentenceCase2 as toSentenceCase,
1042
+ tokenize2 as tokenize
1043
+ };
1044
+ //# sourceMappingURL=index.js.map