codexparser 0.1.54 → 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 -427
- 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,120 +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 3-5" → "2 John 1:3-5"
|
|
235
|
-
parsedPassage.chapter = 1
|
|
236
|
-
parsedPassage.verses.push(part) // e.g., "3-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., "Isaiah 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
|
-
} else {
|
|
270
|
-
parsedPassage.verses.push(Number(part))
|
|
271
|
-
parsedPassage.type = "comma_separated_verses"
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
// Populate passages and scripture after processing all parts
|
|
191
|
+
this.parseReferenceParts(parsedPassage, passage.reference.split(","))
|
|
277
192
|
parsedPassage.passages = this.populate(parsedPassage)
|
|
278
193
|
parsedPassage.scripture = this.scripturize(parsedPassage)
|
|
279
194
|
parsedPassage.valid = this._isValid(parsedPassage, passage.reference)
|
|
280
195
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
parsedPassage.type === "multi_chapter_verse_range" &&
|
|
284
|
-
parts.some((p) => p.includes(":") && p.split(":")[0] !== String(parsedPassage.chapter))
|
|
285
|
-
) {
|
|
286
|
-
const lastPart = parts[parts.length - 1]
|
|
287
|
-
const [endChapter, endVerse] = lastPart.split(":")
|
|
288
|
-
parsedPassage.to = {
|
|
289
|
-
book: book,
|
|
290
|
-
chapter: Number(endChapter),
|
|
291
|
-
verses: endVerse.includes("-") ? [endVerse] : [Number(endVerse)],
|
|
292
|
-
}
|
|
196
|
+
if (parsedPassage.type === this.MULTI_CHAPTER_RANGE) {
|
|
197
|
+
this.handleMultiChapterRange(parsedPassage, passage.reference)
|
|
293
198
|
} else {
|
|
294
|
-
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
|
|
295
232
|
}
|
|
296
233
|
|
|
297
234
|
return parsedPassage
|
|
@@ -300,9 +237,102 @@ class CodexParser {
|
|
|
300
237
|
this.versification()
|
|
301
238
|
return this
|
|
302
239
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
+
|
|
306
336
|
_generateRange(start, end) {
|
|
307
337
|
const range = []
|
|
308
338
|
for (let i = start; i <= end; i++) {
|
|
@@ -315,25 +345,20 @@ class CodexParser {
|
|
|
315
345
|
version = version.toLowerCase()
|
|
316
346
|
if (!this.chapterVerses[book][chapter]) return
|
|
317
347
|
if (!this.versificationDifferences[book]) return
|
|
318
|
-
// Loop through each key-value pair in the dictionary
|
|
319
348
|
for (const [key, value] of Object.entries(this.versificationDifferences[book])) {
|
|
320
|
-
// Check if the key starts with the desired chapter
|
|
321
349
|
if (value[version].startsWith(`${chapter}:`)) {
|
|
322
|
-
// Ensure the version exists in the value object
|
|
323
350
|
if (value[version]) {
|
|
324
|
-
// Extract the verse number from the value
|
|
325
351
|
const verse = value[version].split(":")[1]
|
|
326
352
|
this.chapterVerses[book][chapter].push(Number(verse))
|
|
327
353
|
}
|
|
328
354
|
}
|
|
329
355
|
}
|
|
330
356
|
this.chapterVerses[book][chapter] = Array.from(this.chapterVerses[book][chapter])
|
|
331
|
-
return this.chapterVerses
|
|
357
|
+
return this.chapterVerses
|
|
332
358
|
}
|
|
333
359
|
|
|
334
360
|
_setVersion(book, chapter, version) {
|
|
335
361
|
this.version = version ? version : "eng"
|
|
336
|
-
|
|
337
362
|
if (this.version !== "eng") {
|
|
338
363
|
this._searchVersificationDifferences(book, chapter, version)
|
|
339
364
|
}
|
|
@@ -343,31 +368,25 @@ class CodexParser {
|
|
|
343
368
|
this.passages.forEach((passage) => {
|
|
344
369
|
const hasVersification = this.versificationDifferences[passage.book]
|
|
345
370
|
passage.passages.forEach((subPassage) => {
|
|
346
|
-
// Apply general versification differences
|
|
347
371
|
if (hasVersification) {
|
|
348
372
|
const key = `${subPassage.chapter}:${subPassage.verse}`
|
|
349
373
|
if (this.versificationDifferences[passage.book][key]) {
|
|
350
374
|
subPassage.versification = this.versificationDifferences[passage.book][key]
|
|
351
375
|
}
|
|
352
376
|
}
|
|
353
|
-
|
|
354
|
-
// Handle specific version adjustments for "lxx" or "mt"
|
|
355
377
|
if (passage.version) {
|
|
356
378
|
const versionAbbreviation = passage.version.abbreviation
|
|
357
379
|
const versionType =
|
|
358
380
|
versionAbbreviation === "lxx" ? "lxx" : versionAbbreviation === "mt" ? "mt" : null
|
|
359
|
-
|
|
360
381
|
if (versionType) {
|
|
361
382
|
const versionReference = `${subPassage.chapter}:${subPassage.verse}`
|
|
362
|
-
|
|
363
|
-
// Look for matching versification based on the version type (lxx or mt)
|
|
364
383
|
for (const versification in this.versificationDifferences[passage.book]) {
|
|
365
384
|
if (
|
|
366
385
|
this.versificationDifferences[passage.book][versification][versionType] ===
|
|
367
386
|
versionReference
|
|
368
387
|
) {
|
|
369
388
|
subPassage.versification = this.versificationDifferences[passage.book][versification]
|
|
370
|
-
break
|
|
389
|
+
break
|
|
371
390
|
}
|
|
372
391
|
}
|
|
373
392
|
}
|
|
@@ -376,96 +395,65 @@ class CodexParser {
|
|
|
376
395
|
})
|
|
377
396
|
}
|
|
378
397
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
* @return {Array} An array of passage objects with individual verses.
|
|
384
|
-
*/
|
|
385
|
-
populate(parsedPassage) {
|
|
386
|
-
const passages = []
|
|
387
|
-
const { book, chapter, verses, type, to } = parsedPassage
|
|
388
|
-
const version = parsedPassage.version ? parsedPassage.version.abbreviation : "eng"
|
|
389
|
-
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)
|
|
390
402
|
|
|
391
|
-
|
|
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
|
+
}
|
|
392
407
|
|
|
393
|
-
if (type ===
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
405
|
-
})
|
|
406
|
-
}
|
|
407
|
-
} else if (type === "comma_separated_verses" || type === "chapter_verse_range") {
|
|
408
|
-
// Handle comma-separated verses or single-chapter verse ranges (e.g., "Isaiah 40:3-5,8-9" or "2 John 1:1-3")
|
|
409
|
-
verses.forEach((verse) => {
|
|
410
|
-
if (typeof verse === "string" && verse.includes("-")) {
|
|
411
|
-
const [start, end] = verse.split("-").map(Number)
|
|
412
|
-
for (let i = start; i <= end; i++) {
|
|
413
|
-
passages.push({ book, chapter: Number(chapter), verse: i })
|
|
414
|
-
}
|
|
415
|
-
} else {
|
|
416
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
417
|
-
}
|
|
418
|
-
})
|
|
419
|
-
} else if (type === "chapter_range") {
|
|
420
|
-
// Handle ranges of chapters (e.g., "Isaiah 3-5")
|
|
421
|
-
for (let currentChapter = chapter; currentChapter <= to.chapter; currentChapter++) {
|
|
422
|
-
if (this.chapterVerses[book] && this.chapterVerses[book][currentChapter]) {
|
|
423
|
-
this.chapterVerses[book][currentChapter].forEach((verse) => {
|
|
424
|
-
passages.push({ book, chapter: Number(currentChapter), verse: Number(verse) })
|
|
425
|
-
})
|
|
426
|
-
}
|
|
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
|
+
)
|
|
427
419
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
420
|
+
return passages
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (type === this.MULTI_CHAPTER_RANGE) {
|
|
424
|
+
const passages = []
|
|
431
425
|
const startVerse = verses[0].includes("-") ? Number(verses[0].split("-")[0]) : Number(verses[0])
|
|
432
|
-
const endChapter = to.chapter
|
|
433
426
|
const endVerse = to.verses[0].includes("-") ? Number(to.verses[0].split("-")[1]) : Number(to.verses[0])
|
|
434
427
|
|
|
435
|
-
for (let
|
|
436
|
-
const chapterVerses = this.
|
|
437
|
-
|
|
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
|
+
}
|
|
438
436
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
currentChapter === endChapter ? endVerse : chapterVerses[chapterVerses.length - 1]
|
|
437
|
+
return []
|
|
438
|
+
}
|
|
442
439
|
|
|
443
|
-
|
|
444
|
-
|
|
440
|
+
expandVerses(book, chapter, verses) {
|
|
441
|
+
const passages = []
|
|
442
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
443
|
+
|
|
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 })
|
|
445
449
|
}
|
|
450
|
+
} else {
|
|
451
|
+
passages.push({ book, chapter, verse: Number(verse) })
|
|
446
452
|
}
|
|
447
|
-
}
|
|
448
|
-
// Handle single chapter:verse references (e.g., "2 John 1:1")
|
|
449
|
-
verses.forEach((verse) => {
|
|
450
|
-
passages.push({ book, chapter: Number(chapter), verse: Number(verse) })
|
|
451
|
-
})
|
|
452
|
-
} else if (type === "single_chapter_book_verse_range") {
|
|
453
|
-
// Handle ranges in single-chapter books (e.g., "Jude 5-7")
|
|
454
|
-
const [startVerse, endVerse] = verses[0].split("-").map(Number)
|
|
455
|
-
for (let i = startVerse; i <= endVerse; i++) {
|
|
456
|
-
passages.push({ book, chapter: 1, verse: i })
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
453
|
+
})
|
|
460
454
|
return passages
|
|
461
455
|
}
|
|
462
456
|
|
|
463
|
-
/**
|
|
464
|
-
* Converts a book name to its corresponding full name from the bible.
|
|
465
|
-
*
|
|
466
|
-
* @param {string} book - The abbreviated or partial name of the book.
|
|
467
|
-
* @return {string|undefined} The full name of the book if found, otherwise undefined.
|
|
468
|
-
*/
|
|
469
457
|
bookify(book) {
|
|
470
458
|
if (typeof book !== "string") {
|
|
471
459
|
book = book[0]
|
|
@@ -487,78 +475,105 @@ class CodexParser {
|
|
|
487
475
|
return bookified
|
|
488
476
|
}
|
|
489
477
|
|
|
490
|
-
/**
|
|
491
|
-
* Returns the passages stored in the object.
|
|
492
|
-
*
|
|
493
|
-
* @return {array} The passages stored in the object.
|
|
494
|
-
*/
|
|
495
478
|
getPassages() {
|
|
496
|
-
|
|
497
|
-
const passagesArray = [...this.passages] // Clone the array to avoid mutation
|
|
479
|
+
const passagesArray = [...this.passages]
|
|
498
480
|
|
|
499
|
-
// Add first() method directly to the array
|
|
500
481
|
passagesArray.first = function () {
|
|
501
482
|
return this.length > 0 ? this[0] : null
|
|
502
483
|
}
|
|
503
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
|
+
|
|
504
541
|
return passagesArray
|
|
505
542
|
}
|
|
506
543
|
|
|
507
|
-
// New first() method that can be chained after getPassages()
|
|
508
544
|
first() {
|
|
509
545
|
return this.passages.length > 0 ? this.passages[0] : null
|
|
510
546
|
}
|
|
511
547
|
|
|
512
|
-
/**
|
|
513
|
-
* Converts a passage object into a scripturize object with human-readable name, chapter and verses and a hash.
|
|
514
|
-
*
|
|
515
|
-
* @param {object} passage - The passage object to scripturize.
|
|
516
|
-
* @return {object} The object with the human-readable name, chapter and verses and a hash.
|
|
517
|
-
*/
|
|
518
548
|
scripturize(passage) {
|
|
519
|
-
// Helper function to format a single chapter:verse combination
|
|
520
549
|
const formatChapterVerse = (chapter, verses) => {
|
|
521
550
|
if (!chapter || !verses || verses.length === 0) return ""
|
|
522
551
|
if (verses.length === 1) {
|
|
523
552
|
return `${chapter}:${verses[0]}`
|
|
524
553
|
}
|
|
525
|
-
|
|
526
|
-
// Check if verses are continuous (e.g., [1, 2, 3, 4, 5] -> "1-5")
|
|
527
554
|
const isRange = verses.every((v, i, arr) => i === 0 || v === arr[i - 1] + 1)
|
|
528
|
-
|
|
529
555
|
if (isRange) {
|
|
530
556
|
return `${chapter}:${verses[0]}-${verses[verses.length - 1]}`
|
|
531
557
|
}
|
|
532
|
-
|
|
533
|
-
// Comma-separated (e.g., [1, 3, 5] -> "1,3,5")
|
|
534
558
|
return `${chapter}:${verses.join(",")}`
|
|
535
559
|
}
|
|
536
560
|
|
|
537
|
-
// Start constructing the passage string
|
|
538
561
|
let combined = `${passage.book}`
|
|
539
|
-
|
|
540
562
|
if (passage.type === "multi_chapter_verse_range" && passage.to) {
|
|
541
|
-
// Multi-chapter verse range
|
|
542
|
-
|
|
543
563
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
544
564
|
passage.to.chapter,
|
|
545
565
|
passage.to.verses
|
|
546
566
|
)}`
|
|
547
567
|
} else if (passage.type === "chapter_verse_range") {
|
|
548
|
-
// Single-chapter verse range
|
|
549
568
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
550
569
|
} else if (passage.type === "comma_separated_verses") {
|
|
551
|
-
// Comma-separated verses
|
|
552
570
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
553
571
|
} else if (passage.type === "chapter_range" && passage.to) {
|
|
554
|
-
// Chapter range
|
|
555
572
|
combined += ` ${passage.chapter}-${passage.to.chapter}`
|
|
556
573
|
} else {
|
|
557
|
-
// Single chapter or single verse
|
|
558
574
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
559
575
|
}
|
|
560
576
|
|
|
561
|
-
// Generate the chapter:verse (cv) string
|
|
562
577
|
const cv = passage.to
|
|
563
578
|
? `${formatChapterVerse(passage.chapter, passage.verses)}-${formatChapterVerse(
|
|
564
579
|
passage.to.chapter,
|
|
@@ -566,10 +581,8 @@ class CodexParser {
|
|
|
566
581
|
)}`
|
|
567
582
|
: formatChapterVerse(passage.chapter, passage.verses)
|
|
568
583
|
|
|
569
|
-
// Generate the hash
|
|
570
584
|
const hash = `${passage.book.toLowerCase()}_${cv.replace(/:/g, ".").replace(/-/g, ".")}`
|
|
571
585
|
|
|
572
|
-
// Return the final scripture object
|
|
573
586
|
return {
|
|
574
587
|
passage: combined,
|
|
575
588
|
cv: cv,
|
|
@@ -577,27 +590,16 @@ class CodexParser {
|
|
|
577
590
|
}
|
|
578
591
|
}
|
|
579
592
|
|
|
580
|
-
/**
|
|
581
|
-
* Combine multiple passages into one. The method checks for duplicates, merges overlapping or adjacent ranges,
|
|
582
|
-
* and builds the original and scripture properties.
|
|
583
|
-
* **This method will always combine based on English versification. LXX and MT versifications will be reflected in the combined passage.passages.versification.**
|
|
584
|
-
* This method will fail if the passages are not to the same book and chapter.
|
|
585
|
-
* TODO: Add support for MT and LXX
|
|
586
|
-
* @param {array} passages - An array of passage objects to combine.
|
|
587
|
-
* @return {object} The combined passage object.
|
|
588
|
-
*/
|
|
589
593
|
combine(passages) {
|
|
590
594
|
if (!passages || passages.length === 0) {
|
|
591
595
|
throw new Error("No passages provided to join.")
|
|
592
596
|
}
|
|
593
597
|
|
|
594
|
-
// Ensure all passages are from the same book
|
|
595
598
|
const uniqueBooks = [...new Set(passages.map((p) => p.book))]
|
|
596
599
|
if (uniqueBooks.length > 1) {
|
|
597
600
|
throw new Error("Passages must be from the same book to join.")
|
|
598
601
|
}
|
|
599
602
|
|
|
600
|
-
// Start with the base object
|
|
601
603
|
const combined = {
|
|
602
604
|
...passages[0],
|
|
603
605
|
verses: [],
|
|
@@ -605,36 +607,45 @@ class CodexParser {
|
|
|
605
607
|
to: null,
|
|
606
608
|
scripture: {},
|
|
607
609
|
type: null,
|
|
610
|
+
start: null,
|
|
611
|
+
end: null,
|
|
608
612
|
}
|
|
609
613
|
|
|
610
614
|
const chapterVerses = {}
|
|
611
615
|
let firstChapter = null
|
|
612
616
|
let lastChapter = null
|
|
617
|
+
let firstVerse = null
|
|
618
|
+
let lastVerse = null
|
|
613
619
|
|
|
614
|
-
// Collect all verses and passages, grouped by chapter
|
|
615
620
|
passages.forEach((passage) => {
|
|
616
621
|
passage.passages.forEach((p) => {
|
|
617
622
|
if (!chapterVerses[p.chapter]) {
|
|
618
623
|
chapterVerses[p.chapter] = new Set()
|
|
619
624
|
}
|
|
620
625
|
chapterVerses[p.chapter].add(p.verse)
|
|
621
|
-
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
|
+
}
|
|
622
640
|
})
|
|
623
641
|
|
|
624
|
-
// Track first and last chapters
|
|
625
642
|
const chapters = passage.passages.map((p) => p.chapter)
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
if (!lastChapter || Math.max(...chapters) > lastChapter) {
|
|
630
|
-
lastChapter = Math.max(...chapters)
|
|
631
|
-
}
|
|
643
|
+
firstChapter = firstChapter === null ? Math.min(...chapters) : Math.min(firstChapter, ...chapters)
|
|
644
|
+
lastChapter = lastChapter === null ? Math.max(...chapters) : Math.max(lastChapter, ...chapters)
|
|
632
645
|
})
|
|
633
646
|
|
|
634
|
-
// Ensure unique and sorted passages
|
|
635
647
|
combined.passages = Array.from(new Set(combined.passages.map(JSON.stringify))).map(JSON.parse)
|
|
636
648
|
|
|
637
|
-
// Process chapter and verse data
|
|
638
649
|
const chapterStrings = []
|
|
639
650
|
const sortedChapters = Object.keys(chapterVerses)
|
|
640
651
|
.map(Number)
|
|
@@ -645,13 +656,12 @@ class CodexParser {
|
|
|
645
656
|
const mergedVerses = this.mergeRanges(verses)
|
|
646
657
|
chapterStrings.push(`${chapter}:${mergedVerses.join(",")}`)
|
|
647
658
|
if (chapter === firstChapter) {
|
|
648
|
-
combined.verses = mergedVerses
|
|
659
|
+
combined.verses = mergedVerses
|
|
649
660
|
}
|
|
650
661
|
})
|
|
651
662
|
|
|
652
|
-
// Handle multi-chapter ranges
|
|
653
663
|
if (firstChapter !== lastChapter) {
|
|
654
|
-
combined.type =
|
|
664
|
+
combined.type = this.MULTI_CHAPTER_RANGE
|
|
655
665
|
combined.to = {
|
|
656
666
|
book: combined.book,
|
|
657
667
|
chapter: lastChapter,
|
|
@@ -661,25 +671,37 @@ class CodexParser {
|
|
|
661
671
|
","
|
|
662
672
|
)}; ${lastChapter}:${combined.to.verses.join(",")}`
|
|
663
673
|
} else {
|
|
664
|
-
|
|
665
|
-
if (combined.verses.length > 1) {
|
|
666
|
-
combined.type = "chapter_verse_range"
|
|
667
|
-
} else {
|
|
668
|
-
combined.type = "chapter_verse"
|
|
669
|
-
}
|
|
674
|
+
combined.type = combined.verses.length > 1 ? this.CHAPTER_VERSE_RANGE : this.CHAPTER_VERSE
|
|
670
675
|
combined.original = `${combined.book} ${firstChapter}:${combined.verses.join(",")}`
|
|
671
676
|
}
|
|
672
677
|
|
|
673
|
-
// Build the scripture property
|
|
674
678
|
const chapterString = chapterStrings.join(";")
|
|
675
679
|
combined.scripture = {
|
|
676
680
|
passage: `${combined.book} ${chapterString}`,
|
|
677
681
|
cv: chapterString,
|
|
678
|
-
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])),
|
|
679
694
|
}
|
|
695
|
+
|
|
696
|
+
// Reattach the reference method to the combined passage
|
|
697
|
+
combined.reference = function () {
|
|
698
|
+
return this.scripture.passage
|
|
699
|
+
}
|
|
700
|
+
|
|
680
701
|
if (combined.to === null) {
|
|
681
702
|
delete combined.to
|
|
682
703
|
}
|
|
704
|
+
|
|
683
705
|
return combined
|
|
684
706
|
}
|
|
685
707
|
|
|
@@ -693,7 +715,6 @@ class CodexParser {
|
|
|
693
715
|
if (sortedVerses[i] === end + 1) {
|
|
694
716
|
end = sortedVerses[i]
|
|
695
717
|
} else {
|
|
696
|
-
// Push range or single verse
|
|
697
718
|
if (start === end) {
|
|
698
719
|
merged.push(`${start}`)
|
|
699
720
|
} else {
|
|
@@ -704,7 +725,6 @@ class CodexParser {
|
|
|
704
725
|
}
|
|
705
726
|
}
|
|
706
727
|
|
|
707
|
-
// Push the final range or single verse
|
|
708
728
|
if (start === end) {
|
|
709
729
|
merged.push(`${start}`)
|
|
710
730
|
} else {
|
|
@@ -715,24 +735,17 @@ class CodexParser {
|
|
|
715
735
|
}
|
|
716
736
|
|
|
717
737
|
getToc(version = "ESV") {
|
|
718
|
-
// Initialize the table of contents (toc)
|
|
719
738
|
const toc = {}
|
|
720
|
-
|
|
721
|
-
// Add Old Testament books and their chapters/verses to toc
|
|
722
739
|
this.bible.old.forEach((book) => {
|
|
723
740
|
if (this.chapterVerses[book]) {
|
|
724
741
|
toc[book] = this.chapterVerses[book]
|
|
725
742
|
}
|
|
726
743
|
})
|
|
727
|
-
|
|
728
|
-
// Add New Testament books and their chapters/verses to toc
|
|
729
744
|
this.bible.new.forEach((book) => {
|
|
730
745
|
if (this.chapterVerses[book]) {
|
|
731
746
|
toc[book] = this.chapterVerses[book]
|
|
732
747
|
}
|
|
733
748
|
})
|
|
734
|
-
|
|
735
|
-
// Merge in single-chapter books if not already in toc
|
|
736
749
|
this.singleChapterBook.forEach((item) => {
|
|
737
750
|
Object.keys(item).forEach((book) => {
|
|
738
751
|
if (!toc[book]) {
|
|
@@ -740,8 +753,6 @@ class CodexParser {
|
|
|
740
753
|
}
|
|
741
754
|
})
|
|
742
755
|
})
|
|
743
|
-
|
|
744
|
-
// Sort the keys of toc by canonical order
|
|
745
756
|
const orderedToc = {}
|
|
746
757
|
const canonicalOrder = [...this.bible.old, ...this.bible.new]
|
|
747
758
|
canonicalOrder.forEach((book) => {
|
|
@@ -749,162 +760,79 @@ class CodexParser {
|
|
|
749
760
|
orderedToc[book] = toc[book]
|
|
750
761
|
}
|
|
751
762
|
})
|
|
752
|
-
|
|
753
763
|
return orderedToc
|
|
754
764
|
}
|
|
755
765
|
|
|
756
|
-
/**
|
|
757
|
-
* Validates a parsed passage to ensure the chapter and verses exist.
|
|
758
|
-
*
|
|
759
|
-
* @param {Object} passage - The parsed passage object to validate.
|
|
760
|
-
* @param {string} reference - The original reference string for error messaging.
|
|
761
|
-
* @return {boolean|Object} True if valid, or an error object if invalid.
|
|
762
|
-
*/
|
|
763
766
|
_isValid(passage, reference) {
|
|
764
|
-
const
|
|
767
|
+
const { book, chapter, verses, type } = passage
|
|
765
768
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (passage.type !== "single_chapter") {
|
|
769
|
-
return {
|
|
770
|
-
error: true,
|
|
771
|
-
code: 101,
|
|
772
|
-
message: {
|
|
773
|
-
chapter_exists: false,
|
|
774
|
-
content: "Possible invalid chapter: " + reference,
|
|
775
|
-
},
|
|
776
|
-
}
|
|
777
|
-
}
|
|
769
|
+
if (!verses.length && type !== this.SINGLE_CHAPTER) {
|
|
770
|
+
return this.validationError(101, `Possible invalid chapter: ${reference}`)
|
|
778
771
|
}
|
|
779
772
|
|
|
780
|
-
|
|
781
|
-
if (
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
if (passage.chapter !== 1) {
|
|
785
|
-
return {
|
|
786
|
-
error: true,
|
|
787
|
-
code: 103,
|
|
788
|
-
message: {
|
|
789
|
-
chapter_exists: false,
|
|
790
|
-
content: `Chapter ${passage.chapter} does not exist in ${passage.book}`,
|
|
791
|
-
},
|
|
792
|
-
}
|
|
793
|
-
}
|
|
773
|
+
const chapterVerses = this.getChapterVerses(book, chapter)
|
|
774
|
+
if (!chapterVerses.length) {
|
|
775
|
+
return this.validationError(102, `Chapter ${chapter} does not exist in ${book}`)
|
|
776
|
+
}
|
|
794
777
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
content: `Verse range ${start}-${end} exceeds available verses (1-${verseCount}) in ${passage.book} 1`,
|
|
807
|
-
},
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
return true // If no specific verses or range matches, it’s valid
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// For specific verses in single-chapter books (e.g., "2 John 1:1-3")
|
|
815
|
-
for (let i = 0; i < passage.verses.length; i++) {
|
|
816
|
-
const verseRange = String(passage.verses[i])
|
|
817
|
-
let versesToCheck = verseRange.includes("-") ? verseRange.split("-").map(Number) : [Number(verseRange)]
|
|
818
|
-
|
|
819
|
-
if (versesToCheck.length === 2) {
|
|
820
|
-
const [start, end] = versesToCheck
|
|
821
|
-
versesToCheck = Array.from({ length: end - start + 1 }, (_, idx) => start + idx)
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
for (const verse of versesToCheck) {
|
|
825
|
-
if (verse < 1 || verse > verseCount) {
|
|
826
|
-
return {
|
|
827
|
-
error: true,
|
|
828
|
-
code: 104,
|
|
829
|
-
message: {
|
|
830
|
-
verse_exists: false,
|
|
831
|
-
content: `Verse number ${verse} does not exist in ${passage.book} 1`,
|
|
832
|
-
},
|
|
833
|
-
}
|
|
834
|
-
}
|
|
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
|
+
)
|
|
835
789
|
}
|
|
836
790
|
}
|
|
837
791
|
return true
|
|
838
792
|
}
|
|
839
793
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
return {
|
|
843
|
-
error: true,
|
|
844
|
-
code: 102,
|
|
845
|
-
message: {
|
|
846
|
-
chapter_exists: false,
|
|
847
|
-
content: `Chapter ${passage.chapter} does not exist in ${passage.book}`,
|
|
848
|
-
},
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
if (passage.type === "single_chapter") {
|
|
853
|
-
return true // For multi-chapter books, whole chapter is valid if it exists
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
for (let i = 0; i < passage.verses.length; i++) {
|
|
857
|
-
const passageVerses = String(passage.verses[i])
|
|
858
|
-
let verses = passageVerses.includes("-") ? passageVerses.split("-").map(Number) : [Number(passageVerses)]
|
|
859
|
-
|
|
860
|
-
if (verses.length === 2) {
|
|
861
|
-
// Expand the range if there are two numbers
|
|
862
|
-
verses = Array.from({ length: verses[1] - verses[0] + 1 }, (_, index) => verses[0] + index)
|
|
863
|
-
}
|
|
794
|
+
return this.validateVerses(book, chapter, verses, reference)
|
|
795
|
+
}
|
|
864
796
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
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}`)
|
|
880
811
|
}
|
|
881
812
|
}
|
|
882
813
|
}
|
|
883
|
-
|
|
884
814
|
return true
|
|
885
815
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
}
|
|
893
|
-
if (version.toLowerCase() === "lxx" && testament.toLowerCase() === "old") {
|
|
894
|
-
return {
|
|
895
|
-
name: "Septuagint",
|
|
896
|
-
value: "LXX",
|
|
897
|
-
abbreviation: "lxx",
|
|
898
|
-
}
|
|
816
|
+
|
|
817
|
+
validationError(code, message) {
|
|
818
|
+
return {
|
|
819
|
+
error: true,
|
|
820
|
+
code,
|
|
821
|
+
message: { verse_exists: code === 104, chapter_exists: code !== 104, content: message },
|
|
899
822
|
}
|
|
823
|
+
}
|
|
900
824
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
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" }
|
|
907
834
|
}
|
|
835
|
+
return { name: "English", value: "ENG", abbreviation: "eng" }
|
|
908
836
|
}
|
|
909
837
|
}
|
|
910
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) => {
|