@retorquere/bibtex-parser 6.4.18 → 7.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/index.d.ts CHANGED
@@ -205,6 +205,10 @@ export interface ParserOptions {
205
205
  * If you pass an empty array, or `false`, no sentence casing will be applied (even when there's no language field).
206
206
  */
207
207
  sentenceCase?: string[] | boolean;
208
+ /**
209
+ * If you have sentence-casing on, you can independently choose whether quoted titles within a title are preserved as-is (true) or also sentence-cased(false)
210
+ */
211
+ sentenceCasePreserveQuoted?: boolean;
208
212
  /**
209
213
  * Some bibtex has titles in sentence case, or all-uppercase. If this is on, and there is a field that would normally have sentence-casing applied in which more words are all-`X`case
210
214
  * (where `X` is either lower or upper) than mixed-case, it is assumed that you want them this way, and no sentence-casing will be applied to that field
package/index.js CHANGED
@@ -1228,6 +1228,12 @@ class Parser {
1228
1228
  }
1229
1229
  return parsed;
1230
1230
  }
1231
+ restore(text, orig, preserve) {
1232
+ for (const { start, end } of preserve) {
1233
+ text = text.substring(0, start) + orig.substring(start, end) + text.substring(end);
1234
+ }
1235
+ return text;
1236
+ }
1231
1237
  convert_entry(node) {
1232
1238
  this.entry = {
1233
1239
  key: node.id,
@@ -1357,7 +1363,7 @@ class Parser {
1357
1363
  convertToSentenceCase(text) {
1358
1364
  if (!this.field.preserveRanges)
1359
1365
  return text;
1360
- const sentenceCased = (0, sentence_case_2.restore)((0, sentence_case_2.toSentenceCase)(text, true), text, this.field.preserveRanges);
1366
+ const sentenceCased = this.restore((0, sentence_case_2.toSentenceCase)(text, this.options.sentenceCasePreserveQuoted || false), text, this.field.preserveRanges);
1361
1367
  if (text !== sentenceCased)
1362
1368
  this.entry.sentenceCased = true;
1363
1369
  return sentenceCased;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retorquere/bibtex-parser",
3
- "version": "6.4.18",
3
+ "version": "7.0.0",
4
4
  "description": "BibTeX parser",
5
5
  "main": "index.js",
6
6
  "typings": "./index.d.ts",
@@ -8,6 +8,7 @@
8
8
  "lint": "eslint . --ext .ts --cache --cache-location .eslintcache/",
9
9
  "prestart": "npm run lint && peggy grammar.peggy",
10
10
  "start": "tsc -d",
11
+ "tsc": "tsc -d",
11
12
  "poststart": "npm run docs",
12
13
  "pretest": "npm start",
13
14
  "test": "tap test/tap --no-timeout --no-coverage",
@@ -1,8 +1,2 @@
1
- export type TextRange = {
2
- start: number;
3
- end: number;
4
- description?: string;
5
- };
6
- export declare function restore(text: string, orig: string, preserve: TextRange[]): string;
7
- export declare function toSentenceCase(text: string, ignoreHTML?: boolean): string;
1
+ export declare function toSentenceCase(sentence: string, preserveQuoted?: boolean): string;
8
2
  //# sourceMappingURL=sentence-case.d.ts.map
package/sentence-case.js CHANGED
@@ -1,96 +1,85 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.toSentenceCase = exports.restore = void 0;
3
+ exports.toSentenceCase = void 0;
7
4
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
8
- const xregexp_1 = __importDefault(require("xregexp"));
9
- function restore(text, orig, preserve) {
10
- for (const { start, end } of preserve) {
11
- text = text.substring(0, start) + orig.substring(start, end) + text.substring(end);
12
- }
13
- return text;
5
+ const categories = require("xregexp/tools/output/categories");
6
+ const cat = name => categories.find(c => c.name === name).bmp;
7
+ const L = cat('L');
8
+ const Lu = cat('Lu');
9
+ const Ll = cat('Ll');
10
+ const N = cat('N');
11
+ const No = cat('No');
12
+ const Pc = cat('Pc');
13
+ const re = {
14
+ acronym: new RegExp(`([${Lu}][.])+$`),
15
+ innerCaps: new RegExp(`.[${Lu}]`),
16
+ ident: new RegExp(`^[$p{L}]+[${N}${No}][${L}${N}${No}]*$`),
17
+ allCaps: new RegExp(`^[${Lu}${N}${No}]+$`),
18
+ skipWords: /^(but|or|yet|so|for|and|nor|a|an|the|at|by|from|in|into|of|on|to|with|updown|as)$/i,
19
+ // chemElements: /^(H|He|Li|Be|B|C|N|O|F|Ne|Na|Mg|Al|Si|P|S|Cl|Ar|K|Ca|Sc|Ti|V|Cr|Mn|Fe|Co|Ni|Cu|Zn|Ga|Ge|As|Se|Br|Kr|Rb|Sr|Y|Zr|Nb|Mo|Tc|Ru|Rh|Pb|Ag|Cd|In|Sn|Sb|Te|I|Xe|Cs|Ba|La|Hf|Ta|W|Re|Os|Ir|Pt|Au|Hg|Tl|Pb|Bi|Po|At|Rn|Fr|Ra|Ac|Rf|Db|Sg|Bh|Hs|Mt|Ds|Rg|Cn|Nh|Fl|Mc|Lv|Ts|Og|La|Ce|Pr|Nd|Pm|Sm|Eu|Gd|Tb|Dy|Ho|Er|Tm|Yb|Lu|Ac|Th|Pa|U|Np|Pu|Am|Cm|Bk|Cf|Es|Fm|Md|No|Lr)$/,
20
+ words: new RegExp(`([\uFFFD${L}${N}${No}]+([\uFFFD${Pc}${L}${N}${No}]*))|(\\s([\uFFFD${Lu}]+[.]){2,})?`, 'g'),
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
+ };
26
+ function lowercase(word) {
27
+ if (!word)
28
+ return word;
29
+ const unmasked = word.replace(/\uFFFD/g, '');
30
+ if (unmasked.match(re.skipWords))
31
+ return word.toLowerCase();
32
+ if (unmasked.match(re.titleCase))
33
+ return word.toLowerCase();
34
+ // if (unmasked.match(re.chemElements)) return word
35
+ if (unmasked.length === 1)
36
+ return unmasked === 'A' ? word.toLowerCase() : word;
37
+ if (unmasked.match(re.innerCaps))
38
+ return word;
39
+ if (unmasked.match(re.ident) || unmasked.match(re.allCaps))
40
+ return word;
41
+ return word.toLowerCase();
14
42
  }
15
- exports.restore = restore;
16
- class SentenceCaser {
17
- constructor() {
18
- this.acronym = (0, xregexp_1.default)('^(\\p{Lu}[.])+(?=$|[\\P{L}])');
19
- this.quoted = (0, xregexp_1.default)('^"[^"]+"(?=$|[\\P{L}])');
20
- this.innerCaps = (0, xregexp_1.default)('\\p{Ll}\\p{Lu}');
21
- this.allCaps = (0, xregexp_1.default)('^\\p{Lu}+$');
22
- this.aint = (0, xregexp_1.default)("^\\p{L}n't(?=$|[\\P{L}])"); // isn't
23
- this.word = (0, xregexp_1.default)('^\\p{L}+(-\\p{L}+)*'); // also match gallium-nitride as one word
24
- this.and = (0, xregexp_1.default)('^\\p{Lu}&\\p{Lu}(?=$|[\\P{L}])'); // Q&A
25
- }
26
- convert(text, ignoreHTML = false) {
27
- this.input = text;
28
- this.result = '';
29
- this.sentenceStart = true;
30
- const preserve = [];
31
- if (ignoreHTML) {
32
- this.input.replace(/[^<>]<[^>]+>/g, (match, i) => {
33
- preserve.push({ start: i + 1, end: i + match.length });
34
- // replace markup by the preceding char
35
- return match[0].repeat(match.length);
36
- });
37
- this.input = this.input.replace(/<[^>]+>[^<>]/g, (match, i) => {
38
- preserve.push({ start: i, end: i + match.length - 1 });
39
- // replace markup by the following char
40
- return match[match.length - 1].repeat(match.length);
41
- });
43
+ function toSentenceCase(sentence, preserveQuoted = false) {
44
+ const preserve = [];
45
+ sentence.replace(/([.?!][\s]+)(<[^>]+>)?([A-Z])/g, (match, end, markup, char, i) => {
46
+ if (!sentence.substring(0, i + 1).match(re.acronym)) {
47
+ preserve.push({ pos: i + end.length + ((markup === null || markup === void 0 ? void 0 : markup.length) || 0), text: char, description: 'sub-sentence-start' });
42
48
  }
43
- this.input = this.input.replace(/[;:]\s+A\s/g, match => match.toLowerCase());
44
- this.input = this.input.replace(/[–—]\s*A\s/g, match => match.toLowerCase());
45
- let m;
46
- while (this.input) {
47
- if (m = xregexp_1.default.exec(this.input, this.quoted)) { // "Hello There"
48
- this.add(m[0], undefined, true);
49
- }
50
- else if (m = xregexp_1.default.exec(this.input, this.acronym)) { // U.S.
51
- this.add(m[0], undefined, true);
52
- }
53
- else if (m = xregexp_1.default.exec(this.input, this.aint)) { // isn't
54
- this.add(m[0], undefined, false);
55
- }
56
- else if (m = xregexp_1.default.exec(this.input, this.word)) {
57
- this.add(m[0], '-', false);
58
- }
59
- else if (m = xregexp_1.default.exec(this.input, this.and)) {
60
- this.add(m[0], undefined, true);
61
- }
62
- else {
63
- this.add(this.input[0], undefined, false);
64
- }
65
- }
66
- return restore(this.result, text, preserve);
49
+ return '';
50
+ });
51
+ sentence.replace(/^(<[^>]+>)?([A-Z])/, (match, markup, char) => {
52
+ preserve.push({ pos: ((markup === null || markup === void 0 ? void 0 : markup.length) || 0), text: char, description: 'sentence-start' });
53
+ return '';
54
+ });
55
+ sentence.replace(/<span class="nocase">.*?<\/span>|<nc>.*?<\/nc>/gi, (text, pos) => {
56
+ preserve.push({ pos, text, description: 'nocase' });
57
+ return '';
58
+ });
59
+ let masked = sentence.replace(/<[^>]+>/g, (text, pos) => {
60
+ preserve.push({ pos, text, description: 'markup' });
61
+ return '\uFFFD'.repeat(text.length);
62
+ });
63
+ // last because we're potentially modifying the original
64
+ for (const q of [/(“)(.*?)”/g, /(‘)(.*?)’/g, /(["])(.*?)\1/g]) {
65
+ sentence.replace(q, (text, quote, quoted, pos) => {
66
+ preserve.push({
67
+ pos: pos + (preserveQuoted ? 0 : quote.length),
68
+ text: preserveQuoted ? text : toSentenceCase(quoted, preserveQuoted),
69
+ description: `quoted with ${q}`,
70
+ });
71
+ return '';
72
+ });
67
73
  }
68
- add(word, splitter, keep) {
69
- if (splitter) {
70
- word = word.split(splitter).map((part, i) => {
71
- if ((keep || this.sentenceStart) && i === 0)
72
- return part;
73
- if (xregexp_1.default.exec(part, this.innerCaps))
74
- return part;
75
- if (xregexp_1.default.exec(part, this.allCaps))
76
- return part;
77
- return part.toLowerCase();
78
- }).join(splitter);
79
- }
80
- else {
81
- if (!keep)
82
- word = word.toLowerCase();
83
- }
84
- this.result += word;
85
- this.input = this.input.substr(word.length);
86
- if (!word.match(/^\s+$/)) {
87
- this.sentenceStart = !!word.match(/^[.?!]$/) || (word.length === 2 && word[1] === '.'); // Vitamin A. Vitamin B.
88
- }
74
+ masked = masked
75
+ .replace(/[;:]\uFFFD*\s+\uFFFD*A\s/g, match => match.toLowerCase())
76
+ .replace(/[–—]\uFFFD*\s*\uFFFD*A\s/g, match => match.toLowerCase())
77
+ .replace(re.words, word => lowercase(word));
78
+ sentence = masked;
79
+ for (const { pos, text } of preserve) {
80
+ sentence = sentence.substring(0, pos) + text + sentence.substring(pos + text.length);
89
81
  }
90
- }
91
- const sentenceCaser = new SentenceCaser;
92
- function toSentenceCase(text, ignoreHTML = false) {
93
- return sentenceCaser.convert(text, ignoreHTML);
82
+ return sentence;
94
83
  }
95
84
  exports.toSentenceCase = toSentenceCase;
96
85
  //# sourceMappingURL=sentence-case.js.map