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

Revision:
0:1f3d069211cd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BibleIO.cpp	Sun Feb 27 18:49:23 2011 +0000
@@ -0,0 +1,681 @@
+/* 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);
+}