diff --git a/README.md b/README.md index 3bde866..993f11b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -`bible-ref-parser` is a Node.js package that parses Bible references into structured objects. It supports complex references such as `Genesis 1:1-2; II Peter; John 1:1,3-5,8` and converts them into accessible data formats. +A Node.js package to parses Bible references into structured objects. It supports complex references such as `Genesis 1:1-2; II Peter; John 1:1,3-5,8`. ## Installation @@ -13,38 +13,73 @@ npm install @idrw/bible-ref-parser ## Usage +### Example query, with errors ```typescript -import { parseQuery } from 'bible-ref-parser'; +import { parseQuery } from '@idrw/bible-ref-parser'; -const query = "Genesis 1:1-2; II Peter; John 1:1,3-5,8"; -const result: BookData[] = parseQuery(query); +const result: QueryResult = parseQuery("! pasta;RM-RF/;1John1:2,2:1;IIPeter1:1;IJohn2:4;#--324;1 kebab 2:1;'_&%\"!\"¥"); +``` -console.log(result); +```json +{ + "books": [ + { + "name": "1 John", + "references": [ + { "chapter": 1, "verses": [{ "from": 2 }] }, + { "chapter": 2, "verses": [{ "from": 1 }] } + ] + }, + { + "name": "2 Peter", + "references": [ + { "chapter": 1, "verses": [{ "from": 1 }] } + ] + }, + { + "name": "1 John", + "references": [ + { "chapter": 2, "verses": [{ "from": 4 }] } + ] + } + ], + "errors": ["! pasta", "RM-RF/", "#--324", "1 kebab", "'_&%\"!\"¥"] +} ``` +### Example query, without errors +```typescript +import { parseQuery } from '@idrw/bible-ref-parser'; + +const result: QueryResult = parseQuery("Genesis 1:1-2; II Peter; John 1:1,3-5,8"); ``` -[ - { - book: "Genesis", - references: [ - { chapter: 1, verses: [{ from: 1, to: 2 }] } - ] - }, - { - book: "2 Peter", - references: [] - }, - { - book: "John", - references: [ - { chapter: 1, verses: [ - { from: 1, to: undefined }, - { from: 3, to: 5 }, - { from: 8, to: undefined } - ] } - ] - } -] + +```json +{ + "books": [ + { + "name": "Genesis", + "references": [ + { "chapter": 1, "verses": [{ "from": 1, "to": 2 }] } + ] + }, + { "name": "2 Peter", "references": [] }, + { + "name": "John", + "references": [ + { + "chapter": 1, + "verses": [ + { "from": 1 }, + { "from": 3, "to": 5 }, + { "from": 8 } + ] + } + ] + } + ], + "errors": [] +} ``` ## Contributing diff --git a/package.json b/package.json index 02ea20f..0271330 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@idrw/bible-ref-parser", - "version": "1.0.1", + "version": "2.0.0", "main": "./build/index.js", "type": "module", "scripts": { diff --git a/src/index.ts b/src/index.ts index 617c57b..543196d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,17 @@ import { books } from "./books.js" const QUERY_SEPARATOR = ";" +export type QueryResult = { + books: BookData[], + errors: string[] +} + +export type ParseBookResult = { + book: BookData | null, + error: string | null +} + + export type BookData = { name: string, references: ChapterData[] @@ -20,24 +31,33 @@ export type VerseRange = { } -export function parseQuery(query: string): BookData[] { +export function parseQuery(query: string): QueryResult { const bookQueries = splitQueryByBooks(query) + const queryResult: QueryResult = { + books: [], + errors: [] + } - let bookDataList: BookData[] = [] for (const bookQuery of bookQueries) { if (!isValidQuery(bookQuery)) { + queryResult.errors.push(bookQuery) continue } - const bookData = parseBook(bookQuery) + const parseResult = parseBook(bookQuery) - if (bookData) { - bookDataList.push(bookData) + if (parseResult.error) { + queryResult.errors.push(parseResult.error) + continue + } + + if (parseResult.book) { + queryResult.books.push(parseResult.book) } } - return bookDataList + return queryResult } @@ -55,19 +75,22 @@ function isValidPositiveNumber(n: string) { } -function parseBook(query: string): BookData | null { +function parseBook(query: string): ParseBookResult { query = replaceRomanNumbers(query) let { bookName, chapterBeginIndex } = parseBookName(query) const validatedName = validateBookName(bookName) if (!validatedName) { - return null + return { book: null, error: bookName } } let references = parseReferences(query.slice(chapterBeginIndex)) - return { name: validatedName, references } + return { + book: { name: validatedName, references }, + error: null + } } @@ -147,7 +170,7 @@ function parseBookName(query: string) { for (let i = nameBeginIndex; i < query.length; i++) { const char = query[i] - if (char == " ") { + if (char === " " && char === bookName.charAt(bookName.length - 1)) { continue } diff --git a/test/app.test.ts b/test/app.test.ts index 2dd5a88..baa0e2b 100644 --- a/test/app.test.ts +++ b/test/app.test.ts @@ -88,13 +88,14 @@ test('replaceRomanNumbers() should return the original string if no book number test('parseBookName() should return the book name (including sequence nr) along with the index at which first chapter reference begins', async (t) => { const cases = [ - { input: "Genesis 1:1", expected: { bookName: "Genesis", chapterBeginIndex: 8 } }, - { input: "Genesis", expected: { bookName: "Genesis", chapterBeginIndex: 0 } }, - { input: "1 Peter 1:1", expected: { bookName: "1 Peter", chapterBeginIndex: 8 } }, - { input: "1 John 1", expected: { bookName: "1 John", chapterBeginIndex: 7 } }, - { input: "1 John 1", expected: { bookName: "1 John", chapterBeginIndex: 10 } }, - { input: "1 John 1", expected: { bookName: "1 John", chapterBeginIndex: 10 } }, - { input: "1John", expected: { bookName: "1 John", chapterBeginIndex: 0 } }, + { input: "Genesis 1:1", expected: { bookName: "Genesis", chapterBeginIndex: 8 } }, + { input: "Genesis", expected: { bookName: "Genesis", chapterBeginIndex: 0 } }, + { input: "1 Peter 1:1", expected: { bookName: "1 Peter", chapterBeginIndex: 8 } }, + { input: "1 John 1", expected: { bookName: "1 John", chapterBeginIndex: 7 } }, + { input: "1 John 1", expected: { bookName: "1 John", chapterBeginIndex: 10 } }, + { input: "1 John 1", expected: { bookName: "1 John", chapterBeginIndex: 10 } }, + { input: "1John", expected: { bookName: "1 John", chapterBeginIndex: 0 } }, + { input: "Song of solomon", expected: { bookName: "Song of solomon", chapterBeginIndex: 0 } } ] for (const { input, expected } of cases) { @@ -258,35 +259,43 @@ test('validateBookName() should return true for books that are in the list', asy test('parseBook() should return the expected bookData', async (t) => { const cases = [ { input: "1Kings1:2", expected: { - name: "1 Kings", - references: [ - { chapter: 1, verses: [{ from: 2, to: undefined }] } - ] + book: { name: "1 Kings", references: [{ chapter: 1, verses: [{ from: 2, to: undefined }] }] }, + error: null + } }, + + { input: "III John 1", expected: { + book: { name: "3 John", references: [] }, + error: null }}, - { input: "III John 1", expected: { name: "3 John", references: [] }}, - - { input: "Gen 1", expected: { name: "Genesis", references: [] } }, - - { input: "2 Pt 1:2", expected: { name: "2 Peter", references: [ - { chapter: 1, verses: [{ from: 2, to: undefined }]} - ] } }, - - { input: "2 Pet 1:2", expected: { name: "2 Peter", references: [ - { chapter: 1, verses: [{ from: 2, to: undefined }]} - ] } }, - - { input: "2nd Peter", expected: { name: "2 Peter", references: [] } }, - { input: "First John", expected: { name: "1 John", references: [] } }, - { input: "1 John", expected: { name: "1 John", references: [] } }, - { input: "1st John", expected: { name: "1 John", references: [] } }, - { input: "first JOHN", expected: { name: "1 John", references: [] } }, - { input: "2ndtim", expected: { name: "2 Timothy", references: [] } }, - { input: "isa", expected: { name: "Isaiah", references: [] } }, - { input: "isam", expected: { name: "1 Samuel", references: [] } }, - { input: "Ism", expected: { name: "1 Samuel", references: [] } }, - { input: "IIsm", expected: { name: "2 Samuel", references: [] } }, - { input: "iSaIAH", expected: { name: "Isaiah", references: [] } }, + { input: "Gen 1", expected: { + book: { name: "Genesis", references: [] }, + error: null + } }, + + { input: "2 Pt 1:2", expected: { + book: { name: "2 Peter", references: [{ chapter: 1, verses: [{ from: 2, to: undefined }]}] }, + error: null + } }, + + { input: "2 Pet 1:2", expected: { + book: { name: "2 Peter", references: [{ chapter: 1, verses: [{ from: 2, to: undefined }]}] }, + error: null + } }, + + { input: "2nd Peter", expected: { book: { name: "2 Peter", references: [] }, error: null } }, + { input: "First John", expected: { book: { name: "1 John", references: [] }, error: null } }, + { input: "1 John", expected: { book: { name: "1 John", references: [] }, error: null } }, + { input: "1st John", expected: { book: { name: "1 John", references: [] }, error: null } }, + { input: "first JOHN", expected: { book: { name: "1 John", references: [] }, error: null } }, + { input: "2ndtim", expected: { book: { name: "2 Timothy", references: [] }, error: null } }, + { input: "isa", expected: { book: { name: "Isaiah", references: [] }, error: null } }, + { input: "isam", expected: { book: { name: "1 Samuel", references: [] }, error: null } }, + { input: "Ism", expected: { book: { name: "1 Samuel", references: [] }, error: null } }, + { input: "IIsm", expected: { book: { name: "2 Samuel", references: [] }, error: null } }, + { input: "iSaIAH", expected: { book: { name: "Isaiah", references: [] }, error: null } }, + +// { input: "! Genesis", expected: { book: null, error: "!Genesis" } }, ] for (const { input, expected } of cases) { @@ -299,39 +308,52 @@ test('parseQuery() should return the expected bookData[]', async (t) => { const cases = [ { input: "1Kings1:2;IIIJohn1", - expected: [ - { - name: "1 Kings", - references: [ - { chapter: 1, verses: [{ from: 2, to: undefined }] } - ] - }, - { name: "3 John", references: [] } - ] + expected: { + books: [ + { + name: "1 Kings", + references: [ + { chapter: 1, verses: [{ from: 2, to: undefined }] } + ] + }, + { name: "3 John", references: [] } + ], + errors: [] + } }, { input: "III John 1:1-2;Genesis 1:10-12", - expected: [ - { - name: "3 John", - references: [{ chapter: 1, verses: [{ from: 1, to: 2 }] }] - }, - { - name: "Genesis", - references: [{ chapter: 1, verses: [{ from: 10, to: 12 }] }] - } - ] + expected: { + books: [ + { + name: "3 John", + references: [{ chapter: 1, verses: [{ from: 1, to: 2 }] }] + }, + { + name: "Genesis", + references: [{ chapter: 1, verses: [{ from: 10, to: 12 }] }] + } + ], + errors: [] + } }, { - input: ";##;#!\"@,;Genesis 1:10-12", - expected: [ - { - name: "Genesis", - references: [{ chapter: 1, verses: [{ from: 10, to: 12 }] }] - } - ] + input: ";##;#!\"@,;Genesis 1:10-12;song of solomon", + expected: { + books: [ + { + name: "Genesis", + references: [{ chapter: 1, verses: [{ from: 10, to: 12 }] }] + }, + { + name: "Song of Solomon", + references: [] + } + ], + errors: ["##", "#!\"@,"] + } } ]