codexparser 0.1.55 → 0.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -0
- package/package.json +1 -1
- package/src/CodexParser.js +355 -436
- package/src/esv.js +2 -1
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# CodexParser: The Ultimate Bible Reference Parser 📖✨
|
|
2
|
+
|
|
3
|
+
Welcome to **CodexParser**, a powerful and flexible Node.js library crafted to parse, validate, and structure Bible references with ease. Whether you're extracting verses from a sermon, building a scripture app, or analyzing biblical texts, CodexParser transforms raw references like "John 3:16" or "Genesis 1:1-5, 10" into rich, actionable data—complete with start and end points, versification support, and more. Dive into the Word like never before!
|
|
4
|
+
|
|
5
|
+
Built with precision and passion, CodexParser handles single verses, ranges, multi-chapter spans, and even single-chapter books (looking at you, Jude!). It’s your trusty companion for navigating the sacred texts, supporting English, Septuagint (LXX), and Masoretic Text (MT) versions. Let’s unleash its power!
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features 🌟
|
|
10
|
+
|
|
11
|
+
- **Parse Any Reference**: From "Jn 3:16" to "Exodus 20:1-17; 21:1-5", it’s got you covered.
|
|
12
|
+
- **Structured Output**: Get book, chapter, verse, testament, start/end points, and more in a clean object.
|
|
13
|
+
- **Versification Support**: Handles differences between English, LXX, and MT texts.
|
|
14
|
+
- **Validation**: Ensures references are legit—no more phantom verses!
|
|
15
|
+
- **Combine Passages**: Merge multiple references into a single, cohesive range.
|
|
16
|
+
- **Chainable API**: Fluent, intuitive method chaining for a smooth workflow.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Installation 🚀
|
|
21
|
+
|
|
22
|
+
Grab CodexParser via npm and start parsing scripture in minutes:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install codexparser
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or clone it from GitHub and dive into the source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/your-username/CodexParser.git
|
|
32
|
+
cd CodexParser
|
|
33
|
+
npm install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Start ⚡
|
|
39
|
+
|
|
40
|
+
Here’s how to wield CodexParser’s might:
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
const CodexParser = require("codex-parser")
|
|
44
|
+
|
|
45
|
+
const parser = new CodexParser()
|
|
46
|
+
|
|
47
|
+
// Parse a simple reference
|
|
48
|
+
parser.parse("John 3:16")
|
|
49
|
+
console.log(parser.getPassages().first())
|
|
50
|
+
// Output: {
|
|
51
|
+
// original: "John 3:16",
|
|
52
|
+
// book: "John",
|
|
53
|
+
// chapter: 3,
|
|
54
|
+
// verses: [16],
|
|
55
|
+
// type: "chapter_verse",
|
|
56
|
+
// testament: "new",
|
|
57
|
+
// passages: [{ book: "John", chapter: 3, verse: 16 }],
|
|
58
|
+
// scripture: { passage: "John 3:16", cv: "3:16", hash: "john_3.16" },
|
|
59
|
+
// start: { book: "John", chapter: 3, verse: 16 },
|
|
60
|
+
// end: { book: "John", chapter: 3, verse: 16 },
|
|
61
|
+
// valid: true,
|
|
62
|
+
// version: { name: "English", value: "ENG", abbreviation: "eng" }
|
|
63
|
+
// }
|
|
64
|
+
|
|
65
|
+
// Chain it up!
|
|
66
|
+
console.log(parser.parse("Genesis 1:1-5, 10; 2:1-3").getPassages().combine())
|
|
67
|
+
// Combines into a single passage with start/end spanning the range!
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## API: Your Codex Arsenal 🛠️
|
|
73
|
+
|
|
74
|
+
Here’s the breakdown of CodexParser’s key methods—your tools for mastering scripture:
|
|
75
|
+
|
|
76
|
+
### `new CodexParser()`
|
|
77
|
+
|
|
78
|
+
- **What it does**: Creates a new parser instance, ready to tackle any reference.
|
|
79
|
+
- **Usage**: `const parser = new CodexParser();`
|
|
80
|
+
|
|
81
|
+
### `.scan(text)`
|
|
82
|
+
|
|
83
|
+
- **What it does**: Scans a string for Bible references, storing raw matches in `this.found`. It’s the first step in parsing—think of it as your scripture radar.
|
|
84
|
+
- **Args**: `text` (string) - The text to search (e.g., "Preaching from Jn 3:16 today").
|
|
85
|
+
- **Returns**: The parser instance for chaining.
|
|
86
|
+
- **Example**: `parser.scan("Jn 3:16; Gen 1:1");`
|
|
87
|
+
|
|
88
|
+
### `.parse(reference)`
|
|
89
|
+
|
|
90
|
+
- **What it does**: Takes a reference string, scans it, and builds structured passage objects with `start`, `end`, `passages`, and more. This is your main parsing powerhouse.
|
|
91
|
+
- **Args**: `reference` (string) - The Bible reference (e.g., "John 3:16-18").
|
|
92
|
+
- **Returns**: The parser instance for chaining.
|
|
93
|
+
- **Example**: `parser.parse("Exodus 20:1-5").getPassages();`
|
|
94
|
+
|
|
95
|
+
### `.bibleVersion(version)`
|
|
96
|
+
|
|
97
|
+
- **What it does**: Sets the Bible version (e.g., "lxx", "mt", "bhs") to adjust versification. Great for Old Testament nerds!
|
|
98
|
+
- **Args**: `version` (string) - Version code ("lxx", "mt", "bhs", etc.).
|
|
99
|
+
- **Returns**: The parser instance for chaining.
|
|
100
|
+
- **Example**: `parser.bibleVersion("lxx").parse("Psalm 23:1");`
|
|
101
|
+
|
|
102
|
+
### `.getPassages()`
|
|
103
|
+
|
|
104
|
+
- **What it does**: Returns an array of parsed passage objects with handy methods like `.first()`, `.oldTestament()`, `.newTestament()`, and `.combine()`.
|
|
105
|
+
- **Returns**: Array of passage objects with extra methods.
|
|
106
|
+
- **Example**: `parser.parse("Matt 5:3-5").getPassages();`
|
|
107
|
+
|
|
108
|
+
### `.first()`
|
|
109
|
+
|
|
110
|
+
- **What it does**: Grabs the first parsed passage—perfect for single-reference parsing.
|
|
111
|
+
- **Returns**: The first passage object or `null` if none exist.
|
|
112
|
+
- **Example**: `parser.parse("Luke 2:1").first();`
|
|
113
|
+
|
|
114
|
+
### `.combine(passages)`
|
|
115
|
+
|
|
116
|
+
- **What it does**: Merges multiple passages from the same book into a single passage, calculating a unified range with `start` and `end`. Ideal for consolidating overlapping references.
|
|
117
|
+
- **Args**: `passages` (array) - Array of passage objects to combine.
|
|
118
|
+
- **Returns**: A combined passage object.
|
|
119
|
+
- **Example**:
|
|
120
|
+
```javascript
|
|
121
|
+
const passages = parser.parse("John 3:16, 3:17-18").getPassages()
|
|
122
|
+
const combined = parser.combine(passages)
|
|
123
|
+
// Result: A single "John 3:16-18" passage
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `.getToc(version)`
|
|
127
|
+
|
|
128
|
+
- **What it does**: Generates a table of contents with books and their chapter/verse counts. Useful for reference or validation.
|
|
129
|
+
- **Args**: `version` (string, optional) - Bible version (defaults to "ESV").
|
|
130
|
+
- **Returns**: Object mapping books to chapter/verse data.
|
|
131
|
+
- **Example**: `console.log(parser.getToc());`
|
|
132
|
+
|
|
133
|
+
### Passage Object Structure
|
|
134
|
+
|
|
135
|
+
Each parsed passage looks like this:
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
{
|
|
139
|
+
original: "John 3:16-18", // Original input
|
|
140
|
+
book: "John", // Full book name
|
|
141
|
+
chapter: 3, // Starting chapter
|
|
142
|
+
verses: ["16-18"], // Verse range or list
|
|
143
|
+
type: "chapter_verse_range", // Reference type
|
|
144
|
+
testament: "new", // Old or New Testament
|
|
145
|
+
index: 0, // Position in text
|
|
146
|
+
version: { name: "English", value: "ENG", abbreviation: "eng" }, // Version info
|
|
147
|
+
passages: [{ book: "John", chapter: 3, verse: 16 }, ...], // Expanded verses
|
|
148
|
+
scripture: { passage: "John 3:16-18", cv: "3:16-18", hash: "john_3.16.18" }, // Formatted output
|
|
149
|
+
valid: true, // Validation status
|
|
150
|
+
start: { book: "John", chapter: 3, verse: 16 }, // First verse
|
|
151
|
+
end: { book: "John", chapter: 3, verse: 18 } // Last verse
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Supported Reference Types 📜
|
|
158
|
+
|
|
159
|
+
- **Single Chapter**: `Jude 1` (whole chapter of a single-chapter book).
|
|
160
|
+
- **Chapter Verse**: `John 3:16` (one verse).
|
|
161
|
+
- **Chapter Verse Range**: `Genesis 1:1-5` (verse range in one chapter).
|
|
162
|
+
- **Comma Separated Verses**: `Matthew 5:3, 5, 7` (multiple verses in one chapter).
|
|
163
|
+
- **Chapter Range**: `Exodus 20-22` (full chapters).
|
|
164
|
+
- **Multi-Chapter Verse Range**: `Psalm 119:1-120:5` (spans chapters).
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Contributing 🙌
|
|
169
|
+
|
|
170
|
+
Want to enhance CodexParser? Fork it, tweak it, and send a pull request! Issues and ideas are welcome on the [GitHub Issues page](https://github.com/jeremyam/CodexParser/issues).
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## License ⚖️
|
|
175
|
+
|
|
176
|
+
[MIT License](LICENSE) - Free to use, modify, and share. Spread the Word!
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Acknowledgements 🌍
|
|
181
|
+
|
|
182
|
+
Built with love by [jeremyam], powered by coffee and scripture.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
Let’s parse the scriptures together—happy coding! ✝️📚
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexparser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
4
4
|
"description": "This is a Javascript Bible parser and text scanner. It will search through texts and collate all scripture references into an array and parse them into objects, and it will parse passages into objects by book, chapter, verse, and testament. ",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/src/CodexParser.js
CHANGED
|
@@ -25,43 +25,36 @@ class CodexParser {
|
|
|
25
25
|
sch("Obadiah", 21),
|
|
26
26
|
sch("Philemon", 25),
|
|
27
27
|
]
|
|
28
|
-
|
|
29
28
|
this.chapterVerses = chapter_verses
|
|
30
29
|
this.error = false
|
|
31
30
|
this.version = null
|
|
31
|
+
this.SINGLE_CHAPTER = "single_chapter"
|
|
32
|
+
this.CHAPTER_VERSE = "chapter_verse"
|
|
33
|
+
this.CHAPTER_VERSE_RANGE = "chapter_verse_range"
|
|
34
|
+
this.COMMA_SEPARATED = "comma_separated_verses"
|
|
35
|
+
this.CHAPTER_RANGE = "chapter_range"
|
|
36
|
+
this.MULTI_CHAPTER_RANGE = "multi_chapter_verse_range"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getChapterVerses(book, chapter) {
|
|
40
|
+
const singleChapterBook = this.singleChapterBook.find((b) => Object.keys(b)[0] === book)
|
|
41
|
+
return singleChapterBook ? singleChapterBook[book][chapter] || [] : this.chapterVerses[book]?.[chapter] || []
|
|
32
42
|
}
|
|
33
43
|
|
|
34
|
-
/**
|
|
35
|
-
* Scans the given text for Bible references and stores all found references in the `found` property of the instance.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} text - The text to scan for Bible references.
|
|
38
|
-
* @return {CodexParser} - Returns the instance itself, enabling method chaining.
|
|
39
|
-
*/
|
|
40
44
|
scan(text) {
|
|
41
|
-
// Combine Old and New Testament book names into a single array
|
|
42
45
|
const fullNames = [...this.bible.old, ...this.bible.new]
|
|
43
|
-
|
|
44
|
-
// Retrieve all abbreviation keys from the abbreviations object
|
|
45
46
|
const abbreviations = Object.keys(this.abbreviations)
|
|
46
|
-
|
|
47
|
-
// Initialize the `found` array to store the results
|
|
48
47
|
this.found = []
|
|
49
|
-
|
|
50
|
-
// Preprocess input text: normalize separators while preserving abbreviations
|
|
51
48
|
let normalizedText = text
|
|
52
|
-
.replace(/\.(?=\d)/g, ":")
|
|
53
|
-
.replace(/(\b[A-Za-z]+)\.(?=\s|$)/g, "$1")
|
|
54
|
-
.replace(/\s+/g, " ")
|
|
55
|
-
|
|
56
|
-
// Convert Bible book names, abbreviations, and input text to lowercase for case-insensitive matching
|
|
49
|
+
.replace(/\.(?=\d)/g, ":")
|
|
50
|
+
.replace(/(\b[A-Za-z]+)\.(?=\s|$)/g, "$1")
|
|
51
|
+
.replace(/\s+/g, " ")
|
|
57
52
|
const lowercaseBibleFullNames = fullNames.map((book) => book.toLowerCase())
|
|
58
53
|
const lowercaseBibleAbbreviations = abbreviations.map((abbr) => abbr.toLowerCase())
|
|
59
54
|
const lowerCaseText = normalizedText.toLowerCase()
|
|
60
|
-
|
|
61
55
|
let i = 0
|
|
62
56
|
|
|
63
57
|
const isValidChapterVerseChar = (char) => /[^A-Za-z]/.test(char)
|
|
64
|
-
|
|
65
58
|
const isNextBibleBook = (startIndex) => {
|
|
66
59
|
const textAfterCurrentPosition = lowerCaseText.substring(startIndex).trim()
|
|
67
60
|
return (
|
|
@@ -69,7 +62,6 @@ class CodexParser {
|
|
|
69
62
|
lowercaseBibleAbbreviations.some((abbr) => textAfterCurrentPosition.startsWith(abbr))
|
|
70
63
|
)
|
|
71
64
|
}
|
|
72
|
-
|
|
73
65
|
const detectSuffix = (startIndex) => {
|
|
74
66
|
const suffixMatch = normalizedText.substring(startIndex).match(/\b(LXX|MT)\b/i)
|
|
75
67
|
return suffixMatch ? suffixMatch[0].toUpperCase() : null
|
|
@@ -107,7 +99,6 @@ class CodexParser {
|
|
|
107
99
|
|
|
108
100
|
while (i < normalizedText.length && isValidChapterVerseChar(normalizedText[i])) {
|
|
109
101
|
if (isNextBibleBook(i)) break
|
|
110
|
-
|
|
111
102
|
if (normalizedText[i] === ";") {
|
|
112
103
|
const formattedReference = chapterVerse.trim().replace(/[^a-zA-Z0-9]+$/, "")
|
|
113
104
|
if (formattedReference) references.push(formattedReference)
|
|
@@ -115,7 +106,6 @@ class CodexParser {
|
|
|
115
106
|
i++
|
|
116
107
|
continue
|
|
117
108
|
}
|
|
118
|
-
|
|
119
109
|
chapterVerse += normalizedText[i]
|
|
120
110
|
i++
|
|
121
111
|
}
|
|
@@ -134,14 +124,12 @@ class CodexParser {
|
|
|
134
124
|
const [start, end] = ref.split("-")
|
|
135
125
|
const startParts = start.split(":")
|
|
136
126
|
const endParts = end.split(":")
|
|
137
|
-
|
|
138
|
-
// Determine type based on the chapter (startParts[0] and endParts[0])
|
|
139
127
|
type =
|
|
140
128
|
startParts.length > 1 &&
|
|
141
129
|
endParts.length > 1 &&
|
|
142
130
|
startParts[0].trim() !== endParts[0].trim()
|
|
143
|
-
? "multi_chapter_verse_range"
|
|
144
|
-
: "chapter_verse_range"
|
|
131
|
+
? "multi_chapter_verse_range"
|
|
132
|
+
: "chapter_verse_range"
|
|
145
133
|
} else if (ref.includes(",")) {
|
|
146
134
|
type = "comma_separated_verses"
|
|
147
135
|
} else {
|
|
@@ -178,129 +166,69 @@ class CodexParser {
|
|
|
178
166
|
return this
|
|
179
167
|
}
|
|
180
168
|
|
|
181
|
-
/**
|
|
182
|
-
* Parses a given reference and returns an object with the parsed passage,
|
|
183
|
-
* including book, chapter, verse, type, testament, index, and version.
|
|
184
|
-
*
|
|
185
|
-
* @param {string} reference - The reference to parse.
|
|
186
|
-
* @returns {object} An object with the parsed passage.
|
|
187
|
-
*/
|
|
188
169
|
parse(reference) {
|
|
189
|
-
this.scan(reference)
|
|
170
|
+
this.scan(reference)
|
|
190
171
|
|
|
191
172
|
this.passages = this.found.map((passage) => {
|
|
192
173
|
const book = this.bookify(passage.book)
|
|
193
174
|
const testament = this.bible.old.includes(book) ? "old" : "new"
|
|
194
|
-
const singleChapterBook = this.singleChapterBook.find((b) => Object.keys(b)[0] === book)
|
|
195
175
|
const parsedPassage = {
|
|
196
|
-
original: passage.book
|
|
197
|
-
book
|
|
176
|
+
original: `${passage.book} ${passage.reference}`,
|
|
177
|
+
book,
|
|
198
178
|
chapter: null,
|
|
199
179
|
verses: [],
|
|
200
180
|
type: passage.type,
|
|
201
|
-
testament
|
|
181
|
+
testament,
|
|
202
182
|
index: passage.index,
|
|
203
183
|
version: this._handleVersion(passage.version, testament),
|
|
184
|
+
passages: [],
|
|
185
|
+
scripture: null,
|
|
186
|
+
valid: true,
|
|
187
|
+
start: null,
|
|
188
|
+
end: null,
|
|
204
189
|
}
|
|
205
190
|
|
|
206
|
-
|
|
207
|
-
const parts = passage.reference.split(",")
|
|
208
|
-
|
|
209
|
-
parts.forEach((part, partIndex) => {
|
|
210
|
-
part = part.trim()
|
|
211
|
-
|
|
212
|
-
if (part.includes(":")) {
|
|
213
|
-
// Explicit chapter:verse (e.g., "1:1-3")
|
|
214
|
-
const [chapterPart, versePart] = part.split(":")
|
|
215
|
-
if (partIndex === 0) {
|
|
216
|
-
parsedPassage.chapter = Number(chapterPart) // Set chapter only on first part
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (versePart.includes("-")) {
|
|
220
|
-
parsedPassage.verses.push(versePart) // Add range (e.g., "1-3")
|
|
221
|
-
} else {
|
|
222
|
-
parsedPassage.verses.push(Number(versePart)) // Add single verse
|
|
223
|
-
}
|
|
224
|
-
parsedPassage.type = versePart.includes("-") ? "chapter_verse_range" : "chapter_verse"
|
|
225
|
-
} else if (singleChapterBook) {
|
|
226
|
-
// Handle single-chapter books
|
|
227
|
-
const verseCount = singleChapterBook[book][1].length
|
|
228
|
-
if (part === "1" && parts.length === 1 && partIndex === 0) {
|
|
229
|
-
// "2 John 1" means the whole chapter
|
|
230
|
-
parsedPassage.chapter = 1
|
|
231
|
-
parsedPassage.type = "single_chapter"
|
|
232
|
-
parsedPassage.verses = [`1-${verseCount}`] // e.g., "1-13"
|
|
233
|
-
} else if (part.includes("-")) {
|
|
234
|
-
// "2 John 2-5" → "2 John 1:2-5"
|
|
235
|
-
parsedPassage.chapter = 1
|
|
236
|
-
parsedPassage.verses.push(part) // e.g., "2-5"
|
|
237
|
-
parsedPassage.type = "chapter_verse_range"
|
|
238
|
-
} else {
|
|
239
|
-
// "2 John 2" → "2 John 1:2"
|
|
240
|
-
const num = Number(part)
|
|
241
|
-
if (num > 1 || (num === 1 && parts.length > 1)) {
|
|
242
|
-
parsedPassage.chapter = 1
|
|
243
|
-
parsedPassage.verses.push(num) // Treat as verse number
|
|
244
|
-
parsedPassage.type = "chapter_verse"
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
} else if (part.includes("-") && !parsedPassage.chapter) {
|
|
248
|
-
// Range without chapter for multi-chapter books (e.g., "Matthew 3-5")
|
|
249
|
-
const [start, end] = part.split("-").map(Number)
|
|
250
|
-
parsedPassage.chapter = start
|
|
251
|
-
parsedPassage.verses = [
|
|
252
|
-
`${this.chapterVerses[book][start][0]}-${this.chapterVerses[book][start].slice(-1)[0]}`,
|
|
253
|
-
]
|
|
254
|
-
parsedPassage.to = {
|
|
255
|
-
book,
|
|
256
|
-
chapter: end,
|
|
257
|
-
verses: [`${this.chapterVerses[book][end][0]}-${this.chapterVerses[book][end].slice(-1)[0]}`],
|
|
258
|
-
}
|
|
259
|
-
parsedPassage.type = "chapter_range"
|
|
260
|
-
} else if (part.includes("-")) {
|
|
261
|
-
// Verse range in current chapter (e.g., "8-9" after "40:3-5")
|
|
262
|
-
parsedPassage.verses.push(part)
|
|
263
|
-
parsedPassage.type = "chapter_verse_range"
|
|
264
|
-
} else {
|
|
265
|
-
// Single number (chapter or verse) for multi-chapter books
|
|
266
|
-
if (partIndex === 0 && !parsedPassage.chapter) {
|
|
267
|
-
parsedPassage.chapter = Number(part)
|
|
268
|
-
parsedPassage.type = "single_chapter"
|
|
269
|
-
// For multi-chapter books, set verses to full chapter range
|
|
270
|
-
if (
|
|
271
|
-
!singleChapterBook &&
|
|
272
|
-
this.chapterVerses[book] &&
|
|
273
|
-
this.chapterVerses[book][parsedPassage.chapter]
|
|
274
|
-
) {
|
|
275
|
-
const chapterVerses = this.chapterVerses[book][parsedPassage.chapter]
|
|
276
|
-
parsedPassage.verses = [`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`]
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
parsedPassage.verses.push(Number(part))
|
|
280
|
-
parsedPassage.type = "comma_separated_verses"
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
// Populate passages and scripture after processing all parts
|
|
191
|
+
this.parseReferenceParts(parsedPassage, passage.reference.split(","))
|
|
286
192
|
parsedPassage.passages = this.populate(parsedPassage)
|
|
287
193
|
parsedPassage.scripture = this.scripturize(parsedPassage)
|
|
288
194
|
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
289
195
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
parsedPassage.type === "multi_chapter_verse_range" &&
|
|
293
|
-
parts.some((p) => p.includes(":") && p.split(":")[0] !== String(parsedPassage.chapter))
|
|
294
|
-
) {
|
|
295
|
-
const lastPart = parts[parts.length - 1]
|
|
296
|
-
const [endChapter, endVerse] = lastPart.split(":")
|
|
297
|
-
parsedPassage.to = {
|
|
298
|
-
book: book,
|
|
299
|
-
chapter: Number(endChapter),
|
|
300
|
-
verses: endVerse.includes("-") ? [endVerse] : [Number(endVerse)],
|
|
301
|
-
}
|
|
196
|
+
if (parsedPassage.type === this.MULTI_CHAPTER_RANGE) {
|
|
197
|
+
this.handleMultiChapterRange(parsedPassage, passage.reference)
|
|
302
198
|
} else {
|
|
303
|
-
delete parsedPassage.to
|
|
199
|
+
delete parsedPassage.to
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (parsedPassage.passages.length > 0) {
|
|
203
|
+
const sortedPassages = parsedPassage.passages.slice().sort((a, b) => {
|
|
204
|
+
if (a.chapter !== b.chapter) return a.chapter - b.chapter
|
|
205
|
+
return a.verse - b.verse
|
|
206
|
+
})
|
|
207
|
+
const firstPassage = sortedPassages[0]
|
|
208
|
+
const lastPassage = sortedPassages[sortedPassages.length - 1]
|
|
209
|
+
parsedPassage.start = {
|
|
210
|
+
book: firstPassage.book,
|
|
211
|
+
chapter: firstPassage.chapter,
|
|
212
|
+
verse: firstPassage.verse,
|
|
213
|
+
}
|
|
214
|
+
parsedPassage.end = {
|
|
215
|
+
book: lastPassage.book,
|
|
216
|
+
chapter: lastPassage.chapter,
|
|
217
|
+
verse: lastPassage.verse,
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!parsedPassage.version) {
|
|
222
|
+
parsedPassage.version = {
|
|
223
|
+
name: "English",
|
|
224
|
+
value: "ENG",
|
|
225
|
+
abbreviation: "eng",
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Attach the reference method to this individual passage object
|
|
230
|
+
parsedPassage.reference = function () {
|
|
231
|
+
return this.scripture.passage
|
|
304
232
|
}
|
|
305
233
|
|
|
306
234
|
return parsedPassage
|
|
@@ -309,9 +237,102 @@ class CodexParser {
|
|
|
309
237
|
this.versification()
|
|
310
238
|
return this
|
|
311
239
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
240
|
+
|
|
241
|
+
parseReferenceParts(passage, parts) {
|
|
242
|
+
const singleChapterBook = this.singleChapterBook.find((b) => Object.keys(b)[0] === passage.book)
|
|
243
|
+
|
|
244
|
+
parts.forEach((part, index) => {
|
|
245
|
+
part = part.trim()
|
|
246
|
+
const isFirstPart = index === 0
|
|
247
|
+
|
|
248
|
+
if (part.includes(":")) {
|
|
249
|
+
this.parseChapterVerse(passage, part, isFirstPart)
|
|
250
|
+
} else if (singleChapterBook) {
|
|
251
|
+
this.parseSingleChapterBook(passage, part, isFirstPart && parts.length === 1)
|
|
252
|
+
} else if (part.includes("-")) {
|
|
253
|
+
this.parseRange(passage, part, isFirstPart)
|
|
254
|
+
} else {
|
|
255
|
+
this.parseSingleNumber(passage, part, isFirstPart)
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
parseChapterVerse(passage, part, isFirstPart) {
|
|
261
|
+
const [chapter, verse] = part.split(":")
|
|
262
|
+
if (isFirstPart) passage.chapter = Number(chapter)
|
|
263
|
+
passage.type = verse.includes("-") ? this.CHAPTER_VERSE_RANGE : this.CHAPTER_VERSE
|
|
264
|
+
passage.verses.push(verse.includes("-") ? verse : Number(verse))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
parseSingleChapterBook(passage, part, isWholeChapter) {
|
|
268
|
+
const verseCount = this.getChapterVerses(passage.book, 1).length
|
|
269
|
+
if (part === "1" && isWholeChapter) {
|
|
270
|
+
passage.chapter = 1
|
|
271
|
+
passage.type = this.SINGLE_CHAPTER
|
|
272
|
+
passage.verses = [`1-${verseCount}`]
|
|
273
|
+
} else if (part.includes("-")) {
|
|
274
|
+
passage.chapter = 1
|
|
275
|
+
passage.verses.push(part)
|
|
276
|
+
passage.type = this.CHAPTER_VERSE_RANGE
|
|
277
|
+
} else {
|
|
278
|
+
const num = Number(part)
|
|
279
|
+
if (num > 1 || !isWholeChapter) {
|
|
280
|
+
passage.chapter = 1
|
|
281
|
+
passage.verses.push(num)
|
|
282
|
+
passage.type = this.CHAPTER_VERSE
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
parseRange(passage, part, isFirstPart) {
|
|
288
|
+
if (!passage.chapter && isFirstPart) {
|
|
289
|
+
const [start, end] = part.split("-").map(Number)
|
|
290
|
+
passage.chapter = start
|
|
291
|
+
const startVerses = this.getChapterVerses(passage.book, start)
|
|
292
|
+
passage.verses = [`${startVerses[0]}-${startVerses[startVerses.length - 1]}`]
|
|
293
|
+
passage.to = {
|
|
294
|
+
book: passage.book,
|
|
295
|
+
chapter: end,
|
|
296
|
+
verses: [
|
|
297
|
+
`${this.getChapterVerses(passage.book, end)[0]}-${
|
|
298
|
+
this.getChapterVerses(passage.book, end).slice(-1)[0]
|
|
299
|
+
}`,
|
|
300
|
+
],
|
|
301
|
+
}
|
|
302
|
+
passage.type = this.CHAPTER_RANGE
|
|
303
|
+
} else {
|
|
304
|
+
passage.verses.push(part)
|
|
305
|
+
passage.type = this.CHAPTER_VERSE_RANGE
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
parseSingleNumber(passage, part, isFirstPart) {
|
|
310
|
+
if (isFirstPart && !passage.chapter) {
|
|
311
|
+
passage.chapter = Number(part)
|
|
312
|
+
passage.type = this.SINGLE_CHAPTER
|
|
313
|
+
const chapterVerses = this.getChapterVerses(passage.book, passage.chapter)
|
|
314
|
+
if (chapterVerses.length) {
|
|
315
|
+
passage.verses = [`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`]
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
passage.verses.push(Number(part))
|
|
319
|
+
passage.type = this.COMMA_SEPARATED
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
handleMultiChapterRange(passage, reference) {
|
|
324
|
+
const parts = reference.split(",")
|
|
325
|
+
const lastPart = parts[parts.length - 1]
|
|
326
|
+
const [endChapter, endVerse] = lastPart.split(":")
|
|
327
|
+
if (endChapter !== String(passage.chapter)) {
|
|
328
|
+
passage.to = {
|
|
329
|
+
book: passage.book,
|
|
330
|
+
chapter: Number(endChapter),
|
|
331
|
+
verses: endVerse.includes("-") ? [endVerse] : [Number(endVerse)],
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
315
336
|
_generateRange(start, end) {
|
|
316
337
|
const range = []
|
|
317
338
|
for (let i = start; i <= end; i++) {
|
|
@@ -324,25 +345,20 @@ class CodexParser {
|
|
|
324
345
|
version = version.toLowerCase()
|
|
325
346
|
if (!this.chapterVerses[book][chapter]) return
|
|
326
347
|
if (!this.versificationDifferences[book]) return
|
|
327
|
-
// Loop through each key-value pair in the dictionary
|
|
328
348
|
for (const [key, value] of Object.entries(this.versificationDifferences[book])) {
|
|
329
|
-
// Check if the key starts with the desired chapter
|
|
330
349
|
if (value[version].startsWith(`${chapter}:`)) {
|
|
331
|
-
// Ensure the version exists in the value object
|
|
332
350
|
if (value[version]) {
|
|
333
|
-
// Extract the verse number from the value
|
|
334
351
|
const verse = value[version].split(":")[1]
|
|
335
352
|
this.chapterVerses[book][chapter].push(Number(verse))
|
|
336
353
|
}
|
|
337
354
|
}
|
|
338
355
|
}
|
|
339
356
|
this.chapterVerses[book][chapter] = Array.from(this.chapterVerses[book][chapter])
|
|
340
|
-
return this.chapterVerses
|
|
357
|
+
return this.chapterVerses
|
|
341
358
|
}
|
|
342
359
|
|
|
343
360
|
_setVersion(book, chapter, version) {
|
|
344
361
|
this.version = version ? version : "eng"
|
|
345
|
-
|
|
346
362
|
if (this.version !== "eng") {
|
|
347
363
|
this._searchVersificationDifferences(book, chapter, version)
|
|
348
364
|
}
|
|
@@ -352,31 +368,25 @@ class CodexParser {
|
|
|
352
368
|
this.passages.forEach((passage) => {
|
|
353
369
|
const hasVersification = this.versificationDifferences[passage.book]
|
|
354
370
|
passage.passages.forEach((subPassage) => {
|
|
355
|
-
// Apply general versification differences
|
|
356
371
|
if (hasVersification) {
|
|
357
372
|
const key = `${subPassage.chapter}:${subPassage.verse}`
|
|
358
373
|
if (this.versificationDifferences[passage.book][key]) {
|
|
359
374
|
subPassage.versification = this.versificationDifferences[passage.book][key]
|
|
360
375
|
}
|
|
361
376
|
}
|
|
362
|
-
|
|
363
|
-
// Handle specific version adjustments for "lxx" or "mt"
|
|
364
377
|
if (passage.version) {
|
|
365
378
|
const versionAbbreviation = passage.version.abbreviation
|
|
366
379
|
const versionType =
|
|
367
380
|
versionAbbreviation === "lxx" ? "lxx" : versionAbbreviation === "mt" ? "mt" : null
|
|
368
|
-
|
|
369
381
|
if (versionType) {
|
|
370
382
|
const versionReference = `${subPassage.chapter}:${subPassage.verse}`
|
|
371
|
-
|
|
372
|
-
// Look for matching versification based on the version type (lxx or mt)
|
|
373
383
|
for (const versification in this.versificationDifferences[passage.book]) {
|
|
374
384
|
if (
|
|
375
385
|
this.versificationDifferences[passage.book][versification][versionType] ===
|
|
376
386
|
versionReference
|
|
377
387
|
) {
|
|
378
388
|
subPassage.versification = this.versificationDifferences[passage.book][versification]
|
|
379
|
-
break
|
|
389
|
+
break
|
|
380
390
|
}
|
|
381
391
|
}
|
|
382
392
|
}
|
|
@@ -385,96 +395,65 @@ class CodexParser {
|
|
|
385
395
|
})
|
|
386
396
|
}
|
|
387
397
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
* @return {Array} An array of passage objects with individual verses.
|
|
393
|
-
*/
|
|
394
|
-
populate(parsedPassage) {
|
|
395
|
-
const passages = []
|
|
396
|
-
const { book, chapter, verses, type, to } = parsedPassage
|
|
397
|
-
const version = parsedPassage.version ? parsedPassage.version.abbreviation : "eng"
|
|
398
|
-
this._setVersion(book, chapter, version) // Set version data if needed
|
|
398
|
+
populate(passage) {
|
|
399
|
+
const { book, chapter, verses, type, to } = passage
|
|
400
|
+
const version = passage.version?.abbreviation || "eng"
|
|
401
|
+
this._setVersion(book, chapter, version)
|
|
399
402
|
|
|
400
|
-
|
|
403
|
+
if (type === this.SINGLE_CHAPTER) {
|
|
404
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
405
|
+
return this.expandVerses(book, chapter, [`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`])
|
|
406
|
+
}
|
|
401
407
|
|
|
402
|
-
if (type ===
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
414
|
-
})
|
|
415
|
-
}
|
|
416
|
-
} else if (type === "comma_separated_verses" || type === "chapter_verse_range") {
|
|
417
|
-
// Handle comma-separated verses or single-chapter verse ranges (e.g., "Isaiah 40:3-5,8-9" or "2 John 1:1-3")
|
|
418
|
-
verses.forEach((verse) => {
|
|
419
|
-
if (typeof verse === "string" && verse.includes("-")) {
|
|
420
|
-
const [start, end] = verse.split("-").map(Number)
|
|
421
|
-
for (let i = start; i <= end; i++) {
|
|
422
|
-
passages.push({ book, chapter: Number(chapter), verse: i })
|
|
423
|
-
}
|
|
424
|
-
} else {
|
|
425
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
426
|
-
}
|
|
427
|
-
})
|
|
428
|
-
} else if (type === "chapter_range") {
|
|
429
|
-
// Handle ranges of chapters (e.g., "Isaiah 3-5")
|
|
430
|
-
for (let currentChapter = chapter; currentChapter <= to.chapter; currentChapter++) {
|
|
431
|
-
if (this.chapterVerses[book] && this.chapterVerses[book][currentChapter]) {
|
|
432
|
-
this.chapterVerses[book][currentChapter].forEach((verse) => {
|
|
433
|
-
passages.push({ book, chapter: Number(currentChapter), verse: Number(verse) })
|
|
434
|
-
})
|
|
435
|
-
}
|
|
408
|
+
if (type === this.CHAPTER_VERSE || type === this.COMMA_SEPARATED || type === this.CHAPTER_VERSE_RANGE) {
|
|
409
|
+
return this.expandVerses(book, chapter, verses)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (type === this.CHAPTER_RANGE) {
|
|
413
|
+
const passages = []
|
|
414
|
+
for (let ch = chapter; ch <= to.chapter; ch++) {
|
|
415
|
+
const chapterVerses = this.getChapterVerses(book, ch)
|
|
416
|
+
passages.push(
|
|
417
|
+
...this.expandVerses(book, ch, [`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`])
|
|
418
|
+
)
|
|
436
419
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
420
|
+
return passages
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (type === this.MULTI_CHAPTER_RANGE) {
|
|
424
|
+
const passages = []
|
|
440
425
|
const startVerse = verses[0].includes("-") ? Number(verses[0].split("-")[0]) : Number(verses[0])
|
|
441
|
-
const endChapter = to.chapter
|
|
442
426
|
const endVerse = to.verses[0].includes("-") ? Number(to.verses[0].split("-")[1]) : Number(to.verses[0])
|
|
443
427
|
|
|
444
|
-
for (let
|
|
445
|
-
const chapterVerses = this.
|
|
446
|
-
|
|
428
|
+
for (let ch = chapter; ch <= to.chapter; ch++) {
|
|
429
|
+
const chapterVerses = this.getChapterVerses(book, ch)
|
|
430
|
+
const from = ch === chapter ? startVerse : chapterVerses[0]
|
|
431
|
+
const toVerse = ch === to.chapter ? endVerse : chapterVerses[chapterVerses.length - 1]
|
|
432
|
+
passages.push(...this.expandVerses(book, ch, [`${from}-${toVerse}`]))
|
|
433
|
+
}
|
|
434
|
+
return passages
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return []
|
|
438
|
+
}
|
|
447
439
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
440
|
+
expandVerses(book, chapter, verses) {
|
|
441
|
+
const passages = []
|
|
442
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
451
443
|
|
|
452
|
-
|
|
453
|
-
|
|
444
|
+
verses.forEach((verse) => {
|
|
445
|
+
if (typeof verse === "string" && verse.includes("-")) {
|
|
446
|
+
const [start, end] = verse.split("-").map(Number)
|
|
447
|
+
for (let i = start; i <= end && i <= chapterVerses[chapterVerses.length - 1]; i++) {
|
|
448
|
+
passages.push({ book, chapter, verse: i })
|
|
454
449
|
}
|
|
450
|
+
} else {
|
|
451
|
+
passages.push({ book, chapter, verse: Number(verse) })
|
|
455
452
|
}
|
|
456
|
-
}
|
|
457
|
-
// Handle single chapter:verse references (e.g., "2 John 1:1")
|
|
458
|
-
verses.forEach((verse) => {
|
|
459
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
460
|
-
})
|
|
461
|
-
} else if (type === "single_chapter_book_verse_range") {
|
|
462
|
-
// Handle ranges in single-chapter books (e.g., "Jude 5-7")
|
|
463
|
-
const [startVerse, endVerse] = verses[0].split("-").map(Number)
|
|
464
|
-
for (let i = startVerse; i <= endVerse; i++) {
|
|
465
|
-
passages.push({ book, chapter: 1, verse: i })
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
453
|
+
})
|
|
469
454
|
return passages
|
|
470
455
|
}
|
|
471
456
|
|
|
472
|
-
/**
|
|
473
|
-
* Converts a book name to its corresponding full name from the bible.
|
|
474
|
-
*
|
|
475
|
-
* @param {string} book - The abbreviated or partial name of the book.
|
|
476
|
-
* @return {string|undefined} The full name of the book if found, otherwise undefined.
|
|
477
|
-
*/
|
|
478
457
|
bookify(book) {
|
|
479
458
|
if (typeof book !== "string") {
|
|
480
459
|
book = book[0]
|
|
@@ -496,78 +475,105 @@ class CodexParser {
|
|
|
496
475
|
return bookified
|
|
497
476
|
}
|
|
498
477
|
|
|
499
|
-
/**
|
|
500
|
-
* Returns the passages stored in the object.
|
|
501
|
-
*
|
|
502
|
-
* @return {array} The passages stored in the object.
|
|
503
|
-
*/
|
|
504
478
|
getPassages() {
|
|
505
|
-
|
|
506
|
-
const passagesArray = [...this.passages] // Clone the array to avoid mutation
|
|
479
|
+
const passagesArray = [...this.passages]
|
|
507
480
|
|
|
508
|
-
// Add first() method directly to the array
|
|
509
481
|
passagesArray.first = function () {
|
|
510
482
|
return this.length > 0 ? this[0] : null
|
|
511
483
|
}
|
|
512
484
|
|
|
485
|
+
passagesArray.oldTestament = function () {
|
|
486
|
+
return this.filter((passage) => passage.testament === "old")
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
passagesArray.newTestament = function () {
|
|
490
|
+
return this.filter((passage) => passage.testament === "new")
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
passagesArray.combine = function (options = {}) {
|
|
494
|
+
const { book = true, chapter = true } = options
|
|
495
|
+
|
|
496
|
+
if (!book) {
|
|
497
|
+
return [...this]
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const parser = new CodexParser()
|
|
501
|
+
const groupedByBook = new Map()
|
|
502
|
+
|
|
503
|
+
this.forEach((passage) => {
|
|
504
|
+
const bookKey = passage.book
|
|
505
|
+
if (!groupedByBook.has(bookKey)) {
|
|
506
|
+
groupedByBook.set(bookKey, [])
|
|
507
|
+
}
|
|
508
|
+
groupedByBook.get(bookKey).push(passage)
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
const combinedPassages = []
|
|
512
|
+
|
|
513
|
+
for (const [book, bookPassages] of groupedByBook) {
|
|
514
|
+
if (chapter) {
|
|
515
|
+
const groupedByChapter = new Map()
|
|
516
|
+
bookPassages.forEach((passage) => {
|
|
517
|
+
const chapterKey = `${passage.book}-${passage.chapter}`
|
|
518
|
+
if (!groupedByChapter.has(chapterKey)) {
|
|
519
|
+
groupedByChapter.set(chapterKey, [])
|
|
520
|
+
}
|
|
521
|
+
groupedByChapter.get(chapterKey).push(passage)
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
for (const passages of groupedByChapter.values()) {
|
|
525
|
+
if (passages.length === 1) {
|
|
526
|
+
combinedPassages.push({ ...passages[0] })
|
|
527
|
+
} else {
|
|
528
|
+
const combined = parser.combine(passages)
|
|
529
|
+
combinedPassages.push(combined)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
const combined = parser.combine(bookPassages)
|
|
534
|
+
combinedPassages.push(combined)
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return combinedPassages
|
|
539
|
+
}
|
|
540
|
+
|
|
513
541
|
return passagesArray
|
|
514
542
|
}
|
|
515
543
|
|
|
516
|
-
// New first() method that can be chained after getPassages()
|
|
517
544
|
first() {
|
|
518
545
|
return this.passages.length > 0 ? this.passages[0] : null
|
|
519
546
|
}
|
|
520
547
|
|
|
521
|
-
/**
|
|
522
|
-
* Converts a passage object into a scripturize object with human-readable name, chapter and verses and a hash.
|
|
523
|
-
*
|
|
524
|
-
* @param {object} passage - The passage object to scripturize.
|
|
525
|
-
* @return {object} The object with the human-readable name, chapter and verses and a hash.
|
|
526
|
-
*/
|
|
527
548
|
scripturize(passage) {
|
|
528
|
-
// Helper function to format a single chapter:verse combination
|
|
529
549
|
const formatChapterVerse = (chapter, verses) => {
|
|
530
550
|
if (!chapter || !verses || verses.length === 0) return ""
|
|
531
551
|
if (verses.length === 1) {
|
|
532
552
|
return `${chapter}:${verses[0]}`
|
|
533
553
|
}
|
|
534
|
-
|
|
535
|
-
// Check if verses are continuous (e.g., [1, 2, 3, 4, 5] -> "1-5")
|
|
536
554
|
const isRange = verses.every((v, i, arr) => i === 0 || v === arr[i - 1] + 1)
|
|
537
|
-
|
|
538
555
|
if (isRange) {
|
|
539
556
|
return `${chapter}:${verses[0]}-${verses[verses.length - 1]}`
|
|
540
557
|
}
|
|
541
|
-
|
|
542
|
-
// Comma-separated (e.g., [1, 3, 5] -> "1,3,5")
|
|
543
558
|
return `${chapter}:${verses.join(",")}`
|
|
544
559
|
}
|
|
545
560
|
|
|
546
|
-
// Start constructing the passage string
|
|
547
561
|
let combined = `${passage.book}`
|
|
548
|
-
|
|
549
562
|
if (passage.type === "multi_chapter_verse_range" && passage.to) {
|
|
550
|
-
// Multi-chapter verse range
|
|
551
|
-
|
|
552
563
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
553
564
|
passage.to.chapter,
|
|
554
565
|
passage.to.verses
|
|
555
566
|
)}`
|
|
556
567
|
} else if (passage.type === "chapter_verse_range") {
|
|
557
|
-
// Single-chapter verse range
|
|
558
568
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
559
569
|
} else if (passage.type === "comma_separated_verses") {
|
|
560
|
-
// Comma-separated verses
|
|
561
570
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
562
571
|
} else if (passage.type === "chapter_range" && passage.to) {
|
|
563
|
-
// Chapter range
|
|
564
572
|
combined += ` ${passage.chapter}-${passage.to.chapter}`
|
|
565
573
|
} else {
|
|
566
|
-
// Single chapter or single verse
|
|
567
574
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
568
575
|
}
|
|
569
576
|
|
|
570
|
-
// Generate the chapter:verse (cv) string
|
|
571
577
|
const cv = passage.to
|
|
572
578
|
? `${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
573
579
|
passage.to.chapter,
|
|
@@ -575,10 +581,8 @@ class CodexParser {
|
|
|
575
581
|
)}`
|
|
576
582
|
: formatChapterVerse(passage.chapter, passage.verses)
|
|
577
583
|
|
|
578
|
-
// Generate the hash
|
|
579
584
|
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, ".").replace(/-/g, ".")}`
|
|
580
585
|
|
|
581
|
-
// Return the final scripture object
|
|
582
586
|
return {
|
|
583
587
|
passage: combined,
|
|
584
588
|
cv: cv,
|
|
@@ -586,27 +590,16 @@ class CodexParser {
|
|
|
586
590
|
}
|
|
587
591
|
}
|
|
588
592
|
|
|
589
|
-
/**
|
|
590
|
-
* Combine multiple passages into one. The method checks for duplicates, merges overlapping or adjacent ranges,
|
|
591
|
-
* and builds the original and scripture properties.
|
|
592
|
-
* **This method will always combine based on English versification. LXX and MT versifications will be reflected in the combined passage.passages.versification.**
|
|
593
|
-
* This method will fail if the passages are not to the same book and chapter.
|
|
594
|
-
* TODO: Add support for MT and LXX
|
|
595
|
-
* @param {array} passages - An array of passage objects to combine.
|
|
596
|
-
* @return {object} The combined passage object.
|
|
597
|
-
*/
|
|
598
593
|
combine(passages) {
|
|
599
594
|
if (!passages || passages.length === 0) {
|
|
600
595
|
throw new Error("No passages provided to join.")
|
|
601
596
|
}
|
|
602
597
|
|
|
603
|
-
// Ensure all passages are from the same book
|
|
604
598
|
const uniqueBooks = [...new Set(passages.map((p) => p.book))]
|
|
605
599
|
if (uniqueBooks.length > 1) {
|
|
606
600
|
throw new Error("Passages must be from the same book to join.")
|
|
607
601
|
}
|
|
608
602
|
|
|
609
|
-
// Start with the base object
|
|
610
603
|
const combined = {
|
|
611
604
|
...passages[0],
|
|
612
605
|
verses: [],
|
|
@@ -614,36 +607,45 @@ class CodexParser {
|
|
|
614
607
|
to: null,
|
|
615
608
|
scripture: {},
|
|
616
609
|
type: null,
|
|
610
|
+
start: null,
|
|
611
|
+
end: null,
|
|
617
612
|
}
|
|
618
613
|
|
|
619
614
|
const chapterVerses = {}
|
|
620
615
|
let firstChapter = null
|
|
621
616
|
let lastChapter = null
|
|
617
|
+
let firstVerse = null
|
|
618
|
+
let lastVerse = null
|
|
622
619
|
|
|
623
|
-
// Collect all verses and passages, grouped by chapter
|
|
624
620
|
passages.forEach((passage) => {
|
|
625
621
|
passage.passages.forEach((p) => {
|
|
626
622
|
if (!chapterVerses[p.chapter]) {
|
|
627
623
|
chapterVerses[p.chapter] = new Set()
|
|
628
624
|
}
|
|
629
625
|
chapterVerses[p.chapter].add(p.verse)
|
|
630
|
-
combined.passages.push(p)
|
|
626
|
+
combined.passages.push(p)
|
|
627
|
+
|
|
628
|
+
if (firstChapter === null || p.chapter < firstChapter) {
|
|
629
|
+
firstChapter = p.chapter
|
|
630
|
+
firstVerse = p.verse
|
|
631
|
+
} else if (p.chapter === firstChapter && (firstVerse === null || p.verse < firstVerse)) {
|
|
632
|
+
firstVerse = p.verse
|
|
633
|
+
}
|
|
634
|
+
if (lastChapter === null || p.chapter > lastChapter) {
|
|
635
|
+
lastChapter = p.chapter
|
|
636
|
+
lastVerse = p.verse
|
|
637
|
+
} else if (p.chapter === lastChapter && (lastVerse === null || p.verse > lastVerse)) {
|
|
638
|
+
lastVerse = p.verse
|
|
639
|
+
}
|
|
631
640
|
})
|
|
632
641
|
|
|
633
|
-
// Track first and last chapters
|
|
634
642
|
const chapters = passage.passages.map((p) => p.chapter)
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
if (!lastChapter || Math.max(...chapters) > lastChapter) {
|
|
639
|
-
lastChapter = Math.max(...chapters)
|
|
640
|
-
}
|
|
643
|
+
firstChapter = firstChapter === null ? Math.min(...chapters) : Math.min(firstChapter, ...chapters)
|
|
644
|
+
lastChapter = lastChapter === null ? Math.max(...chapters) : Math.max(lastChapter, ...chapters)
|
|
641
645
|
})
|
|
642
646
|
|
|
643
|
-
// Ensure unique and sorted passages
|
|
644
647
|
combined.passages = Array.from(new Set(combined.passages.map(JSON.stringify))).map(JSON.parse)
|
|
645
648
|
|
|
646
|
-
// Process chapter and verse data
|
|
647
649
|
const chapterStrings = []
|
|
648
650
|
const sortedChapters = Object.keys(chapterVerses)
|
|
649
651
|
.map(Number)
|
|
@@ -654,13 +656,12 @@ class CodexParser {
|
|
|
654
656
|
const mergedVerses = this.mergeRanges(verses)
|
|
655
657
|
chapterStrings.push(`${chapter}:${mergedVerses.join(",")}`)
|
|
656
658
|
if (chapter === firstChapter) {
|
|
657
|
-
combined.verses = mergedVerses
|
|
659
|
+
combined.verses = mergedVerses
|
|
658
660
|
}
|
|
659
661
|
})
|
|
660
662
|
|
|
661
|
-
// Handle multi-chapter ranges
|
|
662
663
|
if (firstChapter !== lastChapter) {
|
|
663
|
-
combined.type =
|
|
664
|
+
combined.type = this.MULTI_CHAPTER_RANGE
|
|
664
665
|
combined.to = {
|
|
665
666
|
book: combined.book,
|
|
666
667
|
chapter: lastChapter,
|
|
@@ -670,25 +671,37 @@ class CodexParser {
|
|
|
670
671
|
","
|
|
671
672
|
)}; ${lastChapter}:${combined.to.verses.join(",")}`
|
|
672
673
|
} else {
|
|
673
|
-
|
|
674
|
-
if (combined.verses.length > 1) {
|
|
675
|
-
combined.type = "chapter_verse_range"
|
|
676
|
-
} else {
|
|
677
|
-
combined.type = "chapter_verse"
|
|
678
|
-
}
|
|
674
|
+
combined.type = combined.verses.length > 1 ? this.CHAPTER_VERSE_RANGE : this.CHAPTER_VERSE
|
|
679
675
|
combined.original = `${combined.book} ${firstChapter}:${combined.verses.join(",")}`
|
|
680
676
|
}
|
|
681
677
|
|
|
682
|
-
// Build the scripture property
|
|
683
678
|
const chapterString = chapterStrings.join(";")
|
|
684
679
|
combined.scripture = {
|
|
685
680
|
passage: `${combined.book} ${chapterString}`,
|
|
686
681
|
cv: chapterString,
|
|
687
|
-
hash: `${combined.book.toLowerCase()}_${chapterString.replace(/:/g, ".").replace(
|
|
682
|
+
hash: `${combined.book.toLowerCase()}_${chapterString.replace(/:/g, ".").replace(/[,;]/g, ".")}`,
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
combined.start = {
|
|
686
|
+
book: combined.book,
|
|
687
|
+
chapter: firstChapter,
|
|
688
|
+
verse: firstVerse || Math.min(...Array.from(chapterVerses[firstChapter])),
|
|
689
|
+
}
|
|
690
|
+
combined.end = {
|
|
691
|
+
book: combined.book,
|
|
692
|
+
chapter: lastChapter,
|
|
693
|
+
verse: lastVerse || Math.max(...Array.from(chapterVerses[lastChapter])),
|
|
688
694
|
}
|
|
695
|
+
|
|
696
|
+
// Reattach the reference method to the combined passage
|
|
697
|
+
combined.reference = function () {
|
|
698
|
+
return this.scripture.passage
|
|
699
|
+
}
|
|
700
|
+
|
|
689
701
|
if (combined.to === null) {
|
|
690
702
|
delete combined.to
|
|
691
703
|
}
|
|
704
|
+
|
|
692
705
|
return combined
|
|
693
706
|
}
|
|
694
707
|
|
|
@@ -702,7 +715,6 @@ class CodexParser {
|
|
|
702
715
|
if (sortedVerses[i] === end + 1) {
|
|
703
716
|
end = sortedVerses[i]
|
|
704
717
|
} else {
|
|
705
|
-
// Push range or single verse
|
|
706
718
|
if (start === end) {
|
|
707
719
|
merged.push(`${start}`)
|
|
708
720
|
} else {
|
|
@@ -713,7 +725,6 @@ class CodexParser {
|
|
|
713
725
|
}
|
|
714
726
|
}
|
|
715
727
|
|
|
716
|
-
// Push the final range or single verse
|
|
717
728
|
if (start === end) {
|
|
718
729
|
merged.push(`${start}`)
|
|
719
730
|
} else {
|
|
@@ -724,24 +735,17 @@ class CodexParser {
|
|
|
724
735
|
}
|
|
725
736
|
|
|
726
737
|
getToc(version = "ESV") {
|
|
727
|
-
// Initialize the table of contents (toc)
|
|
728
738
|
const toc = {}
|
|
729
|
-
|
|
730
|
-
// Add Old Testament books and their chapters/verses to toc
|
|
731
739
|
this.bible.old.forEach((book) => {
|
|
732
740
|
if (this.chapterVerses[book]) {
|
|
733
741
|
toc[book] = this.chapterVerses[book]
|
|
734
742
|
}
|
|
735
743
|
})
|
|
736
|
-
|
|
737
|
-
// Add New Testament books and their chapters/verses to toc
|
|
738
744
|
this.bible.new.forEach((book) => {
|
|
739
745
|
if (this.chapterVerses[book]) {
|
|
740
746
|
toc[book] = this.chapterVerses[book]
|
|
741
747
|
}
|
|
742
748
|
})
|
|
743
|
-
|
|
744
|
-
// Merge in single-chapter books if not already in toc
|
|
745
749
|
this.singleChapterBook.forEach((item) => {
|
|
746
750
|
Object.keys(item).forEach((book) => {
|
|
747
751
|
if (!toc[book]) {
|
|
@@ -749,8 +753,6 @@ class CodexParser {
|
|
|
749
753
|
}
|
|
750
754
|
})
|
|
751
755
|
})
|
|
752
|
-
|
|
753
|
-
// Sort the keys of toc by canonical order
|
|
754
756
|
const orderedToc = {}
|
|
755
757
|
const canonicalOrder = [...this.bible.old, ...this.bible.new]
|
|
756
758
|
canonicalOrder.forEach((book) => {
|
|
@@ -758,162 +760,79 @@ class CodexParser {
|
|
|
758
760
|
orderedToc[book] = toc[book]
|
|
759
761
|
}
|
|
760
762
|
})
|
|
761
|
-
|
|
762
763
|
return orderedToc
|
|
763
764
|
}
|
|
764
765
|
|
|
765
|
-
/**
|
|
766
|
-
* Validates a parsed passage to ensure the chapter and verses exist.
|
|
767
|
-
*
|
|
768
|
-
* @param {Object} passage - The parsed passage object to validate.
|
|
769
|
-
* @param {string} reference - The original reference string for error messaging.
|
|
770
|
-
* @return {boolean|Object} True if valid, or an error object if invalid.
|
|
771
|
-
*/
|
|
772
766
|
_isValid(passage, reference) {
|
|
773
|
-
const
|
|
767
|
+
const { book, chapter, verses, type } = passage
|
|
774
768
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (passage.type !== "single_chapter") {
|
|
778
|
-
return {
|
|
779
|
-
error: true,
|
|
780
|
-
code: 101,
|
|
781
|
-
message: {
|
|
782
|
-
chapter_exists: false,
|
|
783
|
-
content: "Possible invalid chapter: " + reference,
|
|
784
|
-
},
|
|
785
|
-
}
|
|
786
|
-
}
|
|
769
|
+
if (!verses.length && type !== this.SINGLE_CHAPTER) {
|
|
770
|
+
return this.validationError(101, `Possible invalid chapter: ${reference}`)
|
|
787
771
|
}
|
|
788
772
|
|
|
789
|
-
|
|
790
|
-
if (
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
if (passage.chapter !== 1) {
|
|
794
|
-
return {
|
|
795
|
-
error: true,
|
|
796
|
-
code: 103,
|
|
797
|
-
message: {
|
|
798
|
-
chapter_exists: false,
|
|
799
|
-
content: `Chapter ${passage.chapter} does not exist in ${passage.book}`,
|
|
800
|
-
},
|
|
801
|
-
}
|
|
802
|
-
}
|
|
773
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
774
|
+
if (!chapterVerses.length) {
|
|
775
|
+
return this.validationError(102, `Chapter ${chapter} does not exist in ${book}`)
|
|
776
|
+
}
|
|
803
777
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
content: `Verse range ${start}-${end} exceeds available verses (1-${verseCount}) in ${passage.book} 1`,
|
|
816
|
-
},
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
return true // If no specific verses or range matches, it’s valid
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// For specific verses in single-chapter books (e.g., "2 John 1:1-3")
|
|
824
|
-
for (let i = 0; i < passage.verses.length; i++) {
|
|
825
|
-
const verseRange = String(passage.verses[i])
|
|
826
|
-
let versesToCheck = verseRange.includes("-") ? verseRange.split("-").map(Number) : [Number(verseRange)]
|
|
827
|
-
|
|
828
|
-
if (versesToCheck.length === 2) {
|
|
829
|
-
const [start, end] = versesToCheck
|
|
830
|
-
versesToCheck = Array.from({ length: end - start + 1 }, (_, idx) => start + idx)
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
for (const verse of versesToCheck) {
|
|
834
|
-
if (verse < 1 || verse > verseCount) {
|
|
835
|
-
return {
|
|
836
|
-
error: true,
|
|
837
|
-
code: 104,
|
|
838
|
-
message: {
|
|
839
|
-
verse_exists: false,
|
|
840
|
-
content: `Verse number ${verse} does not exist in ${passage.book} 1`,
|
|
841
|
-
},
|
|
842
|
-
}
|
|
843
|
-
}
|
|
778
|
+
if (type === this.SINGLE_CHAPTER) {
|
|
779
|
+
const [range] = verses
|
|
780
|
+
if (range) {
|
|
781
|
+
const [start, end] = range.split("-").map(Number)
|
|
782
|
+
if (start < 1 || end > chapterVerses[chapterVerses.length - 1]) {
|
|
783
|
+
return this.validationError(
|
|
784
|
+
104,
|
|
785
|
+
`Verse range ${start}-${end} exceeds available verses (1-${
|
|
786
|
+
chapterVerses[chapterVerses.length - 1]
|
|
787
|
+
}) in ${book} ${chapter}`
|
|
788
|
+
)
|
|
844
789
|
}
|
|
845
790
|
}
|
|
846
791
|
return true
|
|
847
792
|
}
|
|
848
793
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
return {
|
|
852
|
-
error: true,
|
|
853
|
-
code: 102,
|
|
854
|
-
message: {
|
|
855
|
-
chapter_exists: false,
|
|
856
|
-
content: `Chapter ${passage.chapter} does not exist in ${passage.book}`,
|
|
857
|
-
},
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
if (passage.type === "single_chapter") {
|
|
862
|
-
return true // For multi-chapter books, whole chapter is valid if it exists
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
for (let i = 0; i < passage.verses.length; i++) {
|
|
866
|
-
const passageVerses = String(passage.verses[i])
|
|
867
|
-
let verses = passageVerses.includes("-") ? passageVerses.split("-").map(Number) : [Number(passageVerses)]
|
|
868
|
-
|
|
869
|
-
if (verses.length === 2) {
|
|
870
|
-
// Expand the range if there are two numbers
|
|
871
|
-
verses = Array.from({ length: verses[1] - verses[0] + 1 }, (_, index) => verses[0] + index)
|
|
872
|
-
}
|
|
794
|
+
return this.validateVerses(book, chapter, verses, reference)
|
|
795
|
+
}
|
|
873
796
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
}
|
|
797
|
+
validateVerses(book, chapter, verses, reference) {
|
|
798
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
799
|
+
for (const verse of verses) {
|
|
800
|
+
const verseRange = String(verse)
|
|
801
|
+
const verseNumbers = verseRange.includes("-")
|
|
802
|
+
? Array.from(
|
|
803
|
+
{ length: Number(verseRange.split("-")[1]) - Number(verseRange.split("-")[0]) + 1 },
|
|
804
|
+
(_, i) => Number(verseRange.split("-")[0]) + i
|
|
805
|
+
)
|
|
806
|
+
: [Number(verseRange)]
|
|
807
|
+
|
|
808
|
+
for (const v of verseNumbers) {
|
|
809
|
+
if (!chapterVerses.includes(v)) {
|
|
810
|
+
return this.validationError(104, `Verse number ${v} does not exist in ${book} ${chapter}`)
|
|
889
811
|
}
|
|
890
812
|
}
|
|
891
813
|
}
|
|
892
|
-
|
|
893
814
|
return true
|
|
894
815
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
if (version.toLowerCase() === "lxx" && testament.toLowerCase() === "old") {
|
|
903
|
-
return {
|
|
904
|
-
name: "Septuagint",
|
|
905
|
-
value: "LXX",
|
|
906
|
-
abbreviation: "lxx",
|
|
907
|
-
}
|
|
816
|
+
|
|
817
|
+
validationError(code, message) {
|
|
818
|
+
return {
|
|
819
|
+
error: true,
|
|
820
|
+
code,
|
|
821
|
+
message: { verse_exists: code === 104, chapter_exists: code !== 104, content: message },
|
|
908
822
|
}
|
|
823
|
+
}
|
|
909
824
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
825
|
+
_handleVersion(version, testament) {
|
|
826
|
+
const effectiveVersion = this.version || version || "eng"
|
|
827
|
+
const lowerVersion = effectiveVersion.toLowerCase()
|
|
828
|
+
|
|
829
|
+
if (lowerVersion === "lxx" && testament === "old") {
|
|
830
|
+
return { name: "Septuagint", value: "LXX", abbreviation: "lxx" }
|
|
831
|
+
}
|
|
832
|
+
if (lowerVersion === "mt" && testament === "old") {
|
|
833
|
+
return { name: "Masoretic Text", value: "MT", abbreviation: "mt" }
|
|
916
834
|
}
|
|
835
|
+
return { name: "English", value: "ENG", abbreviation: "eng" }
|
|
917
836
|
}
|
|
918
837
|
}
|
|
919
838
|
|
package/src/esv.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require("dotenv").config()
|
|
1
2
|
const axios = require("axios")
|
|
2
3
|
|
|
3
4
|
// Get all books
|
|
@@ -7,7 +8,7 @@ axios
|
|
|
7
8
|
q: "John+3:16",
|
|
8
9
|
},
|
|
9
10
|
headers: {
|
|
10
|
-
Authorization:
|
|
11
|
+
Authorization: `Token ${process.env.ESV_TOKEN}`,
|
|
11
12
|
},
|
|
12
13
|
})
|
|
13
14
|
.then((response) => {
|