codexparser 0.1.43 → 0.1.45
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 +204 -132
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexparser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
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,129 @@ 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
|
+
// Construct ranges for the `to` object
|
|
260
260
|
parsedPassage.to = {
|
|
261
261
|
book: book,
|
|
262
|
-
chapter: Number(endChapter),
|
|
262
|
+
chapter: Number(endChapter),
|
|
263
|
+
verses: [endVerse],
|
|
263
264
|
}
|
|
264
265
|
|
|
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
|
|
266
|
+
parsedPassage.type = "multi_chapter_verse_range"
|
|
275
267
|
} else {
|
|
276
|
-
// Same-chapter range, just add to the verse array
|
|
277
268
|
parsedPassage.verses.push(`${startVerse}-${endVerse}`)
|
|
278
269
|
}
|
|
279
270
|
} else {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
271
|
+
if (separator !== ":") {
|
|
272
|
+
// then this is a chapter_range
|
|
273
|
+
let [startChapter, endChapter] = part.split("-")
|
|
274
|
+
parsedPassage.chapter = Number(startChapter)
|
|
275
|
+
parsedPassage.verses = [
|
|
276
|
+
`${this.chapterVerses[book][parsedPassage.chapter][0]}-${
|
|
277
|
+
this.chapterVerses[book][parsedPassage.chapter][
|
|
278
|
+
this.chapterVerses[book][parsedPassage.chapter].length - 1
|
|
279
|
+
]
|
|
280
|
+
}`,
|
|
281
|
+
]
|
|
282
|
+
parsedPassage.to = {
|
|
283
|
+
book: book,
|
|
284
|
+
chapter: Number(endChapter),
|
|
285
|
+
verses: [
|
|
286
|
+
`${this.chapterVerses[book][endChapter][0]}-${
|
|
287
|
+
this.chapterVerses[book][endChapter][
|
|
288
|
+
this.chapterVerses[book][endChapter].length - 1
|
|
289
|
+
]
|
|
290
|
+
}`,
|
|
291
|
+
],
|
|
294
292
|
}
|
|
295
293
|
} else {
|
|
296
|
-
|
|
297
|
-
parsedPassage.verses.
|
|
294
|
+
// Handles when verses are present.
|
|
295
|
+
parsedPassage.verses = [part.split(separator)[1]]
|
|
298
296
|
}
|
|
299
297
|
}
|
|
300
298
|
} else {
|
|
301
|
-
// Handle individual chapter:verse references (e.g., "
|
|
302
|
-
|
|
299
|
+
// Handle individual chapter:verse references (e.g., "9:5" in "8:22-9:1,5")
|
|
303
300
|
let [chapterPart, versePart] = part.includes(separator)
|
|
304
301
|
? part.split(separator)
|
|
305
302
|
: [parsedPassage.chapter, part]
|
|
303
|
+
|
|
306
304
|
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
|
-
}
|
|
305
|
+
parsedPassage.chapter = 1
|
|
306
|
+
parsedPassage.verses.push(versePart) // Add single verse to array
|
|
314
307
|
} 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
308
|
if (chapterPart) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (!this.chapterVerses[book][parsedPassage.chapter]) {
|
|
325
|
-
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
309
|
+
if (partIndex === parts.length - 1 && parsedPassage.to) {
|
|
310
|
+
// Add additional verses to the last chapter in multi-chapter range
|
|
311
|
+
if (!parsedPassage.to.verses) parsedPassage.to.verses = []
|
|
312
|
+
parsedPassage.to.verses.push(Number(versePart))
|
|
326
313
|
} 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"
|
|
314
|
+
parsedPassage.chapter = Number(chapterPart)
|
|
315
|
+
parsedPassage.verses.push(Number(versePart))
|
|
335
316
|
}
|
|
336
317
|
}
|
|
337
318
|
}
|
|
338
319
|
}
|
|
320
|
+
|
|
339
321
|
parsedPassage.passages = this.populate(parsedPassage)
|
|
340
322
|
parsedPassage.scripture = this.scripturize(parsedPassage)
|
|
341
323
|
})
|
|
342
324
|
|
|
325
|
+
// Handle range merging in `to` object
|
|
326
|
+
if (parsedPassage.to && Array.isArray(parsedPassage.to.verses)) {
|
|
327
|
+
parsedPassage.to.verses = this.mergeRanges(parsedPassage.to.verses)
|
|
328
|
+
}
|
|
329
|
+
|
|
343
330
|
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
344
331
|
|
|
345
332
|
return parsedPassage
|
|
346
333
|
})
|
|
334
|
+
|
|
347
335
|
this.versification()
|
|
348
336
|
return this // Return this instance
|
|
349
337
|
}
|
|
@@ -566,60 +554,63 @@ class CodexParser {
|
|
|
566
554
|
* @return {object} The object with the human-readable name, chapter and verses and a hash.
|
|
567
555
|
*/
|
|
568
556
|
scripturize(passage) {
|
|
569
|
-
// Helper to format a single chapter:verse combination
|
|
570
|
-
const formatChapterVerse = (chapter,
|
|
571
|
-
if (!chapter) return ""
|
|
572
|
-
if (
|
|
573
|
-
|
|
557
|
+
// Helper function to format a single chapter:verse combination
|
|
558
|
+
const formatChapterVerse = (chapter, verses) => {
|
|
559
|
+
if (!chapter || !verses || verses.length === 0) return ""
|
|
560
|
+
if (verses.length === 1) {
|
|
561
|
+
return `${chapter}:${verses[0]}`
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Check if verses are continuous (e.g., [1, 2, 3, 4, 5] -> "1-5")
|
|
565
|
+
const isRange = verses.every((v, i, arr) => i === 0 || v === arr[i - 1] + 1)
|
|
566
|
+
|
|
567
|
+
if (isRange) {
|
|
568
|
+
return `${chapter}:${verses[0]}-${verses[verses.length - 1]}`
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Comma-separated (e.g., [1, 3, 5] -> "1,3,5")
|
|
572
|
+
return `${chapter}:${verses.join(",")}`
|
|
574
573
|
}
|
|
575
574
|
|
|
576
|
-
//
|
|
575
|
+
// Start constructing the passage string
|
|
577
576
|
let combined = `${passage.book}`
|
|
578
577
|
|
|
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
|
-
}
|
|
578
|
+
if (passage.type === "multi_chapter_verse_range" && passage.to) {
|
|
579
|
+
// Multi-chapter verse range
|
|
580
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
581
|
+
passage.to.chapter,
|
|
582
|
+
passage.to.verses
|
|
583
|
+
)}`
|
|
588
584
|
} else if (passage.type === "chapter_verse_range") {
|
|
589
585
|
// Single-chapter verse range
|
|
590
|
-
combined += ` ${formatChapterVerse(
|
|
591
|
-
passage.chapter,
|
|
592
|
-
passage.verses[0],
|
|
593
|
-
passage.verses[passage.verses.length - 1]
|
|
594
|
-
)}`
|
|
586
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
595
587
|
} else if (passage.type === "comma_separated_verses") {
|
|
596
588
|
// Comma-separated verses
|
|
597
|
-
combined += ` ${passage.chapter
|
|
598
|
-
} else if (passage.type === "chapter_range") {
|
|
589
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
590
|
+
} else if (passage.type === "chapter_range" && passage.to) {
|
|
599
591
|
// Chapter range
|
|
600
|
-
combined += ` ${passage.chapter}
|
|
601
|
-
passage.to.verses[passage.to.verses.length - 1]
|
|
602
|
-
}`
|
|
592
|
+
combined += ` ${passage.chapter}-${passage.to.chapter}`
|
|
603
593
|
} else {
|
|
604
594
|
// Single chapter or single verse
|
|
605
|
-
combined += ` ${formatChapterVerse(passage.chapter, passage.verses
|
|
595
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
606
596
|
}
|
|
607
597
|
|
|
608
|
-
// Generate chapter:verse
|
|
598
|
+
// Generate the chapter:verse (cv) string
|
|
609
599
|
const cv = passage.to
|
|
610
|
-
? `${formatChapterVerse(passage.chapter, passage.verses
|
|
600
|
+
? `${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
611
601
|
passage.to.chapter,
|
|
612
|
-
passage.to.verses
|
|
602
|
+
passage.to.verses
|
|
613
603
|
)}`
|
|
614
|
-
: formatChapterVerse(passage.chapter, passage.verses
|
|
604
|
+
: formatChapterVerse(passage.chapter, passage.verses)
|
|
615
605
|
|
|
616
|
-
// Generate
|
|
617
|
-
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, "
|
|
606
|
+
// Generate the hash
|
|
607
|
+
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, ".").replace(/-/g, ".")}`
|
|
618
608
|
|
|
609
|
+
// Return the final scripture object
|
|
619
610
|
return {
|
|
620
|
-
passage: combined,
|
|
621
|
-
cv: cv,
|
|
622
|
-
hash: hash,
|
|
611
|
+
passage: combined,
|
|
612
|
+
cv: cv,
|
|
613
|
+
hash: hash,
|
|
623
614
|
}
|
|
624
615
|
}
|
|
625
616
|
|
|
@@ -633,28 +624,96 @@ class CodexParser {
|
|
|
633
624
|
* @return {object} The combined passage object.
|
|
634
625
|
*/
|
|
635
626
|
combine(passages) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (sameBook.length > 1) {
|
|
639
|
-
throw new Error("Passages are not from the same book.")
|
|
627
|
+
if (!passages || passages.length === 0) {
|
|
628
|
+
throw new Error("No passages provided to combine.")
|
|
640
629
|
}
|
|
641
630
|
|
|
642
|
-
|
|
631
|
+
// Ensure all passages are from the same book
|
|
632
|
+
const uniqueBooks = [...new Set(passages.map((p) => p.book))]
|
|
633
|
+
if (uniqueBooks.length > 1) {
|
|
634
|
+
throw new Error("Passages must be from the same book to combine.")
|
|
635
|
+
}
|
|
643
636
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
637
|
+
// Initialize combined passage object
|
|
638
|
+
const combined = {
|
|
639
|
+
...passages[0],
|
|
640
|
+
verses: [],
|
|
641
|
+
passages: [],
|
|
642
|
+
to: null,
|
|
643
|
+
type: null,
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const chapterVerses = {}
|
|
647
|
+
let firstChapter = null
|
|
648
|
+
let lastChapter = null
|
|
649
|
+
|
|
650
|
+
// Collect all verses grouped by chapter
|
|
651
|
+
passages.forEach((passage) => {
|
|
652
|
+
passage.passages.forEach((p) => {
|
|
653
|
+
if (!chapterVerses[p.chapter]) {
|
|
654
|
+
chapterVerses[p.chapter] = new Set()
|
|
650
655
|
}
|
|
656
|
+
chapterVerses[p.chapter].add(p.verse)
|
|
657
|
+
combined.passages.push(p) // Add individual passage
|
|
651
658
|
})
|
|
659
|
+
|
|
660
|
+
// Track first and last chapters
|
|
661
|
+
const chapters = passage.passages.map((p) => p.chapter)
|
|
662
|
+
if (!firstChapter || Math.min(...chapters) < firstChapter) {
|
|
663
|
+
firstChapter = Math.min(...chapters)
|
|
664
|
+
}
|
|
665
|
+
if (!lastChapter || Math.max(...chapters) > lastChapter) {
|
|
666
|
+
lastChapter = Math.max(...chapters)
|
|
667
|
+
}
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
// Ensure unique and sorted passages
|
|
671
|
+
combined.passages = Array.from(new Set(combined.passages.map(JSON.stringify))).map(JSON.parse)
|
|
672
|
+
|
|
673
|
+
// Process chapter and verse data
|
|
674
|
+
const sortedChapters = Object.keys(chapterVerses)
|
|
675
|
+
.map(Number)
|
|
676
|
+
.sort((a, b) => a - b)
|
|
677
|
+
|
|
678
|
+
const originalParts = []
|
|
679
|
+
sortedChapters.forEach((chapter, index) => {
|
|
680
|
+
const verses = Array.from(chapterVerses[chapter]).sort((a, b) => a - b)
|
|
681
|
+
if (chapter === firstChapter) {
|
|
682
|
+
combined.verses = verses // First chapter's verses
|
|
683
|
+
}
|
|
684
|
+
if (chapter === lastChapter) {
|
|
685
|
+
combined.to = {
|
|
686
|
+
book: combined.book,
|
|
687
|
+
chapter,
|
|
688
|
+
verses,
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
originalParts.push(`${chapter}:${verses.join(",")}`)
|
|
652
692
|
})
|
|
653
693
|
|
|
654
|
-
|
|
694
|
+
// Ensure `to` is properly set for multi-chapter ranges
|
|
695
|
+
if (firstChapter !== lastChapter) {
|
|
696
|
+
const lastChapterVerses = Array.from(chapterVerses[lastChapter]).sort((a, b) => a - b)
|
|
697
|
+
combined.to = {
|
|
698
|
+
book: combined.book,
|
|
699
|
+
chapter: lastChapter,
|
|
700
|
+
verses: lastChapterVerses,
|
|
701
|
+
}
|
|
702
|
+
}
|
|
655
703
|
|
|
656
|
-
|
|
657
|
-
|
|
704
|
+
// Determine the passage type
|
|
705
|
+
if (firstChapter !== lastChapter) {
|
|
706
|
+
combined.type = "multi_chapter_verse_range"
|
|
707
|
+
} else if (combined.verses.length > 1) {
|
|
708
|
+
combined.type = "chapter_verse_range"
|
|
709
|
+
} else {
|
|
710
|
+
combined.type = "chapter_verse"
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Build the `original` field
|
|
714
|
+
combined.original = `${combined.book} ${originalParts.join("-")}`
|
|
715
|
+
|
|
716
|
+
return this.parse(combined.original).getPassages().first()
|
|
658
717
|
}
|
|
659
718
|
|
|
660
719
|
/**
|
|
@@ -865,18 +924,31 @@ class CodexParser {
|
|
|
865
924
|
}
|
|
866
925
|
}
|
|
867
926
|
for (let i = 0; i < passage.verses.length; i++) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
)
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
927
|
+
const passageVerses = String(passage.verses[i])
|
|
928
|
+
let verses = passageVerses.split("-").map(Number)
|
|
929
|
+
|
|
930
|
+
if (verses.length === 2) {
|
|
931
|
+
// Expand the range if there are two numbers
|
|
932
|
+
verses = Array.from({ length: verses[1] - verses[0] + 1 }, (_, index) => verses[0] + index)
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// The verses array now contains all values between the range or remains as a single value
|
|
936
|
+
for (const verse of verses) {
|
|
937
|
+
// Check if the verse exists in the chapterVerses structure
|
|
938
|
+
const isValidVerse =
|
|
939
|
+
this.chapterVerses[passage.book] &&
|
|
940
|
+
this.chapterVerses[passage.book][passage.chapter] &&
|
|
941
|
+
this.chapterVerses[passage.book][passage.chapter].includes(verse)
|
|
942
|
+
|
|
943
|
+
if (!isValidVerse) {
|
|
944
|
+
return {
|
|
945
|
+
error: true,
|
|
946
|
+
code: 104,
|
|
947
|
+
message: {
|
|
948
|
+
verse_exists: false,
|
|
949
|
+
content: `Verse number ${verse} does not exist in ${passage.book} ${passage.chapter}`,
|
|
950
|
+
},
|
|
951
|
+
}
|
|
880
952
|
}
|
|
881
953
|
}
|
|
882
954
|
}
|