codexparser 0.1.35 → 0.1.37

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.35",
3
+ "version": "0.1.37",
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": {
@@ -46,189 +46,164 @@ class CodexParser {
46
46
  ]
47
47
  this.chapterVerses = chapter_verses
48
48
  this.error = false
49
- this.version = "eng"
49
+ this.version = null
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
- //TODO: set the version and adjust the versifications
199
+ bibleVersion(version) {
200
+ const lowerVersion = version.toLowerCase()
201
+ this.version =
202
+ lowerVersion === "lxx" || lowerVersion === "eng" || lowerVersion === "bhs" || lowerVersion === "mt"
203
+ ? lowerVersion
204
+ : null
205
+ return this
206
+ }
232
207
 
233
208
  /**
234
209
  * Parses a given reference and returns an object with the parsed passage,
@@ -238,145 +213,106 @@ class CodexParser {
238
213
  * @returns {object} An object with the parsed passage.
239
214
  */
240
215
  parse(reference) {
241
- // Call scan to populate this.found
242
216
  this.scan(reference)
243
217
 
244
218
  this.passages = this.found.map((passage) => {
245
- // Clean up spaces and remove any dots from the reference
246
- passage.reference =
247
- passage.reference.match(/[:]/g)?.length > 1 ? passage.reference.replace(/[:]/, "") : passage.reference
248
219
  const book = this.bookify(passage.book)
249
- const testament = this.bible.old.find((bible) => bible === book) ? "old" : "new"
250
- // Initialize the parsed passage object
220
+ const testament = this.bible.old.includes(book) ? "old" : "new"
221
+
251
222
  const parsedPassage = {
252
- original: passage.book + " " + passage.reference,
253
- book: book,
223
+ original: `${passage.book} ${passage.reference}`,
224
+ book,
254
225
  chapter: null,
255
- verses: [], // Verse stored as an array
256
- type: null, // Set type based on reference
257
- testament: testament,
226
+ verses: [],
227
+ type: passage.type,
228
+ testament,
258
229
  index: passage.index,
259
230
  version: this._handleVersion(passage.version, testament),
260
231
  }
261
232
 
262
- // Split reference by commas to handle multiple ranges or verses (e.g., "Ge 27:27-29,39-41")
263
- let parts = passage.reference.split(",")
264
-
265
- // Check for single chapter books
266
- const singleChapterBook = this.singleChapterBook.find((bible) => bible[book])
233
+ const parts = passage.reference.split(",")
234
+ const isSingleChapter = this.singleChapterBook.some((singleChapterBook) => singleChapterBook[book])
267
235
 
268
236
  parts.forEach((part) => {
269
- part = part.trim() // Clean up spaces
270
- // Detect whether it uses ":" or "." for chapter:verse separation
237
+ part = part.trim()
271
238
  const separator = part.includes(":") ? ":" : "."
272
239
 
273
240
  if (part.includes("-")) {
274
- // Handle ranges (e.g., "27:27-29" or "39-41")
275
-
276
- let [start, end] = part.split("-")
277
- // Handle the starting part
278
- let [startChapter, startVerse] = start.includes(separator)
279
- ? start.split(separator)
280
- : [parsedPassage.chapter, start] // Default to same chapter if no chapter is provided
281
-
282
- parsedPassage.chapter = Number(startChapter) // Set the chapter
283
- // Checks to see if we are in a multi chapter verse range, if so, include only relevant verses from the this.chapterVerse to
284
- // the end of the chapter.
285
- if (start.includes(separator) && end.includes(separator)) {
286
- parsedPassage.verses = this.chapterVerses[book][startChapter].slice(
287
- this.chapterVerses[book][startChapter].indexOf(Number(startVerse))
288
- )
289
- }
290
- // Handle same-chapter ranges (e.g., "27:27-29") and multi-chapter ranges (e.g., "Ex 2:1-3:4")
291
- if (end.includes(separator)) {
292
- let [endChapter, endVerse] = end.split(separator)
293
- if (Number(endChapter) !== Number(startChapter)) {
294
- // Cross-chapter range, set 'to' property
295
- parsedPassage.to = {
296
- book: book,
297
- chapter: Number(endChapter), // End chapter
298
- }
299
- if (endVerse > 1) {
300
- parsedPassage.to.verses = this.chapterVerses[book][Number(endChapter)].slice(
301
- 0,
302
- this.chapterVerses[book][Number(endChapter)].indexOf(Number(endVerse)) + 1
303
- )
304
- }
305
- parsedPassage.type = "chapter_verse_range" // Set type to chapter range
306
- } else {
307
- // Same-chapter range, just add to the verse array
308
- parsedPassage.verses.push(`${startVerse}-${endVerse}`)
309
- }
310
- } else {
311
- // Single-chapter range (e.g., "27:27-29" or "39-41")
312
- if (!singleChapterBook) {
313
- if (!startChapter) {
314
- // Then we have a chapter range with no verses
315
- parsedPassage.chapter = start
316
- 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) {
317
254
  parsedPassage.to = {
318
- book: book,
319
- chapter: Number(end),
320
- verses: this.chapterVerses[book][end],
255
+ book,
256
+ chapter: endChapter,
257
+ verses: [endVerse],
321
258
  }
259
+ parsedPassage.verses.push(startVerse)
322
260
  } else {
323
- //
324
- parsedPassage.verses.push(`${startVerse}-${end}`)
261
+ parsedPassage.verses.push(...this._generateRange(startVerse, endVerse))
325
262
  }
326
263
  } else {
327
- parsedPassage.chapter = 1
328
- 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
+ }
329
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"
330
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)
331
283
  } else {
332
- // Handle individual chapter:verse references (e.g., "27:27")
333
- let [chapterPart, versePart] = part.includes(separator)
334
- ? part.split(separator)
335
- : [parsedPassage.chapter, part]
336
- if (singleChapterBook) {
337
- if (!chapterPart) {
338
- parsedPassage.chapter = 1
339
- parsedPassage.verses.push(versePart) // Add single verse to array
340
- } else {
341
- parsedPassage.chapter = Number(chapterPart)
342
- 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
343
288
  }
289
+ parsedPassage.verses.push(Number(part))
344
290
  } else {
345
- // Need to check if chapterPart is undefined
346
- // If it's undefined, then versePart actually is the chapter and we need to populate the
347
- // verses from this.chapterVerses
348
- if (chapterPart) {
349
- parsedPassage.chapter = Number(chapterPart)
350
- parsedPassage.verses.push(versePart) // Add single verse to array
351
- } else {
352
- parsedPassage.chapter = Number(versePart)
353
- if (!this.chapterVerses[book][parsedPassage.chapter]) {
354
- parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
355
- } else {
356
- // Need to set the version of the passage here, i.e. LXX, MT, English
357
- this._setVersion(parsedPassage)
358
- parsedPassage.verses = [
359
- this.chapterVerses[book][parsedPassage.chapter][0] +
360
- "-" +
361
- this.chapterVerses[book][parsedPassage.chapter][
362
- this.chapterVerses[book][parsedPassage.chapter].length - 1
363
- ],
364
- ]
365
- parsedPassage.type = "single_chapter"
366
- }
367
- }
291
+ parsedPassage.chapter = 1
292
+ parsedPassage.verses.push(Number(part))
368
293
  }
369
294
  }
370
- parsedPassage.passages = this.populate(parsedPassage)
371
- parsedPassage.scripture = this.scripturize(parsedPassage)
372
295
  })
373
296
 
297
+ parsedPassage.passages = this.populate(parsedPassage)
298
+ parsedPassage.scripture = this.scripturize(parsedPassage)
374
299
  parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
375
300
 
376
301
  return parsedPassage
377
302
  })
303
+
378
304
  this.versification()
379
- 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
380
316
  }
381
317
 
382
318
  _searchVersificationDifferences(passage) {
@@ -399,8 +335,11 @@ class CodexParser {
399
335
  }
400
336
 
401
337
  _setVersion(passage) {
402
- this.version = passage.version.abbreviation
403
- this._searchVersificationDifferences(passage)
338
+ this.version = passage.version ? passage.version.abbreviation : "eng"
339
+
340
+ if (this.version !== "eng") {
341
+ this._searchVersificationDifferences(passage)
342
+ }
404
343
  }
405
344
 
406
345
  versification() {
@@ -449,33 +388,84 @@ class CodexParser {
449
388
  * @param {Array} verses - Array of verse numbers to add to the set of passages
450
389
  * @return {Array} Array of passage objects
451
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
+ */
452
397
  populate(parsedPassage) {
453
398
  const passages = []
454
- // Helper function to process a parsed passage's verses
455
- const processVerses = (passage) => {
456
- const { book, chapter, verses } = passage
457
-
458
- if (!verses) {
459
- 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
+ })
460
406
  }
461
- verses.forEach((verse) => {
462
- if (isNaN(verse)) {
463
- const [start, end] = verse.split("-").map(Number) // Handle ranges
464
- for (let i = start; i <= end; i++) {
465
- passages.push({ book, chapter: Number(chapter), verse: i })
466
- }
467
- } 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) => {
468
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
+ })
469
425
  }
470
- })
471
- }
472
-
473
- // Process main passage
474
- processVerses(parsedPassage)
475
-
476
- // Process 'to' object if it exists (for cross-chapter ranges)
477
- if (parsedPassage.to) {
478
- 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
+ }
479
469
  }
480
470
 
481
471
  return passages
@@ -589,6 +579,7 @@ class CodexParser {
589
579
  * and builds the original and scripture properties.
590
580
  * **This method will always combine based on English versification. LXX and MT versifications will be reflected in the combined passage.passages.versification.**
591
581
  * This method will fail if the passages are not to the same book and chapter.
582
+ * TODO: Add support for MT and LXX
592
583
  * @param {array} passages - An array of passage objects to combine.
593
584
  * @return {object} The combined passage object.
594
585
  */
@@ -816,8 +807,11 @@ class CodexParser {
816
807
  return true
817
808
  }
818
809
  _handleVersion(version, testament) {
810
+ if (this.version) {
811
+ version = this.version
812
+ }
819
813
  if (!version) {
820
- return null
814
+ version = "eng"
821
815
  }
822
816
  if (version.toLowerCase() === "lxx" && testament.toLowerCase() === "old") {
823
817
  return {
@@ -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",