Bible text I/O library, for retrieving and navigating text in KJV Bible More details at: http://mbed.org/users/davervw/notebook/ebible-abstract/
Diff: BibleIO.cpp
- 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); +}