@retorquere/bibtex-parser 7.0.16 → 8.0.0
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/grammar.js +107 -70
- package/index.d.ts +114 -221
- package/index.js +839 -1318
- package/package.json +28 -19
- package/sentence-case.d.ts +32 -1
- package/sentence-case.js +192 -79
- package/unabbrev.json +743 -749
- package/chunker.d.ts +0 -60
- package/chunker.js +0 -358
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retorquere/bibtex-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "BibTeX parser",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "./index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"unified": "tsx unified.ts",
|
|
8
9
|
"lint": "eslint . --ext .ts --cache --cache-location .eslintcache/",
|
|
9
|
-
"prestart": "npm run lint
|
|
10
|
-
"start": "tsc
|
|
11
|
-
"tsc": "tsc -d",
|
|
12
|
-
"poststart": "npm run docs",
|
|
10
|
+
"prestart": "npm run lint",
|
|
11
|
+
"start": "npm run tsc",
|
|
12
|
+
"tsc": "peggy grammar.peggy && tsc -d",
|
|
13
13
|
"pretest": "npm start",
|
|
14
14
|
"test": "tap --timeout 0 --disable-coverage --allow-empty-coverage",
|
|
15
15
|
"snap": "npm test --snapshot",
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
"list": "tar tvf $(npm pack)",
|
|
19
19
|
"prepublishOnly": "npm start",
|
|
20
20
|
"abbr": "./build/abbr.py",
|
|
21
|
-
"preversion": "npm test && npm run abbr",
|
|
21
|
+
"preversion": "./console.py r && npm test && npm run abbr",
|
|
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
24
|
"x-docs": "rm -rf docs temp && typedoc --name 'BibTeX parser' index.ts && touch docs/.nojekyll",
|
|
25
|
+
"predocs": "npm start",
|
|
25
26
|
"docs": "mkdir -p etc temp && npm run api-extractor && npm run api-documenter",
|
|
26
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",
|
|
27
28
|
"api-documenter": "api-documenter markdown --input temp --output docs",
|
|
@@ -37,31 +38,39 @@
|
|
|
37
38
|
"author": "Emiliano Heyns",
|
|
38
39
|
"license": "ISC",
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@microsoft/api-documenter": "^7.
|
|
41
|
-
"@microsoft/api-extractor": "^7.
|
|
42
|
-
"@types/node": "^20.
|
|
43
|
-
"@typescript-eslint/eslint-plugin": "^7.0
|
|
44
|
-
"@typescript-eslint/parser": "^7.0
|
|
41
|
+
"@microsoft/api-documenter": "^7.24.1",
|
|
42
|
+
"@microsoft/api-extractor": "^7.43.0",
|
|
43
|
+
"@types/node": "^20.12.2",
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
|
45
|
+
"@typescript-eslint/parser": "^7.4.0",
|
|
45
46
|
"command-line-args": "^5.2.1",
|
|
46
|
-
"csv": "^6.3.
|
|
47
|
+
"csv": "^6.3.8",
|
|
47
48
|
"digital-tree": "^2.0.4",
|
|
48
49
|
"ejs": "^3.1.9",
|
|
50
|
+
"esbuild": "^0.20.2",
|
|
49
51
|
"eslint": "^8.57.0",
|
|
50
52
|
"eslint-plugin-import": "^2.29.1",
|
|
51
53
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
|
52
|
-
"glob": "^10.3.
|
|
54
|
+
"glob": "^10.3.12",
|
|
53
55
|
"mkdirp": "^3.0.1",
|
|
54
56
|
"node-fetch": "^3.3.2",
|
|
55
|
-
"peggy": "^4.0.
|
|
56
|
-
"piscina": "^4.
|
|
57
|
-
"slug": "^
|
|
58
|
-
"tap": "^18.7.
|
|
57
|
+
"peggy": "^4.0.2",
|
|
58
|
+
"piscina": "^4.4.0",
|
|
59
|
+
"slug": "^9.0.0",
|
|
60
|
+
"tap": "^18.7.2",
|
|
59
61
|
"ts-node": "^10.9.2",
|
|
60
62
|
"tslib": "^2.6.2",
|
|
61
|
-
"typescript": "^5.
|
|
63
|
+
"typescript": "^5.4.3"
|
|
62
64
|
},
|
|
63
65
|
"dependencies": {
|
|
64
|
-
"
|
|
66
|
+
"@unified-latex/unified-latex-util-pegjs": "^1.7.1",
|
|
67
|
+
"@unified-latex/unified-latex-util-print-raw": "^1.7.1",
|
|
68
|
+
"@unified-latex/unified-latex-util-replace": "^1.7.1",
|
|
69
|
+
"@unified-latex/unified-latex-util-visit": "^1.7.1",
|
|
70
|
+
"compromise": "^14.13.0",
|
|
71
|
+
"unicode2latex": "^6.0.4",
|
|
72
|
+
"wink-eng-lite-web-model": "^1.6.0",
|
|
73
|
+
"wink-nlp": "^2.2.0",
|
|
65
74
|
"xregexp": "^5.1.1"
|
|
66
75
|
},
|
|
67
76
|
"files": [
|
package/sentence-case.d.ts
CHANGED
|
@@ -1,2 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
type WordToken = {
|
|
2
|
+
type: 'word';
|
|
3
|
+
text: string;
|
|
4
|
+
shape: string;
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
sentenceStart?: boolean;
|
|
8
|
+
subSentenceStart?: boolean;
|
|
9
|
+
};
|
|
10
|
+
type PunctuationToken = {
|
|
11
|
+
type: 'punctuation';
|
|
12
|
+
text: string;
|
|
13
|
+
shape: string;
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
};
|
|
17
|
+
type WhitespaceToken = {
|
|
18
|
+
type: 'whitespace';
|
|
19
|
+
text: string;
|
|
20
|
+
shape: string;
|
|
21
|
+
};
|
|
22
|
+
type Token = WordToken | PunctuationToken | WhitespaceToken;
|
|
23
|
+
export declare function tokenize(title: string, markup?: RegExp): Token[];
|
|
24
|
+
export type Options = {
|
|
25
|
+
preserveQuoted?: boolean;
|
|
26
|
+
subSentenceCapitalization?: boolean;
|
|
27
|
+
markup?: RegExp;
|
|
28
|
+
nocase?: RegExp;
|
|
29
|
+
};
|
|
30
|
+
export declare function toSentenceCase(title: string, options?: Options): string;
|
|
31
|
+
export declare function guessSentenceCased(title: string, markup?: RegExp): boolean;
|
|
32
|
+
export {};
|
|
2
33
|
//# sourceMappingURL=sentence-case.d.ts.map
|
package/sentence-case.js
CHANGED
|
@@ -1,90 +1,203 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
2
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toSentenceCase = void 0;
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
titleCase: new RegExp(`^[${Lu}][${Ll}${N}${No}]+$`),
|
|
22
|
-
// private aint = XRegExp("^\\p{L}n't(?=$|[\\P{L}])") // isn't
|
|
23
|
-
// private word = XRegExp('^\\p{L}+([-.]\\p{L}+)*') // also match gallium-nitride as one word
|
|
24
|
-
// private and = XRegExp('^\\p{Lu}&\\p{Lu}(?=$|[\\P{L}])') // Q&A
|
|
25
|
-
subSentenceStart: new RegExp(`([.?!][\\s]+)(<[^>]+>)?([${Lu}])`, 'g'),
|
|
26
|
-
sentenceStart: new RegExp(`^(<[^>]+>)?([${Lu}])`, 'g'),
|
|
7
|
+
exports.guessSentenceCased = exports.toSentenceCase = exports.tokenize = void 0;
|
|
8
|
+
const xregexp_1 = __importDefault(require("xregexp"));
|
|
9
|
+
const one_1 = __importDefault(require("compromise/one"));
|
|
10
|
+
const unicode2latex_1 = require("unicode2latex");
|
|
11
|
+
function gather(tokens) {
|
|
12
|
+
return {
|
|
13
|
+
start: tokens[0].start,
|
|
14
|
+
end: tokens[tokens.length - 1].end,
|
|
15
|
+
text: tokens.map(c => c.text).join(''),
|
|
16
|
+
type: 'word',
|
|
17
|
+
shape: tokens.map(c => c.shape).join(''),
|
|
18
|
+
sentenceStart: !!tokens.find(t => t.type === 'word' && t.sentenceStart) || false,
|
|
19
|
+
subSentenceStart: !!tokens.find(t => t.type === 'word' && t.subSentenceStart) || false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const preposition = {
|
|
23
|
+
simple: /^(a|about|above|across|after|against|along|among|an|and|around|as|at|before|behind|below|beneath|beside|between|but|by|down|during|for|from|in|inside|into|like|near|nor|of|off|on|onto|or|out|over|so|the|through|to|towards|under|underneath|until|up|upon|with|within|without|yet)$/i,
|
|
24
|
+
complex: /^((according to)|(ahead of)|(apart from)|(as for)|(as of)|(as per)|(as regards)|(aside from)|(back to)|(because of)|(close to)|(due to)|(except for)|(far from)|(inside of)|(instead of)|(next to)|(outside of)|(owing to)|(prior to)|(pursuant to)|(regardless of)|(right of)|(subsequent to))$/i,
|
|
27
25
|
};
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
return
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
43
|
-
if (
|
|
44
|
-
return
|
|
45
|
-
|
|
26
|
+
function titleCase(s) {
|
|
27
|
+
return s.replace(/^(.)(.+)/, (match, car, cdr) => `${car}${cdr.toLowerCase()}`);
|
|
28
|
+
}
|
|
29
|
+
const Lu = (0, xregexp_1.default)('\\p{Lu}');
|
|
30
|
+
const Ll = (0, xregexp_1.default)('\\p{Ll}');
|
|
31
|
+
const connectedWord = (0, xregexp_1.default)('(^|-)\\p{Lu}\\p{Ll}*(?=-|$)', 'g');
|
|
32
|
+
const connectedInnerWord = (0, xregexp_1.default)('-\\p{Lu}\\p{Ll}*(?=-|$)', 'g');
|
|
33
|
+
const strayCC = new RegExp(`^(${unicode2latex_1.combining.regex})`);
|
|
34
|
+
function wordSC(token, succ, allCaps, subSentence) {
|
|
35
|
+
if (token.type !== 'word')
|
|
36
|
+
return token.text;
|
|
37
|
+
if (token.text.match(/^I'/))
|
|
38
|
+
return titleCase(token.text);
|
|
39
|
+
if (subSentence && token.subSentenceStart && token.text.match(/^a$/i))
|
|
40
|
+
return 'a';
|
|
41
|
+
if ((subSentence && token.subSentenceStart) || token.sentenceStart) {
|
|
42
|
+
return allCaps ? titleCase(token.text) : xregexp_1.default.replace(token.text, connectedInnerWord, match => match.toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
if (!allCaps && token.shape.match(/^[Xxd]+(-[Xxd]+)+/))
|
|
45
|
+
return xregexp_1.default.replace(token.text, connectedWord, match => match.toLowerCase());
|
|
46
|
+
if (token.text.match(/^[B-Z]$/))
|
|
47
|
+
return token.text;
|
|
48
|
+
if (!allCaps && token.shape.match(/^[-X]+$/))
|
|
49
|
+
return token.text;
|
|
50
|
+
if (succ && `${token.text} ${succ.text}`.match(preposition.complex)) {
|
|
51
|
+
succ.text = succ.text.toLowerCase();
|
|
52
|
+
return token.text.toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
if (token.text.match(preposition.simple))
|
|
55
|
+
return token.text.toLowerCase();
|
|
56
|
+
const shape = token.shape.replace(/[^Xxd]/g, '');
|
|
57
|
+
if (!allCaps && shape.match(/^[Xd-]+$/))
|
|
58
|
+
return token.text;
|
|
59
|
+
if (shape.match(/^X[xd]*(-[Xxd]*)*$/))
|
|
60
|
+
return token.text.toLowerCase();
|
|
61
|
+
if (shape.match(/^[Xd]+$/))
|
|
62
|
+
return allCaps ? token.text.toLowerCase() : token.text;
|
|
63
|
+
if (token.text.includes('.'))
|
|
64
|
+
return token.text;
|
|
65
|
+
if (shape.match(/x.*X/))
|
|
66
|
+
return token.text;
|
|
67
|
+
return token.text.toLowerCase();
|
|
68
|
+
}
|
|
69
|
+
function tokentype(token) {
|
|
70
|
+
if (token.type === 'word' && token.text.match(/^\u2060+$/))
|
|
71
|
+
return 'm';
|
|
72
|
+
if (token.type === 'word')
|
|
73
|
+
return 'w';
|
|
74
|
+
if (token.shape === '-')
|
|
75
|
+
return '-';
|
|
76
|
+
return ' ';
|
|
46
77
|
}
|
|
47
|
-
function
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
preserve.push({ pos: i + end.length + ((markup === null || markup === void 0 ? void 0 : markup.length) || 0), text: char, description: 'sub-sentence-start' });
|
|
78
|
+
function prepost(s, offset) {
|
|
79
|
+
const tokens = [];
|
|
80
|
+
for (const text of s.match(/(\s+|\S+)/g)) {
|
|
81
|
+
if (text.match(/\s/)) {
|
|
82
|
+
tokens.push({ type: 'whitespace', text, shape: text });
|
|
53
83
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
text
|
|
74
|
-
|
|
84
|
+
else if (text) {
|
|
85
|
+
tokens.push({ type: 'punctuation', text, shape: text, start: offset, end: offset + text.length - 1 });
|
|
86
|
+
}
|
|
87
|
+
offset += text.length;
|
|
88
|
+
}
|
|
89
|
+
return tokens;
|
|
90
|
+
}
|
|
91
|
+
function tokenize(title, markup) {
|
|
92
|
+
if (markup)
|
|
93
|
+
title = title.replace(markup, match => '\u2060'.repeat(match.length));
|
|
94
|
+
const doc = (0, one_1.default)(title);
|
|
95
|
+
const tokens = [];
|
|
96
|
+
for (const sentence of doc.json({ offset: true })) {
|
|
97
|
+
const words = [];
|
|
98
|
+
let sentenceStart = true;
|
|
99
|
+
let subSentenceStart = false;
|
|
100
|
+
for (const term of sentence.terms) {
|
|
101
|
+
const m = term.post.match(strayCC);
|
|
102
|
+
if (m) {
|
|
103
|
+
term.text += m[0];
|
|
104
|
+
term.post = term.post.substring(m[0].length);
|
|
105
|
+
term.offset.length += m[0].length;
|
|
106
|
+
}
|
|
107
|
+
if (term.pre)
|
|
108
|
+
words.push(...prepost(term.pre, term.offset.start - term.pre.length));
|
|
109
|
+
words.push({
|
|
110
|
+
start: term.offset.start,
|
|
111
|
+
end: term.offset.start + term.offset.length - 1,
|
|
112
|
+
text: term.text,
|
|
113
|
+
type: 'word',
|
|
114
|
+
shape: xregexp_1.default.replaceEach(term.text, [[Lu, 'X', 'all'], [Ll, 'x', 'all'], [/\d/g, 'd']]),
|
|
115
|
+
sentenceStart,
|
|
116
|
+
subSentenceStart,
|
|
75
117
|
});
|
|
76
|
-
|
|
77
|
-
|
|
118
|
+
sentenceStart = false;
|
|
119
|
+
subSentenceStart = term.post.includes(':');
|
|
120
|
+
if (term.post)
|
|
121
|
+
words.push(...prepost(term.post, term.offset.start + term.offset.length));
|
|
122
|
+
}
|
|
123
|
+
subSentenceStart = true;
|
|
124
|
+
while (words.length) {
|
|
125
|
+
if (words[0].type === 'word' && words[0].text.includes('/')) {
|
|
126
|
+
const multiple = words.shift();
|
|
127
|
+
let start = multiple.start;
|
|
128
|
+
words.unshift(...multiple.text.match(/([/]|[^/]+)/g).map((w) => {
|
|
129
|
+
const started = start;
|
|
130
|
+
start += w.length;
|
|
131
|
+
const ss = w === '/' ? [false, false] : [multiple.sentenceStart, multiple.subSentenceStart];
|
|
132
|
+
if (w !== '/')
|
|
133
|
+
multiple.sentenceStart = multiple.subSentenceStart = false;
|
|
134
|
+
return {
|
|
135
|
+
start: started,
|
|
136
|
+
end: start - 1,
|
|
137
|
+
text: w,
|
|
138
|
+
type: w === '/' ? 'punctuation' : 'word',
|
|
139
|
+
shape: xregexp_1.default.replaceEach(w, [[Lu, 'X', 'all'], [Ll, 'x', 'all'], [/\d/g, 'd']]),
|
|
140
|
+
sentenceStart: ss[0],
|
|
141
|
+
subSentenceStart: ss[1],
|
|
142
|
+
};
|
|
143
|
+
}));
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const type = words.map(tokentype).join('');
|
|
147
|
+
let m;
|
|
148
|
+
if (m = type.match(/^m?[wc]m?(-m?[wc]m?)+/)) {
|
|
149
|
+
tokens.push(gather(words.splice(0, m[0].length)));
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const word = words.shift();
|
|
153
|
+
if (word.type !== 'whitespace')
|
|
154
|
+
tokens.push(word);
|
|
155
|
+
}
|
|
78
156
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
157
|
+
return tokens;
|
|
158
|
+
}
|
|
159
|
+
exports.tokenize = tokenize;
|
|
160
|
+
function toSentenceCase(title, options = {}) {
|
|
161
|
+
options = Object.assign({ preserveQuoted: true, subSentenceCapitalization: true }, options);
|
|
162
|
+
title = title.normalize('NFC'); // https://github.com/winkjs/wink-nlp/issues/134
|
|
163
|
+
const allCaps = title === title.toUpperCase();
|
|
164
|
+
if (allCaps && !title.match(/\s/))
|
|
165
|
+
return title;
|
|
166
|
+
let sentenceCased = title;
|
|
167
|
+
const tokens = tokenize(title, options.markup);
|
|
168
|
+
tokens.forEach((token, i) => {
|
|
169
|
+
if (token.type !== 'whitespace') {
|
|
170
|
+
sentenceCased = sentenceCased.substring(0, token.start) + wordSC(token, tokens[i + 1], allCaps, options.subSentenceCapitalization) + sentenceCased.substring(token.end + 1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
if (options.markup) {
|
|
174
|
+
for (const match of title.matchAll(options.markup)) {
|
|
175
|
+
sentenceCased = sentenceCased.substring(0, match.index) + match[0] + sentenceCased.substring(match.index + match[0].length);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (options.nocase) {
|
|
179
|
+
sentenceCased = sentenceCased
|
|
180
|
+
.replace(options.nocase, (match, tag, offset) => title.substring(offset, offset + match.length));
|
|
86
181
|
}
|
|
87
|
-
|
|
182
|
+
if (options.preserveQuoted) {
|
|
183
|
+
for (const q of [/“.*?”/g, /‘.*?’/g, /".*?"/g]) {
|
|
184
|
+
sentenceCased = sentenceCased.replace(q, (match, offset) => title.substring(offset, offset + match.length));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return sentenceCased;
|
|
88
188
|
}
|
|
89
189
|
exports.toSentenceCase = toSentenceCase;
|
|
190
|
+
function guessSentenceCased(title, markup = /<\/?(?:i|b|sup|sub|ncx?)>/g) {
|
|
191
|
+
const noMarkup = title.replace(markup, '');
|
|
192
|
+
if (noMarkup === noMarkup.toUpperCase())
|
|
193
|
+
return false;
|
|
194
|
+
if (noMarkup === noMarkup.toLowerCase())
|
|
195
|
+
return false;
|
|
196
|
+
const words = tokenize(title, markup).filter(token => token.type === 'word' && token.shape.match(/x/i));
|
|
197
|
+
if (!words.length)
|
|
198
|
+
return true;
|
|
199
|
+
const titleCased = words.filter(word => word.shape.match(/^X+$|^X.*x|[^Xx]+-X/));
|
|
200
|
+
return (titleCased.length / words.length) < 0.5; // eslint-disable-line no-magic-numbers
|
|
201
|
+
}
|
|
202
|
+
exports.guessSentenceCased = guessSentenceCased;
|
|
90
203
|
//# sourceMappingURL=sentence-case.js.map
|