import { XMLParser } from 'fast-xml-parser'; import { readFileSync } from 'fs'; import { join } from 'path'; import { bookNumberToId, getBookByNumber } from './bible'; // XML parser configuration const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '', textNodeName: '_text', parseAttributeValue: true, trimValues: true, isArray: (name) => ['chapter', 'verse'].includes(name) }); // Cache for parsed Bible data to avoid re-reading the file let cachedBible: any = null; let cachedGreekBible: any = null; interface VerseData { number: number; _text: string; } interface ChapterData { number: number; verse: VerseData[]; } interface BookData { number: number; chapter: ChapterData[]; } interface TestamentData { name: string; book: BookData[]; } interface BibleData { bible: { testament: TestamentData[]; }; } /** * Load and parse the Bible XML file */ function loadBibleXml(): BibleData { if (cachedBible) { return cachedBible; } const xmlPath = join(process.cwd(), 'EnglishNKJBible.xml'); const xmlContent = readFileSync(xmlPath, 'utf-8'); cachedBible = parser.parse(xmlContent) as BibleData; return cachedBible; } /** * Load and parse the Greek Bible XML file */ function loadGreekBibleXml(): BibleData { if (cachedGreekBible) { return cachedGreekBible; } const xmlPath = join(process.cwd(), 'GreekModern1904Bible.xml'); const xmlContent = readFileSync(xmlPath, 'utf-8'); cachedGreekBible = parser.parse(xmlContent) as BibleData; return cachedGreekBible; } /** * Get a specific book from the Bible XML */ function getBook(bookNumber: number): BookData | null { const bible = loadBibleXml(); // Old Testament books are 1-39, New Testament are 40-66 const testamentIndex = bookNumber <= 39 ? 0 : 1; const testament = bible.bible.testament[testamentIndex]; if (!testament) { return null; } // Find the book by number within the testament const bookIndex = bookNumber <= 39 ? bookNumber - 1 : bookNumber - 40; return testament.book[bookIndex] || null; } /** * Get a specific book from the Greek Bible XML */ function getGreekBook(bookNumber: number): BookData | null { const bible = loadGreekBibleXml(); // Old Testament books are 1-39, New Testament are 40-66 const testamentIndex = bookNumber <= 39 ? 0 : 1; const testament = bible.bible.testament[testamentIndex]; if (!testament) { return null; } // Find the book by number within the testament const bookIndex = bookNumber <= 39 ? bookNumber - 1 : bookNumber - 40; return testament.book[bookIndex] || null; } /** * Get a specific chapter from a book */ function getChapter(bookNumber: number, chapterNumber: number): ChapterData | null { const book = getBook(bookNumber); if (!book) { return null; } return book.chapter.find((ch) => ch.number === chapterNumber) || null; } /** * Get a specific chapter from the Greek Bible */ function getGreekChapter(bookNumber: number, chapterNumber: number): ChapterData | null { const book = getGreekBook(bookNumber); if (!book) { return null; } return book.chapter.find((ch) => ch.number === chapterNumber) || null; } /** * Get the number of verses in a specific chapter */ function getVerseCount(bookNumber: number, chapterNumber: number): number { const chapter = getChapter(bookNumber, chapterNumber); return chapter ? chapter.verse.length : 0; } /** * Get the number of verses in a specific Greek chapter */ function getGreekVerseCount(bookNumber: number, chapterNumber: number): number { const chapter = getGreekChapter(bookNumber, chapterNumber); return chapter ? chapter.verse.length : 0; } /** * Get the number of chapters in a specific book */ function getChapterCount(bookNumber: number): number { const book = getBook(bookNumber); return book ? book.chapter.length : 0; } /** * Get the number of chapters in a specific Greek book */ function getGreekChapterCount(bookNumber: number): number { const book = getGreekBook(bookNumber); return book ? book.chapter.length : 0; } /** * Extract consecutive verses from a specific location */ export function extractVerses( bookNumber: number, chapterNumber: number, startVerse: number, count: number ): string[] { const chapter = getChapter(bookNumber, chapterNumber); if (!chapter) { return []; } const verses: string[] = []; for (let i = 0; i < count; i++) { const verseIndex = startVerse - 1 + i; // Convert to 0-based index if (verseIndex >= chapter.verse.length) { break; } verses.push(chapter.verse[verseIndex]._text); } return verses; } /** * Extract consecutive verses from a specific Greek location */ function extractGreekVerses( bookNumber: number, chapterNumber: number, startVerse: number, count: number ): string[] { const chapter = getGreekChapter(bookNumber, chapterNumber); if (!chapter) { return []; } const verses: string[] = []; for (let i = 0; i < count; i++) { const verseIndex = startVerse - 1 + i; // Convert to 0-based index if (verseIndex >= chapter.verse.length) { break; } verses.push(chapter.verse[verseIndex]._text); } return verses; } /** * Get a random book number (1-66) */ function getRandomBookNumber(): number { return Math.floor(Math.random() * 66) + 1; } /** * Get a random chapter number for a specific book */ function getRandomChapterNumber(bookNumber: number): number { const chapterCount = getChapterCount(bookNumber); return Math.floor(Math.random() * chapterCount) + 1; } /** * Get a random starting verse number for a specific chapter * Ensures there are enough verses for the requested count */ function getRandomStartVerse(bookNumber: number, chapterNumber: number, verseCount: number): number { const totalVerses = getVerseCount(bookNumber, chapterNumber); const maxStartVerse = Math.max(1, totalVerses - verseCount + 1); return Math.floor(Math.random() * maxStartVerse) + 1; } /** * Get a random chapter number for a specific Greek book */ function getRandomGreekChapterNumber(bookNumber: number): number { const chapterCount = getGreekChapterCount(bookNumber); return Math.floor(Math.random() * chapterCount) + 1; } /** * Get a random starting verse number for a specific Greek chapter * Ensures there are enough verses for the requested count */ function getRandomGreekStartVerse(bookNumber: number, chapterNumber: number, verseCount: number): number { const totalVerses = getGreekVerseCount(bookNumber, chapterNumber); const maxStartVerse = Math.max(1, totalVerses - verseCount + 1); return Math.floor(Math.random() * maxStartVerse) + 1; } /** * Get a random set of verses from the Bible * Returns 3 consecutive verses by default */ export function getRandomVerses(count: number = 3): { bookId: string; bookName: string; chapter: number; startVerse: number; endVerse: number; verses: string[]; } | null { // Try up to 10 times to find a valid passage for (let attempt = 0; attempt < 10; attempt++) { const bookNumber = getRandomBookNumber(); const book = getBookByNumber(bookNumber); if (!book) { continue; } const chapterNumber = getRandomChapterNumber(bookNumber); const verseCount = getVerseCount(bookNumber, chapterNumber); // Skip chapters that don't have enough verses if (verseCount < count) { continue; } const startVerse = getRandomStartVerse(bookNumber, chapterNumber, count); const verses = extractVerses(bookNumber, chapterNumber, startVerse, count); if (verses.length === count) { return { bookId: book.id, bookName: book.name, chapter: chapterNumber, startVerse, endVerse: startVerse + count - 1, verses }; } } return null; } /** * Get a random set of verses from the Greek Bible * Returns 3 consecutive verses by default */ export function getRandomGreekVerses(count: number = 3): { bookId: string; bookName: string; chapter: number; startVerse: number; endVerse: number; verses: string[]; } | null { // Try up to 10 times to find a valid passage for (let attempt = 0; attempt < 10; attempt++) { const bookNumber = getRandomBookNumber(); const book = getBookByNumber(bookNumber); if (!book) { continue; } const chapterNumber = getRandomGreekChapterNumber(bookNumber); const verseCount = getGreekVerseCount(bookNumber, chapterNumber); // Skip chapters that don't have enough verses if (verseCount < count) { continue; } const startVerse = getRandomGreekStartVerse(bookNumber, chapterNumber, count); const verses = extractGreekVerses(bookNumber, chapterNumber, startVerse, count); if (verses.length === count) { return { bookId: book.id, bookName: book.name, chapter: chapterNumber, startVerse, endVerse: startVerse + count - 1, verses }; } } return null; } /** * Get a random set of verses from a specific book * Returns `count` consecutive verses by default */ export function getRandomVersesFromBook( bookNumber: number, count: number = 1 ): { bookId: string; bookName: string; chapter: number; startVerse: number; endVerse: number; verses: string[]; } | null { const book = getBookByNumber(bookNumber); if (!book) { return null; } // Try up to 10 times to find a valid passage for (let attempt = 0; attempt < 10; attempt++) { const chapterNumber = getRandomChapterNumber(bookNumber); const verseCount = getVerseCount(bookNumber, chapterNumber); // Skip chapters that don't have enough verses if (verseCount < count) { continue; } const startVerse = getRandomStartVerse(bookNumber, chapterNumber, count); const verses = extractVerses(bookNumber, chapterNumber, startVerse, count); if (verses.length === count) { return { bookId: book.id, bookName: book.name, chapter: chapterNumber, startVerse, endVerse: startVerse + count - 1, verses }; } } return null; } /** * Format a reference string from verse data */ export function formatReference(bookName: string, chapter: number, startVerse: number, endVerse: number): string { if (startVerse === endVerse) { return `${bookName} ${chapter}:${startVerse}`; } return `${bookName} ${chapter}:${startVerse}-${endVerse}`; } /** * Get all NKJV verses with metadata for embeddings */ export function getAllNKJVVerses(): Array<{ text: string; book: string; chapter: number; verse: number }> { const bible = loadBibleXml(); const verses: Array<{ text: string; book: string; chapter: number; verse: number }> = []; for (const testament of bible.bible.testament) { for (const bookData of testament.book) { const bookNumber = +bookData.number; const bookInfo = getBookByNumber(bookNumber); const bookName = bookInfo ? bookInfo.name : `Book ${bookNumber}`; for (const chapterData of bookData.chapter) { const chapterNum = +chapterData.number; for (const verseData of chapterData.verse) { const verseNum = +verseData.number; verses.push({ text: verseData._text || '', book: bookName, chapter: chapterNum, verse: verseNum }); } } } } return verses; }