Compare commits

...

10 Commits
v1.0 ... main

Author SHA1 Message Date
Paul Mosier 0a8acd40cc Resolution of improper multibyte rendering. 2023-03-02 20:00:49 -05:00
Paul Mosier 8959f4ef97 Commit to log changes in progress for retooling away from wide character strings:
- Functions in free.cpp retooled away from wide chars.  Commented lines for removal still present.
- Locale change line moved in scriptura.cpp.  No further changes needed here.
- Partial changes made in pane.cpp - more to come here.
2023-02-23 15:01:21 -05:00
Paul Mosier 8bf642a8a9 Use swmgr's module type constants intead of magic strings. 2022-12-27 17:46:17 -05:00
Paul Mosier 90bb1d499c Bugfix for issue #4: crash with non-existent default module. 2022-12-27 15:45:30 -05:00
Paul Mosier 0e430dcd9e Makefile patch for package building. Code change to handle warnings. 2022-12-03 18:37:26 -05:00
Paul Mosier bdd140b94a Search & config file bugfixes. Doc updates. 2022-12-01 17:56:00 -05:00
Paul Mosier 7df22c80a3 Allow for multiple pages of panes. 2022-05-26 20:58:32 -04:00
Paul Mosier d57854add5 Makefile updates. Patch contributed courtesy of Troy Griffitts (scribe@crosswire.org) 2021-10-16 20:21:09 -04:00
Paul Mosier 874a084cda Fix segfault on passage retrieval post-search. 2021-09-25 23:28:54 -04:00
Paul Mosier 1d51da03bb Fixed make install. 2021-09-19 22:18:09 -04:00
11 changed files with 665 additions and 210 deletions

2
.gitignore vendored
View File

@ -32,3 +32,5 @@
*.out
*.app
push-checklist.org
scriptura

View File

@ -1,9 +1,9 @@
PREFIX = /usr/local
CC = clang
CCFLAGS = -Wall
PREFIX ?= /usr/local
CC = g++
CCFLAGS ?= -Wall $(shell pkg-config --cflags sword)
TARGET = scriptura
INCLUDES = -I/usr/include/sword -I/usr/include/ncursesw
LDFLAGS = -lmenuw -lformw -lncursesw -lsword -lstdc++
LDFLAGS = -lmenuw -lformw -lncursesw -lstdc++ $(shell pkg-config --libs sword)
SOURCE = free.cpp scabbard.cpp pane.cpp scriptura.cpp
@ -27,11 +27,11 @@ doc:
install:
cp -v scriptura $PREFIX/bin
install -m 0755 -D scriptura $(DESTDIR)$(PREFIX)/bin/scriptura
uninstall:
rm -v $PREFIX/bin
rm -v $(DESTDIR)$(PREFIX)/bin
clean:

View File

@ -5,8 +5,8 @@ This program was written to scratch a personal itch. I do a lot of daily comput
## Dependencies
* clang
* libsword and its development libraries (often available in most distributions' standard repos)
* a C++ compiler and standard libraries installed - the Makefile is currently set for g++ but you could switch it to clang++ if preferred
* libsword and its development libraries (also available in most distributions' standard repos)
* ncurses and its development libraries (any modern Linux version should work)
* doxygen (optional, if you're a documentation nerd)
@ -21,7 +21,7 @@ To install scriptura (into /usr/local by default):
>
> sudo make install
On first run, scriptura assumes that a King James module is intalled and available. Many modules from CrossWire are [available here](https://www.crosswire.org/ftpmirror/pub/sword/packages/rawzip/) and other frontends have their own repos. To install them from the command line:
On first run, scriptura assumes that the King James with Apocrypha module is intalled and available. Many modules from CrossWire are [available here](https://www.crosswire.org/ftpmirror/pub/sword/packages/rawzip/) and other frontends have their own repos. To install them from the command line:
> mkdir -p $HOME/.sword/
>
> cd $HOME/.sword/
@ -30,13 +30,14 @@ On first run, scriptura assumes that a King James module is intalled and availab
>
> unzip KJVA.zip
A configuration file will be created at $HOME/.config/scriptura.ini. You can see the sample config file in the codebase for available options.
A configuration file will be created at $HOME/.config/scriptura.ini. You can see the sample config file in the codebase for available options. Just about everything you can modify from inside the program, with the sole exception of the 'panels' entry under [layout]. This determines if you want the two panes to stack horizontal ("h") or vertical ("v") and must be changed manually.
When the software is running, the '?' key will give you the list of commands available. Open any module you have installed and you can go to or search any text.
## Known issues
Menus and forms will not render correctly if your terminal window is too narrow. Resize the terminal and this should work.
* Menus and forms will not render correctly if your terminal window is too narrow. Resize the terminal and this should work.
* Extended ASCII characters, typically in the Latin-1 set, will not render due to a quirk of how ncurses handles UTF-8.
## Feedback / Support / Gratuity
@ -48,7 +49,6 @@ Other means of contact [can be found here](https://keyoxide.org/hkp/paladin1%40s
This project is basically a one-man operation. If you'd like to show your support financially, you can contribute via:
- _Paypal - paladin1_ at _sdf.org_
- _BTC - bc1q9dfau346z38jth35gkaxacd3fljvfgw6cqcyyv_
## Disclaimer

View File

@ -35,7 +35,7 @@ char CLITEXT[] =
char HELPTEXT[] =
"Scriptura\n\n"
"Key bindings:\n"
"Main key bindings:\n"
" ? this help message\n"
" q quit\n\n"
" ESC close floating window\n"
@ -46,7 +46,11 @@ char HELPTEXT[] =
" arrow up/dn scroll up/down one line\n"
" page up/dn scroll up/down one page\n"
"\n"
"Formatting changes\n"
"Pages:\n"
" 0-4 go to the numbered page\n"
" p create new page\n"
" d delete page\n"
"Formatting changes:\n"
" (takes effect at next search/go to, and depends on loaded module)\n"
" f toggle footnotes\n"
" n toggle non-textual words\n"
@ -56,6 +60,9 @@ char HELPTEXT[] =
"\n(Press any key to return to the main screen)\n"
"\0";
int DEFSEARCH = 1;
int DEFKEY = 0;
void wrapup(int exitval, const char* str) {
endwin();
if (str != NULL) perror(str);
@ -68,18 +75,25 @@ void trim(char* str) {
while (len-- && isspace(str[len])) str[len] = 0;
}
int strmatch(wchar_t text[], const wchar_t* key, int matchnow) {
//int strmatch(wchar_t text[], const wchar_t* key, int matchnow) {
int strmatch(char text[], const char* key, int matchnow) {
int retval = -1;
int length = wcslen(key);
wchar_t* substr = (wchar_t*) malloc((length + 1) * sizeof(wchar_t));
wmemset(substr, L'\0', length + 1);
//int length = wcslen(key);
int length = strlen(key);
//wchar_t* substr = (wchar_t*) malloc((length + 1) * sizeof(wchar_t));
char* substr = (char*) malloc((length + 1) * sizeof(char*));
//wmemset(substr, L'\0', length + 1);
memset(substr, '\0', length + 1);
if (! substr) wrapup(1, "Error allocating memory in strmatch.\n");
int i = 0;
int textlen = wcslen(text);
//int textlen = wcslen(text);
int textlen = strlen(text);
while ((text[i] != '\0') && (i < (textlen - length))) {
wmemcpy(substr, &text[i], length);
if (! wcscmp(substr, key)) {
//wmemcpy(substr, &text[i], length);
memcpy(substr, &text[i], length);
//if (! wcscmp(substr, key)) {
if (! strcmp(substr, key)) {
retval = i;
break;
}
@ -97,10 +111,7 @@ int parseConf(sword::SWBuf buf) {
starray stappend(starray arr, const char* newstr) {
arr.length++;
if (arr.length == 1) {
free(arr.strings); // needed due to stinit()'s malloc(0)
arr.strings = (const char**) malloc(sizeof(char*));
} else {
if (arr.length > 1) {
arr.strings = (const char**)
realloc(arr.strings, arr.length * sizeof(char*));
}
@ -113,7 +124,7 @@ starray stappend(starray arr, const char* newstr) {
starray stinit(starray arr) {
arr.length = 0;
arr.strings = (const char**) malloc(0);
arr.strings = (const char**) malloc(sizeof(char*));
return arr;
}

18
free.h
View File

@ -23,6 +23,12 @@
#include <swconfig.h>
#include <swbuf.h>
// preprocessor directives - for some compiler-constant expressions
#define PAGELIMIT 5 // the maximum number of pages allowed
#define NUMPANES 2 // the max number of panes allowed -- this value
// as is in other places of the code
//! Structure to make managing arrays easier. Who doesn't like arrays?
typedef struct {
int length; //!< number of elements
@ -34,7 +40,8 @@ typedef struct {
* One is stored by each pane. */
typedef struct {
const char* modname; //!< name of module
const char* searchkey; //!< our search key for the module
const char* searchkey; /*!< search key for module - if NULL or empty, the
* struct is considered empty */
int keytype; //!< 0 - versekey, 1 - treekey, 2 - direct
int searchtype; //!< type of search to be performed, if any
const char* scope; //!< scope of search, if any
@ -46,6 +53,12 @@ extern char HELPTEXT[];
//! String giving the informative text on the command line, for '-h'
extern char CLITEXT[];
//! Value of default searchtype for lookups keyed to verse
extern int DEFSEARCH;
//! Value of default keytype for lookups
extern int DEFKEY;
//! Global copy of the program's configuration file settings.
extern sword::SWConfig config;
@ -62,7 +75,8 @@ int parseConf(sword::SWBuf buf);
/*! Determine if a substring is present at either the beginning of a string or
* at any point within it, depending on a toggle for a flag -- return integer
* of matching index. */
int strmatch(wchar_t text[], const wchar_t* key, int matchnow);
//int strmatch(wchar_t text[], const wchar_t* key, int matchnow);
int strmatch(char text[], const char* key, int matchnow);
//! Append a new string to a starray.
starray stappend(starray arr, const char* newstr);

134
pane.cpp
View File

@ -27,25 +27,23 @@
#include "pane.h"
// constructor
pane::pane(int starty, int startx, int lines, int cols, const char* title) {
pane::pane(int starty, int startx, int lines, int cols, const char* title,
modkey defmod) {
buflocx = buflocy = 0;
hasFocus = false;
rawtext = NULL;
// set some default search parameters
mod.modname = config["defaults"]["module"];
mod.searchkey = config["defaults"]["searchkey"];
mod.keytype = 0;
mod.searchtype = 0;
mod.scope = config["defaults"]["scope"];
setTitle(title);
// set up the window -- resize will give us some initializing
setTitle(title);
pad = NULL;
resize(starty, startx, lines, cols);
redraw(true);
// forms/menus send in a null searchkey
if (defmod.searchkey != NULL) {
setModkey(defmod, title);
}
}
// handle pane resizing
@ -103,19 +101,11 @@ void pane::renderText() {
int nontextual = parseConf(config["markup"]["nontextual"]);
// int footnotes = 0; not implemented yet
// convert input to wide characters for proper rendering
wchar_t* text = (wchar_t*) malloc(sizeof(wchar_t) * (length + 1));
// copy our unformatted text to local scope for window formatting
char* text = (char*) malloc(sizeof(char*) * length + 1);
if (! text) wrapup(1, "Error allocating memory in renderText.\n");
wmemset(text, L'\0', length + 1);
/* some typographical quotes won't go through mbstowcs, so alter those; this
* seems inefficient, but we don't know how many bytes are in each multibyte
* char so I think it has to be done this way */
for (int i = 0; i < length; i++) {
int converted = mbstowcs(text, rawtext, 1);
if (converted == -1) strncpy(&(rawtext[i-1]), "'", 1);
}
mbstowcs(text, rawtext, length - 1);
memset(text, '\0', strlen(rawtext) + 1);
strncpy(text, rawtext, length);
// kept for debugging
//fwprintf(stderr, L"(O): %ls\n\n", text);
@ -127,12 +117,14 @@ void pane::renderText() {
for (int p = 0; p < 2; p++) {
// loop through the text
for (int i = 0; i < length; i++) {
int i = 0;
while (i < length) {
/* check if we're in markup - it's not printed and it doesn't
* affect our line lengths, so spin through it */
if (inmarkup) {
if (text[i] == '>') inmarkup = 0;
i++;
continue;
}
@ -140,22 +132,22 @@ void pane::renderText() {
if ((text[i] == '<') && (! rawonly)) {
inmarkup = 1;
if ((! strmatch(&text[i], L"</q>", 1)) && (p == 1)) {
if ((! strmatch(&text[i], "</q>", 1)) && (p == 1)) {
// end of redletter bracket
makered = 0;
} else if ((! strmatch(&text[i], L"</transChange>", 1))
} else if ((! strmatch(&text[i], "</transChange>", 1))
&& (p == 1)) {
// end of interpretive text bracket
makeital = 0;
} else if ((! strmatch(&text[i], L"<p>", 1)) && (p == 0)) {
} else if ((! strmatch(&text[i], "<p>", 1)) && (p == 0)) {
// paragraph break - replace </p> with </>'\n'
int endindex = strmatch(&text[i], L"</p>", 0);
int endindex = strmatch(&text[i], "</p>", 0);
if (endindex != -1)
wmemcpy(&text[i + endindex + 2], L">\n", 2);
memcpy(&text[i + endindex + 2], ">\n", 2);
} else if ((! strmatch(&text[i], L"<w savlm=", 1))
} else if ((! strmatch(&text[i], "<w savlm=", 1))
&& (p == 0) && (strongs == 1)) {
/* Strong's number - the format is below, but note
* there may be 1+ strong:[G|H]NNNN(N)'s to find,
@ -164,29 +156,28 @@ void pane::renderText() {
* <w savlm="strong:[G|H]NNNN(N)">word</w> */
// 1. get boundary of open tag
int endbracket = strmatch(&text[i], L">", 0);
int endbracket = strmatch(&text[i], ">", 0);
// 2. get Strong's parameters
wchar_t* num = (wchar_t*) malloc(sizeof(wchar_t*)
* 100);
char* num = (char*) malloc(sizeof(char*) * 100);
if (! num) wrapup(1,
"Error declaring memory in renderText.\n");
int numidx = 0;
int strnum = strmatch(&text[i], L"strong:", 0) + 7;
int strnum = strmatch(&text[i], "strong:", 0) + 7;
while ((strnum < endbracket) && (strnum != -1)) {
int nextspace = strmatch(&text[i+strnum], L" ", 0);
int space1 = strmatch(&text[i+strnum], L"\"", 0);
int nextspace = strmatch(&text[i+strnum], " ", 0);
int space1 = strmatch(&text[i+strnum], "\"", 0);
int len = ((nextspace == -1) || (space1 < nextspace)
? space1
: nextspace);
if (numidx != 0) wmemcpy(&num[numidx++], L" ", 1);
wmemcpy(&num[numidx], &text[i+strnum], len);
if (numidx != 0) memcpy(&num[numidx++], " ", 1);
memcpy(&num[numidx], &text[i+strnum], len);
numidx += len;
int nextnum =
strmatch(&text[i+strnum], L"strong:", 0);
strmatch(&text[i+strnum], "strong:", 0);
strnum = (nextnum != -1
? strnum + nextnum + 7
: -1);
@ -197,27 +188,27 @@ void pane::renderText() {
* below zero, otherwise get word boundaries */
int wordstart = endbracket + 1;
int endtag =
(endbracket > strmatch(&text[i], L"/", 0)
(endbracket > strmatch(&text[i], "/", 0)
? endbracket + 1
: strmatch(&text[i], L"</w>", 0));
: strmatch(&text[i], "</w>", 0));
// 4. determine word boundaries & rewrite
int wordlen = endtag - wordstart;
wchar_t* word = (wchar_t*) malloc(sizeof(wchar_t*)
char* word = (char*) malloc(sizeof(char*)
* (wordlen == 0 ? 1 : wordlen));
if (! word) wrapup(1,
"Error rewriting markup in renderText.\n");
wmemcpy(word, &text[i+wordstart], wordlen);
memcpy(word, &text[i+wordstart], wordlen);
// rewrite
int start = i + endtag - wordlen - numidx - 4;
wmemcpy(&text[start], L"\"", 1);
wmemcpy(&text[start + 1], L">", 1);
wmemcpy(&text[start + 2], word, wordlen);
wmemcpy(&text[start + 2 + wordlen], L"[", 1);
wmemcpy(&text[start + 3 + wordlen], num, numidx);
wmemcpy(&text[start + 3 + wordlen + numidx], L"]", 1);
memcpy(&text[start], "\"", 1);
memcpy(&text[start + 1], ">", 1);
memcpy(&text[start + 2], word, wordlen);
memcpy(&text[start + 2 + wordlen], "[", 1);
memcpy(&text[start + 3 + wordlen], num, numidx);
memcpy(&text[start + 3 + wordlen + numidx], "]", 1);
// clean up so we can do this again
free(num);
@ -226,12 +217,12 @@ void pane::renderText() {
// kept for debugging
//fwprintf(stderr, L"(I): %ls\n", text);
} else if ((! strmatch(&text[i], L"<q marker", 1))
} else if ((! strmatch(&text[i], "<q marker", 1))
&& (p == 1) && (redletter == 1)) {
// start of redletter bracket
makered = 1;
} else if ((! strmatch(&text[i], L"<transChange type=\"added\"", 1))
} else if ((! strmatch(&text[i], "<transChange type=\"added\"", 1))
&& (p == 1) && (nontextual == 1)) {
// start of interpretive text bracket
makeital = 1;
@ -241,6 +232,17 @@ void pane::renderText() {
continue;
} // markup check
/* determine how large this multibyte character is -- we will need it to
* determine our advance amount in the loop */
int offset = 0;
size_t charlen = mbrlen(&text[i], 5, NULL);
while (((int) mbrlen(&text[i+offset], 5, NULL) == -1) &&
(i + offset < length)) {
/* mbrlen() says the next char is not a valid multibyte char, so find
* the length by figuring out where the succeeding character is */
offset++;
}
if (p == 0) {
// handle word wrapping
if (text[i] == ' ') {
@ -280,15 +282,21 @@ void pane::renderText() {
} else {
// printing -- pull out the single character we care about
wchar_t single[] = L"\0\0";
wcsncpy(&single[0], &text[i], 1);
wattrset(pad, COLOR_PAIR(makered)
| (makeital ? A_ITALIC : 0));
waddwstr(pad, single);
if (((int) charlen == -1) && (offset == 1)) {
/* This is an extended ascii character (probably Latin-1) and not
* UTF-8. In setting up ncurses for UTF-8 we set a locale and seem to
* make these characters unprintable. To avoid massive rendering
* errors we have to substitute them with something else. */
waddstr(pad, "?");
} else waddnstr(pad, &text[i], charlen);
}
printable++;
i += ((int) charlen == -1 ? offset : (int) charlen);
} // text loop
// text rewriting debugging
@ -320,9 +328,7 @@ void pane::setTitle(const char* newtitle) {
void pane::retitle() {
(hasFocus ? wattrset(win, A_STANDOUT) : standend());
mvwprintw(win, 0, 1, "%s", " ");
mvwprintw(win, 0, 3, "%s", titlebar);
mvwprintw(win, 0, 3+strlen(titlebar), "%s", " ");
mvwprintw(win, 0, 1, "%s%s%s", " ", titlebar, " ");
wstandend(win);
}
@ -437,8 +443,8 @@ int pane::loadMenu(starray opts) {
int pane::printInstructions(int y, const char* inst) {
// NOTE -- starting indentation is hardcoded at two characters in
int strctr = 0;
int line = width - 3;
long unsigned int strctr = 0;
long unsigned int line = width - 3;
while (strlen(&inst[strctr]) > line) {
// set write length to be up to the last space, for word wrapping
while ((inst[line] != ' ') && (line > 0)) line--;
@ -574,7 +580,7 @@ void pane::formClean(FORM* form, FIELD** fields, int numfields) {
}
void pane::setModule(const char* newtitle, const char* newmod,
int keytype) {
int keytype) {
mod.modname = newmod;
mod.keytype = keytype;
setTitle(newtitle);
@ -584,7 +590,7 @@ void pane::setModule(const char* newtitle, const char* newmod,
void pane::setKey(const char* newkey) {
mod.searchkey = newkey;
setSearch(0, "Gen 1:1 - Rev 22:21");
setSearch(DEFSEARCH, "Gen 1:1 - Rev 22:21");
}
void pane::setSearch(int type, const char* scope) {
@ -592,6 +598,8 @@ void pane::setSearch(int type, const char* scope) {
mod.scope = scope;
}
modkey pane::getModkey() {
return mod;
void pane::setModkey(modkey newmod, const char* newtitle) {
setModule(newtitle, newmod.modname, newmod.keytype);
setKey(newmod.searchkey);
setSearch(newmod.searchtype, newmod.scope);
}

11
pane.h
View File

@ -74,7 +74,8 @@ class pane {
int startx, //!< the pane's upper left Y coord
int lines, //!< the pane's number of lines
int cols, //!< the pane's number of columns
const char* title //!< starting titlebar text
const char* title, //!< starting titlebar text
modkey defmod //!< starting modkey, or NULL if not applicable
);
//! Resize the window & pad, in the case of terminal resizing.
@ -160,9 +161,11 @@ class pane {
const char* scope //!< scope to search within, in key format
);
/*! Gets the modkey associated with this pane.
* \returns modkey associated with this pane */
modkey getModkey();
/*! Loads an entirely new modkey into the pane. Will call associated
* redrawing functions. */
void setModkey(modkey newmod, //!< new modkey to load in
const char* newtitle //!< new pane title to use
);
};

View File

@ -20,10 +20,15 @@
#include <iostream>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "scabbard.h"
scabbard::scabbard() {
// initialize the thing!
swrd = *(new sword::SWMgr());
/* store what markup information the user has requested be shown
* SWBuf.c_str() returns the ascii code 48 for '0', so we have to do some
* math to get the proper boolean setting */
@ -31,18 +36,22 @@ scabbard::scabbard() {
markstrongs = parseConf(config["markup"]["strongs"]);
markfoot = parseConf(config["markup"]["footnotes"]);
/* everything following is based on constructing the menu used to display
* to the reader about what modules the system has - start with a
* temporary array of module types */
int tmpnum = 4;
modtype tmpmods[4];
strcpy(tmpmods[0].label, "Generic Books");
// construct the menu list of modules
constructModlist();
}
void scabbard::constructModlist() {
int tmpnum = 5;
modtype tmpmods[5];
strcpy(tmpmods[0].label, sword::SWMgr::MODTYPE_GENBOOKS);
tmpmods[0].keytype = 1;
strcpy(tmpmods[1].label, "Biblical Texts");
strcpy(tmpmods[1].label, sword::SWMgr::MODTYPE_BIBLES);
tmpmods[1].keytype = 0;
strcpy(tmpmods[2].label, "Lexicons / Dictionaries");
strcpy(tmpmods[2].label, sword::SWMgr::MODTYPE_LEXDICTS);
tmpmods[2].keytype = 2;
strcpy(tmpmods[3].label, "Commentaries");
strcpy(tmpmods[3].label, sword::SWMgr::MODTYPE_COMMENTARIES);
tmpmods[3].keytype = 0;
strcpy(tmpmods[4].label, sword::SWMgr::MODTYPE_DAILYDEVOS);
tmpmods[3].keytype = 0;
// need some throwaway ints
@ -182,6 +191,26 @@ const char* scabbard::getModDescription(int modt, int mod) {
return (modules[modt].modlist)[mod]->getDescription();
}
const char* scabbard::getModDescription(const char* modname) {
sword::SWModule *text = swrd.getModule(modname);
return text->getDescription();
}
int scabbard::modExists(const char* modname) {
sword::SWModule *text = swrd.getModule(modname);
return (text == 0 ? 0 : 1);
}
starray scabbard::getModules() {
starray retval;
retval = stinit(retval);
for (iter = swrd.Modules.begin(); iter != swrd.Modules.end(); iter++) {
sword::SWModule *mod = iter->second;
retval = stappend(retval, mod->getName());
}
return retval;
}
int scabbard::getKeyType(int modt) {
return modules[modt].keytype;
}
@ -214,14 +243,94 @@ const char* scabbard::search(modkey mod) {
sword::SWKey *clone =
(key->parseVerseList(mod.scope, mod.scope, true)).clone();
sword::ListKey lk =
text->search(mod.searchkey, mod.searchtype, 0, clone);
text->search(mod.searchkey, mod.searchtype, 0, clone);
const char* retval = parseVerses(text, lk);
delete(text);
delete(key);
delete(clone);
return retval;
}
return directSearch(text, mod);
}
} else return directSearch(text, mod);
void scabbard::installKJVA() {
sword::SWBuf swordhome = getenv("HOME");
swordhome += "/.sword";
// ensure some directories exist -- apparently installmgr doesn't create them
if ((mkdir(swordhome + "/mods.d", 00755) != 0) && (errno != EEXIST)) {
printf("Error creating directory to perform the install:\n");
printf("~/.sword/mods.d\n");
printf("Please create manually or check directory permissions.\n");
exit(0);
}
sword::SWBuf finaldir = swordhome.c_str();
finaldir += "/modules";
mkdir(finaldir.c_str(), 00755);
finaldir += "/texts";
mkdir(finaldir.c_str(), 00755);
finaldir += "/ztext";
mkdir(finaldir.c_str(), 00755);
finaldir += "/kjva";
if ((mkdir(finaldir.c_str(), 00755) != 0) && (errno != EEXIST)) {
printf("Error creating directory to perform the install:\n");
printf("~/.sword/modules/texts/ztext/kjva\n");
printf("Please create manually or check directory permissions.\n");
exit(0);
}
printf("Directories created for new module.\n");
// installmgr -init
sword::InstallMgr* im = new sword::InstallMgr(swordhome + "/InstallMgr");
im->setUserDisclaimerConfirmed(true);
// sync config (installmgr -sc)
sword::InstallSource is("FTP");
is.caption = "CrossWire";
is.source = "ftp.crosswire.org";
is.directory = "/pub/sword/raw";
sword::SWConfig installconf(swordhome + "/InstallMgr/InstallMgr.conf");
if (! installconf["General"]["PassiveFTP"])
installconf["General"]["PassiveFTP"] = "true";
if (! installconf["General"]["TimeoutMillis"])
installconf["General"]["TimeoutMillis"] = "10000";
if (! installconf["General"]["UnverifiedPeerAllowed"])
installconf["General"]["UnverifiedPeerAllowed"] = false;
if (! installconf["General"]["FTPSource"])
installconf["Sources"]["FTPSource"] = is.getConfEnt();
installconf.save();
im->refreshRemoteSourceConfiguration();
/* refresh remote source (installmgr -r)
* apparently we can't reuse our prior declared InstallSource here */
sword::InstallSourceMap::iterator source = im->sources.find("CrossWire");
sword::InstallSource *is2 = source->second;
if (source == im->sources.end()) {
fprintf(stderr, "Couldn't find remote source [%s]\n", "CrossWire");
}
if (int refresh = im->refreshRemoteSource(is2)) {
printf("Refresh call failed, error: %d\n", refresh);
printf("Most likely this is an InstalMgr configuration issue.\n");
printf("Please ensure your SWORD environment is properly configured,\n");
printf(" or download & install the module manually before trying again.\n");
exit(0);
}
// install module (installmgr -ri CrossWire KJVA)
sword::SWMgr* rmgr = is2->getMgr();
sword::SWModule* modname = rmgr->getModule("KJVA");
if (im->installModule(new sword::SWMgr(), 0, modname->getName(), is2)) {
printf("Error installing module.\n");
printf("Most likely this is an environment configuration issue.\n");
printf("Please ensure your SWORD environment is properly configured,\n");
printf(" or download & install the module manually before trying again.\n");
exit(0);
}
// update internal module list & refresh SWMgr w/installed module info
swrd = *(new sword::SWMgr());
constructModlist();
}
/* really old code in case we need to pull output from diatheke

View File

@ -27,6 +27,7 @@
#include <swbuf.h>
#include <versekey.h>
#include <treekey.h>
#include <installmgr.h>
#include <osisredletterwords.h>
#include <osisstrongs.h>
@ -41,7 +42,8 @@
#include "free.h"
//! Structure for associating a Sword module type to a list of modules.
/*! Structure for associating a Sword module type (Bibles,
* commentaries, etc) to a list of modules. */
typedef struct {
char label[25]; //!< the module type
int keytype; //!< 0 - versekey, 1 - treekey, 2 - direct/alpha
@ -82,13 +84,16 @@ class scabbard {
//!< modkey containing the search term
);
//! Construct the internal list of modules used in the selection menu.
void constructModlist();
public:
//! Takes as input various preferences on text markup from our config file
scabbard();
/*! Struct to hold listing of different modules - each modtype corresponds
* to a different type of module - see comments for modtype */
modtype modules[4];
modtype modules[5];
/*! Get all module types loaded in and accessible by Sword.
* \returns the module types loaded into an array */
@ -106,12 +111,26 @@ class scabbard {
int mod //!< from getModDescriptions()
);
/*! Get the full text name for a module.
/*! Get the full text name for a module identified by menu indices.
* \returns the module description */
const char* getModDescription(int modt, //!< from getModClassifications()
int mod //!< from getModDescriptions()
);
/*! Get the full text name for a module identified by key name. Vulnerable
* to malicious input.
* \returns the module description */
const char* getModDescription(const char* modname //!< module internal name
);
//! Return an array of all module names.
starray getModules();
/*! Given a text name for a module, determine if it exists.
*/
int modExists(const char* modname //!< module internal name
);
/*! Determine how a module is keyed/indexed.
* \returns the proper keytype; see the keytype field for modkey */
int getKeyType(int modt //!< return value from getModClassifications()
@ -123,11 +142,14 @@ class scabbard {
);
/*! Search for a term or passage.
* \returns the text requested, or in the case of a non-versekeyed text,
* the nearest alphabetical entry to the search term supplied */
* \returns the text requested, or in the case of a non-versekeyed text,
* the nearest alphabetical entry to the search term supplied */
const char* search(modkey mod //!< the modkey supplied by the pane
);
//! Install KJVA from ftp.crosswire.org.
void installKJVA();
};
#endif

View File

@ -31,10 +31,12 @@
#include "pane.h"
#include "scabbard.h"
/* --[ ASSISTS / TOP NAMESPACE ]-- */
/* --[ ASSISTS / GLOBALS ]-- */
// quick array for page names
char* pages[PAGELIMIT][NUMPANES];
int NUMPANES;
int currentpage, totalpages;
int halfcols, halflines;
int floatx, floaty, floatheight, floatwidth;
int startx1, starty1, startx2, starty2;
@ -42,8 +44,30 @@ int panelength, panewidth;
sword::SWConfig config;
//! Write background text in stdscr
void foundation() {
// wipe out old page info line
for (int i = 0; i < COLS - 20; i++) mvaddch(LINES - 1, i, ' ');
// place page info & background text
int placement = 0;
for (int i = 0; i < totalpages; i++) {
// NOTE -- we are assuming the length of a searchkey & size of NUMPANES
if (i == currentpage) attron(A_STANDOUT);
mvprintw(LINES - 1, placement, "[%d]:%s/%s", i,
config[pages[i][0]]["searchkey"].c_str(),
config[pages[i][1]]["searchkey"].c_str());
attroff(A_STANDOUT);
placement += 26;
}
mvprintw(LINES - 1 , COLS - 20, "%s", "? - help q - quit");
refresh();
}
//! Refresh every portion of a list of panes we are given.
void wipeit(pane* panes) {
clear();
foundation();
for (int i = 0; i < NUMPANES; i++) panes[i].redraw(true);
}
@ -51,28 +75,50 @@ void wipeit(pane* panes) {
starray showForm(const char* title, starray inputs, const char* secondinst,
int floaty, int floatx, int floatheight, int floatwidth) {
// pull up the floating form
pane form = {floaty, floatx, floatheight, floatwidth, title};
modkey defmod;
defmod.searchkey = NULL;
pane form = {floaty, floatx, floatheight, floatwidth, title, defmod};
starray ret = form.loadForm(inputs, secondinst);
// sanitize input? -- if (ret.length != 0)
return ret;
}
//! Construct a modkey from the currently selected page & pane
modkey getModkey(int infocus) {
modkey mod;
mod.modname = config[pages[currentpage][infocus]]["module"];
mod.searchkey = config[pages[currentpage][infocus]]["searchkey"];
mod.searchtype = parseConf(config[pages[currentpage][infocus]]["searchtype"]);
mod.keytype = parseConf(config[pages[currentpage][infocus]]["keytype"]);
mod.scope = config[pages[currentpage][infocus]]["scope"];
return mod;
}
//! Refresh to the current page.
void loadPage(pane* panes, scabbard* scab) {
for (int i = 0; i < NUMPANES; i++) {
modkey mod = getModkey(i);
panes[i].setModkey(mod, scab->getModDescription(mod.modname));
// searchtype will tell us whether this is a search or a straight lookup
if (mod.searchtype == 1) {
panes[i].loadText(scab->getSpan(mod));
} else {
panes[i].loadText(scab->search(mod));
}
}
wipeit(panes);
}
//! Save the config array -- we abstract this out in case this changes.
void saveit(char* configfile) {
/* This is clumsy but we have to use this particular constructor for
* config() so we can save to disk. The global config object uses a
* different constructor. */
sword::SWConfig newconfig(configfile);
newconfig.augment(config);
newconfig.save();
void saveit() {
config.save();
}
//! Set various standard screen landmarks.
void landmarks() {
// place background text
mvprintw(LINES - 1 , COLS - 20, "%s", "? - help q - quit");
refresh();
foundation();
// split up the screen
halfcols = COLS / 2;
@ -108,7 +154,7 @@ void doresize(pane* p) {
// reset standard screen landmarks
landmarks();
// reset and refresh main panes
// reset and refresh main panes -- assumes NUMPANES
p[0].resize(starty1, startx1, panelength, panewidth);
p[1].resize(starty2, startx2, panelength, panewidth);
wipeit(p);
@ -153,40 +199,80 @@ int main(int argc, char** argv) {
printf("Please ensure this directory exists and is writable.\n");
return -1;
}
// set default module and other fields to avoid crashing
fprintf(cnf, "[defaults]\n");
fprintf(cnf, "scope=Gen 1:1 - Rev 22:21\n");
fprintf(cnf, "searchkey=2 Tim 1:7\n");
fprintf(cnf, "module=KJV\n");
fclose(cnf);
printf("done.\n");
}
}
sword::SWConfig tempconfig(configfile);
config.augment(tempconfig);
config = *(new sword::SWConfig(configfile));
// ensure some needed config settings are in place
/* load up a scabbard & set blank starting data - scabbard args are
* sanitized in the constructor */
scabbard scab = {};
/* before we render anything, ensure some needed config settings are in
* place & sanitized, with needed changes saved back to file */
if (! strcmp(config["layout"]["panels"], ""))
config["layout"]["panels"] = "v";
if (! strcmp(config["defaults"]["scope"], ""))
config["defaults"]["scope"] = "Gen 1:1 - Rev 22:21";
if (! strcmp(config["defaults"]["searchkey"], ""))
config["defaults"]["searchkey"] = "2 Tim 1:;7";
if (! strcmp(config["defaults"]["module"], ""))
config["defaults"]["module"] = "KJV";
saveit(configfile);
config["defaults"]["searchkey"] = "2 Tim 1:7";
if ((! strcmp(config["defaults"]["module"], "")) ||
(! scab.modExists(config["defaults"]["module"]))) {
/* prompt for a default module - we're on partial startup here and
* still outside of ncurses, so we do this the old fashioned way. */
printf("You either do not have a default module selected, "
"or what you have chosen is not installed.\n");
printf("Please select one before continuing.\n\n");
printf("0 - Install KJVA from ftp.crosswire.org.\n");
starray mods = scab.getModules();
for (int i = 0; i < mods.length; i++) {
const char* longname = scab.getModDescription(mods.strings[i]);
printf("%d - %s\n", i+1, longname);
}
printf("\nDefault module to choose: ");
char readit[4];
fgets(readit, 4, stdin);
long selection = 0;
selection = strtol(readit, NULL, 10);
const char* defmod;
if ((selection == 0) || (selection > mods.length)) {
printf("Installing from ftp.crosswire.org...\n");
scab.installKJVA();
defmod = "KJVA";
printf("done.\n");
} else defmod = mods.strings[selection - 1];
config["defaults"]["module"] = defmod;
}
if (! strcmp(config["defaults"]["searchtype"], "")) {
char* temp;
asprintf(&temp, "%d", DEFSEARCH);
config["defaults"]["searchtype"] = temp;
free(temp);
}
if (! strcmp(config["page0-0"]["searchkey"], ""))
config["page0-0"]["searchkey"] = "2 Tim 1:7";
if (! strcmp(config["page0-1"]["searchkey"], ""))
config["page0-1"]["searchkey"] = "2 Tim 1:7";
saveit();
// NOTE -- anything after this point must use our defined exit wrapup()
/* start ncurses, disable line buffering hide cursor, and allow for fancy
* keys & so on */
setlocale(LC_ALL, "");
initscr();
cbreak();
curs_set(0);
noecho();
start_color();
keypad(stdscr, true);
setlocale(LC_ALL, "");
// get color settings
if (! strcmp(config["markup"]["lettercolor"], "green")) {
@ -203,33 +289,69 @@ int main(int argc, char** argv) {
init_pair(1, COLOR_RED, COLOR_BLACK);
}
// two main panes
NUMPANES = 2;
landmarks();
pane* p = (pane*) malloc(sizeof(pane) * NUMPANES);
const char* title = "Window";
if (! p) wrapup(1, "Error allocating memory for panes.\n");
p[0] = { starty1, startx1, panelength, panewidth, title };
p[1] = { starty2, startx2, panelength, panewidth, title };
// cycle through config file for any saved pages & sanitize
totalpages = 0;
int linking = 1;
for (int i = 0; i < PAGELIMIT; i++) {
for (int j = 0; j < NUMPANES; j++) {
/* sanitize input here --
* It's our own config but the user can edit it. If a searchkey doesn't
* exist then we need to stop to ensure the list is linked properly.
* If it does, accept whatever the searchkey is, even if the user
* modified it. For other settings, verify they are in the file, or take
* the defaults & update the config. When we get to the first blank
* searchkey for the start of a page, no longer take any further input. */
asprintf(&(pages[i][j]), "page%d%s%d", i, "-", j);
pane* focustab;
focustab = &(p[0]);
focustab->toggleFocus();
/* with no searchkey, if this is the first pane then stop linking here;
* otherwise we can get by with setting the searchkey to the default */
if (! strcmp(config[pages[i][j]]["searchkey"], "")) {
(j == 0 ? linking = 0 :
config[pages[i][j]]["searchkey"] = config["defaults"]["searchkey"]);
}
/* load up a scabbard & set blank starting data - scabbard args are
* sanitized in the constructor */
scabbard scab = {};
char blank[] = "Open a text to use this window.\0";
for (int i = 0; i < NUMPANES; i++) {
if (config["defaults"]["module"] && config["defaults"]["searchkey"]) {
// this isn't technically the title, but it's what we have now
p[i].setTitle(config["defaults"]["module"]);
p[i].loadText(scab.getSpan(p[i].getModkey()));
} else {
p[i].loadText(blank);
if (linking && ((! strcmp(config[pages[i][j]]["module"], "")) ||
(! scab.modExists(config[pages[i][j]]["module"])))) {
config[pages[i][j]]["module"] = config["defaults"]["module"];
}
if (linking && (! strcmp(config[pages[i][j]]["keytype"], "")))
config[pages[i][j]]["keytype"] = config["defaults"]["keytype"];
if (linking && (! strcmp(config[pages[i][j]]["searchtype"], "")))
config[pages[i][j]]["searchtype"] = config["defaults"]["searchtype"];
if (linking && (! strcmp(config[pages[i][j]]["scope"], "")))
config[pages[i][j]]["scope"] = config["defaults"]["scope"];
// we have a page
if (linking && (j == 0)) totalpages++;
}
}
wipeit(p);
// set index for page list
currentpage = 0;
// initialize panes and render
landmarks();
pane* p = (pane*) malloc(sizeof(pane) * NUMPANES);
if (! p) wrapup(1, "Error allocating memory for panes.\n");
for (int i = 0; i < NUMPANES; i++) {
modkey mod = getModkey(i);
// NOTE -- assumes NUMPANES, for future work
int starty, startx;
starty = (i == 0 ? starty1 : starty2);
startx = (i == 0 ? startx1 : startx2);
p[i] = { starty, startx, panelength, panewidth,
scab.getModDescription(mod.modname), mod };
}
pane* focustab;
int infocus = 0;
focustab = &(p[infocus]);
focustab->toggleFocus();
loadPage(p, &scab);
// loop for input
int ch;
@ -239,7 +361,8 @@ int main(int argc, char** argv) {
case '\t': {
// switch pane focus
focustab->toggleFocus();
focustab = (focustab == &(p[0]) ? &(p[1]) : &(p[0]));
infocus = (infocus + 1) % NUMPANES;
focustab = &(p[infocus]);
focustab->toggleFocus();
break; }
@ -263,12 +386,64 @@ int main(int argc, char** argv) {
doresize(p);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
// go to page
if (totalpages <= (int) ch - 48) break;
currentpage = (int) ch - 48;
loadPage(p, &scab);
break;
case '<': {
// previous page
currentpage = (currentpage == 0 ? totalpages - 1 : currentpage - 1);
loadPage(p, &scab);
break; }
case '>': {
// next page
currentpage = (currentpage + 1) % totalpages;
loadPage(p, &scab);
break; }
case 'd': {
// delete page
if (totalpages == 1) break;
for (int i = currentpage; i < totalpages; i++) {
for (int j = 0; j < NUMPANES; j++) {
if (i == totalpages - 1) {
// delete the last in list
config[pages[i][j]]["searchkey"] = NULL;
} else {
// shift all later pages down to fill the gap
config[pages[i][j]]["module"] = config[pages[i+1][j]]["module"];
config[pages[i][j]]["searchkey"] =
config[pages[i+1][j]]["searchkey"];
config[pages[i][j]]["searchtype"] =
config[pages[i+1][j]]["searchtype"];
config[pages[i][j]]["keytype"] = config[pages[i+1][j]]["keytype"];
config[pages[i][j]]["scope"] = config[pages[i+1][j]]["scope"];
}
}
}
totalpages--;
// reset page index if needed to stay in bounds
if (currentpage == totalpages) currentpage--;
loadPage(p, &scab);
saveit();
break; }
case 'f': {
// toggle footnotes
int footnotes = ! parseConf(config["markup"]["footnotes"]);
config["markup"]["footnotes"] = footnotes + '0';
wipeit(p);
saveit(configfile);
saveit();
break; }
case 'g': {
@ -290,8 +465,10 @@ int main(int argc, char** argv) {
if ((ret.length != 0) && (strlen((ret.strings)[0]) > 0)) {
// user entered something that's not a blank string
config[pages[currentpage][infocus]]["searchkey"] = (ret.strings)[0];
focustab->setKey((ret.strings)[0]);
focustab->loadText(scab.getSpan(focustab->getModkey()));
focustab->loadText(scab.getSpan(getModkey(infocus)));
saveit();
}
wipeit(p);
break; }
@ -300,15 +477,16 @@ int main(int argc, char** argv) {
// toggle non-textual words
int textual = ! parseConf(config["markup"]["nontextual"]);
config["markup"]["nontextual"] = textual + '0';
wipeit(p);
saveit(configfile);
saveit();
break; }
case 'o': {
// load a module into the current pane
const char* typetitle = "Choose a module type";
modkey defmod;
defmod.searchkey = NULL;
pane menupane = {floaty, floatx, floatheight, floatwidth,
typetitle };
typetitle, defmod };
starray mc = scab.getModClassifications();
int modclass = menupane.loadMenu(mc);
if (modclass == -1) {
@ -320,7 +498,7 @@ int main(int argc, char** argv) {
}
const char* modtitle = "Choose a module";
menupane = {floaty, floatx, floatheight, floatwidth, modtitle};
menupane = {floaty, floatx, floatheight, floatwidth, modtitle, defmod};
starray mds = scab.getModDescriptions(modclass);
int mod = menupane.loadMenu(mds);
if (mod == -1) {
@ -331,19 +509,50 @@ int main(int argc, char** argv) {
break;
}
config[pages[currentpage][infocus]]["module"] =
scab.getModName(modclass, mod);
char* temp;
asprintf(&temp, "%d", scab.getKeyType(modclass));
config[pages[currentpage][infocus]]["keytype"] = temp;
free(temp);
focustab->setModule(scab.getModDescription(modclass, mod),
scab.getModName(modclass, mod),
scab.getKeyType(modclass));
focustab->loadText(scab.getSpan(focustab->getModkey()));
focustab->loadText(scab.getSpan(getModkey(infocus)));
saveit();
wipeit(p);
break; }
case 'p': {
// new page
if (totalpages == PAGELIMIT) break;
for (int j = 0; j < NUMPANES; j++) {
config[pages[totalpages][j]]["module"] = config["defaults"]["module"].c_str();
config[pages[totalpages][j]]["searchkey"] = config["defaults"]["searchkey"].c_str();
config[pages[totalpages][j]]["scope"] = config["defaults"]["scope"].c_str();
char* temp;
asprintf(&temp, "%d", DEFKEY);
config[pages[totalpages][j]]["keytype"] = temp;
free(temp);
asprintf(&temp, "%d", DEFSEARCH);
config[pages[totalpages][j]]["searchtype"] = temp;
free(temp);
}
currentpage = totalpages;
totalpages++;
saveit();
loadPage(p, &scab);
break; }
case 'r': {
// toggle redletter
int redletter = ! parseConf(config["markup"]["redletters"]);
config["markup"]["redletters"] = redletter + '0';
wipeit(p);
saveit(configfile);
saveit();
break; }
case 's': {
@ -376,21 +585,37 @@ int main(int argc, char** argv) {
/* user entered something - get first field with data and
* the search scope (the last item), if any */
int i = 0;
for (i = 0; i < ret.length-1; i++) {
// user entered a scope but no search
//if (i == ret.length - 1) break;
for (i = 0; i < ret.length - 1; i++) {
if (strcmp((ret.strings)[i], "") != 0) {
// ensure we have a search scope
const char* newscope;
if (strcmp((ret.strings)[ret.length-1], "") != 0) {
newscope = (ret.strings)[ret.length-1];
} else {
newscope = config["defaults"]["scope"];
}
// update both pane modkey & config file to keep consistency
config[pages[currentpage][infocus]]["searchkey"] = (ret.strings)[i];
config[pages[currentpage][infocus]]["scope"] = newscope;
char* temp;
asprintf(&temp, "%d", i * -1);
config[pages[currentpage][infocus]]["searchtype"] = temp;
free(temp);
focustab->setKey((ret.strings)[i]);
focustab->setSearch(i * -1,
(ret.strings)[ret.length-1]);
focustab->setSearch(i * -1, newscope);
break;
}
}
if (i != ret.length - 1)
focustab->loadText(scab.search(focustab->getModkey()));
if (i != ret.length - 1) {
focustab->loadText(scab.search(getModkey(infocus)));
saveit();
}
}
wipeit(p);
break; }
@ -399,22 +624,30 @@ int main(int argc, char** argv) {
// toggle Strong's numbers
int strongs = ! parseConf(config["markup"]["strongs"]);
config["markup"]["strongs"] = strongs + '0';
wipeit(p);
saveit(configfile);
saveit();
break; }
case 'w': {
// toggle raw text
int raw = ! parseConf(config["markup"]["rawtext"]);
config["markup"]["rawtext"] = raw + '0';
wipeit(p);
saveit(configfile);
saveit();
break; }
case '?': {
// display help text
clear();
addstr(HELPTEXT);
int x, y;
int i = 0;
while (HELPTEXT[i] != '\0') {
addch(HELPTEXT[i++]);
getyx(stdscr, y, x);
if (y == LINES - 1) {
// pay attention to terminal size
getch();
clear();
}
}
getch();
wipeit(p);
break; }

View File

@ -1,35 +1,88 @@
# Default configuration file
# Part of the Scriptura project
# default section must be present and is reverified on every run
[defaults]
# which module to load by default - this should be the abbreviation used by the Sword module
module=KJV
# When searching the Bible, what default portions are you searching - should be in standard Biblical notation format
module=WEB
scope=Gen 1:1 - Rev 22:21
# What verse are you loading on startup - also standard Biblical notation
searchkey=Mat 5:5
searchkey=2 Tim 1:7
searchtype=1
# general ncurses layout
[layout]
# should panels be arranged vertically 'v' or horizontally 'h'
panels=v
# text markup options - most of these can be set during runtime, and are dependent on what data is present in the module
[markup]
# show footnotes (0 or 1)
footnotes=1
# if showing the words of Jesus in a different color, what color is used - this should be a standard ncurses color
footnotes=0
lettercolor=cyan
# if a translation has identified words as not part of the original text, do we italicize it (0 or 1)
nontextual=0
# ignore all other markup options and give us the raw text stored in the module (0 or 1)
rawtext=0
# do we show the words of Jesus in a separate color (0 or 1)
redletters=1
# if the module contains Strong's numbers, do we display them (0 or 1)
strongs=0
[page0-0]
keytype=0
module=WEB
scope=Gen 1:1 - Rev 22:21
searchkey=2 Tim 1:7
searchtype=1
[page0-1]
keytype=0
module=WEB
scope=Gen 1:1 - Rev 22:21
searchkey=2 Tim 1:7
searchtype=1
[page1-0]
keytype=0
module=WEB
scope=Gen 1:1 - Rev 22:21
searchkey=2 Tim 1:7
searchtype=1
[page1-1]
keytype=0
module=WEB
scope=Gen 1:1 - Rev 22:21
searchkey=2 Tim 1:7
searchtype=1
[page2-0]
keytype=0
module=WEB
scope=Gen 1:1 - Rev 22:21
searchkey=2 Tim 1:7
searchtype=1
[page2-1]
keytype=0
module=WEB
scope=Gen 1:1 - Rev 22:21
searchkey=2 Tim 1:7
searchtype=1
[page3-0]
keytype=
module=
scope=
searchkey=
searchtype=
[page3-1]
keytype=
module=
scope=
searchkey=
searchtype=
[page4-0]
keytype=
module=
scope=
searchkey=
searchtype=
[page4-1]
keytype=
module=
scope=
searchkey=
searchtype=