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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/CodexParser.js +204 -132
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexparser",
3
- "version": "0.1.43",
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": {
@@ -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: [], // Verse stored as an array
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., "Ge 27:27-29,39-41")
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 chapter books
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., "27:27-29" or "39-41")
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
- // Checks to see if we are in a multi chapter verse range, if so, include only relevant verses from the this.chapterVerse to
246
- // the end of the chapter.
245
+
246
+ // Multi-chapter verse range logic
247
247
  if (start.includes(separator) && end.includes(separator)) {
248
- // TODO: Need to update versification and version here.
249
- this._setVersion(book, startChapter, passage.version)
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
- // Cross-chapter range, set 'to' property
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), // End chapter
262
+ chapter: Number(endChapter),
263
+ verses: [endVerse],
263
264
  }
264
265
 
265
- if (endVerse > 1) {
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
- // 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}`)
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
- parsedPassage.chapter = 1
297
- parsedPassage.verses.push(`${startVerse}-${end}`)
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., "27:27")
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
- if (!chapterPart) {
308
- parsedPassage.chapter = 1
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
- parsedPassage.chapter = Number(chapterPart)
321
- parsedPassage.verses.push(versePart) // Add single verse to array
322
- } else {
323
- parsedPassage.chapter = Number(versePart)
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.verses = [
328
- this.chapterVerses[book][parsedPassage.chapter][0] +
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, verseStart, verseEnd = null) => {
571
- if (!chapter) return ""
572
- if (!verseStart) return `${chapter}`
573
- return verseEnd ? `${chapter}:${verseStart}-${verseEnd}` : `${chapter}:${verseStart}`
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
- // Initialize combined passage
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 handling: first verse of first chapter to last verse of last chapter
581
- if (passage.to) {
582
- combined += ` ${formatChapterVerse(passage.chapter, passage.verses[0])}` // Start chapter:verse
583
- combined += `-${formatChapterVerse(
584
- passage.to.chapter,
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}:${passage.verses.join(",")}`
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}:${passage.verses[0]}-${passage.to.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[0])}`
595
+ combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
606
596
  }
607
597
 
608
- // Generate chapter:verse for current and "to" objects
598
+ // Generate the chapter:verse (cv) string
609
599
  const cv = passage.to
610
- ? `${formatChapterVerse(passage.chapter, passage.verses[0])}-${formatChapterVerse(
600
+ ? `${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
611
601
  passage.to.chapter,
612
- passage.to.verses[passage.to.verses.length - 1]
602
+ passage.to.verses
613
603
  )}`
614
- : formatChapterVerse(passage.chapter, passage.verses[0])
604
+ : formatChapterVerse(passage.chapter, passage.verses)
615
605
 
616
- // Generate a hash for the passage
617
- const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, "_").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, // Reconstructed passage
621
- cv: cv, // Chapter:verse range
622
- hash: hash, // Unique 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
- // Only check if passages are from the same book)
637
- const sameBook = [...new Set(passages.map((p) => p.book))]
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
- const newPassages = []
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
- passages.forEach((passageSet) => {
645
- passageSet.passages.forEach((passage) => {
646
- if (passage.versification) {
647
- newPassages.push(passage.book + " " + passage.versification[this.version])
648
- } else {
649
- newPassages.push(passage.book + " " + passage.chapter + ":" + passage.verse)
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
- const noDuplicates2 = [...new Set(newPassages)]
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
- const parsed = this.parse(noDuplicates2.join(" // ")).getPassages()
657
- return this.join(parsed)
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
- let verse = passage.verses[i]
869
- const searchForVerse = this.chapterVerses[passage.book][passage.chapter].find(
870
- (v) => Number(v) === Number(verse)
871
- )
872
- if (!searchForVerse) {
873
- return {
874
- error: true,
875
- code: 104,
876
- message: {
877
- verse_exists: false,
878
- content: `Verse number ${verse} does not exist in ${passage.book} ${passage.chapter}`,
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
  }