@youversion/usfm-references
v0.2.0
Published
A type-safe interface for parsing, validating, and generating USFM Bible reference strings (USFMRef, USFMRefRange), modelled after the URL() interface.
Keywords
Readme
@youversion/usfm-references
Installation
yarn add @youversion/usfm-referencesAbout
An attempt to have an interface to parsing and generating "USFM" strings, that we so commonly use around YouVersion.
Modelled after the URL() interface. Such that using the constructor directly
to parse/validate a reference will throw an error, but using the exposed static
.parse() method will quietly return null if the given string can't be parsed.
See: https://developer.mozilla.org/en-US/docs/Web/API/URL.
Based on on our colloquial implementation of "USFM", which is based on https://ubsicap.github.io/usfm/identification/books.html and related standards.
For a more canonical specification of what is a "Valid USFM", and a suite of tests and validations, see https://github.com/youversion/usfm-references/blob/main/usfm_references/init.py and it's tests at https://github.com/youversion/usfm-references/blob/main/tests/test_valid.py.
Unfortunately, in an "offline" way, we can't possibly validate that a given Verse/Chapter is valid for a given Bible Version. So this parser is about ensuring a correct, specified format.
Quick start
import { USFMRef, USFMRefRange } from '@youversion/usfm-references';
// Chapters and Verses that are valid will give you an instance:
const validReference = new USFMRef('GEN.1.1')
// Instances have `.book` and `.chapter` and `.verse` properties, to get
// each distinct part of a valid USFM.
// They also directly convert to a canonical/normalized representation when
// cast to strings.
console.log(validReference.book) // "GEN"
console.log(validReference.verse) // "1"
console.log(validReference) // "GEN.1.1"
// Invalid references will throw a TypeError:
const thisLinesExplodes = new USFMRef('GEN.XYZ')
// If you don't want to deal with exceptions, use `.parse()`. It'll give you an
// instance if it's parseable, or `null` if not:
const maybeValid = USFMRef.parse(someValue)
if (maybeValid === null) {
// ^ Was invalid / unable to parse.
}
// If you need to work with a range of verses, use `USFMRefRange()`:
const someRange = new USFMRefRange('GEN.1.1-5');
// Now, `someRange.verses` is an array of `USFMRef()` instances.
typeof someRange.verses[0] // USFM
// USFMRange has helpers to quickly get the first/last Verse:
console.log(someRange.first) // "GEN.1.1"
console.log(someRange.last.book) // "GEN"
console.log(someRange.last.verse) // "5"
// And when cast to string, provides a YV API canonical Plus Range representation:
console.log(someRange) // "GEN.1.1+GEN.1.2+GEN.1.3+GEN.1.4+GEN.1.5"Usage
This library provides two classes for working with USFM references: USFMRef and USFMRefRange. Choosing the right class depends on whether you're working with a single verse reference or a range of verses.
USFMRef and USFMRefRange are distinct on purpose.
They can be relied on for type safety, and setting explicit expectations.
(That said, USFMRef could represent a Verse OR a Chapter/Introduction. We can
implement more granularity in the future if we need that kind of control.)
import { USFMRef, USFMRefRange } from '@youversion/usfm-references';
function processVerses(range: USFMRefRange) { /* ... */}
function expectsOnlySingleVerse(usfm: USFMRef) {
if (usfm.verse === null) {
throw new Error("Expected to receive a Verse, but got a Chapter instead.")
}
}
function expectsOnlyChapterNoVerse(usfm: USFMRef) {
if (usfm.verse) {
throw new Error("Chapters only please!")
}
}USFMRef for Single Verse References
The USFMRef class is designed for representing and parsing single USFM verse references, like "GEN.1.1" or "EXO.3.15". Use this class when you need to work with individual verses.
Creating USFMRef Instances:
You can create a USFMRef instance by passing a valid USFM string to the constructor:
import { USFMRef } from '@youversion/usfm-references';
const gen11 = new USFMRef('GEN.1.1');
const exo315 = new USFMRef('EXO.3.15');
const john316 = new USFMRef('JHN.3.16');
const gen1 = new USFMRef('GEN.1'); // Chapter reference
const genIntro = new USFMRef('GEN.INTRO1'); // Intro chapter referenceAccessing Properties:
Once you have a USFMRef instance, you can access its properties:
// Continued from above example...
console.log(gen11.book); // Output: "GEN"
console.log(gen11.chapter); // Output: "1"
console.log(gen11.verse); // Output: "1"
console.log(gen1.book); // Output: "GEN"
console.log(gen1.chapter); // Output: "1"
console.log(gen1.verse); // Output: null
console.log(genIntro.book); // Output: "GEN"
console.log(genIntro.chapter); // Output: "INTRO1"
console.log(genIntro.verse); // Output: nullInvalid USFM:
The USFMRef constructor will throw a TypeError if the input string is not a valid USFM reference. This helps catch errors early in your development process. Examples of invalid USFM include:
import { USFMRef } from '@youversion/usfm-references';
// Throws a TypeError:
new USFMRef('GEN.1.1.1'); // Too many parts
new USFMRef('gen.1.1'); // Book must be uppercase
new USFMRef('GEN.1.a'); // Verse must be a number
new USFMRef('GEN'); // Missing chapter
new USFMRef('GEN.1.1-2'); // Use USFMRange for ranges
new USFMRef('GEN.1.1+GEN.1.2'); // Use USFMRange for rangesParsing without Errors:
If you're unsure whether a string is a valid USFM reference, use the static USFMRef.parse() method. This method returns a USFMRef instance if the string is valid, or null if it's not. This prevents your code from throwing errors due to malformed input.
import { USFMRef } from '@youversion/usfm-references';
const usfmRef = USFMRef.parse('GEN.1.1'); // Returns a USFMRef instance
const invalidRef = USFMRef.parse('invalid-usfm'); // Returns null
if (usfmRef) {
console.log(usfmRef.book); // Safe to access properties
}
if (invalidRef === null) {
console.warn("Invalid USFM reference!");
}USFMRefRange for Verse Ranges
The USFMRefRange class is used to represent and parse ranges of USFM verses, such as "GEN.1.1-5" or "EXO.3.15+EXO.3.16+EXO.3.17". Use this class when you need to work with multiple verses in a contiguous or explicitly listed sequence.
Creating USFMRefRange Instances:
You can create a USFMRefRange instance using a range string (dash or plus notation) or an array of USFM strings:
import { USFMRefRange } from '@youversion/usfm-references';
const range1 = new USFMRefRange('GEN.1.1-5'); // Dash range
const range2 = new USFMRefRange('EXO.3.15+EXO.3.16+EXO.3.17'); // Plus range
const range3 = new USFMRefRange(['GEN.1.1', 'GEN.1.2', 'GEN.1.3']); // Array of USFM stringsAccessing Verses:
The USFMRefRange class provides a verses property, which is an array of USFMRef instances representing the verses in the range:
// Continued from above example...
const verses = range1.verses;
verses.forEach(verse => {
console.log(verse.book, verse.chapter, verse.verse);
});
console.log(range1.first.verse); // Output: "1" (first verse in the range)
console.log(range1.last.verse); // Output: "5" (last verse in the range)
console.log(range2.first.verse); // Output: "15"
console.log(range2.last.verse); // Output: "17"Invalid Ranges:
The USFMRefRange constructor will throw a TypeError for invalid range strings or arrays. Ensure that all verses in the range belong to the same book and chapter, and that the verses are contiguous (for dash ranges) or explicitly listed in the correct order (for plus ranges or arrays). Mixing range types in an array (e.g., dash and plus) is also not allowed.
import { USFMRefRange } from '@youversion/usfm-references';
// Throws a TypeError:
new USFMRefRange('GEN.1.1-5+GEN.1.7'); // Mixed range types
new USFMRefRange('GEN.1.1-GEN.2.1'); // Different chapters
new USFMRefRange('GEN.1.5-1'); // Invalid dash range order
new USFMRefRange(['GEN.1.1', 'GEN.2.1']); // Different chapters in array
new USFMRefRange(['GEN.1.1', 'GEN.1.2-3']); // Mixed range types in array
new USFMRefRange(['GEN.1.1', 123]); // Invalid type in array
new USFMRefRange([]); // Empty array
new USFMRefRange(123); // Invalid input typeRemember to use the USFMRef class for single verse references and the USFMRefRange class for ranges of verses. Using the correct class will help ensure that your code is clear, maintainable, and less prone to errors.
See the test suite for more usage examples.
Validation Helpers
This library also provides several helper functions to quickly validate different parts of a USFM reference. These can be useful for input validation or other checks within your application, when you don't need the granularity of using the classes directly.
validBook(maybeUsfmBook: string): boolean
Checks if a given string is a valid USFM book name (e.g., "GEN", "MAT", "1TI"). It only checks if the string is present in the list of valid book names; it doesn't validate the entire USFM string format.
import { validBook } from '@youversion/usfm-references';
console.log(validBook('GEN')); // true
console.log(validBook('gen')); // false (case-sensitive)
console.log(validBook('GEN.1')); // false (includes chapter)
console.log(validBook('ZZZ')); // false (invalid book name)validVerse(maybeVerse: string): boolean
Checks if a given string is a valid USFM verse reference (e.g., "GEN.1.1"). It verifies that the string includes a book, chapter, and verse.
import { validVerse } from '@youversion/usfm-references';
console.log(validVerse('GEN.1.1')); // true
console.log(validVerse('GEN.1')); // false (chapter only)
console.log(validVerse('GEN.1.a')); // false (invalid verse number)
console.log(validVerse('GEN.1.1-2')); // false (range, not a single verse)validChapter(maybeChapter: string): boolean
Checks if a given string is a valid USFM chapter reference (e.g., "GEN.1" or "1TI.INTRO01"). It confirms the string contains a book and chapter (or intro chapter), but not a verse.
import { validChapter } from '@youversion/usfm-references';
console.log(validChapter('GEN.1')); // true
console.log(validChapter('GEN.INTRO1')); // true (intro chapter)
console.log(validChapter('GEN.1.1')); // false (includes verse)
console.log(validChapter('GEN')); // false (book only)These validation helpers provide a convenient way to perform quick checks on USFM strings in your code. They can be particularly helpful when dealing with user input or external data sources where the validity of USFM references might not be guaranteed.
