codexparser 0.1.43 → 0.1.44
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/package.json +1 -1
- package/src/CodexParser.js +182 -136
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexparser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"description": "This is a Javascript Bible parser and text scanner. It will search through texts and collate all scripture references into an array and parse them into objects, and it will parse passages into objects by book, chapter, verse, and testament. ",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/src/CodexParser.js
CHANGED
|
@@ -209,141 +209,103 @@ class CodexParser {
|
|
|
209
209
|
this.passages = this.found.map((passage) => {
|
|
210
210
|
const book = this.bookify(passage.book)
|
|
211
211
|
const testament = this.bible.old.find((bible) => bible === book) ? "old" : "new"
|
|
212
|
+
|
|
212
213
|
// Initialize the parsed passage object
|
|
213
214
|
const parsedPassage = {
|
|
214
215
|
original: passage.book + " " + passage.reference,
|
|
215
216
|
book: book,
|
|
216
217
|
chapter: null,
|
|
217
|
-
verses: [], //
|
|
218
|
+
verses: [], // Verses stored as an array
|
|
218
219
|
type: passage.type, // Set type based on reference
|
|
219
220
|
testament: testament,
|
|
220
221
|
index: passage.index,
|
|
221
222
|
version: this._handleVersion(passage.version, testament),
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
// Split reference by commas to handle multiple ranges or verses (e.g., "
|
|
225
|
+
// Split reference by commas to handle multiple ranges or verses (e.g., "Isaiah 8:22-9:1,5")
|
|
225
226
|
let parts = passage.reference.split(",")
|
|
226
227
|
|
|
227
|
-
// Check for single
|
|
228
|
+
// Check for single-chapter books
|
|
228
229
|
const singleChapterBook = this.singleChapterBook.find((bible) => bible[book])
|
|
229
230
|
|
|
230
|
-
parts.forEach((part) => {
|
|
231
|
+
parts.forEach((part, partIndex) => {
|
|
231
232
|
part = part.trim() // Clean up spaces
|
|
232
|
-
// Detect whether it uses ":" or "." for chapter:verse separation
|
|
233
233
|
const separator = part.includes(":") ? ":" : "."
|
|
234
234
|
|
|
235
235
|
if (part.includes("-")) {
|
|
236
|
-
// Handle ranges (e.g., "
|
|
237
|
-
|
|
236
|
+
// Handle ranges (e.g., "8:22-9:1")
|
|
238
237
|
let [start, end] = part.split("-")
|
|
238
|
+
|
|
239
239
|
// Handle the starting part
|
|
240
240
|
let [startChapter, startVerse] = start.includes(separator)
|
|
241
241
|
? start.split(separator)
|
|
242
242
|
: [parsedPassage.chapter, start] // Default to same chapter if no chapter is provided
|
|
243
243
|
|
|
244
244
|
parsedPassage.chapter = Number(startChapter) // Set the chapter
|
|
245
|
-
|
|
246
|
-
//
|
|
245
|
+
|
|
246
|
+
// Multi-chapter verse range logic
|
|
247
247
|
if (start.includes(separator) && end.includes(separator)) {
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
let [endChapter, endVerse] = end.split(separator)
|
|
249
|
+
|
|
250
250
|
parsedPassage.verses = this.chapterVerses[book][startChapter].slice(
|
|
251
251
|
this.chapterVerses[book][startChapter].indexOf(Number(startVerse))
|
|
252
252
|
)
|
|
253
|
-
}
|
|
254
253
|
|
|
255
|
-
// Handle chapter ranges (e.g., "27:27-29") and multi-chapter ranges (e.g., "Ex 2:1-3:4")
|
|
256
|
-
if (end.includes(separator)) {
|
|
257
|
-
let [endChapter, endVerse] = end.split(separator)
|
|
258
254
|
if (Number(endChapter) !== Number(startChapter)) {
|
|
259
|
-
|
|
255
|
+
const endVerses = this.chapterVerses[book][endChapter].slice(
|
|
256
|
+
0,
|
|
257
|
+
this.chapterVerses[book][endChapter].indexOf(Number(endVerse)) + 1
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
// Construct ranges for the `to` object
|
|
260
261
|
parsedPassage.to = {
|
|
261
262
|
book: book,
|
|
262
|
-
chapter: Number(endChapter),
|
|
263
|
+
chapter: Number(endChapter),
|
|
264
|
+
verses: [`${1}-${endVerse}`, ...endVerses.filter((v) => v > endVerse)],
|
|
263
265
|
}
|
|
264
266
|
|
|
265
|
-
|
|
266
|
-
parsedPassage.to.verses = this.chapterVerses[book][Number(endChapter)].slice(
|
|
267
|
-
0,
|
|
268
|
-
this.chapterVerses[book][Number(endChapter)].indexOf(Number(endVerse)) + 1
|
|
269
|
-
)
|
|
270
|
-
} else {
|
|
271
|
-
parsedPassage.to.verses = [endVerse]
|
|
272
|
-
}
|
|
273
|
-
parsedPassage.type =
|
|
274
|
-
endChapter !== startChapter ? "multi_chapter_verse_range" : "chapter_verse_range" // Set type to chapter range
|
|
267
|
+
parsedPassage.type = "multi_chapter_verse_range"
|
|
275
268
|
} else {
|
|
276
|
-
// Same-chapter range, just add to the verse array
|
|
277
269
|
parsedPassage.verses.push(`${startVerse}-${endVerse}`)
|
|
278
270
|
}
|
|
279
|
-
} else {
|
|
280
|
-
// Single-chapter range (e.g., "27:27-29" or "39-41")
|
|
281
|
-
if (!singleChapterBook) {
|
|
282
|
-
if (!startChapter) {
|
|
283
|
-
// Then we have a chapter range with no verses
|
|
284
|
-
parsedPassage.chapter = Number(start)
|
|
285
|
-
parsedPassage.verses = this.chapterVerses[book][start]
|
|
286
|
-
parsedPassage.to = {
|
|
287
|
-
book: book,
|
|
288
|
-
chapter: Number(end),
|
|
289
|
-
verses: this.chapterVerses[book][end],
|
|
290
|
-
}
|
|
291
|
-
} else {
|
|
292
|
-
//
|
|
293
|
-
parsedPassage.verses.push(`${startVerse}-${end}`)
|
|
294
|
-
}
|
|
295
|
-
} else {
|
|
296
|
-
parsedPassage.chapter = 1
|
|
297
|
-
parsedPassage.verses.push(`${startVerse}-${end}`)
|
|
298
|
-
}
|
|
299
271
|
}
|
|
300
272
|
} else {
|
|
301
|
-
// Handle individual chapter:verse references (e.g., "
|
|
302
|
-
|
|
273
|
+
// Handle individual chapter:verse references (e.g., "9:5" in "8:22-9:1,5")
|
|
303
274
|
let [chapterPart, versePart] = part.includes(separator)
|
|
304
275
|
? part.split(separator)
|
|
305
276
|
: [parsedPassage.chapter, part]
|
|
277
|
+
|
|
306
278
|
if (singleChapterBook) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
parsedPassage.verses.push(versePart) // Add single verse to array
|
|
310
|
-
} else {
|
|
311
|
-
parsedPassage.chapter = Number(chapterPart)
|
|
312
|
-
parsedPassage.verses.push(versePart) // Add single verse to array
|
|
313
|
-
}
|
|
279
|
+
parsedPassage.chapter = 1
|
|
280
|
+
parsedPassage.verses.push(versePart) // Add single verse to array
|
|
314
281
|
} else {
|
|
315
|
-
// Need to check if chapterPart is undefined
|
|
316
|
-
// If it's undefined, then versePart actually is the chapter and we need to populate the
|
|
317
|
-
// verses from this.chapterVerses
|
|
318
|
-
|
|
319
282
|
if (chapterPart) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (!this.chapterVerses[book][parsedPassage.chapter]) {
|
|
325
|
-
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
283
|
+
if (partIndex === parts.length - 1 && parsedPassage.to) {
|
|
284
|
+
// Add additional verses to the last chapter in multi-chapter range
|
|
285
|
+
if (!parsedPassage.to.verses) parsedPassage.to.verses = []
|
|
286
|
+
parsedPassage.to.verses.push(Number(versePart))
|
|
326
287
|
} else {
|
|
327
|
-
parsedPassage.
|
|
328
|
-
|
|
329
|
-
"-" +
|
|
330
|
-
this.chapterVerses[book][parsedPassage.chapter][
|
|
331
|
-
this.chapterVerses[book][parsedPassage.chapter].length - 1
|
|
332
|
-
],
|
|
333
|
-
]
|
|
334
|
-
parsedPassage.type = "single_chapter"
|
|
288
|
+
parsedPassage.chapter = Number(chapterPart)
|
|
289
|
+
parsedPassage.verses.push(Number(versePart))
|
|
335
290
|
}
|
|
336
291
|
}
|
|
337
292
|
}
|
|
338
293
|
}
|
|
294
|
+
|
|
339
295
|
parsedPassage.passages = this.populate(parsedPassage)
|
|
340
296
|
parsedPassage.scripture = this.scripturize(parsedPassage)
|
|
341
297
|
})
|
|
342
298
|
|
|
299
|
+
// Handle range merging in `to` object
|
|
300
|
+
if (parsedPassage.to && Array.isArray(parsedPassage.to.verses)) {
|
|
301
|
+
parsedPassage.to.verses = this.mergeRanges(parsedPassage.to.verses)
|
|
302
|
+
}
|
|
303
|
+
|
|
343
304
|
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
344
305
|
|
|
345
306
|
return parsedPassage
|
|
346
307
|
})
|
|
308
|
+
|
|
347
309
|
this.versification()
|
|
348
310
|
return this // Return this instance
|
|
349
311
|
}
|
|
@@ -566,60 +528,63 @@ class CodexParser {
|
|
|
566
528
|
* @return {object} The object with the human-readable name, chapter and verses and a hash.
|
|
567
529
|
*/
|
|
568
530
|
scripturize(passage) {
|
|
569
|
-
// Helper to format a single chapter:verse combination
|
|
570
|
-
const formatChapterVerse = (chapter,
|
|
571
|
-
if (!chapter) return ""
|
|
572
|
-
if (
|
|
573
|
-
|
|
531
|
+
// Helper function to format a single chapter:verse combination
|
|
532
|
+
const formatChapterVerse = (chapter, verses) => {
|
|
533
|
+
if (!chapter || !verses || verses.length === 0) return ""
|
|
534
|
+
if (verses.length === 1) {
|
|
535
|
+
return `${chapter}:${verses[0]}`
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Check if verses are continuous (e.g., [1, 2, 3, 4, 5] -> "1-5")
|
|
539
|
+
const isRange = verses.every((v, i, arr) => i === 0 || v === arr[i - 1] + 1)
|
|
540
|
+
|
|
541
|
+
if (isRange) {
|
|
542
|
+
return `${chapter}:${verses[0]}-${verses[verses.length - 1]}`
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Comma-separated (e.g., [1, 3, 5] -> "1,3,5")
|
|
546
|
+
return `${chapter}:${verses.join(",")}`
|
|
574
547
|
}
|
|
575
548
|
|
|
576
|
-
//
|
|
549
|
+
// Start constructing the passage string
|
|
577
550
|
let combined = `${passage.book}`
|
|
578
551
|
|
|
579
|
-
if (passage.type === "multi_chapter_verse_range") {
|
|
580
|
-
// Multi-chapter verse range
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
passage.to.verses[passage.to.verses.length - 1]
|
|
586
|
-
)}` // End chapter:last verse
|
|
587
|
-
}
|
|
552
|
+
if (passage.type === "multi_chapter_verse_range" && passage.to) {
|
|
553
|
+
// Multi-chapter verse range
|
|
554
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
555
|
+
passage.to.chapter,
|
|
556
|
+
passage.to.verses
|
|
557
|
+
)}`
|
|
588
558
|
} else if (passage.type === "chapter_verse_range") {
|
|
589
559
|
// Single-chapter verse range
|
|
590
|
-
combined += ` ${formatChapterVerse(
|
|
591
|
-
passage.chapter,
|
|
592
|
-
passage.verses[0],
|
|
593
|
-
passage.verses[passage.verses.length - 1]
|
|
594
|
-
)}`
|
|
560
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
595
561
|
} else if (passage.type === "comma_separated_verses") {
|
|
596
562
|
// Comma-separated verses
|
|
597
|
-
combined += ` ${passage.chapter
|
|
598
|
-
} else if (passage.type === "chapter_range") {
|
|
563
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
564
|
+
} else if (passage.type === "chapter_range" && passage.to) {
|
|
599
565
|
// Chapter range
|
|
600
|
-
combined += ` ${passage.chapter}
|
|
601
|
-
passage.to.verses[passage.to.verses.length - 1]
|
|
602
|
-
}`
|
|
566
|
+
combined += ` ${passage.chapter}-${passage.to.chapter}`
|
|
603
567
|
} else {
|
|
604
568
|
// Single chapter or single verse
|
|
605
|
-
combined += ` ${formatChapterVerse(passage.chapter, passage.verses
|
|
569
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
606
570
|
}
|
|
607
571
|
|
|
608
|
-
// Generate chapter:verse
|
|
572
|
+
// Generate the chapter:verse (cv) string
|
|
609
573
|
const cv = passage.to
|
|
610
|
-
? `${formatChapterVerse(passage.chapter, passage.verses
|
|
574
|
+
? `${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
611
575
|
passage.to.chapter,
|
|
612
|
-
passage.to.verses
|
|
576
|
+
passage.to.verses
|
|
613
577
|
)}`
|
|
614
|
-
: formatChapterVerse(passage.chapter, passage.verses
|
|
578
|
+
: formatChapterVerse(passage.chapter, passage.verses)
|
|
615
579
|
|
|
616
|
-
// Generate
|
|
617
|
-
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, "
|
|
580
|
+
// Generate the hash
|
|
581
|
+
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, ".").replace(/-/g, ".")}`
|
|
618
582
|
|
|
583
|
+
// Return the final scripture object
|
|
619
584
|
return {
|
|
620
|
-
passage: combined,
|
|
621
|
-
cv: cv,
|
|
622
|
-
hash: hash,
|
|
585
|
+
passage: combined,
|
|
586
|
+
cv: cv,
|
|
587
|
+
hash: hash,
|
|
623
588
|
}
|
|
624
589
|
}
|
|
625
590
|
|
|
@@ -633,28 +598,96 @@ class CodexParser {
|
|
|
633
598
|
* @return {object} The combined passage object.
|
|
634
599
|
*/
|
|
635
600
|
combine(passages) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
601
|
+
if (!passages || passages.length === 0) {
|
|
602
|
+
throw new Error("No passages provided to combine.")
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Ensure all passages are from the same book
|
|
606
|
+
const uniqueBooks = [...new Set(passages.map((p) => p.book))]
|
|
607
|
+
if (uniqueBooks.length > 1) {
|
|
608
|
+
throw new Error("Passages must be from the same book to combine.")
|
|
640
609
|
}
|
|
641
610
|
|
|
642
|
-
|
|
611
|
+
// Initialize combined passage object
|
|
612
|
+
const combined = {
|
|
613
|
+
...passages[0],
|
|
614
|
+
verses: [],
|
|
615
|
+
passages: [],
|
|
616
|
+
to: null,
|
|
617
|
+
type: null,
|
|
618
|
+
}
|
|
643
619
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
620
|
+
const chapterVerses = {}
|
|
621
|
+
let firstChapter = null
|
|
622
|
+
let lastChapter = null
|
|
623
|
+
|
|
624
|
+
// Collect all verses grouped by chapter
|
|
625
|
+
passages.forEach((passage) => {
|
|
626
|
+
passage.passages.forEach((p) => {
|
|
627
|
+
if (!chapterVerses[p.chapter]) {
|
|
628
|
+
chapterVerses[p.chapter] = new Set()
|
|
650
629
|
}
|
|
630
|
+
chapterVerses[p.chapter].add(p.verse)
|
|
631
|
+
combined.passages.push(p) // Add individual passage
|
|
651
632
|
})
|
|
633
|
+
|
|
634
|
+
// Track first and last chapters
|
|
635
|
+
const chapters = passage.passages.map((p) => p.chapter)
|
|
636
|
+
if (!firstChapter || Math.min(...chapters) < firstChapter) {
|
|
637
|
+
firstChapter = Math.min(...chapters)
|
|
638
|
+
}
|
|
639
|
+
if (!lastChapter || Math.max(...chapters) > lastChapter) {
|
|
640
|
+
lastChapter = Math.max(...chapters)
|
|
641
|
+
}
|
|
652
642
|
})
|
|
653
643
|
|
|
654
|
-
|
|
644
|
+
// Ensure unique and sorted passages
|
|
645
|
+
combined.passages = Array.from(new Set(combined.passages.map(JSON.stringify))).map(JSON.parse)
|
|
655
646
|
|
|
656
|
-
|
|
657
|
-
|
|
647
|
+
// Process chapter and verse data
|
|
648
|
+
const sortedChapters = Object.keys(chapterVerses)
|
|
649
|
+
.map(Number)
|
|
650
|
+
.sort((a, b) => a - b)
|
|
651
|
+
|
|
652
|
+
const originalParts = []
|
|
653
|
+
sortedChapters.forEach((chapter, index) => {
|
|
654
|
+
const verses = Array.from(chapterVerses[chapter]).sort((a, b) => a - b)
|
|
655
|
+
if (chapter === firstChapter) {
|
|
656
|
+
combined.verses = verses // First chapter's verses
|
|
657
|
+
}
|
|
658
|
+
if (chapter === lastChapter) {
|
|
659
|
+
combined.to = {
|
|
660
|
+
book: combined.book,
|
|
661
|
+
chapter,
|
|
662
|
+
verses,
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
originalParts.push(`${chapter}:${verses.join(",")}`)
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
// Ensure `to` is properly set for multi-chapter ranges
|
|
669
|
+
if (firstChapter !== lastChapter) {
|
|
670
|
+
const lastChapterVerses = Array.from(chapterVerses[lastChapter]).sort((a, b) => a - b)
|
|
671
|
+
combined.to = {
|
|
672
|
+
book: combined.book,
|
|
673
|
+
chapter: lastChapter,
|
|
674
|
+
verses: lastChapterVerses,
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Determine the passage type
|
|
679
|
+
if (firstChapter !== lastChapter) {
|
|
680
|
+
combined.type = "multi_chapter_verse_range"
|
|
681
|
+
} else if (combined.verses.length > 1) {
|
|
682
|
+
combined.type = "chapter_verse_range"
|
|
683
|
+
} else {
|
|
684
|
+
combined.type = "chapter_verse"
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Build the `original` field
|
|
688
|
+
combined.original = `${combined.book} ${originalParts.join("-")}`
|
|
689
|
+
|
|
690
|
+
return this.parse(combined.original).getPassages().first()
|
|
658
691
|
}
|
|
659
692
|
|
|
660
693
|
/**
|
|
@@ -865,18 +898,31 @@ class CodexParser {
|
|
|
865
898
|
}
|
|
866
899
|
}
|
|
867
900
|
for (let i = 0; i < passage.verses.length; i++) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
)
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
901
|
+
const passageVerses = String(passage.verses[i])
|
|
902
|
+
let verses = passageVerses.split("-").map(Number)
|
|
903
|
+
|
|
904
|
+
if (verses.length === 2) {
|
|
905
|
+
// Expand the range if there are two numbers
|
|
906
|
+
verses = Array.from({ length: verses[1] - verses[0] + 1 }, (_, index) => verses[0] + index)
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// The verses array now contains all values between the range or remains as a single value
|
|
910
|
+
for (const verse of verses) {
|
|
911
|
+
// Check if the verse exists in the chapterVerses structure
|
|
912
|
+
const isValidVerse =
|
|
913
|
+
this.chapterVerses[passage.book] &&
|
|
914
|
+
this.chapterVerses[passage.book][passage.chapter] &&
|
|
915
|
+
this.chapterVerses[passage.book][passage.chapter].includes(verse)
|
|
916
|
+
|
|
917
|
+
if (!isValidVerse) {
|
|
918
|
+
return {
|
|
919
|
+
error: true,
|
|
920
|
+
code: 104,
|
|
921
|
+
message: {
|
|
922
|
+
verse_exists: false,
|
|
923
|
+
content: `Verse number ${verse} does not exist in ${passage.book} ${passage.chapter}`,
|
|
924
|
+
},
|
|
925
|
+
}
|
|
880
926
|
}
|
|
881
927
|
}
|
|
882
928
|
}
|