This is an overview of JSword that explains how the Book and Bible interfaces are arranged and explains some of the design decisions.
The most important thing any Bible program does is to access Bible data, and in more general terms, book data. There may be many different sources of data - Bibles stored in different formats, or even on remote systems, dictionaries, lexicons and so on. It would be good (where appropriate) to be able to treat them all similarly without needing to reinvent the wheel too often. We should be able to use inheritance to specialize where needed. So we start by creating an interface that is common to all 'Books' but that allows us to do as much as possible. The first goal is to read data and to be able to direct where to read from with some sort of pointer system. So we start with an interface that looks like this:
interface Book { BookData getData(Key ptr); }
Before we look at what BookData and Key looks like, a Book will need to be
able to do 2 other things:
Firstly tell us about itself: what it is called, where it comes from and so
on - BookMetaData about the Book itself. Secondly help us to find stuff by searching.
interface Book { BookData getData(Key ptr); String getRawData(Key ptr); BookMetaData getBookMetaData(); Key find(SearchRequest request); }
JSword has split searching away from implmenting the core Book interface so there is a Searcher interface that allows a search engine to index a Book and provide search results.
This interface can be implemented several times, once by something that reads Sword format data, once by something that reads JSword format data, and so on. The users, and indeed the developers of the front ends do not want to know about the various different implementors and what implementations need to be looked at. So we can use a couple of classes to fix this. If you like GoF patterns, an AbstractFactory:
interface BookList { List getBooks(); List getBooks(BookFilter filter); void addBooksListener(BooksListener li); void removeBooksListener(BooksListener li); }
Both getBooks() methods allow you to iterate over the Books known to the whole
system (Books), and to Filter out the uninteresting Books. There is a BookFilters
class that contains a set of default implentations.
A Book contains BookMetaData, which provides information about a Book:
interface BookMetaData extends Comparable { // Get various, different ways that this Book is known. String getName(); String getInitials(); String getOsisID(); // Get general info about any Book BookCategory getBookCategory(); String getLanguage(); boolean isLeftToRight(); boolean isSupported(); boolean isQuestionable(); // get other information about a book boolean hasFeature(FeatureType feature); Map getProperties(); String getProperty(String key); ... Other methods ... }
Book largely replicates this interface and it is expected that methods of the same name are delegated to the Book's BookMetaData. The reason for this delegation is that from a developer's perspective, these are integral characteristics of a Book and in most cases there is no reason to care that they are meta data. However, each Sword module has a conf file which contains this information and this is taken as the Book's meta data.
interface Book { BookData getData(Key ptr); String getRawData(Key key); BookMetaData getBookMetaData(); Key find(SearchRequest request); // Get various, different ways that this Book is known. String getName() String getInitials() String getOsisID() // Get general info about any Book BookCategory getBookCategory(); String getLanguage(); boolean isLeftToRight(); boolean isSupported(); boolean isQuestionable(); // get other information about a book boolean hasFeature(FeatureType feature); Map getProperties(); String getProperty(String key); ... Other methods ... }
Before we move on to what BookData and Key look like, I have intentionally ignored 2 issues: Encrypted works - some works will need to be encrypted - however the finding of keys or deobfustication will be done within the Driver so we don't need to worry about it too much. Configuration - some Books will need configuring before they will work, maybe with encryption keys, maybe with directories under which to find information. Each BookDriver will need to take care of configuring the Books that it creates we don't attempt to do anything more fancy even though there are parts of JSword that have implemented a generic configuration system.
BookData and Key are related. BookData describes the actual Book text (for example "In the beginning God created ...") and Key describes where that text comes from (for example "Gen 1:1")
BookData first. We do not want to force users of this code to use it in any
specific way, so BookData should describe the text in as much detail as possible
without forcing how that text is used. The final display could be a PDA, a
web browser, a matching verse list or a full-blow GUI display. This to me rules
out RTF, HTML and plain text, as they are all either display specific (RTF/HTML)
or low detail (text), and makes me think that XML along with some standard
converters to turn XML into RTF/HTML/PDF/text/blah is the best choice.
This has the added benefit that it allows us to specify not just what output
format is required, but also how that transformation is done, the fonts and
layout details of the produced RTF/HTML are all very configurable.
It also turn out to be very easy in Java simply by using the XSL libraries
produced by Sun and Apache. XSL libraries are freely available in all good
languages.
Key is used to request BookData from a Book and are also used as a reply from
a search - so we have Keys that tell us where the word "aaron" exists in a
particular Book. For the case of a Bible a Key could look like this: "Gen 1:1,
Isa 45:2, Rev 20:4", for the case of a dictionary a pointer would be like this: "aaron",
or for a BookDriver that contained sermons: "page 153, para 4-5".
There is no requirement on Key for them to apply only to single results, or
even for the results to be contiguous (it would be of little use in search
results if this was to be the case).
For the Bible case a Key is a collection of verses, and JSword has a set of classes called Verse, VerseRange and Passage which are a fundamental building block in this. The Passage package has classes to do all sorts of useful manipulations to lists of verses.