Compare commits

...

5 Commits

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
9 changed files with 258 additions and 86 deletions

3
.gitignore vendored
View File

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

View File

@ -1,6 +1,6 @@
PREFIX = /usr/local
PREFIX ?= /usr/local
CC = g++
CCFLAGS = -Wall $(shell pkg-config --cflags sword)
CCFLAGS ?= -Wall $(shell pkg-config --cflags sword)
TARGET = scriptura
INCLUDES = -I/usr/include/sword -I/usr/include/ncursesw
LDFLAGS = -lmenuw -lformw -lncursesw -lstdc++ $(shell pkg-config --libs sword)
@ -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

@ -36,7 +36,8 @@ When the software is running, the '?' key will give you the list of commands ava
## 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

View File

@ -75,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;
}

3
free.h
View File

@ -75,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);

102
pane.cpp
View File

@ -101,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]), "'", 2);
}
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);
@ -125,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;
}
@ -138,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,
@ -162,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);
@ -195,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);
@ -224,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;
@ -239,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] == ' ') {
@ -278,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
@ -318,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);
}

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
@ -187,6 +196,21 @@ const char* scabbard::getModDescription(const char* 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;
}
@ -228,6 +252,87 @@ const char* scabbard::search(modkey mod) {
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
FILE *modlist = popen("diatheke -b system -k modulelist", "r");
char buf[1024];

View File

@ -27,6 +27,7 @@
#include <swbuf.h>
#include <versekey.h>
#include <treekey.h>
#include <installmgr.h>
#include <osisredletterwords.h>
#include <osisstrongs.h>
@ -83,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 */
@ -113,11 +117,20 @@ class scabbard {
int mod //!< from getModDescriptions()
);
/*! Get the full text name for a module identified by key name.
/*! 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()
@ -129,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

@ -203,18 +203,52 @@ int main(int argc, char** argv) {
printf("done.\n");
}
}
config.filename = configfile;
config.load();
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";
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);
@ -231,13 +265,14 @@ int main(int argc, char** argv) {
/* 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")) {
@ -275,8 +310,10 @@ int main(int argc, char** argv) {
config[pages[i][j]]["searchkey"] = config["defaults"]["searchkey"]);
}
if (linking && (! strcmp(config[pages[i][j]]["module"], "")))
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"];
@ -295,11 +332,7 @@ int main(int argc, char** argv) {
// set index for page list
currentpage = 0;
/* load up a scabbard & set blank starting data - scabbard args are
* sanitized in the constructor */
scabbard scab = {};
// configure panes
// initialize panes and render
landmarks();
pane* p = (pane*) malloc(sizeof(pane) * NUMPANES);
if (! p) wrapup(1, "Error allocating memory for panes.\n");