codexparser 0.1.97 → 0.2.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.
Files changed (117) hide show
  1. package/.trunk/trunk.yaml +3 -3
  2. package/REFACTORING.md +214 -0
  3. package/index.js +1 -1
  4. package/package.json +4 -24
  5. package/src/{CodexParser.js → CodexParser.js.backup} +303 -247
  6. package/src/core/CodexParser.js +353 -0
  7. package/src/core/PassageCollection.js +404 -0
  8. package/src/core/PassageValidator.js +107 -0
  9. package/src/core/ReferenceParser.js +622 -0
  10. package/src/core/ScriptureScanner.js +235 -0
  11. package/src/core/VersificationHandler.js +143 -0
  12. package/src/core/VersionHandler.js +77 -0
  13. package/src/data/toc.js +7 -0
  14. package/src/format/osis.js +210 -0
  15. package/src/index.js +12 -0
  16. package/src/utils/PassageUtils.js +117 -0
  17. package/src/utils/chapterVerseCombine.js +65 -0
  18. package/.babelrc +0 -5
  19. package/src/chapterVerseCombine.js +0 -63
  20. package/src/toc.js +0 -2
  21. package/webpack.config.js +0 -38
  22. /package/src/{abbr → data/abbr}/sbl.js +0 -0
  23. /package/src/{bible.js → data/bible.js} +0 -0
  24. /package/src/{chapter_verses → data/chapter_verses}/1chronicles.js +0 -0
  25. /package/src/{chapter_verses → data/chapter_verses}/1corinthians.js +0 -0
  26. /package/src/{chapter_verses → data/chapter_verses}/1john.js +0 -0
  27. /package/src/{chapter_verses → data/chapter_verses}/1kings.js +0 -0
  28. /package/src/{chapter_verses → data/chapter_verses}/1peter.js +0 -0
  29. /package/src/{chapter_verses → data/chapter_verses}/1samuel.js +0 -0
  30. /package/src/{chapter_verses → data/chapter_verses}/1thessalonians.js +0 -0
  31. /package/src/{chapter_verses → data/chapter_verses}/1timothy.js +0 -0
  32. /package/src/{chapter_verses → data/chapter_verses}/2chronicles.js +0 -0
  33. /package/src/{chapter_verses → data/chapter_verses}/2corinthians.js +0 -0
  34. /package/src/{chapter_verses → data/chapter_verses}/2kings.js +0 -0
  35. /package/src/{chapter_verses → data/chapter_verses}/2peter.js +0 -0
  36. /package/src/{chapter_verses → data/chapter_verses}/2samuel.js +0 -0
  37. /package/src/{chapter_verses → data/chapter_verses}/2thessalonians.js +0 -0
  38. /package/src/{chapter_verses → data/chapter_verses}/2timothy.js +0 -0
  39. /package/src/{chapter_verses → data/chapter_verses}/acts.js +0 -0
  40. /package/src/{chapter_verses → data/chapter_verses}/amos.js +0 -0
  41. /package/src/{chapter_verses → data/chapter_verses}/colossians.js +0 -0
  42. /package/src/{chapter_verses → data/chapter_verses}/daniel.js +0 -0
  43. /package/src/{chapter_verses → data/chapter_verses}/deuteronomy.js +0 -0
  44. /package/src/{chapter_verses → data/chapter_verses}/ecclesiastes.js +0 -0
  45. /package/src/{chapter_verses → data/chapter_verses}/ephesians.js +0 -0
  46. /package/src/{chapter_verses → data/chapter_verses}/esther.js +0 -0
  47. /package/src/{chapter_verses → data/chapter_verses}/exodus.js +0 -0
  48. /package/src/{chapter_verses → data/chapter_verses}/ezekiel.js +0 -0
  49. /package/src/{chapter_verses → data/chapter_verses}/ezra.js +0 -0
  50. /package/src/{chapter_verses → data/chapter_verses}/galatians.js +0 -0
  51. /package/src/{chapter_verses → data/chapter_verses}/genesis.js +0 -0
  52. /package/src/{chapter_verses → data/chapter_verses}/habakkuk.js +0 -0
  53. /package/src/{chapter_verses → data/chapter_verses}/haggai.js +0 -0
  54. /package/src/{chapter_verses → data/chapter_verses}/hebrews.js +0 -0
  55. /package/src/{chapter_verses → data/chapter_verses}/hosea.js +0 -0
  56. /package/src/{chapter_verses → data/chapter_verses}/isaiah.js +0 -0
  57. /package/src/{chapter_verses → data/chapter_verses}/james.js +0 -0
  58. /package/src/{chapter_verses → data/chapter_verses}/jeremiah.js +0 -0
  59. /package/src/{chapter_verses → data/chapter_verses}/job.js +0 -0
  60. /package/src/{chapter_verses → data/chapter_verses}/joel.js +0 -0
  61. /package/src/{chapter_verses → data/chapter_verses}/john.js +0 -0
  62. /package/src/{chapter_verses → data/chapter_verses}/jonah.js +0 -0
  63. /package/src/{chapter_verses → data/chapter_verses}/joshua.js +0 -0
  64. /package/src/{chapter_verses → data/chapter_verses}/judges.js +0 -0
  65. /package/src/{chapter_verses → data/chapter_verses}/lamentations.js +0 -0
  66. /package/src/{chapter_verses → data/chapter_verses}/leviticus.js +0 -0
  67. /package/src/{chapter_verses → data/chapter_verses}/luke.js +0 -0
  68. /package/src/{chapter_verses → data/chapter_verses}/malachi.js +0 -0
  69. /package/src/{chapter_verses → data/chapter_verses}/mark.js +0 -0
  70. /package/src/{chapter_verses → data/chapter_verses}/matthew.js +0 -0
  71. /package/src/{chapter_verses → data/chapter_verses}/micah.js +0 -0
  72. /package/src/{chapter_verses → data/chapter_verses}/nahum.js +0 -0
  73. /package/src/{chapter_verses → data/chapter_verses}/nehemiah.js +0 -0
  74. /package/src/{chapter_verses → data/chapter_verses}/numbers.js +0 -0
  75. /package/src/{chapter_verses → data/chapter_verses}/philippians.js +0 -0
  76. /package/src/{chapter_verses → data/chapter_verses}/proverbs.js +0 -0
  77. /package/src/{chapter_verses → data/chapter_verses}/psalms.js +0 -0
  78. /package/src/{chapter_verses → data/chapter_verses}/revelation.js +0 -0
  79. /package/src/{chapter_verses → data/chapter_verses}/romans.js +0 -0
  80. /package/src/{chapter_verses → data/chapter_verses}/ruth.js +0 -0
  81. /package/src/{chapter_verses → data/chapter_verses}/songs.js +0 -0
  82. /package/src/{chapter_verses → data/chapter_verses}/titus.js +0 -0
  83. /package/src/{chapter_verses → data/chapter_verses}/zechariah.js +0 -0
  84. /package/src/{chapter_verses → data/chapter_verses}/zephaniah.js +0 -0
  85. /package/src/{esv.js → data/esv.js} +0 -0
  86. /package/src/{versifications → data/versifications}/1chronicles.js +0 -0
  87. /package/src/{versifications → data/versifications}/1kings.js +0 -0
  88. /package/src/{versifications → data/versifications}/1samuel.js +0 -0
  89. /package/src/{versifications → data/versifications}/2chronicles.js +0 -0
  90. /package/src/{versifications → data/versifications}/2samuel.js +0 -0
  91. /package/src/{versifications → data/versifications}/daniel.js +0 -0
  92. /package/src/{versifications → data/versifications}/deuteronomy.js +0 -0
  93. /package/src/{versifications → data/versifications}/ecclesiastes.js +0 -0
  94. /package/src/{versifications → data/versifications}/exodus.js +0 -0
  95. /package/src/{versifications → data/versifications}/ezekiel.js +0 -0
  96. /package/src/{versifications → data/versifications}/genesis.js +0 -0
  97. /package/src/{versifications → data/versifications}/hosea.js +0 -0
  98. /package/src/{versifications → data/versifications}/isaiah.js +0 -0
  99. /package/src/{versifications → data/versifications}/jeremiah.js +0 -0
  100. /package/src/{versifications → data/versifications}/job.js +0 -0
  101. /package/src/{versifications → data/versifications}/joel.js +0 -0
  102. /package/src/{versifications → data/versifications}/jonah.js +0 -0
  103. /package/src/{versifications → data/versifications}/joshua.js +0 -0
  104. /package/src/{versifications → data/versifications}/leviticus.js +0 -0
  105. /package/src/{versifications → data/versifications}/malachi.js +0 -0
  106. /package/src/{versifications → data/versifications}/micah.js +0 -0
  107. /package/src/{versifications → data/versifications}/nahum.js +0 -0
  108. /package/src/{versifications → data/versifications}/nehemiah.js +0 -0
  109. /package/src/{versifications → data/versifications}/numbers.js +0 -0
  110. /package/src/{versifications → data/versifications}/proverbs.js +0 -0
  111. /package/src/{versifications → data/versifications}/psalms.js +0 -0
  112. /package/src/{versifications → data/versifications}/song.js +0 -0
  113. /package/src/{versifications → data/versifications}/zechariah.js +0 -0
  114. /package/src/{versified.js → data/versified.js} +0 -0
  115. /package/src/{abbr.js → format/abbr.js} +0 -0
  116. /package/src/{functions.js → utils/functions.js} +0 -0
  117. /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
@@ -0,0 +1,7 @@
1
+ const fs = require("fs")
2
+ const path = require("path")
3
+
4
+ const biblePath = path.join(__dirname, "../../bibles/updated_kjv.json")
5
+ const bible = JSON.parse(fs.readFileSync(biblePath, "utf8"))
6
+
7
+ module.exports.bible = bible
@@ -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
+ }