@retorquere/bibtex-parser 12.1.13 → 13.0.2

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 (76) hide show
  1. package/README.md +14 -10
  2. package/dist/esm/crossref.d.ts.map +1 -0
  3. package/dist/esm/crossref.js.map +1 -1
  4. package/dist/esm/fields.d.ts.map +1 -0
  5. package/dist/esm/fields.js.map +1 -1
  6. package/dist/{types → esm}/index.d.ts +41 -12
  7. package/dist/esm/index.d.ts.map +1 -0
  8. package/dist/esm/index.js +27 -3
  9. package/dist/esm/index.js.map +3 -3
  10. package/dist/{types → esm}/jabref.d.ts +9 -21
  11. package/dist/esm/jabref.d.ts.map +1 -0
  12. package/dist/esm/jabref.js +2 -3
  13. package/dist/esm/jabref.js.map +3 -3
  14. package/dist/esm/merge.d.ts.map +1 -0
  15. package/dist/esm/merge.js.map +1 -1
  16. package/dist/esm/prepositions.d.ts.map +1 -0
  17. package/dist/esm/prepositions.js.map +1 -1
  18. package/dist/{types → esm}/sentence-case.d.ts +1 -0
  19. package/dist/esm/sentence-case.d.ts.map +1 -0
  20. package/dist/esm/sentence-case.js +126 -28
  21. package/dist/esm/sentence-case.js.map +3 -3
  22. package/dist/esm/tokenizer.d.ts.map +1 -0
  23. package/dist/esm/tokenizer.js.map +1 -1
  24. package/dist/esm/verbatim.d.ts +90 -0
  25. package/dist/esm/verbatim.d.ts.map +1 -0
  26. package/dist/esm/verbatim.js +10 -0
  27. package/dist/esm/verbatim.js.map +3 -3
  28. package/dist/esm/yield.d.ts.map +1 -0
  29. package/dist/esm/yield.js.map +1 -1
  30. package/dist/wasm/bibtex_parser.d.ts +12 -9
  31. package/dist/wasm/bibtex_parser.js +13 -23
  32. package/dist/wasm/bibtex_parser_bg.wasm +0 -0
  33. package/dist/wasm/bibtex_parser_bg.wasm.d.ts +2 -3
  34. package/dist/wasm/english.json +27 -0
  35. package/dist/wasm/index.d.ts +105 -29
  36. package/dist/wasm/index.js +54 -51
  37. package/dist/wasm/jabref.d.ts +9 -21
  38. package/dist/wasm/jabref.js +3 -3
  39. package/package.json +25 -26
  40. package/dist/cjs/crossref.js +0 -201
  41. package/dist/cjs/crossref.js.map +0 -7
  42. package/dist/cjs/fields.js +0 -7186
  43. package/dist/cjs/fields.js.map +0 -7
  44. package/dist/cjs/index.js +0 -1126
  45. package/dist/cjs/index.js.map +0 -7
  46. package/dist/cjs/jabref.js +0 -135
  47. package/dist/cjs/jabref.js.map +0 -7
  48. package/dist/cjs/merge.js +0 -37
  49. package/dist/cjs/merge.js.map +0 -7
  50. package/dist/cjs/prepositions.js +0 -111
  51. package/dist/cjs/prepositions.js.map +0 -7
  52. package/dist/cjs/sentence-case.js +0 -98
  53. package/dist/cjs/sentence-case.js.map +0 -7
  54. package/dist/cjs/tokenizer.js +0 -194
  55. package/dist/cjs/tokenizer.js.map +0 -7
  56. package/dist/cjs/verbatim.js +0 -403
  57. package/dist/cjs/verbatim.js.map +0 -7
  58. package/dist/cjs/yield.js +0 -26
  59. package/dist/cjs/yield.js.map +0 -7
  60. package/dist/types/crossref.d.ts.map +0 -1
  61. package/dist/types/fields.d.ts.map +0 -1
  62. package/dist/types/index.d.ts.map +0 -1
  63. package/dist/types/jabref.d.ts.map +0 -1
  64. package/dist/types/merge.d.ts.map +0 -1
  65. package/dist/types/prepositions.d.ts.map +0 -1
  66. package/dist/types/sentence-case.d.ts.map +0 -1
  67. package/dist/types/tokenizer.d.ts.map +0 -1
  68. package/dist/types/verbatim.d.ts +0 -66
  69. package/dist/types/verbatim.d.ts.map +0 -1
  70. package/dist/types/yield.d.ts.map +0 -1
  71. /package/dist/{types → esm}/crossref.d.ts +0 -0
  72. /package/dist/{types → esm}/fields.d.ts +0 -0
  73. /package/dist/{types → esm}/merge.d.ts +0 -0
  74. /package/dist/{types → esm}/prepositions.d.ts +0 -0
  75. /package/dist/{types → esm}/tokenizer.d.ts +0 -0
  76. /package/dist/{types → esm}/yield.d.ts +0 -0
@@ -12,6 +12,7 @@ export type { Options as SentenceCaseOptions } from './sentence-case.js';
12
12
  /** Splits text into classified tokens with source offsets. */
13
13
  export { tokenize } from './tokenizer.js';
14
14
  export type { Token } from './tokenizer.js';
15
+ /** Known creator-list fields, each returned as an array of parsed {@link Creator} values. */
15
16
  export type CreatorFields = {
16
17
  author?: Creator[];
17
18
  bookauthor?: Creator[];
@@ -26,6 +27,7 @@ export type CreatorFields = {
26
27
  scriptwriter?: Creator[];
27
28
  translator?: Creator[];
28
29
  };
30
+ /** Known multi-value fields whose elements remain strings. */
29
31
  export type ArrayFields = {
30
32
  keywords?: string[];
31
33
  institution?: string[];
@@ -35,44 +37,52 @@ export type ArrayFields = {
35
37
  location?: string[];
36
38
  origlocation?: string[];
37
39
  };
40
+ /** The known creator-list and string-list fields in a normalized entry. */
38
41
  export type TypedFields = CreatorFields & ArrayFields;
42
+ /** BibTeX field names mapped to normalized values; fields outside the known lists are strings. */
39
43
  export type Fields = TypedFields & Omit<Record<string, string>, keyof TypedFields>;
40
44
  /** A normalized BibTeX entry returned by {@link parse}. */
41
45
  export type Entry = {
46
+ /** Entry type from the source, such as `article` or `inproceedings`. */
42
47
  type: string;
48
+ /** Citation key identifying this entry. */
43
49
  key: string;
50
+ /** Parsed field values, with known creator and list fields represented as arrays. */
44
51
  fields: Fields;
52
+ /** Parsing mode used for each field; see {@link ParseMode}. */
45
53
  mode: Record<string, ParseMode>;
54
+ /** For fields inherited through `crossref`, records the parent entry key and source field name. */
46
55
  crossref?: Record<string, {
47
56
  parent: string;
48
57
  field: string;
49
58
  }>;
59
+ /** Source text for this entry. */
50
60
  input: string;
51
61
  };
52
62
  /** The parsed contents of a BibTeX library. */
53
63
  export interface Library {
54
64
  /**
55
- * errors found while parsing
65
+ * Diagnostics collected while parsing. Recoverable errors do not necessarily prevent entries from being returned.
56
66
  */
57
67
  errors: bibtex.ParseError[];
58
68
  /**
59
- * entries in the order in which they are found, omitting those which could not be parsed.
69
+ * Successfully parsed entries, in source order; entries that could not be parsed are omitted.
60
70
  */
61
71
  entries: Entry[];
62
72
  /**
63
- * `@comment`s found in the bibtex file.
73
+ * Text from `@comment` directives.
64
74
  */
65
75
  comments: string[];
66
76
  /**
67
- * `@string`s found in the bibtex file.
77
+ * Resolved values declared by `@string` directives.
68
78
  */
69
79
  strings: Record<string, string>;
70
80
  /**
71
- * `@preamble` declarations found in the bibtex file
81
+ * Values from `@preamble` directives, in source order.
72
82
  */
73
83
  preambles: string[];
74
84
  /**
75
- * jabref metadata (such as groups information) found in the bibtex file
85
+ * JabRef metadata extracted from the library, including groups and file settings.
76
86
  */
77
87
  jabref: JabRef.JabRefMetadata;
78
88
  }
@@ -146,6 +156,8 @@ export interface Options {
146
156
  * case protection exactly as the input has it
147
157
  */
148
158
  caseProtection?: 'as-needed' | 'strict' | boolean;
159
+ /** Render URL commands as HTML links (default true), or as case-protected destinations. */
160
+ richURL?: boolean;
149
161
  /**
150
162
  * How to handle unknown TeX commands and environments: `tex` wraps the command in a script
151
163
  * element, `text` returns the command without its backslash, `ignore` omits it, and `throw`
@@ -154,10 +166,10 @@ export interface Options {
154
166
  */
155
167
  unsupported?: 'tex' | 'text' | 'ignore' | 'throw';
156
168
  /**
157
- * Some fields such as `url` are parsed in what is called "verbatim mode" where pretty much everything except braces is treated as regular text, not TeX commands. You can change the default list here if you want,
158
- * for example to help parse Mendeley `file` fields, which against spec are not in verbatim mode.
169
+ * Fields parsed in verbatim mode. Selectors use wildflower globs (`?`, `*`, and `\\`) and match
170
+ * complete field names.
159
171
  */
160
- verbatimFields?: (string | RegExp)[];
172
+ verbatimFields?: string[];
161
173
  /**
162
174
  * In the past many bibtex entries would just always wrap a field in double braces, likely because whomever was writing them couldn't figure out the case meddling rules (and who could
163
175
  * blame them). Fields listed here will either have one outer layer of braces treated as case-preserve, or have the outer braced be ignored completely, if this is detected.
@@ -182,13 +194,21 @@ export interface Options {
182
194
  }
183
195
  /** A parsed personal or organizational name. */
184
196
  export interface Creator {
197
+ /** Complete name when the parser cannot safely separate it into components. */
185
198
  name?: string;
199
+ /** Family name, including any parsed name prefix. */
186
200
  lastName?: string;
201
+ /** Given name. */
187
202
  firstName?: string;
203
+ /** Name particle, such as "von" or "de", when separated from the family name. */
188
204
  prefix?: string;
205
+ /** Generational or other name suffix. */
189
206
  suffix?: string;
207
+ /** Initials of the given name. */
190
208
  initial?: string;
209
+ /** Whether the parsed family name includes a name prefix. */
191
210
  useprefix?: boolean;
211
+ /** Whether the source separated the suffix from the name with a comma. */
192
212
  juniorcomma?: boolean;
193
213
  }
194
214
  export declare const FieldMode: {
@@ -198,11 +218,20 @@ export declare const FieldMode: {
198
218
  richtext: string[];
199
219
  literallist: string[];
200
220
  };
201
- /** Controls how a field's TeX content is interpreted. */
221
+ /** Controls how a field's TeX content is interpreted and which normalized value shape it receives. */
202
222
  export type ParseMode = keyof typeof FieldMode | 'literal' | 'verbatimlist';
203
223
  export declare const English: string[];
204
- /** Parses BibTeX or BibLaTeX source into a normalized library. */
224
+ /**
225
+ * Parses BibTeX or BibLaTeX source into normalized entries and field values.
226
+ * Recoverable problems are reported in `Library.errors`; set `unsupported: 'throw'` to throw on unsupported TeX commands or environments.
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * const { entries, errors } = parse('@article{example, author={Ada Lovelace}, title={A title}}')
231
+ * const title = entries[0]?.fields.title
232
+ * ```
233
+ */
205
234
  export declare function parse(input: string, options?: Options): Library;
206
- /** Asynchronous variant of {@link parse}, yielding periodically for large libraries. */
235
+ /** Asynchronous variant of {@link parse}; returns the same result shape and yields periodically for large libraries. */
207
236
  export declare function parseAsync(input: string, options?: Options): Promise<Library>;
208
237
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../js/index.ts"],"names":[],"mappings":"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"}
package/dist/esm/index.js CHANGED
@@ -234,6 +234,7 @@ class BibTeXParser {
234
234
  __publicField(this, "english");
235
235
  __publicField(this, "options");
236
236
  __publicField(this, "fieldMode");
237
+ __publicField(this, "verbatimFieldPatterns");
237
238
  __publicField(this, "newcommands", {});
238
239
  __publicField(this, "bib");
239
240
  __publicField(this, "unhandled", /* @__PURE__ */ new Set());
@@ -510,7 +511,7 @@ class BibTeXParser {
510
511
  url = node.args[0];
511
512
  label = node.args[node.content === "url" ? 0 : 1];
512
513
  }
513
- return `<a href="${this.stringify(url, context)}">${this.stringify(label, context)}</a>`;
514
+ return this.options.richURL === false ? `<span class="nocase">${this.stringify(url, { ...context, caseProtected: true })}</span>` : `<a href="${this.stringify(url, { ...context, caseProtected: true })}">${this.stringify(label, { ...context, caseProtected: true })}</a>`;
514
515
  case "relax":
515
516
  case "aftergroup":
516
517
  case "ignorespaces":
@@ -653,13 +654,33 @@ class BibTeXParser {
653
654
  return node.content[0].type !== "macro";
654
655
  }
655
656
  mode(field) {
656
- if (this.options.verbatimFields && this.options.verbatimFields.find((name) => typeof name === "string" ? name === field : field.match(name))) return "verbatim";
657
+ if (this.verbatimFieldPatterns.some((pattern) => field.match(pattern))) return "verbatim";
657
658
  let mode = "literal";
658
659
  for (const [selected, fields] of Object.entries(this.fieldMode)) {
659
660
  if (fields.find((match) => typeof match === "string" ? field === match : field.match(match))) mode = selected;
660
661
  }
661
662
  return mode;
662
663
  }
664
+ wildcardPattern(pattern) {
665
+ let source = "^";
666
+ let escaped = false;
667
+ for (const character of pattern.toLowerCase()) {
668
+ const literal = character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
669
+ if (escaped) {
670
+ source += literal;
671
+ escaped = false;
672
+ } else if (character === "\\") {
673
+ escaped = true;
674
+ } else if (character === "?") {
675
+ source += ".";
676
+ } else if (character === "*") {
677
+ source += ".*?";
678
+ } else {
679
+ source += literal;
680
+ }
681
+ }
682
+ return new RegExp(`${source}$`, "iu");
683
+ }
663
684
  restoreMarkup(s) {
664
685
  if (!s.includes("")) return s;
665
686
  const restored = [s.replace(/\x0E\/?ncx\x0F/ig, "")];
@@ -915,7 +936,10 @@ class BibTeXParser {
915
936
  }
916
937
  });
917
938
  if (this.options.caseProtection === true) this.options.caseProtection = "as-needed";
918
- 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")));
939
+ this.verbatimFieldPatterns = (this.options.verbatimFields ?? []).map((pattern) => {
940
+ if (typeof pattern !== "string") throw new TypeError("verbatimFields entries must be glob strings");
941
+ return this.wildcardPattern(pattern);
942
+ });
919
943
  if (typeof this.options.english === "boolean") this.options.english = this.options.english ? English : [];
920
944
  this.options.english = this.options.english.map((langid) => langid.toLowerCase());
921
945
  this.fieldMode = Object.entries(FieldMode).reduce((acc, [mode, test]) => {