codexparser 0.1.36 → 0.1.38

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexparser",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
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": {
@@ -50,182 +50,150 @@ class CodexParser {
50
50
  }
51
51
 
52
52
  /**
53
- * Scans the given text for Bible references, and stores all found references in the `found` property of the instance.
54
- * @param {string} text The text to scan for Bible references.
55
- * @return {CodexParser} This instance, for method chaining.
53
+ * Scans the given text for Bible references and stores all found references in the `found` property of the instance.
54
+ *
55
+ * @param {string} text - The text to scan for Bible references.
56
+ * @return {CodexParser} - Returns the instance itself, enabling method chaining.
56
57
  */
57
58
  scan(text) {
58
- const fullNames = [...this.bible.old, ...this.bible.new] // Full Bible book names
59
- const abbreviations = Object.keys(this.abbreviations) // Abbreviations for Bible books
60
-
59
+ // Initialize the `found` property as an empty array to store matched references.
61
60
  this.found = []
62
61
 
63
- // Convert the Bible book names and text to lowercase for case-insensitive matching
62
+ // Retrieve the full names of Bible books (both Old and New Testament).
63
+ const fullNames = [...this.bible.old, ...this.bible.new]
64
+
65
+ // Retrieve the abbreviations for Bible books from the `abbreviations` object.
66
+ const abbreviations = Object.keys(this.abbreviations)
67
+
68
+ // Convert all Bible book names and abbreviations to lowercase for case-insensitive matching.
64
69
  const lowercaseBibleFullNames = fullNames.map((book) => book.toLowerCase())
65
70
  const lowercaseBibleAbbreviations = abbreviations.map((abbr) => abbr.toLowerCase())
71
+
72
+ // Convert the input text to lowercase for consistent comparison.
66
73
  const lowerCaseText = text.toLowerCase()
67
74
 
75
+ // Initialize an index pointer `i` to iterate through the text.
68
76
  let i = 0
69
77
 
70
- // Function to determine if a character is a valid part of a chapter or verse reference (non-word characters)
71
- const isValidChapterVerseChar = (char) => {
72
- return /[^A-Za-z]/.test(char) // Allow any non-word characters
73
- }
74
-
75
- // Function to check if a character at a given index is non-alphabetic or at the boundary of the text
76
- const isBoundaryOrNonAlphabetic = (index, text) => {
77
- return index < 0 || index >= text.length || /[^a-z]/i.test(text[index])
78
- }
78
+ // Helper function to check if a character is valid for chapter or verse notation.
79
+ const isValidChapterVerseChar = (char) => /[^A-Za-z]/.test(char)
79
80
 
80
- // Function to check if the next part of the text starts with a new Bible book (e.g., "2 Corinthians")
81
+ // Helper function to determine if the next sequence in the text matches a Bible book name or abbreviation.
81
82
  const isNextBibleBook = (startIndex) => {
82
83
  const textAfterCurrentPosition = lowerCaseText.substring(startIndex).trim()
83
84
 
84
- // Check if the next part of the text matches any full Bible book name
85
- for (let j = 0; j < lowercaseBibleFullNames.length; j++) {
86
- if (textAfterCurrentPosition.startsWith(lowercaseBibleFullNames[j])) {
87
- return true // Found another Bible book
88
- }
85
+ // Check if the upcoming text starts with any full Bible book name.
86
+ for (const book of lowercaseBibleFullNames) {
87
+ if (textAfterCurrentPosition.startsWith(book)) return true
89
88
  }
90
89
 
91
- for (let j = 0; j < lowercaseBibleAbbreviations.length; j++) {
92
- if (textAfterCurrentPosition.startsWith(lowercaseBibleAbbreviations[j])) {
93
- return true // Found another Bible book abbreviation
94
- }
90
+ // Check if the upcoming text starts with any Bible book abbreviation.
91
+ for (const abbr of lowercaseBibleAbbreviations) {
92
+ if (textAfterCurrentPosition.startsWith(abbr)) return true
95
93
  }
96
94
 
95
+ // If no match is found, return false.
97
96
  return false
98
97
  }
99
98
 
100
- // Function to detect suffixes like "LXX" or "MT"
99
+ // Helper function to detect suffixes like "LXX" or "MT" in the text after a given index.
101
100
  const detectSuffix = (startIndex) => {
102
101
  const suffixMatch = text.substring(startIndex).match(/\b(LXX|MT)\b/i)
103
102
  return suffixMatch ? suffixMatch[0].toUpperCase() : null
104
103
  }
105
104
 
106
- // Loop through the text and check for full names and abbreviations
105
+ // Main loop: Iterate through the input text to search for Bible references.
107
106
  while (i < lowerCaseText.length) {
108
- let foundBook = null
109
- let foundIndex = -1
110
- let matchedLength = 0
107
+ let foundBook = null // Stores the matched Bible book name.
108
+ let foundIndex = -1 // Tracks the index where the match starts.
109
+ let matchedLength = 0 // Tracks the length of the matched book name.
111
110
 
112
- // Check full names first, to prioritize longer matches
111
+ // Check for matches against full Bible book names.
113
112
  for (let j = 0; j < lowercaseBibleFullNames.length; j++) {
114
113
  const book = lowercaseBibleFullNames[j]
115
114
 
116
- // Check if the text starting at position `i` matches the Bible book
117
- if (lowerCaseText.startsWith(book, i)) {
118
- if (book.length > matchedLength) {
119
- foundBook = fullNames[j] // Store the original full name
120
- foundIndex = i // Record the index where the book is found
121
- matchedLength = book.length // Update the length of the match
122
- }
115
+ // If the text at the current index matches a book name and is longer than any previously matched name.
116
+ if (lowerCaseText.startsWith(book, i) && book.length > matchedLength) {
117
+ foundBook = fullNames[j] // Store the original case-sensitive book name.
118
+ foundIndex = i // Update the starting index of the match.
119
+ matchedLength = book.length // Update the length of the match.
123
120
  }
124
121
  }
125
122
 
126
- // If no full book name was found, check for abbreviations
123
+ // If no match was found against full names, try matching against abbreviations.
127
124
  if (!foundBook) {
128
125
  for (let k = 0; k < lowercaseBibleAbbreviations.length; k++) {
129
126
  const abbreviation = lowercaseBibleAbbreviations[k]
130
- const abbreviationWithDot = abbreviation + "."
131
-
132
- // Ensure abbreviation is not part of a larger word (check boundaries)
133
- if (lowerCaseText.startsWith(abbreviationWithDot, i)) {
134
- if (
135
- isBoundaryOrNonAlphabetic(i - 1, lowerCaseText) &&
136
- isBoundaryOrNonAlphabetic(i + abbreviationWithDot.length, lowerCaseText)
137
- ) {
138
- // Look ahead to check if a number or space + number follows the abbreviation
139
- const afterAbbreviation = lowerCaseText.substring(i + abbreviationWithDot.length).trim()
140
- if (/^\d+/.test(afterAbbreviation)) {
141
- // Check if there is a number (chapter/verse)
142
- foundBook = abbreviations[k] // Store the abbreviation without the dot
143
- foundIndex = i // Record the index where the abbreviation is found
144
- matchedLength = abbreviationWithDot.length // Update the length of the match to include the dot
145
- break // Exit once found
146
- }
147
- }
148
- } else if (lowerCaseText.startsWith(abbreviation, i)) {
149
- if (
150
- isBoundaryOrNonAlphabetic(i - 1, lowerCaseText) &&
151
- isBoundaryOrNonAlphabetic(i + abbreviation.length, lowerCaseText)
152
- ) {
153
- // Look ahead to check if a number or space + number follows the abbreviation
154
- const afterAbbreviation = lowerCaseText.substring(i + abbreviation.length).trim()
155
- if (/^\d+/.test(afterAbbreviation)) {
156
- // Check if there is a number (chapter/verse)
157
- if (abbreviation.length > matchedLength) {
158
- foundBook = abbreviations[k] // Store the abbreviation without the dot
159
- foundIndex = i // Record the index where the abbreviation is found
160
- matchedLength = abbreviation.length // Update the length of the match
161
- }
162
- }
163
- }
127
+
128
+ // If the text at the current index matches an abbreviation.
129
+ if (lowerCaseText.startsWith(abbreviation, i)) {
130
+ foundBook = abbreviations[k] // Store the original abbreviation.
131
+ foundIndex = i // Update the starting index of the match.
132
+ matchedLength = abbreviation.length // Update the length of the match.
164
133
  }
165
134
  }
166
135
  }
167
136
 
168
- // If a book or abbreviation was found, look for chapter and verse patterns after the book
169
- if (foundBook !== null) {
170
- i += matchedLength // Skip ahead by the length of the found book
171
- let chapterVerse = ""
172
- const references = []
137
+ // If a Bible book is found.
138
+ if (foundBook) {
139
+ i += matchedLength // Move the pointer past the matched book name.
140
+ let chapterVerse = "" // Initialize a variable to accumulate chapter and verse information.
141
+ const references = [] // Array to store individual chapter and verse references.
173
142
 
174
- // Loop to find all chapter and verse references in the current book
143
+ // Secondary loop: Extract chapter and verse information after the book name.
175
144
  while (i < text.length && isValidChapterVerseChar(text[i])) {
176
- // Look ahead to see if the next characters form a new Bible book
177
- if (isNextBibleBook(i)) {
178
- break // Stop adding to chapterVerse if a new Bible book is found
179
- }
145
+ // Break if another Bible book starts at the current position.
146
+ if (isNextBibleBook(i)) break
180
147
 
181
- // If we hit a semicolon, it means a new reference starts
182
- // || text[i] === " "
148
+ // Handle semicolon-delimited references (e.g., "John 3:16; 4:5").
183
149
  if (text[i] === ";") {
184
150
  const formattedReference = chapterVerse
185
151
  .trim()
186
- .replace(/\./g, ":")
187
- .replace(/[^a-zA-Z0-9]+$/, "")
188
- if (formattedReference.length > 0) {
189
- references.push(formattedReference) // Add the current reference to the list
190
- }
191
- chapterVerse = "" // Reset for the next reference
192
- i++
152
+ .replace(/\.+/g, ":") // Replace dots (.) with colons (:).
153
+ .replace(/[^a-zA-Z0-9:]+$/, "") // Remove trailing invalid characters.
154
+
155
+ if (formattedReference) references.push(formattedReference) // Add the formatted reference to the list.
156
+ chapterVerse = "" // Reset the chapterVerse accumulator.
157
+ i++ // Move past the semicolon.
193
158
  continue
194
159
  }
195
160
 
161
+ // Accumulate valid characters for the chapterVerse.
196
162
  chapterVerse += text[i]
197
163
  i++
198
164
  }
199
165
 
200
- // Process the last found chapter and verse (if any)
166
+ // Handle any remaining chapterVerse after the loop ends.
201
167
  if (chapterVerse.trim().length > 0) {
202
168
  const formattedReference = chapterVerse
203
169
  .trim()
204
- .replace(/\./g, ":")
205
- .replace(/[^a-zA-Z0-9]+$/, "")
206
- if (formattedReference.length > 0) {
207
- references.push(formattedReference)
208
- }
170
+ .replace(/\.+/g, ":") // Replace dots (.) with colons (:).
171
+ .replace(/[^a-zA-Z0-9:]+$/, "") // Remove trailing invalid characters.
172
+
173
+ if (formattedReference) references.push(formattedReference) // Add the formatted reference to the list.
209
174
  }
210
175
 
211
- // Detect if a suffix (LXX or MT) exists after the chapter/verse
176
+ // Detect any suffix (e.g., "LXX" or "MT") after the chapter/verse reference.
212
177
  const suffix = detectSuffix(i)
213
178
 
214
- // Add each reference as a separate object
179
+ // Process each extracted reference to classify its type and store it in the `found` array.
215
180
  references.forEach((ref) => {
216
181
  this.found.push({
217
- book: foundBook,
218
- reference: ref,
219
- index: foundIndex,
220
- version: suffix || null,
182
+ book: foundBook, // The matched book name.
183
+ reference: ref.replace(/^:/, "").trim().replace(/\s+/gim, ""), // Format the reference.
184
+ index: foundIndex, // The starting index of the match in the original text.
185
+ type: ref.includes(":") ? "chapter_verse" : "single_chapter", // Determine the type of reference.
186
+ version: suffix || null, // Add detected suffix (e.g., "LXX" or "MT").
221
187
  })
222
188
  })
223
189
  } else {
190
+ // If no book is found, move the pointer forward by one character.
224
191
  i++
225
192
  }
226
193
  }
227
194
 
228
- return this // Return this instance for method chaining
195
+ // Return the current instance for method chaining.
196
+ return this
229
197
  }
230
198
 
231
199
  bibleVersion(version) {
@@ -237,8 +205,6 @@ class CodexParser {
237
205
  return this
238
206
  }
239
207
 
240
- //TODO: set the version and adjust the versifications
241
-
242
208
  /**
243
209
  * Parses a given reference and returns an object with the parsed passage,
244
210
  * including book, chapter, verse, type, testament, index, and version.
@@ -247,146 +213,106 @@ class CodexParser {
247
213
  * @returns {object} An object with the parsed passage.
248
214
  */
249
215
  parse(reference) {
250
- // Call scan to populate this.found
251
216
  this.scan(reference)
252
217
 
253
218
  this.passages = this.found.map((passage) => {
254
- // Clean up spaces and remove any dots from the reference
255
- passage.reference =
256
- passage.reference.match(/[:]/g)?.length > 1 ? passage.reference.replace(/[:]/, "") : passage.reference
257
219
  const book = this.bookify(passage.book)
258
- const testament = this.bible.old.find((bible) => bible === book) ? "old" : "new"
259
- // Initialize the parsed passage object
220
+ const testament = this.bible.old.includes(book) ? "old" : "new"
260
221
 
261
222
  const parsedPassage = {
262
- original: passage.book + " " + passage.reference,
263
- book: book,
223
+ original: `${passage.book} ${passage.reference}`,
224
+ book,
264
225
  chapter: null,
265
- verses: [], // Verse stored as an array
266
- type: null, // Set type based on reference
267
- testament: testament,
226
+ verses: [],
227
+ type: passage.type,
228
+ testament,
268
229
  index: passage.index,
269
230
  version: this._handleVersion(passage.version, testament),
270
231
  }
271
232
 
272
- // Split reference by commas to handle multiple ranges or verses (e.g., "Ge 27:27-29,39-41")
273
- let parts = passage.reference.split(",")
274
-
275
- // Check for single chapter books
276
- const singleChapterBook = this.singleChapterBook.find((bible) => bible[book])
233
+ const parts = passage.reference.split(",")
234
+ const isSingleChapter = this.singleChapterBook.some((singleChapterBook) => singleChapterBook[book])
277
235
 
278
236
  parts.forEach((part) => {
279
- part = part.trim() // Clean up spaces
280
- // Detect whether it uses ":" or "." for chapter:verse separation
237
+ part = part.trim()
281
238
  const separator = part.includes(":") ? ":" : "."
282
239
 
283
240
  if (part.includes("-")) {
284
- // Handle ranges (e.g., "27:27-29" or "39-41")
285
-
286
- let [start, end] = part.split("-")
287
- // Handle the starting part
288
- let [startChapter, startVerse] = start.includes(separator)
289
- ? start.split(separator)
290
- : [parsedPassage.chapter, start] // Default to same chapter if no chapter is provided
291
-
292
- parsedPassage.chapter = Number(startChapter) // Set the chapter
293
- // Checks to see if we are in a multi chapter verse range, if so, include only relevant verses from the this.chapterVerse to
294
- // the end of the chapter.
295
- if (start.includes(separator) && end.includes(separator)) {
296
- parsedPassage.verses = this.chapterVerses[book][startChapter].slice(
297
- this.chapterVerses[book][startChapter].indexOf(Number(startVerse))
298
- )
299
- }
300
- // Handle same-chapter ranges (e.g., "27:27-29") and multi-chapter ranges (e.g., "Ex 2:1-3:4")
301
- if (end.includes(separator)) {
302
- let [endChapter, endVerse] = end.split(separator)
303
- if (Number(endChapter) !== Number(startChapter)) {
304
- // Cross-chapter range, set 'to' property
305
- parsedPassage.to = {
306
- book: book,
307
- chapter: Number(endChapter), // End chapter
308
- }
309
- if (endVerse > 1) {
310
- parsedPassage.to.verses = this.chapterVerses[book][Number(endChapter)].slice(
311
- 0,
312
- this.chapterVerses[book][Number(endChapter)].indexOf(Number(endVerse)) + 1
313
- )
314
- }
315
- parsedPassage.type = "chapter_verse_range" // Set type to chapter range
316
- } else {
317
- // Same-chapter range, just add to the verse array
318
- parsedPassage.verses.push(`${startVerse}-${endVerse}`)
319
- }
320
- } else {
321
- // Single-chapter range (e.g., "27:27-29" or "39-41")
322
- if (!singleChapterBook) {
323
- if (!startChapter) {
324
- // Then we have a chapter range with no verses
325
- parsedPassage.chapter = start
326
- parsedPassage.verses = this.chapterVerses[book][start]
241
+ if (!isSingleChapter) {
242
+ if (part.includes(":")) {
243
+ let [start, end] = part.split("-")
244
+ const [startChapter, startVerse] = start.includes(separator)
245
+ ? start.split(separator).map(Number)
246
+ : [parsedPassage.chapter, Number(start)]
247
+ const [endChapter, endVerse] = end.includes(separator)
248
+ ? end.split(separator).map(Number)
249
+ : [startChapter, Number(end)]
250
+
251
+ parsedPassage.chapter = startChapter
252
+
253
+ if (startChapter !== endChapter) {
327
254
  parsedPassage.to = {
328
- book: book,
329
- chapter: Number(end),
330
- verses: this.chapterVerses[book][end],
255
+ book,
256
+ chapter: endChapter,
257
+ verses: [endVerse],
331
258
  }
259
+ parsedPassage.verses.push(startVerse)
332
260
  } else {
333
- //
334
- parsedPassage.verses.push(`${startVerse}-${end}`)
261
+ parsedPassage.verses.push(...this._generateRange(startVerse, endVerse))
335
262
  }
336
263
  } else {
337
- parsedPassage.chapter = 1
338
- parsedPassage.verses.push(`${startVerse}-${end}`)
264
+ const [start, end] = part.split("-")
265
+ parsedPassage.chapter = Number(start)
266
+ parsedPassage.to = {
267
+ book,
268
+ chapter: Number(end),
269
+ verses: [],
270
+ }
339
271
  }
272
+ } else {
273
+ part = part.replace(/\d+:/gim, "")
274
+ const [singleChapterStartVerse, singleChapterEndVerse] = part.split("-")
275
+ parsedPassage.chapter = 1
276
+ parsedPassage.verses = [`${singleChapterStartVerse}-${singleChapterEndVerse}`]
277
+ parsedPassage.type = "single_chapter_book_verse_range"
340
278
  }
279
+ } else if (part.includes(separator)) {
280
+ const [chapterPart, versePart] = part.split(separator).map(Number)
281
+ parsedPassage.chapter = chapterPart
282
+ if (versePart) parsedPassage.verses.push(versePart)
341
283
  } else {
342
- // Handle individual chapter:verse references (e.g., "27:27")
343
- let [chapterPart, versePart] = part.includes(separator)
344
- ? part.split(separator)
345
- : [parsedPassage.chapter, part]
346
- if (singleChapterBook) {
347
- if (!chapterPart) {
348
- parsedPassage.chapter = 1
349
- parsedPassage.verses.push(versePart) // Add single verse to array
350
- } else {
351
- parsedPassage.chapter = Number(chapterPart)
352
- parsedPassage.verses.push(versePart) // Add single verse to array
284
+ if (!isSingleChapter) {
285
+ const number = Number(part)
286
+ if (!parsedPassage.verses.length) {
287
+ parsedPassage.chapter = number
353
288
  }
289
+ parsedPassage.verses.push(Number(part))
354
290
  } else {
355
- // Need to check if chapterPart is undefined
356
- // If it's undefined, then versePart actually is the chapter and we need to populate the
357
- // verses from this.chapterVerses
358
- if (chapterPart) {
359
- parsedPassage.chapter = Number(chapterPart)
360
- parsedPassage.verses.push(versePart) // Add single verse to array
361
- } else {
362
- parsedPassage.chapter = Number(versePart)
363
- if (!this.chapterVerses[book][parsedPassage.chapter]) {
364
- parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
365
- } else {
366
- // Need to set the version of the passage here, i.e. LXX, MT, English
367
- this._setVersion(parsedPassage)
368
- parsedPassage.verses = [
369
- this.chapterVerses[book][parsedPassage.chapter][0] +
370
- "-" +
371
- this.chapterVerses[book][parsedPassage.chapter][
372
- this.chapterVerses[book][parsedPassage.chapter].length - 1
373
- ],
374
- ]
375
- parsedPassage.type = "single_chapter"
376
- }
377
- }
291
+ parsedPassage.chapter = 1
292
+ parsedPassage.verses.push(Number(part))
378
293
  }
379
294
  }
380
- parsedPassage.passages = this.populate(parsedPassage)
381
- parsedPassage.scripture = this.scripturize(parsedPassage)
382
295
  })
383
296
 
297
+ parsedPassage.passages = this.populate(parsedPassage)
298
+ parsedPassage.scripture = this.scripturize(parsedPassage)
384
299
  parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
385
300
 
386
301
  return parsedPassage
387
302
  })
303
+
388
304
  this.versification()
389
- return this // Return this instance
305
+ return this
306
+ }
307
+ /**
308
+ * Generates an array of numbers representing a range from start to end, inclusive.
309
+ */
310
+ _generateRange(start, end) {
311
+ const range = []
312
+ for (let i = start; i <= end; i++) {
313
+ range.push(i)
314
+ }
315
+ return range
390
316
  }
391
317
 
392
318
  _searchVersificationDifferences(passage) {
@@ -462,33 +388,84 @@ class CodexParser {
462
388
  * @param {Array} verses - Array of verse numbers to add to the set of passages
463
389
  * @return {Array} Array of passage objects
464
390
  */
391
+ /**
392
+ * Populate all verses from a parsed passage, including all verses in ranges or chapters.
393
+ *
394
+ * @param {Object} parsedPassage - The parsed passage object containing book, chapter, and verses information.
395
+ * @return {Array} An array of passage objects with individual verses.
396
+ */
465
397
  populate(parsedPassage) {
466
398
  const passages = []
467
- // Helper function to process a parsed passage's verses
468
- const processVerses = (passage) => {
469
- const { book, chapter, verses } = passage
470
-
471
- if (!verses) {
472
- return
399
+ const { book, chapter, verses, type } = parsedPassage
400
+ if (type === "single_chapter") {
401
+ // Handle single chapter references
402
+ if (this.chapterVerses[book] && this.chapterVerses[book][chapter]) {
403
+ this.chapterVerses[book][chapter].forEach((verse) => {
404
+ passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
405
+ })
473
406
  }
474
- verses.forEach((verse) => {
475
- if (isNaN(verse)) {
476
- const [start, end] = verse.split("-").map(Number) // Handle ranges
477
- for (let i = start; i <= end; i++) {
478
- passages.push({ book, chapter: Number(chapter), verse: i })
479
- }
480
- } else {
407
+ } else if (type === "comma_separated_verses") {
408
+ // Handle only the explicitly mentioned verses
409
+ if (verses && this.chapterVerses[book] && this.chapterVerses[book][chapter]) {
410
+ verses.forEach((verse) => {
481
411
  passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
412
+ })
413
+ }
414
+ } else if (type === "chapter_range") {
415
+ const { to } = parsedPassage
416
+
417
+ for (let i = chapter; i <= to.chapter; i++) {
418
+ const verses = this.chapterVerses[book][i]
419
+ for (let j = verses[0]; j < verses.length; j++) {
420
+ passages.push({
421
+ book,
422
+ chapter: i,
423
+ verse: j,
424
+ })
482
425
  }
483
- })
484
- }
485
-
486
- // Process main passage
487
- processVerses(parsedPassage)
488
-
489
- // Process 'to' object if it exists (for cross-chapter ranges)
490
- if (parsedPassage.to) {
491
- processVerses(parsedPassage.to)
426
+ }
427
+ } else if (type === "multi_chapter_verse_range") {
428
+ const { to } = parsedPassage
429
+
430
+ // Create an array of reference objects for the start and end of the range
431
+ const refs = [
432
+ {
433
+ chapter: Number(parsedPassage.chapter),
434
+ verse: Number(parsedPassage.verses[0]),
435
+ },
436
+ {
437
+ chapter: Number(to.chapter),
438
+ verse: Number(to.verses[to.verses.length - 1]),
439
+ },
440
+ ]
441
+
442
+ // Iterate over the range of chapters and verses
443
+ for (let i = refs[0].chapter; i <= refs[1].chapter; i++) {
444
+ const startVerse = i === refs[0].chapter ? refs[0].verse : 1
445
+ const endVerse = i === refs[1].chapter ? refs[1].verse : this.chapterVerses[book][i].length
446
+
447
+ for (let j = startVerse; j <= endVerse; j++) {
448
+ passages.push({
449
+ book,
450
+ chapter: i,
451
+ verse: j,
452
+ })
453
+ }
454
+ }
455
+ } else if (type === "chapter_verse" || type === "chapter_verse_range") {
456
+ // Handle chapter:verse or chapter:verse-range references
457
+ if (verses && this.chapterVerses[book] && this.chapterVerses[book][chapter]) {
458
+ verses.forEach((verse) => {
459
+ if (typeof verse === "string" && verse.includes("-")) {
460
+ const [start, end] = verse.split("-").map(Number)
461
+ for (let i = start; i <= end; i++) {
462
+ passages.push({ book, chapter: Number(chapter), verse: i })
463
+ }
464
+ } else {
465
+ passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
466
+ }
467
+ })
468
+ }
492
469
  }
493
470
 
494
471
  return passages
@@ -602,6 +579,7 @@ class CodexParser {
602
579
  * and builds the original and scripture properties.
603
580
  * **This method will always combine based on English versification. LXX and MT versifications will be reflected in the combined passage.passages.versification.**
604
581
  * This method will fail if the passages are not to the same book and chapter.
582
+ * TODO: Add support for MT and LXX
605
583
  * @param {array} passages - An array of passage objects to combine.
606
584
  * @return {object} The combined passage object.
607
585
  */
@@ -2,7 +2,7 @@ module.exports = {
2
2
  "9:1": {
3
3
  lxx: "8:23",
4
4
  mt: "8:23",
5
- eng: "1:1",
5
+ eng: "9:1",
6
6
  },
7
7
  "9:2": {
8
8
  lxx: "9:1",