@allemandi/bible-validate
Version:
Fast, type-safe utilities for parsing, validating, and normalizing Bible references.
2,348 lines (2,327 loc) • 37.4 kB
JavaScript
/**
* Normalizes a Bible book name or alias by trimming spaces, removing common prefixes,
* converting ordinal prefixes to digits, and stripping non-alphanumeric characters.
* @public
* @param {string} name - The raw book name or alias to normalize, possibly with prefixes, punctuation, and mixed case.
* @returns {string|null} - A cleaned lowercase alphanumeric string with numeric prefixes for ordinals, or null if input is null or undefined.
* @example
* // Converts ordinal prefix to digit and removes punctuation
* normalizeBookName('1st John'); // '1john'
* // Removes unified prefixes and lowercases the name
* normalizeBookName('The Epistle to the Romans'); // 'romans'
* // Strips non-alphanumeric characters and trims spaces
* normalizeBookName(' Book of *EX* '); // 'ex'
* // Returns null if input is null or undefined
* normalizeBookName(null); // null
*/
function normalizeBookName(name) {
if (!name) return null;
/** @type {Record<string, string>} */
const prefixMap = {
'1st': '1',
first: '1',
'2nd': '2',
second: '2',
'3rd': '3',
third: '3',
iii: '3',
ii: '2',
i: '1',
};
let cleaned = name.trim().toLowerCase().replace(/\s+/g, ' ');
cleaned = cleaned.replace(
/^(?:the\s+)?(?:(book|epistle|gospel|letter)\s+(?:according\s+)?(?:to|for|of)(?:\s+the)?\s*)?/,
''
);
const prefixMatch = cleaned.match(/^(first|1st|second|2nd|third|3rd|iii|ii|i)\b/);
if (prefixMatch) {
const key = prefixMatch[0];
if (key in prefixMap) {
const prefix = prefixMap[key];
cleaned = prefix + ' ' + cleaned.slice(key.length).trim();
}
}
return cleaned.replace(/[^a-z0-9]/g, '');
}
/**
* Parses a string representing a chapter and optional verse or verse range
* into an object with numeric values.
* @public
* @param {string} str - The string containing chapter and verse references, which may include words, punctuation, and ranges.
* @returns {{chapter: number, verseStart: number|null, verseEnd: number|null}|null} - An object with the chapter number and optional verse start and end numbers, or null if no valid numbers are found.
* @example
* // Parses chapter only reference
* parseChapterVerse('12'); // { chapter: 12, verseStart: null, verseEnd: null }
* // Parses chapter and single verse reference
* parseChapterVerse('5:3'); // { chapter: 5, verseStart: 3, verseEnd: null }
* // Parses chapter with verse range including words and punctuation
* parseChapterVerse('Chapter 13 Verses 4–7'); // { chapter: 13, verseStart: 4, verseEnd: 7 }
* // Parses chapter with verse range using "to" as a separator
* parseChapterVerse('chap. 13, v3 to 8'); // { chapter: 13, verseStart: 3, verseEnd: 8 }
* // Returns null if no numeric chapter is present
* parseChapterVerse('nonsense'); // null
* // Parses chapter and verse range with extra whitespace and punctuation
* parseChapterVerse(' 10 : 2 - 6 '); // { chapter: 10, verseStart: 2, verseEnd: 6 }
* // Handles pure whitespace and number combination
* parseChapterVerse(' 11 1 2 '); // { chapter: 11, verseStart: 1, verseEnd: 2 }
*/
function parseChapterVerse(str) {
if (!str) return null;
const cleaned = str
.toLowerCase()
.replace(/[–—]/g, '-')
.replace(/\bto\b/g, '-')
.replace(/[a-z.,]/g, '')
.replace(/\s+/g, ' ')
.trim();
const tokens = cleaned.split(/[\s\-:]+/).filter(Boolean);
const nums = tokens.map((t) => parseInt(t, 10)).filter((n) => !isNaN(n));
if (nums.length === 0) return null;
switch (nums.length) {
case 1:
return { chapter: nums[0], verseStart: null, verseEnd: null };
case 2:
return { chapter: nums[0], verseStart: nums[1], verseEnd: null };
default:
return { chapter: nums[0], verseStart: nums[1], verseEnd: nums[2] };
}
}
/**
* Splits a Bible reference string into the book name and chapter/verse range parts,
* trimming empty space. No further normalization.
* @public
* @param {string} ref - The Bible reference string containing a book name optionally followed by a chapter/verse range.
* @returns {[string|null, string|null]} - A tuple where the first element is the extracted book name and the second is the range; returns [null, null] if input is invalid or empty.
* @example
* // Extracts book and range from a standard reference
* extractBookAndRange('1st John 3:16'); // ['1st John', '3:16']
* // Extracts book name with punctuation and range
* extractBookAndRange("The Revelation 4:5"); // ["The Revelation", '4:5']
* // Returns book name with empty range when no range is given
* extractBookAndRange('Genesis'); // ['Genesis', '']
* // Returns [null, null] for empty or invalid input
* extractBookAndRange(''); // [null, null]
* // Handles leading spaces and complex ranges with simple chapter abbreviations
* extractBookAndRange(' Exodus 12. 1 to 3'); // ['Exodus', '12. 1 to 3']
* extractBookAndRange('Exodus Chapter 12:1-3'); // ['Exodus', 'Chapter 12:1-3']
* extractBookAndRange(' Exodus Ch. 12. 1 to 3'); // ['Exodus', 'Ch. 12. 1 to 3']
* extractBookAndRange('second Kings Chape 1 to 3'); // ['second Kings Chape', '1 to 3']
*/
function extractBookAndRange(ref) {
if (!ref || typeof ref !== 'string') return [null, null];
const cleaned = ref.trim().replace(/\s+/g, ' ');
const pattern = /^([\d\w\s.']+?)\s*(?=\b(ch(?:apter)?|chap\.?)\b|\d)/i;
const match = cleaned.match(pattern);
if (match) {
const book = match[1].trim();
const range = cleaned.slice(match[0].length).trim();
return [book, range];
}
return [cleaned, ''];
}
/**
* @import { ParsedReference } from './types.js'
*/
/**
* Parses a Bible reference string into its book, chapter, and verse components, supporting various formats and spacing.
* @public
* @param { string } ref - The Bible reference string to parse, which may include ordinal prefixes, varying case, punctuation, and verse ranges.
* @returns {ParsedReference|null} - An object with normalized book name, chapter, verseStart, and verseEnd fields, or null if the input is not a string.
* @example
* // Parses ordinal prefix and returns structured reference
* parseBibleReference('2nd Kings 4:2');
* // { book: '2kings', chapter: 4, verseStart: 2, verseEnd: null }
* // Handles mixed casing, chapter/verse labels, and verse range
* parseBibleReference(' Iii JohN Chap. 1 verses 9 to 11');
* // { book: '3john', chapter: 1, verseStart: 9, verseEnd: 11 }
* // Returns null fields when chapter and verse are omitted
* parseBibleReference('Genesis');
* // { book: 'genesis', chapter: null, verseStart: null, verseEnd: null }
* // Cleans and parses input with excessive spacing
* parseBibleReference(' 1st Samuel 17 : 4-9 ');
* // { book: '1samuel', chapter: 17, verseStart: 4, verseEnd: 9 }
* // Returns null for invalid or non-string input
* parseBibleReference('!!!');
* // { book: null, chapter: null, verseStart: null, verseEnd: null }
* parseBibleReference(42); // null
*/
function parseBibleReference(ref) {
if (!ref || typeof ref !== 'string') return null;
const cleanedRef = ref.trim().replace(/\s+/g, ' ');
const [bookRaw, chapterVerseRaw] = extractBookAndRange(cleanedRef);
const book = bookRaw && normalizeBookName(bookRaw);
if (!book) {
return {
book: null,
chapter: null,
verseStart: null,
verseEnd: null,
};
}
const chapVerse = chapterVerseRaw ? parseChapterVerse(chapterVerseRaw) : null;
return {
book,
chapter: chapVerse?.chapter ?? null,
verseStart: chapVerse?.verseStart ?? null,
verseEnd: chapVerse?.verseEnd ?? null,
};
}var bibleCounts = [
{
book: "Genesis",
aliases: [
"Gen",
"Ge",
"Gn"
],
chapters: [
31,
25,
24,
26,
32,
22,
24,
22,
29,
32,
32,
20,
18,
24,
21,
16,
27,
33,
38,
18,
34,
24,
20,
67,
34,
35,
46,
22,
35,
43,
55,
32,
20,
31,
29,
43,
36,
30,
23,
23,
57,
38,
34,
34,
28,
34,
31,
22,
33,
26
]
},
{
book: "Exodus",
aliases: [
"Ex",
"Exod",
"Exo"
],
chapters: [
22,
25,
22,
31,
23,
30,
25,
32,
35,
29,
10,
51,
22,
31,
27,
36,
16,
27,
25,
26,
36,
31,
33,
18,
40,
37,
21,
43,
46,
38,
18,
35,
23,
35,
35,
38,
29,
31,
43,
38
]
},
{
book: "Leviticus",
aliases: [
"Lev",
"Le",
"Lv"
],
chapters: [
17,
16,
17,
35,
19,
30,
38,
36,
24,
20,
47,
8,
59,
57,
33,
34,
16,
30,
37,
27,
24,
33,
44,
23,
55,
46,
34
]
},
{
book: "Numbers",
aliases: [
"Num",
"Nu",
"Nm",
"Nb"
],
chapters: [
54,
34,
51,
49,
31,
27,
89,
26,
23,
36,
35,
16,
33,
45,
41,
50,
13,
32,
22,
29,
35,
41,
30,
25,
18,
65,
23,
31,
40,
16,
54,
42,
56,
29,
34,
13
]
},
{
book: "Deuteronomy",
aliases: [
"Deut",
"De",
"Dt"
],
chapters: [
46,
37,
29,
49,
33,
25,
26,
20,
29,
22,
32,
32,
18,
29,
23,
22,
20,
22,
21,
20,
23,
30,
25,
22,
19,
19,
26,
68,
29,
20,
30,
52,
29,
12
]
},
{
book: "Joshua",
aliases: [
"Josh",
"Jos",
"Jsh"
],
chapters: [
18,
24,
17,
24,
15,
27,
26,
35,
27,
43,
23,
24,
33,
15,
63,
10,
18,
28,
51,
9,
45,
34,
16,
33
]
},
{
book: "Judges",
aliases: [
"Judg",
"Jdgs",
"Jdg",
"Jg"
],
chapters: [
36,
23,
31,
24,
31,
40,
25,
35,
57,
18,
40,
15,
25,
20,
20,
31,
13,
31,
30,
48,
25
]
},
{
book: "Ruth",
aliases: [
"Rth",
"Ru"
],
chapters: [
22,
23,
18,
22
]
},
{
book: "1 Samuel",
aliases: [
"1 Sam",
"1 Sm",
"1 Sa",
"1 S"
],
chapters: [
28,
36,
21,
22,
12,
21,
17,
22,
27,
27,
15,
25,
23,
52,
35,
23,
58,
30,
24,
42,
15,
23,
29,
22,
44,
25,
12,
25,
11,
31,
13
]
},
{
book: "2 Samuel",
aliases: [
"2 Sam",
"2 Sm",
"2 Sa",
"2 S"
],
chapters: [
27,
32,
39,
12,
25,
23,
29,
18,
13,
19,
27,
31,
39,
33,
37,
23,
29,
33,
43,
26,
22,
51,
39,
25
]
},
{
book: "1 Kings",
aliases: [
"1 Kgs",
"1 Kin",
"1 Ki",
"1 K"
],
chapters: [
53,
46,
28,
34,
18,
38,
51,
66,
28,
29,
43,
33,
34,
31,
34,
34,
24,
46,
21,
43,
29,
53
]
},
{
book: "2 Kings",
aliases: [
"2 Kgs",
"2 Kin",
"2 Ki",
"2 K"
],
chapters: [
18,
25,
27,
44,
27,
33,
20,
29,
37,
36,
21,
21,
25,
29,
38,
20,
41,
37,
37,
21,
26,
20,
37,
20,
30
]
},
{
book: "1 Chronicles",
aliases: [
"1 Chron",
"1 Chr",
"1 Ch"
],
chapters: [
54,
55,
24,
43,
26,
81,
40,
40,
44,
14,
47,
40,
14,
17,
29,
43,
27,
17,
19,
8,
30,
19,
32,
31,
31,
32,
34,
21,
30
]
},
{
book: "2 Chronicles",
aliases: [
"2 Chron",
"2 Chr",
"2 Ch"
],
chapters: [
17,
18,
17,
22,
14,
42,
22,
18,
31,
19,
23,
16,
22,
15,
19,
14,
19,
34,
11,
37,
20,
12,
21,
27,
28,
23,
9,
27,
36,
27,
21,
33,
25,
33,
27,
23
]
},
{
book: "Ezra",
aliases: [
"Ezr",
"Ez"
],
chapters: [
11,
70,
13,
24,
17,
22,
28,
36,
15,
44
]
},
{
book: "Nehemiah",
aliases: [
"Neh",
"Ne"
],
chapters: [
11,
20,
32,
23,
19,
19,
73,
18,
38,
39,
36,
47,
31
]
},
{
book: "Esther",
aliases: [
"Est",
"Esth",
"Es"
],
chapters: [
22,
23,
15,
17,
14,
14,
10,
17,
32,
3
]
},
{
book: "Job",
aliases: [
"Jb"
],
chapters: [
22,
13,
26,
21,
27,
30,
21,
22,
35,
22,
20,
25,
28,
22,
35,
22,
16,
21,
29,
29,
34,
30,
17,
25,
6,
14,
23,
28,
25,
31,
40,
22,
33,
37,
16,
33,
24,
41,
30,
24,
34,
17
]
},
{
book: "Psalms",
aliases: [
"Ps",
"Psalm",
"Pslm",
"Psa",
"Psm",
"Pss"
],
chapters: [
6,
12,
8,
8,
12,
10,
17,
9,
20,
18,
7,
8,
6,
7,
5,
11,
15,
50,
14,
9,
13,
31,
6,
10,
22,
12,
14,
9,
11,
12,
24,
11,
22,
22,
28,
12,
40,
22,
13,
17,
13,
11,
5,
26,
17,
11,
9,
14,
20,
23,
19,
9,
6,
7,
23,
13,
11,
11,
17,
12,
8,
12,
11,
10,
13,
20,
7,
35,
36,
5,
24,
20,
28,
23,
10,
12,
20,
72,
13,
19,
16,
8,
18,
12,
13,
17,
7,
18,
52,
17,
16,
15,
5,
23,
11,
13,
12,
9,
9,
5,
8,
28,
22,
35,
45,
48,
43,
13,
31,
7,
10,
10,
9,
8,
18,
19,
2,
29,
176,
7,
8,
9,
4,
8,
5,
6,
5,
6,
8,
8,
3,
18,
3,
3,
21,
26,
9,
8,
24,
13,
10,
7,
12,
15,
21,
10,
20,
14,
9,
6
]
},
{
book: "Proverbs",
aliases: [
"Prov",
"Pro",
"Prv",
"Pr"
],
chapters: [
33,
22,
35,
27,
23,
35,
27,
36,
18,
32,
31,
28,
25,
35,
33,
33,
28,
24,
29,
30,
31,
29,
35,
34,
28,
28,
27,
28,
27,
33,
31
]
},
{
book: "Ecclesiastes",
aliases: [
"Eccles",
"Eccle",
"Ecc",
"Ec",
"Qoh"
],
chapters: [
18,
26,
22,
16,
20,
12,
29,
17,
18,
20,
10,
14
]
},
{
book: "Song of Solomon",
aliases: [
"Song",
"Song of Songs",
"Sos",
"So",
"Canticle of Canticles",
"Canticles",
"Cant"
],
chapters: [
17,
17,
11,
16,
16,
13,
13,
14
]
},
{
book: "Isaiah",
aliases: [
"Isa",
"Is"
],
chapters: [
31,
22,
26,
6,
30,
13,
25,
22,
21,
34,
16,
6,
22,
32,
9,
14,
14,
7,
25,
6,
17,
25,
18,
23,
12,
21,
13,
29,
24,
33,
9,
20,
24,
17,
10,
22,
38,
22,
8,
31,
29,
25,
28,
28,
25,
13,
15,
22,
26,
11,
23,
15,
12,
17,
13,
12,
21,
14,
21,
22,
11,
12,
19,
12,
25,
24
]
},
{
book: "Jeremiah",
aliases: [
"Jer",
"Je",
"Jr"
],
chapters: [
19,
37,
25,
31,
31,
30,
34,
22,
26,
25,
23,
17,
27,
22,
21,
21,
27,
23,
15,
18,
14,
30,
40,
10,
38,
24,
22,
17,
32,
24,
40,
44,
26,
22,
19,
32,
21,
28,
18,
16,
18,
22,
13,
30,
5,
28,
7,
47,
39,
46,
64,
34
]
},
{
book: "Lamentations",
aliases: [
"Lam",
"La"
],
chapters: [
22,
22,
66,
22,
22
]
},
{
book: "Ezekiel",
aliases: [
"Ezek",
"Eze",
"Ezk"
],
chapters: [
28,
10,
27,
17,
17,
14,
27,
18,
11,
22,
25,
28,
23,
23,
8,
63,
24,
32,
14,
49,
32,
31,
49,
27,
17,
21,
36,
26,
21,
26,
18,
32,
33,
31,
15,
38,
28,
23,
29,
49,
26,
20,
27,
31,
25,
24,
23,
35
]
},
{
book: "Daniel",
aliases: [
"Dan",
"Da",
"Dn"
],
chapters: [
21,
49,
30,
37,
31,
28,
28,
27,
27,
21,
45,
13
]
},
{
book: "Hosea",
aliases: [
"Hos",
"Ho"
],
chapters: [
11,
23,
5,
19,
15,
11,
16,
14,
17,
15,
12,
14,
16,
9
]
},
{
book: "Joel",
aliases: [
"Jl"
],
chapters: [
20,
32,
21
]
},
{
book: "Amos",
aliases: [
"Am"
],
chapters: [
15,
16,
15,
13,
27,
14,
17,
14,
15
]
},
{
book: "Obadiah",
aliases: [
"Obad",
"Ob"
],
chapters: [
21
]
},
{
book: "Jonah",
aliases: [
"Jnh",
"Jon"
],
chapters: [
17,
10,
10,
11
]
},
{
book: "Micah",
aliases: [
"Mic",
"Mc"
],
chapters: [
16,
13,
12,
13,
15,
16,
20
]
},
{
book: "Nahum",
aliases: [
"Nah",
"Na"
],
chapters: [
15,
13,
19
]
},
{
book: "Habakkuk",
aliases: [
"Hab",
"Hb"
],
chapters: [
17,
20,
19
]
},
{
book: "Zephaniah",
aliases: [
"Zeph",
"Zep",
"Zp"
],
chapters: [
18,
15,
20
]
},
{
book: "Haggai",
aliases: [
"Hag",
"Hg"
],
chapters: [
15,
23
]
},
{
book: "Zechariah",
aliases: [
"Zech",
"Zec",
"Zc"
],
chapters: [
21,
13,
10,
14,
11,
15,
14,
23,
17,
12,
17,
14,
9,
21
]
},
{
book: "Malachi",
aliases: [
"Mal",
"Ml"
],
chapters: [
14,
17,
18,
6
]
},
{
book: "Matthew",
aliases: [
"Matt",
"Mt"
],
chapters: [
25,
23,
17,
25,
48,
34,
29,
34,
38,
42,
30,
50,
58,
36,
39,
28,
27,
35,
30,
34,
46,
46,
39,
51,
46,
75,
66,
20
]
},
{
book: "Mark",
aliases: [
"Mrk",
"Mar",
"Mk",
"Mr"
],
chapters: [
45,
28,
35,
41,
43,
56,
37,
38,
50,
52,
33,
44,
37,
72,
47,
20
]
},
{
book: "Luke",
aliases: [
"Luk",
"Lk"
],
chapters: [
80,
52,
38,
44,
39,
49,
50,
56,
62,
42,
54,
59,
35,
35,
32,
31,
37,
43,
48,
47,
38,
71,
56,
53
]
},
{
book: "John",
aliases: [
"Joh",
"Jhn",
"Jn"
],
chapters: [
51,
25,
36,
54,
47,
71,
53,
59,
41,
42,
57,
50,
38,
31,
27,
33,
26,
40,
42,
31,
25
]
},
{
book: "Acts",
aliases: [
"Acts",
"Ac"
],
chapters: [
26,
47,
26,
37,
42,
15,
60,
40,
43,
48,
30,
25,
52,
28,
41,
40,
34,
28,
41,
38,
40,
30,
35,
27,
27,
32,
44,
31
]
},
{
book: "Romans",
aliases: [
"Rom",
"Ro",
"Rm"
],
chapters: [
32,
29,
31,
25,
21,
23,
25,
39,
33,
21,
36,
21,
14,
23,
33,
27
]
},
{
book: "1 Corinthians",
aliases: [
"1 Cor",
"1 Co"
],
chapters: [
31,
16,
23,
21,
13,
20,
40,
13,
27,
33,
34,
31,
13,
40,
58,
24
]
},
{
book: "2 Corinthians",
aliases: [
"2 Cor",
"2 Co"
],
chapters: [
24,
17,
18,
18,
21,
18,
16,
24,
15,
18,
33,
21,
14
]
},
{
book: "Galatians",
aliases: [
"Gal",
"Ga"
],
chapters: [
24,
21,
29,
31,
26,
18
]
},
{
book: "Ephesians",
aliases: [
"Ephes",
"Eph"
],
chapters: [
23,
22,
21,
32,
33,
24
]
},
{
book: "Philippians",
aliases: [
"Phil",
"Php",
"Pp"
],
chapters: [
30,
30,
21,
23
]
},
{
book: "Colossians",
aliases: [
"Col",
"Co"
],
chapters: [
29,
23,
25,
18
]
},
{
book: "1 Thessalonians",
aliases: [
"1 Thess",
"1 Thes",
"1 Th"
],
chapters: [
10,
20,
13,
18,
28
]
},
{
book: "2 Thessalonians",
aliases: [
"2 Thess",
"2 Thes",
"2 Th"
],
chapters: [
12,
17,
18
]
},
{
book: "1 Timothy",
aliases: [
"1 Tim",
"1 Ti"
],
chapters: [
20,
15,
16,
16,
25,
21
]
},
{
book: "2 Timothy",
aliases: [
"2 Tim",
"2 Ti"
],
chapters: [
18,
26,
17,
22
]
},
{
book: "Titus",
aliases: [
"Tit",
"Ti"
],
chapters: [
16,
15,
15
]
},
{
book: "Philemon",
aliases: [
"Philem",
"Phm",
"Pm"
],
chapters: [
25
]
},
{
book: "Hebrews",
aliases: [
"Heb"
],
chapters: [
14,
18,
19,
16,
14,
20,
28,
13,
28,
39,
40,
29,
25
]
},
{
book: "James",
aliases: [
"Jas",
"Jm"
],
chapters: [
27,
26,
18,
17,
20
]
},
{
book: "1 Peter",
aliases: [
"1 Pet",
"1 Pe",
"1 Pt",
"1 P"
],
chapters: [
25,
25,
22,
19,
14
]
},
{
book: "2 Peter",
aliases: [
"2 Pet",
"2 Pe",
"2 Pt",
"2 P"
],
chapters: [
21,
22,
18
]
},
{
book: "1 John",
aliases: [
"1 Jn",
"1 Jo",
"1 Joh",
"1 Jhn",
"1 J"
],
chapters: [
10,
29,
24,
21,
21
]
},
{
book: "2 John",
aliases: [
"2 Jn",
"2 Jo",
"2 Joh",
"2 Jhn",
"2 J"
],
chapters: [
13
]
},
{
book: "3 John",
aliases: [
"3 Jn",
"3 Jo",
"3 Joh",
"3 Jhn",
"3 J"
],
chapters: [
15
]
},
{
book: "Jude",
aliases: [
"Jud",
"Jd"
],
chapters: [
25
]
},
{
book: "Revelation",
aliases: [
"Rev",
"Re",
"Revelation to John"
],
chapters: [
20,
29,
22,
11,
14,
17,
17,
13,
21,
11,
19,
17,
18,
20,
8,
21,
18,
24,
21,
15,
27,
21
]
}
];/**
* @import { BibleBook } from './types.js'
*/
// Build a Map at load time for fast lookup by normalized book name or alias
const bookCache = new Map();
for (const b of bibleCounts) {
const normalizedBook = normalizeBookName(b.book);
if (normalizedBook) bookCache.set(normalizedBook, b);
for (const alias of b.aliases) {
const normalizedAlias = normalizeBookName(alias);
if (normalizedAlias) bookCache.set(normalizedAlias, b);
}
}
/**
* Retrieves a book object from the Bible collection matching the given book name or its aliases, ignoring case and special characters.
* @public
* @param {string} book - The name or alias of the book to lookup, which will be normalized internally.
* @returns {BibleBook|null} - The matched book object containing book name, aliases, and chapters, or null if no match is found.
* @example
* // Returns the Genesis book object with its aliases and 50 chapters
* getBook('Genesis'); // { book: 'Genesis', aliases: ['Gen', 'Ge', 'Gn'], chapters: [...] }
* // Returns the Song of Solomon book object when queried with a normalized alias ignoring punctuation and case
* getBook('The CANticle of CantiClEs !!?*'); // { book: 'Song of Solomon', aliases: [...], chapters: [...] }
* // Returns null for an unknown or invalid book name
* getBook('Judas'); // null
*/
function getBook(book) {
if (!book) return null;
const normalized = normalizeBookName(book);
return bookCache.get(normalized) || null;
}
/**
* Returns the number of chapters for a given Bible book name or alias, or null if the book is not found.
* @public
* @param {string} name - The name or alias of the book to lookup, which will be normalized internally.
* @returns {number|null} - The total number of chapters in the matched book, or null if no book is found.
* @example
* // Returns 50 chapters for Genesis
* getChapterCount('Genesis'); // 50
* // Returns null for an unknown or invalid book name
* getChapterCount('Judas');
*/
function getChapterCount(name) {
const book = getBook(name);
return book ? book.chapters.length : null;
}
/**
* Returns the number of verses in a specified chapter of a given Bible book, or null if the book or chapter is invalid.
* @public
* @param {string} name - The name or alias of the book to lookup, which will be normalized internally.
* @param {number} chapter - The chapter number to retrieve the verse count for; must be within valid range.
* @returns {number|null} - The count of verses in the specified chapter, or null if the book is unknown or chapter is out of bounds.
* @example
* // Returns 25, the number of verses in Genesis chapter 2
* getVerseCount('GeN. ', 2); // 25
* // Returns null for an invalid book name
* getVerseCount('Judas', 1);
* // Returns null for a chapter number that is too high
* getVerseCount('Genesis', 999);
* // Returns null for a chapter number less than 1
* getVerseCount('Genesis', 0);
*/
function getVerseCount(name, chapter) {
const book = getBook(name);
if (!book || chapter < 1 || chapter > book.chapters.length) return null;
return book.chapters[chapter - 1];
}
/**
* Returns an array of all Bible book names in their canonical order.
* @public
* @returns {string[]} - An array containing 66 book names starting with Genesis and ending with Revelation.
* @example
* // Returns an array of 66 Bible books
* listBibleBooks();
* // The first and last elements are Genesis and Revelation respectively
* const books = listBibleBooks();
* console.log(books[0]); // "Genesis"
* console.log(books[books.length - 1]); // "Revelation"
*/
function listBibleBooks() {
return bibleCounts.map((b) => b.book);
}
/**
* Returns all aliases for a given book name, including the official book title, optionally normalized.
* @public
* @param {string} bookName - The name or alias of the book to lookup, which will be normalized internally.
* @param {Object} [options] - Optional settings.
* @param {boolean} [options.normalized=false] - If true, returns all aliases normalized (lowercased and stripped of special characters).
* @returns {string[]|null} - An array of aliases including the official book name, either normalized or in original form, or null if no matching book is found.
* @example
* // Returns non-normalized aliases for "Second Corinthians"
* listAliases('Second Corinthians');
* // Expected output: ["2 Corinthians", "2 Co", ...other aliases]
* // Returns normalized aliases for "Song" with normalization enabled
* listAliases('Song', { normalized: true });
* // Expected output: ["songofsolomon", "canticleofcanticles", "sos", ...]
* // Returns null for unrecognized or empty book names
* listAliases('UnknownBook'); // null
* listAliases(null); // null
* listAliases(''); // null
*/
function listAliases(bookName, { normalized = false } = {}) {
const book = getBook(bookName);
if (!book) return null;
if (normalized) {
return [book.book, ...book.aliases].map(normalizeBookName).filter((s) => s != null);
}
return [book.book, ...book.aliases];
}
/**
* Returns an array of chapter numbers for a given Bible book, starting from 1 up to the total chapter count.
* @public
* @param {string} bookName - The name or alias of the book to lookup, which will be normalized internally.
* @returns {number[]|null} - An array of chapter numbers from 1 to the book's chapter count, or null if the book is invalid or not found.
* @example
* // Returns an array [1, 2, ..., 40] for Exodus, which has 40 chapters
* listChapters('Exodus'); // [1, 2, 3, ..., 40]
* // Returns null for an invalid or unknown book
* listChapters('UnknownBook'); // null
*/
function listChapters(bookName) {
const count = getChapterCount(bookName);
if (count == null) return null;
return Array.from({ length: count }, (_, i) => i + 1);
}
/**
* Lists all verse numbers for a given book and chapter as a sequential array starting from 1.
* @public
* @param {string} bookName - The name or alias of the book to lookup, which will be normalized internally.
* @param {number} chapter - The chapter number within the book.
* @returns {number[]|null} - An array of verse numbers from 1 up to the chapter's verse count, or null if the book or chapter is invalid or out of range.
* @example
* // Returns an array of verses [1, 2, ..., 31] for Genesis chapter 1
* listVerses('Genesis', 1); // [1, 2, 3, ..., 31]
* // Returns null for a missing chapter parameter
* listVerses('Genesis'); // null
* // Returns null for an invalid chapter number or unknown book
* listVerses('Genesis', 0); // null
* listVerses('Genesis', 999); // null
* listVerses('UnknownBook', 1); // null
*/
function listVerses(bookName, chapter) {
const verseCount = getVerseCount(bookName, chapter);
if (verseCount == null) return null;
return Array.from({ length: verseCount }, (_, i) => i + 1);
}/**
* Checks if a given book name or alias corresponds to a valid Bible book.
* @public
* @param {string|null|undefined} book - The name or alias of the book to lookup, which will be normalized internally.
* @returns {boolean} - True if the book exists in the Bible collection, false otherwise.
* @example
* // Valid full book name returns true
* isValidBook('Genesis'); // true
* // Valid alias, case-insensitive, returns true
* isValidBook('gEn'); // true
* // Unknown book returns false
* isValidBook('Judas'); // false
* // Empty string returns false
* isValidBook(''); // false
* // Null or undefined input returns false
* isValidBook(null); // false
* isValidBook(undefined); // false
*/
function isValidBook(book) {
if (typeof book !== 'string' || book.trim() === '') return false;
return getBook(book) !== null;
}
/**
* Checks if the given chapter number is valid for the specified Bible book.
* @public
* @param {string} book - The name or alias of the book to lookup, which will be normalized internally.
* @param {number} chapter - The chapter number to check, expected to be a positive integer.
* @returns {boolean} - True if the chapter is within the valid range for the book; otherwise, false.
* @example
* // Valid chapters for Genesis include 1 and 50
* isValidChapter('Genesis', 1); // true
* isValidChapter('Genesis', 50); // true
* // Invalid chapters are below 1 or above the book's chapter count
* isValidChapter('Genesis', 0); // false
* isValidChapter('Genesis', 51); // false
* isValidChapter('Genesis', -1); // false
* // Returns false if the book is unknown or input is null/undefined
* isValidChapter('Judas', 1); // false
* isValidChapter(null, 1); // false
* isValidChapter('Genesis', null); // false
*/
function isValidChapter(book, chapter) {
const bookObj = getBook(book);
if (!bookObj) return false;
return chapter >= 1 && chapter <= bookObj.chapters.length;
}
/**
* Validates whether a given Bible reference consisting of book, chapter, and verse(s) is valid.
* @public
* @param {string} book - The name or alias of the book to lookup, which will be normalized internally.
* @param {number} chapter - The chapter number, must be a positive integer within the book's chapter count.
* @param {number} verseStart - The starting verse number, must be a positive integer within the chapter's verse count.
* @param {number|null} [verseEnd=null] - Optional ending verse number, must be greater than or equal to verseStart and within the chapter's verse count if provided.
* @returns {boolean} True if the reference is valid within the book's chapter and verse bounds, otherwise false.
* @example
* // Valid single verse in Genesis chapter 1
* isValidReference('Genesis', 1, 1); // true
* // Valid last verse in Genesis chapter 1
* isValidReference('Genesis', 1, 31); // true
* // Invalid verse number exceeding the number of verses in chapter 1 of Genesis
* isValidReference('Genesis', 1, 32); // false
* // Invalid chapter number (0) in Genesis
* isValidReference('Genesis', 0, 1); // false
* // Invalid verse number (0) in Genesis chapter 1
* isValidReference('Genesis', 1, 0); // false
* // Invalid unknown book name returns false
* isValidReference('Blah', 1, 1); // false
* // Reference with a verse range, valid only if verseEnd >= verseStart and within chapter verse count
* isValidReference('Genesis', 1, 1, 5); // true
* // Case-insensitive book name input is accepted
* isValidReference('gEnEsIs', 1, 1); // true
*/
function isValidReference(book, chapter, verseStart, verseEnd = null) {
const bookObj = getBook(book);
if (!bookObj || !isValidChapter(book, chapter)) return false;
const maxVerses = bookObj.chapters[chapter - 1];
if (verseStart < 1 || verseStart > maxVerses) return false;
if (verseEnd !== null) {
if (verseEnd < verseStart || verseEnd > maxVerses) return false;
}
return true;
}/**
* @import { ParseReferenceOptions, ParsedReference, SimpleResult, StructuredResult } from './types.js'
*/
/**
* Formats a scripture reference string based on the provided input. Input is not normalized.
* @public
* @param {Object} reference - The reference object.
* @param {string} reference.book - The name of the book (e.g., "Genesis").
* @param {number} [reference.chapter] - The chapter number.
* @param {number} [reference.verseStart] - The starting verse number.
* @param {number|null} [reference.verseEnd] - The ending verse number (optional, used for ranges).
* @returns {string} - A formatted Bible reference (e.g., "Genesis 1:1-5"). Returns an empty string if no input is provided.
* @example
* formatReference({}); // ''
* formatReference({ book: 'Genesis' }); // 'Genesis'
* formatReference({ book: 'Genesis', chapter: 1 }); // 'Genesis 1'
* formatReference({ book: 'Genesis', chapter: 1, verseStart: 1 }); // 'Genesis 1:1'
* formatReference({ book: 'Genesis', chapter: 1, verseStart: 1, verseEnd: 5 }); // 'Genesis 1:1-5'
* formatReference({ book: 'Genesis', chapter: 1, verseStart: 3, verseEnd: 3 }); // 'Genesis 1:3'
*/
function formatReference({ book, chapter, verseStart, verseEnd }) {
if (!book || !chapter) return book || '';
if (verseStart == null) return `${book} ${chapter}`;
if (verseEnd == null || verseEnd === verseStart) return `${book} ${chapter}:${verseStart}`;
return `${book} ${chapter}:${verseStart}-${verseEnd}`;
}
/**
* Parses and validates a Bible reference string.
* @public
* @param {string} reference - The raw Bible reference string to be parsed, normalized, and formatted (e.g., "Genesis 1:1", "Letter to the Romans. Ch 2 , 1 to 3").
* @param {ParseReferenceOptions} [options] - Optional configuration, return structured object or just the formatted result.
* @returns {SimpleResult|StructuredResult} - Result object depending on options.structured.
* @example
* parseAndValidateReference(' GN. Ch 1 , 1 to 3');
* // → { isValid: true, formatted: 'Genesis 1:1-3', error: null, original: ' GN. Ch 1 , 1 to 3' }
* parseAndValidateReference('gEnEsIs 1 verse 1', { structured: true });
* // → {
* // isValid: true,
* // book: 'Genesis',
* // chapter: 1,
* // verseStart: 1,
* // verseEnd: null,
* // formatted: 'Genesis 1:1',
* // error: null,
* // original: 'gEnEsIs 1 verse 1'
* // }
* parseAndValidateReference('Book of Judas 1:1');
* // → { isValid: false, error: 'Invalid book name', original: 'Book of Judas 1:1' }
*/
function parseAndValidateReference(reference, { structured = false } = {}) {
/**
* @private
* @param {string} msg
* @returns {{ isValid: false, error: string, original: string }}
*/
const fail = (msg) => ({ isValid: false, error: msg, original: reference });
if (typeof reference !== 'string' || !reference.trim()) {
return fail('Empty or invalid input');
}
/**
* @private
* @type {ParsedReference|null}
*/
const parsed = parseBibleReference(reference);
if (!parsed?.book) return fail('Could not parse reference');
const bookObj = getBook(parsed.book);
if (!bookObj) return fail('Invalid book name');
const chapter = parsed.chapter ?? null;
const verseStart = parsed.verseStart ?? null;
const verseEnd = parsed.verseEnd ?? null;
if (chapter === null || verseStart === null) {
return fail('Missing chapter or verse');
}
if (!isValidReference(bookObj.book, chapter, verseStart, verseEnd)) {
return fail('Invalid chapter or verse');
}
const formatted = formatReference({ book: bookObj.book, chapter, verseStart, verseEnd });
const base = {
isValid: true,
formatted,
error: null,
original: reference,
};
return structured
? {
...base,
book: bookObj.book,
chapter,
verseStart,
verseEnd,
}
: base;
}export{extractBookAndRange,formatReference,getBook,getChapterCount,getVerseCount,isValidBook,isValidChapter,isValidReference,listAliases,listBibleBooks,listChapters,listVerses,normalizeBookName,parseAndValidateReference,parseBibleReference,parseChapterVerse};//# sourceMappingURL=index.module.js.map