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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/CodexParser.js +182 -136
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexparser",
3
- "version": "0.1.43",
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": {
@@ -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: [], // 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
+
260
+ // Construct ranges for the `to` object
260
261
  parsedPassage.to = {
261
262
  book: book,
262
- chapter: Number(endChapter), // End chapter
263
+ chapter: Number(endChapter),
264
+ verses: [`${1}-${endVerse}`, ...endVerses.filter((v) => v > endVerse)],
263
265
  }
264
266
 
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
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., "27:27")
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
- 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
- }
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
- 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)
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.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"
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, verseStart, verseEnd = null) => {
571
- if (!chapter) return ""
572
- if (!verseStart) return `${chapter}`
573
- return verseEnd ? `${chapter}:${verseStart}-${verseEnd}` : `${chapter}:${verseStart}`
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
- // Initialize combined passage
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 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
- }
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}:${passage.verses.join(",")}`
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}:${passage.verses[0]}-${passage.to.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[0])}`
569
+ combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
606
570
  }
607
571
 
608
- // Generate chapter:verse for current and "to" objects
572
+ // Generate the chapter:verse (cv) string
609
573
  const cv = passage.to
610
- ? `${formatChapterVerse(passage.chapter, passage.verses[0])}-${formatChapterVerse(
574
+ ? `${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
611
575
  passage.to.chapter,
612
- passage.to.verses[passage.to.verses.length - 1]
576
+ passage.to.verses
613
577
  )}`
614
- : formatChapterVerse(passage.chapter, passage.verses[0])
578
+ : formatChapterVerse(passage.chapter, passage.verses)
615
579
 
616
- // Generate a hash for the passage
617
- const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, "_").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, // Reconstructed passage
621
- cv: cv, // Chapter:verse range
622
- hash: hash, // Unique 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
- // 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.")
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
- const newPassages = []
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
- 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)
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
- const noDuplicates2 = [...new Set(newPassages)]
644
+ // Ensure unique and sorted passages
645
+ combined.passages = Array.from(new Set(combined.passages.map(JSON.stringify))).map(JSON.parse)
655
646
 
656
- const parsed = this.parse(noDuplicates2.join(" // ")).getPassages()
657
- return this.join(parsed)
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
- 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
- },
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
  }