codexparser 0.1.55 → 0.1.57
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/README.md +242 -0
- package/package.json +1 -1
- package/src/CodexParser.js +367 -437
- package/src/abbr/sbl.js +70 -0
- package/src/esv.js +2 -1
package/src/CodexParser.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const versified = require("./versified")
|
|
2
2
|
const bible = require("./bible")
|
|
3
3
|
const { bookRegex, chapterRegex, verseRegex, scripturesRegex } = require("./regex")
|
|
4
|
-
const
|
|
4
|
+
const abbreviations = require("./abbr")
|
|
5
|
+
const sblAbbreviations = require("./abbr/sbl")
|
|
5
6
|
const dump = require("./functions").dump
|
|
6
7
|
const dd = require("./functions").dd
|
|
7
8
|
const sch = require("./functions").sch
|
|
@@ -16,7 +17,8 @@ class CodexParser {
|
|
|
16
17
|
this.chapterRegex = chapterRegex
|
|
17
18
|
this.verseRegex = verseRegex
|
|
18
19
|
this.scripturesRegex = scripturesRegex
|
|
19
|
-
this.abbreviations =
|
|
20
|
+
this.abbreviations = abbreviations
|
|
21
|
+
this.sblAbbreviations = sblAbbreviations
|
|
20
22
|
this.versificationDifferences = versified
|
|
21
23
|
this.singleChapterBook = [
|
|
22
24
|
sch("Jude", 25),
|
|
@@ -25,43 +27,36 @@ class CodexParser {
|
|
|
25
27
|
sch("Obadiah", 21),
|
|
26
28
|
sch("Philemon", 25),
|
|
27
29
|
]
|
|
28
|
-
|
|
29
30
|
this.chapterVerses = chapter_verses
|
|
30
31
|
this.error = false
|
|
31
32
|
this.version = null
|
|
33
|
+
this.SINGLE_CHAPTER = "single_chapter"
|
|
34
|
+
this.CHAPTER_VERSE = "chapter_verse"
|
|
35
|
+
this.CHAPTER_VERSE_RANGE = "chapter_verse_range"
|
|
36
|
+
this.COMMA_SEPARATED = "comma_separated_verses"
|
|
37
|
+
this.CHAPTER_RANGE = "chapter_range"
|
|
38
|
+
this.MULTI_CHAPTER_RANGE = "multi_chapter_verse_range"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getChapterVerses(book, chapter) {
|
|
42
|
+
const singleChapterBook = this.singleChapterBook.find((b) => Object.keys(b)[0] === book)
|
|
43
|
+
return singleChapterBook ? singleChapterBook[book][chapter] || [] : this.chapterVerses[book]?.[chapter] || []
|
|
32
44
|
}
|
|
33
45
|
|
|
34
|
-
/**
|
|
35
|
-
* Scans the given text for Bible references and stores all found references in the `found` property of the instance.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} text - The text to scan for Bible references.
|
|
38
|
-
* @return {CodexParser} - Returns the instance itself, enabling method chaining.
|
|
39
|
-
*/
|
|
40
46
|
scan(text) {
|
|
41
|
-
// Combine Old and New Testament book names into a single array
|
|
42
47
|
const fullNames = [...this.bible.old, ...this.bible.new]
|
|
43
|
-
|
|
44
|
-
// Retrieve all abbreviation keys from the abbreviations object
|
|
45
48
|
const abbreviations = Object.keys(this.abbreviations)
|
|
46
|
-
|
|
47
|
-
// Initialize the `found` array to store the results
|
|
48
49
|
this.found = []
|
|
49
|
-
|
|
50
|
-
// Preprocess input text: normalize separators while preserving abbreviations
|
|
51
50
|
let normalizedText = text
|
|
52
|
-
.replace(/\.(?=\d)/g, ":")
|
|
53
|
-
.replace(/(\b[A-Za-z]+)\.(?=\s|$)/g, "$1")
|
|
54
|
-
.replace(/\s+/g, " ")
|
|
55
|
-
|
|
56
|
-
// Convert Bible book names, abbreviations, and input text to lowercase for case-insensitive matching
|
|
51
|
+
.replace(/\.(?=\d)/g, ":")
|
|
52
|
+
.replace(/(\b[A-Za-z]+)\.(?=\s|$)/g, "$1")
|
|
53
|
+
.replace(/\s+/g, " ")
|
|
57
54
|
const lowercaseBibleFullNames = fullNames.map((book) => book.toLowerCase())
|
|
58
55
|
const lowercaseBibleAbbreviations = abbreviations.map((abbr) => abbr.toLowerCase())
|
|
59
56
|
const lowerCaseText = normalizedText.toLowerCase()
|
|
60
|
-
|
|
61
57
|
let i = 0
|
|
62
58
|
|
|
63
59
|
const isValidChapterVerseChar = (char) => /[^A-Za-z]/.test(char)
|
|
64
|
-
|
|
65
60
|
const isNextBibleBook = (startIndex) => {
|
|
66
61
|
const textAfterCurrentPosition = lowerCaseText.substring(startIndex).trim()
|
|
67
62
|
return (
|
|
@@ -69,7 +64,6 @@ class CodexParser {
|
|
|
69
64
|
lowercaseBibleAbbreviations.some((abbr) => textAfterCurrentPosition.startsWith(abbr))
|
|
70
65
|
)
|
|
71
66
|
}
|
|
72
|
-
|
|
73
67
|
const detectSuffix = (startIndex) => {
|
|
74
68
|
const suffixMatch = normalizedText.substring(startIndex).match(/\b(LXX|MT)\b/i)
|
|
75
69
|
return suffixMatch ? suffixMatch[0].toUpperCase() : null
|
|
@@ -107,7 +101,6 @@ class CodexParser {
|
|
|
107
101
|
|
|
108
102
|
while (i < normalizedText.length && isValidChapterVerseChar(normalizedText[i])) {
|
|
109
103
|
if (isNextBibleBook(i)) break
|
|
110
|
-
|
|
111
104
|
if (normalizedText[i] === ";") {
|
|
112
105
|
const formattedReference = chapterVerse.trim().replace(/[^a-zA-Z0-9]+$/, "")
|
|
113
106
|
if (formattedReference) references.push(formattedReference)
|
|
@@ -115,7 +108,6 @@ class CodexParser {
|
|
|
115
108
|
i++
|
|
116
109
|
continue
|
|
117
110
|
}
|
|
118
|
-
|
|
119
111
|
chapterVerse += normalizedText[i]
|
|
120
112
|
i++
|
|
121
113
|
}
|
|
@@ -134,14 +126,12 @@ class CodexParser {
|
|
|
134
126
|
const [start, end] = ref.split("-")
|
|
135
127
|
const startParts = start.split(":")
|
|
136
128
|
const endParts = end.split(":")
|
|
137
|
-
|
|
138
|
-
// Determine type based on the chapter (startParts[0] and endParts[0])
|
|
139
129
|
type =
|
|
140
130
|
startParts.length > 1 &&
|
|
141
131
|
endParts.length > 1 &&
|
|
142
132
|
startParts[0].trim() !== endParts[0].trim()
|
|
143
|
-
? "multi_chapter_verse_range"
|
|
144
|
-
: "chapter_verse_range"
|
|
133
|
+
? "multi_chapter_verse_range"
|
|
134
|
+
: "chapter_verse_range"
|
|
145
135
|
} else if (ref.includes(",")) {
|
|
146
136
|
type = "comma_separated_verses"
|
|
147
137
|
} else {
|
|
@@ -178,129 +168,78 @@ class CodexParser {
|
|
|
178
168
|
return this
|
|
179
169
|
}
|
|
180
170
|
|
|
181
|
-
/**
|
|
182
|
-
* Parses a given reference and returns an object with the parsed passage,
|
|
183
|
-
* including book, chapter, verse, type, testament, index, and version.
|
|
184
|
-
*
|
|
185
|
-
* @param {string} reference - The reference to parse.
|
|
186
|
-
* @returns {object} An object with the parsed passage.
|
|
187
|
-
*/
|
|
188
171
|
parse(reference) {
|
|
189
|
-
this.scan(reference)
|
|
172
|
+
this.scan(reference)
|
|
190
173
|
|
|
191
174
|
this.passages = this.found.map((passage) => {
|
|
192
175
|
const book = this.bookify(passage.book)
|
|
193
176
|
const testament = this.bible.old.includes(book) ? "old" : "new"
|
|
194
|
-
const singleChapterBook = this.singleChapterBook.find((b) => Object.keys(b)[0] === book)
|
|
195
177
|
const parsedPassage = {
|
|
196
|
-
original: passage.book
|
|
197
|
-
book
|
|
178
|
+
original: `${passage.book} ${passage.reference}`,
|
|
179
|
+
book,
|
|
198
180
|
chapter: null,
|
|
199
181
|
verses: [],
|
|
200
182
|
type: passage.type,
|
|
201
|
-
testament
|
|
183
|
+
testament,
|
|
202
184
|
index: passage.index,
|
|
203
185
|
version: this._handleVersion(passage.version, testament),
|
|
186
|
+
passages: [],
|
|
187
|
+
scripture: null,
|
|
188
|
+
valid: true,
|
|
189
|
+
start: null,
|
|
190
|
+
end: null,
|
|
204
191
|
}
|
|
205
192
|
|
|
206
|
-
|
|
207
|
-
|
|
193
|
+
this.parseReferenceParts(parsedPassage, passage.reference.split(","))
|
|
194
|
+
parsedPassage.passages = this.populate(parsedPassage)
|
|
195
|
+
parsedPassage.scripture = this.scripturize(parsedPassage)
|
|
196
|
+
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
208
197
|
|
|
209
|
-
|
|
210
|
-
|
|
198
|
+
// Add SBL abbreviation as full reference with period, en dashes, and space after commas for comma-separated verses
|
|
199
|
+
const sblBook = this.sblAbbreviations[book] || book
|
|
200
|
+
let abbr = parsedPassage.scripture.passage.replace(book, `${sblBook}.`).replace(/-/g, "–")
|
|
201
|
+
if (parsedPassage.type === "comma_separated_verses") {
|
|
202
|
+
const versePart = parsedPassage.verses.map((v) => `${v}`).join(", ")
|
|
203
|
+
abbr = `${sblBook}. ${parsedPassage.chapter}:${versePart}`
|
|
204
|
+
}
|
|
205
|
+
parsedPassage.abbr = abbr
|
|
211
206
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
207
|
+
if (parsedPassage.type === this.MULTI_CHAPTER_RANGE) {
|
|
208
|
+
this.handleMultiChapterRange(parsedPassage, passage.reference)
|
|
209
|
+
} else {
|
|
210
|
+
delete parsedPassage.to
|
|
211
|
+
}
|
|
218
212
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
parsedPassage.chapter = 1
|
|
231
|
-
parsedPassage.type = "single_chapter"
|
|
232
|
-
parsedPassage.verses = [`1-${verseCount}`] // e.g., "1-13"
|
|
233
|
-
} else if (part.includes("-")) {
|
|
234
|
-
// "2 John 2-5" → "2 John 1:2-5"
|
|
235
|
-
parsedPassage.chapter = 1
|
|
236
|
-
parsedPassage.verses.push(part) // e.g., "2-5"
|
|
237
|
-
parsedPassage.type = "chapter_verse_range"
|
|
238
|
-
} else {
|
|
239
|
-
// "2 John 2" → "2 John 1:2"
|
|
240
|
-
const num = Number(part)
|
|
241
|
-
if (num > 1 || (num === 1 && parts.length > 1)) {
|
|
242
|
-
parsedPassage.chapter = 1
|
|
243
|
-
parsedPassage.verses.push(num) // Treat as verse number
|
|
244
|
-
parsedPassage.type = "chapter_verse"
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
} else if (part.includes("-") && !parsedPassage.chapter) {
|
|
248
|
-
// Range without chapter for multi-chapter books (e.g., "Matthew 3-5")
|
|
249
|
-
const [start, end] = part.split("-").map(Number)
|
|
250
|
-
parsedPassage.chapter = start
|
|
251
|
-
parsedPassage.verses = [
|
|
252
|
-
`${this.chapterVerses[book][start][0]}-${this.chapterVerses[book][start].slice(-1)[0]}`,
|
|
253
|
-
]
|
|
254
|
-
parsedPassage.to = {
|
|
255
|
-
book,
|
|
256
|
-
chapter: end,
|
|
257
|
-
verses: [`${this.chapterVerses[book][end][0]}-${this.chapterVerses[book][end].slice(-1)[0]}`],
|
|
258
|
-
}
|
|
259
|
-
parsedPassage.type = "chapter_range"
|
|
260
|
-
} else if (part.includes("-")) {
|
|
261
|
-
// Verse range in current chapter (e.g., "8-9" after "40:3-5")
|
|
262
|
-
parsedPassage.verses.push(part)
|
|
263
|
-
parsedPassage.type = "chapter_verse_range"
|
|
264
|
-
} else {
|
|
265
|
-
// Single number (chapter or verse) for multi-chapter books
|
|
266
|
-
if (partIndex === 0 && !parsedPassage.chapter) {
|
|
267
|
-
parsedPassage.chapter = Number(part)
|
|
268
|
-
parsedPassage.type = "single_chapter"
|
|
269
|
-
// For multi-chapter books, set verses to full chapter range
|
|
270
|
-
if (
|
|
271
|
-
!singleChapterBook &&
|
|
272
|
-
this.chapterVerses[book] &&
|
|
273
|
-
this.chapterVerses[book][parsedPassage.chapter]
|
|
274
|
-
) {
|
|
275
|
-
const chapterVerses = this.chapterVerses[book][parsedPassage.chapter]
|
|
276
|
-
parsedPassage.verses = [`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`]
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
parsedPassage.verses.push(Number(part))
|
|
280
|
-
parsedPassage.type = "comma_separated_verses"
|
|
281
|
-
}
|
|
213
|
+
if (parsedPassage.passages.length > 0) {
|
|
214
|
+
const sortedPassages = parsedPassage.passages.slice().sort((a, b) => {
|
|
215
|
+
if (a.chapter !== b.chapter) return a.chapter - b.chapter
|
|
216
|
+
return a.verse - b.verse
|
|
217
|
+
})
|
|
218
|
+
const firstPassage = sortedPassages[0]
|
|
219
|
+
const lastPassage = sortedPassages[sortedPassages.length - 1]
|
|
220
|
+
parsedPassage.start = {
|
|
221
|
+
book: firstPassage.book,
|
|
222
|
+
chapter: firstPassage.chapter,
|
|
223
|
+
verse: firstPassage.verse,
|
|
282
224
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
225
|
+
parsedPassage.end = {
|
|
226
|
+
book: lastPassage.book,
|
|
227
|
+
chapter: lastPassage.chapter,
|
|
228
|
+
verse: lastPassage.verse,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
289
231
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const lastPart = parts[parts.length - 1]
|
|
296
|
-
const [endChapter, endVerse] = lastPart.split(":")
|
|
297
|
-
parsedPassage.to = {
|
|
298
|
-
book: book,
|
|
299
|
-
chapter: Number(endChapter),
|
|
300
|
-
verses: endVerse.includes("-") ? [endVerse] : [Number(endVerse)],
|
|
232
|
+
if (!parsedPassage.version) {
|
|
233
|
+
parsedPassage.version = {
|
|
234
|
+
name: "English",
|
|
235
|
+
value: "ENG",
|
|
236
|
+
abbreviation: "eng",
|
|
301
237
|
}
|
|
302
|
-
}
|
|
303
|
-
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Attach the reference method to this individual passage object
|
|
241
|
+
parsedPassage.reference = function () {
|
|
242
|
+
return this.scripture.passage
|
|
304
243
|
}
|
|
305
244
|
|
|
306
245
|
return parsedPassage
|
|
@@ -309,9 +248,102 @@ class CodexParser {
|
|
|
309
248
|
this.versification()
|
|
310
249
|
return this
|
|
311
250
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
251
|
+
|
|
252
|
+
parseReferenceParts(passage, parts) {
|
|
253
|
+
const singleChapterBook = this.singleChapterBook.find((b) => Object.keys(b)[0] === passage.book)
|
|
254
|
+
|
|
255
|
+
parts.forEach((part, index) => {
|
|
256
|
+
part = part.trim()
|
|
257
|
+
const isFirstPart = index === 0
|
|
258
|
+
|
|
259
|
+
if (part.includes(":")) {
|
|
260
|
+
this.parseChapterVerse(passage, part, isFirstPart)
|
|
261
|
+
} else if (singleChapterBook) {
|
|
262
|
+
this.parseSingleChapterBook(passage, part, isFirstPart && parts.length === 1)
|
|
263
|
+
} else if (part.includes("-")) {
|
|
264
|
+
this.parseRange(passage, part, isFirstPart)
|
|
265
|
+
} else {
|
|
266
|
+
this.parseSingleNumber(passage, part, isFirstPart)
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
parseChapterVerse(passage, part, isFirstPart) {
|
|
272
|
+
const [chapter, verse] = part.split(":")
|
|
273
|
+
if (isFirstPart) passage.chapter = Number(chapter)
|
|
274
|
+
passage.type = verse.includes("-") ? this.CHAPTER_VERSE_RANGE : this.CHAPTER_VERSE
|
|
275
|
+
passage.verses.push(verse.includes("-") ? verse : Number(verse))
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
parseSingleChapterBook(passage, part, isWholeChapter) {
|
|
279
|
+
const verseCount = this.getChapterVerses(passage.book, 1).length
|
|
280
|
+
if (part === "1" && isWholeChapter) {
|
|
281
|
+
passage.chapter = 1
|
|
282
|
+
passage.type = this.SINGLE_CHAPTER
|
|
283
|
+
passage.verses = [`1-${verseCount}`]
|
|
284
|
+
} else if (part.includes("-")) {
|
|
285
|
+
passage.chapter = 1
|
|
286
|
+
passage.verses.push(part)
|
|
287
|
+
passage.type = this.CHAPTER_VERSE_RANGE
|
|
288
|
+
} else {
|
|
289
|
+
const num = Number(part)
|
|
290
|
+
if (num > 1 || !isWholeChapter) {
|
|
291
|
+
passage.chapter = 1
|
|
292
|
+
passage.verses.push(num)
|
|
293
|
+
passage.type = this.CHAPTER_VERSE
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
parseRange(passage, part, isFirstPart) {
|
|
299
|
+
if (!passage.chapter && isFirstPart) {
|
|
300
|
+
const [start, end] = part.split("-").map(Number)
|
|
301
|
+
passage.chapter = start
|
|
302
|
+
const startVerses = this.getChapterVerses(passage.book, start)
|
|
303
|
+
passage.verses = [`${startVerses[0]}-${startVerses[startVerses.length - 1]}`]
|
|
304
|
+
passage.to = {
|
|
305
|
+
book: passage.book,
|
|
306
|
+
chapter: end,
|
|
307
|
+
verses: [
|
|
308
|
+
`${this.getChapterVerses(passage.book, end)[0]}-${
|
|
309
|
+
this.getChapterVerses(passage.book, end).slice(-1)[0]
|
|
310
|
+
}`,
|
|
311
|
+
],
|
|
312
|
+
}
|
|
313
|
+
passage.type = this.CHAPTER_RANGE
|
|
314
|
+
} else {
|
|
315
|
+
passage.verses.push(part)
|
|
316
|
+
passage.type = this.CHAPTER_VERSE_RANGE
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
parseSingleNumber(passage, part, isFirstPart) {
|
|
321
|
+
if (isFirstPart && !passage.chapter) {
|
|
322
|
+
passage.chapter = Number(part)
|
|
323
|
+
passage.type = this.SINGLE_CHAPTER
|
|
324
|
+
const chapterVerses = this.getChapterVerses(passage.book, passage.chapter)
|
|
325
|
+
if (chapterVerses.length) {
|
|
326
|
+
passage.verses = [`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`]
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
passage.verses.push(Number(part))
|
|
330
|
+
passage.type = this.COMMA_SEPARATED
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
handleMultiChapterRange(passage, reference) {
|
|
335
|
+
const parts = reference.split(",")
|
|
336
|
+
const lastPart = parts[parts.length - 1]
|
|
337
|
+
const [endChapter, endVerse] = lastPart.split(":")
|
|
338
|
+
if (endChapter !== String(passage.chapter)) {
|
|
339
|
+
passage.to = {
|
|
340
|
+
book: passage.book,
|
|
341
|
+
chapter: Number(endChapter),
|
|
342
|
+
verses: endVerse.includes("-") ? [endVerse] : [Number(endVerse)],
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
315
347
|
_generateRange(start, end) {
|
|
316
348
|
const range = []
|
|
317
349
|
for (let i = start; i <= end; i++) {
|
|
@@ -324,25 +356,20 @@ class CodexParser {
|
|
|
324
356
|
version = version.toLowerCase()
|
|
325
357
|
if (!this.chapterVerses[book][chapter]) return
|
|
326
358
|
if (!this.versificationDifferences[book]) return
|
|
327
|
-
// Loop through each key-value pair in the dictionary
|
|
328
359
|
for (const [key, value] of Object.entries(this.versificationDifferences[book])) {
|
|
329
|
-
// Check if the key starts with the desired chapter
|
|
330
360
|
if (value[version].startsWith(`${chapter}:`)) {
|
|
331
|
-
// Ensure the version exists in the value object
|
|
332
361
|
if (value[version]) {
|
|
333
|
-
// Extract the verse number from the value
|
|
334
362
|
const verse = value[version].split(":")[1]
|
|
335
363
|
this.chapterVerses[book][chapter].push(Number(verse))
|
|
336
364
|
}
|
|
337
365
|
}
|
|
338
366
|
}
|
|
339
367
|
this.chapterVerses[book][chapter] = Array.from(this.chapterVerses[book][chapter])
|
|
340
|
-
return this.chapterVerses
|
|
368
|
+
return this.chapterVerses
|
|
341
369
|
}
|
|
342
370
|
|
|
343
371
|
_setVersion(book, chapter, version) {
|
|
344
372
|
this.version = version ? version : "eng"
|
|
345
|
-
|
|
346
373
|
if (this.version !== "eng") {
|
|
347
374
|
this._searchVersificationDifferences(book, chapter, version)
|
|
348
375
|
}
|
|
@@ -352,31 +379,25 @@ class CodexParser {
|
|
|
352
379
|
this.passages.forEach((passage) => {
|
|
353
380
|
const hasVersification = this.versificationDifferences[passage.book]
|
|
354
381
|
passage.passages.forEach((subPassage) => {
|
|
355
|
-
// Apply general versification differences
|
|
356
382
|
if (hasVersification) {
|
|
357
383
|
const key = `${subPassage.chapter}:${subPassage.verse}`
|
|
358
384
|
if (this.versificationDifferences[passage.book][key]) {
|
|
359
385
|
subPassage.versification = this.versificationDifferences[passage.book][key]
|
|
360
386
|
}
|
|
361
387
|
}
|
|
362
|
-
|
|
363
|
-
// Handle specific version adjustments for "lxx" or "mt"
|
|
364
388
|
if (passage.version) {
|
|
365
389
|
const versionAbbreviation = passage.version.abbreviation
|
|
366
390
|
const versionType =
|
|
367
391
|
versionAbbreviation === "lxx" ? "lxx" : versionAbbreviation === "mt" ? "mt" : null
|
|
368
|
-
|
|
369
392
|
if (versionType) {
|
|
370
393
|
const versionReference = `${subPassage.chapter}:${subPassage.verse}`
|
|
371
|
-
|
|
372
|
-
// Look for matching versification based on the version type (lxx or mt)
|
|
373
394
|
for (const versification in this.versificationDifferences[passage.book]) {
|
|
374
395
|
if (
|
|
375
396
|
this.versificationDifferences[passage.book][versification][versionType] ===
|
|
376
397
|
versionReference
|
|
377
398
|
) {
|
|
378
399
|
subPassage.versification = this.versificationDifferences[passage.book][versification]
|
|
379
|
-
break
|
|
400
|
+
break
|
|
380
401
|
}
|
|
381
402
|
}
|
|
382
403
|
}
|
|
@@ -385,96 +406,65 @@ class CodexParser {
|
|
|
385
406
|
})
|
|
386
407
|
}
|
|
387
408
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
* @return {Array} An array of passage objects with individual verses.
|
|
393
|
-
*/
|
|
394
|
-
populate(parsedPassage) {
|
|
395
|
-
const passages = []
|
|
396
|
-
const { book, chapter, verses, type, to } = parsedPassage
|
|
397
|
-
const version = parsedPassage.version ? parsedPassage.version.abbreviation : "eng"
|
|
398
|
-
this._setVersion(book, chapter, version) // Set version data if needed
|
|
409
|
+
populate(passage) {
|
|
410
|
+
const { book, chapter, verses, type, to } = passage
|
|
411
|
+
const version = passage.version?.abbreviation || "eng"
|
|
412
|
+
this._setVersion(book, chapter, version)
|
|
399
413
|
|
|
400
|
-
|
|
414
|
+
if (type === this.SINGLE_CHAPTER) {
|
|
415
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
416
|
+
return this.expandVerses(book, chapter, [`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`])
|
|
417
|
+
}
|
|
401
418
|
|
|
402
|
-
if (type ===
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
414
|
-
})
|
|
415
|
-
}
|
|
416
|
-
} else if (type === "comma_separated_verses" || type === "chapter_verse_range") {
|
|
417
|
-
// Handle comma-separated verses or single-chapter verse ranges (e.g., "Isaiah 40:3-5,8-9" or "2 John 1:1-3")
|
|
418
|
-
verses.forEach((verse) => {
|
|
419
|
-
if (typeof verse === "string" && verse.includes("-")) {
|
|
420
|
-
const [start, end] = verse.split("-").map(Number)
|
|
421
|
-
for (let i = start; i <= end; i++) {
|
|
422
|
-
passages.push({ book, chapter: Number(chapter), verse: i })
|
|
423
|
-
}
|
|
424
|
-
} else {
|
|
425
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
426
|
-
}
|
|
427
|
-
})
|
|
428
|
-
} else if (type === "chapter_range") {
|
|
429
|
-
// Handle ranges of chapters (e.g., "Isaiah 3-5")
|
|
430
|
-
for (let currentChapter = chapter; currentChapter <= to.chapter; currentChapter++) {
|
|
431
|
-
if (this.chapterVerses[book] && this.chapterVerses[book][currentChapter]) {
|
|
432
|
-
this.chapterVerses[book][currentChapter].forEach((verse) => {
|
|
433
|
-
passages.push({ book, chapter: Number(currentChapter), verse: Number(verse) })
|
|
434
|
-
})
|
|
435
|
-
}
|
|
419
|
+
if (type === this.CHAPTER_VERSE || type === this.COMMA_SEPARATED || type === this.CHAPTER_VERSE_RANGE) {
|
|
420
|
+
return this.expandVerses(book, chapter, verses)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (type === this.CHAPTER_RANGE) {
|
|
424
|
+
const passages = []
|
|
425
|
+
for (let ch = chapter; ch <= to.chapter; ch++) {
|
|
426
|
+
const chapterVerses = this.getChapterVerses(book, ch)
|
|
427
|
+
passages.push(
|
|
428
|
+
...this.expandVerses(book, ch, [`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`])
|
|
429
|
+
)
|
|
436
430
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
431
|
+
return passages
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (type === this.MULTI_CHAPTER_RANGE) {
|
|
435
|
+
const passages = []
|
|
440
436
|
const startVerse = verses[0].includes("-") ? Number(verses[0].split("-")[0]) : Number(verses[0])
|
|
441
|
-
const endChapter = to.chapter
|
|
442
437
|
const endVerse = to.verses[0].includes("-") ? Number(to.verses[0].split("-")[1]) : Number(to.verses[0])
|
|
443
438
|
|
|
444
|
-
for (let
|
|
445
|
-
const chapterVerses = this.
|
|
446
|
-
|
|
439
|
+
for (let ch = chapter; ch <= to.chapter; ch++) {
|
|
440
|
+
const chapterVerses = this.getChapterVerses(book, ch)
|
|
441
|
+
const from = ch === chapter ? startVerse : chapterVerses[0]
|
|
442
|
+
const toVerse = ch === to.chapter ? endVerse : chapterVerses[chapterVerses.length - 1]
|
|
443
|
+
passages.push(...this.expandVerses(book, ch, [`${from}-${toVerse}`]))
|
|
444
|
+
}
|
|
445
|
+
return passages
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return []
|
|
449
|
+
}
|
|
447
450
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
+
expandVerses(book, chapter, verses) {
|
|
452
|
+
const passages = []
|
|
453
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
451
454
|
|
|
452
|
-
|
|
453
|
-
|
|
455
|
+
verses.forEach((verse) => {
|
|
456
|
+
if (typeof verse === "string" && verse.includes("-")) {
|
|
457
|
+
const [start, end] = verse.split("-").map(Number)
|
|
458
|
+
for (let i = start; i <= end && i <= chapterVerses[chapterVerses.length - 1]; i++) {
|
|
459
|
+
passages.push({ book, chapter, verse: i })
|
|
454
460
|
}
|
|
461
|
+
} else {
|
|
462
|
+
passages.push({ book, chapter, verse: Number(verse) })
|
|
455
463
|
}
|
|
456
|
-
}
|
|
457
|
-
// Handle single chapter:verse references (e.g., "2 John 1:1")
|
|
458
|
-
verses.forEach((verse) => {
|
|
459
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
460
|
-
})
|
|
461
|
-
} else if (type === "single_chapter_book_verse_range") {
|
|
462
|
-
// Handle ranges in single-chapter books (e.g., "Jude 5-7")
|
|
463
|
-
const [startVerse, endVerse] = verses[0].split("-").map(Number)
|
|
464
|
-
for (let i = startVerse; i <= endVerse; i++) {
|
|
465
|
-
passages.push({ book, chapter: 1, verse: i })
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
464
|
+
})
|
|
469
465
|
return passages
|
|
470
466
|
}
|
|
471
467
|
|
|
472
|
-
/**
|
|
473
|
-
* Converts a book name to its corresponding full name from the bible.
|
|
474
|
-
*
|
|
475
|
-
* @param {string} book - The abbreviated or partial name of the book.
|
|
476
|
-
* @return {string|undefined} The full name of the book if found, otherwise undefined.
|
|
477
|
-
*/
|
|
478
468
|
bookify(book) {
|
|
479
469
|
if (typeof book !== "string") {
|
|
480
470
|
book = book[0]
|
|
@@ -496,78 +486,105 @@ class CodexParser {
|
|
|
496
486
|
return bookified
|
|
497
487
|
}
|
|
498
488
|
|
|
499
|
-
/**
|
|
500
|
-
* Returns the passages stored in the object.
|
|
501
|
-
*
|
|
502
|
-
* @return {array} The passages stored in the object.
|
|
503
|
-
*/
|
|
504
489
|
getPassages() {
|
|
505
|
-
|
|
506
|
-
const passagesArray = [...this.passages] // Clone the array to avoid mutation
|
|
490
|
+
const passagesArray = [...this.passages]
|
|
507
491
|
|
|
508
|
-
// Add first() method directly to the array
|
|
509
492
|
passagesArray.first = function () {
|
|
510
493
|
return this.length > 0 ? this[0] : null
|
|
511
494
|
}
|
|
512
495
|
|
|
496
|
+
passagesArray.oldTestament = function () {
|
|
497
|
+
return this.filter((passage) => passage.testament === "old")
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
passagesArray.newTestament = function () {
|
|
501
|
+
return this.filter((passage) => passage.testament === "new")
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
passagesArray.combine = function (options = {}) {
|
|
505
|
+
const { book = true, chapter = true } = options
|
|
506
|
+
|
|
507
|
+
if (!book) {
|
|
508
|
+
return [...this]
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const parser = new CodexParser()
|
|
512
|
+
const groupedByBook = new Map()
|
|
513
|
+
|
|
514
|
+
this.forEach((passage) => {
|
|
515
|
+
const bookKey = passage.book
|
|
516
|
+
if (!groupedByBook.has(bookKey)) {
|
|
517
|
+
groupedByBook.set(bookKey, [])
|
|
518
|
+
}
|
|
519
|
+
groupedByBook.get(bookKey).push(passage)
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
const combinedPassages = []
|
|
523
|
+
|
|
524
|
+
for (const [book, bookPassages] of groupedByBook) {
|
|
525
|
+
if (chapter) {
|
|
526
|
+
const groupedByChapter = new Map()
|
|
527
|
+
bookPassages.forEach((passage) => {
|
|
528
|
+
const chapterKey = `${passage.book}-${passage.chapter}`
|
|
529
|
+
if (!groupedByChapter.has(chapterKey)) {
|
|
530
|
+
groupedByChapter.set(chapterKey, [])
|
|
531
|
+
}
|
|
532
|
+
groupedByChapter.get(chapterKey).push(passage)
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
for (const passages of groupedByChapter.values()) {
|
|
536
|
+
if (passages.length === 1) {
|
|
537
|
+
combinedPassages.push({ ...passages[0] })
|
|
538
|
+
} else {
|
|
539
|
+
const combined = parser.combine(passages)
|
|
540
|
+
combinedPassages.push(combined)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
const combined = parser.combine(bookPassages)
|
|
545
|
+
combinedPassages.push(combined)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return combinedPassages
|
|
550
|
+
}
|
|
551
|
+
|
|
513
552
|
return passagesArray
|
|
514
553
|
}
|
|
515
554
|
|
|
516
|
-
// New first() method that can be chained after getPassages()
|
|
517
555
|
first() {
|
|
518
556
|
return this.passages.length > 0 ? this.passages[0] : null
|
|
519
557
|
}
|
|
520
558
|
|
|
521
|
-
/**
|
|
522
|
-
* Converts a passage object into a scripturize object with human-readable name, chapter and verses and a hash.
|
|
523
|
-
*
|
|
524
|
-
* @param {object} passage - The passage object to scripturize.
|
|
525
|
-
* @return {object} The object with the human-readable name, chapter and verses and a hash.
|
|
526
|
-
*/
|
|
527
559
|
scripturize(passage) {
|
|
528
|
-
// Helper function to format a single chapter:verse combination
|
|
529
560
|
const formatChapterVerse = (chapter, verses) => {
|
|
530
561
|
if (!chapter || !verses || verses.length === 0) return ""
|
|
531
562
|
if (verses.length === 1) {
|
|
532
563
|
return `${chapter}:${verses[0]}`
|
|
533
564
|
}
|
|
534
|
-
|
|
535
|
-
// Check if verses are continuous (e.g., [1, 2, 3, 4, 5] -> "1-5")
|
|
536
565
|
const isRange = verses.every((v, i, arr) => i === 0 || v === arr[i - 1] + 1)
|
|
537
|
-
|
|
538
566
|
if (isRange) {
|
|
539
567
|
return `${chapter}:${verses[0]}-${verses[verses.length - 1]}`
|
|
540
568
|
}
|
|
541
|
-
|
|
542
|
-
// Comma-separated (e.g., [1, 3, 5] -> "1,3,5")
|
|
543
569
|
return `${chapter}:${verses.join(",")}`
|
|
544
570
|
}
|
|
545
571
|
|
|
546
|
-
// Start constructing the passage string
|
|
547
572
|
let combined = `${passage.book}`
|
|
548
|
-
|
|
549
573
|
if (passage.type === "multi_chapter_verse_range" && passage.to) {
|
|
550
|
-
// Multi-chapter verse range
|
|
551
|
-
|
|
552
574
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
553
575
|
passage.to.chapter,
|
|
554
576
|
passage.to.verses
|
|
555
577
|
)}`
|
|
556
578
|
} else if (passage.type === "chapter_verse_range") {
|
|
557
|
-
// Single-chapter verse range
|
|
558
579
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
559
580
|
} else if (passage.type === "comma_separated_verses") {
|
|
560
|
-
// Comma-separated verses
|
|
561
581
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
562
582
|
} else if (passage.type === "chapter_range" && passage.to) {
|
|
563
|
-
// Chapter range
|
|
564
583
|
combined += ` ${passage.chapter}-${passage.to.chapter}`
|
|
565
584
|
} else {
|
|
566
|
-
// Single chapter or single verse
|
|
567
585
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
568
586
|
}
|
|
569
587
|
|
|
570
|
-
// Generate the chapter:verse (cv) string
|
|
571
588
|
const cv = passage.to
|
|
572
589
|
? `${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
573
590
|
passage.to.chapter,
|
|
@@ -575,10 +592,8 @@ class CodexParser {
|
|
|
575
592
|
)}`
|
|
576
593
|
: formatChapterVerse(passage.chapter, passage.verses)
|
|
577
594
|
|
|
578
|
-
// Generate the hash
|
|
579
595
|
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, ".").replace(/-/g, ".")}`
|
|
580
596
|
|
|
581
|
-
// Return the final scripture object
|
|
582
597
|
return {
|
|
583
598
|
passage: combined,
|
|
584
599
|
cv: cv,
|
|
@@ -586,27 +601,16 @@ class CodexParser {
|
|
|
586
601
|
}
|
|
587
602
|
}
|
|
588
603
|
|
|
589
|
-
/**
|
|
590
|
-
* Combine multiple passages into one. The method checks for duplicates, merges overlapping or adjacent ranges,
|
|
591
|
-
* and builds the original and scripture properties.
|
|
592
|
-
* **This method will always combine based on English versification. LXX and MT versifications will be reflected in the combined passage.passages.versification.**
|
|
593
|
-
* This method will fail if the passages are not to the same book and chapter.
|
|
594
|
-
* TODO: Add support for MT and LXX
|
|
595
|
-
* @param {array} passages - An array of passage objects to combine.
|
|
596
|
-
* @return {object} The combined passage object.
|
|
597
|
-
*/
|
|
598
604
|
combine(passages) {
|
|
599
605
|
if (!passages || passages.length === 0) {
|
|
600
606
|
throw new Error("No passages provided to join.")
|
|
601
607
|
}
|
|
602
608
|
|
|
603
|
-
// Ensure all passages are from the same book
|
|
604
609
|
const uniqueBooks = [...new Set(passages.map((p) => p.book))]
|
|
605
610
|
if (uniqueBooks.length > 1) {
|
|
606
611
|
throw new Error("Passages must be from the same book to join.")
|
|
607
612
|
}
|
|
608
613
|
|
|
609
|
-
// Start with the base object
|
|
610
614
|
const combined = {
|
|
611
615
|
...passages[0],
|
|
612
616
|
verses: [],
|
|
@@ -614,36 +618,45 @@ class CodexParser {
|
|
|
614
618
|
to: null,
|
|
615
619
|
scripture: {},
|
|
616
620
|
type: null,
|
|
621
|
+
start: null,
|
|
622
|
+
end: null,
|
|
617
623
|
}
|
|
618
624
|
|
|
619
625
|
const chapterVerses = {}
|
|
620
626
|
let firstChapter = null
|
|
621
627
|
let lastChapter = null
|
|
628
|
+
let firstVerse = null
|
|
629
|
+
let lastVerse = null
|
|
622
630
|
|
|
623
|
-
// Collect all verses and passages, grouped by chapter
|
|
624
631
|
passages.forEach((passage) => {
|
|
625
632
|
passage.passages.forEach((p) => {
|
|
626
633
|
if (!chapterVerses[p.chapter]) {
|
|
627
634
|
chapterVerses[p.chapter] = new Set()
|
|
628
635
|
}
|
|
629
636
|
chapterVerses[p.chapter].add(p.verse)
|
|
630
|
-
combined.passages.push(p)
|
|
637
|
+
combined.passages.push(p)
|
|
638
|
+
|
|
639
|
+
if (firstChapter === null || p.chapter < firstChapter) {
|
|
640
|
+
firstChapter = p.chapter
|
|
641
|
+
firstVerse = p.verse
|
|
642
|
+
} else if (p.chapter === firstChapter && (firstVerse === null || p.verse < firstVerse)) {
|
|
643
|
+
firstVerse = p.verse
|
|
644
|
+
}
|
|
645
|
+
if (lastChapter === null || p.chapter > lastChapter) {
|
|
646
|
+
lastChapter = p.chapter
|
|
647
|
+
lastVerse = p.verse
|
|
648
|
+
} else if (p.chapter === lastChapter && (lastVerse === null || p.verse > lastVerse)) {
|
|
649
|
+
lastVerse = p.verse
|
|
650
|
+
}
|
|
631
651
|
})
|
|
632
652
|
|
|
633
|
-
// Track first and last chapters
|
|
634
653
|
const chapters = passage.passages.map((p) => p.chapter)
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
if (!lastChapter || Math.max(...chapters) > lastChapter) {
|
|
639
|
-
lastChapter = Math.max(...chapters)
|
|
640
|
-
}
|
|
654
|
+
firstChapter = firstChapter === null ? Math.min(...chapters) : Math.min(firstChapter, ...chapters)
|
|
655
|
+
lastChapter = lastChapter === null ? Math.max(...chapters) : Math.max(lastChapter, ...chapters)
|
|
641
656
|
})
|
|
642
657
|
|
|
643
|
-
// Ensure unique and sorted passages
|
|
644
658
|
combined.passages = Array.from(new Set(combined.passages.map(JSON.stringify))).map(JSON.parse)
|
|
645
659
|
|
|
646
|
-
// Process chapter and verse data
|
|
647
660
|
const chapterStrings = []
|
|
648
661
|
const sortedChapters = Object.keys(chapterVerses)
|
|
649
662
|
.map(Number)
|
|
@@ -654,13 +667,12 @@ class CodexParser {
|
|
|
654
667
|
const mergedVerses = this.mergeRanges(verses)
|
|
655
668
|
chapterStrings.push(`${chapter}:${mergedVerses.join(",")}`)
|
|
656
669
|
if (chapter === firstChapter) {
|
|
657
|
-
combined.verses = mergedVerses
|
|
670
|
+
combined.verses = mergedVerses
|
|
658
671
|
}
|
|
659
672
|
})
|
|
660
673
|
|
|
661
|
-
// Handle multi-chapter ranges
|
|
662
674
|
if (firstChapter !== lastChapter) {
|
|
663
|
-
combined.type =
|
|
675
|
+
combined.type = this.MULTI_CHAPTER_RANGE
|
|
664
676
|
combined.to = {
|
|
665
677
|
book: combined.book,
|
|
666
678
|
chapter: lastChapter,
|
|
@@ -670,25 +682,37 @@ class CodexParser {
|
|
|
670
682
|
","
|
|
671
683
|
)}; ${lastChapter}:${combined.to.verses.join(",")}`
|
|
672
684
|
} else {
|
|
673
|
-
|
|
674
|
-
if (combined.verses.length > 1) {
|
|
675
|
-
combined.type = "chapter_verse_range"
|
|
676
|
-
} else {
|
|
677
|
-
combined.type = "chapter_verse"
|
|
678
|
-
}
|
|
685
|
+
combined.type = combined.verses.length > 1 ? this.CHAPTER_VERSE_RANGE : this.CHAPTER_VERSE
|
|
679
686
|
combined.original = `${combined.book} ${firstChapter}:${combined.verses.join(",")}`
|
|
680
687
|
}
|
|
681
688
|
|
|
682
|
-
// Build the scripture property
|
|
683
689
|
const chapterString = chapterStrings.join(";")
|
|
684
690
|
combined.scripture = {
|
|
685
691
|
passage: `${combined.book} ${chapterString}`,
|
|
686
692
|
cv: chapterString,
|
|
687
|
-
hash: `${combined.book.toLowerCase()}_${chapterString.replace(/:/g, ".").replace(
|
|
693
|
+
hash: `${combined.book.toLowerCase()}_${chapterString.replace(/:/g, ".").replace(/[,;]/g, ".")}`,
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
combined.start = {
|
|
697
|
+
book: combined.book,
|
|
698
|
+
chapter: firstChapter,
|
|
699
|
+
verse: firstVerse || Math.min(...Array.from(chapterVerses[firstChapter])),
|
|
700
|
+
}
|
|
701
|
+
combined.end = {
|
|
702
|
+
book: combined.book,
|
|
703
|
+
chapter: lastChapter,
|
|
704
|
+
verse: lastVerse || Math.max(...Array.from(chapterVerses[lastChapter])),
|
|
688
705
|
}
|
|
706
|
+
|
|
707
|
+
// Reattach the reference method to the combined passage
|
|
708
|
+
combined.reference = function () {
|
|
709
|
+
return this.scripture.passage
|
|
710
|
+
}
|
|
711
|
+
|
|
689
712
|
if (combined.to === null) {
|
|
690
713
|
delete combined.to
|
|
691
714
|
}
|
|
715
|
+
|
|
692
716
|
return combined
|
|
693
717
|
}
|
|
694
718
|
|
|
@@ -702,7 +726,6 @@ class CodexParser {
|
|
|
702
726
|
if (sortedVerses[i] === end + 1) {
|
|
703
727
|
end = sortedVerses[i]
|
|
704
728
|
} else {
|
|
705
|
-
// Push range or single verse
|
|
706
729
|
if (start === end) {
|
|
707
730
|
merged.push(`${start}`)
|
|
708
731
|
} else {
|
|
@@ -713,7 +736,6 @@ class CodexParser {
|
|
|
713
736
|
}
|
|
714
737
|
}
|
|
715
738
|
|
|
716
|
-
// Push the final range or single verse
|
|
717
739
|
if (start === end) {
|
|
718
740
|
merged.push(`${start}`)
|
|
719
741
|
} else {
|
|
@@ -724,24 +746,17 @@ class CodexParser {
|
|
|
724
746
|
}
|
|
725
747
|
|
|
726
748
|
getToc(version = "ESV") {
|
|
727
|
-
// Initialize the table of contents (toc)
|
|
728
749
|
const toc = {}
|
|
729
|
-
|
|
730
|
-
// Add Old Testament books and their chapters/verses to toc
|
|
731
750
|
this.bible.old.forEach((book) => {
|
|
732
751
|
if (this.chapterVerses[book]) {
|
|
733
752
|
toc[book] = this.chapterVerses[book]
|
|
734
753
|
}
|
|
735
754
|
})
|
|
736
|
-
|
|
737
|
-
// Add New Testament books and their chapters/verses to toc
|
|
738
755
|
this.bible.new.forEach((book) => {
|
|
739
756
|
if (this.chapterVerses[book]) {
|
|
740
757
|
toc[book] = this.chapterVerses[book]
|
|
741
758
|
}
|
|
742
759
|
})
|
|
743
|
-
|
|
744
|
-
// Merge in single-chapter books if not already in toc
|
|
745
760
|
this.singleChapterBook.forEach((item) => {
|
|
746
761
|
Object.keys(item).forEach((book) => {
|
|
747
762
|
if (!toc[book]) {
|
|
@@ -749,8 +764,6 @@ class CodexParser {
|
|
|
749
764
|
}
|
|
750
765
|
})
|
|
751
766
|
})
|
|
752
|
-
|
|
753
|
-
// Sort the keys of toc by canonical order
|
|
754
767
|
const orderedToc = {}
|
|
755
768
|
const canonicalOrder = [...this.bible.old, ...this.bible.new]
|
|
756
769
|
canonicalOrder.forEach((book) => {
|
|
@@ -758,162 +771,79 @@ class CodexParser {
|
|
|
758
771
|
orderedToc[book] = toc[book]
|
|
759
772
|
}
|
|
760
773
|
})
|
|
761
|
-
|
|
762
774
|
return orderedToc
|
|
763
775
|
}
|
|
764
776
|
|
|
765
|
-
/**
|
|
766
|
-
* Validates a parsed passage to ensure the chapter and verses exist.
|
|
767
|
-
*
|
|
768
|
-
* @param {Object} passage - The parsed passage object to validate.
|
|
769
|
-
* @param {string} reference - The original reference string for error messaging.
|
|
770
|
-
* @return {boolean|Object} True if valid, or an error object if invalid.
|
|
771
|
-
*/
|
|
772
777
|
_isValid(passage, reference) {
|
|
773
|
-
const
|
|
778
|
+
const { book, chapter, verses, type } = passage
|
|
774
779
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (passage.type !== "single_chapter") {
|
|
778
|
-
return {
|
|
779
|
-
error: true,
|
|
780
|
-
code: 101,
|
|
781
|
-
message: {
|
|
782
|
-
chapter_exists: false,
|
|
783
|
-
content: "Possible invalid chapter: " + reference,
|
|
784
|
-
},
|
|
785
|
-
}
|
|
786
|
-
}
|
|
780
|
+
if (!verses.length && type !== this.SINGLE_CHAPTER) {
|
|
781
|
+
return this.validationError(101, `Possible invalid chapter: ${reference}`)
|
|
787
782
|
}
|
|
788
783
|
|
|
789
|
-
|
|
790
|
-
if (
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
if (passage.chapter !== 1) {
|
|
794
|
-
return {
|
|
795
|
-
error: true,
|
|
796
|
-
code: 103,
|
|
797
|
-
message: {
|
|
798
|
-
chapter_exists: false,
|
|
799
|
-
content: `Chapter ${passage.chapter} does not exist in ${passage.book}`,
|
|
800
|
-
},
|
|
801
|
-
}
|
|
802
|
-
}
|
|
784
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
785
|
+
if (!chapterVerses.length) {
|
|
786
|
+
return this.validationError(102, `Chapter ${chapter} does not exist in ${book}`)
|
|
787
|
+
}
|
|
803
788
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
content: `Verse range ${start}-${end} exceeds available verses (1-${verseCount}) in ${passage.book} 1`,
|
|
816
|
-
},
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
return true // If no specific verses or range matches, it’s valid
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// For specific verses in single-chapter books (e.g., "2 John 1:1-3")
|
|
824
|
-
for (let i = 0; i < passage.verses.length; i++) {
|
|
825
|
-
const verseRange = String(passage.verses[i])
|
|
826
|
-
let versesToCheck = verseRange.includes("-") ? verseRange.split("-").map(Number) : [Number(verseRange)]
|
|
827
|
-
|
|
828
|
-
if (versesToCheck.length === 2) {
|
|
829
|
-
const [start, end] = versesToCheck
|
|
830
|
-
versesToCheck = Array.from({ length: end - start + 1 }, (_, idx) => start + idx)
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
for (const verse of versesToCheck) {
|
|
834
|
-
if (verse < 1 || verse > verseCount) {
|
|
835
|
-
return {
|
|
836
|
-
error: true,
|
|
837
|
-
code: 104,
|
|
838
|
-
message: {
|
|
839
|
-
verse_exists: false,
|
|
840
|
-
content: `Verse number ${verse} does not exist in ${passage.book} 1`,
|
|
841
|
-
},
|
|
842
|
-
}
|
|
843
|
-
}
|
|
789
|
+
if (type === this.SINGLE_CHAPTER) {
|
|
790
|
+
const [range] = verses
|
|
791
|
+
if (range) {
|
|
792
|
+
const [start, end] = range.split("-").map(Number)
|
|
793
|
+
if (start < 1 || end > chapterVerses[chapterVerses.length - 1]) {
|
|
794
|
+
return this.validationError(
|
|
795
|
+
104,
|
|
796
|
+
`Verse range ${start}-${end} exceeds available verses (1-${
|
|
797
|
+
chapterVerses[chapterVerses.length - 1]
|
|
798
|
+
}) in ${book} ${chapter}`
|
|
799
|
+
)
|
|
844
800
|
}
|
|
845
801
|
}
|
|
846
802
|
return true
|
|
847
803
|
}
|
|
848
804
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
return {
|
|
852
|
-
error: true,
|
|
853
|
-
code: 102,
|
|
854
|
-
message: {
|
|
855
|
-
chapter_exists: false,
|
|
856
|
-
content: `Chapter ${passage.chapter} does not exist in ${passage.book}`,
|
|
857
|
-
},
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
if (passage.type === "single_chapter") {
|
|
862
|
-
return true // For multi-chapter books, whole chapter is valid if it exists
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
for (let i = 0; i < passage.verses.length; i++) {
|
|
866
|
-
const passageVerses = String(passage.verses[i])
|
|
867
|
-
let verses = passageVerses.includes("-") ? passageVerses.split("-").map(Number) : [Number(passageVerses)]
|
|
868
|
-
|
|
869
|
-
if (verses.length === 2) {
|
|
870
|
-
// Expand the range if there are two numbers
|
|
871
|
-
verses = Array.from({ length: verses[1] - verses[0] + 1 }, (_, index) => verses[0] + index)
|
|
872
|
-
}
|
|
805
|
+
return this.validateVerses(book, chapter, verses, reference)
|
|
806
|
+
}
|
|
873
807
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
}
|
|
808
|
+
validateVerses(book, chapter, verses, reference) {
|
|
809
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
810
|
+
for (const verse of verses) {
|
|
811
|
+
const verseRange = String(verse)
|
|
812
|
+
const verseNumbers = verseRange.includes("-")
|
|
813
|
+
? Array.from(
|
|
814
|
+
{ length: Number(verseRange.split("-")[1]) - Number(verseRange.split("-")[0]) + 1 },
|
|
815
|
+
(_, i) => Number(verseRange.split("-")[0]) + i
|
|
816
|
+
)
|
|
817
|
+
: [Number(verseRange)]
|
|
818
|
+
|
|
819
|
+
for (const v of verseNumbers) {
|
|
820
|
+
if (!chapterVerses.includes(v)) {
|
|
821
|
+
return this.validationError(104, `Verse number ${v} does not exist in ${book} ${chapter}`)
|
|
889
822
|
}
|
|
890
823
|
}
|
|
891
824
|
}
|
|
892
|
-
|
|
893
825
|
return true
|
|
894
826
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
if (version.toLowerCase() === "lxx" && testament.toLowerCase() === "old") {
|
|
903
|
-
return {
|
|
904
|
-
name: "Septuagint",
|
|
905
|
-
value: "LXX",
|
|
906
|
-
abbreviation: "lxx",
|
|
907
|
-
}
|
|
827
|
+
|
|
828
|
+
validationError(code, message) {
|
|
829
|
+
return {
|
|
830
|
+
error: true,
|
|
831
|
+
code,
|
|
832
|
+
message: { verse_exists: code === 104, chapter_exists: code !== 104, content: message },
|
|
908
833
|
}
|
|
834
|
+
}
|
|
909
835
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
836
|
+
_handleVersion(version, testament) {
|
|
837
|
+
const effectiveVersion = this.version || version || "eng"
|
|
838
|
+
const lowerVersion = effectiveVersion.toLowerCase()
|
|
839
|
+
|
|
840
|
+
if (lowerVersion === "lxx" && testament === "old") {
|
|
841
|
+
return { name: "Septuagint", value: "LXX", abbreviation: "lxx" }
|
|
842
|
+
}
|
|
843
|
+
if (lowerVersion === "mt" && testament === "old") {
|
|
844
|
+
return { name: "Masoretic Text", value: "MT", abbreviation: "mt" }
|
|
916
845
|
}
|
|
846
|
+
return { name: "English", value: "ENG", abbreviation: "eng" }
|
|
917
847
|
}
|
|
918
848
|
}
|
|
919
849
|
|