Skip to content

Instantly share code, notes, and snippets.

@eldhosejoys
Created August 21, 2025 18:05
Show Gist options
  • Save eldhosejoys/e4d4dfa277bc46d2380d25ec5b3c47a4 to your computer and use it in GitHub Desktop.
Save eldhosejoys/e4d4dfa277bc46d2380d25ec5b3c47a4 to your computer and use it in GitHub Desktop.
To convert openbible.info cross-references to json files (https://www.openbible.info/labs/cross-references/)
{
"Genesis": 1, "Gen": 1, "Ge": 1, "Gn": 1,
"Exodus": 2, "Exod": 2, "Exo": 2, "Ex": 2,
"Leviticus": 3, "Lev": 3, "Le": 3, "Lv": 3,
"Numbers": 4, "Num": 4, "Nu": 4, "Nm": 4, "Nb": 4,
"Deuteronomy": 5, "Deut": 5, "De": 5, "Dt": 5,
"Joshua": 6, "Josh": 6, "Jos": 6, "Jsh": 6,
"Judges": 7, "Judg": 7, "Jdg": 7, "Jg": 7, "Jdgs": 7,
"Ruth": 8, "Rth": 8, "Ru": 8,
"1 Samuel": 9, "1 Sam": 9, "1Sam": 9, "1S": 9, "1Sm": 9, "1Sa": 9,
"2 Samuel": 10, "2 Sam": 10, "2Sam": 10, "2S": 10, "2Sm": 10, "2Sa": 10,
"1 Kings": 11, "1 Kgs": 11, "1Kgs": 11, "1K": 11, "1Ki": 11,
"2 Kings": 12, "2 Kgs": 12, "2Kgs": 12, "2K": 12, "2Ki": 12,
"1 Chronicles": 13, "1 Chr": 13, "1Chr": 13, "1Ch": 13,
"2 Chronicles": 14, "2 Chr": 14, "2Chr": 14, "2Ch": 14,
"Ezra": 15, "Ezr": 15, "Ez": 15,
"Nehemiah": 16, "Neh": 16, "Ne": 16,
"Esther": 17, "Esth": 17, "Es": 17, "Est": 17,
"Job": 18, "Jb": 18,
"Psalms": 19, "Psalm": 19, "Ps": 19, "Psa": 19, "Psm": 19, "Pss": 19,
"Proverbs": 20, "Prov": 20, "Pro": 20, "Prv": 20, "Pr": 20,
"Ecclesiastes": 21, "Eccl": 21, "Ecc": 21, "Ec": 21, "Qoh": 21,
"Song of Solomon": 22, "Song of Songs": 22, "Song": 22, "So": 22, "SOS": 22, "Cant": 22,
"Isaiah": 23, "Isa": 23, "Is": 23,
"Jeremiah": 24, "Jer": 24, "Je": 24, "Jr": 24,
"Lamentations": 25, "Lam": 25, "La": 25,
"Ezekiel": 26, "Ezek": 26, "Eze": 26, "Ezk": 26,
"Daniel": 27, "Dan": 27, "Da": 27, "Dn": 27,
"Hosea": 28, "Hos": 28, "Ho": 28,
"Joel": 29, "Joe": 29, "Jl": 29,
"Amos": 30, "Am": 30,
"Obadiah": 31, "Obad": 31, "Ob": 31,
"Jonah": 32, "Jnh": 32, "Jon": 32,
"Micah": 33, "Mic": 33, "Mi": 33,
"Nahum": 34, "Nah": 34, "Na": 34,
"Habakkuk": 35, "Hab": 35, "Hb": 35,
"Zephaniah": 36, "Zeph": 36, "Zep": 36, "Zp": 36,
"Haggai": 37, "Hag": 37, "Hg": 37,
"Zechariah": 38, "Zech": 38, "Zec": 38, "Zc": 38,
"Malachi": 39, "Mal": 39, "Ml": 39,
"Matthew": 40, "Matt": 40, "Mat": 40, "Mt": 40,
"Mark": 41, "Mrk": 41, "Mar": 41, "Mk": 41, "Mr": 41,
"Luke": 42, "Luk": 42, "Lk": 42,
"John": 43, "Jhn": 43, "Jn": 43,
"Acts": 44, "Act": 44, "Ac": 44,
"Romans": 45, "Rom": 45, "Ro": 45, "Rm": 45,
"1 Corinthians": 46, "1 Cor": 46, "1Cor": 46, "1Co": 46,
"2 Corinthians": 47, "2 Cor": 47, "2Cor": 47, "2Co": 47,
"Galatians": 48, "Gal": 48, "Ga": 48,
"Ephesians": 49, "Eph": 49, "Ep": 49,
"Philippians": 50, "Phil": 50, "Php": 50, "Pp": 50,
"Colossians": 51, "Col": 51, "Co": 51,
"1 Thessalonians": 52, "1 Thess": 52, "1Thess": 52, "1Th": 52,
"2 Thessalonians": 53, "2 Thess": 53, "2Thess": 53, "2Th": 53,
"1 Timothy": 54, "1 Tim": 54, "1Tim": 54, "1Ti": 54,
"2 Timothy": 55, "2 Tim": 55, "2Tim": 55, "2Ti": 55,
"Titus": 56, "Tit": 56, "Ti": 56,
"Philemon": 57, "Phlm": 57, "Phm": 57, "Pm": 57,
"Hebrews": 58, "Heb": 58,
"James": 59, "Jas": 59, "Jm": 59,
"1 Peter": 60, "1 Pet": 60, "1Pet": 60, "1P": 60, "1Pt": 60,
"2 Peter": 61, "2 Pet": 61, "2Pet": 61, "2P": 61, "2Pt": 61,
"1 John": 62, "1 John": 62, "1John": 62, "1Jn": 62,
"2 John": 63, "2 John": 63, "2John": 63, "2Jn": 63,
"3 John": 64, "3 John": 64, "3John": 64, "3Jn": 64,
"Jude": 65, "Jud": 65, "Jd": 65,
"Revelation": 66, "Rev": 66, "Re": 66, "The Rev": 66
}
const fs = require('fs');
const path = require('path');
// --- CONFIGURATION ---
const abbrevsFilePath = path.join(__dirname, 'abbrevs.json');
const inputFilePath = path.join(__dirname, 'cross_references.txt');
const outputDir = path.join(__dirname, 'books-cross');
// A Set to store all unique unknown abbreviations we find
const unknownAbbrevs = new Set();
/**
* Parses a verse reference string (e.g., "Gen.1.1") into its components.
*/
function parseVerse(verseString, abbrevsMap, lineNumber) {
const match = verseString.match(/^(.+?)\.(\d+)\.(\d+)$/);
if (!match) {
console.warn(`[Line ${lineNumber}] Could not parse verse format: ${verseString}`);
return null;
}
const [, bookAbbrev, chapter, verse] = match;
const bookNum = abbrevsMap[bookAbbrev];
if (!bookNum) {
if (!unknownAbbrevs.has(bookAbbrev)) {
console.warn(`[Line ${lineNumber}] Unknown book abbreviation found: '${bookAbbrev}'`);
unknownAbbrevs.add(bookAbbrev);
}
return null;
}
return {
book: bookAbbrev,
bookNum: bookNum,
chapter: parseInt(chapter, 10),
verse: parseInt(verse, 10)
};
}
/**
* Parses a 'toVerse' string.
* If it's a single verse, returns an array with one element.
* If it's a range, returns an array with the start and end verses.
*/
function formatToVerse(toVerseString, abbrevsMap, lineNumber) {
// If it's NOT a range, handle it as a single verse.
if (!toVerseString.includes('-')) {
const parsed = parseVerse(toVerseString, abbrevsMap, lineNumber);
return parsed ? [`${parsed.bookNum}/${parsed.chapter}/${parsed.verse}`] : [];
}
// --- THIS IS THE MODIFIED LOGIC FOR HANDLING RANGES ---
const [startVerseStr, endVerseStr] = toVerseString.split('-');
const startVerse = parseVerse(startVerseStr, abbrevsMap, lineNumber);
if (!startVerse) return [];
let endVerse;
// Handle ranges where the end is just a number (e.g., "Gen.1.1-5")
if (endVerseStr.includes('.')) {
endVerse = parseVerse(endVerseStr, abbrevsMap, lineNumber);
} else {
// Re-use the book and chapter from the start verse
endVerse = { ...startVerse, verse: parseInt(endVerseStr, 10) };
}
if (!endVerse || isNaN(endVerse.verse)) {
console.warn(`[Line ${lineNumber}] Could not parse range's end verse: ${toVerseString}`);
return [];
}
const startVerseFormatted = `${startVerse.bookNum}/${startVerse.chapter}/${startVerse.verse}`;
const endVerseFormatted = `${endVerse.bookNum}/${endVerse.chapter}/${endVerse.verse}`;
// If start and end are identical (e.g., from a malformed range "Gen.1.1-1"), just return one.
if (startVerseFormatted === endVerseFormatted) {
return [startVerseFormatted];
}
// Return an array with only the start and end of the range.
return [startVerseFormatted, endVerseFormatted];
}
// --- Main Execution ---
try {
const abbrevsMap = JSON.parse(fs.readFileSync(abbrevsFilePath, 'utf8'));
const fileContent = fs.readFileSync(inputFilePath, 'utf8');
const lines = fileContent.trim().split('\n');
if (lines.length <= 1) {
console.log('The file is empty or contains only a header.');
return;
}
const groupedData = {};
for (let i = 1; i < lines.length; i++) {
const lineNumber = i + 1;
const line = lines[i];
const columns = line.split('\t');
if (columns.length < 3) continue;
const fromVerse = parseVerse(columns[0], abbrevsMap, lineNumber);
if (!fromVerse) continue;
const toVerses = formatToVerse(columns[1], abbrevsMap, lineNumber);
if (toVerses.length === 0) continue;
const crossRefObject = {
b: fromVerse.bookNum,
c: fromVerse.chapter,
v: fromVerse.verse,
to: toVerses,
votes: parseInt(columns[2], 10)
};
if (!groupedData[fromVerse.bookNum]) {
groupedData[fromVerse.bookNum] = [];
}
groupedData[fromVerse.bookNum].push(crossRefObject);
}
// --- Final Report and File Writing ---
if (unknownAbbrevs.size > 0) {
console.error("\n--- DEBUG SUMMARY ---");
console.error("Conversion failed. Found unknown abbreviations:");
console.error(Array.from(unknownAbbrevs).join(', '));
console.error(`\nPlease add these to '${path.basename(abbrevsFilePath)}' and run again.`);
} else {
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
let filesWritten = 0;
for (const bookNumber in groupedData) {
const bookData = groupedData[bookNumber];
const outputFilePath = path.join(outputDir, `${bookNumber}.json`);
const jsonString = JSON.stringify(bookData, null, 2);
fs.writeFileSync(outputFilePath, jsonString, 'utf8');
filesWritten++;
}
console.log(`\nSuccess! Conversion complete.`);
console.log(`${filesWritten} files were written to the '${outputDir}' directory.`);
}
} catch (error) {
console.error('An error occurred:', error);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment