UNPKG

bible-reference-formatter

Version:

Utility that converts Bible references from OSIS to human-readable formats and Paratext.

110 lines (101 loc) 5.5 kB
"use strict"; const paratexts = Object.freeze({ "GEN": "Gen", "EXO": "Exod", "LEV": "Lev", "NUM": "Num", "DEU": "Deut", "JOS": "Josh", "JDG": "Judg", "RUT": "Ruth", "1SA": "1Sam", "2SA": "2Sam", "1KI": "1Kgs", "2KI": "2Kgs", "1CH": "1Chr", "2CH": "2Chr", "EZR": "Ezra", "NEH": "Neh", "EST": "Esth", "JOB": "Job", "PSA": "Ps", "PRO": "Prov", "ECC": "Eccl", "SNG": "Song", "ISA": "Isa", "JER": "Jer", "LAM": "Lam", "EZK": "Ezek", "DAN": "Dan", "HOS": "Hos", "JOL": "Joel", "AMO": "Amos", "OBA": "Obad", "JON": "Jonah", "MIC": "Mic", "NAM": "Nah", "HAB": "Hab", "ZEP": "Zeph", "HAG": "Hag", "ZEC": "Zech", "MAL": "Mal", "MAT": "Matt", "MRK": "Mark", "LUK": "Luke", "JHN": "John", "ACT": "Acts", "ROM": "Rom", "1CO": "1Cor", "2CO": "2Cor", "GAL": "Gal", "EPH": "Eph", "PHP": "Phil", "COL": "Col", "1TH": "1Thess", "2TH": "2Thess", "1TI": "1Tim", "2TI": "2Tim", "TIT": "Titus", "PHM": "Phlm", "HEB": "Heb", "JAS": "Jas", "1PE": "1Pet", "2PE": "2Pet", "1JN": "1John", "2JN": "2John", "3JN": "3John", "JUD": "Jude", "REV": "Rev", "TOB": "Tob", "JDT": "Jdt", "ESG": "EsthGr", "ADE": "AddEsth", "WIS": "Wis", "SIR": "Sir", "BAR": "Bar", "LJE": "EpJer", "DAG": "DanGr", "S3Y": "PrAzar", "SUS": "Sus", "BEL": "Bel", "1MA": "1Macc", "2MA": "2Macc", "3MA": "3Macc", "4MA": "4Macc", "MAN": "PrMan", "1ES": "1Esd", "2ES": "2Esd", "PS2": "AddPs" }); // Some subset of "MAT 1:2-MRK 3:4" const paratextFormat = /^[1-4A-Z]{3}(?: \d{1,3}(?::\d{1,3})?)?(?:-(?:[1-4A-Z]{3}(?: \d{1,3}(?::\d{1,3})?)?|\d{1,3}(?::\d{1,3})?))?$/; const paratextBookFormat = /^[1-4A-Z]{3}$/; const paratextCVFormat = /^\d{1,3}:\d{1,3}$/; const numberFormat = /^\d+$/; // Convert a Paratext reference (`GEN 1:1`) to the equivalent OSIS reference (`Gen.1.1`). function paratextToOsis(paratext) { if (typeof paratext !== "string") { throw "paratextToOsis: first argument must be a string."; } // The string can be a comma-separated list. const parts = paratext.split(","); const osises = []; // Handle each item in the list individually. Context doesn't propagate across comma boundaries. for (let i = 0, max = parts.length; i < max; i++) { const part = parts[i]; // Throws an exception on failure. validateFormat(part); osises.push(partToOsis(part)); } return osises.join(","); } // Confirm that the string matches the expected format of a Paratext string. Throw an exception if not. function validateFormat(paratext) { if (paratextFormat.test(paratext)) { return true; } throw `Invalid paratext format: '${ paratext }'`; } // Convert a single, non-comma-separated Paratext reference to OSIS. function partToOsis(paratext) { // `end` may be undefined. const [start, end] = paratext.split("-"); const [startBook, startChapter, startVerse] = splitPart(start); if (typeof paratexts[startBook] === "undefined") { throw `Unknown paratext start book: ${ startBook }`; } const startOsis = bcvToOsis(startBook, startChapter, startVerse); // If `end` is undefined, don't go any further. if (end === undefined) { return startOsis; } const [endBook, endChapter, endVerse] = getEnd(end, startBook, startChapter, startVerse); const endOsis = bcvToOsis(endBook, endChapter, endVerse); return startOsis + "-" + endOsis; } // Given a book and possible chapter and verse, convert them to OSIS. We already know that `book` exists in `paratexts`. function bcvToOsis(book, chapter, verse) { const out = [paratexts[book]]; if (typeof chapter === "string") { out.push(chapter); if (typeof verse === "string") { out.push(verse); } } return out.join("."); } // Handle the end of a range. It needs the start context to fill in missing values in some cases. function getEnd(end, startBook, startChapter, startVerse) { const [endBook, endChapter, endVerse] = splitPart(end); // If there's a valid book, use all the end values. if (endBook.length > 0) { // Throw an exception if the book name isn't a valid Paratext abbreviation. if (typeof paratexts[endBook] === "undefined") { throw `Unknown paratext end book: ${ endBook }`; } return [endBook, endChapter, endVerse]; // If there's an `endVerse`, we know that both `endChapter` and `endVerse` are defined. Use the `startBook` but the other end values. } if (typeof endVerse === "string") { return [startBook, endChapter, endVerse]; // There's a single numeric value, but whether it's a chapter or a verse depends on the context. } // If there was a verse specified in `start`, then assume the value is an ending verse. if (typeof startVerse === "string") { return [startBook, startChapter, endChapter]; } return [startBook, endChapter]; } // Try to put the values in the right slot. The only ambiguous one is when the part consists of only a number. In that case, the number could be either a chapter or a verse. function splitPart(paratext) { // `1:2`. It omits a book. if (paratextCVFormat.test(paratext)) { const [chapter, verse] = paratext.split(":"); return ["", chapter, verse]; // `3`. Not sure whether it's a chapter or a verse (only happens at the end of a range). We return it as a chapter and let the calling function decide what to do. } else if (numberFormat.test(paratext)) { return ["", paratext]; // `MAT`. If it's only a book, we don't need to figure out what to do with it. } else if (paratextBookFormat.test(paratext)) { return [paratext]; } // Otherwise, we know it's of the format `MAT 1:2` (`B C:V`). return paratext.split(/[ :]/); } /* global module */ module.exports = paratextToOsis;