/****************************************************************************** * * versificationmgr.cpp - implementation of class VersificationMgr used * for managing versification systems * * $Id$ * * Copyright 2008-2013 CrossWire Bible Society (http://www.crosswire.org) * CrossWire Bible Society * P. O. Box 2528 * Tempe, AZ 85280-2528 * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation version 2. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * */ #include #include #include #include #include // KJV internal versification system #include #include #include // null v11n system #include // Leningrad Codex (WLC) v11n system #include // Masoretic Text (MT) v11n system #include // KJV + Apocrypha v11n system #include // NRSV v11n system #include // NRSV + Apocrypha v11n system #include // Russian Synodal v11n system #include // Russian Synodal v11n system #include // Vulgate v11n system #include // German v11n system #include // Luther v11n system #include // Catholic v11n system (10 chapter Esther) #include // Catholic2 v11n system (16 chapter Esther) #include // General LXX v11n system (includes GNT, as used in Orthodox Bibles) #include // Orthodox v11n system as used in Orthodox Bibles #include // French v11n system as used by Segond Bibles and its derivatives #include // French v11n system #include // French v11n system based on John Darby's French translation using std::vector; using std::map; using std::distance; using std::lower_bound; SWORD_NAMESPACE_START VersificationMgr *VersificationMgr::getSystemVersificationMgr() { if (!systemVersificationMgr) { systemVersificationMgr = new VersificationMgr(); systemVersificationMgr->registerVersificationSystem("KJV", otbooks, ntbooks, vm); systemVersificationMgr->registerVersificationSystem("Leningrad", otbooks_leningrad, ntbooks_null, vm_leningrad); systemVersificationMgr->registerVersificationSystem("MT", otbooks_mt, ntbooks_null, vm_mt); systemVersificationMgr->registerVersificationSystem("KJVA", otbooks_kjva, ntbooks, vm_kjva); systemVersificationMgr->registerVersificationSystem("NRSV", otbooks, ntbooks, vm_nrsv, mappings_nrsv); systemVersificationMgr->registerVersificationSystem("NRSVA", otbooks_nrsva, ntbooks, vm_nrsva); systemVersificationMgr->registerVersificationSystem("Synodal", otbooks_synodal, ntbooks_synodal, vm_synodal, mappings_synodal); systemVersificationMgr->registerVersificationSystem("SynodalProt", otbooks_synodalProt, ntbooks_synodal, vm_synodalProt); systemVersificationMgr->registerVersificationSystem("Vulg", otbooks_vulg, ntbooks_vulg, vm_vulg, mappings_vulg); systemVersificationMgr->registerVersificationSystem("German", otbooks_german, ntbooks, vm_german); systemVersificationMgr->registerVersificationSystem("Luther", otbooks_luther, ntbooks_luther, vm_luther); systemVersificationMgr->registerVersificationSystem("Catholic", otbooks_catholic, ntbooks, vm_catholic); systemVersificationMgr->registerVersificationSystem("Catholic2", otbooks_catholic2, ntbooks, vm_catholic2); systemVersificationMgr->registerVersificationSystem("LXX", otbooks_lxx, ntbooks, vm_lxx); systemVersificationMgr->registerVersificationSystem("Orthodox", otbooks_orthodox, ntbooks, vm_orthodox); systemVersificationMgr->registerVersificationSystem("Calvin", otbooks, ntbooks, vm_calvin, mappings_calvin); systemVersificationMgr->registerVersificationSystem("DarbyFr", otbooks, ntbooks, vm_darbyfr, mappings_darbyfr); systemVersificationMgr->registerVersificationSystem("Segond", otbooks, ntbooks, vm_segond, mappings_segond); } return systemVersificationMgr; } class VersificationMgr::System::Private { public: /** Array[chapmax] of maximum verses in chapters */ vector books; map osisLookup; /** General mapping rule is that first verse of every chapter corresponds first verse of another chapter in default intermediate canon(kjva), so mapping data contains expections. Intermediate canon could not contain corresponding data. Each element in @variable mappings contains of all rules that are related to particular book. @typedef mappingRule is a pointer on uchar[7]: 1 value - book id 1-based, ot+nt, 2-4 map to, 5-7 map from (chap,verse from, verse to if greater then "verse from"). Size of rule would be 8 if there is inter-book mapping and target book is abscent in reference system, in this case @variable mappingsExtraBooks data is used and id is rule book id minus book count. */ typedef vector mappingRule; vector mappings; vector mappingsExtraBooks; Private() { } Private(const VersificationMgr::System::Private &other) { books = other.books; osisLookup = other.osisLookup; } VersificationMgr::System::Private &operator =(const VersificationMgr::System::Private &other) { books = other.books; osisLookup = other.osisLookup; return *this; } }; class VersificationMgr::Book::Private { friend struct BookOffsetLess; public: /** Array[chapmax] of maximum verses in chapters */ vector verseMax; vector offsetPrecomputed; Private() { verseMax.clear(); } Private(const VersificationMgr::Book::Private &other) { verseMax.clear(); verseMax = other.verseMax; offsetPrecomputed = other.offsetPrecomputed; } VersificationMgr::Book::Private &operator =(const VersificationMgr::Book::Private &other) { verseMax.clear(); int s = (int)other.verseMax.size(); if (s) verseMax = other.verseMax; offsetPrecomputed = other.offsetPrecomputed; return *this; } }; struct BookOffsetLess { bool operator() (const VersificationMgr::Book &o1, const VersificationMgr::Book &o2) const { return o1.p->offsetPrecomputed[0] < o2.p->offsetPrecomputed[0]; } bool operator() (const long &o1, const VersificationMgr::Book &o2) const { return o1 < o2.p->offsetPrecomputed[0]; } bool operator() (const VersificationMgr::Book &o1, const long &o2) const { return o1.p->offsetPrecomputed[0] < o2; } bool operator() (const long &o1, const long &o2) const { return o1 < o2; } }; void VersificationMgr::Book::init() { p = new Private(); } void VersificationMgr::System::init() { p = new Private(); BMAX[0] = 0; BMAX[1] = 0; ntStartOffset = 0; } VersificationMgr::System::System(const System &other) { init(); name = other.name; BMAX[0] = other.BMAX[0]; BMAX[1] = other.BMAX[1]; (*p) = *(other.p); ntStartOffset = other.ntStartOffset; } VersificationMgr::System &VersificationMgr::System::operator =(const System &other) { name = other.name; BMAX[0] = other.BMAX[0]; BMAX[1] = other.BMAX[1]; (*p) = *(other.p); ntStartOffset = other.ntStartOffset; return *this; } VersificationMgr::System::~System() { delete p; } const VersificationMgr::Book *VersificationMgr::System::getBook(int number) const { return (number < (signed int)p->books.size()) ? &(p->books[number]) : 0; } int VersificationMgr::System::getBookNumberByOSISName(const char *bookName) const { map::const_iterator it = p->osisLookup.find(bookName); return (it != p->osisLookup.end()) ? it->second : -1; } void VersificationMgr::System::loadFromSBook(const sbook *ot, const sbook *nt, int *chMax, const unsigned char *mappings) { int chap = 0; int book = 0; long offset = 0; // module heading offset++; // testament heading while (ot->chapmax) { p->books.push_back(Book(ot->name, ot->osis, ot->prefAbbrev, ot->chapmax)); offset++; // book heading Book &b = p->books[p->books.size()-1]; p->osisLookup[b.getOSISName()] = (int)p->books.size(); for (int i = 0; i < ot->chapmax; i++) { b.p->verseMax.push_back(chMax[chap]); offset++; // chapter heading b.p->offsetPrecomputed.push_back(offset); offset += chMax[chap++]; } ot++; book++; } BMAX[0] = book; book = 0; ntStartOffset = offset; offset++; // testament heading while (nt->chapmax) { p->books.push_back(Book(nt->name, nt->osis, nt->prefAbbrev, nt->chapmax)); offset++; // book heading Book &b = p->books[p->books.size()-1]; p->osisLookup[b.getOSISName()] = (int)p->books.size(); for (int i = 0; i < nt->chapmax; i++) { b.p->verseMax.push_back(chMax[chap]); offset++; // chapter heading b.p->offsetPrecomputed.push_back(offset); offset += chMax[chap++]; } nt++; book++; } BMAX[1] = book; // TODO: build offset speed array // parse mappings if (mappings != NULL) { const unsigned char *m=mappings; for (; *m != 0; m += strlen((const char*)m)+1) { p->mappingsExtraBooks.push_back((const char*)m); } p->mappings.resize(p->books.size()+p->mappingsExtraBooks.size()); for (++m; *m != 0; m += 7) { p->mappings[m[0]-1].push_back(m); if (*m > p->books.size()) { p->mappings[m[7]-1].push_back(m); m += 1; } } } } VersificationMgr::Book::Book(const Book &other) { longName = other.longName; osisName = other.osisName; prefAbbrev = other.prefAbbrev; chapMax = other.chapMax; init(); (*p) = *(other.p); } VersificationMgr::Book& VersificationMgr::Book::operator =(const Book &other) { longName = other.longName; osisName = other.osisName; prefAbbrev = other.prefAbbrev; chapMax = other.chapMax; init(); (*p) = *(other.p); return *this; } VersificationMgr::Book::~Book() { delete p; } int VersificationMgr::Book::getVerseMax(int chapter) const { chapter--; return (p && (chapter < (signed int)p->verseMax.size()) && (chapter > -1)) ? p->verseMax[chapter] : -1; } int VersificationMgr::System::getBookCount() const { return (int)(p ? p->books.size() : 0); } long VersificationMgr::System::getOffsetFromVerse(int book, int chapter, int verse) const { long offset = -1; chapter--; const Book *b = getBook(book); if (!b) return -1; // assert we have a valid book if ((chapter > -1) && (chapter >= (signed int)b->p->offsetPrecomputed.size())) return -1; // assert we have a valid chapter offset = b->p->offsetPrecomputed[(chapter > -1)?chapter:0]; if (chapter < 0) offset--; /* old code * offset = offsets[testament-1][0][book]; offset = offsets[testament-1][1][(int)offset + chapter]; if (!(offset|verse)) // if we have a testament but nothing else. offset = 1; */ return (offset + verse); } char VersificationMgr::System::getVerseFromOffset(long offset, int *book, int *chapter, int *verse) const { if (offset < 1) { // just handle the module heading corner case up front (and error case) (*book) = -1; (*chapter) = 0; (*verse) = 0; return offset; // < 0 = error } // binary search for book vector::iterator b = lower_bound(p->books.begin(), p->books.end(), offset, BookOffsetLess()); if (b == p->books.end()) b--; (*book) = distance(p->books.begin(), b)+1; if (offset < (*(b->p->offsetPrecomputed.begin()))-((((!(*book)) || (*book)==BMAX[0]+1))?2:1)) { // -1 for chapter headings (*book)--; if (b != p->books.begin()) { b--; } } vector::iterator c = lower_bound(b->p->offsetPrecomputed.begin(), b->p->offsetPrecomputed.end(), offset); // if we're a book heading, we are lessthan chapter precomputes, but greater book. This catches corner case. if (c == b->p->offsetPrecomputed.end()) { c--; } if ((offset < *c) && (c == b->p->offsetPrecomputed.begin())) { (*chapter) = (offset - *c)+1; // should be 0 or -1 (for testament heading) (*verse) = 0; } else { if (offset < *c) c--; (*chapter) = distance(b->p->offsetPrecomputed.begin(), c)+1; (*verse) = (offset - *c); } return ((*chapter > 0) && (*verse > b->getVerseMax(*chapter))) ? KEYERR_OUTOFBOUNDS : 0; } /*************************************************** * VersificationMgr */ class VersificationMgr::Private { public: Private() { } Private(const VersificationMgr::Private &other) { systems = other.systems; } VersificationMgr::Private &operator =(const VersificationMgr::Private &other) { systems = other.systems; return *this; } map systems; }; // ---------------- statics ----------------- VersificationMgr *VersificationMgr::systemVersificationMgr = 0; class __staticsystemVersificationMgr { public: __staticsystemVersificationMgr() { } ~__staticsystemVersificationMgr() { delete VersificationMgr::systemVersificationMgr; } } _staticsystemVersificationMgr; void VersificationMgr::init() { p = new Private(); } VersificationMgr::~VersificationMgr() { delete p; } void VersificationMgr::setSystemVersificationMgr(VersificationMgr *newVersificationMgr) { if (systemVersificationMgr) delete systemVersificationMgr; systemVersificationMgr = newVersificationMgr; } const VersificationMgr::System *VersificationMgr::getVersificationSystem(const char *name) const { map::const_iterator it = p->systems.find(name); return (it != p->systems.end()) ? &(it->second) : 0; } void VersificationMgr::registerVersificationSystem(const char *name, const sbook *ot, const sbook *nt, int *chMax, const unsigned char *mappings) { p->systems[name] = name; System &s = p->systems[name]; s.loadFromSBook(ot, nt, chMax, mappings); } void VersificationMgr::registerVersificationSystem(const char *name, const TreeKey *tk) { } const StringList VersificationMgr::getVersificationSystems() const { StringList retVal; for (map::const_iterator it = p->systems.begin(); it != p->systems.end(); it++) { retVal.push_back(it->first); } return retVal; } void VersificationMgr::System::translateVerse(const System *dstSys, const char **book, int *chapter, int *verse, int *verse_end) const { //dbg_mapping SWLOGD("translate verse from %s to %s: %s.%i.%i-%i\n",getName(), dstSys->getName(), *book, *chapter, *verse, *verse_end); if (!strcmp(getName(),"KJVA") || !strcmp(getName(),"KJV")) { if (!strcmp(dstSys->getName(),"KJVA") || !strcmp(dstSys->getName(),"KJV")) return; // reversed mapping //dbg_mapping SWLOGD("Perform reversed mapping.\n"); int b = dstSys->getBookNumberByOSISName(*book)-1; //dbg_mapping SWLOGD("\tgetBookNumberByOSISName %i %s.\n", b, *book); if (b < 0) { //dbg_mapping SWLOGD("\tmappingsExtraBooks.size() %i.\n", dstSys->p->mappingsExtraBooks.size()); for (int i=0; i<(int)dstSys->p->mappingsExtraBooks.size(); ++i) { //dbg_mapping SWLOGD("\t%s %s.\n", *book, dstSys->p->mappingsExtraBooks[i]); if (!strcmp(*book, dstSys->p->mappingsExtraBooks[i])) { b = (int)p->books.size()+i-2; break; } } } //dbg_mapping SWLOGD("\tb %i.\n", b); if (b >= (int)dstSys->p->mappings.size() || b < 0) { //dbg_mapping SWLOGD("no modification"); return; } const unsigned char *a = NULL; // reversed mapping should use forward search for item for (unsigned int i=0; ip->mappings[b].size(); ++i) { const unsigned char *m = dstSys->p->mappings[b][i]; if (m[0] != b+1) continue; // filter inter-book rules if (m[4] == *chapter && m[5] <= *verse) { //dbg_mapping SWLOGD("found mapping %i %i %i %i %i %i\n",m[1],m[2],m[3],m[4],m[5],m[6]); if (m[5] == *verse || (m[6] >= *verse && m[5] <= *verse)) { // inside of any mapping range *chapter = m[1]; *verse = m[2]; *verse_end = m[3]; if (*m >= dstSys->p->books.size()) { SWLog::getSystemLog()->logWarning("map to extra books, possible bug source\n"); *book = dstSys->getBook(m[7]-1)->getOSISName(); } return; } // destination mapping can have duplicate items, use the last (by using <=) if (a == NULL || (a[5]>a[6]?a[5]:a[6]) <= (m[5]>m[6]?m[5]:m[6])) a = m; } } if (a != NULL) { //dbg_mapping SWLOGD("set appropriate: %i %i %i %i %i %i\n",a[1],a[2],a[3],a[4],a[5],a[6]); (*chapter) = a[1]; // shift verse const int d = (a[3]>a[2]?a[3]:a[2])-(a[6]>a[5]?a[6]:a[5]); if (*verse < *verse_end) *verse_end += d; else *verse_end = (*verse) + d; *verse += d; if (*a > dstSys->p->books.size()) { //dbg_mapping SWLOGD("appropriate: %i %i %i %i %i %i %i %i\n",a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7]); //dbg_mapping SWLOGD("book: %s\n", dstSys->getBook(a[7]-1)->getOSISName()); *book = dstSys->getBook(a[7]-1)->getOSISName(); } return; } //dbg_mapping SWLOGD("There is no mapping.\n"); } else if (strcmp(dstSys->getName(),"KJVA") && strcmp(dstSys->getName(),"KJV")) { const System *kjva = getSystemVersificationMgr()->getVersificationSystem("KJVA"); const int src_verse = *verse; translateVerse(kjva, book, chapter, verse, verse_end); int interm_verse = *verse, interm_range = *verse_end, interm_chapter = *chapter; const char *interm_book = *book; kjva->translateVerse(dstSys, book, chapter, verse, verse_end); // contraction->expansion fix if (verse < verse_end && !(interm_verse < interm_range)) { kjva->translateVerse(this, &interm_book, &interm_chapter, &interm_verse, &interm_range); if (interm_verse < interm_range) { *verse += src_verse - interm_verse; if (*verse > *verse_end) *verse = *verse_end; else *verse_end = *verse; } } } else { //dbg_mapping SWLOGD("Perform forward mapping.\n"); const int b = getBookNumberByOSISName(*book)-1; if (b >= (int)p->mappings.size()) return; // forward mapping should use reversed search for item for (int i = (int)p->mappings[b].size()-1; i>=0; --i) { const unsigned char *m = p->mappings[b][i]; if (m[1] < *chapter) { SWLog::getSystemLog()->logWarning("There is no mapping for this chapter.\n"); return; } if (m[1] == *chapter && m[2] <= *verse) { //dbg_mapping SWLOGD("found mapping %i %i %i %i %i %i\n",m[1],m[2],m[3],m[4],m[5],m[6]); if (m[2] == *verse || (m[3] >= *verse && m[2] <= *verse)) { *chapter = m[4]; *verse = m[5]; *verse_end = m[6]; } else { *chapter = m[4]; // shift verse const int d = (m[6]>m[5]?m[6]:m[5])-(m[3]>m[2]?m[3]:m[2]); if (*verse < *verse_end) *verse_end += d; else *verse_end = (*verse) + d; *verse += d; } if (*m > p->books.size()) *book = p->mappingsExtraBooks[m[0]-p->books.size()-1]; return; } } //dbg_mapping SWLOGD("No mapping.\n"); } } SWORD_NAMESPACE_END