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.
Files changed (120) hide show
  1. package/.trunk/trunk.yaml +3 -3
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +76 -0
  4. package/REFACTORING.md +214 -0
  5. package/RELEASE_NOTES_v0.2.0.md +5 -0
  6. package/index.js +1 -1
  7. package/package.json +4 -24
  8. package/src/{CodexParser.js → CodexParser.js.backup} +303 -247
  9. package/src/core/CodexParser.js +353 -0
  10. package/src/core/PassageCollection.js +404 -0
  11. package/src/core/PassageValidator.js +107 -0
  12. package/src/core/ReferenceParser.js +638 -0
  13. package/src/core/ScriptureScanner.js +235 -0
  14. package/src/core/VersificationHandler.js +143 -0
  15. package/src/core/VersionHandler.js +77 -0
  16. package/src/data/toc.js +7 -0
  17. package/src/format/osis.js +210 -0
  18. package/src/index.js +12 -0
  19. package/src/utils/PassageUtils.js +117 -0
  20. package/src/utils/chapterVerseCombine.js +65 -0
  21. package/.babelrc +0 -5
  22. package/src/chapterVerseCombine.js +0 -63
  23. package/src/toc.js +0 -2
  24. package/webpack.config.js +0 -38
  25. /package/src/{abbr → data/abbr}/sbl.js +0 -0
  26. /package/src/{bible.js → data/bible.js} +0 -0
  27. /package/src/{chapter_verses → data/chapter_verses}/1chronicles.js +0 -0
  28. /package/src/{chapter_verses → data/chapter_verses}/1corinthians.js +0 -0
  29. /package/src/{chapter_verses → data/chapter_verses}/1john.js +0 -0
  30. /package/src/{chapter_verses → data/chapter_verses}/1kings.js +0 -0
  31. /package/src/{chapter_verses → data/chapter_verses}/1peter.js +0 -0
  32. /package/src/{chapter_verses → data/chapter_verses}/1samuel.js +0 -0
  33. /package/src/{chapter_verses → data/chapter_verses}/1thessalonians.js +0 -0
  34. /package/src/{chapter_verses → data/chapter_verses}/1timothy.js +0 -0
  35. /package/src/{chapter_verses → data/chapter_verses}/2chronicles.js +0 -0
  36. /package/src/{chapter_verses → data/chapter_verses}/2corinthians.js +0 -0
  37. /package/src/{chapter_verses → data/chapter_verses}/2kings.js +0 -0
  38. /package/src/{chapter_verses → data/chapter_verses}/2peter.js +0 -0
  39. /package/src/{chapter_verses → data/chapter_verses}/2samuel.js +0 -0
  40. /package/src/{chapter_verses → data/chapter_verses}/2thessalonians.js +0 -0
  41. /package/src/{chapter_verses → data/chapter_verses}/2timothy.js +0 -0
  42. /package/src/{chapter_verses → data/chapter_verses}/acts.js +0 -0
  43. /package/src/{chapter_verses → data/chapter_verses}/amos.js +0 -0
  44. /package/src/{chapter_verses → data/chapter_verses}/colossians.js +0 -0
  45. /package/src/{chapter_verses → data/chapter_verses}/daniel.js +0 -0
  46. /package/src/{chapter_verses → data/chapter_verses}/deuteronomy.js +0 -0
  47. /package/src/{chapter_verses → data/chapter_verses}/ecclesiastes.js +0 -0
  48. /package/src/{chapter_verses → data/chapter_verses}/ephesians.js +0 -0
  49. /package/src/{chapter_verses → data/chapter_verses}/esther.js +0 -0
  50. /package/src/{chapter_verses → data/chapter_verses}/exodus.js +0 -0
  51. /package/src/{chapter_verses → data/chapter_verses}/ezekiel.js +0 -0
  52. /package/src/{chapter_verses → data/chapter_verses}/ezra.js +0 -0
  53. /package/src/{chapter_verses → data/chapter_verses}/galatians.js +0 -0
  54. /package/src/{chapter_verses → data/chapter_verses}/genesis.js +0 -0
  55. /package/src/{chapter_verses → data/chapter_verses}/habakkuk.js +0 -0
  56. /package/src/{chapter_verses → data/chapter_verses}/haggai.js +0 -0
  57. /package/src/{chapter_verses → data/chapter_verses}/hebrews.js +0 -0
  58. /package/src/{chapter_verses → data/chapter_verses}/hosea.js +0 -0
  59. /package/src/{chapter_verses → data/chapter_verses}/isaiah.js +0 -0
  60. /package/src/{chapter_verses → data/chapter_verses}/james.js +0 -0
  61. /package/src/{chapter_verses → data/chapter_verses}/jeremiah.js +0 -0
  62. /package/src/{chapter_verses → data/chapter_verses}/job.js +0 -0
  63. /package/src/{chapter_verses → data/chapter_verses}/joel.js +0 -0
  64. /package/src/{chapter_verses → data/chapter_verses}/john.js +0 -0
  65. /package/src/{chapter_verses → data/chapter_verses}/jonah.js +0 -0
  66. /package/src/{chapter_verses → data/chapter_verses}/joshua.js +0 -0
  67. /package/src/{chapter_verses → data/chapter_verses}/judges.js +0 -0
  68. /package/src/{chapter_verses → data/chapter_verses}/lamentations.js +0 -0
  69. /package/src/{chapter_verses → data/chapter_verses}/leviticus.js +0 -0
  70. /package/src/{chapter_verses → data/chapter_verses}/luke.js +0 -0
  71. /package/src/{chapter_verses → data/chapter_verses}/malachi.js +0 -0
  72. /package/src/{chapter_verses → data/chapter_verses}/mark.js +0 -0
  73. /package/src/{chapter_verses → data/chapter_verses}/matthew.js +0 -0
  74. /package/src/{chapter_verses → data/chapter_verses}/micah.js +0 -0
  75. /package/src/{chapter_verses → data/chapter_verses}/nahum.js +0 -0
  76. /package/src/{chapter_verses → data/chapter_verses}/nehemiah.js +0 -0
  77. /package/src/{chapter_verses → data/chapter_verses}/numbers.js +0 -0
  78. /package/src/{chapter_verses → data/chapter_verses}/philippians.js +0 -0
  79. /package/src/{chapter_verses → data/chapter_verses}/proverbs.js +0 -0
  80. /package/src/{chapter_verses → data/chapter_verses}/psalms.js +0 -0
  81. /package/src/{chapter_verses → data/chapter_verses}/revelation.js +0 -0
  82. /package/src/{chapter_verses → data/chapter_verses}/romans.js +0 -0
  83. /package/src/{chapter_verses → data/chapter_verses}/ruth.js +0 -0
  84. /package/src/{chapter_verses → data/chapter_verses}/songs.js +0 -0
  85. /package/src/{chapter_verses → data/chapter_verses}/titus.js +0 -0
  86. /package/src/{chapter_verses → data/chapter_verses}/zechariah.js +0 -0
  87. /package/src/{chapter_verses → data/chapter_verses}/zephaniah.js +0 -0
  88. /package/src/{esv.js → data/esv.js} +0 -0
  89. /package/src/{versifications → data/versifications}/1chronicles.js +0 -0
  90. /package/src/{versifications → data/versifications}/1kings.js +0 -0
  91. /package/src/{versifications → data/versifications}/1samuel.js +0 -0
  92. /package/src/{versifications → data/versifications}/2chronicles.js +0 -0
  93. /package/src/{versifications → data/versifications}/2samuel.js +0 -0
  94. /package/src/{versifications → data/versifications}/daniel.js +0 -0
  95. /package/src/{versifications → data/versifications}/deuteronomy.js +0 -0
  96. /package/src/{versifications → data/versifications}/ecclesiastes.js +0 -0
  97. /package/src/{versifications → data/versifications}/exodus.js +0 -0
  98. /package/src/{versifications → data/versifications}/ezekiel.js +0 -0
  99. /package/src/{versifications → data/versifications}/genesis.js +0 -0
  100. /package/src/{versifications → data/versifications}/hosea.js +0 -0
  101. /package/src/{versifications → data/versifications}/isaiah.js +0 -0
  102. /package/src/{versifications → data/versifications}/jeremiah.js +0 -0
  103. /package/src/{versifications → data/versifications}/job.js +0 -0
  104. /package/src/{versifications → data/versifications}/joel.js +0 -0
  105. /package/src/{versifications → data/versifications}/jonah.js +0 -0
  106. /package/src/{versifications → data/versifications}/joshua.js +0 -0
  107. /package/src/{versifications → data/versifications}/leviticus.js +0 -0
  108. /package/src/{versifications → data/versifications}/malachi.js +0 -0
  109. /package/src/{versifications → data/versifications}/micah.js +0 -0
  110. /package/src/{versifications → data/versifications}/nahum.js +0 -0
  111. /package/src/{versifications → data/versifications}/nehemiah.js +0 -0
  112. /package/src/{versifications → data/versifications}/numbers.js +0 -0
  113. /package/src/{versifications → data/versifications}/proverbs.js +0 -0
  114. /package/src/{versifications → data/versifications}/psalms.js +0 -0
  115. /package/src/{versifications → data/versifications}/song.js +0 -0
  116. /package/src/{versifications → data/versifications}/zechariah.js +0 -0
  117. /package/src/{versified.js → data/versified.js} +0 -0
  118. /package/src/{abbr.js → format/abbr.js} +0 -0
  119. /package/src/{functions.js → utils/functions.js} +0 -0
  120. /package/src/{regex.js → utils/regex.js} +0 -0
@@ -1,303 +1,359 @@
1
1
  /**
2
2
  * CodexParser.js
3
- * A class for scanning and parsing scripture references from text, supporting various formats
4
- * (e.g., single verses, ranges, multi-chapter references) with validation and version-specific
5
- * versification. Handles book names, abbreviations, and SBL-style formatting.
3
+ * A modern ES6+ class for scanning and parsing scripture references from text.
4
+ * Supports various formats (single verses, ranges, multi-chapter references) with
5
+ * validation and version-specific versification.
6
6
  */
7
7
 
8
- const versified = require("./versified")
9
- const bible = require("./bible")
10
- const { bookRegex, chapterRegex, verseRegex, scripturesRegex } = require("./regex")
11
- const abbreviations = require("./abbr")
12
- const sblAbbreviations = require("./abbr/sbl")
13
- const dump = require("./functions").dump
14
- const dd = require("./functions").dd
15
- const sch = require("./functions").sch
16
- const chapter_verses = require("./chapterVerseCombine")
8
+ const bible = require("./bible");
9
+ const { bookRegex, chapterRegex, verseRegex, scripturesRegex } = require("./regex");
10
+ const ScriptureScanner = require("./ScriptureScanner");
11
+ const ReferenceParser = require("./ReferenceParser");
12
+ const VersificationHandler = require("./VersificationHandler");
13
+ const PassageCollection = require("./PassageCollection");
14
+ const PassageUtils = require("./PassageUtils");
15
+ const VersionHandler = require("./VersionHandler");
16
+ const chapter_verses = require("./chapterVerseCombine");
17
17
 
18
18
  /**
19
- * Class for parsing and validating scripture references.
19
+ * Main class for parsing and validating scripture references
20
20
  * @class
21
21
  */
22
22
  class CodexParser {
23
+ // Private fields using ES6+ syntax
24
+ #scanner;
25
+ #parser;
26
+ #versificationHandler;
27
+ #config;
28
+ #version;
29
+ #found;
30
+ #passages;
31
+
23
32
  /**
24
- * Initializes the parser with default properties and data.
33
+ * Initializes the parser with configuration
34
+ * @param {Object} config - Configuration options
25
35
  */
26
36
  constructor(config = {}) {
27
- this.found = []
28
- this.passages = []
29
- this.bible = bible
30
- this.bookRegex = bookRegex
31
- this.chapterRegex = chapterRegex
32
- this.verseRegex = verseRegex
33
- this.scripturesRegex = scripturesRegex
34
- this.abbreviations = abbreviations
35
- this.sblAbbreviations = sblAbbreviations
36
- this.versificationDifferences = versified
37
- this.singleChapterBook = [
38
- sch("Jude", 25),
39
- sch("2 John", 13),
40
- sch("3 John", 15),
41
- sch("Obadiah", 21),
42
- sch("Philemon", 25),
43
- ]
44
- this.chapterVerses = chapter_verses
45
- this.error = false
46
- this.version = null
47
- this.SINGLE_CHAPTER = "single_chapter"
48
- this.CHAPTER_VERSE = "chapter_verse"
49
- this.CHAPTER_VERSE_RANGE = "chapter_verse_range"
50
- this.COMMA_SEPARATED = "comma_separated_verses"
51
- this.CHAPTER_RANGE = "chapter_range"
52
- this.MULTI_CHAPTER_RANGE = "multi_chapter_verse_range"
53
- this.config = {
37
+ this.#config = {
54
38
  booksOnly: config.booksOnly ?? false,
55
39
  invalid_sequence_strategy: config.invalid_sequence_strategy ?? "include",
56
40
  invalid_passage_strategy: config.invalid_passage_strategy ?? "include",
57
- }
41
+ };
42
+
43
+ this.#scanner = new ScriptureScanner(this.#config);
44
+ this.#parser = new ReferenceParser(this.#config);
45
+ this.#versificationHandler = new VersificationHandler();
46
+ this.#version = null;
47
+ this.#found = [];
48
+ this.#passages = [];
49
+
50
+ // Legacy public properties for backward compatibility
51
+ this.bible = bible;
52
+ this.bookRegex = bookRegex;
53
+ this.chapterRegex = chapterRegex;
54
+ this.verseRegex = verseRegex;
55
+ this.scripturesRegex = scripturesRegex;
56
+ this.chapterVerses = chapter_verses;
57
+ this.error = false;
58
+
59
+ // Legacy constants
60
+ this.SINGLE_CHAPTER = "single_chapter";
61
+ this.CHAPTER_VERSE = "chapter_verse";
62
+ this.CHAPTER_VERSE_RANGE = "chapter_verse_range";
63
+ this.COMMA_SEPARATED = "comma_separated_verses";
64
+ this.CHAPTER_RANGE = "chapter_range";
65
+ this.MULTI_CHAPTER_RANGE = "multi_chapter_verse_range";
66
+ }
67
+
68
+ /**
69
+ * Gets the found references (legacy getter)
70
+ */
71
+ get found() {
72
+ return this.#found;
73
+ }
74
+
75
+ /**
76
+ * Sets the found references (legacy setter)
77
+ */
78
+ set found(value) {
79
+ this.#found = value;
80
+ }
81
+
82
+ /**
83
+ * Gets the parsed passages (legacy getter)
84
+ */
85
+ get passages() {
86
+ return this.#passages;
87
+ }
88
+
89
+ /**
90
+ * Sets the parsed passages (legacy setter)
91
+ */
92
+ set passages(value) {
93
+ this.#passages = value;
58
94
  }
59
95
 
60
96
  /**
61
- * Sets configuration options for the parser.
62
- * @param {Object} config - Configuration options.
63
- * @param {boolean} [config.booksOnly=false] - Whether to capture book names without references.
64
- * @returns {CodexParser} The parser instance for method chaining.
97
+ * Gets the current version (legacy getter)
98
+ */
99
+ get version() {
100
+ return this.#version;
101
+ }
102
+
103
+ /**
104
+ * Sets the current version (legacy setter)
105
+ */
106
+ set version(value) {
107
+ this.#version = value;
108
+ }
109
+
110
+ /**
111
+ * Gets the current config (legacy getter)
112
+ */
113
+ get config() {
114
+ return this.#config;
115
+ }
116
+
117
+ /**
118
+ * Sets the current config (legacy setter)
119
+ */
120
+ set config(value) {
121
+ this.#config = value;
122
+ }
123
+
124
+ /**
125
+ * Sets configuration options for the parser
126
+ * @param {Object} config - Configuration options
127
+ * @param {boolean} [config.booksOnly=false] - Whether to capture book names without references
128
+ * @returns {CodexParser} The parser instance for method chaining
65
129
  */
66
130
  options(config) {
67
- this.config = {
68
- booksOnly: config.booksOnly ?? false,
69
- invalid_sequence_strategy: config.invalid_sequence_strategy ?? this.config.invalid_sequence_strategy,
70
- invalid_passage_strategy: config.invalid_passage_strategy ?? this.config.invalid_passage_strategy,
71
- }
72
- return this
131
+ this.#config = {
132
+ booksOnly: config.booksOnly ?? this.#config.booksOnly,
133
+ invalid_sequence_strategy: config.invalid_sequence_strategy ?? this.#config.invalid_sequence_strategy,
134
+ invalid_passage_strategy: config.invalid_passage_strategy ?? this.#config.invalid_passage_strategy,
135
+ };
136
+
137
+ // Update scanner and parser configs
138
+ this.#scanner = new ScriptureScanner(this.#config);
139
+ this.#parser = new ReferenceParser(this.#config);
140
+
141
+ return this;
73
142
  }
74
143
 
75
144
  /**
76
- * Retrieves available verses for a given book and chapter.
77
- * @param {string} book - The book name (e.g., "Genesis").
78
- * @param {number} chapter - The chapter number.
79
- * @returns {number[]} Array of valid verse numbers.
145
+ * Retrieves available verses for a given book and chapter (legacy method)
146
+ * @param {string} book - The book name
147
+ * @param {number} chapter - The chapter number
148
+ * @returns {number[]} Array of valid verse numbers
80
149
  */
81
150
  getChapterVerses(book, chapter) {
82
- const singleChapterBook = this.singleChapterBook.find((b) => Object.keys(b)[0] === book)
83
- return singleChapterBook ? singleChapterBook[book][chapter] || [] : this.chapterVerses[book]?.[chapter] || []
151
+ return PassageUtils.getChapterVerses(book, chapter);
84
152
  }
85
153
 
86
154
  /**
87
- * Scans text for scripture references and stores them in `this.found`.
88
- * @param {string} text - The text to scan.
89
- * @returns {CodexParser} The parser instance for method chaining.
155
+ * Scans text for scripture references
156
+ * @param {string} text - The text to scan
157
+ * @returns {CodexParser} The parser instance for method chaining
90
158
  */
91
159
  scan(text) {
92
- const fullNames = [...this.bible.old, ...this.bible.new]
93
- const abbreviations = Object.keys(this.abbreviations)
94
- this.found = []
95
- // Minimal normalization: fix periods before numbers, remove trailing periods
96
- let normalizedText = text.replace(/\.(?=\d)/g, ":").replace(/(\b[A-Za-z]+)\.(?=\s|$)/g, "$1")
97
- const lowercaseBibleFullNames = fullNames.map((book) => book.toLowerCase())
98
- const lowercaseBibleAbbreviations = abbreviations.map((abbr) => abbr.toLowerCase())
99
- const lowerCaseText = normalizedText.toLowerCase()
100
- let i = 0
101
-
102
- const isValidChapterVerseChar = (char) => /[\d:,\-;\s]/.test(char)
103
- const isNextBibleBook = (startIndex) => {
104
- const textAfterCurrentPosition = lowerCaseText.substring(startIndex).trim()
105
- return (
106
- lowercaseBibleFullNames.some((book) => textAfterCurrentPosition.startsWith(book)) ||
107
- lowercaseBibleAbbreviations.some((abbr) => textAfterCurrentPosition.startsWith(abbr))
108
- )
109
- }
110
- const detectSuffix = (startIndex) => {
111
- const suffixMatch = normalizedText.substring(startIndex).match(/\b(LXX|MT)\b/i)
112
- return suffixMatch ? { suffix: suffixMatch[0].toUpperCase(), length: suffixMatch[0].length } : null
113
- }
114
-
115
- while (i < lowerCaseText.length) {
116
- let foundBook = null
117
- let bookStartIndex = -1
118
- let matchedLength = 0
160
+ this.#found = this.#scanner.scan(text);
161
+ return this;
162
+ }
119
163
 
120
- // Skip whitespace and special characters before checking for book
121
- while (i < lowerCaseText.length && /[\s—-]/.test(lowerCaseText[i])) {
122
- i++
123
- }
124
- if (i >= lowerCaseText.length) break
125
-
126
- for (let j = 0; j < lowercaseBibleFullNames.length; j++) {
127
- const book = lowercaseBibleFullNames[j]
128
- if (lowerCaseText.startsWith(book, i) && book.length > matchedLength) {
129
- foundBook = fullNames[j]
130
- bookStartIndex = i
131
- matchedLength = book.length
132
- }
133
- }
164
+ /**
165
+ * Sets the Bible version for parsing
166
+ * @param {string} version - The version (e.g., "lxx", "mt", "eng")
167
+ * @returns {CodexParser} The parser instance
168
+ */
169
+ bibleVersion(version) {
170
+ this.#version = VersionHandler.normalizeVersion(version);
171
+ return this;
172
+ }
134
173
 
135
- if (!foundBook) {
136
- for (let k = 0; k < lowercaseBibleAbbreviations.length; k++) {
137
- const abbreviation = lowercaseBibleAbbreviations[k]
138
- if (lowerCaseText.startsWith(abbreviation, i) && abbreviation.length > matchedLength) {
139
- foundBook = this.abbreviations[abbreviations[k]]
140
- bookStartIndex = i
141
- matchedLength = abbreviation.length
142
- }
143
- }
144
- }
174
+ /**
175
+ * Parses a scripture reference into structured passage objects
176
+ * @param {string} reference - The reference to parse (e.g., "John 3:16")
177
+ * @returns {CodexParser} The parser instance
178
+ */
179
+ parse(reference) {
180
+ this.scan(reference);
181
+ this.#passages = this.#parser.parse(this.#found, this.#version);
182
+ this.#passages = this.#versificationHandler.applyVersification(this.#passages);
183
+ return this;
184
+ }
145
185
 
146
- if (foundBook) {
147
- i += matchedLength
148
- let chapterVerse = ""
149
- const references = []
150
- let refStartIndex = bookStartIndex
151
- let originalRefStartIndex = bookStartIndex
186
+ /**
187
+ * Returns parsed passages with utility methods
188
+ * @returns {PassageCollection} Collection of passages with utility methods
189
+ */
190
+ getPassages() {
191
+ return PassageCollection.from(this.#passages);
192
+ }
152
193
 
153
- while (i < normalizedText.length && isValidChapterVerseChar(normalizedText[i])) {
154
- if (isNextBibleBook(i)) {
155
- break
156
- }
157
- if (normalizedText[i] === ";") {
158
- const formattedReference = chapterVerse.trim()
159
- if (formattedReference) {
160
- const refEndIndex = i
161
- references.push({
162
- ref: formattedReference,
163
- start: refStartIndex,
164
- end: refEndIndex,
165
- })
166
- }
167
- chapterVerse = ""
168
- refStartIndex = i + 1
169
- const semicolonIndex = text.indexOf(";", originalRefStartIndex)
170
- originalRefStartIndex = semicolonIndex !== -1 ? semicolonIndex + 1 : refStartIndex
171
- i++
172
- continue
173
- }
174
- chapterVerse += normalizedText[i]
175
- i++
176
- }
194
+ /**
195
+ * Returns the first parsed passage
196
+ * @returns {Object|null} The first passage or null
197
+ */
198
+ first() {
199
+ return this.#passages.length > 0 ? this.#passages[0] : null;
200
+ }
177
201
 
178
- if (chapterVerse.trim().length > 0) {
179
- const formattedReference = chapterVerse.trim()
180
- if (formattedReference) {
181
- const refEndIndex = i
182
- references.push({
183
- ref: formattedReference,
184
- start: refStartIndex,
185
- end: refEndIndex,
186
- })
187
- }
188
- }
202
+ /**
203
+ * Normalizes book names using abbreviations or full names (legacy method)
204
+ * @param {string|Array} book - The book name or array
205
+ * @returns {string} Normalized book name
206
+ */
207
+ bookify(book) {
208
+ return this.#parser.constructor.prototype.normalizeBookName?.(book) || book;
209
+ }
189
210
 
190
- // Align indices with original text
191
- const originalBookText = text.slice(bookStartIndex, bookStartIndex + matchedLength)
192
- const originalBookStartIndex =
193
- text.indexOf(originalBookText, bookStartIndex) !== -1
194
- ? text.indexOf(originalBookText, bookStartIndex)
195
- : bookStartIndex
196
-
197
- references.forEach(({ ref, start, end }) => {
198
- let type
199
- if (ref.includes(":")) {
200
- if (ref.includes("-")) {
201
- const [start, end] = ref.split("-")
202
- const startParts = start.split(":")
203
- const endParts = end.split(":")
204
- type =
205
- startParts.length > 1 &&
206
- endParts.length > 1 &&
207
- startParts[0].trim() !== endParts[0].trim()
208
- ? "multi_chapter_verse_range"
209
- : "chapter_verse_range"
210
- } else if (ref.includes(",")) {
211
- type = "comma_separated_verses"
212
- } else {
213
- type = "chapter_verse"
214
- }
215
- } else if (ref.includes("-")) {
216
- type = "chapter_range"
217
- } else {
218
- type = "single_chapter"
219
- }
211
+ /**
212
+ * Combines multiple passages into a single reference
213
+ * @param {Object[]} [passages=this.passages] - Array of passages to combine
214
+ * @returns {Object} Combined passage object
215
+ */
216
+ combine(passages = this.#passages) {
217
+ return PassageCollection.combinePassages(passages);
218
+ }
220
219
 
221
- // Construct full reference text for original text
222
- const fullRefText = `${originalBookText} ${ref.replace(":", ".")}`
223
- const suffixData = detectSuffix(end)
224
- const suffix = suffixData ? suffixData.suffix : null
225
- let refEndIndex = end
226
- if (suffixData) {
227
- refEndIndex += suffixData.length
228
- i += suffixData.length // Skip suffix
229
- }
220
+ /**
221
+ * Merges verses into ranges or comma-separated lists (legacy method)
222
+ * @param {number[]} verses - Array of verse numbers
223
+ * @returns {string[]} Array of verse strings
224
+ */
225
+ mergeRanges(verses) {
226
+ return PassageUtils.mergeRanges(verses);
227
+ }
230
228
 
231
- // Map to original text
232
- let originalStartIndex =
233
- text.indexOf(fullRefText, originalRefStartIndex) !== -1
234
- ? text.indexOf(fullRefText, originalRefStartIndex)
235
- : originalBookStartIndex
229
+ /**
230
+ * Generates a table of contents for the Bible
231
+ * @param {string} [version="ESV"] - The Bible version
232
+ * @returns {Object} TOC with book-chapter-verse mappings
233
+ */
234
+ getToc(version = "ESV") {
235
+ const toc = {};
236
+
237
+ bible.old.forEach((book) => {
238
+ if (chapter_verses[book]) {
239
+ toc[book] = chapter_verses[book];
240
+ }
241
+ });
242
+
243
+ bible.new.forEach((book) => {
244
+ if (chapter_verses[book]) {
245
+ toc[book] = chapter_verses[book];
246
+ }
247
+ });
248
+
249
+ PassageUtils.SINGLE_CHAPTER_BOOKS.forEach((item) => {
250
+ Object.keys(item).forEach((book) => {
251
+ if (!toc[book]) {
252
+ toc[book] = item[book];
253
+ }
254
+ });
255
+ });
256
+
257
+ const orderedToc = {};
258
+ const canonicalOrder = [...bible.old, ...bible.new];
259
+ canonicalOrder.forEach((book) => {
260
+ if (toc[book]) {
261
+ orderedToc[book] = toc[book];
262
+ }
263
+ });
264
+
265
+ return orderedToc;
266
+ }
236
267
 
237
- let originalEndIndex = originalStartIndex + fullRefText.length
238
- let originalText = text.slice(originalStartIndex, originalEndIndex)
268
+ /**
269
+ * Replaces scripture references in text with formatted references
270
+ * @param {string} text - The original text
271
+ * @param {boolean} useAbbreviations - Whether to use abbreviated book names
272
+ * @returns {string} Text with replaced references
273
+ */
274
+ replace(text, useAbbreviations = true) {
275
+ if (!this.#passages.length) {
276
+ return text;
277
+ }
239
278
 
240
- // Adjust for suffix in original text
241
- if (suffixData) {
242
- originalEndIndex += suffixData.length
243
- originalText = text.slice(originalStartIndex, originalEndIndex)
244
- }
279
+ let result = text;
280
+ for (let i = this.#passages.length - 1; i >= 0; i--) {
281
+ const passage = this.#passages[i];
282
+ const { originalText, abbr, original } = passage;
283
+ const newReference = useAbbreviations ? abbr : original;
245
284
 
246
- // Trim trailing whitespace from originalText
247
- while (originalEndIndex > originalStartIndex && /[\s]/.test(text[originalEndIndex - 1])) {
248
- originalEndIndex--
249
- originalText = text.slice(originalStartIndex, originalEndIndex)
250
- }
285
+ const regex = new RegExp(`${originalText.replace(/([.*+?^${}()|[\]\\])/g, "\\$1")}`, "g");
251
286
 
252
- this.found.push({
253
- book: foundBook,
254
- reference: ref,
255
- startIndex: originalStartIndex,
256
- endIndex: originalEndIndex,
257
- version: suffix || null,
258
- type,
259
- originalText: originalText,
260
- })
261
- })
262
- } else {
263
- i++
287
+ const matches = [...result.matchAll(regex)];
288
+ if (matches.length > 0) {
289
+ for (let j = matches.length - 1; j >= 0; j--) {
290
+ const match = matches[j];
291
+ const startIndex = match.index;
292
+ const endIndex = startIndex + match[0].length;
293
+ const leadingSpace = match[1] || "";
294
+ const hasOpeningParen = match[2] === "(";
295
+ const hasClosingParen = match[3] === ")";
296
+ const trailingSpace = match[4] || " ";
297
+ const replacement =
298
+ hasOpeningParen && hasClosingParen
299
+ ? `${leadingSpace}(${newReference})${trailingSpace}`
300
+ : `${leadingSpace}${newReference}${trailingSpace}`;
301
+ result = result.slice(0, startIndex) + replacement + result.slice(endIndex);
302
+ }
264
303
  }
265
304
  }
266
305
 
267
- return this
306
+ return result;
268
307
  }
269
308
 
270
309
  /**
271
- * Sets the Bible version for parsing.
272
- * @param {string} version - The version (e.g., "lxx", "mt", "eng").
273
- * @returns {CodexParser} The parser instance.
310
+ * Checks if all references in the passages array are from the same book
311
+ * @returns {boolean} True if all passages are from the same book
274
312
  */
275
- bibleVersion(version) {
276
- const lowerVersion = version.toLowerCase()
277
- this.version =
278
- lowerVersion === "lxx" || lowerVersion === "eng" || lowerVersion === "bhs" || lowerVersion === "mt"
279
- ? lowerVersion
280
- : null
281
- return this
313
+ same() {
314
+ if (this.#passages.length <= 1) {
315
+ return true;
316
+ }
317
+
318
+ const firstBook = this.#passages[0].book.toLowerCase();
319
+ return this.#passages.every((passage) => passage.book.toLowerCase() === firstBook);
282
320
  }
283
321
 
322
+ // Legacy methods for backward compatibility
323
+
284
324
  /**
285
- * Parses a scripture reference into structured passage objects.
286
- * @param {string} reference - The reference to parse (e.g., "John 3:16").
287
- * @returns {CodexParser} The parser instance.
325
+ * Expands verse references into individual verse objects (legacy)
288
326
  */
289
- parse(reference) {
290
- this.scan(reference)
291
-
292
- this.passages = this.found.map((passage) => {
293
- const book = this.bookify(passage.book)
294
- const testament = this.bible.old.includes(book) ? "old" : "new"
295
- const parsedPassage = {
296
- original: `${passage.book} ${passage.reference}`,
297
- book,
298
- chapter: null,
299
- verses: [],
300
- type: passage.type,
327
+ expandVerses(book, chapter, verses) {
328
+ return PassageUtils.expandVerses(book, chapter, verses);
329
+ }
330
+
331
+ /**
332
+ * Populates passage with expanded verse objects (legacy)
333
+ */
334
+ populate(passage) {
335
+ return this.#parser.constructor.prototype._populatePassage?.(passage) || [];
336
+ }
337
+
338
+ /**
339
+ * Formats a passage into a human-readable reference (legacy)
340
+ */
341
+ scripturize(passage) {
342
+ return this.#parser.constructor.prototype._formatScripture?.(passage) || {};
343
+ }
344
+
345
+ /**
346
+ * Applies versification differences to parsed passages (legacy)
347
+ */
348
+ versification() {
349
+ this.#passages = this.#versificationHandler.applyVersification(this.#passages);
350
+ }
351
+ }
352
+
353
+ module.exports = CodexParser;
354
+
355
+
356
+
301
357
  testament,
302
358
  startIndex: passage.startIndex,
303
359
  endIndex: passage.endIndex,