codexparser 0.1.42 → 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.
- package/package.json +1 -1
- package/src/CodexParser.js +274 -197
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexparser",
|
|
3
|
-
"version": "0.1.
|
|
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": {
|
package/src/CodexParser.js
CHANGED
|
@@ -147,16 +147,19 @@ class CodexParser {
|
|
|
147
147
|
|
|
148
148
|
references.forEach((ref) => {
|
|
149
149
|
let type
|
|
150
|
-
|
|
151
150
|
if (ref.includes(":")) {
|
|
152
151
|
if (ref.includes("-")) {
|
|
153
152
|
const [start, end] = ref.split("-")
|
|
154
153
|
const startParts = start.split(":")
|
|
155
154
|
const endParts = end.split(":")
|
|
155
|
+
|
|
156
|
+
// Determine type based on the chapter (startParts[0] and endParts[0])
|
|
156
157
|
type =
|
|
158
|
+
startParts.length > 1 &&
|
|
159
|
+
endParts.length > 1 &&
|
|
157
160
|
startParts[0].trim() !== endParts[0].trim()
|
|
158
|
-
? "multi_chapter_verse_range"
|
|
159
|
-
: "chapter_verse_range"
|
|
161
|
+
? "multi_chapter_verse_range" // Chapters differ
|
|
162
|
+
: "chapter_verse_range" // Same chapter
|
|
160
163
|
} else if (ref.includes(",")) {
|
|
161
164
|
type = "comma_separated_verses"
|
|
162
165
|
} else {
|
|
@@ -206,136 +209,103 @@ class CodexParser {
|
|
|
206
209
|
this.passages = this.found.map((passage) => {
|
|
207
210
|
const book = this.bookify(passage.book)
|
|
208
211
|
const testament = this.bible.old.find((bible) => bible === book) ? "old" : "new"
|
|
212
|
+
|
|
209
213
|
// Initialize the parsed passage object
|
|
210
214
|
const parsedPassage = {
|
|
211
215
|
original: passage.book + " " + passage.reference,
|
|
212
216
|
book: book,
|
|
213
217
|
chapter: null,
|
|
214
|
-
verses: [], //
|
|
218
|
+
verses: [], // Verses stored as an array
|
|
215
219
|
type: passage.type, // Set type based on reference
|
|
216
220
|
testament: testament,
|
|
217
221
|
index: passage.index,
|
|
218
222
|
version: this._handleVersion(passage.version, testament),
|
|
219
223
|
}
|
|
220
224
|
|
|
221
|
-
// 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")
|
|
222
226
|
let parts = passage.reference.split(",")
|
|
223
227
|
|
|
224
|
-
// Check for single
|
|
228
|
+
// Check for single-chapter books
|
|
225
229
|
const singleChapterBook = this.singleChapterBook.find((bible) => bible[book])
|
|
226
230
|
|
|
227
|
-
parts.forEach((part) => {
|
|
231
|
+
parts.forEach((part, partIndex) => {
|
|
228
232
|
part = part.trim() // Clean up spaces
|
|
229
|
-
// Detect whether it uses ":" or "." for chapter:verse separation
|
|
230
233
|
const separator = part.includes(":") ? ":" : "."
|
|
231
234
|
|
|
232
235
|
if (part.includes("-")) {
|
|
233
|
-
// Handle ranges (e.g., "
|
|
234
|
-
|
|
236
|
+
// Handle ranges (e.g., "8:22-9:1")
|
|
235
237
|
let [start, end] = part.split("-")
|
|
238
|
+
|
|
236
239
|
// Handle the starting part
|
|
237
240
|
let [startChapter, startVerse] = start.includes(separator)
|
|
238
241
|
? start.split(separator)
|
|
239
242
|
: [parsedPassage.chapter, start] // Default to same chapter if no chapter is provided
|
|
240
243
|
|
|
241
244
|
parsedPassage.chapter = Number(startChapter) // Set the chapter
|
|
242
|
-
|
|
243
|
-
//
|
|
245
|
+
|
|
246
|
+
// Multi-chapter verse range logic
|
|
244
247
|
if (start.includes(separator) && end.includes(separator)) {
|
|
248
|
+
let [endChapter, endVerse] = end.split(separator)
|
|
249
|
+
|
|
245
250
|
parsedPassage.verses = this.chapterVerses[book][startChapter].slice(
|
|
246
251
|
this.chapterVerses[book][startChapter].indexOf(Number(startVerse))
|
|
247
252
|
)
|
|
248
|
-
}
|
|
249
253
|
|
|
250
|
-
// Handle same-chapter ranges (e.g., "27:27-29") and multi-chapter ranges (e.g., "Ex 2:1-3:4")
|
|
251
|
-
if (end.includes(separator)) {
|
|
252
|
-
let [endChapter, endVerse] = end.split(separator)
|
|
253
254
|
if (Number(endChapter) !== Number(startChapter)) {
|
|
254
|
-
|
|
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
|
|
255
261
|
parsedPassage.to = {
|
|
256
262
|
book: book,
|
|
257
|
-
chapter: Number(endChapter),
|
|
258
|
-
|
|
259
|
-
if (endVerse > 1) {
|
|
260
|
-
parsedPassage.to.verses = this.chapterVerses[book][Number(endChapter)].slice(
|
|
261
|
-
0,
|
|
262
|
-
this.chapterVerses[book][Number(endChapter)].indexOf(Number(endVerse)) + 1
|
|
263
|
-
)
|
|
263
|
+
chapter: Number(endChapter),
|
|
264
|
+
verses: [`${1}-${endVerse}`, ...endVerses.filter((v) => v > endVerse)],
|
|
264
265
|
}
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
|
|
267
|
+
parsedPassage.type = "multi_chapter_verse_range"
|
|
267
268
|
} else {
|
|
268
|
-
// Same-chapter range, just add to the verse array
|
|
269
269
|
parsedPassage.verses.push(`${startVerse}-${endVerse}`)
|
|
270
270
|
}
|
|
271
|
-
} else {
|
|
272
|
-
// Single-chapter range (e.g., "27:27-29" or "39-41")
|
|
273
|
-
if (!singleChapterBook) {
|
|
274
|
-
if (!startChapter) {
|
|
275
|
-
// Then we have a chapter range with no verses
|
|
276
|
-
parsedPassage.chapter = Number(start)
|
|
277
|
-
parsedPassage.verses = this.chapterVerses[book][start]
|
|
278
|
-
parsedPassage.to = {
|
|
279
|
-
book: book,
|
|
280
|
-
chapter: Number(end),
|
|
281
|
-
verses: this.chapterVerses[book][end],
|
|
282
|
-
}
|
|
283
|
-
} else {
|
|
284
|
-
//
|
|
285
|
-
parsedPassage.verses.push(`${startVerse}-${end}`)
|
|
286
|
-
}
|
|
287
|
-
} else {
|
|
288
|
-
parsedPassage.chapter = 1
|
|
289
|
-
parsedPassage.verses.push(`${startVerse}-${end}`)
|
|
290
|
-
}
|
|
291
271
|
}
|
|
292
272
|
} else {
|
|
293
|
-
// Handle individual chapter:verse references (e.g., "
|
|
294
|
-
|
|
273
|
+
// Handle individual chapter:verse references (e.g., "9:5" in "8:22-9:1,5")
|
|
295
274
|
let [chapterPart, versePart] = part.includes(separator)
|
|
296
275
|
? part.split(separator)
|
|
297
276
|
: [parsedPassage.chapter, part]
|
|
277
|
+
|
|
298
278
|
if (singleChapterBook) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
parsedPassage.verses.push(versePart) // Add single verse to array
|
|
302
|
-
} else {
|
|
303
|
-
parsedPassage.chapter = Number(chapterPart)
|
|
304
|
-
parsedPassage.verses.push(versePart) // Add single verse to array
|
|
305
|
-
}
|
|
279
|
+
parsedPassage.chapter = 1
|
|
280
|
+
parsedPassage.verses.push(versePart) // Add single verse to array
|
|
306
281
|
} else {
|
|
307
|
-
// Need to check if chapterPart is undefined
|
|
308
|
-
// If it's undefined, then versePart actually is the chapter and we need to populate the
|
|
309
|
-
// verses from this.chapterVerses
|
|
310
|
-
|
|
311
282
|
if (chapterPart) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (!this.chapterVerses[book][parsedPassage.chapter]) {
|
|
317
|
-
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))
|
|
318
287
|
} else {
|
|
319
|
-
parsedPassage.
|
|
320
|
-
|
|
321
|
-
"-" +
|
|
322
|
-
this.chapterVerses[book][parsedPassage.chapter][
|
|
323
|
-
this.chapterVerses[book][parsedPassage.chapter].length - 1
|
|
324
|
-
],
|
|
325
|
-
]
|
|
326
|
-
parsedPassage.type = "single_chapter"
|
|
288
|
+
parsedPassage.chapter = Number(chapterPart)
|
|
289
|
+
parsedPassage.verses.push(Number(versePart))
|
|
327
290
|
}
|
|
328
291
|
}
|
|
329
292
|
}
|
|
330
293
|
}
|
|
294
|
+
|
|
331
295
|
parsedPassage.passages = this.populate(parsedPassage)
|
|
332
296
|
parsedPassage.scripture = this.scripturize(parsedPassage)
|
|
333
297
|
})
|
|
334
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
|
+
|
|
335
304
|
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
336
305
|
|
|
337
306
|
return parsedPassage
|
|
338
307
|
})
|
|
308
|
+
|
|
339
309
|
this.versification()
|
|
340
310
|
return this // Return this instance
|
|
341
311
|
}
|
|
@@ -350,18 +320,17 @@ class CodexParser {
|
|
|
350
320
|
return range
|
|
351
321
|
}
|
|
352
322
|
|
|
353
|
-
_searchVersificationDifferences(
|
|
354
|
-
|
|
323
|
+
_searchVersificationDifferences(book, chapter, version) {
|
|
324
|
+
version = version.toLowerCase()
|
|
355
325
|
if (!this.chapterVerses[book][chapter]) return
|
|
356
|
-
|
|
357
326
|
// Loop through each key-value pair in the dictionary
|
|
358
327
|
for (const [key, value] of Object.entries(this.versificationDifferences[book])) {
|
|
359
328
|
// Check if the key starts with the desired chapter
|
|
360
|
-
if (value[version
|
|
329
|
+
if (value[version].startsWith(`${chapter}:`)) {
|
|
361
330
|
// Ensure the version exists in the value object
|
|
362
|
-
if (value[version
|
|
331
|
+
if (value[version]) {
|
|
363
332
|
// Extract the verse number from the value
|
|
364
|
-
const verse = value[version
|
|
333
|
+
const verse = value[version].split(":")[1]
|
|
365
334
|
this.chapterVerses[book][chapter].push(Number(verse))
|
|
366
335
|
}
|
|
367
336
|
}
|
|
@@ -370,11 +339,11 @@ class CodexParser {
|
|
|
370
339
|
return this.chapterVerses // Return the array of verses
|
|
371
340
|
}
|
|
372
341
|
|
|
373
|
-
_setVersion(
|
|
374
|
-
this.version =
|
|
342
|
+
_setVersion(book, chapter, version) {
|
|
343
|
+
this.version = version ? version : "eng"
|
|
375
344
|
|
|
376
345
|
if (this.version !== "eng") {
|
|
377
|
-
this._searchVersificationDifferences(
|
|
346
|
+
this._searchVersificationDifferences(book, chapter, version)
|
|
378
347
|
}
|
|
379
348
|
}
|
|
380
349
|
|
|
@@ -432,8 +401,8 @@ class CodexParser {
|
|
|
432
401
|
populate(parsedPassage) {
|
|
433
402
|
const passages = []
|
|
434
403
|
const { book, chapter, verses, type, to } = parsedPassage
|
|
435
|
-
|
|
436
|
-
this._setVersion(
|
|
404
|
+
const version = parsedPassage.version ? parsedPassage.version.abbreviation : "eng"
|
|
405
|
+
this._setVersion(book, chapter, version) // Set version data if needed
|
|
437
406
|
|
|
438
407
|
if (type === "single_chapter") {
|
|
439
408
|
// Handle entire chapter references
|
|
@@ -460,7 +429,6 @@ class CodexParser {
|
|
|
460
429
|
}
|
|
461
430
|
} else if (type === "multi_chapter_verse_range") {
|
|
462
431
|
// Handle multi-chapter verse ranges (e.g., 3:1-5:6)
|
|
463
|
-
|
|
464
432
|
const startChapter = chapter
|
|
465
433
|
const startVerse = verses[0]
|
|
466
434
|
const endChapter = to.chapter
|
|
@@ -560,60 +528,63 @@ class CodexParser {
|
|
|
560
528
|
* @return {object} The object with the human-readable name, chapter and verses and a hash.
|
|
561
529
|
*/
|
|
562
530
|
scripturize(passage) {
|
|
563
|
-
// Helper to format a single chapter:verse combination
|
|
564
|
-
const formatChapterVerse = (chapter,
|
|
565
|
-
if (!chapter) return ""
|
|
566
|
-
if (
|
|
567
|
-
|
|
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(",")}`
|
|
568
547
|
}
|
|
569
548
|
|
|
570
|
-
//
|
|
549
|
+
// Start constructing the passage string
|
|
571
550
|
let combined = `${passage.book}`
|
|
572
551
|
|
|
573
|
-
if (passage.type === "multi_chapter_verse_range") {
|
|
574
|
-
// Multi-chapter verse range
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
passage.to.verses[passage.to.verses.length - 1]
|
|
580
|
-
)}` // End chapter:last verse
|
|
581
|
-
}
|
|
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
|
+
)}`
|
|
582
558
|
} else if (passage.type === "chapter_verse_range") {
|
|
583
559
|
// Single-chapter verse range
|
|
584
|
-
combined += ` ${formatChapterVerse(
|
|
585
|
-
passage.chapter,
|
|
586
|
-
passage.verses[0],
|
|
587
|
-
passage.verses[passage.verses.length - 1]
|
|
588
|
-
)}`
|
|
560
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
589
561
|
} else if (passage.type === "comma_separated_verses") {
|
|
590
562
|
// Comma-separated verses
|
|
591
|
-
combined += ` ${passage.chapter
|
|
592
|
-
} else if (passage.type === "chapter_range") {
|
|
563
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
564
|
+
} else if (passage.type === "chapter_range" && passage.to) {
|
|
593
565
|
// Chapter range
|
|
594
|
-
combined += ` ${passage.chapter}
|
|
595
|
-
passage.to.verses[passage.to.verses.length - 1]
|
|
596
|
-
}`
|
|
566
|
+
combined += ` ${passage.chapter}-${passage.to.chapter}`
|
|
597
567
|
} else {
|
|
598
568
|
// Single chapter or single verse
|
|
599
|
-
combined += ` ${formatChapterVerse(passage.chapter, passage.verses
|
|
569
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
600
570
|
}
|
|
601
571
|
|
|
602
|
-
// Generate chapter:verse
|
|
572
|
+
// Generate the chapter:verse (cv) string
|
|
603
573
|
const cv = passage.to
|
|
604
|
-
? `${formatChapterVerse(passage.chapter, passage.verses
|
|
574
|
+
? `${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
605
575
|
passage.to.chapter,
|
|
606
|
-
passage.to.verses
|
|
576
|
+
passage.to.verses
|
|
607
577
|
)}`
|
|
608
|
-
: formatChapterVerse(passage.chapter, passage.verses
|
|
578
|
+
: formatChapterVerse(passage.chapter, passage.verses)
|
|
609
579
|
|
|
610
|
-
// Generate
|
|
611
|
-
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, "
|
|
580
|
+
// Generate the hash
|
|
581
|
+
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, ".").replace(/-/g, ".")}`
|
|
612
582
|
|
|
583
|
+
// Return the final scripture object
|
|
613
584
|
return {
|
|
614
|
-
passage: combined,
|
|
615
|
-
cv: cv,
|
|
616
|
-
hash: hash,
|
|
585
|
+
passage: combined,
|
|
586
|
+
cv: cv,
|
|
587
|
+
hash: hash,
|
|
617
588
|
}
|
|
618
589
|
}
|
|
619
590
|
|
|
@@ -627,29 +598,96 @@ class CodexParser {
|
|
|
627
598
|
* @return {object} The combined passage object.
|
|
628
599
|
*/
|
|
629
600
|
combine(passages) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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.")
|
|
634
609
|
}
|
|
635
610
|
|
|
636
|
-
|
|
611
|
+
// Initialize combined passage object
|
|
612
|
+
const combined = {
|
|
613
|
+
...passages[0],
|
|
614
|
+
verses: [],
|
|
615
|
+
passages: [],
|
|
616
|
+
to: null,
|
|
617
|
+
type: null,
|
|
618
|
+
}
|
|
637
619
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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()
|
|
644
629
|
}
|
|
630
|
+
chapterVerses[p.chapter].add(p.verse)
|
|
631
|
+
combined.passages.push(p) // Add individual passage
|
|
645
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
|
+
}
|
|
646
642
|
})
|
|
647
643
|
|
|
648
|
-
|
|
644
|
+
// Ensure unique and sorted passages
|
|
645
|
+
combined.passages = Array.from(new Set(combined.passages.map(JSON.stringify))).map(JSON.parse)
|
|
649
646
|
|
|
650
|
-
|
|
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
|
+
})
|
|
651
667
|
|
|
652
|
-
|
|
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()
|
|
653
691
|
}
|
|
654
692
|
|
|
655
693
|
/**
|
|
@@ -660,86 +698,100 @@ class CodexParser {
|
|
|
660
698
|
* @return {object} The combined passage object.
|
|
661
699
|
*/
|
|
662
700
|
join(passages) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const uniquePassages = new Set() // Track unique passages to prevent duplicates
|
|
667
|
-
// Add initial passages to the unique set to avoid duplication
|
|
668
|
-
newObject.passages.forEach((p) => {
|
|
669
|
-
const passageKey = `${p.book}-${p.chapter}-${p.verse}`
|
|
670
|
-
uniquePassages.add(passageKey)
|
|
671
|
-
})
|
|
701
|
+
if (!passages || passages.length === 0) {
|
|
702
|
+
throw new Error("No passages provided to join.")
|
|
703
|
+
}
|
|
672
704
|
|
|
673
|
-
//
|
|
674
|
-
passages.
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
705
|
+
// Ensure all passages are from the same book
|
|
706
|
+
const uniqueBooks = [...new Set(passages.map((p) => p.book))]
|
|
707
|
+
if (uniqueBooks.length > 1) {
|
|
708
|
+
throw new Error("Passages must be from the same book to join.")
|
|
709
|
+
}
|
|
678
710
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
711
|
+
// Start with the base object
|
|
712
|
+
const combined = {
|
|
713
|
+
...passages[0],
|
|
714
|
+
verses: [],
|
|
715
|
+
passages: [],
|
|
716
|
+
to: null,
|
|
717
|
+
scripture: {},
|
|
718
|
+
type: null,
|
|
719
|
+
}
|
|
682
720
|
|
|
683
|
-
|
|
684
|
-
|
|
721
|
+
const chapterVerses = {}
|
|
722
|
+
let firstChapter = null
|
|
723
|
+
let lastChapter = null
|
|
685
724
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
725
|
+
// Collect all verses and passages, grouped by chapter
|
|
726
|
+
passages.forEach((passage) => {
|
|
727
|
+
passage.passages.forEach((p) => {
|
|
728
|
+
if (!chapterVerses[p.chapter]) {
|
|
729
|
+
chapterVerses[p.chapter] = new Set()
|
|
690
730
|
}
|
|
731
|
+
chapterVerses[p.chapter].add(p.verse)
|
|
732
|
+
combined.passages.push(p) // Add individual passage
|
|
691
733
|
})
|
|
692
|
-
})
|
|
693
734
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if (
|
|
697
|
-
|
|
735
|
+
// Track first and last chapters
|
|
736
|
+
const chapters = passage.passages.map((p) => p.chapter)
|
|
737
|
+
if (!firstChapter || Math.min(...chapters) < firstChapter) {
|
|
738
|
+
firstChapter = Math.min(...chapters)
|
|
739
|
+
}
|
|
740
|
+
if (!lastChapter || Math.max(...chapters) > lastChapter) {
|
|
741
|
+
lastChapter = Math.max(...chapters)
|
|
698
742
|
}
|
|
699
|
-
return a.verse - b.verse // Sort by verse within the same chapter
|
|
700
743
|
})
|
|
701
744
|
|
|
702
|
-
//
|
|
745
|
+
// Ensure unique and sorted passages
|
|
746
|
+
combined.passages = Array.from(new Set(combined.passages.map(JSON.stringify))).map(JSON.parse)
|
|
747
|
+
|
|
748
|
+
// Process chapter and verse data
|
|
703
749
|
const chapterStrings = []
|
|
704
|
-
|
|
705
|
-
|
|
750
|
+
const sortedChapters = Object.keys(chapterVerses)
|
|
751
|
+
.map(Number)
|
|
752
|
+
.sort((a, b) => a - b)
|
|
706
753
|
|
|
707
|
-
|
|
708
|
-
const verses = Array.from(
|
|
709
|
-
const mergedVerses = this.mergeRanges(verses)
|
|
754
|
+
sortedChapters.forEach((chapter) => {
|
|
755
|
+
const verses = Array.from(chapterVerses[chapter]).sort((a, b) => a - b)
|
|
756
|
+
const mergedVerses = this.mergeRanges(verses)
|
|
710
757
|
chapterStrings.push(`${chapter}:${mergedVerses.join(",")}`)
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (!firstChapter) firstChapter = Number(chapter) // Ensure chapter is a number
|
|
714
|
-
lastChapter = Number(chapter) // Always update to the current chapter as a number
|
|
715
|
-
|
|
716
|
-
// Update the newObject.verses with the merged ranges for the current chapter
|
|
717
|
-
if (Number(chapter) === firstChapter) {
|
|
718
|
-
newObject.verses = mergedVerses
|
|
758
|
+
if (chapter === firstChapter) {
|
|
759
|
+
combined.verses = mergedVerses // First chapter's verses
|
|
719
760
|
}
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
// Build the final combined object with `to` key for multi-chapter passages
|
|
723
|
-
newObject.original = `${newObject.book} ${firstChapter}:${newObject.verses.join(",")}`
|
|
761
|
+
})
|
|
724
762
|
|
|
763
|
+
// Handle multi-chapter ranges
|
|
725
764
|
if (firstChapter !== lastChapter) {
|
|
726
|
-
|
|
727
|
-
|
|
765
|
+
combined.type = "multi_chapter_verse_range"
|
|
766
|
+
combined.to = {
|
|
767
|
+
book: combined.book,
|
|
728
768
|
chapter: lastChapter,
|
|
729
|
-
verses: this.mergeRanges(Array.from(
|
|
769
|
+
verses: this.mergeRanges(Array.from(chapterVerses[lastChapter])),
|
|
770
|
+
}
|
|
771
|
+
combined.original = `${combined.book} ${firstChapter}:${combined.verses.join(
|
|
772
|
+
","
|
|
773
|
+
)}-${lastChapter}:${combined.to.verses.join(",")}`
|
|
774
|
+
} else {
|
|
775
|
+
// Single-chapter range or comma-separated
|
|
776
|
+
if (combined.verses.length > 1) {
|
|
777
|
+
combined.type = "chapter_verse_range"
|
|
778
|
+
} else {
|
|
779
|
+
combined.type = "chapter_verse"
|
|
730
780
|
}
|
|
781
|
+
combined.original = `${combined.book} ${firstChapter}:${combined.verses.join(",")}`
|
|
731
782
|
}
|
|
732
783
|
|
|
733
|
-
// Build the scripture
|
|
734
|
-
const chapterString = chapterStrings.join(",")
|
|
735
|
-
|
|
736
|
-
passage: `${
|
|
784
|
+
// Build the scripture property
|
|
785
|
+
const chapterString = chapterStrings.join(",")
|
|
786
|
+
combined.scripture = {
|
|
787
|
+
passage: `${combined.book} ${chapterString}`,
|
|
737
788
|
cv: chapterString,
|
|
738
|
-
hash: `${
|
|
789
|
+
hash: `${combined.book.toLowerCase()}_${chapterString.replace(/:/g, ".").replace(/,/g, ".")}`,
|
|
739
790
|
}
|
|
740
791
|
|
|
741
|
-
return
|
|
792
|
+
return combined
|
|
742
793
|
}
|
|
794
|
+
|
|
743
795
|
mergeRanges(verses) {
|
|
744
796
|
const sortedVerses = [...new Set(verses)].sort((a, b) => a - b)
|
|
745
797
|
const merged = []
|
|
@@ -750,11 +802,9 @@ class CodexParser {
|
|
|
750
802
|
if (sortedVerses[i] === end + 1) {
|
|
751
803
|
end = sortedVerses[i]
|
|
752
804
|
} else {
|
|
753
|
-
// Push
|
|
805
|
+
// Push range or single verse
|
|
754
806
|
if (start === end) {
|
|
755
807
|
merged.push(`${start}`)
|
|
756
|
-
} else if (end === start + 1) {
|
|
757
|
-
merged.push(`${start},${end}`)
|
|
758
808
|
} else {
|
|
759
809
|
merged.push(`${start}-${end}`)
|
|
760
810
|
}
|
|
@@ -763,11 +813,9 @@ class CodexParser {
|
|
|
763
813
|
}
|
|
764
814
|
}
|
|
765
815
|
|
|
766
|
-
// Push the final range or
|
|
816
|
+
// Push the final range or single verse
|
|
767
817
|
if (start === end) {
|
|
768
818
|
merged.push(`${start}`)
|
|
769
|
-
} else if (end === start + 1) {
|
|
770
|
-
merged.push(`${start},${end}`)
|
|
771
819
|
} else {
|
|
772
820
|
merged.push(`${start}-${end}`)
|
|
773
821
|
}
|
|
@@ -849,6 +897,35 @@ class CodexParser {
|
|
|
849
897
|
}
|
|
850
898
|
}
|
|
851
899
|
}
|
|
900
|
+
for (let i = 0; i < passage.verses.length; i++) {
|
|
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
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
852
929
|
return true
|
|
853
930
|
}
|
|
854
931
|
_handleVersion(version, testament) {
|