package net.bible.android.control.navigation; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import net.bible.android.control.versification.Scripture; import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.basic.AbstractBook; import org.crosswire.jsword.book.basic.AbstractPassageBook; import org.crosswire.jsword.passage.Verse; import org.crosswire.jsword.versification.BibleBook; import org.crosswire.jsword.versification.Versification; import org.crosswire.jsword.versification.system.SystemSynodal; /** * Calculate which books are actually included in a Bible document. * Necessary for boks with v11n like Synodal but without dc books eg IBT. * Useful for partial documents eg NT or WIP. * * @author Martin Denham [mjdenham at gmail dot com] * @see gnu.lgpl.License for license details.<br> * The copyright to this program is held by it's author. */ public class DocumentBibleBooks { private List<BibleBook> bookList; private AbstractPassageBook document; private boolean onlyScripture = true; private Boolean isProbablyIBT = null; private static final int IBT_EMPTY_VERSE_STUB_MIN_LENGTH = "<chapter eID=\"gen4\" osisID=\"Gen.1\"/>".length(); private static final int IBT_EMPTY_VERSE_STUB_MAX_LENGTH = "<chapter eID=\"gen1146\" osisID=\"1Macc.1\"/>".length(); private static final int IBT_1_CHAPTER_BOOK_EMPTY_VERSE_STUB_MIN_LENGTH = "<chapter eID=\"gen955\" osisID=\"Obad.1\"/> <div eID=\"gen954\" osisID=\"Obad\" type=\"book\"/> <div eID=\"gen953\" type=\"x-Synodal-empty\"/>".length(); private static final int IBT_1_CHAPTER_BOOK_EMPTY_VERSE_STUB_MAX_LENGTH = "<chapter eID=\"gen1136\" osisID=\"EpJer.1\"/> <div eID=\"gen1135\" osisID=\"EpJer\" type=\"book\"/> <div eID=\"gen1134\" type=\"x-Synodal-non-canonical\"/>".length(); private static Scripture scripture = new Scripture(); @SuppressWarnings("unused") private static final String TAG = "DocumentBibleBooks"; public DocumentBibleBooks(AbstractPassageBook document) { this.document = document; calculateBibleBookList(); } /** * Iterate all books checking if document contains a verse from the book */ private void calculateBibleBookList() { List<BibleBook> bookList = new ArrayList<BibleBook>(); // iterate over all book possible in this document Versification documentVersification = document.getVersification(); Iterator<BibleBook> v11nBookIterator = documentVersification.getBookIterator(); while (v11nBookIterator.hasNext()) { BibleBook bibleBook = v11nBookIterator.next(); // test some random verses - normally ch1 v 1 is sufficient - but we don't want to miss any if (isVerseInBook(document, documentVersification, bibleBook, 1, 1) || isVerseInBook(document, documentVersification, bibleBook, 1, 2)) { bookList.add(bibleBook); onlyScripture &= scripture.isScripture(bibleBook); } } this.bookList = bookList; } public boolean contains(BibleBook book) { return bookList.contains(book); } public List<BibleBook> getBookList() { return Collections.unmodifiableList(bookList); } public boolean isOnlyScripture() { return onlyScripture; } public void setContainsOnlyScripture(boolean containsOnlyScripture) { this.onlyScripture = containsOnlyScripture; } private boolean isVerseInBook(Book document, Versification v11n, BibleBook bibleBook, int chapter, int verseNo ) { Verse verse = new Verse(v11n, bibleBook, chapter, verseNo); // no content for specified verse implies this verse clearly is not in this document if (!document.contains(verse)) { return false; } // IBT Synodal documents sometimes return stub data for missing verses in dc books e.g. <chapter eID="gen7" osisID="1Esd.1"/> if (isProbablyIBTDocument(document, v11n) && isProbablyEmptyVerseInDocument(document, verse)) { // it is just IBT dummy empty verse content return false; } return true; } /** IBT books are Synodal but are known to have mistakenly added empty verses for all dc books * Here we check to see if this document probably has that problem. */ private boolean isProbablyIBTDocument(Book document, Versification v11n) { if (isProbablyIBT==null) { isProbablyIBT = SystemSynodal.V11N_NAME.equals(v11n.getName()) && isProbablyEmptyVerseInDocument(document, new Verse(v11n, BibleBook.TOB, 1, 1)); } return isProbablyIBT; } /** * Some IBT books mistakenly had dummy empty verses which returned the following for verse 1,2,... lastVerse-1 * <chapter eID="gen7" osisID="1Esd.1"/> * <chapter eID="gen1010" osisID="Mal.3"/> */ private boolean isProbablyEmptyVerseInDocument(Book document, Verse verse) { int rawTextLength = ((AbstractBook)document).getBackend().getRawTextLength(verse); if (verse.getBook().isShortBook()) { return isProbablyShortBookEmptyVerseStub(rawTextLength); } else { return isProbablyEmptyVerseStub(rawTextLength); } } /** There is a standard type of tag padding in each empty verse that has a fairly predictable length */ private boolean isProbablyEmptyVerseStub(int rawTextLength) { return rawTextLength >= IBT_EMPTY_VERSE_STUB_MIN_LENGTH && rawTextLength <= IBT_EMPTY_VERSE_STUB_MAX_LENGTH; } /** 1 chapter books have a different type of empty verse stub that includes the end of chapter tag * <chapter eID="gen955" osisID="Obad.1"/> <div eID="gen954" osisID="Obad" type="book"/> <div eID="gen953" type="x-Synodal-empty"/> */ private boolean isProbablyShortBookEmptyVerseStub(int rawTextLength) { return rawTextLength >= IBT_1_CHAPTER_BOOK_EMPTY_VERSE_STUB_MIN_LENGTH && rawTextLength <= IBT_1_CHAPTER_BOOK_EMPTY_VERSE_STUB_MAX_LENGTH; } }