codexparser 0.1.79 → 0.1.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.trunk/configs/.markdownlint.yaml +2 -0
- package/.trunk/trunk.yaml +32 -0
- package/package.json +1 -1
- package/src/CodexParser.js +265 -169
- package/src/versifications/psalms.js +103 -108
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# This file controls the behavior of Trunk: https://docs.trunk.io/cli
|
|
2
|
+
# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
|
|
3
|
+
version: 0.1
|
|
4
|
+
cli:
|
|
5
|
+
version: 1.24.0
|
|
6
|
+
# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
|
|
7
|
+
plugins:
|
|
8
|
+
sources:
|
|
9
|
+
- id: trunk
|
|
10
|
+
ref: v1.7.0
|
|
11
|
+
uri: https://github.com/trunk-io/plugins
|
|
12
|
+
# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
|
|
13
|
+
runtimes:
|
|
14
|
+
enabled:
|
|
15
|
+
- node@22.16.0
|
|
16
|
+
- python@3.10.8
|
|
17
|
+
# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
|
|
18
|
+
lint:
|
|
19
|
+
enabled:
|
|
20
|
+
- checkov@3.2.445
|
|
21
|
+
- git-diff-check
|
|
22
|
+
- markdownlint@0.45.0
|
|
23
|
+
- osv-scanner@2.0.3
|
|
24
|
+
- prettier@3.6.0
|
|
25
|
+
- trufflehog@3.89.2
|
|
26
|
+
actions:
|
|
27
|
+
disabled:
|
|
28
|
+
- trunk-announce
|
|
29
|
+
- trunk-check-pre-push
|
|
30
|
+
- trunk-fmt-pre-commit
|
|
31
|
+
enabled:
|
|
32
|
+
- trunk-upgrade-available
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexparser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.81",
|
|
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
|
@@ -23,7 +23,7 @@ class CodexParser {
|
|
|
23
23
|
/**
|
|
24
24
|
* Initializes the parser with default properties and data.
|
|
25
25
|
*/
|
|
26
|
-
constructor() {
|
|
26
|
+
constructor(config = {}) {
|
|
27
27
|
this.found = [] // Array to store detected references
|
|
28
28
|
this.passages = [] // Array to store parsed passages
|
|
29
29
|
this.bible = bible // Bible data (Old/New Testament books)
|
|
@@ -53,9 +53,12 @@ class CodexParser {
|
|
|
53
53
|
this.CHAPTER_RANGE = "chapter_range"
|
|
54
54
|
this.MULTI_CHAPTER_RANGE = "multi_chapter_verse_range"
|
|
55
55
|
this.config = {
|
|
56
|
-
booksOnly: false,
|
|
56
|
+
booksOnly: config.booksOnly ?? false,
|
|
57
|
+
invalid_sequence_strategy: config.invalid_sequence_strategy ?? "include",
|
|
58
|
+
invalid_passage_strategy: config.invalid_passage_strategy ?? "include",
|
|
57
59
|
}
|
|
58
60
|
}
|
|
61
|
+
|
|
59
62
|
/**
|
|
60
63
|
* Sets configuration options for the parser.
|
|
61
64
|
* @param {Object} config - Configuration options.
|
|
@@ -65,6 +68,8 @@ class CodexParser {
|
|
|
65
68
|
options(config) {
|
|
66
69
|
this.config = {
|
|
67
70
|
booksOnly: config.booksOnly ?? false,
|
|
71
|
+
invalid_sequence_strategy: config.invalid_sequence_strategy ?? this.config.invalid_sequence_strategy,
|
|
72
|
+
invalid_passage_strategy: config.invalid_passage_strategy ?? this.config.invalid_passage_strategy,
|
|
68
73
|
}
|
|
69
74
|
return this
|
|
70
75
|
}
|
|
@@ -87,159 +92,213 @@ class CodexParser {
|
|
|
87
92
|
*/
|
|
88
93
|
scan(text) {
|
|
89
94
|
const fullNames = [...this.bible.old, ...this.bible.new]
|
|
90
|
-
const abbreviations = Object.keys(this.abbreviations)
|
|
95
|
+
const abbreviations = Object.keys(this.abbreviations)
|
|
91
96
|
this.found = []
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
// Normalize text for parsing but keep original for originalText
|
|
98
|
+
let normalizedText = text
|
|
99
|
+
.replace(/[“”]/g, "") // Remove curly quotes
|
|
100
|
+
.replace(/\.(?=\d)/g, ":") // Replace periods before digits with colons (e.g., "Re13.8" -> "Re13:8")
|
|
101
|
+
.replace(/\s+/g, " ") // Normalize multiple spaces to single
|
|
102
|
+
const lowercaseBibleFullNames = fullNames.map((book) => book.toLowerCase())
|
|
103
|
+
const lowercaseBibleAbbreviations = abbreviations.map((abbr) => abbr.toLowerCase())
|
|
95
104
|
const lowerCaseText = normalizedText.toLowerCase()
|
|
96
105
|
let i = 0
|
|
97
106
|
|
|
107
|
+
console.log("[Scan] Input text:", text)
|
|
108
|
+
console.log("[Scan] Normalized text:", normalizedText)
|
|
109
|
+
|
|
110
|
+
const isValidChapterVerseChar = (char) => /[^A-Za-z]/.test(char) // Non-letter characters
|
|
111
|
+
const isNextBibleBook = (startIndex) => {
|
|
112
|
+
const textAfterCurrentPosition = lowerCaseText.substring(startIndex).trim()
|
|
113
|
+
// Check if the text starts with a book name or abbreviation followed by a digit
|
|
114
|
+
return (
|
|
115
|
+
lowercaseBibleFullNames.some((book) => {
|
|
116
|
+
if (textAfterCurrentPosition.startsWith(book)) {
|
|
117
|
+
const nextIndex = startIndex + book.length
|
|
118
|
+
const nextChar = lowerCaseText[nextIndex]
|
|
119
|
+
return nextChar && /\d/.test(nextChar)
|
|
120
|
+
}
|
|
121
|
+
return false
|
|
122
|
+
}) ||
|
|
123
|
+
lowercaseBibleAbbreviations.some((abbr) => {
|
|
124
|
+
if (textAfterCurrentPosition.startsWith(abbr)) {
|
|
125
|
+
const nextIndex = startIndex + abbr.length
|
|
126
|
+
const nextChar = lowerCaseText[nextIndex]
|
|
127
|
+
return nextChar && (/\d/.test(nextChar) || /\./.test(nextChar))
|
|
128
|
+
}
|
|
129
|
+
return false
|
|
130
|
+
})
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
const detectSuffix = (startIndex, inputText) => {
|
|
134
|
+
const suffixMatch = inputText.substring(startIndex).match(/\b(LXX|MT)\b/i)
|
|
135
|
+
return suffixMatch ? { version: suffixMatch[0].toUpperCase(), length: suffixMatch[0].length } : null
|
|
136
|
+
}
|
|
137
|
+
|
|
98
138
|
while (i < lowerCaseText.length) {
|
|
99
139
|
let foundBook = null
|
|
100
|
-
let startIndex = i
|
|
101
140
|
let matchedLength = 0
|
|
141
|
+
let originalBookText = ""
|
|
142
|
+
let startIndex = i
|
|
102
143
|
|
|
103
|
-
// Check
|
|
104
|
-
for (let
|
|
144
|
+
// Check full book names
|
|
145
|
+
for (let j = 0; j < lowercaseBibleFullNames.length; j++) {
|
|
146
|
+
const book = lowercaseBibleFullNames[j]
|
|
105
147
|
if (
|
|
106
|
-
lowerCaseText.startsWith(book
|
|
107
|
-
(i + book.length >= lowerCaseText.length ||
|
|
148
|
+
lowerCaseText.startsWith(book, i) &&
|
|
149
|
+
(i + book.length >= lowerCaseText.length || /\d/.test(lowerCaseText[i + book.length]))
|
|
108
150
|
) {
|
|
109
|
-
foundBook =
|
|
151
|
+
foundBook = fullNames[j]
|
|
110
152
|
matchedLength = book.length
|
|
111
|
-
|
|
153
|
+
originalBookText = text.slice(i, i + book.length)
|
|
154
|
+
console.log(`[Scan] Matched full book name: "${foundBook}" at index ${i}`)
|
|
112
155
|
}
|
|
113
156
|
}
|
|
157
|
+
|
|
158
|
+
// Check abbreviations
|
|
114
159
|
if (!foundBook) {
|
|
115
|
-
for (let
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
160
|
+
for (let k = 0; k < lowercaseBibleAbbreviations.length; k++) {
|
|
161
|
+
const abbreviation = lowercaseBibleAbbreviations[k]
|
|
162
|
+
const abbrPattern = abbreviation.replace(/\./g, "\\.?")
|
|
163
|
+
const regex = new RegExp(`^${abbrPattern}(\\.?\\s*\\d)`, "i")
|
|
164
|
+
const match = lowerCaseText.slice(i).match(regex)
|
|
165
|
+
if (match) {
|
|
166
|
+
foundBook = this.abbreviations[abbreviations[k]]
|
|
167
|
+
matchedLength = match[0].length - match[1].length // Exclude chapter-verse part
|
|
168
|
+
originalBookText = text.slice(i, i + matchedLength)
|
|
169
|
+
console.log(
|
|
170
|
+
`[Scan] Matched abbreviation: "${abbreviations[k]}" -> "${foundBook}" at index ${i}`
|
|
171
|
+
)
|
|
123
172
|
}
|
|
124
173
|
}
|
|
125
174
|
}
|
|
126
175
|
|
|
127
176
|
if (foundBook) {
|
|
128
|
-
|
|
129
|
-
let
|
|
130
|
-
let
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Skip spaces
|
|
140
|
-
while (j < lowerCaseText.length && /\s/.test(lowerCaseText[j])) {
|
|
141
|
-
chapterVerse += normalizedText[j]
|
|
142
|
-
j++
|
|
177
|
+
i += matchedLength
|
|
178
|
+
let chapterVerse = ""
|
|
179
|
+
let originalChapterVerseText = ""
|
|
180
|
+
const references = []
|
|
181
|
+
|
|
182
|
+
// Capture chapter-verse until a letter (potential new book) or semicolon
|
|
183
|
+
while (i < normalizedText.length && isValidChapterVerseChar(normalizedText[i])) {
|
|
184
|
+
if (isNextBibleBook(i)) {
|
|
185
|
+
console.log(`[Scan] Detected next book at index ${i}, breaking`)
|
|
186
|
+
break
|
|
143
187
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
188
|
+
if (normalizedText[i] === ";") {
|
|
189
|
+
const formattedReference = chapterVerse.trim().replace(/[^a-zA-Z0-9:,\-]+$/g, "")
|
|
190
|
+
if (formattedReference) {
|
|
191
|
+
// Find the last digit in the reference
|
|
192
|
+
const lastDigitMatch = formattedReference.match(/\d(?=[^0-9]*$)/)
|
|
193
|
+
let endIndex = i - 1 // Default to position before semicolon
|
|
194
|
+
if (lastDigitMatch) {
|
|
195
|
+
const lastDigitIndex = formattedReference.lastIndexOf(lastDigitMatch[0])
|
|
196
|
+
endIndex = startIndex + matchedLength + lastDigitIndex
|
|
197
|
+
}
|
|
198
|
+
references.push({
|
|
199
|
+
reference: formattedReference,
|
|
200
|
+
originalText: (originalBookText + originalChapterVerseText).trim(),
|
|
201
|
+
startIndex,
|
|
202
|
+
endIndex,
|
|
203
|
+
})
|
|
152
204
|
}
|
|
153
|
-
|
|
154
|
-
|
|
205
|
+
chapterVerse = ""
|
|
206
|
+
originalChapterVerseText = ""
|
|
207
|
+
originalBookText = foundBook // Reuse book for semicolon-separated references
|
|
208
|
+
startIndex = i + 1 // Start of next reference
|
|
209
|
+
i++
|
|
210
|
+
continue
|
|
155
211
|
}
|
|
212
|
+
chapterVerse += normalizedText[i]
|
|
213
|
+
originalChapterVerseText += text[i]
|
|
214
|
+
i++
|
|
215
|
+
}
|
|
156
216
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
217
|
+
// Add any remaining reference
|
|
218
|
+
if (chapterVerse.trim().length > 0) {
|
|
219
|
+
const formattedReference = chapterVerse.trim().replace(/[^a-zA-Z0-9:,\-]+$/g, "")
|
|
220
|
+
if (formattedReference) {
|
|
221
|
+
// Find the last digit in the reference
|
|
222
|
+
const lastDigitMatch = formattedReference.match(/\d(?=[^0-9]*$)/)
|
|
223
|
+
let endIndex = i - 1 // Default to last character
|
|
224
|
+
if (lastDigitMatch) {
|
|
225
|
+
const lastDigitIndex = formattedReference.lastIndexOf(lastDigitMatch[0])
|
|
226
|
+
endIndex = startIndex + matchedLength + lastDigitIndex
|
|
227
|
+
}
|
|
228
|
+
references.push({
|
|
229
|
+
reference: formattedReference,
|
|
230
|
+
originalText: (originalBookText + originalChapterVerseText).trim(),
|
|
231
|
+
startIndex,
|
|
232
|
+
endIndex,
|
|
233
|
+
})
|
|
170
234
|
}
|
|
235
|
+
}
|
|
171
236
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
237
|
+
// Process each reference
|
|
238
|
+
references.forEach((refObj) => {
|
|
239
|
+
// Detect version suffix (LXX or MT)
|
|
240
|
+
let version = null
|
|
241
|
+
let originalText = refObj.originalText
|
|
242
|
+
const suffix = detectSuffix(i, text)
|
|
243
|
+
if (suffix) {
|
|
244
|
+
version = suffix.version
|
|
245
|
+
originalText += ` ${version}`
|
|
246
|
+
i += suffix.length
|
|
247
|
+
// Update endIndex if version suffix follows a digit
|
|
248
|
+
if (refObj.endIndex === i - suffix.length - 1) {
|
|
249
|
+
refObj.endIndex = i - 1
|
|
250
|
+
}
|
|
179
251
|
}
|
|
180
252
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
? "multi_chapter_verse_range"
|
|
197
|
-
: "chapter_verse_range"
|
|
198
|
-
} else if (ref.includes(",")) {
|
|
199
|
-
type = "comma_separated_verses"
|
|
200
|
-
} else {
|
|
201
|
-
type = "chapter_verse"
|
|
202
|
-
}
|
|
203
|
-
} else if (ref.includes("-")) {
|
|
204
|
-
type = "chapter_range"
|
|
205
|
-
} else if (/\d/.test(ref)) {
|
|
206
|
-
type = "single_chapter"
|
|
253
|
+
let type
|
|
254
|
+
let ref = refObj.reference.replace(/^\.\s*/, "") // Remove leading period and space
|
|
255
|
+
if (this.config.booksOnly && !ref) {
|
|
256
|
+
type = "book_only"
|
|
257
|
+
} else if (ref.includes(":")) {
|
|
258
|
+
if (ref.includes("-")) {
|
|
259
|
+
const [start, end] = ref.split("-").map((s) => s.trim())
|
|
260
|
+
const startParts = start.split(":").map((s) => s.trim())
|
|
261
|
+
const endParts = end.split(":").map((s) => s.trim())
|
|
262
|
+
type =
|
|
263
|
+
startParts.length > 1 && endParts.length > 1 && startParts[0] !== endParts[0]
|
|
264
|
+
? "multi_chapter_verse_range"
|
|
265
|
+
: "chapter_verse_range"
|
|
266
|
+
} else if (ref.includes(",")) {
|
|
267
|
+
type = "comma_separated_verses"
|
|
207
268
|
} else {
|
|
208
|
-
type = "
|
|
269
|
+
type = "chapter_verse"
|
|
209
270
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
version,
|
|
217
|
-
type,
|
|
218
|
-
originalText: normalizedText.slice(currentStartIndex, endIndex),
|
|
219
|
-
})
|
|
271
|
+
} else if (ref.includes("-")) {
|
|
272
|
+
type = "chapter_range"
|
|
273
|
+
} else if (/\d/.test(ref)) {
|
|
274
|
+
type = "single_chapter"
|
|
275
|
+
} else {
|
|
276
|
+
type = "book_only"
|
|
220
277
|
}
|
|
221
278
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
continue // Process next reference
|
|
279
|
+
const referenceObj = {
|
|
280
|
+
book: foundBook,
|
|
281
|
+
reference: ref,
|
|
282
|
+
version,
|
|
283
|
+
type,
|
|
284
|
+
originalText,
|
|
285
|
+
startIndex: refObj.startIndex,
|
|
286
|
+
endIndex: refObj.endIndex,
|
|
231
287
|
}
|
|
288
|
+
this.found.push(referenceObj)
|
|
289
|
+
console.log(`[Scan] Stored reference: ${JSON.stringify(referenceObj)}`)
|
|
290
|
+
})
|
|
232
291
|
|
|
233
|
-
|
|
234
|
-
|
|
292
|
+
// Skip any trailing spaces after the reference
|
|
293
|
+
while (i < lowerCaseText.length && /\s/.test(lowerCaseText[i])) {
|
|
294
|
+
i++
|
|
235
295
|
}
|
|
236
|
-
|
|
237
|
-
i = j
|
|
238
296
|
} else {
|
|
239
297
|
i++
|
|
240
298
|
}
|
|
241
299
|
}
|
|
242
300
|
|
|
301
|
+
console.log("[Scan] Final found references:", JSON.stringify(this.found, null, 2))
|
|
243
302
|
return this
|
|
244
303
|
}
|
|
245
304
|
|
|
@@ -287,14 +346,16 @@ class CodexParser {
|
|
|
287
346
|
abbr: null,
|
|
288
347
|
}
|
|
289
348
|
|
|
290
|
-
// Clean reference for parsing
|
|
291
|
-
let cleanReference = passage.reference
|
|
292
|
-
if (
|
|
293
|
-
cleanReference = cleanReference.
|
|
349
|
+
// Clean reference for parsing
|
|
350
|
+
let cleanReference = passage.reference.replace(/\s*(LXX|MT)$/i, "").trim()
|
|
351
|
+
if (cleanReference.endsWith(",")) {
|
|
352
|
+
cleanReference = cleanReference.slice(0, -1).trim()
|
|
294
353
|
}
|
|
295
354
|
|
|
296
|
-
// Handle
|
|
297
|
-
if (!cleanReference
|
|
355
|
+
// Handle book-only or empty references
|
|
356
|
+
if (!cleanReference && this.config.booksOnly) {
|
|
357
|
+
parsedPassage.type = "book_only"
|
|
358
|
+
} else if (!cleanReference || cleanReference.match(/^\d+\s*[:;]?\s*$/)) {
|
|
298
359
|
const chapterMatch = cleanReference.match(/\d+/) || ["1"]
|
|
299
360
|
const chapter = Number(chapterMatch[0])
|
|
300
361
|
parsedPassage.chapter = chapter
|
|
@@ -306,7 +367,7 @@ class CodexParser {
|
|
|
306
367
|
parsedPassage.verses = [`${startVerse}-${endVerse}`]
|
|
307
368
|
}
|
|
308
369
|
} else {
|
|
309
|
-
this.parseReferenceParts(parsedPassage, cleanReference
|
|
370
|
+
this.parseReferenceParts(parsedPassage, cleanReference)
|
|
310
371
|
}
|
|
311
372
|
|
|
312
373
|
parsedPassage.passages = this.populate(parsedPassage)
|
|
@@ -371,20 +432,40 @@ class CodexParser {
|
|
|
371
432
|
/**
|
|
372
433
|
* Parses reference parts into chapter and verse components.
|
|
373
434
|
* @param {Object} passage - The passage object to populate.
|
|
374
|
-
* @param {string
|
|
435
|
+
* @param {string} reference - The reference string.
|
|
375
436
|
* @private
|
|
376
437
|
*/
|
|
377
|
-
parseReferenceParts(passage,
|
|
438
|
+
parseReferenceParts(passage, reference) {
|
|
378
439
|
const singleChapterBook = this.singleChapterBook.find((b) => Object.keys(b)[0] === passage.book)
|
|
440
|
+
const parts = reference
|
|
441
|
+
.split(",")
|
|
442
|
+
.map((part) => part.trim())
|
|
443
|
+
.filter((part) => part)
|
|
379
444
|
|
|
380
445
|
parts.forEach((part, index) => {
|
|
381
|
-
part = part.trim()
|
|
382
|
-
if (!part) return // Skip empty parts from trailing commas
|
|
383
446
|
const isFirstPart = index === 0
|
|
384
447
|
|
|
385
|
-
// Handle chapter
|
|
448
|
+
// Handle multi-chapter ranges (e.g., "2:1-3:19")
|
|
449
|
+
if (part.includes("-") && part.includes(":")) {
|
|
450
|
+
const [start, end] = part.split("-").map((s) => s.trim())
|
|
451
|
+
const startParts = start.split(/[:.]/).map((s) => s.trim())
|
|
452
|
+
const endParts = end.split(/[:.]/).map((s) => s.trim())
|
|
453
|
+
if (startParts.length > 1 && endParts.length > 1 && startParts[0] !== endParts[0]) {
|
|
454
|
+
passage.type = this.MULTI_CHAPTER_RANGE
|
|
455
|
+
passage.chapter = Number(startParts[0])
|
|
456
|
+
passage.verses = [startParts[1] || "1"]
|
|
457
|
+
passage.to = {
|
|
458
|
+
book: passage.book,
|
|
459
|
+
chapter: Number(endParts[0]),
|
|
460
|
+
verses: [endParts[1] || "1"],
|
|
461
|
+
}
|
|
462
|
+
return
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Handle chapter-only references (e.g., "3")
|
|
386
467
|
if (!part.includes(":") && !part.includes("-") && !singleChapterBook) {
|
|
387
|
-
const chapter = Number(part.replace(/[^0-9]/g, ""))
|
|
468
|
+
const chapter = Number(part.replace(/[^0-9]/g, ""))
|
|
388
469
|
if (chapter > 0) {
|
|
389
470
|
passage.chapter = chapter
|
|
390
471
|
passage.type = this.SINGLE_CHAPTER
|
|
@@ -409,6 +490,7 @@ class CodexParser {
|
|
|
409
490
|
}
|
|
410
491
|
})
|
|
411
492
|
}
|
|
493
|
+
|
|
412
494
|
/**
|
|
413
495
|
* Parses chapter-verse references (e.g., "3:16").
|
|
414
496
|
* @param {Object} passage - The passage object.
|
|
@@ -417,10 +499,18 @@ class CodexParser {
|
|
|
417
499
|
* @private
|
|
418
500
|
*/
|
|
419
501
|
parseChapterVerse(passage, part, isFirstPart) {
|
|
420
|
-
const [chapter, verse] = part.split(
|
|
502
|
+
const [chapter, verse] = part.split(/[:.]/).map((s) => s.trim())
|
|
421
503
|
if (isFirstPart) passage.chapter = Number(chapter)
|
|
422
|
-
passage.type = verse.includes("-")
|
|
423
|
-
|
|
504
|
+
passage.type = verse.includes("-")
|
|
505
|
+
? this.CHAPTER_VERSE_RANGE
|
|
506
|
+
: verse.includes(",")
|
|
507
|
+
? this.COMMA_SEPARATED
|
|
508
|
+
: this.CHAPTER_VERSE
|
|
509
|
+
if (verse.includes(",")) {
|
|
510
|
+
passage.verses.push(...verse.split(",").map((v) => v.trim()))
|
|
511
|
+
} else {
|
|
512
|
+
passage.verses.push(verse)
|
|
513
|
+
}
|
|
424
514
|
}
|
|
425
515
|
|
|
426
516
|
/**
|
|
@@ -440,9 +530,13 @@ class CodexParser {
|
|
|
440
530
|
passage.chapter = 1
|
|
441
531
|
passage.verses.push(part)
|
|
442
532
|
passage.type = this.CHAPTER_VERSE_RANGE
|
|
533
|
+
} else if (part.includes(",")) {
|
|
534
|
+
passage.chapter = 1
|
|
535
|
+
passage.verses.push(...part.split(",").map((v) => v.trim()))
|
|
536
|
+
passage.type = this.COMMA_SEPARATED
|
|
443
537
|
} else {
|
|
444
538
|
const num = Number(part)
|
|
445
|
-
if (num >
|
|
539
|
+
if (num > 0) {
|
|
446
540
|
passage.chapter = 1
|
|
447
541
|
passage.verses.push(num)
|
|
448
542
|
passage.type = this.CHAPTER_VERSE
|
|
@@ -508,13 +602,13 @@ class CodexParser {
|
|
|
508
602
|
*/
|
|
509
603
|
handleMultiChapterRange(passage, reference) {
|
|
510
604
|
const parts = reference.split(",")
|
|
511
|
-
const lastPart = parts[parts.length - 1]
|
|
512
|
-
const [endChapter, endVerse] = lastPart.split(
|
|
605
|
+
const lastPart = parts[parts.length - 1].trim()
|
|
606
|
+
const [endChapter, endVerse] = lastPart.split(/[:.]/).map((s) => s.trim())
|
|
513
607
|
if (endChapter !== String(passage.chapter)) {
|
|
514
608
|
passage.to = {
|
|
515
609
|
book: passage.book,
|
|
516
610
|
chapter: Number(endChapter),
|
|
517
|
-
verses: endVerse
|
|
611
|
+
verses: endVerse ? [endVerse] : ["1"],
|
|
518
612
|
}
|
|
519
613
|
}
|
|
520
614
|
}
|
|
@@ -563,17 +657,16 @@ class CodexParser {
|
|
|
563
657
|
}
|
|
564
658
|
}
|
|
565
659
|
}
|
|
566
|
-
// Ensure unique verses and update the singleChapterBook entry
|
|
567
660
|
singleChapterBook[book][chapter] = Array.from(new Set(singleChapterBook[book][chapter]))
|
|
568
|
-
return singleChapterBook[book]
|
|
661
|
+
return singleChapterBook[book]
|
|
569
662
|
}
|
|
570
663
|
|
|
571
664
|
// Handle all other books using chapterVerses
|
|
572
665
|
if (!this.chapterVerses[book][chapter]) {
|
|
573
|
-
return
|
|
666
|
+
return
|
|
574
667
|
}
|
|
575
668
|
if (!this.versificationDifferences[book]) {
|
|
576
|
-
return
|
|
669
|
+
return
|
|
577
670
|
}
|
|
578
671
|
for (const [key, value] of Object.entries(this.versificationDifferences[book])) {
|
|
579
672
|
if (value[version].startsWith(`${chapter}:`)) {
|
|
@@ -583,8 +676,8 @@ class CodexParser {
|
|
|
583
676
|
}
|
|
584
677
|
}
|
|
585
678
|
}
|
|
586
|
-
this.chapterVerses[book][chapter] = Array.from(this.chapterVerses[book][chapter])
|
|
587
|
-
return this.chapterVerses
|
|
679
|
+
this.chapterVerses[book][chapter] = Array.from(new Set(this.chapterVerses[book][chapter]))
|
|
680
|
+
return this.chapterVerses
|
|
588
681
|
}
|
|
589
682
|
|
|
590
683
|
/**
|
|
@@ -656,24 +749,31 @@ class CodexParser {
|
|
|
656
749
|
|
|
657
750
|
if (type === this.CHAPTER_RANGE) {
|
|
658
751
|
const passages = []
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
752
|
+
if (to && to.chapter) {
|
|
753
|
+
for (let ch = chapter; ch <= to.chapter; ch++) {
|
|
754
|
+
const chapterVerses = this.getChapterVerses(book, ch)
|
|
755
|
+
passages.push(
|
|
756
|
+
...this.expandVerses(book, ch, [
|
|
757
|
+
`${chapterVerses[0]}-${chapterVerses[chapterVerses.length - 1]}`,
|
|
758
|
+
])
|
|
759
|
+
)
|
|
760
|
+
}
|
|
664
761
|
}
|
|
665
762
|
return passages
|
|
666
763
|
}
|
|
667
764
|
|
|
668
765
|
if (type === this.MULTI_CHAPTER_RANGE) {
|
|
669
766
|
const passages = []
|
|
670
|
-
const startVerse = verses[0]
|
|
671
|
-
const endVerse = to
|
|
767
|
+
const startVerse = verses[0]?.includes("-") ? Number(verses[0].split("-")[0]) : Number(verses[0]) || 1
|
|
768
|
+
const endVerse = to?.verses?.[0]?.includes("-")
|
|
769
|
+
? Number(to.verses[0].split("-")[1])
|
|
770
|
+
: Number(to?.verses?.[0]) || 1
|
|
771
|
+
const endChapter = to?.chapter || chapter
|
|
672
772
|
|
|
673
|
-
for (let ch = chapter; ch <=
|
|
773
|
+
for (let ch = chapter; ch <= endChapter; ch++) {
|
|
674
774
|
const chapterVerses = this.getChapterVerses(book, ch)
|
|
675
775
|
const from = ch === chapter ? startVerse : chapterVerses[0]
|
|
676
|
-
const toVerse = ch ===
|
|
776
|
+
const toVerse = ch === endChapter ? endVerse : chapterVerses[chapterVerses.length - 1]
|
|
677
777
|
passages.push(...this.expandVerses(book, ch, [`${from}-${toVerse}`]))
|
|
678
778
|
}
|
|
679
779
|
return passages
|
|
@@ -700,7 +800,10 @@ class CodexParser {
|
|
|
700
800
|
passages.push({ book, chapter, verse: i })
|
|
701
801
|
}
|
|
702
802
|
} else {
|
|
703
|
-
|
|
803
|
+
const verseNum = Number(verse)
|
|
804
|
+
if (!isNaN(verseNum) && verseNum > 0) {
|
|
805
|
+
passages.push({ book, chapter, verse: verseNum })
|
|
806
|
+
}
|
|
704
807
|
}
|
|
705
808
|
})
|
|
706
809
|
return passages
|
|
@@ -716,15 +819,13 @@ class CodexParser {
|
|
|
716
819
|
book = book[0]
|
|
717
820
|
}
|
|
718
821
|
book = book.toLowerCase()
|
|
719
|
-
// Check if book is an abbreviation
|
|
720
822
|
let bookified = this.abbreviations[Object.keys(this.abbreviations).find((abbr) => abbr.toLowerCase() === book)]
|
|
721
823
|
if (bookified) {
|
|
722
824
|
return bookified
|
|
723
825
|
}
|
|
724
|
-
// Check if book is a full name
|
|
725
826
|
bookified =
|
|
726
827
|
this.bible.new.find((b) => b.toLowerCase() === book) || this.bible.old.find((b) => b.toLowerCase() === book)
|
|
727
|
-
return bookified || book
|
|
828
|
+
return bookified || book
|
|
728
829
|
}
|
|
729
830
|
|
|
730
831
|
/**
|
|
@@ -816,7 +917,7 @@ class CodexParser {
|
|
|
816
917
|
if (verses.length === 1) {
|
|
817
918
|
return `${chapter}:${verses[0]}`
|
|
818
919
|
}
|
|
819
|
-
const isRange = verses.every((v, i, arr) => i === 0 || v === arr[i - 1] + 1)
|
|
920
|
+
const isRange = verses.every((v, i, arr) => i === 0 || Number(v) === Number(arr[i - 1]) + 1)
|
|
820
921
|
if (isRange) {
|
|
821
922
|
return `${chapter}:${verses[0]}-${verses[verses.length - 1]}`
|
|
822
923
|
}
|
|
@@ -829,9 +930,7 @@ class CodexParser {
|
|
|
829
930
|
passage.to.chapter,
|
|
830
931
|
passage.to.verses
|
|
831
932
|
)}`
|
|
832
|
-
} else if (passage.type === "chapter_verse_range") {
|
|
833
|
-
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
834
|
-
} else if (passage.type === "comma_separated_verses") {
|
|
933
|
+
} else if (passage.type === "chapter_verse_range" || passage.type === "comma_separated_verses") {
|
|
835
934
|
combined += ` ${formatChapterVerse(passage.chapter, passage.verses)}`
|
|
836
935
|
} else if (passage.type === "chapter_range" && passage.to) {
|
|
837
936
|
combined += ` ${passage.chapter}-${passage.to.chapter}`
|
|
@@ -857,7 +956,7 @@ class CodexParser {
|
|
|
857
956
|
|
|
858
957
|
/**
|
|
859
958
|
* Combines multiple passages into a single reference.
|
|
860
|
-
* @param {Object[]} [passages=this.passages] - Array of passages to combine
|
|
959
|
+
* @param {Object[]} [passages=this.passages] - Array of passages to combine.
|
|
861
960
|
* @returns {Object} Combined passage object.
|
|
862
961
|
*/
|
|
863
962
|
combine(passages = this.passages) {
|
|
@@ -920,7 +1019,7 @@ class CodexParser {
|
|
|
920
1019
|
sortedChapters.forEach((chapter) => {
|
|
921
1020
|
const verses = Array.from(chapterVerses[chapter])
|
|
922
1021
|
.map(Number)
|
|
923
|
-
.filter((verse) => verse > 0)
|
|
1022
|
+
.filter((verse) => verse > 0)
|
|
924
1023
|
.sort((a, b) => a - b)
|
|
925
1024
|
if (verses.length > 0) {
|
|
926
1025
|
const mergedVerses = this.mergeRanges(verses)
|
|
@@ -972,7 +1071,6 @@ class CodexParser {
|
|
|
972
1071
|
: Math.max(...Array.from(chapterVerses[lastChapter]).filter((verse) => verse > 0)),
|
|
973
1072
|
}
|
|
974
1073
|
|
|
975
|
-
// Reattach the reference method to the combined passage
|
|
976
1074
|
combined.reference = function () {
|
|
977
1075
|
return this.scripture.passage
|
|
978
1076
|
}
|
|
@@ -1111,7 +1209,7 @@ class CodexParser {
|
|
|
1111
1209
|
: [Number(verseRange)]
|
|
1112
1210
|
|
|
1113
1211
|
for (const v of verseNumbers) {
|
|
1114
|
-
if (!chapterVerses.includes(v)) {
|
|
1212
|
+
if (isNaN(v) || v <= 0 || !chapterVerses.includes(v)) {
|
|
1115
1213
|
return this.validationError(104, `Verse number ${v} does not exist in ${book} ${chapter}`)
|
|
1116
1214
|
}
|
|
1117
1215
|
}
|
|
@@ -1171,21 +1269,18 @@ class CodexParser {
|
|
|
1171
1269
|
const { originalText, abbr, original } = passage
|
|
1172
1270
|
const newReference = useAbbreviations ? abbr : original
|
|
1173
1271
|
|
|
1174
|
-
const regex = new RegExp(`${originalText}`, "g")
|
|
1272
|
+
const regex = new RegExp(`${originalText.replace(/([.*+?^${}()|[\]\\])/g, "\\$1")}`, "g")
|
|
1175
1273
|
|
|
1176
|
-
// Find all matches
|
|
1177
1274
|
const matches = [...result.matchAll(regex)]
|
|
1178
1275
|
if (matches.length > 0) {
|
|
1179
|
-
// Process matches in reverse to avoid index shifting
|
|
1180
1276
|
for (let j = matches.length - 1; j >= 0; j--) {
|
|
1181
1277
|
const match = matches[j]
|
|
1182
1278
|
const startIndex = match.index
|
|
1183
1279
|
const endIndex = startIndex + match[0].length
|
|
1184
|
-
const leadingSpace = match[1] || ""
|
|
1280
|
+
const leadingSpace = match[1] || ""
|
|
1185
1281
|
const hasOpeningParen = match[2] === "("
|
|
1186
1282
|
const hasClosingParen = match[3] === ")"
|
|
1187
|
-
const trailingSpace = match[4] || " "
|
|
1188
|
-
// Preserve parentheses if present
|
|
1283
|
+
const trailingSpace = match[4] || " "
|
|
1189
1284
|
const replacement =
|
|
1190
1285
|
hasOpeningParen && hasClosingParen
|
|
1191
1286
|
? `${leadingSpace}(${newReference})${trailingSpace}`
|
|
@@ -1197,6 +1292,7 @@ class CodexParser {
|
|
|
1197
1292
|
|
|
1198
1293
|
return result
|
|
1199
1294
|
}
|
|
1295
|
+
|
|
1200
1296
|
/**
|
|
1201
1297
|
* Checks if all references in the passages array are from the same book.
|
|
1202
1298
|
* @returns {boolean} True if all passages are from the same book, false otherwise.
|
|
@@ -6807,269 +6807,264 @@ module.exports = {
|
|
|
6807
6807
|
eng: "88:19",
|
|
6808
6808
|
},
|
|
6809
6809
|
"89:1": {
|
|
6810
|
-
lxx: "88:1",
|
|
6811
|
-
mt: "89:1",
|
|
6812
|
-
eng: "89:1",
|
|
6813
|
-
},
|
|
6814
|
-
"89:2": {
|
|
6815
6810
|
lxx: "88:2",
|
|
6816
6811
|
mt: "89:2",
|
|
6817
|
-
eng: "89:
|
|
6812
|
+
eng: "89:1",
|
|
6818
6813
|
},
|
|
6819
|
-
"89:
|
|
6814
|
+
"89:2": {
|
|
6820
6815
|
lxx: "88:3",
|
|
6821
6816
|
mt: "89:3",
|
|
6822
|
-
eng: "89:
|
|
6817
|
+
eng: "89:2",
|
|
6823
6818
|
},
|
|
6824
|
-
"89:
|
|
6819
|
+
"89:3": {
|
|
6825
6820
|
lxx: "88:4",
|
|
6826
6821
|
mt: "89:4",
|
|
6827
|
-
eng: "89:
|
|
6822
|
+
eng: "89:3",
|
|
6828
6823
|
},
|
|
6829
|
-
"89:
|
|
6824
|
+
"89:4": {
|
|
6830
6825
|
lxx: "88:5",
|
|
6831
6826
|
mt: "89:5",
|
|
6832
|
-
eng: "89:
|
|
6827
|
+
eng: "89:4",
|
|
6833
6828
|
},
|
|
6834
|
-
"89:
|
|
6829
|
+
"89:5": {
|
|
6835
6830
|
lxx: "88:6",
|
|
6836
6831
|
mt: "89:6",
|
|
6837
|
-
eng: "89:
|
|
6832
|
+
eng: "89:5",
|
|
6838
6833
|
},
|
|
6839
|
-
"89:
|
|
6834
|
+
"89:6": {
|
|
6840
6835
|
lxx: "88:7",
|
|
6841
6836
|
mt: "89:7",
|
|
6842
|
-
eng: "89:
|
|
6837
|
+
eng: "89:6",
|
|
6843
6838
|
},
|
|
6844
|
-
"89:
|
|
6839
|
+
"89:7": {
|
|
6845
6840
|
lxx: "88:8",
|
|
6846
6841
|
mt: "89:8",
|
|
6847
|
-
eng: "89:
|
|
6842
|
+
eng: "89:7",
|
|
6848
6843
|
},
|
|
6849
|
-
"89:
|
|
6844
|
+
"89:8": {
|
|
6850
6845
|
lxx: "88:9",
|
|
6851
6846
|
mt: "89:9",
|
|
6852
|
-
eng: "89:
|
|
6847
|
+
eng: "89:8",
|
|
6853
6848
|
},
|
|
6854
|
-
"89:
|
|
6849
|
+
"89:9": {
|
|
6855
6850
|
lxx: "88:10",
|
|
6856
6851
|
mt: "89:10",
|
|
6857
|
-
eng: "89:
|
|
6852
|
+
eng: "89:9",
|
|
6858
6853
|
},
|
|
6859
|
-
"89:
|
|
6854
|
+
"89:10": {
|
|
6860
6855
|
lxx: "88:11",
|
|
6861
6856
|
mt: "89:11",
|
|
6862
|
-
eng: "89:
|
|
6857
|
+
eng: "89:10",
|
|
6863
6858
|
},
|
|
6864
|
-
"89:
|
|
6859
|
+
"89:11": {
|
|
6865
6860
|
lxx: "88:12",
|
|
6866
6861
|
mt: "89:12",
|
|
6867
|
-
eng: "89:
|
|
6862
|
+
eng: "89:11",
|
|
6868
6863
|
},
|
|
6869
|
-
"89:
|
|
6864
|
+
"89:12": {
|
|
6870
6865
|
lxx: "88:13",
|
|
6871
6866
|
mt: "89:13",
|
|
6872
|
-
eng: "89:
|
|
6867
|
+
eng: "89:12",
|
|
6873
6868
|
},
|
|
6874
|
-
"89:
|
|
6869
|
+
"89:13": {
|
|
6875
6870
|
lxx: "88:14",
|
|
6876
6871
|
mt: "89:14",
|
|
6877
|
-
eng: "89:
|
|
6872
|
+
eng: "89:13",
|
|
6878
6873
|
},
|
|
6879
|
-
"89:
|
|
6874
|
+
"89:14": {
|
|
6880
6875
|
lxx: "88:15",
|
|
6881
6876
|
mt: "89:15",
|
|
6882
|
-
eng: "89:
|
|
6877
|
+
eng: "89:14",
|
|
6883
6878
|
},
|
|
6884
|
-
"89:
|
|
6879
|
+
"89:15": {
|
|
6885
6880
|
lxx: "88:16",
|
|
6886
6881
|
mt: "89:16",
|
|
6887
|
-
eng: "89:
|
|
6882
|
+
eng: "89:15",
|
|
6888
6883
|
},
|
|
6889
|
-
"89:
|
|
6884
|
+
"89:16": {
|
|
6890
6885
|
lxx: "88:17",
|
|
6891
6886
|
mt: "89:17",
|
|
6892
|
-
eng: "89:
|
|
6887
|
+
eng: "89:16",
|
|
6893
6888
|
},
|
|
6894
|
-
"89:
|
|
6889
|
+
"89:17": {
|
|
6895
6890
|
lxx: "88:18",
|
|
6896
6891
|
mt: "89:18",
|
|
6897
|
-
eng: "89:
|
|
6892
|
+
eng: "89:17",
|
|
6898
6893
|
},
|
|
6899
|
-
"89:
|
|
6894
|
+
"89:18": {
|
|
6900
6895
|
lxx: "88:19",
|
|
6901
6896
|
mt: "89:19",
|
|
6902
|
-
eng: "89:
|
|
6897
|
+
eng: "89:18",
|
|
6903
6898
|
},
|
|
6904
|
-
"89:
|
|
6899
|
+
"89:19": {
|
|
6905
6900
|
lxx: "88:20",
|
|
6906
6901
|
mt: "89:20",
|
|
6907
|
-
eng: "89:
|
|
6902
|
+
eng: "89:19",
|
|
6908
6903
|
},
|
|
6909
|
-
"89:
|
|
6904
|
+
"89:20": {
|
|
6910
6905
|
lxx: "88:21",
|
|
6911
6906
|
mt: "89:21",
|
|
6912
|
-
eng: "89:
|
|
6907
|
+
eng: "89:20",
|
|
6913
6908
|
},
|
|
6914
|
-
"89:
|
|
6909
|
+
"89:21": {
|
|
6915
6910
|
lxx: "88:22",
|
|
6916
6911
|
mt: "89:22",
|
|
6917
|
-
eng: "89:
|
|
6912
|
+
eng: "89:21",
|
|
6918
6913
|
},
|
|
6919
|
-
"89:
|
|
6914
|
+
"89:22": {
|
|
6920
6915
|
lxx: "88:23",
|
|
6921
6916
|
mt: "89:23",
|
|
6922
|
-
eng: "89:
|
|
6917
|
+
eng: "89:22",
|
|
6923
6918
|
},
|
|
6924
|
-
"89:
|
|
6919
|
+
"89:23": {
|
|
6925
6920
|
lxx: "88:24",
|
|
6926
6921
|
mt: "89:24",
|
|
6927
|
-
eng: "89:
|
|
6922
|
+
eng: "89:23",
|
|
6928
6923
|
},
|
|
6929
|
-
"89:
|
|
6924
|
+
"89:24": {
|
|
6930
6925
|
lxx: "88:25",
|
|
6931
6926
|
mt: "89:25",
|
|
6932
|
-
eng: "89:
|
|
6927
|
+
eng: "89:24",
|
|
6933
6928
|
},
|
|
6934
|
-
"89:
|
|
6929
|
+
"89:25": {
|
|
6935
6930
|
lxx: "88:26",
|
|
6936
6931
|
mt: "89:26",
|
|
6937
|
-
eng: "89:
|
|
6932
|
+
eng: "89:25",
|
|
6938
6933
|
},
|
|
6939
|
-
"89:
|
|
6934
|
+
"89:26": {
|
|
6940
6935
|
lxx: "88:27",
|
|
6941
6936
|
mt: "89:27",
|
|
6942
|
-
eng: "89:
|
|
6937
|
+
eng: "89:26",
|
|
6943
6938
|
},
|
|
6944
|
-
"89:
|
|
6939
|
+
"89:27": {
|
|
6945
6940
|
lxx: "88:28",
|
|
6946
6941
|
mt: "89:28",
|
|
6947
|
-
eng: "89:
|
|
6942
|
+
eng: "89:27",
|
|
6948
6943
|
},
|
|
6949
|
-
"89:
|
|
6944
|
+
"89:28": {
|
|
6950
6945
|
lxx: "88:29",
|
|
6951
6946
|
mt: "89:29",
|
|
6952
|
-
eng: "89:
|
|
6947
|
+
eng: "89:28",
|
|
6953
6948
|
},
|
|
6954
|
-
"89:
|
|
6949
|
+
"89:29": {
|
|
6955
6950
|
lxx: "88:30",
|
|
6956
6951
|
mt: "89:30",
|
|
6957
|
-
eng: "89:
|
|
6952
|
+
eng: "89:29",
|
|
6958
6953
|
},
|
|
6959
|
-
"89:
|
|
6954
|
+
"89:30": {
|
|
6960
6955
|
lxx: "88:31",
|
|
6961
6956
|
mt: "89:31",
|
|
6962
|
-
eng: "89:
|
|
6957
|
+
eng: "89:30",
|
|
6963
6958
|
},
|
|
6964
|
-
"89:
|
|
6959
|
+
"89:31": {
|
|
6965
6960
|
lxx: "88:32",
|
|
6966
6961
|
mt: "89:32",
|
|
6967
|
-
eng: "89:
|
|
6962
|
+
eng: "89:31",
|
|
6968
6963
|
},
|
|
6969
|
-
"89:
|
|
6964
|
+
"89:32": {
|
|
6970
6965
|
lxx: "88:33",
|
|
6971
6966
|
mt: "89:33",
|
|
6972
|
-
eng: "89:
|
|
6967
|
+
eng: "89:32",
|
|
6973
6968
|
},
|
|
6974
|
-
"89:
|
|
6969
|
+
"89:33": {
|
|
6975
6970
|
lxx: "88:34",
|
|
6976
6971
|
mt: "89:34",
|
|
6977
|
-
eng: "89:
|
|
6972
|
+
eng: "89:33",
|
|
6978
6973
|
},
|
|
6979
|
-
"89:
|
|
6974
|
+
"89:34": {
|
|
6980
6975
|
lxx: "88:35",
|
|
6981
6976
|
mt: "89:35",
|
|
6982
|
-
eng: "89:
|
|
6977
|
+
eng: "89:34",
|
|
6983
6978
|
},
|
|
6984
|
-
"89:
|
|
6979
|
+
"89:35": {
|
|
6985
6980
|
lxx: "88:36",
|
|
6986
6981
|
mt: "89:36",
|
|
6987
|
-
eng: "89:
|
|
6982
|
+
eng: "89:35",
|
|
6988
6983
|
},
|
|
6989
|
-
"89:
|
|
6984
|
+
"89:36": {
|
|
6990
6985
|
lxx: "88:37",
|
|
6991
6986
|
mt: "89:37",
|
|
6992
|
-
eng: "89:
|
|
6987
|
+
eng: "89:36",
|
|
6993
6988
|
},
|
|
6994
|
-
"89:
|
|
6989
|
+
"89:37": {
|
|
6995
6990
|
lxx: "88:38",
|
|
6996
6991
|
mt: "89:38",
|
|
6997
|
-
eng: "89:
|
|
6992
|
+
eng: "89:37",
|
|
6998
6993
|
},
|
|
6999
|
-
"89:
|
|
6994
|
+
"89:38": {
|
|
7000
6995
|
lxx: "88:39",
|
|
7001
6996
|
mt: "89:39",
|
|
7002
|
-
eng: "89:
|
|
6997
|
+
eng: "89:38",
|
|
7003
6998
|
},
|
|
7004
|
-
"89:
|
|
6999
|
+
"89:39": {
|
|
7005
7000
|
lxx: "88:40",
|
|
7006
7001
|
mt: "89:40",
|
|
7007
|
-
eng: "89:
|
|
7002
|
+
eng: "89:39",
|
|
7008
7003
|
},
|
|
7009
|
-
"89:
|
|
7004
|
+
"89:40": {
|
|
7010
7005
|
lxx: "88:41",
|
|
7011
7006
|
mt: "89:41",
|
|
7012
|
-
eng: "89:
|
|
7007
|
+
eng: "89:40",
|
|
7013
7008
|
},
|
|
7014
|
-
"89:
|
|
7009
|
+
"89:41": {
|
|
7015
7010
|
lxx: "88:42",
|
|
7016
7011
|
mt: "89:42",
|
|
7017
|
-
eng: "89:
|
|
7012
|
+
eng: "89:41",
|
|
7018
7013
|
},
|
|
7019
|
-
"89:
|
|
7014
|
+
"89:42": {
|
|
7020
7015
|
lxx: "88:43",
|
|
7021
7016
|
mt: "89:43",
|
|
7022
|
-
eng: "89:
|
|
7017
|
+
eng: "89:42",
|
|
7023
7018
|
},
|
|
7024
|
-
"89:
|
|
7019
|
+
"89:43": {
|
|
7025
7020
|
lxx: "88:44",
|
|
7026
7021
|
mt: "89:44",
|
|
7027
|
-
eng: "89:
|
|
7022
|
+
eng: "89:43",
|
|
7028
7023
|
},
|
|
7029
|
-
"89:
|
|
7024
|
+
"89:44": {
|
|
7030
7025
|
lxx: "88:45",
|
|
7031
7026
|
mt: "89:45",
|
|
7032
|
-
eng: "89:
|
|
7027
|
+
eng: "89:44",
|
|
7033
7028
|
},
|
|
7034
|
-
"89:
|
|
7029
|
+
"89:45": {
|
|
7035
7030
|
lxx: "88:46",
|
|
7036
7031
|
mt: "89:46",
|
|
7037
|
-
eng: "89:
|
|
7032
|
+
eng: "89:45",
|
|
7038
7033
|
},
|
|
7039
|
-
"89:
|
|
7034
|
+
"89:46": {
|
|
7040
7035
|
lxx: "88:47",
|
|
7041
7036
|
mt: "89:47",
|
|
7042
|
-
eng: "89:
|
|
7037
|
+
eng: "89:46",
|
|
7043
7038
|
},
|
|
7044
|
-
"89:
|
|
7039
|
+
"89:47": {
|
|
7045
7040
|
lxx: "88:48",
|
|
7046
7041
|
mt: "89:48",
|
|
7047
|
-
eng: "89:
|
|
7042
|
+
eng: "89:47",
|
|
7048
7043
|
},
|
|
7049
|
-
"89:
|
|
7044
|
+
"89:48": {
|
|
7050
7045
|
lxx: "88:49",
|
|
7051
7046
|
mt: "89:49",
|
|
7052
|
-
eng: "89:
|
|
7047
|
+
eng: "89:48",
|
|
7053
7048
|
},
|
|
7054
|
-
"89:
|
|
7049
|
+
"89:49": {
|
|
7055
7050
|
lxx: "88:50",
|
|
7056
7051
|
mt: "89:50",
|
|
7057
|
-
eng: "89:
|
|
7052
|
+
eng: "89:49",
|
|
7058
7053
|
},
|
|
7059
|
-
"89:
|
|
7054
|
+
"89:50": {
|
|
7060
7055
|
lxx: "88:51",
|
|
7061
7056
|
mt: "89:51",
|
|
7062
|
-
eng: "89:
|
|
7057
|
+
eng: "89:50",
|
|
7063
7058
|
},
|
|
7064
|
-
"89:
|
|
7059
|
+
"89:51": {
|
|
7065
7060
|
lxx: "88:52",
|
|
7066
7061
|
mt: "89:52",
|
|
7067
|
-
eng: "89:
|
|
7062
|
+
eng: "89:51",
|
|
7068
7063
|
},
|
|
7069
|
-
"89:
|
|
7064
|
+
"89:52": {
|
|
7070
7065
|
lxx: "88:53",
|
|
7071
7066
|
mt: "89:53",
|
|
7072
|
-
eng: "89:
|
|
7067
|
+
eng: "89:52",
|
|
7073
7068
|
},
|
|
7074
7069
|
"90:1": {
|
|
7075
7070
|
lxx: "89:1",
|