#include "libbible.h" #include #include #include #include #include using namespace sword; using namespace std; SWMgr library(new MarkupFilterMgr(FMT_XHTML)); vector getBooks(SWModule *target) { vector books; VerseKey *key = (VerseKey *) target->getKey(); for(char t = 1; t <= key->getTestamentMax(); t++) { key->setTestament(t); for(char b = 1; b <= key->getBookMax(); b++) { key->setBook(b); // Bug (whose fault??) in JPS; they CLAIM to have two testaments, // but they only have one, which causes repeats. if(std::find(books.begin(), books.end(), key->getBookName()) != books.end()) { continue; } // Another issue (maybe bug?) Some translations are NT only, // but still report OT books/chapters. if(string(target->renderText()).empty()) { continue; } books.push_back(key->getBookName()); } } return books; } map> libbible::getModules() { library.load(); map> mods; ModMap::iterator it; for (it = library.getModules().begin(); it != library.getModules().end(); it++) { string modName = (*it).second->getName(); SWModule *target = library.getModule(modName.c_str()); mods[modName] = getBooks(target); } return mods; } vector libbible::getPassages(string modName, string book) { vector passages; SWModule *target = library.getModule(modName.c_str()); if(target == nullptr) { // Module doesn't exist return passages; } target->setKey((book + " " + "1").c_str()); VerseKey *key = (VerseKey *) target->getKey(); int maxChapter = key->getChapterMax(); for(int chapter = 1; chapter <= maxChapter; chapter++) { string ref = book + ' ' + to_string(chapter); target->setKey(ref.c_str()); VerseKey *key = (VerseKey *) target->getKey(); libbible::passage pass; pass.modName = modName; pass.book = string(key->getBookName()); pass.bookShort = string(key->getBookAbbrev()); pass.chapterStart = chapter; pass.chapterEnd = chapter; pass.verseStart = 1; pass.verseEnd = key->getVerseMax(); passages.push_back(pass); } return passages; } libbible::text getEmptyText(VerseKey *key) { libbible::text t; t.chapter = key->getChapter(); t.verse = key->getVerse(); t.book = key->getBookName(); t.bookShort = key->getBookAbbrev(); return t; } libbible::passage libbible::getPassage(string modName, string reference) { libbible::passage pass; pass.modName = modName; SWModule *target = library.getModule(pass.modName.c_str()); if(target == nullptr || reference.empty()) { // Bad input return pass; } vector validBooks = getBooks(target); //printf("Hey, I'm inferring missing parts!\n"); // Let's use the target to help us target->setKey(reference.c_str()); VerseKey *key = (VerseKey *) target->getKey(); pass.book = string(key->getBookName()); // Hold on a moment, is this book even legal? if(find(validBooks.begin(), validBooks.end(), pass.book) == validBooks.end()) { key->setBookName(validBooks[0].c_str()); pass.book = string(key->getBookName()); } pass.bookShort = string(key->getBookAbbrev()); pass.chapterStart = key->getChapter(); pass.verseStart = key->getVerse(); //printf("Results so far: book: %s; chapterStart: %d; verseStart: %d\n", pass.book.c_str(), pass.chapterStart, pass.verseStart); // And now we just need chapterEnd and verseEnd. Yippee. string ref = string(reference); ref.erase(remove(ref.begin(), ref.end(), ' '), ref.end()); if(ref.find('-') == string::npos) { // There's no range! if(ref.find(':') == string::npos) { // It's a full chapter reference pass.chapterEnd = pass.chapterStart; pass.verseEnd = key->getVerseMax(); } else { // It's a single verse reference pass.chapterEnd = pass.chapterStart; pass.verseEnd = pass.verseStart; //printf("Hey, it's a single verse reference!\n"); } } else { if(ref.find(':') == string::npos) { // It's a multi-full-chapter reference pass.chapterEnd = stoi(ref.substr(ref.find_last_of('-')+1)); key->setChapter(pass.chapterEnd); pass.verseEnd = key->getVerseMax(); } else { // It falls in categories c:v-v or c:v-c:v (or, technically, c-c:v) string rangeEnd = ref.substr(ref.find_last_of('-')+1); if(rangeEnd.find(':') == string::npos) { // It's c:v-v pass.verseEnd = stoi(rangeEnd); pass.chapterEnd = pass.chapterStart; } else { // It's c:v-c:v (or c-c:v, but code is the same) pass.chapterEnd = stoi(rangeEnd.substr(0, rangeEnd.find(':'))); pass.verseEnd = stoi(rangeEnd.substr(rangeEnd.find(':')+1)); } } } return pass; } vector libbible::getText(libbible::passage pass) { vector texts; SWModule *target = library.getModule(pass.modName.c_str()); if(target == nullptr) { // Module doesn't exist return texts; } if(pass.book.empty()) { pass.book = pass.bookShort; } target->setKey((pass.book + " " + to_string(pass.chapterStart) + ":" + to_string(pass.verseStart)).c_str()); VerseKey *key = (VerseKey *) target->getKey(); bool endOfParagraph = false; for(; key->getChapter() < pass.chapterEnd || (key->getChapter() == pass.chapterEnd && key->getVerse() <= pass.verseEnd); (*key)++) { string text = string(target->renderText()); //printf("Processing text \"%s\"", text.c_str()); // Handle ¶ symbol if at beginning of line /*if(text.find("¶") == 0) { text.erase(0, 1); // Append \n to text in previous texts (if applicable) if(! texts.empty()) { texts.back().text += '\n'; } endOfParagraph = true; }*/ texts.push_back(getEmptyText(key)); /* Things to enforce: * 1. No whitespace at start of verse */ // Find and replace everything in "subs" map /*const map subs = {{"¶", "\n\t"}}; for(auto const &repl : subs) { string::size_type location; while((location = text.find(repl.first)) != string::npos) { text.replace(location, repl.first.size(), repl.second); } }*/ if(key->getVerse() == 1 || endOfParagraph) { if(find(texts.back().modifiers.begin(), texts.back().modifiers.end(), "paragraph") == texts.back().modifiers.end()) { texts.back().modifiers.push_back("paragraph"); } endOfParagraph = false; } // Variable to accumulate unterminated spans std::vector spans; bool spansChanged = false; bool hasAddedText = false; // Iterate over text for(auto i = text.begin(); i != text.end(); i++) { if(*i != '<') { if(spansChanged) { spansChanged = false; if(!texts.back().text.empty()) { texts.push_back(getEmptyText(key)); } for(string modifier : spans) { if(find(texts.back().modifiers.begin(), texts.back().modifiers.end(), modifier) == texts.back().modifiers.end()) { texts.back().modifiers.push_back(modifier); } } } if(! hasAddedText && (*i == ' ' || *i == '\t')) { continue; } if(*i == "¶"[0] && *(i+1) == "¶"[1]) { i++; if(hasAddedText) { texts.back().text += '\n'; } else { // Append \n to text in previous texts (if applicable) if(texts.size() > 1) { texts[texts.size()-2].text += '\n'; } texts.back().modifiers.push_back("paragraph"); continue; } } texts.back().text += *i; hasAddedText = true; } else { string span; for(; i != text.end(); i++) { span.push_back(*i); if(*i == '>') { // The end of the span will be "". if(span == "") { if(! spans.empty()) { spans.pop_back(); spansChanged = true; } } else { // The span will be formatted "" // We want just the NAME size_t start = span.find_first_of('"')+1; size_t end = span.find_last_of('"'); if(start > 0) { spans.push_back(span.substr(start, end-start)); spansChanged = true; } } break; } } } } endOfParagraph = (text[text.length()-1] == '\n'); } return texts; }