@helloao/tools 0.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/generation/api.d.ts +203 -0
  3. package/generation/api.js +153 -0
  4. package/generation/api.spec.d.ts +2 -0
  5. package/generation/api.spec.js +463 -0
  6. package/generation/audio.d.ts +20 -0
  7. package/generation/audio.js +60 -0
  8. package/generation/audio.spec.d.ts +2 -0
  9. package/generation/audio.spec.js +36 -0
  10. package/generation/book-order.d.ts +15 -0
  11. package/generation/book-order.js +494 -0
  12. package/generation/book-order.spec.d.ts +2 -0
  13. package/generation/book-order.spec.js +8 -0
  14. package/generation/common-types.d.ts +327 -0
  15. package/generation/common-types.js +2 -0
  16. package/generation/dataset.d.ts +34 -0
  17. package/generation/dataset.js +106 -0
  18. package/generation/index.d.ts +7 -0
  19. package/generation/index.js +38 -0
  20. package/index.d.ts +5 -0
  21. package/index.js +32 -0
  22. package/package.json +27 -0
  23. package/parser/codex-parser.d.ts +97 -0
  24. package/parser/codex-parser.js +160 -0
  25. package/parser/codex-parser.spec.d.ts +2 -0
  26. package/parser/codex-parser.spec.js +645 -0
  27. package/parser/index.d.ts +7 -0
  28. package/parser/index.js +35 -0
  29. package/parser/iterators.d.ts +89 -0
  30. package/parser/iterators.js +222 -0
  31. package/parser/iterators.spec.d.ts +2 -0
  32. package/parser/iterators.spec.js +174 -0
  33. package/parser/types.d.ts +144 -0
  34. package/parser/types.js +2 -0
  35. package/parser/usfm-parser.d.ts +135 -0
  36. package/parser/usfm-parser.js +840 -0
  37. package/parser/usfm-parser.spec.d.ts +2 -0
  38. package/parser/usfm-parser.spec.js +1593 -0
  39. package/parser/usx-parser.d.ts +33 -0
  40. package/parser/usx-parser.js +425 -0
  41. package/parser/usx-parser.spec.d.ts +2 -0
  42. package/parser/usx-parser.spec.js +1260 -0
  43. package/typings/types.d.ts +14 -0
  44. package/utils.d.ts +29 -0
  45. package/utils.js +73 -0
  46. package/utils.spec.d.ts +2 -0
  47. package/utils.spec.js +42 -0
@@ -0,0 +1,840 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UsfmParser = exports.UsfmTokenizer = void 0;
4
+ exports.isDigit = isDigit;
5
+ exports.isWhitespace = isWhitespace;
6
+ exports.t = t;
7
+ exports.loc = loc;
8
+ exports.marker = marker;
9
+ exports.word = word;
10
+ exports.whitespace = whitespace;
11
+ const lodash_1 = require("lodash");
12
+ /**
13
+ * Defines a class that can tokenize a stream of characters into tokens.
14
+ */
15
+ class UsfmTokenizer {
16
+ _input = '';
17
+ _index = 0;
18
+ _start = 0;
19
+ get _tokenLength() {
20
+ return this._index - this._start;
21
+ }
22
+ /**
23
+ * Converts the given input into a list of tokens.
24
+ * @param input The input that should be tokenized.
25
+ */
26
+ tokenize(input) {
27
+ this._input = input;
28
+ this._index = 0;
29
+ return this._parseTokens();
30
+ }
31
+ _parseTokens() {
32
+ let tokens = [];
33
+ let token = this._parseToken();
34
+ while (token) {
35
+ tokens.push(token);
36
+ token = this._parseToken();
37
+ }
38
+ return tokens;
39
+ }
40
+ _parseToken() {
41
+ let state = 'none';
42
+ let kind = null;
43
+ this._start = this._index;
44
+ while (this._index < this._input.length) {
45
+ const codePointNumber = this._input.codePointAt(this._index);
46
+ if (typeof codePointNumber === 'undefined') {
47
+ throw new Error('Unable to get code point!');
48
+ }
49
+ const codePoint = String.fromCodePoint(codePointNumber);
50
+ if (state === 'none') {
51
+ if (codePoint === '\\') {
52
+ state = 'marker_start';
53
+ }
54
+ else if (isWhitespace(codePoint)) {
55
+ state = 'whitespace';
56
+ }
57
+ else {
58
+ state = 'word';
59
+ }
60
+ }
61
+ else if (state === 'marker_start') {
62
+ if (isDigit(codePoint)) {
63
+ if (this._tokenLength === 0) {
64
+ throw new Error('Invalid Marker: Markers must not contain only digits.');
65
+ }
66
+ state = 'marker_number';
67
+ }
68
+ else if (codePoint === '*') {
69
+ this._index += codePoint.length;
70
+ kind = 'marker';
71
+ break;
72
+ }
73
+ else if (isWhitespace(codePoint)) {
74
+ kind = 'marker';
75
+ break;
76
+ }
77
+ }
78
+ else if (state === 'marker_number') {
79
+ if (codePoint === '*') {
80
+ this._index += codePoint.length;
81
+ kind = 'marker';
82
+ break;
83
+ }
84
+ else if (!isDigit(codePoint)) {
85
+ kind = 'marker';
86
+ break;
87
+ }
88
+ }
89
+ else if (state === 'whitespace') {
90
+ if (!isWhitespace(codePoint)) {
91
+ kind = 'whitespace';
92
+ break;
93
+ }
94
+ }
95
+ else if (state === 'word') {
96
+ if (isWhitespace(codePoint) || codePoint === '\\') {
97
+ kind = 'word';
98
+ break;
99
+ }
100
+ }
101
+ this._index += codePoint.length;
102
+ }
103
+ if (!kind) {
104
+ if (this._index >= this._input.length) {
105
+ if (state == 'marker_start' || state === 'marker_number') {
106
+ kind = 'marker';
107
+ }
108
+ else if (state === 'word') {
109
+ kind = 'word';
110
+ }
111
+ else if (state === 'whitespace') {
112
+ kind = 'whitespace';
113
+ }
114
+ }
115
+ }
116
+ if (kind) {
117
+ return t(loc(this._start, this._index), kind);
118
+ }
119
+ return null;
120
+ }
121
+ }
122
+ exports.UsfmTokenizer = UsfmTokenizer;
123
+ /**
124
+ * Defines a USFM Parser.
125
+ */
126
+ class UsfmParser {
127
+ _poem = null;
128
+ _wordsOfJesus = false;
129
+ tokenize(input) {
130
+ const simpleTokens = new UsfmTokenizer().tokenize(input);
131
+ let tokens = [];
132
+ for (let t of simpleTokens) {
133
+ if (t.kind === 'marker') {
134
+ let source = input.substring(t.loc.start, t.loc.end);
135
+ const isEnd = source.endsWith('*');
136
+ if (isEnd) {
137
+ source = source.substring(0, source.length - 1);
138
+ }
139
+ let numberIndex = -1;
140
+ for (let i = 0; i < source.length; i++) {
141
+ if (isDigit(source[i])) {
142
+ numberIndex = i;
143
+ break;
144
+ }
145
+ }
146
+ let number = null;
147
+ if (numberIndex === 1) {
148
+ throw new Error('Markers must not be made only of numbers!');
149
+ }
150
+ if (numberIndex > 0) {
151
+ number = parseInt(source.substring(numberIndex));
152
+ source = source.substring(0, numberIndex);
153
+ }
154
+ if (source.length === 1) {
155
+ if (isEnd) {
156
+ // Ending marker does not have a command.
157
+ // We should look for a matching start marker.
158
+ const startMarker = (0, lodash_1.findLast)(tokens, t => t.kind === 'marker' && t.type === 'start');
159
+ if (startMarker) {
160
+ source = startMarker.command;
161
+ }
162
+ }
163
+ if (source.length === 1) {
164
+ throw new Error(`Markers must have a command! Token: ${t.loc.start}-${t.loc.end}`);
165
+ }
166
+ }
167
+ tokens.push(marker(t.loc, source, number, isEnd ? 'end' : 'start'));
168
+ }
169
+ else if (t.kind === 'whitespace') {
170
+ let source = input.substring(t.loc.start, t.loc.end);
171
+ tokens.push(whitespace(t.loc, source));
172
+ }
173
+ else if (t.kind === 'word') {
174
+ let source = input.substring(t.loc.start, t.loc.end);
175
+ tokens.push(word(t.loc, source));
176
+ }
177
+ }
178
+ return tokens;
179
+ }
180
+ parse(input) {
181
+ let root = {
182
+ type: 'root',
183
+ content: []
184
+ };
185
+ const tokens = this.tokenize(input);
186
+ let expectingId = 0;
187
+ let expectingName = 0;
188
+ let expectingTitle = 0;
189
+ let expectingSectionHeading = 0;
190
+ let expectingFootnote = 0;
191
+ let expectingFootnoteReference = 0;
192
+ let expectingFootnoteText = 0;
193
+ let expectingReferenceText = 0;
194
+ let expectingWordAttribute = 0;
195
+ let expectingNestedWordAttribute = 0;
196
+ let expectingWordsOfJesus = 0;
197
+ let expectingIntroParagraph = 0;
198
+ let expectingCrossReference = 0;
199
+ let expectingUnknownCommand = 0;
200
+ let canParseFootnotes = true;
201
+ let chapter = null;
202
+ let lastVerse = null;
203
+ let verse = null;
204
+ let subtitle = null;
205
+ let words = [];
206
+ let verseContent = [];
207
+ let sectionContent = '';
208
+ let currentFootnoteId = 0;
209
+ let footnote = null;
210
+ this._poem = null;
211
+ const addWordsToVerseOrSubtitle = () => {
212
+ if (words.length > 0) {
213
+ const text = this._text(words.join('').trimEnd());
214
+ if (verse) {
215
+ verse.content.push(text);
216
+ }
217
+ else if (subtitle) {
218
+ subtitle.content.push(text);
219
+ }
220
+ else {
221
+ verseContent.push(text);
222
+ }
223
+ words = [];
224
+ }
225
+ };
226
+ const addVerseContentToChapter = (token) => {
227
+ if (!chapter) {
228
+ return;
229
+ }
230
+ if (verseContent.length > 0) {
231
+ if (chapter.content.some(c => c.type === 'verse')) {
232
+ this._throwError(input, token, 'Cannot infer first verse after other verses have been added to the chapter!');
233
+ }
234
+ // Implicit first verse
235
+ verse = {
236
+ type: 'verse',
237
+ number: 1,
238
+ content: verseContent
239
+ };
240
+ chapter.content.push(verse);
241
+ verseContent = [];
242
+ }
243
+ };
244
+ const cleanupVerse = () => {
245
+ if (!verse || !chapter) {
246
+ return;
247
+ }
248
+ let chapterContent = [];
249
+ for (let i = verse.content.length - 1; i >= 0; i--) {
250
+ let content = verse.content[i];
251
+ if (typeof content === 'object' && 'heading' in content) {
252
+ // move headings that occur at the end of a verse to the chapter
253
+ chapterContent.unshift({
254
+ type: 'heading',
255
+ content: [content.heading]
256
+ });
257
+ verse.content.splice(i, 1);
258
+ }
259
+ else if (typeof content === 'object' && 'lineBreak' in content && content.lineBreak) {
260
+ // move line breaks that occur at the end of a verse to the chapter
261
+ chapterContent.unshift({
262
+ type: 'line_break'
263
+ });
264
+ verse.content.splice(i, 1);
265
+ }
266
+ else {
267
+ break;
268
+ }
269
+ }
270
+ for (let content of chapterContent) {
271
+ chapter.content.push(content);
272
+ }
273
+ };
274
+ const completeVerseOrSubtitle = (token) => {
275
+ if (verse && isNaN(verse.number)) {
276
+ // Verse is invalid for some reason.
277
+ const index = chapter.content.indexOf(verse);
278
+ if (index >= 0) {
279
+ chapter.content.splice(index, 1);
280
+ }
281
+ verse = null;
282
+ }
283
+ if (verse || subtitle) {
284
+ addWordsToVerseOrSubtitle();
285
+ }
286
+ addVerseContentToChapter(token);
287
+ cleanupVerse();
288
+ };
289
+ const completeSection = () => {
290
+ if (expectingSectionHeading > 0) {
291
+ if (verse) {
292
+ addWordsToVerseOrSubtitle();
293
+ verse.content.push({
294
+ heading: sectionContent
295
+ });
296
+ }
297
+ else if (chapter) {
298
+ chapter.content.push({
299
+ type: 'heading',
300
+ content: [sectionContent]
301
+ });
302
+ }
303
+ else {
304
+ root.content.push({
305
+ type: 'heading',
306
+ content: [sectionContent]
307
+ });
308
+ }
309
+ sectionContent = '';
310
+ expectingSectionHeading = 0;
311
+ }
312
+ };
313
+ const addWordsToFootnote = () => {
314
+ if (footnote && words.length > 0) {
315
+ footnote.text += words.join(' ');
316
+ words = [];
317
+ }
318
+ };
319
+ for (let token of tokens) {
320
+ if (token.kind === 'marker') {
321
+ if (token.command === '\\c') {
322
+ addWordsToVerseOrSubtitle();
323
+ cleanupVerse();
324
+ chapter = {
325
+ type: 'chapter',
326
+ number: NaN,
327
+ content: [],
328
+ footnotes: [],
329
+ };
330
+ verse = null;
331
+ verseContent = [];
332
+ root.content.push(chapter);
333
+ }
334
+ else if (token.command === '\\v') {
335
+ if (!chapter) {
336
+ this._throwError(input, token, 'Cannot parse a verse without chapter information!');
337
+ }
338
+ else {
339
+ completeSection();
340
+ completeVerseOrSubtitle(token);
341
+ lastVerse = verse;
342
+ verse = {
343
+ type: 'verse',
344
+ number: NaN,
345
+ content: []
346
+ };
347
+ chapter.content.push(verse);
348
+ }
349
+ }
350
+ else if (token.command === '\\d') {
351
+ if (!chapter) {
352
+ this._throwError(input, token, 'Cannot parse a hebrew subtitle without chapter information!');
353
+ }
354
+ else {
355
+ completeVerseOrSubtitle(token);
356
+ subtitle = {
357
+ type: 'hebrew_subtitle',
358
+ content: []
359
+ };
360
+ chapter.content.push(subtitle);
361
+ }
362
+ }
363
+ else if (token.command === '\\b' || token.command === '\\p') {
364
+ if (!chapter) {
365
+ this._throwError(input, token, 'Cannot parse a line break without chapter information!');
366
+ }
367
+ else {
368
+ if (verse) {
369
+ addWordsToVerseOrSubtitle();
370
+ verse.content.push({
371
+ lineBreak: true
372
+ });
373
+ }
374
+ else {
375
+ completeVerseOrSubtitle(token);
376
+ chapter.content.push({
377
+ type: 'line_break'
378
+ });
379
+ }
380
+ }
381
+ }
382
+ else if (token.command === '\\q') {
383
+ addWordsToVerseOrSubtitle();
384
+ this._poem = token.number;
385
+ }
386
+ else if (token.command === '\\p') {
387
+ addWordsToVerseOrSubtitle();
388
+ this._poem = null;
389
+ }
390
+ else if (token.command === '\\id') {
391
+ expectingId = 1;
392
+ }
393
+ else if (token.command === '\\h') {
394
+ expectingName = 1;
395
+ root.header = undefined;
396
+ }
397
+ else if (token.command === '\\mt' || token.command === '\\+mt') {
398
+ expectingTitle = 1;
399
+ }
400
+ else if (token.command === '\\s') {
401
+ expectingSectionHeading = 1;
402
+ }
403
+ else if (token.command === '\\r') {
404
+ expectingReferenceText = 1;
405
+ }
406
+ else if (token.command === '\\f' && canParseFootnotes) {
407
+ if (token.type === 'start') {
408
+ if (!chapter) {
409
+ this._throwError(input, token, 'Cannot start a footnote outside of a chapter!', true);
410
+ }
411
+ else {
412
+ addWordsToVerseOrSubtitle();
413
+ footnote = {
414
+ noteId: currentFootnoteId,
415
+ text: '',
416
+ caller: null
417
+ };
418
+ const ref = {
419
+ noteId: footnote.noteId
420
+ };
421
+ expectingFootnote = 1;
422
+ chapter.footnotes.push(footnote);
423
+ if (verse) {
424
+ verse.content.push(ref);
425
+ }
426
+ else if (subtitle) {
427
+ subtitle.content.push(ref);
428
+ }
429
+ else {
430
+ verseContent.push(ref);
431
+ }
432
+ currentFootnoteId += 1;
433
+ }
434
+ }
435
+ else {
436
+ addWordsToFootnote();
437
+ expectingFootnote = 0;
438
+ expectingFootnoteText = 0;
439
+ expectingFootnoteReference = 0;
440
+ footnote = null;
441
+ }
442
+ }
443
+ else if (token.command === '\\fr' && canParseFootnotes) {
444
+ if (!footnote) {
445
+ this._throwError(input, token, 'Cannot start a footnote reference outside of a footnote!', true);
446
+ }
447
+ else {
448
+ expectingFootnoteReference = 1;
449
+ }
450
+ }
451
+ else if (token.command === '\\ft' && canParseFootnotes) {
452
+ if (!footnote) {
453
+ this._throwError(input, token, 'Cannot start footnote text outside of a footnote!', true);
454
+ }
455
+ else {
456
+ expectingFootnoteText = 1;
457
+ }
458
+ }
459
+ else if (token.command === '\\w') {
460
+ if (token.type === 'start') {
461
+ expectingWordAttribute = 1;
462
+ }
463
+ else {
464
+ expectingWordAttribute = 0;
465
+ }
466
+ }
467
+ else if (token.command === '\\+w') {
468
+ if (token.type === 'start') {
469
+ expectingNestedWordAttribute = 1;
470
+ }
471
+ else {
472
+ expectingNestedWordAttribute = 0;
473
+ }
474
+ }
475
+ else if (token.command === '\\wj' || token.command === '\\+wj') {
476
+ if (token.type === 'start') {
477
+ addWordsToVerseOrSubtitle();
478
+ this._wordsOfJesus = true;
479
+ }
480
+ else {
481
+ addWordsToVerseOrSubtitle();
482
+ this._wordsOfJesus = false;
483
+ }
484
+ }
485
+ else if (token.command === '\\ip') {
486
+ expectingIntroParagraph = 1;
487
+ canParseFootnotes = false;
488
+ }
489
+ else if (token.command === '\\x') {
490
+ if (token.type === 'start') {
491
+ expectingCrossReference = 1;
492
+ }
493
+ else {
494
+ expectingCrossReference = 0;
495
+ }
496
+ }
497
+ else if (token.command.indexOf('-') >= 0) {
498
+ if (token.type === 'start') {
499
+ expectingUnknownCommand = 1;
500
+ }
501
+ }
502
+ else if (token.type === 'end') {
503
+ expectingUnknownCommand = 0;
504
+ }
505
+ }
506
+ else if (token.kind === 'word') {
507
+ if (expectingId > 0) {
508
+ if (expectingId === 1) {
509
+ root.id = token.word;
510
+ expectingId = 2;
511
+ }
512
+ }
513
+ else if (expectingName > 0) {
514
+ if (root.header) {
515
+ root.header += ' ' + token.word;
516
+ }
517
+ else {
518
+ root.header = token.word;
519
+ }
520
+ }
521
+ else if (expectingTitle > 0) {
522
+ if (root.title) {
523
+ root.title += ' ' + token.word;
524
+ }
525
+ else {
526
+ root.title = token.word;
527
+ }
528
+ }
529
+ else if (expectingSectionHeading > 0) {
530
+ if (sectionContent) {
531
+ sectionContent += ' ' + token.word;
532
+ }
533
+ else {
534
+ sectionContent = token.word;
535
+ }
536
+ }
537
+ else if (expectingFootnoteReference > 0) {
538
+ if (expectingFootnoteReference = 1) {
539
+ const [chapter, verse] = token.word.split(/[\.\:]/);
540
+ if (footnote) {
541
+ footnote.reference = {
542
+ chapter: parseInt(chapter),
543
+ verse: parseInt(verse)
544
+ };
545
+ }
546
+ expectingFootnoteReference = 0;
547
+ }
548
+ }
549
+ else if (expectingFootnoteText > 0) {
550
+ words.push(token.word);
551
+ }
552
+ else if (expectingFootnote > 0) {
553
+ if (expectingFootnote === 1) {
554
+ if (token.word) {
555
+ if (footnote) {
556
+ if (token.word === '+' || token.word !== '-') {
557
+ footnote.caller = token.word;
558
+ }
559
+ else {
560
+ footnote.caller = null;
561
+ }
562
+ }
563
+ // this._throwError(input, token, 'Footnotes must use the "+" caller.');
564
+ }
565
+ expectingFootnote = 2;
566
+ }
567
+ else {
568
+ words.push(token.word);
569
+ }
570
+ }
571
+ else if (expectingReferenceText > 0) {
572
+ // Skip processing words for references
573
+ // because references aren't included in the JSON format
574
+ // (for now)
575
+ }
576
+ else if (expectingCrossReference > 0) {
577
+ // Skip processing words for cross references
578
+ }
579
+ else if (chapter && isNaN(chapter.number)) {
580
+ chapter.number = parseInt(token.word);
581
+ if (isNaN(chapter.number)) {
582
+ this._throwError(input, token, 'The first word token after a chapter marker must be parsable to an integer!');
583
+ }
584
+ }
585
+ else if (verse && isNaN(verse.number)) {
586
+ verse.number = parseInt(token.word);
587
+ if (isNaN(verse.number)) {
588
+ this._throwError(input, token, 'The first word token after a verse marker must be parsable to an integer!');
589
+ }
590
+ }
591
+ else if (expectingWordAttribute > 0) {
592
+ if (expectingWordAttribute === 1) {
593
+ const firstVerticalBarIndex = token.word.indexOf('|');
594
+ if (firstVerticalBarIndex >= 0) {
595
+ const name = token.word.slice(0, firstVerticalBarIndex);
596
+ // const rest = token.word.slice(firstVerticalBarIndex + '|'.length);
597
+ words.push(name);
598
+ expectingWordAttribute = 2;
599
+ }
600
+ else {
601
+ words.push(token.word);
602
+ }
603
+ }
604
+ }
605
+ else if (expectingNestedWordAttribute > 0) {
606
+ if (expectingNestedWordAttribute === 1) {
607
+ const firstVerticalBarIndex = token.word.indexOf('|');
608
+ if (firstVerticalBarIndex >= 0) {
609
+ const name = token.word.slice(0, firstVerticalBarIndex);
610
+ // const rest = token.word.slice(firstVerticalBarIndex + '|'.length);
611
+ words.push(name);
612
+ expectingNestedWordAttribute = 2;
613
+ }
614
+ else {
615
+ words.push(token.word);
616
+ }
617
+ }
618
+ }
619
+ else if (expectingUnknownCommand > 0) {
620
+ }
621
+ else if (expectingIntroParagraph > 0) {
622
+ // Skip processing words for intro paragraphs
623
+ }
624
+ else {
625
+ words.push(token.word);
626
+ }
627
+ }
628
+ else if (token.kind === 'whitespace') {
629
+ if (expectingId > 0) {
630
+ if (token.whitespace.includes('\n')) {
631
+ expectingId = 0;
632
+ }
633
+ }
634
+ else if (expectingName > 0) {
635
+ if (token.whitespace.includes('\n')) {
636
+ expectingName = 0;
637
+ }
638
+ }
639
+ else if (expectingTitle > 0) {
640
+ if (token.whitespace.includes('\n')) {
641
+ expectingTitle = 0;
642
+ }
643
+ }
644
+ else if (expectingSectionHeading > 0) {
645
+ if (token.whitespace.includes('\n')) {
646
+ completeSection();
647
+ }
648
+ }
649
+ else if (expectingReferenceText > 0) {
650
+ if (token.whitespace.includes('\n')) {
651
+ expectingReferenceText = 0;
652
+ }
653
+ }
654
+ else if (expectingIntroParagraph > 0) {
655
+ if (token.whitespace.includes('\n')) {
656
+ expectingIntroParagraph = 0;
657
+ canParseFootnotes = true;
658
+ }
659
+ }
660
+ else if (expectingId > 0 || expectingName > 0 || expectingTitle > 0 || expectingSectionHeading > 0 || expectingFootnote > 0 || expectingFootnoteReference > 0 || expectingFootnoteText > 0 || expectingReferenceText > 0 || expectingCrossReference > 0 || expectingWordAttribute > 0 || expectingNestedWordAttribute > 0) {
661
+ // Skip
662
+ }
663
+ else if (expectingUnknownCommand > 0) {
664
+ if (token.whitespace.includes('\n')) {
665
+ expectingUnknownCommand = 0;
666
+ }
667
+ }
668
+ else if (words.length > 0) {
669
+ let lastWord = words[words.length - 1];
670
+ if (lastWord !== ' ') {
671
+ words.push(' ');
672
+ }
673
+ }
674
+ }
675
+ }
676
+ completeVerseOrSubtitle(null);
677
+ return root;
678
+ }
679
+ renderMarkdown(tree) {
680
+ let md = '';
681
+ if (tree.header) {
682
+ md += `# ${tree.header}\n`;
683
+ }
684
+ for (let c of tree.content) {
685
+ if (c.type === 'heading') {
686
+ md += `## ${c.content.join(' ')}\n`;
687
+ }
688
+ else if (c.type === 'chapter') {
689
+ md += `### ${c.number}\n`;
690
+ for (let content of c.content) {
691
+ if (content.type === 'heading') {
692
+ md += `#### ${content.content.join(' ')}\n`;
693
+ }
694
+ else if (content.type === 'line_break') {
695
+ md += '\n\n';
696
+ }
697
+ else if (content.type === 'verse') {
698
+ md += `<em>${content.number}</em>`;
699
+ for (let v of content.content) {
700
+ if (typeof v === 'string') {
701
+ md += v + ' ';
702
+ }
703
+ else if ('text' in v) {
704
+ md += v.text + ' ';
705
+ }
706
+ }
707
+ md += '\n';
708
+ }
709
+ }
710
+ }
711
+ }
712
+ return md;
713
+ }
714
+ _hasAttribute() {
715
+ return this._poem !== null || this._wordsOfJesus;
716
+ }
717
+ _text(text) {
718
+ if (!this._hasAttribute()) {
719
+ return text;
720
+ }
721
+ const t = {
722
+ text
723
+ };
724
+ if (this._poem !== null) {
725
+ t.poem = this._poem;
726
+ }
727
+ if (this._wordsOfJesus) {
728
+ t.wordsOfJesus = true;
729
+ }
730
+ return t;
731
+ }
732
+ _throwError(source, token, message, warn = false) {
733
+ if (token) {
734
+ let line = 1;
735
+ let column = 1;
736
+ let start = token.loc.start;
737
+ for (let i = 0; i < start; i++) {
738
+ let char = source[i];
739
+ if (char === '\n') {
740
+ line += 1;
741
+ column = 1;
742
+ }
743
+ else {
744
+ column += 1;
745
+ }
746
+ }
747
+ let tokenDebug = '';
748
+ if (token.kind === 'word') {
749
+ tokenDebug = ', word';
750
+ }
751
+ else if (token.kind === 'marker') {
752
+ tokenDebug = ', ' + token.command;
753
+ }
754
+ else {
755
+ tokenDebug = '';
756
+ }
757
+ if (warn) {
758
+ console.warn(`(${line}, ${column}${tokenDebug}) ${message}`);
759
+ }
760
+ else {
761
+ throw new Error(`(${line}, ${column}${tokenDebug}) ${message}`);
762
+ }
763
+ }
764
+ else {
765
+ if (warn) {
766
+ console.warn(message);
767
+ }
768
+ else {
769
+ throw new Error(message);
770
+ }
771
+ }
772
+ }
773
+ }
774
+ exports.UsfmParser = UsfmParser;
775
+ /**
776
+ * Determines if the given character is a digit.
777
+ * @param char The character.
778
+ */
779
+ function isDigit(char) {
780
+ return char.length === 1 && char >= '0' && char <= '9';
781
+ }
782
+ /**
783
+ * Determines if the given character is considered whitespace.
784
+ * @param char The character.
785
+ */
786
+ function isWhitespace(char) {
787
+ return char === ' ' || char === '\t' || char === '\n' || char === '\r';
788
+ }
789
+ function t(loc, kind) {
790
+ return {
791
+ loc,
792
+ kind
793
+ };
794
+ }
795
+ /**
796
+ * Creates a new source location.
797
+ * @param start The start of the location.
798
+ * @param end The end of the location.
799
+ */
800
+ function loc(start, end) {
801
+ return {
802
+ start,
803
+ end
804
+ };
805
+ }
806
+ /**
807
+ * Creates a new marker token.
808
+ * @param loc The location for the token.
809
+ * @param command The command that the token contains.
810
+ * @param number The number that the marker contains.
811
+ * @param type The type of the marker.
812
+ */
813
+ function marker(loc, command, number = null, type = 'start') {
814
+ return {
815
+ kind: 'marker',
816
+ loc,
817
+ command,
818
+ number,
819
+ type
820
+ };
821
+ }
822
+ /**
823
+ * Creates a new word token.
824
+ * @param loc The location for the token.
825
+ * @param word The word contained by the token.
826
+ */
827
+ function word(loc, word) {
828
+ return {
829
+ kind: 'word',
830
+ loc,
831
+ word
832
+ };
833
+ }
834
+ function whitespace(loc, whitespace) {
835
+ return {
836
+ kind: 'whitespace',
837
+ loc,
838
+ whitespace
839
+ };
840
+ }