Dave Van Wagner / BibleIO

Dependents:   eBible

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers BibleIO.cpp Source File

BibleIO.cpp

00001 /* Bible I/O Class Implementation
00002  *
00003  * Copyright (c) 2011 David R. Van Wagner davervw@yahoo.com
00004  *
00005  * Permission is hereby granted, free of charge, to any person obtaining a copy
00006  * of this software and associated documentation files (the "Software"), to deal
00007  * in the Software without restriction, including without limitation the rights
00008  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00009  * copies of the Software, and to permit persons to whom the Software is
00010  * furnished to do so, subject to the following conditions:
00011  *
00012  * The above copyright notice and this permission notice shall be included in
00013  * all copies or substantial portions of the Software.
00014  *
00015  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00016  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00017  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00018  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00019  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00020  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
00021  * THE SOFTWARE.
00022  */
00023 
00024 /*
00025  * This class assumes bible text file is present and in a specific format extracted from source such as:
00026  *      http://printkjv.ifbweb.com/AV_txt.zip
00027  */
00028 
00029 #include <mbed.h> // mkdir
00030 #include <stdio.h>
00031 #include <string.h>
00032 #include <stdlib.h>
00033 #include <locale>
00034 #include "BibleIO.h"
00035 
00036 //extern Serial console;
00037 
00038 BibleIO::BibleIO(char* filename, void (*indexing_callback)(int, void*), void* indexing_context)
00039 {
00040     name = 0;
00041     num_books = 0;
00042     books = 0;
00043     bookmarks = 0;
00044     text_filename = filename;
00045     this->indexing_callback = indexing_callback;
00046     this->indexing_context = indexing_context;
00047     
00048     fp = fopen(text_filename, "r");
00049     load_bookmarks();
00050     
00051     if (!load_index())
00052         build_index();
00053 }
00054 
00055 BibleIO::~BibleIO()
00056 {   
00057     delete [] name;
00058     name = 0;
00059     
00060     delete [] books;
00061     books = 0;
00062 
00063     delete [] bookmarks;
00064     bookmarks = 0;
00065 
00066     fclose(fp);
00067 }
00068 
00069 void BibleIO::build_index()
00070 {
00071     read_name();
00072     read_preface();
00073     read_books();
00074     load_index();
00075 }
00076 
00077 void BibleIO::read_name()
00078 {
00079     name = read_line();
00080     
00081     //console.printf("name=%s\n", name);
00082     //lcd.printf("%s\n", name);
00083 }
00084 
00085 void BibleIO::read_preface()
00086 {
00087     char* line = 0;
00088     long book_offset = ftell(fp);
00089     long line_offset = book_offset;
00090 
00091     do
00092     {
00093         if (line != 0 && *line != 0)
00094             book_offset = line_offset;
00095         delete [] line;
00096         line_offset = ftell(fp);
00097         line = read_line();
00098         //console.printf("%s\n", line);
00099     } while (strcmp(line, "CHAPTER 1") != 0);
00100     delete [] line;
00101 
00102     // read too far, reset file pointer    
00103     fseek(fp, book_offset, SEEK_SET);
00104 }
00105 
00106 void BibleIO::read_books()
00107 {  
00108     char* path = index_path();
00109     mkdir(path, 0666);
00110     delete [] path;
00111     path = 0;
00112     
00113     char* fn = index_path("books");
00114     FILE* index = fopen(fn, "w");
00115     delete [] fn;
00116     fn = 0;
00117     
00118     long title_offset = 0;
00119     fwrite(&title_offset, sizeof(title_offset), 1, index);
00120 
00121     short book = 0;
00122     //lcd.cls();
00123     if (indexing_callback != 0)
00124         (*indexing_callback)(0, indexing_context);
00125     //lcd.printf("Indexing0%%");
00126     long offset = ftell(fp);
00127     fseek(fp, 0, SEEK_END);
00128     long filesize = ftell(fp);
00129     fseek(fp, offset, SEEK_SET);
00130     while(read_book(index, book))
00131     {
00132         offset = ftell(fp);
00133         //lcd.printf("\n\n%d%%", offset*100/filesize);
00134         if (indexing_callback != 0)
00135             (*indexing_callback)(offset*100/filesize, indexing_context);
00136         ++book;
00137     }
00138     //lcd.cls();
00139     //lcd.printf("Complete100%%");
00140     if (indexing_callback != 0)
00141         (*indexing_callback)(100, indexing_context);
00142         
00143     //printf("%hd books\n", book);
00144         
00145     fclose(index);
00146 }
00147 
00148 bool BibleIO::read_book(FILE* index, short book)
00149 {
00150     long offset = ftell(fp);
00151 
00152     char *book_name = BibleIO::read_line();
00153     if (book_name == 0 || *(book_name) == 0 || strcmp(book_name, "THE END") == 0
00154         || (book_name[0] >= '0' && book_name[0] <= '9'))
00155     {
00156         delete [] book_name;
00157         return false;
00158     }
00159 
00160     fwrite(&offset, sizeof(offset), 1, index);
00161     
00162     format_book_name(book_name);
00163     //printf("book=%s\n", book_name);
00164     //lcd.cls();
00165     //lcd.printf("%s_\n", book_name);
00166 
00167     delete [] book_name;
00168 
00169     short chapter = 0;
00170     while (read_chapter(book, chapter))
00171         ++chapter;
00172 
00173     //console.printf("%hd chapters\n", chapter);
00174 
00175     long num_chapters = chapter;
00176     fwrite(&num_chapters, sizeof(num_chapters), 1, index);
00177     
00178     return true;
00179 }
00180 
00181 void BibleIO::format_book_name(char* &book_name)
00182 {
00183     char* last_word = strrchr(book_name, ' ');
00184     if (last_word != 0)
00185     {
00186         int number = 0;
00187         if (strstr(book_name, "MOSES") == 0)
00188         {
00189             if (strstr(book_name, " FIRST ") != 0)
00190                 number = 1;
00191             else if (strstr(book_name, " SECOND ") != 0)
00192                 number = 2;
00193             else if (strstr(book_name, " THIRD ") != 0)
00194                 number = 3;
00195         }
00196         if (strstr(book_name, "SONG") != 0 || strstr(book_name, "ACTS ") != 0) 
00197             last_word = book_name;
00198         if (strstr(book_name, "ECCLESIASTES") != 0
00199             || strstr(book_name, "LAMENTATIONS") != 0 
00200             || strstr(book_name, "ACTS OF") != 0 
00201             || strstr(book_name, "REVELATION") != 0)
00202         {
00203             last_word = book_name;
00204             if (strncmp(last_word, "THE ", 4) == 0)
00205                 last_word += 4;
00206             char* next_word = strchr(last_word, ' ');
00207             if (next_word != 0)
00208                 *next_word = 0;
00209         }
00210         if (strncmp(last_word, "THE ", 4) == 0)
00211             last_word += 4;
00212         if (*last_word == ' ')
00213             ++last_word;
00214         if (last_word[strlen(last_word)-1] < 'A' || last_word[strlen(last_word)-1] > 'Z')
00215             last_word[strlen(last_word)-1] = 0;
00216         char* new_name = new char[strlen(last_word)+((number==0)?1:3)];
00217         if (number == 0)
00218             strcpy(new_name, last_word);
00219         else
00220             sprintf(new_name, "%d %s", number, last_word);
00221         delete [] book_name;
00222         book_name = new_name;
00223     }
00224 }
00225 
00226 bool BibleIO::read_chapter(short book, short chapter)
00227 {
00228     char* line = 0;
00229     long offset;
00230     do
00231     {
00232         delete [] line;
00233         offset = ftell(fp);
00234         line = BibleIO::read_line();
00235     } while (line != 0 && (*line == 0 || strstr(line, "CALLED,") != 0 || line[0]=='#'));
00236 
00237     if (line != 0 && strcmp(line, "THE END OF THE OLD TESTAMENT")==0)
00238     {
00239         do
00240         {
00241             delete [] line;
00242             offset = ftell(fp);
00243             line = BibleIO::read_line();
00244         } while (line != 0 && strstr(line, "MATTHEW")==0);
00245     }
00246 
00247     if (line == 0 || (strncmp(line, "CHAPTER ", 8) != 0 && strncmp(line, "PSALM ", 6) != 0)
00248         || (line[0] == 'P' && atoi(line+6) != chapter+1)
00249         || (line[0] == 'C' && atoi(line+8) != chapter+1))
00250     {
00251         delete [] line;
00252         fseek(fp, offset, SEEK_SET);
00253         return false;
00254     }
00255 
00256     delete [] line;
00257 
00258     char* path = index_path(book);
00259     mkdir(path, 0666);
00260     delete [] path;
00261     path = 0;
00262 
00263     char* fn = index_path(book, chapter);
00264     FILE* index = fopen(fn, "wb");
00265     //console.printf("index file pointer=%08lx\n", (long)index);
00266     delete [] fn;
00267     fn = 0;
00268 
00269     // chapter name
00270     fwrite(&offset, sizeof(offset), 1, index);
00271    
00272     //console.printf("Chapter %d\n", chapter+1);
00273 
00274     // support Psalms optional description of chapter
00275     long desc_offset = ftell(fp);
00276     char* chapter_desc = BibleIO::read_line();
00277     if (chapter_desc == 0 || (chapter_desc[0] >= '0' && chapter_desc[0] <= '9'))
00278     {
00279         delete [] chapter_desc;
00280         chapter_desc = 0;
00281         fseek(fp, desc_offset, SEEK_SET);
00282     }
00283     delete [] chapter_desc; // not currently used
00284 
00285     short verse = 0;
00286     while (read_verse(index, book, chapter, verse))
00287         ++verse;
00288 
00289     //console.printf("\n");
00290     //console.printf("%hd verses\n", verse);
00291 
00292     fclose(index);
00293 
00294     return true;
00295 }
00296 
00297 char* BibleIO::read_line()
00298 {
00299     char* line;
00300     const int size = 4096;
00301     line = new char[size];
00302     fgets(line, size, fp);
00303     
00304     // remove CR/LF
00305     while (strlen(line)>0 && (line[strlen(line)-1] == 0x0a || line[strlen(line)-1] == 0x0d))
00306         line[strlen(line)-1] = 0;
00307 
00308     // resize line to actual size
00309     char* new_line = new char[strlen(line)+1];
00310     strcpy(new_line, line);
00311     delete [] line;
00312     line = 0;
00313 
00314     //console.printf("%s\n", new_line);
00315     
00316     return new_line;
00317 }
00318 
00319 bool BibleIO::load_index()
00320 {
00321     char* fn = index_path("books");
00322     FILE* index = fopen(fn, "r");
00323     delete [] fn;
00324     fn = 0;
00325     
00326     if (index == 0)
00327         return false;
00328         
00329     fseek(index, 0, SEEK_END);
00330     long filesize = ftell(index);
00331     num_books = (filesize - sizeof(long)) / sizeof(book_index);
00332     delete [] books;
00333     books = (bible_index*)new char[filesize];
00334     fseek(index, 0, SEEK_SET);
00335 
00336     fread(books, filesize, 1, index);
00337     
00338     long save_offset = ftell(fp);
00339     fseek(fp, books->title_offset, SEEK_SET);
00340     delete [] name;
00341     name = read_line();
00342     fseek(fp, save_offset, SEEK_SET);
00343     
00344     fclose(index);
00345     return true;
00346 }
00347 
00348 short BibleIO::get_num_books()
00349 {
00350     return num_books;   
00351 }
00352 
00353 short BibleIO::get_num_chapters(short book)
00354 {
00355     return books->books[book].num_chapters;
00356 }
00357 
00358 short BibleIO::get_num_verses(short book, short chapter)
00359 {
00360     char* fn = index_path(book, chapter);
00361     FILE* index = fopen(fn, "r");
00362     delete [] fn;
00363     fn = 0;
00364     
00365     fseek(index, 0, SEEK_END);
00366     long filesize = ftell(index);
00367     short num_verses = (filesize - sizeof(long)) / sizeof(chapter_index);
00368     fclose(index);
00369     return num_verses;
00370 }
00371 
00372 char* BibleIO::title()
00373 {
00374     char *t = new char[strlen(name)+1];
00375     strcpy(t, name);
00376     return t;
00377 }
00378 
00379 char* BibleIO::title_book(short book)
00380 {
00381     if (book < 0 || book > num_books)
00382         return 0;
00383         
00384     long offset = ftell(fp);
00385     long title_offset = books->books[book].title_offset;
00386     fseek(fp, title_offset, SEEK_SET);
00387     char* title = read_line();
00388     fseek(fp, offset, SEEK_SET);
00389     format_book_name(title);
00390     return title;
00391 }
00392 
00393 char* BibleIO::title_chapter(short book, short chapter)
00394 {
00395     char* fn = index_path(book, chapter);
00396     FILE* index = fopen(fn, "r");
00397     delete [] fn;
00398     fn = 0;
00399     
00400     long title_offset;
00401     fread(&title_offset, sizeof(title_offset), 1, index);
00402     fclose(index);
00403     
00404     long offset = ftell(fp);
00405     fseek(fp, title_offset, SEEK_SET);
00406     char* title = read_line();
00407     fseek(fp, offset, SEEK_SET);
00408     
00409     return title;
00410 }
00411 
00412 char* BibleIO::text_chapter(short book, short chapter)
00413 {
00414     if (book < 0 || book > num_books)
00415         return 0;
00416 
00417     char* fn = index_path(book, chapter);
00418     FILE* index = fopen(fn, "r");
00419     delete [] fn;
00420     fn = 0;
00421     
00422     if (index == 0)
00423         return 0;
00424         
00425     long title_offset;
00426     fread(&title_offset, sizeof(title_offset), 1, index);
00427     fseek(index, 0, SEEK_END);
00428     long filesize = ftell(index);
00429     short num_verses = (filesize - sizeof(long)) / sizeof(chapter_index);
00430     fseek(index, sizeof(long) + (num_verses-1)*sizeof(long), SEEK_SET);
00431     long verse_offset = -1;
00432     fread(&verse_offset, sizeof(verse_offset), 1, index);
00433     fclose(index);
00434     
00435     long offset = ftell(fp);
00436     fseek(fp, verse_offset, SEEK_SET);
00437     char* last_verse = read_line();
00438     delete [] last_verse;
00439     verse_offset = ftell(fp);
00440     fseek(fp, title_offset, SEEK_SET);
00441     int size = verse_offset-title_offset+1;
00442     char* text = new char[size];
00443     fread(text, size-1, 1, fp);
00444     text[size-1] = 0;
00445     fseek(fp, offset, SEEK_SET);
00446     
00447     return text;
00448 }
00449 
00450 char* BibleIO::text_verse(short book, short chapter, short verse)
00451 {
00452     //console.printf("text_verse: book %hd, chapter %hd, verse %hd\n", book, chapter, verse);
00453     if (book > num_books)
00454         return 0;
00455         
00456     char* fn = index_path(book, chapter);
00457     FILE* index = fopen(fn, "r");
00458     delete [] fn;
00459     fn = 0;
00460     
00461     if (index == 0)
00462         return 0;
00463         
00464     fseek(index, 0, SEEK_END);
00465     long filesize = ftell(index);
00466     short num_verses = (filesize - sizeof(long)) / sizeof(chapter_index);
00467     if (verse >= num_verses || verse < 0)
00468     {
00469         fclose(index);
00470         return 0;
00471     }
00472     fseek(index, sizeof(long) + verse*sizeof(long), SEEK_SET);
00473     long verse_offset = -1;
00474     fread(&verse_offset, sizeof(verse_offset), 1, index);
00475     fclose(index);
00476     
00477     long offset = ftell(fp);
00478     fseek(fp, verse_offset, SEEK_SET);
00479     char* text = read_line();
00480     fseek(fp, offset, SEEK_SET);
00481     
00482     //console.printf("text: %s\n", text);    
00483     return text;
00484 }
00485 
00486 bool BibleIO::read_verse(FILE* index, short book, short chapter, short verse)
00487 {
00488     long offset = ftell(fp);
00489     char* line = 0;
00490     
00491     do
00492     {
00493         delete [] line;
00494         line = BibleIO::read_line();
00495     } while (line != 0 && (*line == 0 || line[0] >= 'A' && line[0] <= 'Z' && line[strlen(line)-1] == '.'));
00496     
00497     if (line == 0 || *line == 0 || atoi(line) == 0)
00498     {
00499         delete [] line;
00500         fseek(fp, offset, SEEK_SET);
00501         return false;
00502     }
00503     
00504     short verse_num = atoi(line);
00505     delete [] line;
00506 
00507 // !!! 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.
00508 //    if (verse+1 != verse_num)
00509 //        return false;
00510  
00511     //console.printf("%d ", verse_num);
00512     
00513    fwrite(&offset, sizeof(offset), 1, index);
00514 
00515    return true;
00516 }
00517 
00518 bool BibleIO::load_bookmarks()
00519 {
00520     delete [] bookmarks;
00521     bookmarks = 0;
00522     num_bookmarks = 0;
00523 
00524     char* fn = index_path("marks");
00525     FILE* index = fopen(fn, "r");
00526     delete [] fn;
00527     fn = 0;
00528     
00529     if (index == 0)
00530         return true; // okay it is not present yet, empty
00531     
00532     fseek(index, 0, SEEK_END);
00533     long filesize = ftell(index);
00534     num_bookmarks = filesize / sizeof(position);
00535     fseek(index, 0, SEEK_SET);
00536     bookmarks = new position[num_bookmarks];
00537     fread(bookmarks, sizeof(position), num_bookmarks, index);
00538     fclose(index);
00539     
00540     return true;
00541 }
00542 
00543 bool BibleIO::save_bookmarks()
00544 {
00545     char* fn = index_path("marks");
00546     remove(fn);
00547     FILE* index = fopen(fn, "w");
00548     delete [] fn;
00549     fn = 0;
00550     
00551     if (index == 0)
00552         return false;
00553         
00554     fwrite(bookmarks, sizeof(position), num_bookmarks, index);
00555     fclose(index);
00556 
00557     return true;
00558 }
00559 
00560 bool BibleIO::bookmark_add(short book, short chapter, short verse)
00561 {
00562     int i;
00563     for (i=0; i<num_bookmarks; ++i)
00564     {
00565         if (bookmarks[i].book == book && bookmarks[i].chapter == chapter && bookmarks[i].verse == verse)
00566         {
00567             return false; // already present
00568         }
00569     }
00570             
00571     // resize list
00572     position* new_bookmarks = new position[num_bookmarks+1];
00573     for (i=0; i<num_bookmarks; ++i)
00574         new_bookmarks[i] = bookmarks[i];
00575     new_bookmarks[i].book = book;
00576     new_bookmarks[i].chapter = chapter;
00577     new_bookmarks[i].verse = verse;
00578     new_bookmarks[i].rsvd = 0;
00579     delete [] bookmarks;
00580     bookmarks = new_bookmarks;
00581     ++num_bookmarks;
00582     
00583     return save_bookmarks();
00584 }
00585 
00586 bool BibleIO::bookmark_del(short book, short chapter, short verse)
00587 {
00588     // search for bookmark
00589     int i;
00590     for (i=0; i<num_bookmarks; ++i)
00591         if (bookmarks[i].book == book && bookmarks[i].chapter == chapter && bookmarks[i].verse == verse)
00592             break;
00593     if (i>=num_bookmarks)
00594         return false; // not found
00595         
00596     // move previous entries down
00597     for (int j=i+1; j<num_bookmarks; ++j)
00598         bookmarks[j-1] = bookmarks[j];
00599         
00600     --num_bookmarks;
00601         
00602     // rewrite to filesystem
00603     return save_bookmarks();
00604 }
00605 
00606 bool BibleIO::bookmark_prev(short &book, short &chapter, short &verse)
00607 {
00608     // search for bookmark
00609     int i;
00610     for (i=0; i<num_bookmarks; ++i)
00611         if (bookmarks[i].book == book && bookmarks[i].chapter == chapter && bookmarks[i].verse == verse)
00612             break;
00613     if (i==0)
00614         i=num_bookmarks; // wrap around
00615         
00616     // previous
00617     --i;
00618 
00619     if (i < 0 || i >= num_bookmarks)
00620         return false;
00621     
00622     book = bookmarks[i].book;
00623     chapter = bookmarks[i].chapter;
00624     verse = bookmarks[i].verse;
00625     
00626     return true;
00627 }
00628 
00629 bool BibleIO::bookmark_next(short &book, short &chapter, short &verse)
00630 {
00631     // search for bookmark
00632     int i;
00633     for (i=0; i<num_bookmarks; ++i)
00634         if (bookmarks[i].book == book && bookmarks[i].chapter == chapter && bookmarks[i].verse == verse)
00635             break;
00636     // next
00637     if (++i >= num_bookmarks)
00638         i = 0;
00639     
00640     if (i < 0 || i >= num_bookmarks)
00641         return false;
00642     
00643     book = bookmarks[i].book;
00644     chapter = bookmarks[i].chapter;
00645     verse = bookmarks[i].verse;
00646     
00647     return true;
00648 }
00649 
00650 char* BibleIO::index_path()
00651 {
00652     char* path = index_path("");
00653     path[strlen(path)-1] = 0; // remove trailing slash
00654     return path;
00655 }
00656 
00657 char* BibleIO::index_path(char* name)
00658 {
00659     int base_len = strrchr(text_filename, '/') + 1 - text_filename;
00660     int index_len = base_len + strlen("index/") + strlen(name);
00661     char* index = new char[index_len+1];
00662     memcpy(index, text_filename, base_len);
00663     index[base_len] = 0;
00664     strcat(index, "index/");
00665     strcat(index, name);
00666     return index;
00667 }
00668 
00669 char* BibleIO::index_path(short book)
00670 {
00671     char indexname[50];
00672     sprintf(indexname, "%hd", book);
00673     return index_path(indexname);
00674 }
00675 
00676 char* BibleIO::index_path(short book, short chapter)
00677 {
00678     char indexname[50];
00679     sprintf(indexname, "%hd/%hd", book, chapter);
00680     return index_path(indexname);
00681 }