node-sword-interface 1.0.41 → 1.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,9 +10,9 @@ jobs:
10
10
  - uses: actions/checkout@v2
11
11
  - uses: actions/setup-node@v2
12
12
  with:
13
- node-version: '14'
13
+ node-version: '18'
14
14
 
15
- - run: sudo apt update && sudo apt install -y libcurl4-gnutls-dev
15
+ - run: sudo apt update && sudo apt install -y libcurl4-gnutls-dev subversion
16
16
  - run: npm install
17
17
  - run: npm run doc
18
18
 
@@ -25,7 +25,7 @@ jobs:
25
25
  - uses: actions/checkout@v2
26
26
  - uses: actions/setup-node@v2
27
27
  with:
28
- node-version: '14'
28
+ node-version: '18'
29
29
 
30
30
  - run: npm install --arch=ia32
31
31
  - run: npm run doc
package/API.md CHANGED
@@ -692,6 +692,7 @@ An object representation of a SWORD module.
692
692
  | shortCopyright | <code>String</code> | The short copyright information of the SWORD module |
693
693
  | version | <code>String</code> | The version of the SWORD module |
694
694
  | lastUpdate | <code>String</code> | The date of the last version update of the SWORD module |
695
+ | history | <code>Array.&lt;String&gt;</code> | The history of the SWORD module. A list of strings where each looks like this: <version>=<version-info> |
695
696
  | category | <code>String</code> | The category of the SWORD module |
696
697
  | repository | <code>String</code> | The repository of the SWORD module |
697
698
  | about | <code>String</code> | Extended description of the SWORD module |
package/index.js CHANGED
@@ -43,6 +43,7 @@ const nodeSwordInterfaceModule = require('./build/Release/node_sword_interface.n
43
43
  * @property {String} shortCopyright - The short copyright information of the SWORD module
44
44
  * @property {String} version - The version of the SWORD module
45
45
  * @property {String} lastUpdate - The date of the last version update of the SWORD module
46
+ * @property {String[]} history - The history of the SWORD module. A list of strings where each looks like this: <version>=<version-info>
46
47
  * @property {String} category - The category of the SWORD module
47
48
  * @property {String} repository - The repository of the SWORD module
48
49
  * @property {String} about - Extended description of the SWORD module
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-sword-interface",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "Javascript (N-API) interface to SWORD library",
5
5
  "keywords": [
6
6
  "C++",
@@ -128,6 +128,10 @@ void NapiSwordHelper::swordModuleToNapiObject(const Napi::Env& env, SWModule* sw
128
128
 
129
129
  object["hasGreekStrongsKeys"] = Napi::Boolean::New(env, this->_moduleHelper.moduleHasFeature(swModule, "GreekDef"));
130
130
  object["hasHebrewStrongsKeys"] = Napi::Boolean::New(env, this->_moduleHelper.moduleHasFeature(swModule, "HebrewDef"));
131
+
132
+ // Add module history entries
133
+ std::vector<std::string> historyEntries = this->_moduleHelper.getModuleHistoryEntries(swModule);
134
+ object["history"] = this->getNapiArrayFromStringVector(env, historyEntries);
131
135
  }
132
136
 
133
137
  Napi::String NapiSwordHelper::getConfigEntry(sword::SWModule* swModule, std::string key, const Napi::Env& env)
@@ -198,6 +198,45 @@ map<string, int> ModuleHelper::getAbsoluteVerseNumberMap(SWModule* module, vecto
198
198
  return absoluteVerseNumbers;
199
199
  }
200
200
 
201
+ vector<string> ModuleHelper::getModuleConfigEntries(sword::SWModule* module)
202
+ {
203
+ vector<string> configEntries;
204
+
205
+ const ConfigEntMap& config = module->getConfig();
206
+
207
+ for (ConfigEntMap::const_iterator it = config.begin(); it != config.end(); ++it) {
208
+ configEntries.push_back(it->first.c_str());
209
+ }
210
+
211
+ return configEntries;
212
+ }
213
+
214
+ std::vector<std::string> ModuleHelper::getModuleHistoryEntries(sword::SWModule* module)
215
+ {
216
+ std::vector<std::string> historyEntries;
217
+
218
+ if (module == 0) {
219
+ cerr << "module is 0! Cannot get history entries!" << endl;
220
+ return historyEntries;
221
+ }
222
+
223
+ // Get all config entries
224
+ const ConfigEntMap& config = module->getConfig();
225
+
226
+ // Find all entries that start with "History_"
227
+ for (ConfigEntMap::const_iterator it = config.begin(); it != config.end(); ++it) {
228
+ std::string key = it->first.c_str();
229
+ if (key.find("History_") == 0) {
230
+ std::string value = it->second.c_str();
231
+ std::string version = key.substr(8); // Remove "History_" prefix
232
+ std::string entry = version + "=" + value;
233
+ historyEntries.push_back(entry);
234
+ }
235
+ }
236
+
237
+ return historyEntries;
238
+ }
239
+
201
240
  bool ModuleHelper::isBrokenMarkupModule(std::string moduleName)
202
241
  {
203
242
  return std::find(this->_brokenMarkupModules.begin(),
@@ -50,6 +50,8 @@ public:
50
50
  std::map<std::string, int> getAbsoluteVerseNumberMap(sword::SWModule* module, std::vector<std::string> bookList={});
51
51
  bool isBrokenMarkupModule(std::string moduleName);
52
52
  bool isInconsistentClosingEndDivModule(std::string moduleName);
53
+ std::vector<std::string> getModuleConfigEntries(sword::SWModule* module);
54
+ std::vector<std::string> getModuleHistoryEntries(sword::SWModule* module);
53
55
 
54
56
  private:
55
57
  bool moduleHasKeyValuePair(sword::SWModule* module, std::string key, std::string value);
@@ -101,21 +101,24 @@ inline bool isDisallowedCharacter(char c) {
101
101
  return disallowedPunctuation.find(c) != string::npos;
102
102
  }
103
103
 
104
- vector<Verse> ModuleSearch::getModuleSearchResults(string moduleName,
105
- string searchTerm,
106
- SearchType searchType,
107
- SearchScope searchScope,
108
- bool isCaseSensitive,
109
- bool useExtendedVerseBoundaries,
110
- bool filterOnWordBoundaries)
104
+ bool ModuleSearch::validateSearchParameters(SWModule* module, const string& searchTerm)
105
+ {
106
+ if (module == 0) {
107
+ cerr << "ModuleSearch::getModuleSearchResults: getLocalModule returned zero pointer for " << _currentModuleName << endl;
108
+ return false;
109
+ }
110
+
111
+ if (searchTerm == "") {
112
+ cerr << "ModuleSearch::getModuleSearchResults: cannot work with empty search term!" << endl;
113
+ return false;
114
+ }
115
+
116
+ return true;
117
+ }
118
+
119
+ int ModuleSearch::getSearchFlags(bool isCaseSensitive, bool useExtendedVerseBoundaries)
111
120
  {
112
- this->_currentModuleName = moduleName;
113
- SWModule* module = this->_moduleStore.getSearchSwMgr()->getModule(moduleName.c_str());
114
- ListKey listKey;
115
- SWKey* scope = 0;
116
121
  int flags = 0;
117
- vector<Verse> searchResults;
118
- vector<string> searchResultReferences;
119
122
 
120
123
  if (!isCaseSensitive) {
121
124
  flags |= REG_ICASE;
@@ -125,126 +128,207 @@ vector<Verse> ModuleSearch::getModuleSearchResults(string moduleName,
125
128
  flags |= SWModule::SEARCHFLAG_STRICTBOUNDARIES;
126
129
  }
127
130
 
128
- if (module == 0) {
129
- cerr << "ModuleSearch::getModuleSearchResults: getLocalModule returned zero pointer for " << moduleName << endl;
130
- } else if (searchTerm == "") {
131
- cerr << "ModuleSearch::getModuleSearchResults: cannot work with empty search term!" << endl;
132
- } else {
133
- ListKey scopeKey;
131
+ return flags;
132
+ }
134
133
 
135
- if (searchScope != SearchScope::BIBLE) {
136
- scopeKey = this->getScopeKey(module, searchScope);
137
- scope = &scopeKey;
134
+ string ModuleSearch::prepareStrongsSearchTerm(string searchTerm, SearchType searchType, SWModule* module)
135
+ {
136
+ string strongsNumber = searchTerm.substr(1, searchTerm.size());
137
+
138
+ if (searchTerm[0] == 'H') {
139
+ if (this->_textProcessor.moduleHasStrongsPaddedZeroPrefixes(module)) {
140
+ string paddedStrongsNumber = this->_textProcessor.padStrongsNumber(strongsNumber);
141
+ searchTerm = "H" + paddedStrongsNumber;
142
+ } else if (this->_textProcessor.moduleHasStrongsZeroPrefixes(module)) {
143
+ searchTerm = "H0" + strongsNumber;
138
144
  }
145
+ } else if (searchTerm[0] == 'G') {
146
+ if (this->_textProcessor.moduleHasStrongsPaddedZeroPrefixes(module)) {
147
+ string paddedStrongsNumber = this->_textProcessor.padStrongsNumber(strongsNumber);
148
+ searchTerm = "G" + paddedStrongsNumber;
149
+ }
150
+ }
139
151
 
140
- bool hasStrongs = this->_moduleHelper.moduleHasGlobalOption(module, "Strongs");
141
- bool moduleMarkupIsBroken = this->_moduleHelper.isBrokenMarkupModule(moduleName);
142
- bool hasInconsistentClosingEndDivs = this->_moduleHelper.isInconsistentClosingEndDivModule(moduleName);
143
-
144
- if (searchType == SearchType::strongsNumber) {
145
- if (!hasStrongs) {
146
- return searchResults;
147
- }
148
-
149
- string strongsNumber = searchTerm.substr(1, searchTerm.size());
152
+ return "Word//Lemma./" + searchTerm;
153
+ }
150
154
 
151
- if (searchTerm[0] == 'H') {
152
- if (this->_textProcessor.moduleHasStrongsPaddedZeroPrefixes(module)) {
153
- string paddedStrongsNumber = this->_textProcessor.padStrongsNumber(strongsNumber);
154
- searchTerm = "H" + paddedStrongsNumber;
155
- } else if (this->_textProcessor.moduleHasStrongsZeroPrefixes(module)) {
156
- searchTerm = "H0" + strongsNumber;
157
- }
158
- } else if (searchTerm[0] == 'G') {
159
- if (this->_textProcessor.moduleHasStrongsPaddedZeroPrefixes(module)) {
160
- string paddedStrongsNumber = this->_textProcessor.padStrongsNumber(strongsNumber);
161
- searchTerm = "G" + paddedStrongsNumber;
155
+ bool ModuleSearch::phraseSequenceCheck(const vector<string>& words, const vector<string>& searchWords)
156
+ {
157
+ // For single word searches, sequence doesn't matter
158
+ if (searchWords.size() <= 1) {
159
+ return true;
160
+ }
161
+
162
+ // Iterate through potential starting positions in the verse
163
+ for (size_t startPos = 0; startPos < words.size(); startPos++) {
164
+ // Check if this is a match for the first word
165
+ if (words[startPos] == searchWords[0]) {
166
+ // See if subsequent words match in sequence
167
+ bool sequenceMatch = true;
168
+ for (size_t i = 1; i < searchWords.size(); i++) {
169
+ size_t nextPos = startPos + i;
170
+ // Make sure we don't go beyond the verse text
171
+ if (nextPos >= words.size() || words[nextPos] != searchWords[i]) {
172
+ sequenceMatch = false;
173
+ break;
162
174
  }
163
175
  }
164
-
165
- flags |= SWModule::SEARCHFLAG_MATCHWHOLEENTRY;
166
- searchTerm = "Word//Lemma./" + searchTerm;
176
+
177
+ if (sequenceMatch) {
178
+ return true;
179
+ }
167
180
  }
181
+ }
182
+
183
+ return false;
184
+ }
185
+
186
+ vector<string> ModuleSearch::getSearchResultReferences(SWModule* module, ListKey& listKey,
187
+ const string& searchTerm, SearchType searchType,
188
+ bool isCaseSensitive, bool filterOnWordBoundaries,
189
+ bool hasStrongs, bool hasInconsistentClosingEndDivs,
190
+ bool moduleMarkupIsBroken)
191
+ {
192
+ vector<string> filteredReferences;
193
+
194
+ // Disable markup before filtering on word boundaries
195
+ this->_textProcessor.disableMarkup();
168
196
 
169
- // Perform search
170
- listKey = module->search(searchTerm.c_str(), int(searchType), flags, scope, 0, internalModuleSearchProgressCB);
197
+ // Make the search term lower case if case sensitivity is not required
198
+ string lowerCaseSearchTerm = searchTerm;
199
+ if (!isCaseSensitive) {
200
+ std::transform(lowerCaseSearchTerm.begin(), lowerCaseSearchTerm.end(), lowerCaseSearchTerm.begin(), ::tolower);
201
+ }
171
202
 
172
- // Disable markup before filtering on word boundaries
173
- this->_textProcessor.disableMarkup();
203
+ // Split the search term into individual words
204
+ vector<string> searchWords = StringHelper::split(lowerCaseSearchTerm, " ");
174
205
 
175
- vector<string> filteredReferences;
206
+ while (!listKey.popError()) {
207
+ module->setKey(listKey.getElement());
208
+ string verseText = this->_textProcessor.getCurrentVerseText(module,
209
+ hasStrongs,
210
+ hasInconsistentClosingEndDivs,
211
+ moduleMarkupIsBroken);
176
212
 
177
- // Make the search term lower case if case sensitivity is not required
178
- string lowerCaseSearchTerm = searchTerm;
213
+ // Make the verse text lower case if case sensitivity is not required
214
+ string lowerCaseVerseText = verseText;
179
215
  if (!isCaseSensitive) {
180
- std::transform(lowerCaseSearchTerm.begin(), lowerCaseSearchTerm.end(), lowerCaseSearchTerm.begin(), ::tolower);
216
+ std::transform(lowerCaseVerseText.begin(), lowerCaseVerseText.end(), lowerCaseVerseText.begin(), ::tolower);
181
217
  }
182
218
 
183
- // Split the search term into individual words
184
- vector<string> searchWords = StringHelper::split(lowerCaseSearchTerm, " ");
185
-
186
219
  // Filter verses based on word boundaries
187
- while (!listKey.popError()) {
188
- module->setKey(listKey.getElement());
189
- string verseText = this->_textProcessor.getCurrentVerseText(module,
190
- hasStrongs,
191
- hasInconsistentClosingEndDivs,
192
- moduleMarkupIsBroken);
193
-
194
- // Make the verse text lower case if case sensitivity is not required
195
- string lowerCaseVerseText = verseText;
196
- if (!isCaseSensitive) {
197
- std::transform(lowerCaseVerseText.begin(), lowerCaseVerseText.end(), lowerCaseVerseText.begin(), ::tolower);
220
+ if (filterOnWordBoundaries) {
221
+ // Replace disallowed characters with spaces
222
+ std::replace_if(lowerCaseVerseText.begin(), lowerCaseVerseText.end(),
223
+ [](char c) { return isDisallowedCharacter(c); }, ' ');
224
+
225
+ vector<string> words = StringHelper::split(lowerCaseVerseText, " ");
226
+
227
+ // Check if all parts of the search term match any word in the verse
228
+ bool allPartsMatch = true;
229
+ for (const auto& searchWord : searchWords) {
230
+ if (std::find(words.begin(), words.end(), searchWord) == words.end()) {
231
+ allPartsMatch = false;
232
+ break;
233
+ }
198
234
  }
199
235
 
200
- if (filterOnWordBoundaries) {
201
- // Replace disallowed characters with spaces
202
- std::replace_if(lowerCaseVerseText.begin(), lowerCaseVerseText.end(),
203
- [](char c) { return isDisallowedCharacter(c); }, ' ');
204
-
205
- vector<string> words = StringHelper::split(lowerCaseVerseText, " ");
236
+ bool correctOrder = true;
206
237
 
207
- // Check if all parts of the search term match any word in the verse
208
- bool allPartsMatch = true;
209
- for (const auto& searchWord : searchWords) {
210
- if (std::find(words.begin(), words.end(), searchWord) == words.end()) {
211
- allPartsMatch = false;
212
- break;
213
- }
214
- }
238
+ if (searchType == SearchType::phrase && allPartsMatch) {
239
+ correctOrder = phraseSequenceCheck(words, searchWords);
240
+ }
215
241
 
216
- if (allPartsMatch) {
217
- filteredReferences.push_back(module->getKey()->getShortText());
218
- }
219
- } else {
242
+ if (allPartsMatch && correctOrder) {
220
243
  filteredReferences.push_back(module->getKey()->getShortText());
221
244
  }
222
-
223
- listKey++;
245
+ } else {
246
+ // If not filtering on word boundaries, we simply push the reference
247
+ filteredReferences.push_back(module->getKey()->getShortText());
224
248
  }
225
249
 
226
- // Enable markup again after word boundary filtering
227
- this->_textProcessor.enableMarkup();
250
+ listKey++;
251
+ }
252
+
253
+ // Enable markup again after word boundary filtering
254
+ this->_textProcessor.enableMarkup();
255
+
256
+ return filteredReferences;
257
+ }
258
+
259
+ vector<Verse> ModuleSearch::createVersesFromReferences(SWModule* module, const vector<string>& references,
260
+ bool hasStrongs, bool hasInconsistentClosingEndDivs,
261
+ bool moduleMarkupIsBroken)
262
+ {
263
+ vector<Verse> verses;
264
+ map<string, int> absoluteVerseNumbers = this->_moduleHelper.getAbsoluteVerseNumberMap(module);
265
+
266
+ for (const auto& reference : references) {
267
+ module->setKey(reference.c_str());
268
+ string verseText = this->_textProcessor.getCurrentVerseText(module,
269
+ hasStrongs,
270
+ hasInconsistentClosingEndDivs,
271
+ moduleMarkupIsBroken);
272
+
273
+ Verse currentVerse;
274
+ currentVerse.reference = reference;
275
+ currentVerse.absoluteVerseNumber = absoluteVerseNumbers[reference];
276
+ currentVerse.content = verseText;
277
+
278
+ verses.push_back(currentVerse);
279
+ }
280
+
281
+ return verses;
282
+ }
283
+
284
+ vector<Verse> ModuleSearch::getModuleSearchResults(string moduleName,
285
+ string searchTerm,
286
+ SearchType searchType,
287
+ SearchScope searchScope,
288
+ bool isCaseSensitive,
289
+ bool useExtendedVerseBoundaries,
290
+ bool filterOnWordBoundaries)
291
+ {
292
+ this->_currentModuleName = moduleName;
293
+ SWModule* module = this->_moduleStore.getSearchSwMgr()->getModule(moduleName.c_str());
294
+ ListKey listKey;
295
+ SWKey* scope = 0;
296
+ vector<Verse> searchResults;
297
+
298
+ if (!validateSearchParameters(module, searchTerm)) {
299
+ return searchResults;
300
+ }
228
301
 
229
- map<string, int> absoluteVerseNumbers = this->_moduleHelper.getAbsoluteVerseNumberMap(module);
302
+ int flags = getSearchFlags(isCaseSensitive, useExtendedVerseBoundaries);
230
303
 
231
- // Populate searchResults vector
232
- for (const auto& reference : filteredReferences) {
233
- module->setKey(reference.c_str());
234
- string verseText = this->_textProcessor.getCurrentVerseText(module,
235
- hasStrongs,
236
- hasInconsistentClosingEndDivs,
237
- moduleMarkupIsBroken);
304
+ ListKey scopeKey;
238
305
 
239
- Verse currentVerse;
240
- currentVerse.reference = reference;
241
- currentVerse.absoluteVerseNumber = absoluteVerseNumbers[reference];
242
- currentVerse.content = verseText;
306
+ if (searchScope != SearchScope::BIBLE) {
307
+ scopeKey = this->getScopeKey(module, searchScope);
308
+ scope = &scopeKey;
309
+ }
243
310
 
244
- searchResults.push_back(currentVerse);
245
- }
311
+ bool hasStrongs = this->_moduleHelper.moduleHasGlobalOption(module, "Strongs");
312
+ bool moduleMarkupIsBroken = this->_moduleHelper.isBrokenMarkupModule(moduleName);
313
+ bool hasInconsistentClosingEndDivs = this->_moduleHelper.isInconsistentClosingEndDivModule(moduleName);
314
+
315
+ if (searchType == SearchType::strongsNumber && hasStrongs) {
316
+ searchTerm = prepareStrongsSearchTerm(searchTerm, searchType, module);
317
+ flags |= SWModule::SEARCHFLAG_MATCHWHOLEENTRY;
246
318
  }
247
319
 
320
+ // Perform search
321
+ listKey = module->search(searchTerm.c_str(), int(searchType), flags, scope, 0, internalModuleSearchProgressCB);
322
+
323
+ // Get search result references while considering the word boundary filter option
324
+ vector<string> filteredReferences = getSearchResultReferences(module, listKey, searchTerm, searchType,
325
+ isCaseSensitive, filterOnWordBoundaries,
326
+ hasStrongs, hasInconsistentClosingEndDivs,
327
+ moduleMarkupIsBroken);
328
+
329
+ searchResults = createVersesFromReferences(module, filteredReferences, hasStrongs,
330
+ hasInconsistentClosingEndDivs, moduleMarkupIsBroken);
331
+
248
332
  this->_currentModuleName = "";
249
333
 
250
334
  return searchResults;
@@ -67,6 +67,19 @@ public:
67
67
 
68
68
  private:
69
69
  sword::ListKey getScopeKey(sword::SWModule* module, SearchScope scope);
70
+
71
+ bool validateSearchParameters(sword::SWModule* module, const std::string& searchTerm);
72
+ int getSearchFlags(bool isCaseSensitive, bool useExtendedVerseBoundaries);
73
+ std::string prepareStrongsSearchTerm(std::string searchTerm, SearchType searchType, sword::SWModule* module);
74
+ std::vector<std::string> getSearchResultReferences(sword::SWModule* module, sword::ListKey& listKey,
75
+ const std::string& searchTerm, SearchType searchType,
76
+ bool isCaseSensitive, bool filterOnWordBoundaries,
77
+ bool hasStrongs, bool hasInconsistentClosingEndDivs,
78
+ bool moduleMarkupIsBroken);
79
+ std::vector<Verse> createVersesFromReferences(sword::SWModule* module, const std::vector<std::string>& references,
80
+ bool hasStrongs, bool hasInconsistentClosingEndDivs,
81
+ bool moduleMarkupIsBroken);
82
+ bool phraseSequenceCheck(const std::vector<std::string>& words, const std::vector<std::string>& searchWords);
70
83
 
71
84
  ModuleStore& _moduleStore;
72
85
  ModuleHelper& _moduleHelper;