Bible text I/O library, for retrieving and navigating text in KJV Bible More details at: http://mbed.org/users/davervw/notebook/ebible-abstract/

Dependents:   eBible

BibleIO.cpp

Committer:
davervw
Date:
2011-02-27
Revision:
0:1f3d069211cd

File content as of revision 0:1f3d069211cd:

/* Bible I/O Class Implementation
 *
 * Copyright (c) 2011 David R. Van Wagner davervw@yahoo.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/*
 * This class assumes bible text file is present and in a specific format extracted from source such as:
 *      http://printkjv.ifbweb.com/AV_txt.zip
 */

#include <mbed.h> // mkdir
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <locale>
#include "BibleIO.h"

//extern Serial console;

BibleIO::BibleIO(char* filename, void (*indexing_callback)(int, void*), void* indexing_context)
{
    name = 0;
    num_books = 0;
    books = 0;
    bookmarks = 0;
    text_filename = filename;
    this->indexing_callback = indexing_callback;
    this->indexing_context = indexing_context;
    
    fp = fopen(text_filename, "r");
    load_bookmarks();
    
    if (!load_index())
        build_index();
}

BibleIO::~BibleIO()
{   
    delete [] name;
    name = 0;
    
    delete [] books;
    books = 0;

    delete [] bookmarks;
    bookmarks = 0;

    fclose(fp);
}

void BibleIO::build_index()
{
    read_name();
    read_preface();
    read_books();
    load_index();
}

void BibleIO::read_name()
{
    name = read_line();
    
    //console.printf("name=%s\n", name);
    //lcd.printf("%s\n", name);
}

void BibleIO::read_preface()
{
    char* line = 0;
    long book_offset = ftell(fp);
    long line_offset = book_offset;

    do
    {
        if (line != 0 && *line != 0)
            book_offset = line_offset;
        delete [] line;
        line_offset = ftell(fp);
        line = read_line();
        //console.printf("%s\n", line);
    } while (strcmp(line, "CHAPTER 1") != 0);
    delete [] line;

    // read too far, reset file pointer    
    fseek(fp, book_offset, SEEK_SET);
}

void BibleIO::read_books()
{  
    char* path = index_path();
    mkdir(path, 0666);
    delete [] path;
    path = 0;
    
    char* fn = index_path("books");
    FILE* index = fopen(fn, "w");
    delete [] fn;
    fn = 0;
    
    long title_offset = 0;
    fwrite(&title_offset, sizeof(title_offset), 1, index);

    short book = 0;
    //lcd.cls();
    if (indexing_callback != 0)
        (*indexing_callback)(0, indexing_context);
    //lcd.printf("Indexing0%%");
    long offset = ftell(fp);
    fseek(fp, 0, SEEK_END);
    long filesize = ftell(fp);
    fseek(fp, offset, SEEK_SET);
    while(read_book(index, book))
    {
        offset = ftell(fp);
        //lcd.printf("\n\n%d%%", offset*100/filesize);
        if (indexing_callback != 0)
            (*indexing_callback)(offset*100/filesize, indexing_context);
        ++book;
    }
    //lcd.cls();
    //lcd.printf("Complete100%%");
    if (indexing_callback != 0)
        (*indexing_callback)(100, indexing_context);
        
    //printf("%hd books\n", book);
        
    fclose(index);
}

bool BibleIO::read_book(FILE* index, short book)
{
    long offset = ftell(fp);

    char *book_name = BibleIO::read_line();
    if (book_name == 0 || *(book_name) == 0 || strcmp(book_name, "THE END") == 0
        || (book_name[0] >= '0' && book_name[0] <= '9'))
    {
        delete [] book_name;
        return false;
    }

    fwrite(&offset, sizeof(offset), 1, index);
    
    format_book_name(book_name);
    //printf("book=%s\n", book_name);
    //lcd.cls();
    //lcd.printf("%s_\n", book_name);

    delete [] book_name;

    short chapter = 0;
    while (read_chapter(book, chapter))
        ++chapter;

    //console.printf("%hd chapters\n", chapter);

    long num_chapters = chapter;
    fwrite(&num_chapters, sizeof(num_chapters), 1, index);
    
    return true;
}

void BibleIO::format_book_name(char* &book_name)
{
    char* last_word = strrchr(book_name, ' ');
    if (last_word != 0)
    {
        int number = 0;
        if (strstr(book_name, "MOSES") == 0)
        {
            if (strstr(book_name, " FIRST ") != 0)
                number = 1;
            else if (strstr(book_name, " SECOND ") != 0)
                number = 2;
            else if (strstr(book_name, " THIRD ") != 0)
                number = 3;
        }
        if (strstr(book_name, "SONG") != 0 || strstr(book_name, "ACTS ") != 0) 
            last_word = book_name;
        if (strstr(book_name, "ECCLESIASTES") != 0
            || strstr(book_name, "LAMENTATIONS") != 0 
            || strstr(book_name, "ACTS OF") != 0 
            || strstr(book_name, "REVELATION") != 0)
        {
            last_word = book_name;
            if (strncmp(last_word, "THE ", 4) == 0)
                last_word += 4;
            char* next_word = strchr(last_word, ' ');
            if (next_word != 0)
                *next_word = 0;
        }
        if (strncmp(last_word, "THE ", 4) == 0)
            last_word += 4;
        if (*last_word == ' ')
            ++last_word;
        if (last_word[strlen(last_word)-1] < 'A' || last_word[strlen(last_word)-1] > 'Z')
            last_word[strlen(last_word)-1] = 0;
        char* new_name = new char[strlen(last_word)+((number==0)?1:3)];
        if (number == 0)
            strcpy(new_name, last_word);
        else
            sprintf(new_name, "%d %s", number, last_word);
        delete [] book_name;
        book_name = new_name;
    }
}

bool BibleIO::read_chapter(short book, short chapter)
{
    char* line = 0;
    long offset;
    do
    {
        delete [] line;
        offset = ftell(fp);
        line = BibleIO::read_line();
    } while (line != 0 && (*line == 0 || strstr(line, "CALLED,") != 0 || line[0]=='#'));

    if (line != 0 && strcmp(line, "THE END OF THE OLD TESTAMENT")==0)
    {
        do
        {
            delete [] line;
            offset = ftell(fp);
            line = BibleIO::read_line();
        } while (line != 0 && strstr(line, "MATTHEW")==0);
    }

    if (line == 0 || (strncmp(line, "CHAPTER ", 8) != 0 && strncmp(line, "PSALM ", 6) != 0)
        || (line[0] == 'P' && atoi(line+6) != chapter+1)
        || (line[0] == 'C' && atoi(line+8) != chapter+1))
    {
        delete [] line;
        fseek(fp, offset, SEEK_SET);
        return false;
    }

    delete [] line;

    char* path = index_path(book);
    mkdir(path, 0666);
    delete [] path;
    path = 0;

    char* fn = index_path(book, chapter);
    FILE* index = fopen(fn, "wb");
    //console.printf("index file pointer=%08lx\n", (long)index);
    delete [] fn;
    fn = 0;

    // chapter name
    fwrite(&offset, sizeof(offset), 1, index);
   
    //console.printf("Chapter %d\n", chapter+1);

    // support Psalms optional description of chapter
    long desc_offset = ftell(fp);
    char* chapter_desc = BibleIO::read_line();
    if (chapter_desc == 0 || (chapter_desc[0] >= '0' && chapter_desc[0] <= '9'))
    {
        delete [] chapter_desc;
        chapter_desc = 0;
        fseek(fp, desc_offset, SEEK_SET);
    }
    delete [] chapter_desc; // not currently used

    short verse = 0;
    while (read_verse(index, book, chapter, verse))
        ++verse;

    //console.printf("\n");
    //console.printf("%hd verses\n", verse);

    fclose(index);

    return true;
}

char* BibleIO::read_line()
{
    char* line;
    const int size = 4096;
    line = new char[size];
    fgets(line, size, fp);
    
    // remove CR/LF
    while (strlen(line)>0 && (line[strlen(line)-1] == 0x0a || line[strlen(line)-1] == 0x0d))
        line[strlen(line)-1] = 0;

    // resize line to actual size
    char* new_line = new char[strlen(line)+1];
    strcpy(new_line, line);
    delete [] line;
    line = 0;

    //console.printf("%s\n", new_line);
    
    return new_line;
}

bool BibleIO::load_index()
{
    char* fn = index_path("books");
    FILE* index = fopen(fn, "r");
    delete [] fn;
    fn = 0;
    
    if (index == 0)
        return false;
        
    fseek(index, 0, SEEK_END);
    long filesize = ftell(index);
    num_books = (filesize - sizeof(long)) / sizeof(book_index);
    delete [] books;
    books = (bible_index*)new char[filesize];
    fseek(index, 0, SEEK_SET);

    fread(books, filesize, 1, index);
    
    long save_offset = ftell(fp);
    fseek(fp, books->title_offset, SEEK_SET);
    delete [] name;
    name = read_line();
    fseek(fp, save_offset, SEEK_SET);
    
    fclose(index);
    return true;
}

short BibleIO::get_num_books()
{
    return num_books;   
}

short BibleIO::get_num_chapters(short book)
{
    return books->books[book].num_chapters;
}

short BibleIO::get_num_verses(short book, short chapter)
{
    char* fn = index_path(book, chapter);
    FILE* index = fopen(fn, "r");
    delete [] fn;
    fn = 0;
    
    fseek(index, 0, SEEK_END);
    long filesize = ftell(index);
    short num_verses = (filesize - sizeof(long)) / sizeof(chapter_index);
    fclose(index);
    return num_verses;
}

char* BibleIO::title()
{
    char *t = new char[strlen(name)+1];
    strcpy(t, name);
    return t;
}

char* BibleIO::title_book(short book)
{
    if (book < 0 || book > num_books)
        return 0;
        
    long offset = ftell(fp);
    long title_offset = books->books[book].title_offset;
    fseek(fp, title_offset, SEEK_SET);
    char* title = read_line();
    fseek(fp, offset, SEEK_SET);
    format_book_name(title);
    return title;
}

char* BibleIO::title_chapter(short book, short chapter)
{
    char* fn = index_path(book, chapter);
    FILE* index = fopen(fn, "r");
    delete [] fn;
    fn = 0;
    
    long title_offset;
    fread(&title_offset, sizeof(title_offset), 1, index);
    fclose(index);
    
    long offset = ftell(fp);
    fseek(fp, title_offset, SEEK_SET);
    char* title = read_line();
    fseek(fp, offset, SEEK_SET);
    
    return title;
}

char* BibleIO::text_chapter(short book, short chapter)
{
    if (book < 0 || book > num_books)
        return 0;

    char* fn = index_path(book, chapter);
    FILE* index = fopen(fn, "r");
    delete [] fn;
    fn = 0;
    
    if (index == 0)
        return 0;
        
    long title_offset;
    fread(&title_offset, sizeof(title_offset), 1, index);
    fseek(index, 0, SEEK_END);
    long filesize = ftell(index);
    short num_verses = (filesize - sizeof(long)) / sizeof(chapter_index);
    fseek(index, sizeof(long) + (num_verses-1)*sizeof(long), SEEK_SET);
    long verse_offset = -1;
    fread(&verse_offset, sizeof(verse_offset), 1, index);
    fclose(index);
    
    long offset = ftell(fp);
    fseek(fp, verse_offset, SEEK_SET);
    char* last_verse = read_line();
    delete [] last_verse;
    verse_offset = ftell(fp);
    fseek(fp, title_offset, SEEK_SET);
    int size = verse_offset-title_offset+1;
    char* text = new char[size];
    fread(text, size-1, 1, fp);
    text[size-1] = 0;
    fseek(fp, offset, SEEK_SET);
    
    return text;
}

char* BibleIO::text_verse(short book, short chapter, short verse)
{
    //console.printf("text_verse: book %hd, chapter %hd, verse %hd\n", book, chapter, verse);
    if (book > num_books)
        return 0;
        
    char* fn = index_path(book, chapter);
    FILE* index = fopen(fn, "r");
    delete [] fn;
    fn = 0;
    
    if (index == 0)
        return 0;
        
    fseek(index, 0, SEEK_END);
    long filesize = ftell(index);
    short num_verses = (filesize - sizeof(long)) / sizeof(chapter_index);
    if (verse >= num_verses || verse < 0)
    {
        fclose(index);
        return 0;
    }
    fseek(index, sizeof(long) + verse*sizeof(long), SEEK_SET);
    long verse_offset = -1;
    fread(&verse_offset, sizeof(verse_offset), 1, index);
    fclose(index);
    
    long offset = ftell(fp);
    fseek(fp, verse_offset, SEEK_SET);
    char* text = read_line();
    fseek(fp, offset, SEEK_SET);
    
    //console.printf("text: %s\n", text);    
    return text;
}

bool BibleIO::read_verse(FILE* index, short book, short chapter, short verse)
{
    long offset = ftell(fp);
    char* line = 0;
    
    do
    {
        delete [] line;
        line = BibleIO::read_line();
    } while (line != 0 && (*line == 0 || line[0] >= 'A' && line[0] <= 'Z' && line[strlen(line)-1] == '.'));
    
    if (line == 0 || *line == 0 || atoi(line) == 0)
    {
        delete [] line;
        fseek(fp, offset, SEEK_SET);
        return false;
    }
    
    short verse_num = atoi(line);
    delete [] line;

// !!! NOTE: this test fails at Philemon 1:2 because verses 1 & 2 are on the same line, for now ignore this test.  Workaround is to edit text file to avoid error.
//    if (verse+1 != verse_num)
//        return false;
 
    //console.printf("%d ", verse_num);
    
   fwrite(&offset, sizeof(offset), 1, index);

   return true;
}

bool BibleIO::load_bookmarks()
{
    delete [] bookmarks;
    bookmarks = 0;
    num_bookmarks = 0;

    char* fn = index_path("marks");
    FILE* index = fopen(fn, "r");
    delete [] fn;
    fn = 0;
    
    if (index == 0)
        return true; // okay it is not present yet, empty
    
    fseek(index, 0, SEEK_END);
    long filesize = ftell(index);
    num_bookmarks = filesize / sizeof(position);
    fseek(index, 0, SEEK_SET);
    bookmarks = new position[num_bookmarks];
    fread(bookmarks, sizeof(position), num_bookmarks, index);
    fclose(index);
    
    return true;
}

bool BibleIO::save_bookmarks()
{
    char* fn = index_path("marks");
    remove(fn);
    FILE* index = fopen(fn, "w");
    delete [] fn;
    fn = 0;
    
    if (index == 0)
        return false;
        
    fwrite(bookmarks, sizeof(position), num_bookmarks, index);
    fclose(index);

    return true;
}

bool BibleIO::bookmark_add(short book, short chapter, short verse)
{
    int i;
    for (i=0; i<num_bookmarks; ++i)
    {
        if (bookmarks[i].book == book && bookmarks[i].chapter == chapter && bookmarks[i].verse == verse)
        {
            return false; // already present
        }
    }
            
    // resize list
    position* new_bookmarks = new position[num_bookmarks+1];
    for (i=0; i<num_bookmarks; ++i)
        new_bookmarks[i] = bookmarks[i];
    new_bookmarks[i].book = book;
    new_bookmarks[i].chapter = chapter;
    new_bookmarks[i].verse = verse;
    new_bookmarks[i].rsvd = 0;
    delete [] bookmarks;
    bookmarks = new_bookmarks;
    ++num_bookmarks;
    
    return save_bookmarks();
}

bool BibleIO::bookmark_del(short book, short chapter, short verse)
{
    // search for bookmark
    int i;
    for (i=0; i<num_bookmarks; ++i)
        if (bookmarks[i].book == book && bookmarks[i].chapter == chapter && bookmarks[i].verse == verse)
            break;
    if (i>=num_bookmarks)
        return false; // not found
        
    // move previous entries down
    for (int j=i+1; j<num_bookmarks; ++j)
        bookmarks[j-1] = bookmarks[j];
        
    --num_bookmarks;
        
    // rewrite to filesystem
    return save_bookmarks();
}

bool BibleIO::bookmark_prev(short &book, short &chapter, short &verse)
{
    // search for bookmark
    int i;
    for (i=0; i<num_bookmarks; ++i)
        if (bookmarks[i].book == book && bookmarks[i].chapter == chapter && bookmarks[i].verse == verse)
            break;
    if (i==0)
        i=num_bookmarks; // wrap around
        
    // previous
    --i;

    if (i < 0 || i >= num_bookmarks)
        return false;
    
    book = bookmarks[i].book;
    chapter = bookmarks[i].chapter;
    verse = bookmarks[i].verse;
    
    return true;
}

bool BibleIO::bookmark_next(short &book, short &chapter, short &verse)
{
    // search for bookmark
    int i;
    for (i=0; i<num_bookmarks; ++i)
        if (bookmarks[i].book == book && bookmarks[i].chapter == chapter && bookmarks[i].verse == verse)
            break;
    // next
    if (++i >= num_bookmarks)
        i = 0;
    
    if (i < 0 || i >= num_bookmarks)
        return false;
    
    book = bookmarks[i].book;
    chapter = bookmarks[i].chapter;
    verse = bookmarks[i].verse;
    
    return true;
}

char* BibleIO::index_path()
{
    char* path = index_path("");
    path[strlen(path)-1] = 0; // remove trailing slash
    return path;
}

char* BibleIO::index_path(char* name)
{
    int base_len = strrchr(text_filename, '/') + 1 - text_filename;
    int index_len = base_len + strlen("index/") + strlen(name);
    char* index = new char[index_len+1];
    memcpy(index, text_filename, base_len);
    index[base_len] = 0;
    strcat(index, "index/");
    strcat(index, name);
    return index;
}

char* BibleIO::index_path(short book)
{
    char indexname[50];
    sprintf(indexname, "%hd", book);
    return index_path(indexname);
}

char* BibleIO::index_path(short book, short chapter)
{
    char indexname[50];
    sprintf(indexname, "%hd/%hd", book, chapter);
    return index_path(indexname);
}