@retorquere/bibtex-parser 8.0.12 → 8.0.13

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/index.js CHANGED
@@ -57,6 +57,7 @@ const JabRef = __importStar(require("./jabref"));
57
57
  var sentence_case_1 = require("./sentence-case");
58
58
  Object.defineProperty(exports, "toSentenceCase", { enumerable: true, get: function () { return sentence_case_1.toSentenceCase; } });
59
59
  const sentence_case_2 = require("./sentence-case");
60
+ const tokenizer_1 = require("./tokenizer");
60
61
  const crossref_json_1 = __importDefault(require("./crossref.json"));
61
62
  const fields_json_1 = __importDefault(require("./fields.json"));
62
63
  const unabbreviations = require('./unabbrev.json');
@@ -753,14 +754,13 @@ class BibTeXParser {
753
754
  if (FieldAction.parseInt.includes(field) && value.trim().match(/^-?\d+$/))
754
755
  return `${parseInt(value)}`;
755
756
  if (mode === 'title' && sentenceCase) {
756
- if (!this.options.sentenceCase.guess || !(0, sentence_case_2.guessSentenceCased)(value, /\x0E\/?([a-z]+)\x0F/ig)) {
757
- value = (0, sentence_case_2.toSentenceCase)(value, {
758
- preserveQuoted: this.options.sentenceCase.preserveQuoted,
759
- subSentenceCapitalization: this.options.sentenceCase.subSentence,
760
- markup: /\x0E\/?([a-z]+)\x0F/ig,
761
- nocase: /\x0E(ncx?)\x0F.*?\x0E\/\1\x0F/ig,
762
- });
763
- }
757
+ value = (0, sentence_case_2.toSentenceCase)(value, {
758
+ preserveQuoted: this.options.sentenceCase.preserveQuoted,
759
+ subSentenceCapitalization: this.options.sentenceCase.subSentence,
760
+ markup: /\x0E\/?([a-z]+)\x0F/ig,
761
+ nocase: /\x0E(ncx?)\x0F.*?\x0E\/\1\x0F/ig,
762
+ guess: this.options.sentenceCase.guess && !value.match(/\x0E(nc)\x0F/),
763
+ });
764
764
  let cancel = (_match, stripped) => stripped;
765
765
  switch (this.options.caseProtection) {
766
766
  case 'strict':
@@ -768,7 +768,7 @@ class BibTeXParser {
768
768
  break;
769
769
  case 'as-needed':
770
770
  cancel = (match, stripped) => {
771
- const words = (0, sentence_case_2.tokenize)(stripped, /\x0E\/?([a-z]+)\x0F/ig);
771
+ const words = (0, tokenizer_1.tokenize)(stripped, /\x0E\/?([a-z]+)\x0F/ig);
772
772
  return words.find(w => w.shape.match(/^(?!.*X).*x.*$/)) ? match : this.wrap(stripped, 'ncx');
773
773
  };
774
774
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retorquere/bibtex-parser",
3
- "version": "8.0.12",
3
+ "version": "8.0.13",
4
4
  "description": "BibTeX parser",
5
5
  "main": "index.js",
6
6
  "typings": "./index.d.ts",
@@ -23,7 +23,7 @@
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
  "predocs": "npm start",
25
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"
26
+ "ncu": "./build/submodules.py && ./build/crossref.py && ncu -u -x eslint && npm i && git add package.json package-lock.json && npm audit fix && git commit -m ncu"
27
27
  },
28
28
  "repository": {
29
29
  "type": "git",
@@ -35,9 +35,9 @@
35
35
  "author": "Emiliano Heyns",
36
36
  "license": "ISC",
37
37
  "devDependencies": {
38
- "@types/node": "^20.12.2",
39
- "@typescript-eslint/eslint-plugin": "^7.4.0",
40
- "@typescript-eslint/parser": "^7.4.0",
38
+ "@types/node": "^20.12.7",
39
+ "@typescript-eslint/eslint-plugin": "^7.6.0",
40
+ "@typescript-eslint/parser": "^7.6.0",
41
41
  "command-line-args": "^5.2.1",
42
42
  "csv": "^6.3.8",
43
43
  "digital-tree": "^2.0.4",
@@ -54,15 +54,15 @@
54
54
  "tap": "^18.7.2",
55
55
  "ts-node": "^10.9.2",
56
56
  "tslib": "^2.6.2",
57
- "typedoc": "^0.25.12",
58
- "typescript": "^5.4.3"
57
+ "typedoc": "^0.25.13",
58
+ "typescript": "^5.4.5"
59
59
  },
60
60
  "dependencies": {
61
61
  "@unified-latex/unified-latex-util-pegjs": "^1.7.1",
62
62
  "@unified-latex/unified-latex-util-print-raw": "^1.7.1",
63
63
  "@unified-latex/unified-latex-util-replace": "^1.7.1",
64
64
  "@unified-latex/unified-latex-util-visit": "^1.7.1",
65
- "compromise": "^14.13.0",
65
+ "moo": "^0.5.2",
66
66
  "unicode2latex": "^6.0.4",
67
67
  "wink-eng-lite-web-model": "^1.6.0",
68
68
  "wink-nlp": "^2.2.0",
@@ -84,7 +84,8 @@
84
84
  "sentence-case.d.ts",
85
85
  "fields.json",
86
86
  "strings.bib",
87
- "unabbrev.json"
87
+ "unabbrev.json",
88
+ "prepositions.json"
88
89
  ],
89
90
  "publishConfig": {
90
91
  "access": "public"
@@ -0,0 +1,88 @@
1
+ [
2
+ "a",
3
+ "about",
4
+ "above",
5
+ "according to",
6
+ "across",
7
+ "after",
8
+ "against",
9
+ "ahead of",
10
+ "along",
11
+ "although",
12
+ "among",
13
+ "an",
14
+ "and",
15
+ "apart from",
16
+ "around",
17
+ "as for",
18
+ "as if",
19
+ "as long as",
20
+ "as of",
21
+ "as per",
22
+ "as regards",
23
+ "as soon as",
24
+ "as",
25
+ "aside from",
26
+ "at",
27
+ "back to",
28
+ "because of",
29
+ "because",
30
+ "before",
31
+ "behind",
32
+ "below",
33
+ "beneath",
34
+ "beside",
35
+ "between",
36
+ "but",
37
+ "by",
38
+ "close to",
39
+ "down",
40
+ "due to",
41
+ "during",
42
+ "except for",
43
+ "far from",
44
+ "for",
45
+ "from",
46
+ "if",
47
+ "in",
48
+ "inside of",
49
+ "inside",
50
+ "instead of",
51
+ "into",
52
+ "like",
53
+ "near",
54
+ "next to",
55
+ "nor",
56
+ "of",
57
+ "off",
58
+ "on",
59
+ "onto",
60
+ "or",
61
+ "out",
62
+ "outside of",
63
+ "over",
64
+ "owing to",
65
+ "prior to",
66
+ "pursuant to",
67
+ "regardless of",
68
+ "right of",
69
+ "since",
70
+ "so",
71
+ "subsequent to",
72
+ "the",
73
+ "through",
74
+ "to",
75
+ "towards",
76
+ "under",
77
+ "underneath",
78
+ "unless",
79
+ "until",
80
+ "up",
81
+ "upon",
82
+ "when",
83
+ "while",
84
+ "with",
85
+ "within",
86
+ "without",
87
+ "yet"
88
+ ]
@@ -1,33 +1,9 @@
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
1
  export type Options = {
25
2
  preserveQuoted?: boolean;
26
3
  subSentenceCapitalization?: boolean;
27
4
  markup?: RegExp;
28
5
  nocase?: RegExp;
6
+ guess?: boolean;
29
7
  };
30
8
  export declare function toSentenceCase(title: string, options?: Options): string;
31
- export declare function guessSentenceCased(title: string, markup?: RegExp): boolean;
32
- export {};
33
9
  //# sourceMappingURL=sentence-case.d.ts.map
package/sentence-case.js CHANGED
@@ -4,34 +4,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.guessSentenceCased = exports.toSentenceCase = exports.tokenize = void 0;
7
+ exports.toSentenceCase = void 0;
8
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,
25
- };
9
+ const tokenizer_1 = require("./tokenizer");
10
+ // eslint-disable-next-line no-magic-numbers
11
+ // const show = (obj: any): string => JSON.stringify(obj, null, 2).replace(/[\u007F-\uFFFF]/g, chr => `\\u${(`0000${chr.charCodeAt(0).toString(16)}`).substr(-4)}`)
26
12
  function titleCase(s) {
27
13
  return s.replace(/^(.)(.+)/, (match, car, cdr) => `${car}${cdr.toLowerCase()}`);
28
14
  }
29
- const Lu = (0, xregexp_1.default)('\\p{Lu}');
30
- const Ll = (0, xregexp_1.default)('\\p{Ll}');
31
15
  const connectedWord = (0, xregexp_1.default)('(^|-)\\p{Lu}\\p{Ll}*(?=-|$)', 'g');
32
16
  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) {
17
+ function wordSC(token, allCaps, subSentence) {
35
18
  if (token.type !== 'word')
36
19
  return token.text;
37
20
  if (token.text.match(/^I'/))
@@ -41,18 +24,16 @@ function wordSC(token, succ, allCaps, subSentence) {
41
24
  if ((subSentence && token.subSentenceStart) || token.sentenceStart) {
42
25
  return allCaps ? titleCase(token.text) : xregexp_1.default.replace(token.text, connectedInnerWord, match => match.toLowerCase());
43
26
  }
27
+ if (token.subtype === 'preposition')
28
+ return token.text.toLowerCase();
29
+ if (token.subtype === 'acronym')
30
+ return token.text;
44
31
  if (!allCaps && token.shape.match(/^[Xxd]+(-[Xxd]+)+/))
45
32
  return xregexp_1.default.replace(token.text, connectedWord, match => match.toLowerCase());
46
33
  if (token.text.match(/^[B-Z]$/))
47
34
  return token.text;
48
35
  if (!allCaps && token.shape.match(/^[-X]+$/))
49
36
  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
37
  const shape = token.shape.replace(/[^Xxd]/g, '');
57
38
  if (!allCaps && shape.match(/^[Xd-]+$/))
58
39
  return token.text;
@@ -66,110 +47,48 @@ function wordSC(token, succ, allCaps, subSentence) {
66
47
  return token.text;
67
48
  return token.text.toLowerCase();
68
49
  }
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 ' ';
77
- }
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 });
83
- }
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;
50
+ function toSentenceCase(title, options = {}) {
51
+ options = Object.assign({ preserveQuoted: true, subSentenceCapitalization: true }, options);
52
+ const allCaps = title === title.toUpperCase();
53
+ const allLower = title === title.toLowerCase();
54
+ if (options.guess && !allCaps && !allLower) {
55
+ let $title = title;
56
+ if (options.nocase)
57
+ $title = $title.replace(options.nocase, match => match.match(/\s/) ? ' ' : '');
58
+ if (options.markup)
59
+ $title = $title.replace(options.markup, '');
60
+ if (options.preserveQuoted) {
61
+ for (const q of [/“.*?”/g, /‘.*?’/g, /".*?"/g]) {
62
+ $title = $title.replace(q, '');
106
63
  }
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,
117
- });
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
64
  }
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;
65
+ let sc = 0;
66
+ const words = (0, tokenizer_1.tokenize)($title);
67
+ const minsc = 2;
68
+ words.forEach((token, i) => {
69
+ // reset for each subsentence
70
+ if (options.subSentenceCapitalization && token.type === 'punctuation' && token.subtype === 'end' && sc < minsc) {
71
+ sc = 0;
145
72
  }
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;
73
+ if (token.type === 'word' && token.text.length > 1 && !token.subtype.match(/preposition|acronym|ordinal/) && token.shape.match(/^[^X]*x[^X]*$/)) {
74
+ if (i === 0 || words[i - 1].type === 'whitespace') {
75
+ sc += 1;
76
+ }
151
77
  }
152
- const word = words.shift();
153
- if (word.type !== 'whitespace')
154
- tokens.push(word);
155
- }
78
+ });
79
+ if (sc >= minsc)
80
+ return title;
156
81
  }
157
- return tokens;
158
- }
159
- exports.tokenize = tokenize;
160
- function toSentenceCase(title, options = {}) {
161
- options = Object.assign({ preserveQuoted: true, subSentenceCapitalization: true }, options);
162
82
  title = title.normalize('NFC'); // https://github.com/winkjs/wink-nlp/issues/134
163
- const allCaps = title === title.toUpperCase();
164
83
  if (allCaps && !title.match(/\s/))
165
84
  return title;
166
85
  let sentenceCased = title;
167
- const tokens = tokenize(title, options.markup);
168
- tokens.forEach((token, i) => {
86
+ const tokens = (0, tokenizer_1.tokenize)(title, options.markup);
87
+ for (const token of tokens) {
169
88
  if (token.type !== 'whitespace') {
170
- sentenceCased = sentenceCased.substring(0, token.start) + wordSC(token, tokens[i + 1], allCaps, options.subSentenceCapitalization) + sentenceCased.substring(token.end + 1);
89
+ sentenceCased = sentenceCased.substring(0, token.start) + wordSC(token, allCaps, options.subSentenceCapitalization) + sentenceCased.substring(token.end + 1);
171
90
  }
172
- });
91
+ }
173
92
  if (options.markup) {
174
93
  for (const match of title.matchAll(options.markup)) {
175
94
  sentenceCased = sentenceCased.substring(0, match.index) + match[0] + sentenceCased.substring(match.index + match[0].length);
@@ -187,17 +106,4 @@ function toSentenceCase(title, options = {}) {
187
106
  return sentenceCased;
188
107
  }
189
108
  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;
203
109
  //# sourceMappingURL=sentence-case.js.map