@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 +1 -1
- package/index.d.ts +40 -27
- package/index.js +95 -29
- package/package.json +6 -11
- package/verbatim.d.ts +0 -1
- package/verbatim.js +4 -5
- package/grammar.js +0 -5020
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 [
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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, /(^& | & | &$)/)
|
|
882
|
-
|
|
883
|
-
|
|
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, /(^& | & | &$)/)
|
|
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(
|
|
941
|
-
let langid = (
|
|
942
|
-
if (!langid && this.options.sentenceCase.language &&
|
|
943
|
-
langid =
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
|
957
|
-
keywords = [...keywords, entry.fields[
|
|
958
|
-
delete entry.fields
|
|
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(
|
|
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 (
|
|
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.
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
}
|