@retorquere/bibtex-parser 8.0.6 → 8.0.8

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/README.md CHANGED
@@ -8,4 +8,4 @@ While parsing, the parser will apply markup for commands such as `\textbf`, `\te
8
8
 
9
9
  BibTeX is a reference management program that makes it easy for users of TeX and LaTeX to generate bibliographies for books and journal articles. When possible, it is usually stylized in the TeX font the way TeX and LaTeX are. LaTeX was first created in 1985 by Leslie Lamport and Oren Patashnik.
10
10
 
11
- Documenation is available at [http://retorque.re/bibtex-parser/](http://retorque.re/bibtex-parser/).
11
+ Documenation is available at [https://retorque.re/bibtex-parser/](https://retorque.re/bibtex-parser/).
package/index.d.ts CHANGED
@@ -2,7 +2,47 @@ import { Node } from '@unified-latex/unified-latex-types';
2
2
  import * as bibtex from './verbatim';
3
3
  import * as JabRef from './jabref';
4
4
  export { JabRefMetadata } from './jabref';
5
+ export { ParseError } from './verbatim';
5
6
  export { toSentenceCase } from './sentence-case';
7
+ type CreatorFields = {
8
+ author?: Creator[];
9
+ bookauthor?: Creator[];
10
+ collaborator?: Creator[];
11
+ commentator?: Creator[];
12
+ director?: Creator[];
13
+ editor?: Creator[];
14
+ editora?: Creator[];
15
+ editorb?: Creator[];
16
+ editors?: Creator[];
17
+ holder?: Creator[];
18
+ scriptwriter?: Creator[];
19
+ translator?: Creator[];
20
+ };
21
+ type ArrayFields = {
22
+ keywords?: string[];
23
+ institution?: string[];
24
+ publisher?: string[];
25
+ origpublisher?: string[];
26
+ organization?: string[];
27
+ location?: string[];
28
+ origlocation?: string[];
29
+ };
30
+ type NumberFields = {
31
+ year?: number | string;
32
+ month?: number | string;
33
+ };
34
+ type TypedFields = CreatorFields & ArrayFields & NumberFields;
35
+ type Fields = TypedFields & Omit<Record<string, string>, keyof TypedFields>;
36
+ export type Entry = {
37
+ type: string;
38
+ key: string;
39
+ fields: Fields;
40
+ crossref?: {
41
+ inherited: string[];
42
+ donated: [];
43
+ };
44
+ input: string;
45
+ };
6
46
  export interface Bibliography {
7
47
  /**
8
48
  * errors found while parsing
@@ -135,38 +175,11 @@ export interface Creator {
135
175
  useprefix?: boolean;
136
176
  juniorcomma?: boolean;
137
177
  }
138
- export interface Entry {
139
- type: string;
140
- key: string;
141
- fields: {
142
- author?: Creator[];
143
- bookauthor?: Creator[];
144
- collaborator?: Creator[];
145
- commentator?: Creator[];
146
- director?: Creator[];
147
- editor?: Creator[];
148
- editora?: Creator[];
149
- editorb?: Creator[];
150
- editors?: Creator[];
151
- holder?: Creator[];
152
- scriptwriter?: Creator[];
153
- translator?: Creator[];
154
- keywords?: string[];
155
- institution?: string[];
156
- publisher?: string[];
157
- origpublisher?: string[];
158
- organization?: string[];
159
- location?: string[];
160
- origlocation?: string[];
161
- [key: string]: number | string | string[] | Creator[];
162
- };
163
- }
164
178
  export declare const FieldMode: {
165
179
  creator: string[];
166
180
  title: string[];
167
181
  verbatim: (string | RegExp)[];
168
182
  richtext: string[];
169
- commalist: string[];
170
183
  literallist: string[];
171
184
  };
172
185
  type ParseMode = keyof typeof FieldMode | 'literal';
package/index.js CHANGED
@@ -76,6 +76,31 @@ for (const tag of ['i', 'b', 'sc', 'nc', 'ncx', 'br', 'p', 'li', 'code']) {
76
76
  close[tag] = `\x0E/${tag}\x0F`;
77
77
  }
78
78
  const collapsable = /\x0E\/([a-z]+)\x0F(\s*)\x0E\1\x0F/ig;
79
+ const Month = {
80
+ jan: 1,
81
+ january: 1,
82
+ feb: 2,
83
+ february: 2,
84
+ mar: 3,
85
+ march: 3,
86
+ apr: 4,
87
+ april: 4,
88
+ may: 5,
89
+ jun: 6,
90
+ june: 6,
91
+ jul: 7,
92
+ july: 7,
93
+ aug: 8,
94
+ august: 8,
95
+ sep: 9,
96
+ september: 9,
97
+ oct: 10,
98
+ october: 10,
99
+ nov: 11,
100
+ november: 11,
101
+ dec: 12,
102
+ december: 12,
103
+ };
79
104
  exports.FieldMode = {
80
105
  creator: [
81
106
  'author',
@@ -114,6 +139,7 @@ exports.FieldMode = {
114
139
  'verba',
115
140
  'verbb',
116
141
  'verbc',
142
+ /^keywords([+]duplicate-\d+)?$/,
117
143
  /^citeulike-linkout-[0-9]+$/,
118
144
  /^bdsk-url-[0-9]+$/,
119
145
  ],
@@ -125,9 +151,6 @@ exports.FieldMode = {
125
151
  'notes',
126
152
  'note',
127
153
  ],
128
- commalist: [
129
- 'keywords',
130
- ],
131
154
  literallist: [
132
155
  'institution',
133
156
  'publisher',
@@ -180,6 +203,9 @@ const FieldAction = {
180
203
  'year',
181
204
  'month',
182
205
  ],
206
+ noCrossRef: [
207
+ 'file',
208
+ ],
183
209
  };
184
210
  const narguments = {
185
211
  advance: 1,
@@ -250,6 +276,7 @@ function asyncGenerator(array) {
250
276
  class BibTeXParser {
251
277
  constructor() {
252
278
  this.newcommands = {};
279
+ this.unhandled = new Set;
253
280
  }
254
281
  split(ast, sep, split) {
255
282
  const roots = [];
@@ -412,19 +439,30 @@ class BibTeXParser {
412
439
  }
413
440
  unsupported(node) {
414
441
  var _a;
442
+ const tex = (0, unified_latex_util_print_raw_1.printRaw)(node);
415
443
  if (this.fallback)
416
- return (_a = this.fallback(node, (0, unified_latex_util_print_raw_1.printRaw)(node), this.current)) !== null && _a !== void 0 ? _a : '';
444
+ return (_a = this.fallback(node, tex, this.current)) !== null && _a !== void 0 ? _a : '';
445
+ let id;
417
446
  switch (node.type) {
418
447
  case 'macro':
419
- this.bib.errors.push({ error: `unhandled ${node.type} ${node.content} (${(0, unified_latex_util_print_raw_1.printRaw)(node)})`, input: (0, unified_latex_util_print_raw_1.printRaw)(node) });
448
+ id = `${node.type}.${node.content}`;
449
+ if (!this.unhandled.has(id)) {
450
+ this.unhandled.add(id);
451
+ this.bib.errors.push({ error: `unhandled ${node.type} ${(0, unified_latex_util_print_raw_1.printRaw)(node)}`, input: (0, unified_latex_util_print_raw_1.printRaw)(node) });
452
+ }
420
453
  break;
421
454
  case 'environment':
422
- this.bib.errors.push({ error: `unhandled ${node.type} ${node.env} (${(0, unified_latex_util_print_raw_1.printRaw)(node)})`, input: (0, unified_latex_util_print_raw_1.printRaw)(node) });
455
+ id = `${node.type}.${node.env}`;
456
+ if (!this.unhandled.has(id)) {
457
+ this.unhandled.add(id);
458
+ this.bib.errors.push({ error: `unhandled ${node.type} ${node.env} (${(0, unified_latex_util_print_raw_1.printRaw)(node)})`, input: (0, unified_latex_util_print_raw_1.printRaw)(node) });
459
+ }
423
460
  break;
424
461
  default:
425
462
  this.bib.errors.push({ error: `unhandled ${node.type} (${(0, unified_latex_util_print_raw_1.printRaw)(node)})`, input: (0, unified_latex_util_print_raw_1.printRaw)(node) });
426
463
  break;
427
464
  }
465
+ return tex;
428
466
  }
429
467
  wrap(text, tag, wrap = true) {
430
468
  if (!text || !wrap)
@@ -878,15 +916,16 @@ class BibTeXParser {
878
916
  });
879
917
  switch (mode) {
880
918
  case 'creator':
881
- entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/).map(cr => this.parseCreator(cr)).filter(cr => Object.keys(cr).length);
882
- break;
883
- case 'commalist':
884
- entry.fields[field] = this.split(ast, /^[;,]$/, /(&)/).map(elt => this.stringify(elt, { mode: 'literal' }).trim());
919
+ entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/)
920
+ .map(cr => this.parseCreator(cr))
921
+ .filter(cr => Object.keys(cr).length); // pacify typescript
885
922
  break;
886
923
  case 'literallist':
887
- entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/).map(elt => this.stringify(elt, { mode: 'literal' }).trim());
924
+ entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/)
925
+ .map(elt => this.stringify(elt, { mode: 'literal' }).trim()); // pacify typescript
888
926
  break;
889
927
  default:
928
+ // can be number or string, but typescript cannot know this
890
929
  entry.fields[field] = this.stringField(field, this.stringify(ast, { mode }), sentenceCase, mode);
891
930
  break;
892
931
  }
@@ -937,31 +976,41 @@ class BibTeXParser {
937
976
  jabref: null,
938
977
  };
939
978
  }
940
- reparse(entry) {
941
- let langid = (entry.fields.langid || entry.fields.hyphenation || '').toLowerCase();
942
- if (!langid && this.options.sentenceCase.language && entry.fields.language)
943
- langid = entry.fields.language.toLowerCase();
979
+ reparse(verbatim) {
980
+ let langid = (verbatim.fields.langid || verbatim.fields.hyphenation || '').toLowerCase();
981
+ if (!langid && this.options.sentenceCase.language && verbatim.fields.language)
982
+ langid = verbatim.fields.language.toLowerCase();
944
983
  const sentenceCase = this.options.sentenceCase.langids.includes(langid);
945
- this.current = entry;
984
+ const entry = this.current = {
985
+ type: verbatim.type,
986
+ key: verbatim.key,
987
+ fields: {},
988
+ input: verbatim.input,
989
+ };
946
990
  let keywords = []; // OMG #783
947
991
  try {
948
- for (const [field, value] of Object.entries(entry.fields)) {
949
- if (!value.trim()) {
950
- delete entry.fields[field];
992
+ for (let [field, value] of Object.entries(verbatim.fields)) {
993
+ if (!value.trim())
951
994
  continue;
952
- }
953
- if (this.options.raw)
995
+ if (this.options.raw) {
996
+ entry.fields[field] = value.trim();
954
997
  continue;
998
+ }
999
+ if (field.match(/^keywords([+]duplicate-\d+)?$/))
1000
+ field = 'keywords'; // #873
955
1001
  this.field(entry, field, value, sentenceCase);
956
- if (field.match(/^keywords\[\d+\]$/)) {
957
- keywords = [...keywords, entry.fields[field]];
958
- delete entry.fields[field];
1002
+ if (field === 'keywords') { // #873
1003
+ keywords = [...keywords, ...entry.fields.keywords.split(/\s*[,;]\s*/)].map((k) => k.trim()).filter(k => k);
1004
+ delete entry.fields.keywords;
959
1005
  }
960
- if (typeof entry.fields[field] === 'string')
1006
+ if (typeof entry.fields[field] === 'string') {
961
1007
  entry.fields[field] = entry.fields[field].trim();
1008
+ if (field === 'month')
1009
+ entry.fields[field] = Month[entry.fields[field].toLowerCase()] || entry.fields[field];
1010
+ }
962
1011
  }
963
1012
  if (keywords.length)
964
- entry.fields.keywords = [...(new Set([...(entry.fields.keywords || []), ...keywords]))].sort();
1013
+ entry.fields.keywords = [...(new Set(keywords))].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
965
1014
  this.bib.entries.push(entry);
966
1015
  }
967
1016
  catch (err) {
@@ -969,7 +1018,7 @@ class BibTeXParser {
969
1018
  }
970
1019
  }
971
1020
  content(tex) {
972
- const entry = { key: '', type: '', fields: {} };
1021
+ const entry = { key: '', type: '', fields: {}, input: tex };
973
1022
  this.field(entry, 'tex', tex, false);
974
1023
  return entry.fields.tex;
975
1024
  }
@@ -1006,24 +1055,41 @@ class BibTeXParser {
1006
1055
  if (!order.includes(key))
1007
1056
  order.push(key);
1008
1057
  }
1058
+ const add = (obj, kind, field) => {
1059
+ obj[kind] = [...(new Set([...obj[kind], field]))].sort();
1060
+ };
1009
1061
  for (const key of order) {
1010
1062
  const child = entries[key.toUpperCase()];
1011
1063
  const parent = entries[(_a = child.fields.crossref) === null || _a === void 0 ? void 0 : _a.toUpperCase()];
1012
1064
  if (!parent)
1013
1065
  continue;
1066
+ child.crossref = child.crossref || { donated: [], inherited: [] };
1067
+ parent.crossref = parent.crossref || { donated: [], inherited: [] };
1014
1068
  for (const mappings of [crossref_json_1.default[child.type], crossref_json_1.default['*']].filter(m => m)) {
1015
1069
  for (const mapping of [mappings[parent.type], mappings['*']].filter(m => m)) {
1016
1070
  for (const [childfield, parentfield] of Object.entries(mapping)) {
1017
- if (!child.fields[childfield] && parent.fields[parentfield])
1071
+ if (!child.fields[childfield] && parent.fields[parentfield]) {
1018
1072
  child.fields[childfield] = parent.fields[parentfield];
1073
+ add(child.crossref, 'inherited', childfield);
1074
+ add(parent.crossref, 'donated', parentfield);
1075
+ }
1019
1076
  }
1020
1077
  for (const field of (fields_json_1.default[child.type] || [])) {
1021
- if (!child.fields[field] && parent.fields[field])
1078
+ if (FieldAction.noCrossRef.includes(field))
1079
+ continue;
1080
+ if (!child.fields[field] && parent.fields[field]) {
1022
1081
  child.fields[field] = parent.fields[field];
1082
+ add(child.crossref, 'inherited', field);
1083
+ add(parent.crossref, 'donated', field);
1084
+ }
1023
1085
  }
1024
1086
  }
1025
1087
  }
1026
1088
  }
1089
+ for (const entry of this.bib.entries) {
1090
+ if (entry.crossref && !entry.crossref.donated.length && !entry.crossref.inherited.length)
1091
+ delete entry.crossref;
1092
+ }
1027
1093
  }
1028
1094
  const { comments, jabref } = JabRef.parse(base.comments);
1029
1095
  this.bib.comments = comments;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retorquere/bibtex-parser",
3
- "version": "8.0.6",
3
+ "version": "8.0.8",
4
4
  "description": "BibTeX parser",
5
5
  "main": "index.js",
6
6
  "typings": "./index.d.ts",
@@ -9,24 +9,21 @@
9
9
  "lint": "eslint . --ext .ts --cache --cache-location .eslintcache/",
10
10
  "prestart": "npm run lint",
11
11
  "start": "npm run tsc",
12
- "tsc": "peggy grammar.peggy && tsc -d",
12
+ "tsc": "tsc -d",
13
13
  "pretest": "npm start",
14
14
  "test": "tap --timeout 0 --disable-coverage --allow-empty-coverage",
15
15
  "snap": "npm test --snapshot",
16
16
  "snap:big": "npm test --snapshot --big",
17
- "minitest": "peggy grammar.peggy && ./build/crossref.py && tsc -d && node i.js",
17
+ "minitest": "./build/crossref.py && tsc -d && node i.js",
18
18
  "list": "tar tvf $(npm pack)",
19
19
  "prepublishOnly": "npm start",
20
20
  "abbr": "./build/abbr.py",
21
21
  "preversion": "./console.py r && npm run abbr && npm run test",
22
22
  "postversion": "git push --follow-tags",
23
23
  "update-cases": "curl -s https://raw.githubusercontent.com/retorquere/zotero-better-bibtex/master/test/loaded.json | jq -r '.[] | select(test(\"\\\\.bib((la)?tex)?$\"))' | rsync -av --delete -m --files-from=- ../better-bibtex/test/fixtures/ test/better-bibtex/",
24
- "x-docs": "rm -rf docs temp && typedoc --name 'BibTeX parser' index.ts && touch docs/.nojekyll",
25
24
  "predocs": "npm start",
26
- "docs": "mkdir -p etc temp && npm run api-extractor && npm run api-documenter",
27
- "ncu": "./build/submodules.py && ./build/crossref.py && ncu -u && npm i && git add package.json package-lock.json && npm audit fix && git commit -m ncu",
28
- "api-documenter": "api-documenter markdown --input temp --output docs",
29
- "api-extractor": "api-extractor run --local --verbose"
25
+ "docs": "typedoc",
26
+ "ncu": "./build/submodules.py && ./build/crossref.py && ncu -u && npm i && git add package.json package-lock.json && npm audit fix && git commit -m ncu"
30
27
  },
31
28
  "repository": {
32
29
  "type": "git",
@@ -38,8 +35,6 @@
38
35
  "author": "Emiliano Heyns",
39
36
  "license": "ISC",
40
37
  "devDependencies": {
41
- "@microsoft/api-documenter": "^7.24.1",
42
- "@microsoft/api-extractor": "^7.43.0",
43
38
  "@types/node": "^20.12.2",
44
39
  "@typescript-eslint/eslint-plugin": "^7.4.0",
45
40
  "@typescript-eslint/parser": "^7.4.0",
@@ -54,12 +49,12 @@
54
49
  "glob": "^10.3.12",
55
50
  "mkdirp": "^3.0.1",
56
51
  "node-fetch": "^3.3.2",
57
- "peggy": "^4.0.2",
58
52
  "piscina": "^4.4.0",
59
53
  "slug": "^9.0.0",
60
54
  "tap": "^18.7.2",
61
55
  "ts-node": "^10.9.2",
62
56
  "tslib": "^2.6.2",
57
+ "typedoc": "^0.25.12",
63
58
  "typescript": "^5.4.3"
64
59
  },
65
60
  "dependencies": {
package/verbatim.d.ts CHANGED
@@ -34,7 +34,6 @@ declare class Parser {
34
34
  private linebreaks;
35
35
  private input;
36
36
  private max_entries;
37
- private keywords;
38
37
  constructor(input: string, options?: ParserOptions);
39
38
  parse(): void;
40
39
  parseAsync(): Promise<void>;
package/verbatim.js CHANGED
@@ -85,7 +85,6 @@ class Parser {
85
85
  this.max_entries = options.max_entries || 0;
86
86
  this.input = input;
87
87
  this.parsing = null;
88
- this.keywords = 0;
89
88
  if (typeof options.strings === 'string') {
90
89
  this.input = options.strings + '\n' + input; // eslint-disable-line prefer-template
91
90
  }
@@ -271,10 +270,10 @@ class Parser {
271
270
  this.strings[key.toUpperCase()] = val;
272
271
  }
273
272
  else {
274
- key = key.toLowerCase();
275
- if (key.toLowerCase() === 'keywords' && this.entries[0].fields.keywords) { // #873... the things people do with bibtex
276
- this.keywords++;
277
- key = `keywords[${this.keywords}]`;
273
+ const bare = key.toLowerCase();
274
+ let postfix = 0;
275
+ while (typeof this.entries[0].fields[key = (postfix ? `${bare}+duplicate-${postfix}` : bare)] === 'string') {
276
+ postfix++;
278
277
  }
279
278
  this.entries[0].fields[key] = val;
280
279
  }