codexparser 0.1.40 → 0.1.42
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 +242 -241
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexparser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
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
|
@@ -65,115 +65,86 @@ class CodexParser {
|
|
|
65
65
|
// Initialize the `found` array to store the results
|
|
66
66
|
this.found = []
|
|
67
67
|
|
|
68
|
+
// Preprocess input text: normalize separators while preserving abbreviations
|
|
69
|
+
let normalizedText = text
|
|
70
|
+
.replace(/\.(?=\d)/g, ":") // Convert periods before numbers into colons (e.g., 12.15 -> 12:15)
|
|
71
|
+
.replace(/(\b[A-Za-z]+)\.(?=\s|$)/g, "$1") // Remove periods after abbreviations (e.g., Jd. -> Jd)
|
|
72
|
+
.replace(/\s+/g, " ") // Normalize multiple spaces to a single space
|
|
73
|
+
|
|
68
74
|
// Convert Bible book names, abbreviations, and input text to lowercase for case-insensitive matching
|
|
69
75
|
const lowercaseBibleFullNames = fullNames.map((book) => book.toLowerCase())
|
|
70
76
|
const lowercaseBibleAbbreviations = abbreviations.map((abbr) => abbr.toLowerCase())
|
|
71
|
-
const lowerCaseText =
|
|
77
|
+
const lowerCaseText = normalizedText.toLowerCase()
|
|
72
78
|
|
|
73
|
-
let i = 0
|
|
79
|
+
let i = 0
|
|
74
80
|
|
|
75
|
-
/**
|
|
76
|
-
* Helper function to check if a character is part of a chapter or verse reference.
|
|
77
|
-
* Non-word characters (anything not A-Z or a-z) are considered valid.
|
|
78
|
-
*/
|
|
79
81
|
const isValidChapterVerseChar = (char) => /[^A-Za-z]/.test(char)
|
|
80
82
|
|
|
81
|
-
/**
|
|
82
|
-
* Helper function to determine if the text starting at a given index contains
|
|
83
|
-
* the name of a new Bible book.
|
|
84
|
-
*/
|
|
85
83
|
const isNextBibleBook = (startIndex) => {
|
|
86
84
|
const textAfterCurrentPosition = lowerCaseText.substring(startIndex).trim()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Check for Bible book abbreviations
|
|
94
|
-
for (const abbr of lowercaseBibleAbbreviations) {
|
|
95
|
-
if (textAfterCurrentPosition.startsWith(abbr)) return true
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return false // No match found
|
|
85
|
+
return (
|
|
86
|
+
lowercaseBibleFullNames.some((book) => textAfterCurrentPosition.startsWith(book)) ||
|
|
87
|
+
lowercaseBibleAbbreviations.some((abbr) => textAfterCurrentPosition.startsWith(abbr))
|
|
88
|
+
)
|
|
99
89
|
}
|
|
100
90
|
|
|
101
|
-
/**
|
|
102
|
-
* Helper function to detect suffixes like "LXX" or "MT" in the text after a given index.
|
|
103
|
-
* These suffixes are case-insensitive and indicate the version of the Bible reference.
|
|
104
|
-
*/
|
|
105
91
|
const detectSuffix = (startIndex) => {
|
|
106
|
-
const suffixMatch =
|
|
92
|
+
const suffixMatch = normalizedText.substring(startIndex).match(/\b(LXX|MT)\b/i)
|
|
107
93
|
return suffixMatch ? suffixMatch[0].toUpperCase() : null
|
|
108
94
|
}
|
|
109
95
|
|
|
110
|
-
// Iterate through the input text to detect and process Bible references
|
|
111
96
|
while (i < lowerCaseText.length) {
|
|
112
|
-
let foundBook = null
|
|
113
|
-
let foundIndex = -1
|
|
114
|
-
let matchedLength = 0
|
|
97
|
+
let foundBook = null
|
|
98
|
+
let foundIndex = -1
|
|
99
|
+
let matchedLength = 0
|
|
115
100
|
|
|
116
|
-
// Search for full Bible book names in the text
|
|
117
101
|
for (let j = 0; j < lowercaseBibleFullNames.length; j++) {
|
|
118
102
|
const book = lowercaseBibleFullNames[j]
|
|
119
103
|
if (lowerCaseText.startsWith(book, i) && book.length > matchedLength) {
|
|
120
|
-
foundBook = fullNames[j]
|
|
104
|
+
foundBook = fullNames[j]
|
|
121
105
|
foundIndex = i
|
|
122
|
-
matchedLength = book.length
|
|
106
|
+
matchedLength = book.length
|
|
123
107
|
}
|
|
124
108
|
}
|
|
125
109
|
|
|
126
|
-
// If no full book name is found, search for abbreviations
|
|
127
110
|
if (!foundBook) {
|
|
128
111
|
for (let k = 0; k < lowercaseBibleAbbreviations.length; k++) {
|
|
129
112
|
const abbreviation = lowercaseBibleAbbreviations[k]
|
|
130
|
-
if (lowerCaseText.startsWith(abbreviation, i)) {
|
|
131
|
-
foundBook = abbreviations[k]
|
|
113
|
+
if (lowerCaseText.startsWith(abbreviation, i) && abbreviation.length > matchedLength) {
|
|
114
|
+
foundBook = this.abbreviations[abbreviations[k]]
|
|
132
115
|
foundIndex = i
|
|
133
116
|
matchedLength = abbreviation.length
|
|
134
117
|
}
|
|
135
118
|
}
|
|
136
119
|
}
|
|
137
120
|
|
|
138
|
-
// If a Bible book is found
|
|
139
121
|
if (foundBook) {
|
|
140
|
-
i += matchedLength
|
|
141
|
-
let chapterVerse = ""
|
|
142
|
-
const references = []
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (text[i] === ";") {
|
|
150
|
-
const formattedReference = chapterVerse
|
|
151
|
-
.trim()
|
|
152
|
-
.replace(/\./g, ":")
|
|
153
|
-
.replace(/[^a-zA-Z0-9]+$/, "")
|
|
122
|
+
i += matchedLength
|
|
123
|
+
let chapterVerse = ""
|
|
124
|
+
const references = []
|
|
125
|
+
|
|
126
|
+
while (i < normalizedText.length && isValidChapterVerseChar(normalizedText[i])) {
|
|
127
|
+
if (isNextBibleBook(i)) break
|
|
128
|
+
|
|
129
|
+
if (normalizedText[i] === ";") {
|
|
130
|
+
const formattedReference = chapterVerse.trim().replace(/[^a-zA-Z0-9]+$/, "")
|
|
154
131
|
if (formattedReference) references.push(formattedReference)
|
|
155
|
-
chapterVerse = ""
|
|
132
|
+
chapterVerse = ""
|
|
156
133
|
i++
|
|
157
134
|
continue
|
|
158
135
|
}
|
|
159
136
|
|
|
160
|
-
chapterVerse +=
|
|
137
|
+
chapterVerse += normalizedText[i]
|
|
161
138
|
i++
|
|
162
139
|
}
|
|
163
140
|
|
|
164
|
-
// Process the last detected chapter/verse reference
|
|
165
141
|
if (chapterVerse.trim().length > 0) {
|
|
166
|
-
const formattedReference = chapterVerse
|
|
167
|
-
.trim()
|
|
168
|
-
.replace(/\./g, ":")
|
|
169
|
-
.replace(/[^a-zA-Z0-9]+$/, "")
|
|
142
|
+
const formattedReference = chapterVerse.trim().replace(/[^a-zA-Z0-9]+$/, "")
|
|
170
143
|
if (formattedReference) references.push(formattedReference)
|
|
171
144
|
}
|
|
172
145
|
|
|
173
|
-
// Detect any suffix (e.g., "LXX" or "MT") after the chapter/verse reference
|
|
174
146
|
const suffix = detectSuffix(i)
|
|
175
147
|
|
|
176
|
-
// Add each reference as a separate object to the `found` array with type recognition
|
|
177
148
|
references.forEach((ref) => {
|
|
178
149
|
let type
|
|
179
150
|
|
|
@@ -182,21 +153,19 @@ class CodexParser {
|
|
|
182
153
|
const [start, end] = ref.split("-")
|
|
183
154
|
const startParts = start.split(":")
|
|
184
155
|
const endParts = end.split(":")
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
type = "chapter_verse_range" // Example: "8:23-25"
|
|
190
|
-
}
|
|
156
|
+
type =
|
|
157
|
+
startParts[0].trim() !== endParts[0].trim()
|
|
158
|
+
? "multi_chapter_verse_range"
|
|
159
|
+
: "chapter_verse_range"
|
|
191
160
|
} else if (ref.includes(",")) {
|
|
192
|
-
type = "comma_separated_verses"
|
|
161
|
+
type = "comma_separated_verses"
|
|
193
162
|
} else {
|
|
194
|
-
type = "chapter_verse"
|
|
163
|
+
type = "chapter_verse"
|
|
195
164
|
}
|
|
196
165
|
} else if (ref.includes("-")) {
|
|
197
|
-
type = "chapter_range"
|
|
166
|
+
type = "chapter_range"
|
|
198
167
|
} else {
|
|
199
|
-
type = "single_chapter"
|
|
168
|
+
type = "single_chapter"
|
|
200
169
|
}
|
|
201
170
|
|
|
202
171
|
this.found.push({
|
|
@@ -208,11 +177,11 @@ class CodexParser {
|
|
|
208
177
|
})
|
|
209
178
|
})
|
|
210
179
|
} else {
|
|
211
|
-
i++
|
|
180
|
+
i++
|
|
212
181
|
}
|
|
213
182
|
}
|
|
214
183
|
|
|
215
|
-
return this
|
|
184
|
+
return this
|
|
216
185
|
}
|
|
217
186
|
|
|
218
187
|
bibleVersion(version) {
|
|
@@ -232,93 +201,143 @@ class CodexParser {
|
|
|
232
201
|
* @returns {object} An object with the parsed passage.
|
|
233
202
|
*/
|
|
234
203
|
parse(reference) {
|
|
235
|
-
this.scan(reference)
|
|
204
|
+
this.scan(reference) // Call scan to populate this.found
|
|
236
205
|
|
|
237
206
|
this.passages = this.found.map((passage) => {
|
|
238
207
|
const book = this.bookify(passage.book)
|
|
239
|
-
const testament = this.bible.old.
|
|
240
|
-
|
|
208
|
+
const testament = this.bible.old.find((bible) => bible === book) ? "old" : "new"
|
|
209
|
+
// Initialize the parsed passage object
|
|
241
210
|
const parsedPassage = {
|
|
242
|
-
original:
|
|
243
|
-
book,
|
|
211
|
+
original: passage.book + " " + passage.reference,
|
|
212
|
+
book: book,
|
|
244
213
|
chapter: null,
|
|
245
|
-
verses: [],
|
|
246
|
-
type: passage.type,
|
|
247
|
-
testament,
|
|
214
|
+
verses: [], // Verse stored as an array
|
|
215
|
+
type: passage.type, // Set type based on reference
|
|
216
|
+
testament: testament,
|
|
248
217
|
index: passage.index,
|
|
249
218
|
version: this._handleVersion(passage.version, testament),
|
|
250
219
|
}
|
|
251
|
-
|
|
252
|
-
|
|
220
|
+
|
|
221
|
+
// Split reference by commas to handle multiple ranges or verses (e.g., "Ge 27:27-29,39-41")
|
|
222
|
+
let parts = passage.reference.split(",")
|
|
223
|
+
|
|
224
|
+
// Check for single chapter books
|
|
225
|
+
const singleChapterBook = this.singleChapterBook.find((bible) => bible[book])
|
|
253
226
|
|
|
254
227
|
parts.forEach((part) => {
|
|
255
|
-
part = part.trim()
|
|
228
|
+
part = part.trim() // Clean up spaces
|
|
229
|
+
// Detect whether it uses ":" or "." for chapter:verse separation
|
|
256
230
|
const separator = part.includes(":") ? ":" : "."
|
|
257
231
|
|
|
258
232
|
if (part.includes("-")) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
233
|
+
// Handle ranges (e.g., "27:27-29" or "39-41")
|
|
234
|
+
|
|
235
|
+
let [start, end] = part.split("-")
|
|
236
|
+
// Handle the starting part
|
|
237
|
+
let [startChapter, startVerse] = start.includes(separator)
|
|
238
|
+
? start.split(separator)
|
|
239
|
+
: [parsedPassage.chapter, start] // Default to same chapter if no chapter is provided
|
|
240
|
+
|
|
241
|
+
parsedPassage.chapter = Number(startChapter) // Set the chapter
|
|
242
|
+
// Checks to see if we are in a multi chapter verse range, if so, include only relevant verses from the this.chapterVerse to
|
|
243
|
+
// the end of the chapter.
|
|
244
|
+
if (start.includes(separator) && end.includes(separator)) {
|
|
245
|
+
parsedPassage.verses = this.chapterVerses[book][startChapter].slice(
|
|
246
|
+
this.chapterVerses[book][startChapter].indexOf(Number(startVerse))
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
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
|
+
if (Number(endChapter) !== Number(startChapter)) {
|
|
254
|
+
// Cross-chapter range, set 'to' property
|
|
255
|
+
parsedPassage.to = {
|
|
256
|
+
book: book,
|
|
257
|
+
chapter: Number(endChapter), // End chapter
|
|
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
|
+
)
|
|
264
|
+
}
|
|
265
|
+
parsedPassage.type =
|
|
266
|
+
endChapter !== startChapter ? "multi_chapter_verse_range" : "chapter_verse_range" // Set type to chapter range
|
|
267
|
+
} else {
|
|
268
|
+
// Same-chapter range, just add to the verse array
|
|
269
|
+
parsedPassage.verses.push(`${startVerse}-${endVerse}`)
|
|
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]
|
|
272
278
|
parsedPassage.to = {
|
|
273
|
-
book,
|
|
274
|
-
chapter:
|
|
275
|
-
verses: [
|
|
279
|
+
book: book,
|
|
280
|
+
chapter: Number(end),
|
|
281
|
+
verses: this.chapterVerses[book][end],
|
|
276
282
|
}
|
|
277
|
-
parsedPassage.verses.push(startVerse)
|
|
278
283
|
} else {
|
|
279
|
-
|
|
284
|
+
//
|
|
285
|
+
parsedPassage.verses.push(`${startVerse}-${end}`)
|
|
280
286
|
}
|
|
281
287
|
} else {
|
|
282
|
-
|
|
283
|
-
parsedPassage.
|
|
284
|
-
parsedPassage.to = {
|
|
285
|
-
book,
|
|
286
|
-
chapter: Number(end),
|
|
287
|
-
verses: [],
|
|
288
|
-
}
|
|
288
|
+
parsedPassage.chapter = 1
|
|
289
|
+
parsedPassage.verses.push(`${startVerse}-${end}`)
|
|
289
290
|
}
|
|
290
|
-
} else {
|
|
291
|
-
part = part.replace(/\d+:/gim, "")
|
|
292
|
-
const [singleChapterStartVerse, singleChapterEndVerse] = part.split("-")
|
|
293
|
-
parsedPassage.chapter = 1
|
|
294
|
-
parsedPassage.verses = [`${singleChapterStartVerse}-${singleChapterEndVerse}`]
|
|
295
|
-
parsedPassage.type = "single_chapter_book_verse_range"
|
|
296
291
|
}
|
|
297
|
-
} else if (part.includes(separator)) {
|
|
298
|
-
const [chapterPart, versePart] = part.split(separator).map(Number)
|
|
299
|
-
parsedPassage.chapter = chapterPart
|
|
300
|
-
if (versePart) parsedPassage.verses.push(versePart)
|
|
301
292
|
} else {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
293
|
+
// Handle individual chapter:verse references (e.g., "27:27")
|
|
294
|
+
|
|
295
|
+
let [chapterPart, versePart] = part.includes(separator)
|
|
296
|
+
? part.split(separator)
|
|
297
|
+
: [parsedPassage.chapter, part]
|
|
298
|
+
if (singleChapterBook) {
|
|
299
|
+
if (!chapterPart) {
|
|
300
|
+
parsedPassage.chapter = 1
|
|
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
|
|
306
305
|
}
|
|
307
|
-
parsedPassage.verses.push(Number(part))
|
|
308
306
|
} else {
|
|
309
|
-
|
|
310
|
-
|
|
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
|
+
if (chapterPart) {
|
|
312
|
+
parsedPassage.chapter = Number(chapterPart)
|
|
313
|
+
parsedPassage.verses.push(versePart) // Add single verse to array
|
|
314
|
+
} else {
|
|
315
|
+
parsedPassage.chapter = Number(versePart)
|
|
316
|
+
if (!this.chapterVerses[book][parsedPassage.chapter]) {
|
|
317
|
+
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
318
|
+
} else {
|
|
319
|
+
parsedPassage.verses = [
|
|
320
|
+
this.chapterVerses[book][parsedPassage.chapter][0] +
|
|
321
|
+
"-" +
|
|
322
|
+
this.chapterVerses[book][parsedPassage.chapter][
|
|
323
|
+
this.chapterVerses[book][parsedPassage.chapter].length - 1
|
|
324
|
+
],
|
|
325
|
+
]
|
|
326
|
+
parsedPassage.type = "single_chapter"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
311
329
|
}
|
|
312
330
|
}
|
|
331
|
+
parsedPassage.passages = this.populate(parsedPassage)
|
|
332
|
+
parsedPassage.scripture = this.scripturize(parsedPassage)
|
|
313
333
|
})
|
|
314
334
|
|
|
315
|
-
parsedPassage.passages = this.populate(parsedPassage)
|
|
316
|
-
parsedPassage.scripture = this.scripturize(parsedPassage)
|
|
317
335
|
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
336
|
+
|
|
318
337
|
return parsedPassage
|
|
319
338
|
})
|
|
320
339
|
this.versification()
|
|
321
|
-
return this
|
|
340
|
+
return this // Return this instance
|
|
322
341
|
}
|
|
323
342
|
/**
|
|
324
343
|
* Generates an array of numbers representing a range from start to end, inclusive.
|
|
@@ -333,6 +352,7 @@ class CodexParser {
|
|
|
333
352
|
|
|
334
353
|
_searchVersificationDifferences(passage) {
|
|
335
354
|
const { book, chapter, version } = passage
|
|
355
|
+
if (!this.chapterVerses[book][chapter]) return
|
|
336
356
|
|
|
337
357
|
// Loop through each key-value pair in the dictionary
|
|
338
358
|
for (const [key, value] of Object.entries(this.versificationDifferences[book])) {
|
|
@@ -361,7 +381,6 @@ class CodexParser {
|
|
|
361
381
|
versification() {
|
|
362
382
|
this.passages.forEach((passage) => {
|
|
363
383
|
const hasVersification = this.versificationDifferences[passage.book]
|
|
364
|
-
|
|
365
384
|
passage.passages.forEach((subPassage) => {
|
|
366
385
|
// Apply general versification differences
|
|
367
386
|
if (hasVersification) {
|
|
@@ -412,79 +431,57 @@ class CodexParser {
|
|
|
412
431
|
*/
|
|
413
432
|
populate(parsedPassage) {
|
|
414
433
|
const passages = []
|
|
415
|
-
const { book, chapter, verses, type } = parsedPassage
|
|
416
|
-
|
|
434
|
+
const { book, chapter, verses, type, to } = parsedPassage
|
|
435
|
+
|
|
436
|
+
this._setVersion(parsedPassage) // Set version data if needed
|
|
437
|
+
|
|
417
438
|
if (type === "single_chapter") {
|
|
418
|
-
// Handle
|
|
439
|
+
// Handle entire chapter references
|
|
419
440
|
if (this.chapterVerses[book] && this.chapterVerses[book][chapter]) {
|
|
420
441
|
this.chapterVerses[book][chapter].forEach((verse) => {
|
|
421
442
|
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
422
443
|
})
|
|
423
444
|
}
|
|
424
445
|
} else if (type === "comma_separated_verses") {
|
|
425
|
-
// Handle
|
|
426
|
-
if (
|
|
446
|
+
// Handle explicitly mentioned verses (e.g., 3:1,3,6)
|
|
447
|
+
if (this.chapterVerses[book] && this.chapterVerses[book][chapter]) {
|
|
427
448
|
verses.forEach((verse) => {
|
|
428
449
|
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
429
450
|
})
|
|
430
451
|
}
|
|
431
452
|
} else if (type === "chapter_range") {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
passages.push({
|
|
438
|
-
book,
|
|
439
|
-
chapter: i,
|
|
440
|
-
verse: j,
|
|
453
|
+
// Handle ranges of chapters (e.g., 3-5)
|
|
454
|
+
for (let currentChapter = chapter; currentChapter <= to.chapter; currentChapter++) {
|
|
455
|
+
if (this.chapterVerses[book] && this.chapterVerses[book][currentChapter]) {
|
|
456
|
+
this.chapterVerses[book][currentChapter].forEach((verse) => {
|
|
457
|
+
passages.push({ book, chapter: Number(currentChapter), verse: Number(verse) })
|
|
441
458
|
})
|
|
442
459
|
}
|
|
443
460
|
}
|
|
444
461
|
} else if (type === "multi_chapter_verse_range") {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
// Loop through the range of chapters
|
|
464
|
-
for (let chapter = startChapter; chapter <= endChapter; chapter++) {
|
|
465
|
-
// Determine the starting verse for the current chapter
|
|
466
|
-
const chapterStartVerse = chapter === startChapter ? startVerse : 1
|
|
467
|
-
// Determine the ending verse for the current chapter
|
|
468
|
-
const chapterEndVerse = chapter === endChapter ? endVerse : this.chapterVerses[book][chapter].length
|
|
469
|
-
|
|
470
|
-
// Get the array of verses for the current chapter
|
|
471
|
-
const verses = this.chapterVerses[book][chapter].slice(chapterStartVerse - 1, chapterEndVerse)
|
|
472
|
-
|
|
473
|
-
// Loop through the verses in the current chapter
|
|
474
|
-
for (let j = 0; j < verses.length; j++) {
|
|
475
|
-
const currentVerse = chapterStartVerse + j
|
|
476
|
-
|
|
477
|
-
// Add the verse to the passages array
|
|
478
|
-
passages.push({
|
|
479
|
-
book,
|
|
480
|
-
chapter,
|
|
481
|
-
verse: currentVerse,
|
|
482
|
-
})
|
|
462
|
+
// Handle multi-chapter verse ranges (e.g., 3:1-5:6)
|
|
463
|
+
|
|
464
|
+
const startChapter = chapter
|
|
465
|
+
const startVerse = verses[0]
|
|
466
|
+
const endChapter = to.chapter
|
|
467
|
+
const endVerse = to.verses[to.verses.length - 1]
|
|
468
|
+
|
|
469
|
+
for (let currentChapter = startChapter; currentChapter <= endChapter; currentChapter++) {
|
|
470
|
+
const chapterVerses = this.chapterVerses[book][currentChapter]
|
|
471
|
+
if (!chapterVerses) continue
|
|
472
|
+
|
|
473
|
+
// Determine start and end verses for each chapter
|
|
474
|
+
const chapterStartVerse = currentChapter === startChapter ? startVerse : 1
|
|
475
|
+
const chapterEndVerse =
|
|
476
|
+
currentChapter === endChapter ? endVerse : chapterVerses[chapterVerses.length - 1]
|
|
477
|
+
|
|
478
|
+
for (let verse = chapterStartVerse; verse <= chapterEndVerse; verse++) {
|
|
479
|
+
passages.push({ book, chapter: currentChapter, verse })
|
|
483
480
|
}
|
|
484
481
|
}
|
|
485
482
|
} else if (type === "chapter_verse" || type === "chapter_verse_range") {
|
|
486
|
-
// Handle chapter:verse or chapter:verse-
|
|
487
|
-
if (
|
|
483
|
+
// Handle single chapter:verse or chapter:verse ranges (e.g., 3:1 or 3:1-5)
|
|
484
|
+
if (this.chapterVerses[book] && this.chapterVerses[book][chapter]) {
|
|
488
485
|
verses.forEach((verse) => {
|
|
489
486
|
if (typeof verse === "string" && verse.includes("-")) {
|
|
490
487
|
const [start, end] = verse.split("-").map(Number)
|
|
@@ -496,6 +493,12 @@ class CodexParser {
|
|
|
496
493
|
}
|
|
497
494
|
})
|
|
498
495
|
}
|
|
496
|
+
} else if (type === "single_chapter_book_verse_range") {
|
|
497
|
+
// Handle ranges in single-chapter books (e.g., Jude 5-7)
|
|
498
|
+
const [startVerse, endVerse] = verses[0].split("-").map(Number)
|
|
499
|
+
for (let i = startVerse; i <= endVerse; i++) {
|
|
500
|
+
passages.push({ book, chapter: 1, verse: i })
|
|
501
|
+
}
|
|
499
502
|
}
|
|
500
503
|
|
|
501
504
|
return passages
|
|
@@ -557,64 +560,60 @@ class CodexParser {
|
|
|
557
560
|
* @return {object} The object with the human-readable name, chapter and verses and a hash.
|
|
558
561
|
*/
|
|
559
562
|
scripturize(passage) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (to && to.chapter && to.chapter !== chapter) {
|
|
567
|
-
// Handle multi-chapter range
|
|
568
|
-
const startChapter = chapter
|
|
569
|
-
const startVerses = verses.filter((v) => v.chapter === startChapter).map((v) => v.verse)
|
|
570
|
-
|
|
571
|
-
const endChapter = to.chapter
|
|
572
|
-
const endVerses = verses.filter((v) => v.chapter === endChapter).map((v) => v.verse)
|
|
573
|
-
|
|
574
|
-
const startFormatted =
|
|
575
|
-
startVerses.length > 1 ? `${startVerses[0]}-${startVerses[startVerses.length - 1]}` : startVerses[0]
|
|
576
|
-
|
|
577
|
-
const endFormatted =
|
|
578
|
-
endVerses.length > 1 ? `${endVerses[0]}-${endVerses[endVerses.length - 1]}` : endVerses[0]
|
|
579
|
-
|
|
580
|
-
formattedVerses = `${startChapter}:${startFormatted}-${endChapter}:${endFormatted}`
|
|
581
|
-
} else {
|
|
582
|
-
// Handle single-chapter range
|
|
583
|
-
const startVerses = verses.map((v) => v.verse)
|
|
584
|
-
|
|
585
|
-
if (startVerses.length === 1) {
|
|
586
|
-
formattedVerses = startVerses[0].toString()
|
|
587
|
-
} else {
|
|
588
|
-
// Group consecutive verses into ranges
|
|
589
|
-
let ranges = []
|
|
590
|
-
let tempRange = [startVerses[0]]
|
|
591
|
-
|
|
592
|
-
for (let i = 1; i < startVerses.length; i++) {
|
|
593
|
-
if (startVerses[i] === startVerses[i - 1] + 1) {
|
|
594
|
-
tempRange.push(startVerses[i])
|
|
595
|
-
} else {
|
|
596
|
-
ranges.push(tempRange)
|
|
597
|
-
tempRange = [startVerses[i]]
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
ranges.push(tempRange)
|
|
563
|
+
// Helper to format a single chapter:verse combination
|
|
564
|
+
const formatChapterVerse = (chapter, verseStart, verseEnd = null) => {
|
|
565
|
+
if (!chapter) return ""
|
|
566
|
+
if (!verseStart) return `${chapter}`
|
|
567
|
+
return verseEnd ? `${chapter}:${verseStart}-${verseEnd}` : `${chapter}:${verseStart}`
|
|
568
|
+
}
|
|
601
569
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
570
|
+
// Initialize combined passage
|
|
571
|
+
let combined = `${passage.book}`
|
|
572
|
+
|
|
573
|
+
if (passage.type === "multi_chapter_verse_range") {
|
|
574
|
+
// Multi-chapter verse range handling: first verse of first chapter to last verse of last chapter
|
|
575
|
+
if (passage.to) {
|
|
576
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses[0])}` // Start chapter:verse
|
|
577
|
+
combined += `-${formatChapterVerse(
|
|
578
|
+
passage.to.chapter,
|
|
579
|
+
passage.to.verses[passage.to.verses.length - 1]
|
|
580
|
+
)}` // End chapter:last verse
|
|
605
581
|
}
|
|
606
|
-
|
|
607
|
-
|
|
582
|
+
} else if (passage.type === "chapter_verse_range") {
|
|
583
|
+
// Single-chapter verse range
|
|
584
|
+
combined += ` ${formatChapterVerse(
|
|
585
|
+
passage.chapter,
|
|
586
|
+
passage.verses[0],
|
|
587
|
+
passage.verses[passage.verses.length - 1]
|
|
588
|
+
)}`
|
|
589
|
+
} else if (passage.type === "comma_separated_verses") {
|
|
590
|
+
// Comma-separated verses
|
|
591
|
+
combined += ` ${passage.chapter}:${passage.verses.join(",")}`
|
|
592
|
+
} else if (passage.type === "chapter_range") {
|
|
593
|
+
// Chapter range
|
|
594
|
+
combined += ` ${passage.chapter}:${passage.verses[0]}-${passage.to.chapter}:${
|
|
595
|
+
passage.to.verses[passage.to.verses.length - 1]
|
|
596
|
+
}`
|
|
597
|
+
} else {
|
|
598
|
+
// Single chapter or single verse
|
|
599
|
+
combined += ` ${formatChapterVerse(passage.chapter, passage.verses[0])}`
|
|
608
600
|
}
|
|
609
601
|
|
|
610
|
-
//
|
|
611
|
-
const
|
|
612
|
-
|
|
602
|
+
// Generate chapter:verse for current and "to" objects
|
|
603
|
+
const cv = passage.to
|
|
604
|
+
? `${formatChapterVerse(passage.chapter, passage.verses[0])}-${formatChapterVerse(
|
|
605
|
+
passage.to.chapter,
|
|
606
|
+
passage.to.verses[passage.to.verses.length - 1]
|
|
607
|
+
)}`
|
|
608
|
+
: formatChapterVerse(passage.chapter, passage.verses[0])
|
|
609
|
+
|
|
610
|
+
// Generate a hash for the passage
|
|
611
|
+
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, "_").replace(/-/g, "_")}`
|
|
613
612
|
|
|
614
613
|
return {
|
|
615
|
-
passage:
|
|
616
|
-
cv:
|
|
617
|
-
hash,
|
|
614
|
+
passage: combined, // Reconstructed passage
|
|
615
|
+
cv: cv, // Chapter:verse range
|
|
616
|
+
hash: hash, // Unique hash
|
|
618
617
|
}
|
|
619
618
|
}
|
|
620
619
|
|
|
@@ -628,17 +627,18 @@ class CodexParser {
|
|
|
628
627
|
* @return {object} The combined passage object.
|
|
629
628
|
*/
|
|
630
629
|
combine(passages) {
|
|
631
|
-
// Only check if passages are from the same book
|
|
632
|
-
const
|
|
633
|
-
if (
|
|
630
|
+
// Only check if passages are from the same book)
|
|
631
|
+
const sameBook = [...new Set(passages.map((p) => p.book))]
|
|
632
|
+
if (sameBook.length > 1) {
|
|
634
633
|
throw new Error("Passages are not from the same book.")
|
|
635
634
|
}
|
|
636
635
|
|
|
637
636
|
const newPassages = []
|
|
637
|
+
|
|
638
638
|
passages.forEach((passageSet) => {
|
|
639
639
|
passageSet.passages.forEach((passage) => {
|
|
640
640
|
if (passage.versification) {
|
|
641
|
-
newPassages.push(passage.book + " " + passage.versification.
|
|
641
|
+
newPassages.push(passage.book + " " + passage.versification[this.version])
|
|
642
642
|
} else {
|
|
643
643
|
newPassages.push(passage.book + " " + passage.chapter + ":" + passage.verse)
|
|
644
644
|
}
|
|
@@ -646,7 +646,9 @@ class CodexParser {
|
|
|
646
646
|
})
|
|
647
647
|
|
|
648
648
|
const noDuplicates2 = [...new Set(newPassages)]
|
|
649
|
+
|
|
649
650
|
const parsed = this.parse(noDuplicates2.join(" // ")).getPassages()
|
|
651
|
+
|
|
650
652
|
return this.join(parsed)
|
|
651
653
|
}
|
|
652
654
|
|
|
@@ -662,7 +664,6 @@ class CodexParser {
|
|
|
662
664
|
|
|
663
665
|
const chapters = {} // Store verses by chapters
|
|
664
666
|
const uniquePassages = new Set() // Track unique passages to prevent duplicates
|
|
665
|
-
|
|
666
667
|
// Add initial passages to the unique set to avoid duplication
|
|
667
668
|
newObject.passages.forEach((p) => {
|
|
668
669
|
const passageKey = `${p.book}-${p.chapter}-${p.verse}`
|