codexparser 0.1.97 → 0.3.0
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/.trunk/trunk.yaml +3 -3
- package/CHANGELOG.md +18 -0
- package/README.md +76 -0
- package/REFACTORING.md +214 -0
- package/RELEASE_NOTES_v0.2.0.md +5 -0
- package/index.js +1 -1
- package/package.json +4 -24
- package/src/{CodexParser.js → CodexParser.js.backup} +303 -247
- package/src/core/CodexParser.js +353 -0
- package/src/core/PassageCollection.js +404 -0
- package/src/core/PassageValidator.js +107 -0
- package/src/core/ReferenceParser.js +638 -0
- package/src/core/ScriptureScanner.js +235 -0
- package/src/core/VersificationHandler.js +143 -0
- package/src/core/VersionHandler.js +77 -0
- package/src/data/toc.js +7 -0
- package/src/format/osis.js +210 -0
- package/src/index.js +12 -0
- package/src/utils/PassageUtils.js +117 -0
- package/src/utils/chapterVerseCombine.js +65 -0
- package/.babelrc +0 -5
- package/src/chapterVerseCombine.js +0 -63
- package/src/toc.js +0 -2
- package/webpack.config.js +0 -38
- /package/src/{abbr → data/abbr}/sbl.js +0 -0
- /package/src/{bible.js → data/bible.js} +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/1chronicles.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/1corinthians.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/1john.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/1kings.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/1peter.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/1samuel.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/1thessalonians.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/1timothy.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/2chronicles.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/2corinthians.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/2kings.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/2peter.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/2samuel.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/2thessalonians.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/2timothy.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/acts.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/amos.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/colossians.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/daniel.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/deuteronomy.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/ecclesiastes.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/ephesians.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/esther.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/exodus.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/ezekiel.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/ezra.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/galatians.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/genesis.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/habakkuk.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/haggai.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/hebrews.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/hosea.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/isaiah.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/james.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/jeremiah.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/job.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/joel.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/john.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/jonah.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/joshua.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/judges.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/lamentations.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/leviticus.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/luke.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/malachi.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/mark.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/matthew.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/micah.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/nahum.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/nehemiah.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/numbers.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/philippians.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/proverbs.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/psalms.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/revelation.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/romans.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/ruth.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/songs.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/titus.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/zechariah.js +0 -0
- /package/src/{chapter_verses → data/chapter_verses}/zephaniah.js +0 -0
- /package/src/{esv.js → data/esv.js} +0 -0
- /package/src/{versifications → data/versifications}/1chronicles.js +0 -0
- /package/src/{versifications → data/versifications}/1kings.js +0 -0
- /package/src/{versifications → data/versifications}/1samuel.js +0 -0
- /package/src/{versifications → data/versifications}/2chronicles.js +0 -0
- /package/src/{versifications → data/versifications}/2samuel.js +0 -0
- /package/src/{versifications → data/versifications}/daniel.js +0 -0
- /package/src/{versifications → data/versifications}/deuteronomy.js +0 -0
- /package/src/{versifications → data/versifications}/ecclesiastes.js +0 -0
- /package/src/{versifications → data/versifications}/exodus.js +0 -0
- /package/src/{versifications → data/versifications}/ezekiel.js +0 -0
- /package/src/{versifications → data/versifications}/genesis.js +0 -0
- /package/src/{versifications → data/versifications}/hosea.js +0 -0
- /package/src/{versifications → data/versifications}/isaiah.js +0 -0
- /package/src/{versifications → data/versifications}/jeremiah.js +0 -0
- /package/src/{versifications → data/versifications}/job.js +0 -0
- /package/src/{versifications → data/versifications}/joel.js +0 -0
- /package/src/{versifications → data/versifications}/jonah.js +0 -0
- /package/src/{versifications → data/versifications}/joshua.js +0 -0
- /package/src/{versifications → data/versifications}/leviticus.js +0 -0
- /package/src/{versifications → data/versifications}/malachi.js +0 -0
- /package/src/{versifications → data/versifications}/micah.js +0 -0
- /package/src/{versifications → data/versifications}/nahum.js +0 -0
- /package/src/{versifications → data/versifications}/nehemiah.js +0 -0
- /package/src/{versifications → data/versifications}/numbers.js +0 -0
- /package/src/{versifications → data/versifications}/proverbs.js +0 -0
- /package/src/{versifications → data/versifications}/psalms.js +0 -0
- /package/src/{versifications → data/versifications}/song.js +0 -0
- /package/src/{versifications → data/versifications}/zechariah.js +0 -0
- /package/src/{versified.js → data/versified.js} +0 -0
- /package/src/{abbr.js → format/abbr.js} +0 -0
- /package/src/{functions.js → utils/functions.js} +0 -0
- /package/src/{regex.js → utils/regex.js} +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScriptureScanner.js
|
|
3
|
+
* Handles scanning text for scripture references
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const bible = require("../data/bible")
|
|
7
|
+
const abbreviations = require("../format/abbr")
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Scanner for finding scripture references in text
|
|
11
|
+
*/
|
|
12
|
+
class ScriptureScanner {
|
|
13
|
+
#bible
|
|
14
|
+
#abbreviations
|
|
15
|
+
#config
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {Object} config - Configuration options
|
|
19
|
+
*/
|
|
20
|
+
constructor(config = {}) {
|
|
21
|
+
this.#bible = bible
|
|
22
|
+
this.#abbreviations = abbreviations
|
|
23
|
+
this.#config = config
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Scans text for scripture references
|
|
28
|
+
* @param {string} text - The text to scan
|
|
29
|
+
* @returns {Array} Array of found references
|
|
30
|
+
*/
|
|
31
|
+
scan(text) {
|
|
32
|
+
const fullNames = [...this.#bible.old, ...this.#bible.new]
|
|
33
|
+
const abbreviationKeys = Object.keys(this.#abbreviations)
|
|
34
|
+
const found = []
|
|
35
|
+
|
|
36
|
+
// Minimal normalization: fix periods before numbers, remove trailing periods
|
|
37
|
+
const normalizedText = text.replace(/\.(?=\d)/g, ":").replace(/(\b[A-Za-z]+)\.(?=\s|$)/g, "$1")
|
|
38
|
+
|
|
39
|
+
const lowercaseBibleFullNames = fullNames.map((book) => book.toLowerCase())
|
|
40
|
+
const lowercaseBibleAbbreviations = abbreviationKeys.map((abbr) => abbr.toLowerCase())
|
|
41
|
+
const lowerCaseText = normalizedText.toLowerCase()
|
|
42
|
+
|
|
43
|
+
let i = 0
|
|
44
|
+
|
|
45
|
+
while (i < lowerCaseText.length) {
|
|
46
|
+
let foundBook = null
|
|
47
|
+
let bookStartIndex = -1
|
|
48
|
+
let matchedLength = 0
|
|
49
|
+
|
|
50
|
+
// Skip whitespace and special characters before checking for book
|
|
51
|
+
while (i < lowerCaseText.length && /[\s—-]/.test(lowerCaseText[i])) {
|
|
52
|
+
i++
|
|
53
|
+
}
|
|
54
|
+
if (i >= lowerCaseText.length) break
|
|
55
|
+
|
|
56
|
+
// Check for full book names
|
|
57
|
+
for (let j = 0; j < lowercaseBibleFullNames.length; j++) {
|
|
58
|
+
const book = lowercaseBibleFullNames[j]
|
|
59
|
+
if (lowerCaseText.startsWith(book, i) && book.length > matchedLength) {
|
|
60
|
+
foundBook = fullNames[j]
|
|
61
|
+
bookStartIndex = i
|
|
62
|
+
matchedLength = book.length
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check for abbreviations
|
|
67
|
+
if (!foundBook) {
|
|
68
|
+
for (let k = 0; k < lowercaseBibleAbbreviations.length; k++) {
|
|
69
|
+
const abbreviation = lowercaseBibleAbbreviations[k]
|
|
70
|
+
if (lowerCaseText.startsWith(abbreviation, i) && abbreviation.length > matchedLength) {
|
|
71
|
+
foundBook = this.#abbreviations[abbreviationKeys[k]]
|
|
72
|
+
bookStartIndex = i
|
|
73
|
+
matchedLength = abbreviation.length
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (foundBook) {
|
|
79
|
+
i += matchedLength
|
|
80
|
+
let chapterVerse = ""
|
|
81
|
+
const references = []
|
|
82
|
+
let refStartIndex = bookStartIndex
|
|
83
|
+
let originalRefStartIndex = bookStartIndex
|
|
84
|
+
|
|
85
|
+
while (i < normalizedText.length && this.#isValidChapterVerseChar(normalizedText[i])) {
|
|
86
|
+
if (this.#isNextBibleBook(i, lowerCaseText, lowercaseBibleFullNames, lowercaseBibleAbbreviations)) {
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
if (normalizedText[i] === ";") {
|
|
90
|
+
const formattedReference = chapterVerse.trim()
|
|
91
|
+
if (formattedReference) {
|
|
92
|
+
const refEndIndex = i
|
|
93
|
+
references.push({
|
|
94
|
+
ref: formattedReference,
|
|
95
|
+
start: refStartIndex,
|
|
96
|
+
end: refEndIndex,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
chapterVerse = ""
|
|
100
|
+
refStartIndex = i + 1
|
|
101
|
+
const semicolonIndex = text.indexOf(";", originalRefStartIndex)
|
|
102
|
+
originalRefStartIndex = semicolonIndex !== -1 ? semicolonIndex + 1 : refStartIndex
|
|
103
|
+
i++
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
chapterVerse += normalizedText[i]
|
|
107
|
+
i++
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (chapterVerse.trim().length > 0) {
|
|
111
|
+
const formattedReference = chapterVerse.trim()
|
|
112
|
+
if (formattedReference) {
|
|
113
|
+
const refEndIndex = i
|
|
114
|
+
references.push({
|
|
115
|
+
ref: formattedReference,
|
|
116
|
+
start: refStartIndex,
|
|
117
|
+
end: refEndIndex,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Align indices with original text
|
|
123
|
+
const originalBookText = text.slice(bookStartIndex, bookStartIndex + matchedLength)
|
|
124
|
+
const originalBookStartIndex =
|
|
125
|
+
text.indexOf(originalBookText, bookStartIndex) !== -1
|
|
126
|
+
? text.indexOf(originalBookText, bookStartIndex)
|
|
127
|
+
: bookStartIndex
|
|
128
|
+
|
|
129
|
+
references.forEach(({ ref, start, end }) => {
|
|
130
|
+
const type = this.#determineReferenceType(ref)
|
|
131
|
+
const fullRefText = `${originalBookText} ${ref.replace(":", ".")}`
|
|
132
|
+
const suffixData = this.#detectSuffix(normalizedText, end)
|
|
133
|
+
const suffix = suffixData ? suffixData.suffix : null
|
|
134
|
+
let refEndIndex = end
|
|
135
|
+
|
|
136
|
+
if (suffixData) {
|
|
137
|
+
refEndIndex += suffixData.length
|
|
138
|
+
i += suffixData.length
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Map to original text
|
|
142
|
+
let originalStartIndex =
|
|
143
|
+
text.indexOf(fullRefText, originalRefStartIndex) !== -1
|
|
144
|
+
? text.indexOf(fullRefText, originalRefStartIndex)
|
|
145
|
+
: originalBookStartIndex
|
|
146
|
+
|
|
147
|
+
let originalEndIndex = originalStartIndex + fullRefText.length
|
|
148
|
+
let originalText = text.slice(originalStartIndex, originalEndIndex)
|
|
149
|
+
|
|
150
|
+
// Adjust for suffix in original text
|
|
151
|
+
if (suffixData) {
|
|
152
|
+
originalEndIndex += suffixData.length
|
|
153
|
+
originalText = text.slice(originalStartIndex, originalEndIndex)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Trim trailing whitespace from originalText
|
|
157
|
+
while (originalEndIndex > originalStartIndex && /[\s]/.test(text[originalEndIndex - 1])) {
|
|
158
|
+
originalEndIndex--
|
|
159
|
+
originalText = text.slice(originalStartIndex, originalEndIndex)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
found.push({
|
|
163
|
+
book: foundBook,
|
|
164
|
+
reference: ref,
|
|
165
|
+
startIndex: originalStartIndex,
|
|
166
|
+
endIndex: originalEndIndex,
|
|
167
|
+
version: suffix || null,
|
|
168
|
+
type,
|
|
169
|
+
originalText: originalText,
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
} else {
|
|
173
|
+
i++
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return found
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Checks if character is valid for chapter/verse references
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
#isValidChapterVerseChar(char) {
|
|
185
|
+
return /[\d:,\-;\s]/.test(char)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Checks if the next position starts another Bible book
|
|
190
|
+
* @private
|
|
191
|
+
*/
|
|
192
|
+
#isNextBibleBook(startIndex, lowerCaseText, lowercaseBibleFullNames, lowercaseBibleAbbreviations) {
|
|
193
|
+
const textAfterCurrentPosition = lowerCaseText.substring(startIndex).trim()
|
|
194
|
+
return (
|
|
195
|
+
lowercaseBibleFullNames.some((book) => textAfterCurrentPosition.startsWith(book)) ||
|
|
196
|
+
lowercaseBibleAbbreviations.some((abbr) => textAfterCurrentPosition.startsWith(abbr))
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Detects version suffix (LXX, MT)
|
|
202
|
+
* @private
|
|
203
|
+
*/
|
|
204
|
+
#detectSuffix(text, startIndex) {
|
|
205
|
+
const suffixMatch = text.substring(startIndex).match(/\b(LXX|MT)\b/i)
|
|
206
|
+
return suffixMatch ? { suffix: suffixMatch[0].toUpperCase(), length: suffixMatch[0].length } : null
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Determines the type of scripture reference
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
#determineReferenceType(ref) {
|
|
214
|
+
if (ref.includes(":")) {
|
|
215
|
+
if (ref.includes("-")) {
|
|
216
|
+
const [start, end] = ref.split("-")
|
|
217
|
+
const startParts = start.split(":")
|
|
218
|
+
const endParts = end.split(":")
|
|
219
|
+
return startParts.length > 1 && endParts.length > 1 && startParts[0].trim() !== endParts[0].trim()
|
|
220
|
+
? "multi_chapter_verse_range"
|
|
221
|
+
: "chapter_verse_range"
|
|
222
|
+
} else if (ref.includes(",")) {
|
|
223
|
+
return "comma_separated_verses"
|
|
224
|
+
} else {
|
|
225
|
+
return "chapter_verse"
|
|
226
|
+
}
|
|
227
|
+
} else if (ref.includes("-")) {
|
|
228
|
+
return "chapter_range"
|
|
229
|
+
} else {
|
|
230
|
+
return "single_chapter"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = ScriptureScanner
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VersificationHandler.js
|
|
3
|
+
* Handles versification differences between Bible versions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const versified = require("../data/versified")
|
|
7
|
+
const PassageUtils = require("../utils/PassageUtils")
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handles versification differences
|
|
11
|
+
*/
|
|
12
|
+
class VersificationHandler {
|
|
13
|
+
#versificationDifferences
|
|
14
|
+
#chapterVerses
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this.#versificationDifferences = versified
|
|
18
|
+
// We need to make a copy to avoid mutating the original
|
|
19
|
+
this.#chapterVerses = {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Searches for versification differences for a book and chapter
|
|
24
|
+
* @param {string} book - The book name
|
|
25
|
+
* @param {number} chapter - The chapter number
|
|
26
|
+
* @param {string} version - The Bible version
|
|
27
|
+
* @returns {Object|undefined} Updated chapter verses or undefined
|
|
28
|
+
*/
|
|
29
|
+
searchVersificationDifferences(book, chapter, version) {
|
|
30
|
+
version = version.toLowerCase()
|
|
31
|
+
|
|
32
|
+
// Handle single-chapter book "Obadiah"
|
|
33
|
+
if (book === "Obadiah") {
|
|
34
|
+
const singleChapterBook = PassageUtils.SINGLE_CHAPTER_BOOKS.find((b) => Object.keys(b)[0] === "Obadiah")
|
|
35
|
+
if (!singleChapterBook || !singleChapterBook[book][chapter]) {
|
|
36
|
+
return // No data for Obadiah or chapter
|
|
37
|
+
}
|
|
38
|
+
if (!this.#versificationDifferences[book]) {
|
|
39
|
+
return // No versification differences for Obadiah
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Process versification differences
|
|
43
|
+
for (const [key, value] of Object.entries(this.#versificationDifferences[book])) {
|
|
44
|
+
if (value[version]?.startsWith(`${chapter}:`)) {
|
|
45
|
+
if (value[version]) {
|
|
46
|
+
const verse = value[version].split(":")[1]
|
|
47
|
+
singleChapterBook[book][chapter].push(Number(verse))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
singleChapterBook[book][chapter] = Array.from(new Set(singleChapterBook[book][chapter]))
|
|
52
|
+
return singleChapterBook[book]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle all other books using chapterVerses
|
|
56
|
+
const chapterVerses = PassageUtils.getChapterVerses(book, chapter)
|
|
57
|
+
if (!chapterVerses.length) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
if (!this.#versificationDifferences[book]) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Create a mutable copy
|
|
65
|
+
if (!this.#chapterVerses[book]) {
|
|
66
|
+
this.#chapterVerses[book] = {}
|
|
67
|
+
}
|
|
68
|
+
if (!this.#chapterVerses[book][chapter]) {
|
|
69
|
+
this.#chapterVerses[book][chapter] = [...chapterVerses]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const [key, value] of Object.entries(this.#versificationDifferences[book])) {
|
|
73
|
+
if (value[version]?.startsWith(`${chapter}:`)) {
|
|
74
|
+
if (value[version]) {
|
|
75
|
+
const verse = value[version].split(":")[1]
|
|
76
|
+
this.#chapterVerses[book][chapter].push(Number(verse))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.#chapterVerses[book][chapter] = Array.from(new Set(this.#chapterVerses[book][chapter]))
|
|
82
|
+
return this.#chapterVerses
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Applies versification differences to parsed passages
|
|
87
|
+
* @param {Array} passages - Array of passage objects
|
|
88
|
+
* @returns {Array} Updated passages with versification info
|
|
89
|
+
*/
|
|
90
|
+
applyVersification(passages) {
|
|
91
|
+
passages.forEach((passage) => {
|
|
92
|
+
const hasVersification = this.#versificationDifferences[passage.book]
|
|
93
|
+
|
|
94
|
+
passage.passages.forEach((subPassage) => {
|
|
95
|
+
if (hasVersification) {
|
|
96
|
+
const key = `${subPassage.chapter}:${subPassage.verse}`
|
|
97
|
+
if (this.#versificationDifferences[passage.book][key]) {
|
|
98
|
+
subPassage.versification = this.#versificationDifferences[passage.book][key]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (passage.version) {
|
|
103
|
+
const versionAbbreviation = passage.version.abbreviation
|
|
104
|
+
const versionType =
|
|
105
|
+
versionAbbreviation === "lxx" ? "lxx" : versionAbbreviation === "mt" ? "mt" : null
|
|
106
|
+
|
|
107
|
+
if (versionType) {
|
|
108
|
+
const versionReference = `${subPassage.chapter}:${subPassage.verse}`
|
|
109
|
+
for (const versification in this.#versificationDifferences[passage.book]) {
|
|
110
|
+
if (
|
|
111
|
+
this.#versificationDifferences[passage.book][versification][versionType] ===
|
|
112
|
+
versionReference
|
|
113
|
+
) {
|
|
114
|
+
subPassage.versification = this.#versificationDifferences[passage.book][versification]
|
|
115
|
+
break
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
return passages
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Gets versification for a specific passage
|
|
128
|
+
* @param {string} book - Book name
|
|
129
|
+
* @param {number} chapter - Chapter number
|
|
130
|
+
* @param {number} verse - Verse number
|
|
131
|
+
* @returns {Object|null} Versification object or null
|
|
132
|
+
*/
|
|
133
|
+
getVersification(book, chapter, verse) {
|
|
134
|
+
if (!this.#versificationDifferences[book]) {
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const key = `${chapter}:${verse}`
|
|
139
|
+
return this.#versificationDifferences[book][key] || null
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = VersificationHandler
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VersionHandler.js
|
|
3
|
+
* Handles Bible version management and conversions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handles Bible version operations
|
|
8
|
+
*/
|
|
9
|
+
class VersionHandler {
|
|
10
|
+
static VERSIONS = {
|
|
11
|
+
ENG: { name: "English", value: "ENG", abbreviation: "eng" },
|
|
12
|
+
LXX: { name: "Septuagint", value: "LXX", abbreviation: "lxx" },
|
|
13
|
+
MT: { name: "Masoretic Text", value: "MT", abbreviation: "mt" },
|
|
14
|
+
BHS: { name: "Masoretic Text", value: "MT", abbreviation: "mt" }, // BHS is alias for MT
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes version string
|
|
19
|
+
* @param {string} version - Version string
|
|
20
|
+
* @returns {string} Normalized version
|
|
21
|
+
*/
|
|
22
|
+
static normalizeVersion(version) {
|
|
23
|
+
if (!version) return "eng"
|
|
24
|
+
const lowerVersion = version.toLowerCase()
|
|
25
|
+
return lowerVersion === "bhs" ? "mt" : lowerVersion
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Gets version object for a given version and testament
|
|
30
|
+
* @param {string} version - Version abbreviation
|
|
31
|
+
* @param {string} testament - Testament (old/new)
|
|
32
|
+
* @returns {Object} Version object
|
|
33
|
+
*/
|
|
34
|
+
static getVersion(version, testament) {
|
|
35
|
+
const effectiveVersion = version || "eng"
|
|
36
|
+
const lowerVersion = effectiveVersion.toLowerCase()
|
|
37
|
+
|
|
38
|
+
if (lowerVersion === "lxx" && testament === "old") {
|
|
39
|
+
return VersionHandler.VERSIONS.LXX
|
|
40
|
+
}
|
|
41
|
+
if ((lowerVersion === "mt" || lowerVersion === "bhs") && testament === "old") {
|
|
42
|
+
return VersionHandler.VERSIONS.MT
|
|
43
|
+
}
|
|
44
|
+
return VersionHandler.VERSIONS.ENG
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validates version string
|
|
49
|
+
* @param {string} version - Version to validate
|
|
50
|
+
* @returns {boolean} True if valid
|
|
51
|
+
*/
|
|
52
|
+
static isValidVersion(version) {
|
|
53
|
+
const normalized = VersionHandler.normalizeVersion(version)
|
|
54
|
+
return ["eng", "lxx", "mt"].includes(normalized)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gets version object from abbreviation
|
|
59
|
+
* @param {string} abbr - Version abbreviation
|
|
60
|
+
* @returns {Object} Version object
|
|
61
|
+
*/
|
|
62
|
+
static getVersionObject(abbr) {
|
|
63
|
+
const normalized = VersionHandler.normalizeVersion(abbr)
|
|
64
|
+
|
|
65
|
+
switch (normalized) {
|
|
66
|
+
case "lxx":
|
|
67
|
+
return VersionHandler.VERSIONS.LXX
|
|
68
|
+
case "mt":
|
|
69
|
+
return VersionHandler.VERSIONS.MT
|
|
70
|
+
case "eng":
|
|
71
|
+
default:
|
|
72
|
+
return VersionHandler.VERSIONS.ENG
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = VersionHandler
|
package/src/data/toc.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* osis.js
|
|
3
|
+
* OSIS reference formatter and book code mapping (text + numeric)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const bible = require("../data/bible")
|
|
7
|
+
|
|
8
|
+
const OSIS_BOOKS = {
|
|
9
|
+
// Old Testament
|
|
10
|
+
Genesis: "Gen",
|
|
11
|
+
Exodus: "Exod",
|
|
12
|
+
Leviticus: "Lev",
|
|
13
|
+
Numbers: "Num",
|
|
14
|
+
Deuteronomy: "Deut",
|
|
15
|
+
Joshua: "Josh",
|
|
16
|
+
Judges: "Judg",
|
|
17
|
+
Ruth: "Ruth",
|
|
18
|
+
"1 Samuel": "1Sam",
|
|
19
|
+
"2 Samuel": "2Sam",
|
|
20
|
+
"1 Kings": "1Kgs",
|
|
21
|
+
"2 Kings": "2Kgs",
|
|
22
|
+
"1 Chronicles": "1Chr",
|
|
23
|
+
"2 Chronicles": "2Chr",
|
|
24
|
+
Ezra: "Ezra",
|
|
25
|
+
Nehemiah: "Neh",
|
|
26
|
+
Esther: "Esth",
|
|
27
|
+
Job: "Job",
|
|
28
|
+
Psalms: "Ps",
|
|
29
|
+
Psalm: "Ps",
|
|
30
|
+
Proverbs: "Prov",
|
|
31
|
+
Ecclesiastes: "Eccl",
|
|
32
|
+
"Song of Solomon": "Song",
|
|
33
|
+
"Song of Songs": "Song",
|
|
34
|
+
Isaiah: "Isa",
|
|
35
|
+
Jeremiah: "Jer",
|
|
36
|
+
Lamentations: "Lam",
|
|
37
|
+
Ezekiel: "Ezek",
|
|
38
|
+
Daniel: "Dan",
|
|
39
|
+
Hosea: "Hos",
|
|
40
|
+
Joel: "Joel",
|
|
41
|
+
Amos: "Amos",
|
|
42
|
+
Obadiah: "Obad",
|
|
43
|
+
Jonah: "Jonah",
|
|
44
|
+
Micah: "Mic",
|
|
45
|
+
Nahum: "Nah",
|
|
46
|
+
Habakkuk: "Hab",
|
|
47
|
+
Zephaniah: "Zeph",
|
|
48
|
+
Haggai: "Hag",
|
|
49
|
+
Zechariah: "Zech",
|
|
50
|
+
Malachi: "Mal",
|
|
51
|
+
|
|
52
|
+
// New Testament
|
|
53
|
+
Matthew: "Matt",
|
|
54
|
+
Mark: "Mark",
|
|
55
|
+
Luke: "Luke",
|
|
56
|
+
John: "John",
|
|
57
|
+
Acts: "Acts",
|
|
58
|
+
Romans: "Rom",
|
|
59
|
+
"1 Corinthians": "1Cor",
|
|
60
|
+
"2 Corinthians": "2Cor",
|
|
61
|
+
Galatians: "Gal",
|
|
62
|
+
Ephesians: "Eph",
|
|
63
|
+
Philippians: "Phil",
|
|
64
|
+
Colossians: "Col",
|
|
65
|
+
"1 Thessalonians": "1Thess",
|
|
66
|
+
"2 Thessalonians": "2Thess",
|
|
67
|
+
"1 Timothy": "1Tim",
|
|
68
|
+
"2 Timothy": "2Tim",
|
|
69
|
+
Titus: "Titus",
|
|
70
|
+
Philemon: "Phlm",
|
|
71
|
+
Hebrews: "Heb",
|
|
72
|
+
James: "Jas",
|
|
73
|
+
"1 Peter": "1Pet",
|
|
74
|
+
"2 Peter": "2Pet",
|
|
75
|
+
"1 John": "1John",
|
|
76
|
+
"2 John": "2John",
|
|
77
|
+
"3 John": "3John",
|
|
78
|
+
Jude: "Jude",
|
|
79
|
+
Revelation: "Rev",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getOsisBook(book) {
|
|
83
|
+
return OSIS_BOOKS[book] || book.replace(/\s+/g, "")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Build canonical OSIS order (unique, Old + New) using bible.js and OSIS names
|
|
87
|
+
const CANONICAL_OSIS_ORDER = (() => {
|
|
88
|
+
const order = []
|
|
89
|
+
const seen = new Set()
|
|
90
|
+
const pushIfNew = (name) => {
|
|
91
|
+
const osis = getOsisBook(name)
|
|
92
|
+
if (!seen.has(osis)) {
|
|
93
|
+
seen.add(osis)
|
|
94
|
+
order.push(osis)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
;[...bible.old, ...bible.new].forEach((name) => pushIfNew(name))
|
|
98
|
+
return order
|
|
99
|
+
})()
|
|
100
|
+
|
|
101
|
+
function getCanonicalBookNumber(book) {
|
|
102
|
+
const osis = getOsisBook(book)
|
|
103
|
+
const idx = CANONICAL_OSIS_ORDER.indexOf(osis)
|
|
104
|
+
return idx >= 0 ? idx + 1 : null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Converts a book/chapter/verse into the pythonbible-style integer verse id
|
|
109
|
+
* Scheme: bookNumber * 1_000_000 + chapter * 1_000 + verse
|
|
110
|
+
* Example: John 3:16 (book 43) => 43_003_016 => 43003016
|
|
111
|
+
*/
|
|
112
|
+
function toVerseId(book, chapter, verse) {
|
|
113
|
+
const bnum = getCanonicalBookNumber(book)
|
|
114
|
+
const ch = Number(chapter)
|
|
115
|
+
const vs = Number(verse)
|
|
116
|
+
if (bnum == null || Number.isNaN(ch) || Number.isNaN(vs)) return null
|
|
117
|
+
return bnum * 1_000_000 + ch * 1_000 + vs
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Formats a passage object into an OSIS reference string
|
|
122
|
+
* - Single verse: Book.Ch.V
|
|
123
|
+
* - Verse range: Book.Ch.V1-Book.Ch.Vn
|
|
124
|
+
* - Multi-chapter range: Book.Ch1.V1-Book.Ch2.Vn
|
|
125
|
+
* - Multiple verses: Book.Ch.V1,Book.Ch.V2
|
|
126
|
+
* - Sequences across chapters: Book.Ch1.V1;Book.Ch2.V2
|
|
127
|
+
* @param {Object} passage
|
|
128
|
+
* @returns {string}
|
|
129
|
+
*/
|
|
130
|
+
function formatOsis(passage) {
|
|
131
|
+
const book = getOsisBook(passage.book)
|
|
132
|
+
const chapter = Number(passage.chapter)
|
|
133
|
+
const verses = passage.verses || []
|
|
134
|
+
|
|
135
|
+
const isRange = passage.to && (passage.type === "multi_chapter_verse_range" || passage.type === "chapter_range")
|
|
136
|
+
if (isRange) {
|
|
137
|
+
const startVerse =
|
|
138
|
+
verses[0] && String(verses[0]).includes("-")
|
|
139
|
+
? Number(String(verses[0]).split("-")[0])
|
|
140
|
+
: Number(verses[0]) || 1
|
|
141
|
+
const endChapter = Number(passage.to.chapter)
|
|
142
|
+
const endFirst = passage.to.verses?.[0] || "1"
|
|
143
|
+
const endVerse = String(endFirst).includes("-") ? Number(String(endFirst).split("-")[1]) : Number(endFirst) || 1
|
|
144
|
+
return `${book}.${chapter}.${startVerse}-${book}.${endChapter}.${endVerse}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// No explicit range: build from verse list at same chapter
|
|
148
|
+
const parts = []
|
|
149
|
+
for (const v of verses) {
|
|
150
|
+
if (String(v).includes("-")) {
|
|
151
|
+
const [s, e] = String(v).split("-").map(Number)
|
|
152
|
+
parts.push(`${book}.${chapter}.${s}-${book}.${chapter}.${e}`)
|
|
153
|
+
} else {
|
|
154
|
+
const num = Number(v)
|
|
155
|
+
if (!isNaN(num) && num > 0) {
|
|
156
|
+
parts.push(`${book}.${chapter}.${num}`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return parts.join(",")
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Formats a passage object into an OSIS numeric reference string
|
|
165
|
+
* - Single verse: B.Ch.V (B = canonical book number 1..66)
|
|
166
|
+
* - Verse range (same chapter): B.Ch.V1-B.Ch.Vn
|
|
167
|
+
* - Multi-chapter range: B.Ch1.V1-B.Ch2.Vn
|
|
168
|
+
* - Multiple verses: B.Ch.V1,B.Ch.V2
|
|
169
|
+
* - Sequences across chapters: B.Ch1.V1;B.Ch2.V2
|
|
170
|
+
* @param {Object} passage
|
|
171
|
+
* @returns {string}
|
|
172
|
+
*/
|
|
173
|
+
function formatOsisNumeric(passage) {
|
|
174
|
+
const chapter = Number(passage.chapter)
|
|
175
|
+
const verses = passage.verses || []
|
|
176
|
+
|
|
177
|
+
if (Number.isNaN(chapter)) return ""
|
|
178
|
+
|
|
179
|
+
const isRange = passage.to && (passage.type === "multi_chapter_verse_range" || passage.type === "chapter_range")
|
|
180
|
+
if (isRange) {
|
|
181
|
+
const startVerse =
|
|
182
|
+
verses[0] && String(verses[0]).includes("-")
|
|
183
|
+
? Number(String(verses[0]).split("-")[0])
|
|
184
|
+
: Number(verses[0]) || 1
|
|
185
|
+
const endChapter = Number(passage.to.chapter)
|
|
186
|
+
const endFirst = passage.to.verses?.[0] || "1"
|
|
187
|
+
const endVerse = String(endFirst).includes("-") ? Number(String(endFirst).split("-")[1]) : Number(endFirst) || 1
|
|
188
|
+
const startId = toVerseId(passage.book, chapter, startVerse)
|
|
189
|
+
const endId = toVerseId(passage.book, endChapter, endVerse)
|
|
190
|
+
return startId != null && endId != null ? `${startId}-${endId}` : ""
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const parts = []
|
|
194
|
+
for (const v of verses) {
|
|
195
|
+
if (String(v).includes("-")) {
|
|
196
|
+
const [s, e] = String(v).split("-").map(Number)
|
|
197
|
+
const startId = toVerseId(passage.book, chapter, s)
|
|
198
|
+
const endId = toVerseId(passage.book, chapter, e)
|
|
199
|
+
if (startId != null && endId != null) {
|
|
200
|
+
parts.push(`${startId}-${endId}`)
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
const num = Number(v)
|
|
204
|
+
const id = toVerseId(passage.book, chapter, num)
|
|
205
|
+
if (id != null) parts.push(String(id))
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return parts.join(",")
|
|
209
|
+
}
|
|
210
|
+
module.exports = { formatOsis, getOsisBook, formatOsisNumeric, getCanonicalBookNumber, toVerseId }
|
package/src/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Public barrel for consumers
|
|
2
|
+
const CodexParser = require("./core/CodexParser")
|
|
3
|
+
const { formatOsis, formatOsisNumeric, toVerseId } = require("./format/osis")
|
|
4
|
+
const PassageUtils = require("./utils/PassageUtils")
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
CodexParser,
|
|
8
|
+
formatOsis,
|
|
9
|
+
formatOsisNumeric,
|
|
10
|
+
toVerseId,
|
|
11
|
+
PassageUtils,
|
|
12
|
+
}
|