@retorquere/bibtex-parser 7.0.16 → 8.0.1

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
@@ -22,98 +22,61 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
26
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
27
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
28
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
29
+ return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
30
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
31
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
32
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
33
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
34
+ function fulfill(value) { resume("next", value); }
35
+ function reject(value) { resume("throw", value); }
36
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
37
+ };
38
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
39
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
40
+ var m = o[Symbol.asyncIterator], i;
41
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
42
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
43
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
44
+ };
25
45
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
46
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
47
  };
28
48
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.jabref = exports.chunker = exports.promises = exports.ast = exports.parse = exports.fields = exports.toSentenceCase = void 0;
30
- /* eslint-disable @typescript-eslint/no-unsafe-argument */
31
- const bibtex = __importStar(require("./grammar"));
32
- const chunker = __importStar(require("./chunker"));
33
- exports.chunker = chunker;
34
- const jabref = __importStar(require("./jabref"));
35
- exports.jabref = jabref;
36
- const jabref_1 = require("./jabref");
49
+ exports.parseAsync = exports.parse = exports.FieldMode = exports.toSentenceCase = void 0;
50
+ const unified_latex_util_replace_1 = require("@unified-latex/unified-latex-util-replace");
51
+ const unified_latex_util_pegjs_1 = require("@unified-latex/unified-latex-util-pegjs");
52
+ const unified_latex_util_visit_1 = require("@unified-latex/unified-latex-util-visit");
53
+ const unified_latex_util_print_raw_1 = require("@unified-latex/unified-latex-util-print-raw");
37
54
  const unicode2latex_1 = require("unicode2latex");
38
- const crossref_json_1 = __importDefault(require("./crossref.json"));
39
- const fields_json_1 = __importDefault(require("./fields.json"));
55
+ const bibtex = __importStar(require("./verbatim"));
56
+ const JabRef = __importStar(require("./jabref"));
40
57
  var sentence_case_1 = require("./sentence-case");
41
58
  Object.defineProperty(exports, "toSentenceCase", { enumerable: true, get: function () { return sentence_case_1.toSentenceCase; } });
42
59
  const sentence_case_2 = require("./sentence-case");
43
- class ParserError extends Error {
44
- constructor(message, node) {
45
- super(message); // 'Error' breaks prototype chain here
46
- Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
47
- this.name = this.constructor.name;
48
- this.node = node;
49
- }
60
+ const crossref_json_1 = __importDefault(require("./crossref.json"));
61
+ const fields_json_1 = __importDefault(require("./fields.json"));
62
+ const unabbreviations = require('./unabbrev.json');
63
+ function latexMode(node) {
64
+ return node._renderInfo.mode;
50
65
  }
51
- class TeXError extends Error {
52
- constructor(message, node, text) {
53
- super(message); // 'Error' breaks prototype chain here
54
- Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
55
- this.name = this.constructor.name;
56
- this.node = node;
57
- this.text = text;
58
- }
66
+ function latex2unicode(tex, node) {
67
+ const text = unicode2latex_1.latex2unicode[tex];
68
+ if (typeof text === 'string')
69
+ return text;
70
+ return text && text[latexMode(node)];
59
71
  }
60
- function charCat(charcats, select) {
61
- return charcats.filter((cat) => select.includes(cat.alias)).map((cat) => cat.bmp).join('');
72
+ const open = {};
73
+ const close = {};
74
+ for (const tag of ['i', 'b', 'sc', 'nc', 'ncx', 'br', 'p', 'li', 'code']) {
75
+ open[tag] = `\x0E${tag}\x0F`;
76
+ close[tag] = `\x0E/${tag}\x0F`;
62
77
  }
63
- const charCategories = require("xregexp/tools/output/categories");
64
- const charClass = {
65
- Lu: charCat(charCategories, ['Uppercase_Letter', 'Titlecase_Letter']),
66
- Ll: charCat(charCategories, ['Lowercase_Letter']),
67
- LnotLu: charCat(charCategories, ['Lowercase_Letter', 'Modifier_Letter', 'Other_Letter', 'Nonspacing_Mark', 'Spacing_Mark', 'Decimal_Number', 'Letter_Number']),
68
- P: charCat(charCategories, ['Punctuation']),
69
- L: charCat(charCategories, ['Letter']),
70
- N: charCat(charCategories, ['Decimal_Number', 'Letter_Number']),
71
- AlphaNum: charCat(charCategories, ['Letter', 'Decimal_Number', 'Letter_Number']),
72
- LC: charCat(charCategories, ['Cased_Letter']),
73
- };
74
- const marker = {
75
- and: '\u0001',
76
- comma: '\u0002',
77
- space: '\u0003',
78
- literal: '\u0004',
79
- markup: '\u0005',
80
- re: {
81
- and: /./,
82
- comma: /./,
83
- space: /./,
84
- literal: /./,
85
- literalName: /./,
86
- },
87
- clean(s) {
88
- return s.replace(marker.re.space, ' ').replace(marker.re.comma, ', ').replace(marker.re.literal, '');
89
- },
90
- };
91
- marker.re = {
92
- and: new RegExp(marker.and, 'g'),
93
- comma: new RegExp(marker.comma, 'g'),
94
- space: new RegExp(marker.space, 'g'),
95
- literal: new RegExp(marker.literal, 'g'),
96
- literalName: new RegExp(`^${marker.literal}([^${marker.literal}]*)${marker.literal}$`),
97
- };
98
- const preserveCase = {
99
- leadingCap: new RegExp(`^[${charClass.Lu}][${charClass.LnotLu}]+[${charClass.P}]?$`),
100
- allCaps: new RegExp(`^[${charClass.Lu}${charClass.N}]{2,}$`),
101
- allLower: new RegExp(`^[${charClass.Ll}${charClass.N}]{2,}$`),
102
- joined: new RegExp(`^[${charClass.Lu}][${charClass.LnotLu}]*([-+][${charClass.L}${charClass.N}]+)*[${charClass.P}]*$`),
103
- hasUpper: new RegExp(`[${charClass.Lu}]`),
104
- hasLower: new RegExp(`[${charClass.Ll}]`),
105
- isNumber: /^[0-9]+$/,
106
- hasAlpha: new RegExp(`[${charClass.L}]`),
107
- hasAlphaNum: new RegExp(`[${charClass.AlphaNum}]`),
108
- notAlphaNum: new RegExp(`[^${charClass.AlphaNum}]`, 'g'),
109
- sentenceStart: new RegExp(`(^|([\u2014:?!.]\\s+))[${charClass.Lu}]`, 'g'),
110
- markup: /<\/?span[^>]*>/g,
111
- acronym: new RegExp(`.*\\.${marker.markup}*[${charClass.Lu}]${marker.markup}*\\.$`),
112
- notCaseSensitive: new RegExp(`[^${charClass.LC}]`),
113
- isCaseSensitive: new RegExp(`[${charClass.LC}]`),
114
- quoted: /("[^"]+")|(“[^“]+“)/g,
115
- };
116
- exports.fields = {
78
+ const collapsable = /\x0E\/([a-z]+)\x0F(\s*)\x0E\1\x0F/ig;
79
+ exports.FieldMode = {
117
80
  creator: [
118
81
  'author',
119
82
  'bookauthor',
@@ -130,18 +93,15 @@ exports.fields = {
130
93
  ],
131
94
  title: [
132
95
  'title',
96
+ 'subtitle',
133
97
  'series',
134
98
  'shorttitle',
135
99
  'booktitle',
136
- 'type',
100
+ // 'type',
137
101
  'origtitle',
138
102
  'maintitle',
139
103
  'eventtitle',
140
104
  ],
141
- unnest: [
142
- 'publisher',
143
- 'location',
144
- ],
145
105
  verbatim: [
146
106
  'doi',
147
107
  'eprint',
@@ -154,8 +114,10 @@ exports.fields = {
154
114
  'verba',
155
115
  'verbb',
156
116
  'verbc',
117
+ /^citeulike-linkout-[0-9]+$/,
118
+ /^bdsk-url-[0-9]+$/,
157
119
  ],
158
- html: [
120
+ richtext: [
159
121
  'annotation',
160
122
  'comment',
161
123
  'annote',
@@ -163,13 +125,20 @@ exports.fields = {
163
125
  'notes',
164
126
  'note',
165
127
  ],
166
- unabbrev: [
167
- 'journal',
168
- 'journaltitle',
169
- 'journal-full',
128
+ commalist: [
129
+ 'keywords',
130
+ ],
131
+ literallist: [
132
+ 'institution',
133
+ 'publisher',
134
+ 'origpublisher',
135
+ 'organization',
136
+ 'location',
137
+ 'origlocation',
170
138
  ],
171
139
  };
172
- const english = [
140
+ const English = [
141
+ '',
173
142
  'american',
174
143
  'british',
175
144
  'canadian',
@@ -195,1326 +164,878 @@ const english = [
195
164
  'en-zw',
196
165
  'anglais', // don't do this people
197
166
  ];
198
- class Parser {
199
- constructor(options = {}) {
200
- this.in_preamble = false;
201
- this.preamble = [];
202
- this.log = function (_str) { }; // eslint-disable-line prefer-arrow/prefer-arrow-functions, @typescript-eslint/no-empty-function, id-blacklist
203
- for (const [option, value] of Object.entries(options)) {
204
- if (typeof value === 'undefined')
205
- delete options[option];
206
- }
207
- if (options.errorHandler === false) {
208
- // eslint-disable-next-line prefer-arrow/prefer-arrow-functions, @typescript-eslint/no-empty-function
209
- options.errorHandler = function (_err) { };
210
- }
211
- else if (typeof options.errorHandler === 'undefined') {
212
- // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
213
- options.errorHandler = function (err) { throw err; };
214
- }
215
- if (typeof options.sentenceCase === 'boolean') {
216
- options.sentenceCase = options.sentenceCase ? english : [];
217
- }
218
- else {
219
- options.sentenceCase = options.sentenceCase || english;
220
- }
221
- if (!options.strings)
222
- options.strings = {};
223
- if (!options.unabbreviate)
224
- options.unabbreviate = {};
225
- if (options.raw) {
226
- options.sentenceCase = false;
227
- options.caseProtection = false;
228
- }
229
- this.options = Object.assign({ caseProtection: 'as-needed', verbatimFields: [/^citeulike-linkout-[0-9]+$/, /^bdsk-url-[0-9]+$/, ...exports.fields.verbatim], verbatimCommands: ['url'], unnestFields: [...exports.fields.title, ...exports.fields.unnest, ...exports.fields.verbatim], unnestMode: 'unwrap', htmlFields: exports.fields.html, guessAlreadySentenceCased: true, applyCrossRef: options.applyCrossRef || typeof options.applyCrossRef === 'undefined', markup: {} }, options);
230
- const markup_defaults = {
231
- enquote: { open: '\u201c', close: '\u201d' },
232
- sub: { open: '<sub>', close: '</sub>' },
233
- sup: { open: '<sup>', close: '</sup>' },
234
- bold: { open: '<b>', close: '</b>' },
235
- italics: { open: '<i>', close: '</i>' },
236
- smallCaps: { open: '<span style="font-variant:small-caps;">', close: '</span>' },
237
- caseProtect: { open: '<span class="nocase">', close: '</span>' },
238
- roman: { open: '', close: '' },
239
- fixedWidth: { open: '<tt>', close: '</tt>' },
240
- };
241
- // patch in because the options will likely not have enquote and case-protect
242
- for (const [markup, { open, close }] of Object.entries(markup_defaults)) {
243
- this.options.markup[markup] = this.options.markup[markup] || { open, close };
244
- }
245
- for (const i of [1, 2, 3, 4]) { // eslint-disable-line no-magic-numbers
246
- this.options.markup[`h${i}`] = this.options.markup[`h${i}`] || { open: `<h${i}>`, close: `</h${i}>` };
167
+ const FieldAction = {
168
+ removeOuterBraces: [
169
+ 'doi',
170
+ // 'publisher',
171
+ // 'location',
172
+ ],
173
+ unabbrev: [
174
+ 'journal',
175
+ 'journaltitle',
176
+ 'journal-full',
177
+ 'series',
178
+ ],
179
+ parseInt: [
180
+ 'year',
181
+ 'month',
182
+ ],
183
+ };
184
+ const narguments = {
185
+ ElsevierGlyph: 1,
186
+ bar: 1,
187
+ bibcyr: 1,
188
+ bibstring: 1,
189
+ chsf: 1,
190
+ cite: 1,
191
+ citeauthor: 1,
192
+ cyrchar: 1,
193
+ ding: 1,
194
+ emph: 1,
195
+ enquote: 1,
196
+ frac: 2,
197
+ href: 2,
198
+ hspace: 1,
199
+ mathrm: 1,
200
+ mbox: 1,
201
+ mkbibbold: 1,
202
+ mkbibemph: 1,
203
+ mkbibitalic: 1,
204
+ mkbibquote: 1,
205
+ newcommand: 2,
206
+ noopsort: 1,
207
+ ocirc: 1,
208
+ sb: 1,
209
+ section: 1,
210
+ sp: 1,
211
+ subsection: 1,
212
+ subsubsection: 1,
213
+ subsubsubsection: 1,
214
+ t: 1,
215
+ textbf: 1,
216
+ textcite: 1,
217
+ textit: 1,
218
+ textrm: 1,
219
+ textsc: 1,
220
+ textsl: 1,
221
+ textsubscript: 1,
222
+ textsuperscript: 1,
223
+ texttt: 1,
224
+ textup: 1,
225
+ url: 1,
226
+ vphantom: 1,
227
+ vspace: 1,
228
+ overline: 1,
229
+ // math
230
+ 'math\t_': 1,
231
+ 'math\t^': 1,
232
+ };
233
+ for (const m in unicode2latex_1.combining.tounicode) { // eslint-disable-line guard-for-in
234
+ narguments[m] = 1;
235
+ }
236
+ function asyncGenerator(array) {
237
+ return __asyncGenerator(this, arguments, function* asyncGenerator_1() {
238
+ for (const item of array) {
239
+ yield yield __await(yield __await(Promise.resolve(item)));
247
240
  }
248
- this.unresolvedStrings = {};
249
- this.errors = [];
250
- this.comments = [];
251
- this.entries = [];
252
- this.strings = {};
241
+ });
242
+ }
243
+ class BibTeXParser {
244
+ constructor() {
253
245
  this.newcommands = {};
254
- this.default_strings = {
255
- JAN: [this.text('01')],
256
- FEB: [this.text('02')],
257
- MAR: [this.text('03')],
258
- APR: [this.text('04')],
259
- MAY: [this.text('05')],
260
- JUN: [this.text('06')],
261
- JUL: [this.text('07')],
262
- AUG: [this.text('08')],
263
- SEP: [this.text('09')],
264
- OCT: [this.text('10')],
265
- NOV: [this.text('11')],
266
- DEC: [this.text('12')],
267
- ACMCS: [this.text('ACM Computing Surveys')],
268
- ACTA: [this.text('Acta Informatica')],
269
- CACM: [this.text('Communications of the ACM')],
270
- IBMJRD: [this.text('IBM Journal of Research and Development')],
271
- IBMSJ: [this.text('IBM Systems Journal')],
272
- IEEESE: [this.text('IEEE Transactions on Software Engineering')],
273
- IEEETC: [this.text('IEEE Transactions on Computers')],
274
- IEEETCAD: [this.text('IEEE Transactions on Computer-Aided Design of Integrated Circuits')],
275
- IPL: [this.text('Information Processing Letters')],
276
- JACM: [this.text('Journal of the ACM')],
277
- JCSS: [this.text('Journal of Computer and System Sciences')],
278
- SCP: [this.text('Science of Computer Programming')],
279
- SICOMP: [this.text('SIAM Journal on Computing')],
280
- TOCS: [this.text('ACM Transactions on Computer Systems')],
281
- TODS: [this.text('ACM Transactions on Database Systems')],
282
- TOG: [this.text('ACM Transactions on Graphics')],
283
- TOMS: [this.text('ACM Transactions on Mathematical Software')],
284
- TOOIS: [this.text('ACM Transactions on Office Information Systems')],
285
- TOPLAS: [this.text('ACM Transactions on Programming Languages and Systems')],
286
- TCS: [this.text('Theoretical Computer Science')],
287
- };
288
- if (typeof this.options.strings === 'string') {
289
- const strings = this.options.strings;
290
- this.options.strings = {};
291
- this.parseChunk({ text: strings, offset: { pos: 0, line: 0 } });
292
- this.preloaded_strings = this.strings;
293
- this.strings = {};
294
- }
295
- else {
296
- this.preloaded_strings = {};
297
- }
298
- }
299
- ast(input, clean = true) {
300
- let parsed = [];
301
- for (const chunk of chunker.parse(input)) {
302
- const { children } = bibtex.parse(chunk.text, Object.assign(Object.assign({}, this.options), { combining: unicode2latex_1.combining.macros }));
303
- if (clean)
304
- this.clean(children);
305
- parsed = parsed.concat(children);
306
- }
307
- return parsed;
308
- }
309
- parse(input) {
310
- for (const chunk of chunker.parse(input)) {
311
- this.parseChunk(chunk);
312
- }
313
- return this.parsed();
314
246
  }
315
- async parseAsync(input) {
316
- // eslint-disable-next-line @typescript-eslint/await-thenable
317
- for (const chunk of await chunker.promises.parse(input)) {
318
- this.parseChunk(chunk);
319
- }
320
- return this.parsed();
247
+ split(ast, sep, split) {
248
+ const roots = [];
249
+ const nodes = [...ast.content];
250
+ const types = nodes.map(node => {
251
+ if (node.type === 'whitespace')
252
+ return ' ';
253
+ if (node.type === 'string' && node.content.match(sep))
254
+ return '&';
255
+ return '.';
256
+ }).join('');
257
+ types.split(split).forEach((match, i) => {
258
+ const content = match.length ? nodes.splice(0, match.length) : [];
259
+ if ((i % 2) === 0)
260
+ roots.push({ type: 'root', content });
261
+ });
262
+ return roots;
321
263
  }
322
- applyCrossrefField(parent, parentfield, child, childfield) {
323
- let applied = false;
324
- for (const field of ['fields', 'creators']) {
325
- if (!child[field][childfield] && parent[field][parentfield]) {
326
- child[field][childfield] = parent[field][parentfield];
327
- applied = true;
328
- if (!child.crossref.inherited.includes(childfield))
329
- child.crossref.inherited.push(childfield);
330
- if (!parent.crossref.donated.includes(parentfield))
331
- parent.crossref.donated.push(parentfield);
332
- }
333
- }
334
- return applied;
264
+ trimCreator(cr) {
265
+ // trims strings but coincidentally removes undefined fields
266
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
267
+ return JSON.parse(JSON.stringify(cr, (k, v) => typeof v === 'string' ? (v.trim() || undefined) : v));
335
268
  }
336
- applyCrossref(entry, entries) {
337
- for (const xref of ['crossref', 'xdata']) {
338
- if (!entry.fields[xref])
339
- continue;
340
- for (const parents of entry.fields[xref]) {
341
- let applied = false;
342
- for (const parent of parents.split(/\s*,\s*/).map(key => entries[key]).filter(p => p)) {
343
- this.applyCrossref(parent, entries);
344
- for (const mappings of [crossref_json_1.default[entry.type], crossref_json_1.default['*']].filter(m => m)) {
345
- for (const mapping of [mappings[parent.type], mappings['*']].filter(m => m)) {
346
- for (const [target, source] of Object.entries(mapping)) {
347
- if (this.applyCrossrefField(parent, source, entry, target))
348
- applied = true;
349
- }
350
- for (const field of (fields_json_1.default[entry.type] || [])) {
351
- if (this.applyCrossrefField(parent, field, entry, field))
352
- applied = true;
353
- }
354
- }
355
- }
269
+ parseCreator(ast) {
270
+ if (ast.content.length === 1 && ast.content[0].type === 'group')
271
+ return this.trimCreator({ name: this.stringify(ast, { mode: 'creator' }) });
272
+ if (ast.content.find(node => node.type === 'string' && node.content === ',')) {
273
+ const nameparts = this.split(ast, /^,$/, /(&)/).map((part) => this.stringify(part, { mode: 'creator' }).trim());
274
+ const extended = nameparts.every(p => p.match(/^[a-z][-a-z]+\s*=/i));
275
+ if (!extended) {
276
+ // eslint-disable-next-line no-magic-numbers
277
+ if (nameparts.length === 3 && nameparts[2] === '')
278
+ nameparts.pop();
279
+ // eslint-disable-next-line no-magic-numbers
280
+ if (nameparts.length > 3) {
281
+ const key = this.current.key ? `@${this.current.key}: ` : '';
282
+ this.bib.errors.push({
283
+ // eslint-disable-next-line no-magic-numbers
284
+ error: `${key}unexpected ${nameparts.length}-part name "${nameparts.join(', ')}", dropping "${nameparts.slice(3).join(', ')}"`,
285
+ input: nameparts.join(', '),
286
+ });
356
287
  }
357
- if (applied)
358
- delete entry.fields[xref];
359
- }
360
- }
361
- }
362
- parsed() {
363
- this.field = null;
364
- const strings = {};
365
- this.cleaning = { type: 'other' };
366
- for (const [key, value] of Object.entries(this.strings)) {
367
- this.field = {
368
- name: '@string',
369
- text: '',
370
- level: 0,
371
- preserveRanges: null,
372
- words: {
373
- upper: 0,
374
- lower: 0,
375
- other: 0,
376
- },
377
- };
378
- this.convert(this.clean(value));
379
- strings[key] = this.field.text;
380
- }
381
- if (this.options.applyCrossRef) {
382
- const entries = this.entries.reduce((acc, entry) => {
383
- acc[entry.key] = entry;
384
- return acc;
385
- }, {});
386
- for (const entry of this.entries) {
387
- this.applyCrossref(entry, entries);
288
+ let [lastName, suffix, firstName] = (nameparts.length === 2)
289
+ ? [nameparts[0], undefined, nameparts[1]]
290
+ // > 3 nameparts are invalid and are dropped
291
+ // eslint-disable-next-line no-magic-numbers
292
+ : nameparts.slice(0, 3);
293
+ let prefix;
294
+ const m = lastName.match(/^([a-z'. ]+) (.+)/);
295
+ if (m) {
296
+ prefix = m[1];
297
+ lastName = m[2];
298
+ }
299
+ return this.trimCreator({
300
+ lastName,
301
+ firstName,
302
+ prefix,
303
+ suffix,
304
+ });
388
305
  }
389
- }
390
- const { comments, jabref } = (0, jabref_1.parse)(this.comments); // eslint-disable-line @typescript-eslint/no-shadow
391
- return {
392
- errors: this.errors,
393
- entries: this.entries,
394
- comments,
395
- jabref,
396
- strings,
397
- preamble: this.preamble,
398
- };
399
- }
400
- preserve(start, end, reason) {
401
- if (!this.field.preserveRanges)
402
- return;
403
- if (!end) {
404
- this.field.preserveRanges = null;
405
- return;
406
- }
407
- /*
408
- this.field.preserveRanges = this.field.preserveRanges.filter(range => range.start < start || range.end > end)
409
- if (this.field.preserveRanges.find(range => range.start <= start && range.end >= end)) return
410
- */
411
- /*
412
- if (this.field.preserveRanges && this.field.preserveRanges.length) {
413
- const last = this.field.preserveRanges[this.field.preserveRanges.length - 1]
414
- if (start < last.start) throw new Error(JSON.stringify({...last, new: { start, end, reason }, text: this.field.text}))
415
- }
416
- */
417
- this.field.preserveRanges.push({ start, end, description: reason });
418
- }
419
- parseChunk(chunk) {
420
- this.chunk = chunk.text;
421
- try {
422
- let bib = bibtex.parse(chunk.text, Object.assign(Object.assign({}, this.options), { combining: unicode2latex_1.combining.macros }));
423
- if (bib.kind !== 'Bibliography')
424
- throw new Error(this.show(bib));
425
- bib = this.clean(bib);
426
- for (const entity of bib.children) {
427
- switch (entity.kind) {
428
- case 'Entry':
429
- case 'BracedComment':
430
- case 'LineComment':
431
- case 'PreambleExpression':
432
- this.convert(entity);
306
+ const name = {};
307
+ // eslint-disable-next-line no-magic-numbers
308
+ for (let [attr, value] of nameparts.map(p => { var _a; return (_a = p.match(/^([^=]+)=(.*)/)) === null || _a === void 0 ? void 0 : _a.slice(1, 3); })) {
309
+ attr = attr.toLowerCase();
310
+ switch (attr) {
311
+ case '':
312
+ break;
313
+ case 'given':
314
+ name.firstName = value;
433
315
  break;
434
- case 'StringDeclaration':
435
- case 'NonEntryText':
316
+ case 'family':
317
+ name.lastName = value;
318
+ break;
319
+ case 'given-i':
320
+ name.initial = value;
321
+ break;
322
+ case 'useprefix':
323
+ case 'juniorcomma':
324
+ name[attr] = value.toLowerCase() === 'true';
325
+ break;
326
+ default:
327
+ name[attr] = value;
436
328
  break;
437
329
  }
438
330
  }
439
- return bib;
440
- }
441
- catch (err) {
442
- if (!err.location)
443
- throw err;
444
- this.errors.push({
445
- message: err.message,
446
- // no idea why eslint doesn't understand this type guard
447
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
448
- line: err.location.start.line + chunk.offset.line,
449
- column: err.location.start.column,
450
- source: this.chunk,
331
+ return this.trimCreator(name);
332
+ }
333
+ else { // first-last mode
334
+ const nameparts = this.split(ast, /^$/, /( )/).map(part => this.stringify(part, { mode: 'creator' })).filter(n => n);
335
+ if (nameparts.length === 1)
336
+ return this.trimCreator({ lastName: nameparts[0] });
337
+ const prefix = nameparts.findIndex(n => n.match(/^[a-z]/));
338
+ const postfix = prefix <= 0 ? -1 : nameparts.findIndex((n, i) => i > prefix && !n.match(/^[a-z]/));
339
+ if (postfix === -1) {
340
+ const lastName = nameparts.pop();
341
+ return this.trimCreator({ lastName, firstName: nameparts.join(' ') });
342
+ }
343
+ return this.trimCreator({
344
+ firstName: nameparts.slice(0, prefix).join(' '),
345
+ prefix: nameparts.slice(prefix, postfix).join(' '),
346
+ lastName: nameparts.slice(postfix).join(' '),
451
347
  });
452
- return null;
453
348
  }
454
349
  }
455
- show(o) {
456
- // eslint-disable-next-line prefer-template
457
- let text = JSON.stringify(o);
458
- if (this.chunk)
459
- text += `\n${this.chunk.trim()}`;
460
- return text;
461
- }
462
- text(value = '') {
463
- return { kind: 'Text', value, mode: 'text' };
464
- }
465
- error(err, returnvalue) {
466
- if (typeof this.options.errorHandler === 'function')
467
- this.options.errorHandler(err);
468
- return returnvalue || this.text();
469
- }
470
- condense(node) {
471
- // apply cleaning to resulting children
472
- node.value = this.clean(node.value);
473
- // unpack redundant blocks
474
- node.value = node.value.reduce((acc, child) => {
475
- if (child.kind === 'Block' && !child.case && Object.keys(child.markup).length === 0) {
476
- acc = acc.concat(child.value);
477
- }
478
- else {
479
- acc.push(child);
480
- }
481
- return acc;
482
- }, []);
483
- // condense text nodes to make whole words for sentence casing
484
- node.value = node.value.reduce((acc, child, i) => {
485
- if (acc.length === 0) {
486
- acc.push(child);
487
- return acc;
488
- }
489
- const last = acc[acc.length - 1];
490
- const next = node.value[i + 1];
491
- if (this.options.caseProtection === 'strict' && this.onlyCaseProtected(last) && child.kind === 'Text' && !child.value.match(preserveCase.isCaseSensitive) && this.onlyCaseProtected(next)) {
492
- last.value.push(child);
493
- delete last.source;
494
- return acc;
495
- }
496
- if (last.kind === 'Block' && child.kind === 'Block' && Object.keys(last.markup).sort().join('/') === Object.keys(child.markup).sort().join('/')) {
497
- last.value = last.value.concat(child.value);
498
- delete last.source;
499
- return acc;
500
- }
501
- if (last.kind === 'Text' && child.kind === 'Text' && last.mode === child.mode) {
502
- last.value += child.value;
503
- delete last.source;
504
- return acc;
505
- }
506
- acc.push(child);
507
- return acc;
508
- }, []);
509
- }
510
- onlyCaseProtected(node) {
511
- return (node === null || node === void 0 ? void 0 : node.kind) === 'Block' && node.case === 'protect' && Object.keys(node.markup).join('/') === '';
512
- }
513
- argument(node, kind) {
514
- if (!node.arguments || !node.arguments.required.length)
515
- return (kind === 'none');
516
- // expect 'n' text arguments
517
- if (typeof kind === 'number') {
518
- return node.arguments.required.length === kind ? node.arguments.required : false;
519
- }
520
- // return first argument if it's the only one
521
- if (node.arguments.required.length !== 1)
522
- return false;
523
- // loose checking for text
524
- if (kind === 'text') {
525
- const first = node.arguments.required[0];
526
- if (first.kind === 'Block') {
527
- switch (first.value.length) {
528
- case 0:
529
- return '';
530
- case 1:
531
- if (first.value[0].kind === 'Text')
532
- return first.value[0].value;
533
- break;
350
+ ligature(nodes) {
351
+ if (latexMode(nodes[0]) !== 'text')
352
+ return null;
353
+ const max = 3;
354
+ const slice = nodes.slice(0, max);
355
+ const type = slice.map(n => n.type === 'string' ? 's' : ' ').join('');
356
+ if (type[0] !== 's')
357
+ return null;
358
+ const content = slice.map(n => n.type === 'string' ? n.content : '');
359
+ let latex;
360
+ while (content.length) {
361
+ if (type.startsWith('s'.repeat(content.length)) && (latex = latex2unicode(content.join(''), slice[0]))) {
362
+ try {
363
+ return { type: 'string', content: latex, _renderInfo: {} };
534
364
  }
535
- }
536
- // fall back to strict kind check
537
- kind = 'Text';
538
- }
539
- // return first argument if it's the only one and is of the specified kind
540
- if (node.arguments.required.length !== 1 || node.arguments.required[0].kind !== kind)
541
- return false;
542
- switch (node.arguments.required[0].kind) {
543
- case 'Text':
544
- // no idea why eslint insists this is a @typescript-eslint/no-unsafe-return
545
- return node.arguments.required[0].value;
546
- case 'RegularCommand':
547
- return node.arguments.required[0];
548
- case 'Block':
549
- return node.arguments.required[0];
550
- }
551
- return false;
552
- }
553
- isBreak(node, kind) {
554
- return node && node.kind === 'RegularCommand' && kind.includes(node.command);
555
- }
556
- clean(node) {
557
- if (Array.isArray(node)) {
558
- return node
559
- .filter((child, i) => !(this.isBreak(child, ['\\']) && this.isBreak(node[i - 1], ['\\', 'par']))) // condense linebreaks
560
- .filter((child, i) => !this.isBreak(child, ['par']) || !this.isBreak(node[i - 1], ['par'])) // condense parbreaks
561
- .map(child => this.clean(child)); // eslint-disable-line @typescript-eslint/no-unsafe-return
562
- }
563
- delete node.loc;
564
- switch (node.kind) {
565
- case 'InlineMath':
566
- case 'DisplayMath':
567
- return this.clean_block(node);
568
- case 'Environment':
569
- return this.clean_environment(node);
570
- case 'Block':
571
- return this.clean_block(node);
572
- case 'Bibliography':
573
- return this.clean_bib(node);
574
- case 'RegularCommand':
575
- return this.clean_command(node);
576
- case 'DiacriticCommand':
577
- return this.clean_diacritic(node);
578
- case 'Entry':
579
- return this.clean_entry(node);
580
- case 'Field':
581
- return this.options.raw ? node : this.clean_field(node);
582
- case 'StringDeclaration':
583
- return this.clean_stringdecl(node);
584
- case 'StringReference':
585
- return this.clean_stringref(node);
586
- case 'SubscriptCommand':
587
- case 'SuperscriptCommand':
588
- return this.clean_script(node);
589
- case 'SymbolCommand':
590
- return this.clean_symbol(node);
591
- case 'PreambleExpression':
592
- return this.clean_preamble(node);
593
- case 'Number':
594
- case 'Text':
595
- case 'BracedComment':
596
- case 'LineComment':
597
- case 'NonEntryText':
598
- return node;
599
- default:
600
- return this.error(new ParserError(`no cleanup method for ${this.show(node)}`, node), this.text());
601
- }
602
- }
603
- clean_preamble(node) {
604
- this.in_preamble = true;
605
- try {
606
- return this.clean(node.value);
607
- }
608
- finally {
609
- this.in_preamble = false;
610
- }
611
- }
612
- clean_bib(node) {
613
- node.children = node.children.filter(child => child.kind !== 'NonEntryText').map(child => this.clean(child));
614
- return node;
615
- }
616
- clean_stringdecl(node) {
617
- this.strings[node.name.toUpperCase()] = node.value;
618
- return node;
619
- }
620
- clean_stringref(node) {
621
- const name = node.name.toUpperCase();
622
- const stringvalue = this.strings[name]
623
- || this.options.strings[name]
624
- || this.preloaded_strings[name]
625
- || this.default_strings[name]
626
- || (exports.fields.unabbrev.includes(this.cleaning.name) && this.options.unabbreviate[name] && [this.text(this.options.unabbreviate[name])]);
627
- if (!stringvalue) {
628
- if (!this.unresolvedStrings[name])
629
- this.errors.push({ message: `Unresolved @string reference ${JSON.stringify(node.name)}` });
630
- this.unresolvedStrings[name] = true;
631
- }
632
- return this.clean({
633
- kind: 'Block',
634
- // if the string isn't found, add it as-is but exempt it from sentence casing
635
- case: stringvalue ? undefined : 'preserve',
636
- markup: {},
637
- value: stringvalue ? JSON.parse(JSON.stringify(stringvalue)) : [this.text(node.name)],
638
- });
639
- }
640
- clean_entry(node) {
641
- const shortjournals = [];
642
- for (const field of node.fields) {
643
- if (exports.fields.unabbrev.includes(field.name) && Array.isArray(field.value)) {
644
- const abbr = field.value.map(v => v.source).join('').toUpperCase();
645
- const journal = this.options.unabbreviate[abbr];
646
- if (journal) {
647
- shortjournals.push(Object.assign(Object.assign({}, JSON.parse(JSON.stringify(field))), { name: 'shortjournal' }));
648
- field.value = [this.text(journal)];
365
+ finally {
366
+ nodes.splice(0, content.length);
649
367
  }
650
368
  }
369
+ content.pop();
651
370
  }
652
- node.fields = node.fields.concat(shortjournals).map(child => this.clean(child));
653
- return node;
371
+ return null;
654
372
  }
655
- startCleaning(name) {
656
- name = name.toLowerCase();
657
- if (exports.fields.title.includes(name)) {
658
- this.cleaning = { type: 'title', name };
659
- }
660
- else if (exports.fields.creator.includes(name.replace(/s$/, ''))) {
661
- this.cleaning = { type: 'creator', name: name.replace(/s$/, '') };
662
- }
663
- else {
664
- this.cleaning = { type: 'other', name };
665
- }
373
+ wraparg(node, macro) {
374
+ if (macro.content.match(/^(itshape|textsl|textit|emph|mkbibemph)$/))
375
+ node._renderInfo.emph = true;
376
+ if (macro.content.match(/^(textbf|mkbibbold|bfseries)$/))
377
+ node._renderInfo.bold = true;
378
+ if (macro.content.match(/^(textsc)$/))
379
+ node._renderInfo.smallCaps = true;
380
+ if (macro.content.match(/^(texttt)$/))
381
+ node._renderInfo.code = true;
382
+ return { type: 'argument', content: [node], openMark: '', closeMark: '', _renderInfo: { mode: node._renderInfo.mode } };
666
383
  }
667
- stripNoCase(node, strip, preserve) {
668
- switch (node.kind) {
669
- case 'RegularCommand':
670
- // a bit cheaty to assume these to be nocased, but it's just more likely to be what people want
671
- if (['chsf', 'bibstring', 'cite'].includes(node.command))
672
- strip = true;
673
- node.arguments.required.map(arg => this.stripNoCase(arg, strip, preserve));
674
- break;
675
- case 'Block':
676
- case 'InlineMath':
677
- case 'DisplayMath':
678
- if (strip && node.case === 'protect') {
679
- if (preserve) {
680
- node.case = 'preserve';
681
- }
682
- else {
683
- delete node.case;
684
- }
685
- }
686
- node.value.map(v => this.stripNoCase(v, strip || node.case === 'protect', preserve));
687
- break;
688
- case 'Field':
689
- if (Array.isArray(node.value))
690
- node.value.forEach(v => this.stripNoCase(v, strip, preserve));
691
- break;
692
- }
384
+ argtogroup(node) {
385
+ if (node.content.length === 1 && node.content[0].type === 'group')
386
+ return node.content[0];
387
+ return { type: 'group', content: node.content };
693
388
  }
694
- isVerbatimField(name) {
695
- return !!this.options.verbatimFields.find(p => (typeof p === 'string') ? name === p : name.match(p));
696
- }
697
- clean_field(node) {
698
- this.startCleaning(node.name);
699
- this.stripNoCase(node, !this.options.caseProtection || this.isVerbatimField(node.name), this.options.sentenceCase.length === 0);
700
- if (Array.isArray(node.value))
701
- this.condense(node);
702
- return node;
703
- }
704
- clean_script(node) {
705
- let m, value, singlechar;
706
- // recognize combined forms like \^\circ
707
- if (singlechar = unicode2latex_1.latex2unicode[node.source])
708
- return this.text(singlechar);
709
- if ((m = node.source.match(/^([\^_])([^{}]+)$/)) && ((singlechar = unicode2latex_1.latex2unicode[`${m[1]}${m[2]}`]) || (singlechar = unicode2latex_1.latex2unicode[`${m[1]}{${m[2]}}`])))
710
- return this.text(singlechar);
711
- if ((m = node.source.match(/^([\^_])\{([^{}]+)\}$/)) && ((singlechar = unicode2latex_1.latex2unicode[`${m[1]}${m[2]}`]) || (singlechar = unicode2latex_1.latex2unicode[`${m[1]}{${m[2]}}`])))
712
- return this.text(singlechar);
713
- const cmd = node.kind === 'SuperscriptCommand' ? '^' : '_';
714
- if (typeof node.value === 'string' && (singlechar = unicode2latex_1.latex2unicode[`${cmd}${node.value}`] || unicode2latex_1.latex2unicode[`${cmd}{${node.value}}`])) {
715
- return this.text(singlechar);
716
- }
717
- if (typeof node.value === 'string') {
718
- value = [this.text(node.value)];
719
- }
720
- else if (!Array.isArray(node.value)) {
721
- value = [node.value];
722
- }
723
- else {
724
- value = node.value;
725
- }
726
- const mode = node.kind === 'SuperscriptCommand' ? 'sup' : 'sub';
727
- return this.clean({
728
- kind: 'Block',
729
- markup: { [mode]: true },
730
- value,
731
- });
732
- }
733
- clean_environment(node) {
734
- this.condense(node);
735
- return node;
736
- }
737
- needsProtection(word) {
738
- return !word.match(preserveCase.hasUpper) && !!word.match(preserveCase.hasLower);
389
+ argument(nodes, macro) {
390
+ if (!nodes.length)
391
+ return null;
392
+ if (nodes[0].type === 'whitespace')
393
+ nodes.shift();
394
+ if (!nodes.length)
395
+ return null;
396
+ if (nodes[0].type === 'string') {
397
+ const char = nodes[0].content[0];
398
+ nodes[0].content = nodes[0].content.substr(1);
399
+ const arg = Object.assign(Object.assign({}, nodes[0]), { content: char });
400
+ if (!nodes[0].content)
401
+ nodes.shift();
402
+ return this.wraparg(arg, macro);
403
+ }
404
+ return this.wraparg(nodes.shift(), macro);
739
405
  }
740
- clean_block(node) {
406
+ unsupported(node) {
741
407
  var _a;
742
- this.condense(node);
743
- if (this.options.caseProtection !== 'strict' && ((_a = this.cleaning) === null || _a === void 0 ? void 0 : _a.type) === 'title' && node.case === 'protect') {
744
- // test whether we can get away with skipping case protection because it contains all words that will be preserved anyway when converting back to Title Case
745
- let preserve = true;
746
- for (const child of node.value) {
747
- if (child.kind === 'Text') {
748
- const value = child.value.trim();
749
- preserve = !value.match(preserveCase.isCaseSensitive) || !value.split(/\s+/).find(word => this.needsProtection(word));
750
- }
751
- else {
752
- preserve = false;
753
- }
754
- if (!preserve)
755
- break;
756
- }
757
- if (preserve)
758
- node.case = 'preserve';
759
- }
760
- if (node.kind === 'Block') {
761
- for (const [markup, on] of Object.entries(node.markup)) {
762
- if (!on)
763
- delete node.markup[markup];
764
- }
408
+ if (this.fallback)
409
+ return (_a = this.fallback(node, (0, unified_latex_util_print_raw_1.printRaw)(node), this.current)) !== null && _a !== void 0 ? _a : '';
410
+ switch (node.type) {
411
+ case 'macro':
412
+ throw new Error(`unhandled ${node.type} ${node.content} (${(0, unified_latex_util_print_raw_1.printRaw)(node)})`);
413
+ case 'environment':
414
+ throw new Error(`unhandled ${node.type} ${node.env} (${(0, unified_latex_util_print_raw_1.printRaw)(node)})`);
415
+ default:
416
+ throw new Error(`unhandled ${node.type} (${(0, unified_latex_util_print_raw_1.printRaw)(node)})`);
765
417
  }
766
- return node;
767
418
  }
768
- clean_diacritic(node) {
769
- const char = node.dotless ? `\\${node.character}` : node.character;
770
- let unicode = unicode2latex_1.latex2unicode[`\\${node.mark}{${char}}`]
771
- || unicode2latex_1.latex2unicode[`\\${node.mark}${char}`]
772
- || unicode2latex_1.latex2unicode[`{\\${node.mark} ${char}}`]
773
- || unicode2latex_1.latex2unicode[`{\\${node.mark}${char}}`]
774
- || unicode2latex_1.latex2unicode[`\\${node.mark} ${char}`];
775
- if (!unicode && !node.dotless && node.character.length === 1 && unicode2latex_1.combining.tounicode[node.mark])
776
- unicode = node.character + unicode2latex_1.combining.tounicode[node.mark];
777
- if (!unicode && !this.in_preamble)
778
- return this.error(new TeXError(`Unhandled \\${node.mark}{${char}}`, node, this.chunk));
779
- return this.text(unicode);
419
+ wrap(text, tag, wrap = true) {
420
+ if (!text || !wrap)
421
+ return text || '';
422
+ return `\x0E${tag}\x0F${text}\x0E/${tag}\x0F`;
780
423
  }
781
- clean_symbol(node) {
782
- if (node.command === '\\')
783
- return this.text('\n');
784
- return this.text(unicode2latex_1.latex2unicode[`\\${node.command}`] || node.command);
424
+ registercommand(node) {
425
+ const types = (nodes) => nodes.map(n => n.type).join('.');
426
+ const group = (arg, kind) => {
427
+ if (!arg)
428
+ throw new Error(`missing ${kind} for ${(0, unified_latex_util_print_raw_1.printRaw)(node)} @ ${JSON.stringify(node.position)}`);
429
+ if (types(arg.content) !== 'group')
430
+ throw new Error(`Malformed ${kind} for ${(0, unified_latex_util_print_raw_1.printRaw)(node)} @ ${JSON.stringify(node.position)}`);
431
+ return arg.content[0].content;
432
+ };
433
+ if (!node.args)
434
+ throw new Error(`No arguments for ${(0, unified_latex_util_print_raw_1.printRaw)(node)} @ ${JSON.stringify(node.position)}`);
435
+ const namearg = group(node.args[0], 'name');
436
+ if (types(namearg) !== 'macro')
437
+ throw new Error(`Unexpected name for ${(0, unified_latex_util_print_raw_1.printRaw)(node)} @ ${JSON.stringify(node.position)}`);
438
+ this.newcommands[namearg[0].content] = node.args[1];
439
+ return '';
785
440
  }
786
- first_text_block(node) {
787
- if (!node)
788
- return null;
789
- if (node.kind === 'Block') {
790
- for (const child of node.value) {
791
- switch (child.kind) {
792
- case 'Text':
793
- return child.value ? node : null;
794
- case 'Block':
795
- // eslint-disable-next-line no-case-declarations
796
- const candidate = this.first_text_block(child);
797
- if (candidate)
798
- return candidate;
799
- break;
800
- default:
801
- return null;
802
- }
441
+ subp(text, macro) {
442
+ let subp = '';
443
+ for (let char of text) {
444
+ char = unicode2latex_1.latex2unicode[`${macro}{${char}}`];
445
+ if (char) {
446
+ subp += char;
447
+ }
448
+ else {
449
+ const tag = { _: 'sub', '^': 'sup' }[macro];
450
+ return `\x0E${tag}\x0F${text}\x0E/${tag}\x0F`;
803
451
  }
804
452
  }
805
- else {
806
- return null;
807
- }
453
+ return subp;
808
454
  }
809
- clean_command(node) {
810
- var _a;
811
- let arg, unicode;
812
- if (unicode = unicode2latex_1.latex2unicode[node.source])
813
- return this.text(unicode);
814
- switch (node.command) {
455
+ macro(node, context) {
456
+ var _a, _b, _c, _d, _e, _f, _g;
457
+ const text = latex2unicode((0, unified_latex_util_print_raw_1.printRaw)(node), node);
458
+ if (text)
459
+ return text;
460
+ let url;
461
+ let label;
462
+ let arg;
463
+ let resolved;
464
+ switch (node.content) {
815
465
  case 'newcommand':
816
- if (((_a = node.arguments) === null || _a === void 0 ? void 0 : _a.required.length) === 2
817
- && node.arguments.required[0].kind === 'Block'
818
- && node.arguments.required[0].value.length === 1
819
- && node.arguments.required[0].value[0].kind === 'RegularCommand'
820
- && node.arguments.required[1].kind === 'Block') {
821
- this.newcommands[node.arguments.required[0].value[0].command] = node.arguments.required[1].value;
822
- return this.text();
823
- }
824
- // console.log('newcommand?', JSON.stringify(node, null, 2))
825
- break;
826
- case 'item':
827
- return { kind: 'Markup', value: '<li>', source: node.source };
828
- case 'frac':
829
- if (arg = this.argument(node, 2)) {
830
- if (arg[0].kind === 'Text' && arg[1].kind === 'Text' && (unicode = unicode2latex_1.latex2unicode[`\\frac{${arg[0].value}}{${arg[1].value}}`]))
831
- return this.text(unicode);
832
- return this.clean({
833
- kind: 'Block',
834
- case: 'protect',
835
- markup: {},
836
- value: [
837
- { kind: 'Block', markup: { sup: true }, value: [arg[0]] },
838
- this.text('\u2044'),
839
- { kind: 'Block', markup: { sub: true }, value: [arg[1]] },
840
- ],
841
- });
842
- }
843
- break;
844
- // ignore
845
- case 'vspace':
466
+ return this.registercommand(node);
846
467
  case 'vphantom':
847
- case 'path':
848
- case 'aftergroup':
849
- case 'ignorespaces':
850
- case 'relax':
851
468
  case 'noopsort':
852
- case 'ifdefined':
853
- case 'DeclarePrefChars':
854
- case 'else':
855
- case 'fi':
856
- case 'makeatletter':
857
- case 'mathslbb':
858
- return this.text();
859
- case 'ElsevierGlyph':
860
- if (arg = this.argument(node, 'Text')) {
861
- if (unicode = unicode2latex_1.latex2unicode[`\\${node.command}{${arg}}`])
862
- return this.text(unicode);
863
- return this.text(String.fromCharCode(parseInt(arg, 16)));
469
+ case 'left':
470
+ case 'right':
471
+ return '';
472
+ case 'path':
473
+ return ''; // until https://github.com/siefkenj/unified-latex/issues/94 is fixed
474
+ case 'hspace':
475
+ if (node.args && node.args.length) {
476
+ if ((0, unified_latex_util_print_raw_1.printRaw)(node.args).match(/^[{]?0[a-z]*$/))
477
+ return '';
478
+ return ' ';
864
479
  }
865
- break;
866
- case 'chsf':
867
- if (this.argument(node, 'none'))
868
- return this.text();
869
- if (arg = this.argument(node, 'Block'))
870
- return this.clean(arg);
871
- break;
872
- case 'enquote':
873
- case 'mkbibquote':
874
- case 'bibstring':
875
- case 'cite':
876
- case 'textcite':
480
+ return '';
481
+ case 'overline':
482
+ case 'bar':
483
+ return node.args.map(a => this.stringify(a, context)).join('').replace(/[a-z0-9]/ig, m => `${m}\u0305`);
877
484
  case 'textup':
878
- case 'citeauthor':
879
485
  case 'textsc':
880
- case 'textbf':
486
+ case 'textrm':
881
487
  case 'texttt':
882
- case 'mkbibbold':
883
- case 'section':
884
- case 'subsection':
885
- case 'subsubsection':
886
- case 'subsubsubsection':
887
- if (arg = this.argument(node, 'Block'))
888
- return this.clean(arg);
889
- break;
890
- case 'textsuperscript':
891
- case 'sp':
892
- if ((arg = this.argument(node, 'Text')) && (unicode = unicode2latex_1.latex2unicode[`^{${arg}}`]))
893
- return this.text(unicode);
894
- if (arg = this.argument(node, 'Block'))
895
- return this.clean(arg);
896
- break;
897
- case 'textsubscript':
898
- case 'sb':
899
- if ((arg = this.argument(node, 'Text')) && (unicode = unicode2latex_1.latex2unicode[`_{${arg}}`]))
900
- return this.text(unicode);
901
- if (arg = this.argument(node, 'Block'))
902
- return this.clean(arg);
903
- break;
904
- case 'mkbibitalic':
905
- case 'mkbibemph':
906
- case 'textit':
907
- case 'emph':
908
- if (arg = this.argument(node, 'Block'))
909
- return this.clean(arg);
910
- if (arg = this.argument(node, 'Text'))
911
- return this.clean({ kind: 'Block', markup: { italics: true }, value: [this.text(arg)] });
912
- break;
913
- case 'bibcyr':
914
- if (this.argument(node, 'none'))
915
- return this.text();
916
- if (arg = this.argument(node, 'Block'))
917
- return this.clean(arg);
918
- break;
919
- case 'hspace':
920
488
  case 'mathrm':
921
- case 'textrm':
922
- case 'ocirc':
923
489
  case 'mbox':
924
- if (arg = this.argument(node, 'text')) {
925
- if (node.command === 'mbox' && !arg)
926
- return this.text('\u200b');
927
- unicode = unicode2latex_1.latex2unicode[`\\${node.command}{${arg}}`];
928
- return this.text(unicode || (node.command === 'hspace' ? ' ' : arg));
929
- }
930
- else if (!node.arguments.required.length) {
931
- return (node.command === 'mbox') ? this.text('\u200b') : this.text();
932
- }
933
- else if (arg = this.argument(node, 'Block')) {
934
- return this.clean(arg);
935
- }
936
- break;
490
+ return node.args.map(n => this.stringify(n, context)).join('');
937
491
  case 'href':
938
492
  case 'url':
939
- node.arguments.required = node.arguments.required.map(a => this.clean(a));
940
- return node;
941
- case 'sl':
493
+ if (node.args) {
494
+ url = node.args[0];
495
+ label = node.args[node.content === 'url' ? 0 : 1];
496
+ }
497
+ return `<a href="${this.stringify(url, context)}">${this.stringify(label, context)}</a>`;
498
+ case 'relax':
499
+ case 'aftergroup':
500
+ case 'ignorespaces':
942
501
  case 'em':
943
502
  case 'it':
944
- case 'itshape':
945
- case 'bf':
946
- case 'bfseries':
947
- case 'sc':
948
- case 'scshape':
949
503
  case 'tt':
504
+ case 'sl':
505
+ return '';
950
506
  case 'rm':
951
- case 'sf':
952
- case 'verb':
953
- // handled in the grammar
954
- return this.text();
955
- // wouldn't know what to do with these
956
- case 'left':
957
- case 'right':
958
- return this.text();
959
- case 'par':
960
- case '\\':
961
- return node;
962
- case 'cyr':
963
- if (this.argument(node, 'none'))
964
- return this.text();
965
- break;
966
- case 'polhk':
967
- if (unicode = this.argument(node, 'text')) {
968
- if (unicode.length === 1)
969
- return this.text(`${unicode}\u0328`);
507
+ case 'sc':
508
+ return '';
509
+ // bold/emph/smallcaps is handled in the wrapper
510
+ case 'textbf':
511
+ case 'mkbibbold':
512
+ case 'textit':
513
+ case 'emph':
514
+ case 'mkbibemph':
515
+ return this.stringify((_a = node.args) === null || _a === void 0 ? void 0 : _a[0], context);
516
+ case 'textsuperscript':
517
+ return this.subp(this.stringify((_b = node.args) === null || _b === void 0 ? void 0 : _b[0], context), '^');
518
+ case 'textsubscript':
519
+ return this.subp(this.stringify((_c = node.args) === null || _c === void 0 ? void 0 : _c[0], context), '_');
520
+ case '_':
521
+ case '^':
522
+ switch (latexMode(node)) {
523
+ case 'math':
524
+ return this.subp(this.stringify((_d = node.args) === null || _d === void 0 ? void 0 : _d[0], context), node.content);
525
+ default:
526
+ return node.content;
970
527
  }
971
- if (this.argument(node, 'none'))
972
- return this.text('\u0328');
973
- break;
528
+ case 'LaTeX':
529
+ return this.wrap(`L${this.subp('A', '^')}T${this.subp('E', '_')}X`, 'ncx');
530
+ case 'enquote':
531
+ case 'mkbibquote':
532
+ return this.wrap(this.stringify((_e = node.args) === null || _e === void 0 ? void 0 : _e[0], context), 'enquote');
533
+ case '\\':
534
+ return context.mode === 'richtext' ? open.br : ' ';
535
+ case 'par':
536
+ return context.mode === 'richtext' ? open.p : ' ';
537
+ case 'item':
538
+ return context.mode === 'richtext' ? open.li : ' ';
539
+ case 'section':
540
+ case 'subsection':
541
+ case 'subsubsection':
542
+ case 'subsubsubsection':
543
+ return this.wrap(this.stringify((_f = node.args) === null || _f === void 0 ? void 0 : _f[0], context), `h${node.content.split('sub').length}`);
544
+ case 'frac':
545
+ arg = node.args.map(a => this.stringify(a, context));
546
+ if (arg.length === 2 && (resolved = unicode2latex_1.latex2unicode[`\\frac${arg.map(a => `{${a}}`).join('')}`]))
547
+ return resolved;
548
+ return arg.map((part, i) => this.subp(part, i ? '_' : '^')).join('\u2044');
549
+ case 'chsf':
550
+ case 'bibstring':
551
+ case 'cite':
552
+ case 'textcite':
553
+ case 'citeauthor':
554
+ // ncx protects but will be stripped later
555
+ return this.wrap(this.stringify((_g = node.args) === null || _g === void 0 ? void 0 : _g[0], context), 'ncx', context.mode === 'title');
974
556
  default:
975
- if (node.kind === 'RegularCommand' && this.newcommands[node.command]) {
976
- return this.clean({
977
- kind: 'Block',
978
- markup: {},
979
- value: JSON.parse(JSON.stringify(this.newcommands[node.command])),
980
- });
981
- }
982
- if (unicode2latex_1.combining.tounicode[node.command]) {
983
- node.arguments.required = this.clean(node.arguments.required);
984
- let block;
985
- if (node.arguments.required.length === 1 && node.arguments.required[0].kind === 'Text') {
986
- // no idea why I can't just straight return this but typescript just won't shut up
987
- block = {
988
- kind: 'Block',
989
- markup: {},
990
- value: [{
991
- kind: 'DiacriticCommand',
992
- mark: node.command,
993
- character: (node.arguments.required[0]).value,
994
- dotless: false,
995
- loc: node.arguments.required[0].loc,
996
- source: node.arguments.required[0].source,
997
- }],
998
- };
999
- return this.clean(block);
1000
- }
1001
- else if (block = this.first_text_block(node.arguments.required[0])) {
1002
- let fixed = false;
1003
- block.value = block.value.reduce((value, child) => {
1004
- if (!fixed && child.kind === 'Text') {
1005
- fixed = true;
1006
- value.push({ kind: 'DiacriticCommand', mark: node.command, character: child.value[0] });
1007
- value.push(Object.assign(Object.assign({}, child), { value: child.value.substring(1) }));
1008
- }
1009
- else {
1010
- value.push(child);
1011
- }
1012
- return value;
1013
- }, []);
1014
- return this.clean({
1015
- kind: 'Block',
1016
- markup: {},
1017
- value: node.arguments.required,
1018
- });
1019
- }
1020
- else {
1021
- // overline without arguments doesn't seem to render in LaTeX
1022
- if (node.command === 'overline')
1023
- return this.text(' ');
1024
- return this.clean({
1025
- kind: 'Block',
1026
- markup: {},
1027
- value: [this.text(` ${unicode2latex_1.combining.tounicode[node.command]}`)].concat(node.arguments.required),
1028
- });
1029
- }
1030
- }
1031
- if (unicode = unicode2latex_1.latex2unicode[node.source] || unicode2latex_1.latex2unicode[`${node.source}{}`])
1032
- return this.text(unicode);
1033
- if ((unicode = unicode2latex_1.latex2unicode[`\\${node.command}`] || unicode2latex_1.latex2unicode[`\\${node.command}{}`]) && this.argument(node, 'none'))
1034
- return this.text(unicode);
1035
- if ((arg = this.argument(node, 'Text')) && (unicode = unicode2latex_1.latex2unicode[`\\${node.command}{${arg}}`]))
1036
- return this.text(unicode);
1037
- break;
557
+ if (this.newcommands[node.content])
558
+ return this.stringify(this.newcommands[node.content], context);
559
+ return this.unsupported(node);
1038
560
  }
1039
- if (this.in_preamble)
1040
- return this.text(node.source);
1041
- if (this.options.unknownCommandHandler) {
1042
- return this.options.unknownCommandHandler.call(this, node);
561
+ }
562
+ what(node) {
563
+ var _a;
564
+ if (!node)
565
+ return '';
566
+ switch (node.type) {
567
+ case 'macro': return `macro:${(_a = node._renderInfo.mode) !== null && _a !== void 0 ? _a : 'text'}:${node.content}`;
568
+ case 'environment': return `env:${node.env}`;
569
+ default: return node.type;
1043
570
  }
1044
- else if (this.options.unknownCommandHandler === false) {
1045
- return this.text();
571
+ }
572
+ environment(node, context) {
573
+ while (node.content.length && this.what(node.content[0]).match(/^parbreak|whitespace|macro:text:par$/))
574
+ node.content.shift();
575
+ while (node.content.length && this.what(node.content[node.content.length - 1]).match(/^parbreak|whitespace|macro:text:par$/))
576
+ node.content.pop();
577
+ switch (node.env) {
578
+ case 'quotation':
579
+ return this.wrap(node.content.map(n => this.stringify(n, context)).join(''), 'blockquote', context.mode === 'richtext');
580
+ case 'itemize':
581
+ return this.wrap(node.content.map(n => this.stringify(n, context)).join(''), 'ul', context.mode === 'richtext');
582
+ case 'em':
583
+ return this.wrap(node.content.map(n => this.stringify(n, context)).join(''), 'i', context.mode === 'richtext');
584
+ default:
585
+ return this.unsupported(node);
1046
586
  }
1047
- else {
1048
- return this.error(new TeXError(`Unhandled command: ${node.command}${this.show(node)}`, node, this.chunk));
587
+ }
588
+ stringify(node, context) {
589
+ let content = this.stringifyContent(node, context);
590
+ if (content && node._renderInfo) {
591
+ if (node._renderInfo.emph)
592
+ content = `${open.i}${content}${close.i}`;
593
+ if (node._renderInfo.bold)
594
+ content = `${open.b}${content}${close.b}`;
595
+ if (node._renderInfo.smallCaps)
596
+ content = `${open.sc}${content}${close.sc}`;
597
+ if (node._renderInfo.code)
598
+ content = `${open.code}${content}${close.code}`;
599
+ if (node._renderInfo.protectCase)
600
+ content = `${open.nc}${content}${close.nc}`;
601
+ }
602
+ return content;
603
+ }
604
+ stringifyContent(node, context) {
605
+ if (!node)
606
+ return '';
607
+ switch (node.type) {
608
+ case 'root':
609
+ case 'argument':
610
+ case 'group':
611
+ case 'inlinemath':
612
+ return node.content.map(n => this.stringify(n, context)).join('');
613
+ case 'string':
614
+ case 'verbatim':
615
+ return node.content;
616
+ case 'macro':
617
+ return this.macro(node, context);
618
+ case 'parbreak':
619
+ return context.mode === 'richtext' ? open.p : ' ';
620
+ case 'whitespace':
621
+ return node._renderInfo.mode === 'math' ? '' : ' ';
622
+ case 'comment':
623
+ return '';
624
+ case 'environment':
625
+ return this.environment(node, context);
626
+ case 'verb':
627
+ return node.content;
628
+ default:
629
+ return this.unsupported(node);
1049
630
  }
1050
631
  }
1051
- preserveCase(word) {
1052
- // word = word.replace(new RegExp(`"[${this.markup.enquote.open}${this.markup.enquote.close}:()]`, 'g'), '')
1053
- var _a, _b;
1054
- if (!word.trim())
1055
- return false;
1056
- if (!word.match(preserveCase.hasAlphaNum))
1057
- return true;
1058
- word = word.replace(/[/’'”:()]/g, '');
1059
- if (word === 'I')
632
+ protect(node) {
633
+ if (node.type === 'inlinemath')
1060
634
  return true;
1061
- if (word.length === 1)
1062
- return false;
1063
- if (word.replace(preserveCase.notCaseSensitive) === '')
635
+ if (node.type !== 'group')
1064
636
  return false;
1065
- // word = word.replace(preserveCase.notAlphaNum, '')
1066
- // simple cap at start of field
1067
- if (word.match(preserveCase.leadingCap) && ((_b = (_a = this.field) === null || _a === void 0 ? void 0 : _a.text) === null || _b === void 0 ? void 0 : _b.length) === 0)
637
+ if (!node.content.length)
1068
638
  return false;
1069
- if (word.match(preserveCase.allCaps))
1070
- return true;
1071
- if (word.length > 1 && word.match(preserveCase.joined))
1072
- return false;
1073
- if (word.match(preserveCase.hasUpper))
1074
- return true;
1075
- if (word.match(preserveCase.isNumber))
1076
- return true;
1077
- return false;
639
+ return (node.content[0].type !== 'macro');
1078
640
  }
1079
- // private convert(node: Node[]): Node[]
1080
- // private convert(node: Node): Node
1081
- convert(node) {
1082
- if (Array.isArray(node))
1083
- return node.map((child) => this.convert(child));
1084
- if (this.options.raw && this.field)
1085
- node = this.text(node.source);
1086
- switch (node.kind) {
1087
- case 'Markup':
1088
- if (this.field)
1089
- this.field.text += node.value;
1090
- break;
1091
- case 'BracedComment':
1092
- case 'LineComment':
1093
- this.comments.push(node.value);
1094
- break;
1095
- case 'Entry':
1096
- this.convert_entry(node);
1097
- break;
1098
- case 'Number':
1099
- this.convert_number(node);
1100
- break;
1101
- case 'Text':
1102
- this.convert_text(node);
1103
- break;
1104
- case 'Block':
1105
- case 'InlineMath':
1106
- case 'DisplayMath':
1107
- // eslint-disable-next-line no-case-declarations
1108
- const start = this.field ? this.field.text.length : null;
1109
- // eslint-disable-next-line no-case-declarations
1110
- const preserve = typeof start === 'number' && this.field.preserveRanges;
1111
- this.convert_block(node);
1112
- if (preserve && (node.case || node.kind.endsWith('Math')))
1113
- this.preserve(start, this.field.text.length); // , `convert-block: case=${node.case}, math=${node.kind.endsWith('Math')}`)
1114
- break;
1115
- case 'Environment':
1116
- this.convert_environment(node);
1117
- break;
1118
- case 'PreambleExpression':
1119
- this.preamble.push(node.value.map(preamble => preamble.source).join('\n\n'));
1120
- break;
1121
- case 'StringDeclaration':
1122
- break;
1123
- default:
1124
- if (node.kind === 'RegularCommand' && (node.command === 'href' || node.command === 'url')) {
1125
- this.convert_href(node);
1126
- }
1127
- else if (node.kind === 'RegularCommand' && node.command === 'par') {
1128
- this.field.text += this.field.html ? '<p>\n' : ' ';
1129
- }
1130
- else if (node.kind === 'RegularCommand' && node.command === '\\') {
1131
- this.field.text += this.field.html ? '<b>\n' : ' ';
1132
- }
1133
- else {
1134
- return this.error(new ParserError(`no converter for ${node.kind}: ${this.show(node)}`, node));
1135
- }
641
+ mode(field) {
642
+ let mode = 'literal';
643
+ for (const [selected, fields] of Object.entries(this.fieldMode)) {
644
+ if (fields.find(match => typeof match === 'string' ? field === match : field.match(match)))
645
+ mode = selected;
1136
646
  }
647
+ return mode;
1137
648
  }
1138
- splitOnce(s, sep, fromEnd = false) {
1139
- const split = fromEnd ? s.lastIndexOf(sep) : s.indexOf(sep);
1140
- return (split < 0) ? [s, ''] : [s.substr(0, split), s.substr(split + 1)];
649
+ restoreMarkup(s) {
650
+ if (!s.includes('\x0E'))
651
+ return s;
652
+ const restored = [s.replace(/\x0E\/?ncx\x0F/ig, '')];
653
+ while (restored[0] !== restored[1]) {
654
+ restored.unshift(restored[0].replace(collapsable, '$2'));
655
+ }
656
+ return restored[0]
657
+ .replace(/(\x0Ep\x0F\s*){2,}/ig, '\x0Ep\x0F')
658
+ .replace(/\s*(\x0E\/p\x0F){2,}/ig, '\x0E/p\x0F')
659
+ .replace(/\x0Eenquote\x0F/ig, '\u201C').replace(/\x0E\/enquote\x0F/ig, '\u201D')
660
+ .replace(/\x0Esc\x0F/ig, '<span style="font-variant:small-caps;">').replace(/\x0E\/sc\x0F/ig, '</span>')
661
+ .replace(/\x0Enc\x0F/ig, '<span class="nocase">').replace(/\x0E\/nc\x0F/ig, '</span>')
662
+ .replace(/\x0E/ig, '<').replace(/\x0F/ig, '>');
1141
663
  }
1142
- parseName(name) {
1143
- let parsed = null;
1144
- const parts = name.split(marker.comma);
1145
- if (parts.length && !parts.find(p => !p.match(/^[a-z]+(-i)?=/i))) { // extended name format
1146
- for (const part of parts) {
1147
- parsed = parsed || {};
1148
- const [attr, value] = this.splitOnce(part.replace(marker.re.space, ''), '=').map((v) => v.trim());
1149
- if (!value) {
1150
- parsed = null;
1151
- break;
1152
- }
1153
- switch (attr.toLowerCase()) {
1154
- case 'family':
1155
- parsed.lastName = value;
1156
- break;
1157
- case 'given-i':
1158
- parsed.initial = value;
1159
- break;
1160
- case 'given':
1161
- parsed.firstName = value;
1162
- break;
1163
- case 'prefix':
1164
- parsed.prefix = value;
1165
- break;
1166
- case 'suffix':
1167
- parsed.suffix = value;
1168
- break;
1169
- case 'useprefix':
1170
- parsed.useprefix = value.toLowerCase() === 'true';
1171
- break;
1172
- case 'juniorcomma':
1173
- parsed.useprefix = value.toLowerCase() === 'true';
1174
- break;
1175
- default:
1176
- parsed[attr.toLowerCase()] = value;
1177
- break;
664
+ stringField(field, value, sentenceCase, mode) {
665
+ if (FieldAction.unabbrev.includes(field)) {
666
+ let full = this.options.unabbreviations[value.toUpperCase()] || this.options.unabbreviations[value.toUpperCase().replace(/s\S+$/, '')];
667
+ if (!full) {
668
+ const m = value.toUpperCase().match(/(.*)(\s+\S*\d\S*)$/);
669
+ if (m) {
670
+ full = this.options.unabbreviations[m[1]];
671
+ if (full)
672
+ full += m[2];
1178
673
  }
1179
674
  }
1180
- }
1181
- const prefix = /(.+?)\s+(vere|von|van den|van der|van|de|del|della|der|di|da|pietro|vanden|du|st.|st|la|lo|ter|bin|ibn|te|ten|op|ben|al)\s+(.+)/;
1182
- let m;
1183
- switch (parsed ? 0 : parts.length) {
1184
- case 0:
1185
- // already parsed
1186
- break;
1187
- case 1: // name without commas
1188
- // literal
1189
- if (marker.re.literalName.test(parts[0])) {
1190
- parsed = { literal: parts[0] };
1191
- }
1192
- else if (m = parts[0].replace(marker.re.space, ' ').match(prefix)) { // split on prefix
1193
- parsed = {
1194
- firstName: m[1],
1195
- prefix: m[2],
1196
- lastName: m[3], // eslint-disable-line no-magic-numbers
675
+ if (full)
676
+ value = full;
677
+ if (!sentenceCase)
678
+ return value;
679
+ }
680
+ if (field === 'crossref')
681
+ return value;
682
+ if (FieldAction.parseInt.includes(field) && value.trim().match(/^-?\d+$/))
683
+ return parseInt(value);
684
+ if (mode === 'title' && sentenceCase) {
685
+ if (!this.options.sentenceCase.guess || !(0, sentence_case_2.guessSentenceCased)(value, /\x0E\/?([a-z]+)\x0F/ig)) {
686
+ value = (0, sentence_case_2.toSentenceCase)(value, {
687
+ preserveQuoted: this.options.sentenceCase.preserveQuoted,
688
+ subSentenceCapitalization: this.options.sentenceCase.subSentence,
689
+ markup: /\x0E\/?([a-z]+)\x0F/ig,
690
+ nocase: /\x0E(ncx?)\x0F.*?\x0E\/\1\x0F/ig,
691
+ });
692
+ }
693
+ let cancel = (_match, stripped) => stripped;
694
+ switch (this.options.caseProtection) {
695
+ case 'strict':
696
+ cancel = (match, _stripped) => match;
697
+ break;
698
+ case 'as-needed':
699
+ cancel = (match, stripped) => {
700
+ const words = (0, sentence_case_2.tokenize)(stripped, /\x0E\/?([a-z]+)\x0F/ig);
701
+ return words.find(w => w.shape.match(/^(?!.*X).*x.*$/)) ? match : this.wrap(stripped, 'ncx');
1197
702
  };
1198
- }
1199
- else {
1200
- // top-level "firstname lastname"
1201
- const [firstName, lastName] = this.splitOnce(parts[0], marker.space, true);
1202
- if (lastName) {
1203
- parsed = { firstName, lastName };
1204
- }
1205
- else {
1206
- parsed = { lastName: firstName };
1207
- }
1208
- }
1209
- break;
1210
- case 2: // lastname, firstname
1211
- parsed = {
1212
- lastName: parts[0],
1213
- firstName: parts[1],
1214
- };
1215
- break;
1216
- default: // lastname, suffix, firstname
1217
- parsed = {
1218
- lastName: parts[0],
1219
- suffix: parts[1],
1220
- firstName: parts.slice(2).join(marker.comma),
1221
- };
1222
- }
1223
- for (const [k, v] of Object.entries(parsed)) {
1224
- if (typeof v !== 'string')
1225
- continue;
1226
- parsed[k] = marker.clean(v).trim();
703
+ break;
704
+ }
705
+ return value.replace(/\x0Enc\x0F(.*?)\x0E\/nc\x0F/ig, cancel);
1227
706
  }
1228
- return parsed;
707
+ return value;
1229
708
  }
1230
- restore(text, orig, preserve) {
1231
- for (const { start, end } of preserve) {
1232
- text = text.substring(0, start) + orig.substring(start, end) + text.substring(end);
709
+ field(entry, field, value, sentenceCase) {
710
+ const mode = this.mode(field);
711
+ const ast = unified_latex_util_pegjs_1.LatexPegParser.parse(value);
712
+ if (this.options.removeOuterBraces.includes(field) && ast.content.length === 1 && ast.content[0].type === 'group') {
713
+ ast.content = ast.content[0].content;
714
+ }
715
+ if (mode === 'verbatim') { // &^%@#&^%@# idiots wrapping verbatim fields
716
+ entry.fields[field] = (0, unified_latex_util_print_raw_1.printRaw)(ast);
717
+ return;
1233
718
  }
1234
- return text;
1235
- }
1236
- convert_entry(node) {
1237
- this.entry = {
1238
- key: node.id,
1239
- type: node.type,
1240
- fields: {},
1241
- creators: {},
1242
- crossref: {
1243
- inherited: [],
1244
- donated: [],
1245
- },
1246
- };
1247
- this.entries.push(this.entry);
1248
- // order these first for language-dependent sentence casing
1249
- const order = ['langid', 'hyphenation', 'language'];
1250
- node.fields.sort((a, b) => {
1251
- const ia = order.indexOf(a.name);
1252
- const ib = order.indexOf(b.name);
1253
- if (ia === -1 && ib === -1)
1254
- return a.name.localeCompare(b.name); // doesn't matter really
1255
- if (ia === -1)
1256
- return 1;
1257
- if (ib === -1)
1258
- return -1;
1259
- return ia - ib;
1260
- });
1261
- let sentenceCase = !!this.options.sentenceCase.length; // if sentenceCase is empty, no sentence casing
1262
- for (const field of node.fields) {
1263
- if (field.kind !== 'Field')
1264
- return this.error(new ParserError(`Expected Field, got ${field.kind}`, node));
1265
- this.startCleaning(field.name);
1266
- /*
1267
- if (this.options.raw && this.fieldType !== 'creator') {
1268
- this.entry.fields[field.name] = [ field.value.map(v => v.source).join('') ]
1269
- continue
1270
- }
1271
- */
1272
- this.field = {
1273
- name: field.name,
1274
- text: '',
1275
- level: 0,
1276
- words: {
1277
- upper: 0,
1278
- lower: 0,
1279
- other: 0,
1280
- },
1281
- preserveRanges: (sentenceCase && exports.fields.title.includes(field.name)) ? [] : null,
1282
- html: this.options.htmlFields.includes(field.name),
1283
- };
1284
- this.entry.fields[this.field.name] = this.entry.fields[this.field.name] || [];
1285
- // special case for 'title = 2020'
1286
- if (field.value.kind === 'Number') {
1287
- this.entry.fields[this.field.name].push(field.value.value);
1288
- this.field = null;
1289
- continue;
719
+ if (mode === 'title') {
720
+ let root = [...ast.content];
721
+ while (root.length) {
722
+ const node = root.shift();
723
+ // only root groups offer case protecten -- but it may be as an macro arg, so mark here before gobbling
724
+ if (this.protect(node))
725
+ node._renderInfo = { root: true };
726
+ // environments are considered root when at root
727
+ if (node.type === 'environment')
728
+ root = [...root, ...node.content];
1290
729
  }
1291
- this.convert(field.value);
1292
- this.field.text = this.field.text.trim();
1293
- this.field.text = this.field.text.replace(/<\/([a-z])><\1>/g, '');
1294
- this.field.text = this.field.text.replace(/<([a-z])>(\s*)<\/\1>/g, '$1');
1295
- this.field.text = this.field.text.replace(/<p>[ \r\n]*(<li>|<\/ol>|<\/ul>)/g, '$1');
1296
- if (!this.field.text)
1297
- continue;
1298
- // disable sentenceCasing if not an english
1299
- switch (this.field.name) {
1300
- case 'langid':
1301
- case 'hyphenation':
1302
- sentenceCase = sentenceCase && this.options.sentenceCase.includes(this.field.text.toLowerCase());
1303
- break;
1304
- case 'language':
1305
- sentenceCase = sentenceCase && !!(this.field.text.toLowerCase().trim().split(/\s*,\s*/).find(lang => this.options.sentenceCase.includes(lang)));
1306
- break;
730
+ }
731
+ // pass 0 -- register parse mode
732
+ (0, unified_latex_util_visit_1.visit)(ast, (node, info) => {
733
+ if (!node._renderInfo)
734
+ node._renderInfo = {};
735
+ node._renderInfo.mode = info.context.inMathMode ? 'math' : 'text';
736
+ // if (info.context.inMathMode || info.context.hasMathModeAncestor) return
737
+ if (mode === 'title' && node.type === 'inlinemath' && !info.parents.find(p => p._renderInfo.protectCase))
738
+ node._renderInfo.protectCase = true;
739
+ if (!info.context.inMathMode) {
740
+ if (mode === 'title' && node._renderInfo.root)
741
+ node._renderInfo.protectCase = true;
742
+ if (node.type === 'macro' && typeof node.escapeToken !== 'string')
743
+ node.escapeToken = '\\';
744
+ if (node.type === 'environment' && node.env === 'em')
745
+ node._renderInfo.emph = true;
1307
746
  }
1308
- // "groups" is a jabref 3.8+ monstrosity
1309
- if (this.field.name.match(/^(keywords?|groups)$/)) {
1310
- for (let text of this.field.text.split(marker.comma)) {
1311
- text = text.trim();
1312
- if (text)
1313
- this.entry.fields[this.field.name].push(text);
747
+ });
748
+ // pass 1 -- mark & normalize
749
+ (0, unified_latex_util_visit_1.visit)(ast, (nodes, info) => {
750
+ var _a;
751
+ let node;
752
+ const compacted = [];
753
+ let inif = 0;
754
+ while (nodes.length) {
755
+ if (node = this.ligature(nodes)) {
756
+ compacted.push(node);
757
+ continue;
1314
758
  }
1315
- }
1316
- else if (this.cleaning.type === 'creator') {
1317
- if (!this.entry.creators[this.field.name])
1318
- this.entry.creators[this.field.name] = [];
1319
- // {M. Halle, J. Bresnan, and G. Miller}
1320
- if (this.field.text.includes(`${marker.comma}${marker.and}`)) { //
1321
- this.field.text = this.field.text.replace(new RegExp(`${marker.comma}${marker.and}`, 'g'), marker.and).replace(new RegExp(marker.comma), marker.and);
759
+ node = nodes.shift();
760
+ if (node.type === 'macro' && node.content === 'ifdefined') {
761
+ inif += 1;
762
+ continue;
1322
763
  }
1323
- for (const creator of this.field.text.split(marker.and)) {
1324
- this.entry.fields[this.field.name].push(marker.clean(creator));
1325
- this.entry.creators[this.field.name].push(this.parseName(creator));
764
+ else if (node.type === 'macro' && node.content === 'else') {
765
+ continue;
1326
766
  }
1327
- }
1328
- else if (exports.fields.unabbrev.includes(field.name)) { // doesn't get sentence casing anyhow TODO: booktitle does!
1329
- this.entry.fields[this.field.name].push(this.options.unabbreviate[this.field.text] || this.field.text);
1330
- }
1331
- else {
1332
- if (this.field.preserveRanges) {
1333
- const allCaps = this.field.text.match(/\s/) && this.field.words.upper && !this.field.words.lower && !this.field.words.other;
1334
- const seemsSentenceCased = Math.max(this.field.words.upper, this.field.words.lower) > (this.field.words.other + Math.min(this.field.words.upper, this.field.words.lower));
1335
- if (!allCaps && seemsSentenceCased && this.options.guessAlreadySentenceCased) {
1336
- this.preserve(null, null); // , 'mostly sentence cased already')
767
+ else if (node.type === 'macro' && node.content === 'fi') {
768
+ inif = Math.max(inif - 1, 0);
769
+ continue;
770
+ }
771
+ else if (inif) {
772
+ continue;
773
+ }
774
+ const nargs = node.type === 'macro'
775
+ ? narguments[node.content] || narguments[`${info.context.inMathMode ? 'math' : 'text'}\t${node.content}`]
776
+ : 0;
777
+ if (node.type === 'macro' && nargs) {
778
+ node.args = Array(nargs).fill(undefined).map(_i => this.argument(nodes, node)).filter(arg => arg);
779
+ if (node.content.match(/^(url|href)$/) && node.args.length) {
780
+ let url = node.args[0].content;
781
+ if (url.length === 1 && url[0].type === 'group')
782
+ url = url[0].content;
783
+ node.args[0] = this.wraparg({ type: 'string', content: (0, unified_latex_util_print_raw_1.printRaw)(url), _renderInfo: { mode: url[0]._renderInfo.mode } }, node);
1337
784
  }
1338
- else {
1339
- if (allCaps)
1340
- this.field.preserveRanges = [];
1341
- const txt = this.field.text.replace(preserveCase.markup, markup => marker.markup.repeat(markup.length));
1342
- let match;
1343
- preserveCase.sentenceStart.lastIndex = 0;
1344
- while ((match = preserveCase.sentenceStart.exec(txt))) {
1345
- // exclude stuff like "U.S. Taxes"
1346
- // eslint doesn't grok these are numbers
1347
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
1348
- if (match.index > 2 && txt.substr(0, match.index + 1).match(preserveCase.acronym))
1349
- continue;
1350
- // eslint doesn't grok these are numbers
1351
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
1352
- this.preserve(match.index, match.index + match[0].length);
1353
- }
1354
- preserveCase.quoted.lastIndex = 0;
1355
- while ((match = preserveCase.quoted.exec(this.field.text))) {
1356
- // eslint doesn't grok these are numbers
1357
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
1358
- this.preserve(match.index, match.index + match[0].length);
785
+ }
786
+ else if (node.type === 'macro' && node.content.match(/^[a-z]+$/i) && ((_a = nodes[0]) === null || _a === void 0 ? void 0 : _a.type) === 'whitespace') {
787
+ nodes.shift();
788
+ }
789
+ compacted.push(node);
790
+ }
791
+ if (!info.context.inMathMode) {
792
+ // feed-forward inline macros
793
+ for (const [macro, markup] of Object.entries({ em: 'emph', it: 'emph', sl: 'emph', bf: 'bold', sc: 'smallCaps', tt: 'code' })) {
794
+ if (info.parents.find(p => p._renderInfo[markup]))
795
+ continue;
796
+ compacted.forEach((markup_node, i) => {
797
+ if (markup_node.type === 'macro' && markup_node.content === macro) {
798
+ compacted.slice(i + 1).forEach(n => n._renderInfo[markup] = true);
1359
799
  }
800
+ });
801
+ }
802
+ }
803
+ nodes.push(...compacted);
804
+ }, { test: Array.isArray, includeArrays: true });
805
+ (0, unified_latex_util_replace_1.replaceNode)(ast, (node, _info) => {
806
+ if (node.type !== 'macro')
807
+ return;
808
+ if (node.escapeToken && unicode2latex_1.combining.tounicode[node.content]) {
809
+ let arg;
810
+ // no args, args of zero length, or first arg has no content
811
+ if (!node.args || node.args.length === 0 || node.args[0].content.length === 0) {
812
+ arg = { type: 'string', content: ' ', _renderInfo: {} };
813
+ }
814
+ else if (node.args.length !== 1 || node.args[0].content.length !== 1) {
815
+ return;
816
+ }
817
+ else {
818
+ arg = node.args[0].content[0];
819
+ }
820
+ if (arg.type === 'group') {
821
+ switch (arg.content.length) {
822
+ case 0:
823
+ arg = { type: 'string', content: ' ', _renderInfo: {} };
824
+ break;
825
+ case 1:
826
+ arg = arg.content[0];
827
+ break;
828
+ default:
829
+ return;
1360
830
  }
1361
831
  }
1362
- this.entry.fields[this.field.name].push(this.convertToSentenceCase(this.field.text));
832
+ switch (arg.type) {
833
+ case 'verbatim':
834
+ case 'string':
835
+ return { type: 'string', content: `${arg.content}${unicode2latex_1.combining.tounicode[node.content]}`, _renderInfo: {} };
836
+ default:
837
+ return;
838
+ }
1363
839
  }
840
+ let latex = `${node.escapeToken}${node.content}`;
841
+ latex += (node.args || []).map(arg => (0, unified_latex_util_print_raw_1.printRaw)(this.argtogroup(arg))).join('');
842
+ if (latex in unicode2latex_1.latex2unicode)
843
+ return { type: 'string', content: latex2unicode(latex, node), _renderInfo: {} };
844
+ // return null to delete
845
+ });
846
+ switch (mode) {
847
+ case 'creator':
848
+ entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/).map(cr => this.parseCreator(cr)).filter(cr => Object.keys(cr).length);
849
+ break;
850
+ case 'commalist':
851
+ entry.fields[field] = this.split(ast, /^[;,]$/, /(&)/).map(elt => this.stringify(elt, { mode: 'literal' }).trim());
852
+ break;
853
+ case 'literallist':
854
+ entry.fields[field] = this.split(ast, /^and$/i, /(^& | & | &$)/).map(elt => this.stringify(elt, { mode: 'literal' }).trim());
855
+ break;
856
+ default:
857
+ entry.fields[field] = this.stringField(field, this.stringify(ast, { mode }), sentenceCase, mode);
858
+ break;
1364
859
  }
1365
- this.field = null;
860
+ // recursively restores markup in string content. 'as string' is just here to pacify typescript
861
+ entry.fields[field] = JSON.parse(JSON.stringify(entry.fields[field], (k, v) => (typeof v === 'string' ? this.restoreMarkup(v) : v)));
1366
862
  }
1367
- convertToSentenceCase(text) {
1368
- if (!this.field.preserveRanges)
1369
- return text;
1370
- const sentenceCased = this.restore((0, sentence_case_2.toSentenceCase)(text, this.options.sentenceCasePreserveQuoted || false), text, this.field.preserveRanges);
1371
- if (text !== sentenceCased)
1372
- this.entry.sentenceCased = true;
1373
- return sentenceCased;
1374
- }
1375
- convert_number(node) {
1376
- this.field.text += `${node.value}`;
863
+ reset(options = {}) {
864
+ var _a;
865
+ const sentenceCase = Object.assign({ langids: English, guess: true, preserveQuoted: true }, (options.sentenceCase || {}));
866
+ this.options = Object.assign(Object.assign({ caseProtection: 'as-needed', unabbreviations: true, applyCrossRef: (_a = options.applyCrossRef) !== null && _a !== void 0 ? _a : true, fieldMode: {} }, options), { sentenceCase });
867
+ if (this.options.caseProtection === true)
868
+ this.options.caseProtection = 'as-needed';
869
+ if (typeof this.options.sentenceCase.langids === 'boolean')
870
+ this.options.sentenceCase.langids = this.options.sentenceCase.langids ? English : [];
871
+ this.options.sentenceCase.langids = this.options.sentenceCase.langids.map(langid => langid.toLowerCase());
872
+ if (typeof this.options.unabbreviations === 'boolean')
873
+ this.options.unabbreviations = this.options.unabbreviations ? unabbreviations : {};
874
+ const unabbr = {};
875
+ for (const abbr in this.options.unabbreviations) { // eslint-disable-line guard-for-in
876
+ unabbr[abbr.toUpperCase()] = this.options.unabbreviations[abbr];
877
+ }
878
+ this.options.unabbreviations = unabbr;
879
+ this.fieldMode = Object.entries(exports.FieldMode).reduce((acc, [mode, test]) => {
880
+ const strings = test.filter(fieldname_or_regex => typeof fieldname_or_regex === 'string' && !this.options.fieldMode[fieldname_or_regex]);
881
+ const regexes = test.filter(fieldname_or_regex => typeof fieldname_or_regex !== 'string');
882
+ acc[mode] = [...strings, ...regexes];
883
+ return acc;
884
+ }, {});
885
+ for (const [field, mode] of Object.entries(this.options.fieldMode)) {
886
+ this.fieldMode[mode].unshift(field);
887
+ }
888
+ if (!this.options.removeOuterBraces) {
889
+ this.options.removeOuterBraces = [
890
+ ...FieldAction.removeOuterBraces,
891
+ ...this.fieldMode.title,
892
+ ...this.fieldMode.verbatim,
893
+ ].filter(field => typeof field === 'string');
894
+ }
895
+ this.fallback = options.unsupported === 'ignore' ? ((_node, _tex) => '') : options.unsupported;
896
+ this.bib = {
897
+ errors: [],
898
+ entries: [],
899
+ comments: [],
900
+ strings: {},
901
+ preamble: [],
902
+ jabref: null,
903
+ };
1377
904
  }
1378
- convert_text(node) {
1379
- if (node.mode === 'verbatim') {
1380
- this.field.text += node.value.trim();
1381
- return;
1382
- }
1383
- // heuristic to detect pre-sentencecased text
1384
- for (const word of node.value.split(/\b/)) {
1385
- if (word.match(preserveCase.allLower)) {
1386
- this.field.words.lower++;
1387
- }
1388
- else if (word.match(preserveCase.allCaps)) {
1389
- this.field.words.upper++;
1390
- }
1391
- else if (word.match(preserveCase.hasAlpha)) {
1392
- this.field.words.other++;
1393
- }
1394
- }
1395
- if (this.field.level === 0 && this.cleaning.type === 'creator') {
1396
- this.field.text += node.value.replace(/\s+and\s+/ig, marker.and).replace(/\s*,\s*/g, marker.comma).replace(/\s+/g, marker.space);
1397
- return;
1398
- }
1399
- if (this.field.level === 0 && this.field.name.match(/^(keywords?|groups)$/)) {
1400
- this.field.text += node.value.replace(/\s*[;,]\s*/g, marker.comma);
1401
- return;
1402
- }
1403
- if (this.field.html) {
1404
- this.field.text += node.value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1405
- }
1406
- else if (this.field.preserveRanges) {
1407
- const words = node.value.split(/(\s+)/);
1408
- for (const word of words) {
1409
- const start = this.field.text.length;
1410
- this.field.text += word;
1411
- if (this.preserveCase(word))
1412
- this.preserve(start, this.field.text.length); // , `word: ${JSON.stringify(word)}`)
905
+ reparse(entry) {
906
+ let langid = (entry.fields.langid || entry.fields.hyphenation || '').toLowerCase();
907
+ if (!langid && this.options.sentenceCase.language && entry.fields.language)
908
+ langid = entry.fields.language.toLowerCase();
909
+ const sentenceCase = this.options.sentenceCase.langids.includes(langid);
910
+ this.current = entry;
911
+ let keywords = []; // OMG #783
912
+ try {
913
+ for (const [field, value] of Object.entries(entry.fields)) {
914
+ if (!value.trim()) {
915
+ delete entry.fields[field];
916
+ continue;
917
+ }
918
+ this.field(entry, field, value, sentenceCase);
919
+ if (field.match(/^keywords\[\d+\]$/)) {
920
+ keywords = [...keywords, entry.fields[field]];
921
+ delete entry.fields[field];
922
+ }
923
+ if (typeof entry.fields[field] === 'string')
924
+ entry.fields[field] = entry.fields[field].trim();
1413
925
  }
926
+ if (keywords.length)
927
+ entry.fields.keywords = [...(new Set([...(entry.fields.keywords || []), ...keywords]))].sort();
928
+ this.bib.entries.push(entry);
1414
929
  }
1415
- else {
1416
- this.field.text += node.value;
930
+ catch (err) {
931
+ this.bib.errors.push({ error: `${err.message}\n${entry.input}`, input: entry.input });
1417
932
  }
1418
933
  }
1419
- convert_environment(node) {
1420
- const [open, close] = {
1421
- enumerate: ['<ol>', '</ol>'],
1422
- itemize: ['<ul>', '</ul>'],
1423
- quotation: ['<blockquote>', '</blockquote>'],
1424
- }[node.env] || ['', ''];
1425
- if (!open)
1426
- this.error(new TeXError(`Unhandled \\${node.env}{...}`, node, this.chunk));
1427
- this.field.text += open;
1428
- this.convert_block(Object.assign(Object.assign({}, node), { kind: 'Block', markup: {} }));
1429
- this.field.text += close;
934
+ content(tex) {
935
+ const entry = { key: '', type: '', fields: {} };
936
+ this.field(entry, 'tex', tex, false);
937
+ return entry.fields.tex;
1430
938
  }
1431
- convert_href(node) {
1432
- this.field.text += '<a href="';
1433
- this.convert(node.arguments.required[0]);
1434
- this.field.text += '">';
1435
- this.convert(node.arguments.required[1] || node.arguments.required[0]);
1436
- this.field.text += '</a>';
1437
- }
1438
- convert_block(node) {
1439
- const start = this.field.text.length;
1440
- let prefix = '';
1441
- let postfix = '';
1442
- if (this.options.caseProtection !== 'strict' && this.cleaning.type === 'other')
1443
- delete node.case;
1444
- if (this.cleaning.type === 'creator' && node.case === 'protect') {
1445
- prefix += marker.literal;
1446
- postfix = marker.literal + postfix;
1447
- delete node.case;
939
+ prep(base) {
940
+ for (const preamble of base.preambles) {
941
+ try {
942
+ this.content(preamble);
943
+ }
944
+ catch (err) {
945
+ }
1448
946
  }
1449
- if (node.case === 'protect') {
1450
- prefix += this.options.markup.caseProtect.open;
1451
- postfix = this.options.markup.caseProtect.close + postfix;
947
+ for (const [k, v] of Object.entries(base.strings)) {
948
+ this.bib.strings[k] = this.content(v);
1452
949
  }
1453
- if (node.kind === 'Block') {
1454
- for (const markup of Object.keys(node.markup)) {
1455
- if (!this.options.markup[markup]) {
1456
- this.error(new ParserError(`markup: ${markup}`, node));
1457
- return;
950
+ }
951
+ finalize(base) {
952
+ var _a;
953
+ if (this.options.applyCrossRef) {
954
+ const entries = {};
955
+ for (const entry of this.bib.entries) {
956
+ if (entry.key)
957
+ entries[entry.key.toUpperCase()] = entry;
958
+ }
959
+ const order = [];
960
+ for (const entry of this.bib.entries) {
961
+ if (!entry.key || typeof entry.fields.crossref !== 'string')
962
+ continue;
963
+ const crossref = entry.fields.crossref.toUpperCase();
964
+ if (!entries[crossref])
965
+ continue;
966
+ const key = entry.key.toUpperCase();
967
+ if (!order.includes(crossref))
968
+ order.unshift(crossref);
969
+ if (!order.includes(key))
970
+ order.push(key);
971
+ }
972
+ for (const key of order) {
973
+ const child = entries[key.toUpperCase()];
974
+ const parent = entries[(_a = child.fields.crossref) === null || _a === void 0 ? void 0 : _a.toUpperCase()];
975
+ if (!parent)
976
+ continue;
977
+ for (const mappings of [crossref_json_1.default[child.type], crossref_json_1.default['*']].filter(m => m)) {
978
+ for (const mapping of [mappings[parent.type], mappings['*']].filter(m => m)) {
979
+ for (const [childfield, parentfield] of Object.entries(mapping)) {
980
+ if (!child.fields[childfield] && parent.fields[parentfield])
981
+ child.fields[childfield] = parent.fields[parentfield];
982
+ }
983
+ for (const field of (fields_json_1.default[child.type] || [])) {
984
+ if (!child.fields[field] && parent.fields[field])
985
+ child.fields[field] = parent.fields[field];
986
+ }
987
+ }
1458
988
  }
1459
- prefix += this.options.markup[markup].open;
1460
- postfix = `${this.options.markup[markup].close}${postfix}`;
1461
989
  }
1462
990
  }
1463
- const end = {
1464
- withoutPrefix: this.field.text.length,
1465
- withPrefix: this.field.text.length + prefix.length,
1466
- };
1467
- this.field.text += prefix;
1468
- this.field.level++;
1469
- this.convert(node.value);
1470
- this.field.level--;
1471
- const added = this.field.text.substring(end.withPrefix);
1472
- const added_text = added.replace(/<\/?[^>]+>/g, '');
1473
- const needsProtection = added_text && ((this.options.caseProtection === 'strict' && added_text.match(preserveCase.isCaseSensitive))
1474
- ||
1475
- (this.options.caseProtection === 'as-needed' && added_text.split(/\s+/).find(word => this.needsProtection(word))));
1476
- if (!added) { // nothing was added, so remove prefix
1477
- this.field.text = this.field.text.substring(0, end.withoutPrefix);
1478
- }
1479
- else if (this.field.preserveRanges && prefix === this.options.markup.caseProtect.open && !needsProtection) {
1480
- // something was added that didn't actually need case protection
1481
- this.field.text = this.field.text.substring(0, end.withoutPrefix) + added;
1482
- this.field.preserveRanges = this.field.preserveRanges.filter(range => range.start < end.withoutPrefix);
1483
- }
1484
- else {
1485
- this.field.text += postfix;
991
+ const { comments, jabref } = JabRef.parse(base.comments);
992
+ this.bib.comments = comments;
993
+ this.bib.jabref = jabref;
994
+ this.bib.preamble = base.preambles;
995
+ this.bib.errors = [...base.errors, ...this.bib.errors];
996
+ }
997
+ parse(input, options = {}) {
998
+ this.reset(options);
999
+ const base = bibtex.parse(input);
1000
+ this.prep(base);
1001
+ for (const entry of base.entries) {
1002
+ this.reparse(entry);
1003
+ }
1004
+ this.finalize(base);
1005
+ return this.bib;
1006
+ }
1007
+ async parseAsync(input, options = {}) {
1008
+ var _a, e_1, _b, _c;
1009
+ this.reset(options);
1010
+ const base = await bibtex.promises.parse(input);
1011
+ this.prep(base);
1012
+ try {
1013
+ for (var _d = true, _e = __asyncValues(asyncGenerator(base.entries)), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
1014
+ _c = _f.value;
1015
+ _d = false;
1016
+ const entry = _c;
1017
+ this.reparse(entry);
1018
+ }
1486
1019
  }
1487
- this.field.text = this.field.text.replace(/<(sup|sub)>([^<>]+)<\/\1>$/i, (m, mode, chars) => {
1488
- const cmd = mode === 'sup' ? '^' : '_';
1489
- let script = '';
1490
- for (const char of chars) {
1491
- const unicode = unicode2latex_1.latex2unicode[`${cmd}${char}`] || unicode2latex_1.latex2unicode[`${cmd}{${char}}`];
1492
- script += unicode ? unicode : `<${mode}>${char}</${mode}>`;
1020
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
1021
+ finally {
1022
+ try {
1023
+ if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
1493
1024
  }
1494
- script = script.replace(new RegExp(`</${mode}><${mode}>`, 'g'), '');
1495
- return script.length < m.length ? script : m;
1496
- });
1497
- if (node.case && this.field.preserveRanges)
1498
- this.preserve(start, this.field.text.length); // , 'in convert-block ' + node.source || '<source>')
1025
+ finally { if (e_1) throw e_1.error; }
1026
+ }
1027
+ this.finalize(base);
1028
+ return this.bib;
1499
1029
  }
1500
1030
  }
1501
- /**
1502
- * parse bibtex. This will try to convert TeX commands into unicode equivalents, and apply `@string` expansion
1503
- */
1504
1031
  function parse(input, options = {}) {
1505
- const parser = new Parser(options);
1506
- return parser.parse(input);
1032
+ const parser = new BibTeXParser;
1033
+ return parser.parse(input, options);
1507
1034
  }
1508
1035
  exports.parse = parse;
1509
- function ast(input, options = {}, clean = true) {
1510
- const parser = new Parser(options);
1511
- return parser.ast(input, clean);
1036
+ async function parseAsync(input, options = {}) {
1037
+ const parser = new BibTeXParser;
1038
+ return await parser.parseAsync(input, options);
1512
1039
  }
1513
- exports.ast = ast;
1514
- exports.promises = {
1515
- async parse(input, options = {}) {
1516
- const parser = new Parser(options);
1517
- return await parser.parseAsync(input);
1518
- },
1519
- };
1040
+ exports.parseAsync = parseAsync;
1520
1041
  //# sourceMappingURL=index.js.map