1/*	SwordModule.mm - Sword API wrapper for Modules.
2
3	Copyright 2008 Manfred Bergmann
4	Based on code by Will Thimbleby
5
6	This program is free software; you can redistribute it and/or modify it under the terms of the
7	GNU General Public License as published by the Free Software Foundation version 2.
8
9	This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10	even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11	General Public License for more details. (http://www.gnu.org/licenses/gpl.html)
12*/
13
14#import "ObjCSword_Prefix.pch"
15#import "SwordModule.h"
16#import "SwordManager.h"
17#import "SwordModuleTextEntry.h"
18#import "SwordVerseKey.h"
19#import "SwordBible.h"
20#import "SwordCommentary.h"
21#import "SwordUtil.h"
22
23@interface SwordModule ()
24
25@end
26
27@implementation SwordModule
28
29+ (id)moduleForSWModule:(sword::SWModule *)aModule {
30    return [[SwordModule alloc] initWithSWModule:aModule];
31}
32
33+ (id)moduleForType:(ModuleType)aType swModule:(sword::SWModule *)swModule {
34    SwordModule *sm;
35    if(aType == Bible) {
36        sm = [[SwordBible alloc] initWithSWModule:swModule];
37    } else if(aType == Commentary) {
38        sm = [[SwordCommentary alloc] initWithSWModule:swModule];
39    } else if(aType == Dictionary) {
40        sm = [[SwordDictionary alloc] initWithSWModule:swModule];
41    } else if(aType == Genbook) {
42        sm = [[SwordBook alloc] initWithSWModule:swModule];
43    } else {
44        sm = [[SwordModule alloc] initWithSWModule:swModule];
45    }
46
47    return sm;
48}
49
50+ (ModuleType)moduleTypeForModuleTypeString:(NSString *)typeStr {
51     ModuleType ret = Bible;
52
53    if(typeStr == nil) {
54        ALog(@"have a nil typeStr!");
55        return ret;
56    }
57
58    if([typeStr isEqualToString:SWMOD_TYPES_BIBLES]) {
59        ret = Bible;
60    } else if([typeStr isEqualToString:SWMOD_TYPES_COMMENTARIES]) {
61        ret = Commentary;
62    } else if([typeStr isEqualToString:SWMOD_TYPES_DICTIONARIES]) {
63        ret = Dictionary;
64    } else if([typeStr isEqualToString:SWMOD_TYPES_GENBOOKS]) {
65        ret = Genbook;
66    }
67
68    return ret;
69}
70
71+ (ModuleCategory)moduleCategoryForModuleCategoryString:(NSString *)categoryStr {
72    ModuleCategory ret = NoCategory;
73
74    if(categoryStr == nil) {
75        ALog(@"have a nil categoryStr!");
76        return ret;
77    }
78
79    if([categoryStr isEqualToString:SWMOD_CATEGORY_MAPS]) {
80        ret = Maps;
81    } else if([categoryStr isEqualToString:SWMOD_CATEGORY_IMAGES]) {
82        ret = Images;
83    } else if([categoryStr isEqualToString:SWMOD_CATEGORY_DAILYDEVS]) {
84        ret = DailyDevotion;
85    } else if([categoryStr isEqualToString:SWMOD_CATEGORY_ESSEYS]) {
86        ret = Essays;
87    } else if([categoryStr isEqualToString:SWMOD_CATEGORY_GLOSSARIES]) {
88        ret = Glossary;
89    } else if([categoryStr isEqualToString:SWMOD_CATEGORY_CULTS]) {
90        ret = Cults;
91    }
92
93    return ret;
94}
95
96#pragma mark - Initializer
97
98- (void)mainInit {
99    category = Unset;
100
101    self.type = [SwordModule moduleTypeForModuleTypeString:[self typeString]];
102    self.moduleLock = [[NSRecursiveLock alloc] init];
103    self.indexLock = [[NSLock alloc] init];
104    self.configEntries = [NSMutableDictionary dictionary];
105}
106
107- (id)initWithSWModule:(sword::SWModule *)aModule {
108    self = [super init];
109    if(self) {
110        swModule = aModule;
111
112        [self mainInit];
113    }
114
115    return self;
116}
117
118#pragma mark - Filters
119
120- (void)addRenderFilter:(SwordFilter *)aFilter {
121    swModule->removeRenderFilter([aFilter swFilter]);
122    swModule->addRenderFilter([aFilter swFilter]);
123}
124
125- (void)addStripFilter:(SwordFilter *)aFilter {
126    swModule->addStripFilter([aFilter swFilter]);
127}
128
129#pragma mark - Module access semaphores
130
131- (void)lockModuleAccess {
132    [self.moduleLock lock];
133}
134
135- (void)unlockModuleAccess {
136    [self.moduleLock unlock];
137}
138
139- (NSString *)name {
140    NSString *str = [NSString stringWithCString:swModule->getName() encoding:NSUTF8StringEncoding];
141    if(!str) {
142        str = [NSString stringWithCString:swModule->getName() encoding:NSISOLatin1StringEncoding];
143    }
144    return str;
145}
146
147- (NSString *)descr {
148    NSString *str = [NSString stringWithCString:swModule->getDescription() encoding:NSUTF8StringEncoding];
149    if(!str) {
150        str = [NSString stringWithCString:swModule->getDescription() encoding:NSISOLatin1StringEncoding];
151    }
152    return str;
153}
154
155- (NSString *)lang {
156    NSString *str = [NSString stringWithCString:swModule->getLanguage() encoding:NSUTF8StringEncoding];
157    if(!str) {
158        str = [NSString stringWithCString:swModule->getLanguage() encoding:NSISOLatin1StringEncoding];
159    }
160    return str;
161}
162
163- (NSString *)typeString {
164    NSString *str = [NSString stringWithCString:swModule->getType() encoding:NSUTF8StringEncoding];
165    if(!str) {
166        str = [NSString stringWithCString:swModule->getType() encoding:NSISOLatin1StringEncoding];
167    }
168    return str;
169}
170
171- (NSAttributedString *)fullAboutText {
172    return [[NSAttributedString alloc] initWithString:@""];
173}
174
175- (NSInteger)error {
176    return swModule->popError();
177}
178
179#pragma mark - Conf entries
180
181- (NSString *)categoryString {
182    NSString *cat = self.configEntries[SWMOD_CONFENTRY_CATEGORY];
183    if(cat == nil) {
184        cat = [self configFileEntryForConfigKey:SWMOD_CONFENTRY_CATEGORY];
185        if(cat != nil) {
186            self.configEntries[SWMOD_CONFENTRY_CATEGORY] = cat;
187        }
188    }
189
190    return cat;
191}
192
193- (ModuleCategory)category {
194    if(category == Unset) {
195        category = [SwordModule moduleCategoryForModuleCategoryString:[self categoryString]];
196    }
197    return category;
198}
199
200- (NSString *)cipherKey {
201    NSString *cipherKey = self.configEntries[SWMOD_CONFENTRY_CIPHERKEY];
202    if(cipherKey == nil) {
203        cipherKey = [self configFileEntryForConfigKey:SWMOD_CONFENTRY_CIPHERKEY];
204        if(cipherKey != nil) {
205            self.configEntries[SWMOD_CONFENTRY_CIPHERKEY] = cipherKey;
206        }
207    }
208
209    return cipherKey;
210}
211
212- (NSString *)version {
213    NSString *version = self.configEntries[SWMOD_CONFENTRY_VERSION];
214    if(version == nil) {
215        version = [self configFileEntryForConfigKey:SWMOD_CONFENTRY_VERSION];
216        if(version != nil) {
217            self.configEntries[SWMOD_CONFENTRY_VERSION] = version;
218        }
219    }
220
221    return version;
222}
223
224- (NSString *)minVersion {
225    NSString *minVersion = self.configEntries[SWMOD_CONFENTRY_MINVERSION];
226    if(minVersion == nil) {
227        minVersion = [self configFileEntryForConfigKey:SWMOD_CONFENTRY_MINVERSION];
228        if(minVersion != nil) {
229            self.configEntries[SWMOD_CONFENTRY_MINVERSION] = minVersion;
230        }
231    }
232
233    return minVersion;
234}
235
236/** this might be RTF string  but the return value will be converted to UTF8 */
237- (NSString *)aboutText {
238    NSMutableString *aboutText = self.configEntries[SWMOD_CONFENTRY_ABOUT];
239    if(aboutText == nil) {
240        aboutText = [NSMutableString stringWithString:[self configFileEntryForConfigKey:SWMOD_CONFENTRY_ABOUT]];
241        if(aboutText != nil) {
242			//search & replace the RTF markup:
243			// "\\qc"		- for centering							--->>>  ignore these
244			// "\\pard"		- for resetting paragraph attributes	--->>>  ignore these
245			// "\\par"		- for paragraph breaks					--->>>  honour these
246			// "\\u{num}?"	- for unicode characters				--->>>  honour these
247			[aboutText replaceOccurrencesOfString:@"\\qc" withString:@"" options:0 range:NSMakeRange(0, [aboutText length])];
248			[aboutText replaceOccurrencesOfString:@"\\pard" withString:@"" options:0 range:NSMakeRange(0, [aboutText length])];
249			[aboutText replaceOccurrencesOfString:@"\\par" withString:@"\n" options:0 range:NSMakeRange(0, [aboutText length])];
250
251			NSMutableString *retStr = [@"" mutableCopy];
252			for(NSUInteger i=0; i<[aboutText length]; i++) {
253				unichar c = [aboutText characterAtIndex:i];
254
255				if(c == '\\' && ((i+1) < [aboutText length])) {
256					unichar d = [aboutText characterAtIndex:(i+1)];
257					if (d == 'u') {
258						//we have an unicode character!
259						@try {
260							NSInteger unicodeChar = 0;
261							NSMutableString *unicodeCharString = [@"" mutableCopy];
262							int j = 0;
263							BOOL negative = NO;
264							if ([aboutText characterAtIndex:(i+2)] == '-') {
265								//we have a negative unicode char
266								negative = YES;
267								j++;//skip past the '-'
268							}
269							while(isdigit([aboutText characterAtIndex:(i+2+j)])) {
270								[unicodeCharString appendFormat:@"%C", [aboutText characterAtIndex:(i+2+j)]];
271								j++;
272							}
273							unicodeChar = [unicodeCharString integerValue];
274							if (negative) unicodeChar = 65536 - unicodeChar;
275							i += j+2;
276							[retStr appendFormat:@"%C", (unichar)unicodeChar];
277						}
278						@catch (NSException * e) {
279							[retStr appendFormat:@"%C", c];
280						}
281						//end dealing with the unicode character.
282					} else {
283						[retStr appendFormat:@"%C", c];
284					}
285				} else {
286					[retStr appendFormat:@"%C", c];
287				}
288			}
289
290			aboutText = retStr;
291        } else {
292            aboutText = [NSMutableString string];
293        }
294        self.configEntries[SWMOD_CONFENTRY_ABOUT] = aboutText;
295    }
296
297    return aboutText;
298}
299
300/** this is only relevant for bible and commentaries */
301- (NSString *)versification {
302    return @"";
303}
304
305- (BOOL)isEditable {
306    BOOL ret = NO;
307    NSString *editable = self.configEntries[SWMOD_CONFENTRY_EDITABLE];
308    if(editable == nil) {
309        editable = [self configFileEntryForConfigKey:SWMOD_CONFENTRY_EDITABLE];
310        if(editable != nil) {
311            self.configEntries[SWMOD_CONFENTRY_EDITABLE] = editable;
312        }
313    }
314
315    if(editable) {
316        if([editable isEqualToString:@"YES"]) {
317            ret = YES;
318        }
319    }
320
321    return ret;
322}
323
324- (BOOL)isRTL {
325    BOOL ret = NO;
326    NSString *direction = self.configEntries[SWMOD_CONFENTRY_DIRECTION];
327    if(direction == nil) {
328        direction = [self configFileEntryForConfigKey:SWMOD_CONFENTRY_DIRECTION];
329        if(direction != nil) {
330            self.configEntries[SWMOD_CONFENTRY_DIRECTION] = direction;
331        }
332    }
333
334    if(direction) {
335        if([direction isEqualToString:SW_DIRECTION_RTL]) {
336            ret = YES;
337        }
338    }
339
340    return ret;
341}
342
343- (BOOL)isUnicode {
344    return swModule->isUnicode();
345}
346
347- (BOOL)isEncrypted {
348    BOOL encrypted = YES;
349    if([self cipherKey] == nil) {
350        encrypted = NO;
351    }
352
353    return encrypted;
354}
355
356- (BOOL)isLocked {
357    /** is module locked/has cipherkey config entry but cipherkey entry is empty */
358    BOOL locked = NO;
359    NSString *key = [self cipherKey];
360    if(key != nil) {
361        // check user defaults, that's where we store the entered keys
362        NSDictionary *cipherKeys = [[NSUserDefaults standardUserDefaults] objectForKey:DefaultsModuleCipherKeysKey];
363        if([key length] == 0 && ![[cipherKeys allKeys] containsObject:[self name]]) {
364            locked = YES;
365        }
366    }
367
368    return locked;
369}
370
371// general feature access
372- (BOOL)hasFeature:(NSString *)feature {
373	BOOL has = NO;
374
375	if(swModule->getConfig().has("Feature", [feature UTF8String])) {
376		has = YES;
377    } else if (swModule->getConfig().has("GlobalOptionFilter", [[NSString stringWithFormat:@"GBF%@", feature] UTF8String])) {
378 		has = YES;
379    } else if (swModule->getConfig().has("GlobalOptionFilter", [[NSString stringWithFormat:@"ThML%@", feature] UTF8String])) {
380 		has = YES;
381    } else if (swModule->getConfig().has("GlobalOptionFilter", [[NSString stringWithFormat:@"UTF8%@", feature] UTF8String])) {
382 		has = YES;
383    } else if (swModule->getConfig().has("GlobalOptionFilter", [[NSString stringWithFormat:@"OSIS%@", feature] UTF8String])) {
384 		has = YES;
385    } else if (swModule->getConfig().has("GlobalOptionFilter", [feature UTF8String])) {
386 		has = YES;
387    }
388
389	return has;
390}
391
392- (NSString *)configFileEntryForConfigKey:(NSString *)entryKey {
393	NSString *result = nil;
394
395	[self.moduleLock lock];
396    const char *entryStr = swModule->getConfigEntry([entryKey UTF8String]);
397	if(entryStr) {
398		result = [NSString stringWithUTF8String:entryStr];
399        if(!result) {
400            result = [NSString stringWithCString:entryStr encoding:NSISOLatin1StringEncoding];
401        }
402    }
403	[self.moduleLock unlock];
404
405	return result;
406}
407
408#pragma mark - Module positioning
409
410- (void)incKeyPosition {
411    swModule->increment(1);
412}
413
414- (void)decKeyPosition {
415    swModule->decrement(1);
416}
417
418- (void)setKeyString:(NSString *)aKeyString {
419    swModule->setKey([aKeyString UTF8String]);
420}
421
422- (void)setSwordKey:(SwordKey *)aKey {
423    swModule->getKey()->setPersist(true);
424    swModule->setKey([aKey swKey]);
425}
426
427- (SwordKey *)createKey {
428    sword::SWKey *sk = swModule->createKey();
429    SwordKey *newKey = [SwordKey swordKeyWithSWKey:sk makeCopy:YES];
430    delete sk;
431
432    return newKey;
433}
434
435- (SwordKey *)getKey {
436    return [SwordKey swordKeyWithSWKey:swModule->getKey()];
437}
438
439- (SwordKey *)getKeyCopy {
440    return [SwordKey swordKeyWithSWKey:swModule->getKey() makeCopy:YES];
441}
442
443#pragma mark - Module metadata processing
444
445- (id)attributeValueForParsedLinkData:(NSDictionary *)data {
446    return [self attributeValueForParsedLinkData:data withTextRenderType:TextTypeStripped];
447}
448
449- (id)attributeValueForParsedLinkData:(NSDictionary *)data withTextRenderType:(TextPullType)textType {
450    id ret = nil;
451
452    NSString *passage = data[ATTRTYPE_PASSAGE];
453    if(passage) {
454        passage = [[passage stringByReplacingOccurrencesOfString:@"+" withString:@" "] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
455    }
456    NSString *attrType = data[ATTRTYPE_TYPE];
457    if([attrType isEqualToString:@"n"]) {
458        NSString *footnoteText = [self entryAttributeValueFootnoteOfType:attrType
459                                                              indexValue:data[ATTRTYPE_VALUE]
460                                                                  forKey:[SwordKey swordKeyWithRef:passage]];
461        ret = footnoteText;
462    } else if([attrType isEqualToString:@"x"] || [attrType isEqualToString:@"scriptRef"] || [attrType isEqualToString:@"scripRef"]) {
463        NSString *key = @"";
464        if([attrType isEqualToString:@"x"]) {
465            key = [self entryAttributeValueFootnoteOfType:attrType
466                                               indexValue:data[ATTRTYPE_VALUE]
467                                                   forKey:[SwordKey swordKeyWithRef:passage]];
468        } else {
469            key = [[data[ATTRTYPE_VALUE] stringByReplacingOccurrencesOfString:@"+"
470                                                                   withString:@" "] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
471        }
472        if(textType == TextTypeRendered) {
473            ret = [self renderedTextEntriesForRef:key];
474        } else {
475            ret = [self strippedTextEntriesForRef:key];
476        }
477    }
478
479    return ret;
480}
481
482- (void)setProcessEntryAttributes:(BOOL)flag {
483    swModule->setProcessEntryAttributes(flag);
484}
485
486- (BOOL)processEntryAttributes {
487    return swModule->isProcessEntryAttributes();
488}
489
490- (NSString *)entryAttributeValuePreverse {
491    NSString *ret = [NSString stringWithUTF8String:swModule->getEntryAttributes()["Heading"]["Preverse"]["0"].c_str()];
492
493    return ret;
494}
495
496- (NSString *)entryAttributeValueFootnoteOfType:(NSString *)fnType indexValue:(NSString *)index {
497    NSString *ret = @"";
498    if([fnType isEqualToString:@"x"]) {
499        ret = [NSString stringWithUTF8String:swModule->getEntryAttributes()["Footnote"][[index UTF8String]]["refList"].c_str()];
500    } else if([fnType isEqualToString:@"n"]) {
501        ret = [NSString stringWithUTF8String:swModule->getEntryAttributes()["Footnote"][[index UTF8String]]["body"].c_str()];
502    }
503    return ret;
504}
505
506- (NSArray *)entryAttributeValuesLemma {
507    NSMutableArray *array = [NSMutableArray array];
508
509    swModule->stripText(); // force processing of key, if it hasn't been done already
510
511    // parse entry attributes and look for Lemma (Strong's numbers)
512    sword::AttributeTypeList::iterator words;
513    sword::AttributeList::iterator word;
514    sword::AttributeValue::iterator strongVal;
515    words = swModule->getEntryAttributes().find("Word");
516    if(words != swModule->getEntryAttributes().end()) {
517        for(word = words->second.begin();word != words->second.end(); word++) {
518            strongVal = word->second.find("Lemma");
519            if(strongVal != word->second.end()) {
520                // pass empty "Text" entries
521                if(strongVal->second == "G3588") {
522                    if (word->second.find("Text") == word->second.end())
523                        continue;	// no text? let's skip
524                }
525                NSMutableString *stringValStr = [NSMutableString stringWithUTF8String:(const char *)strongVal->second];
526                if(stringValStr) {
527                    [stringValStr replaceOccurrencesOfString:@"|x-Strongs:" withString:@" " options:0 range:NSMakeRange(0, [stringValStr length])];
528                    [array addObject:stringValStr];
529                }
530            }
531        }
532    }
533    return [NSArray arrayWithArray:array];
534}
535
536- (NSArray *)entryAttributeValuesLemmaNormalized {
537    NSArray *lemmas = [self entryAttributeValuesLemma];
538    // post process all codes and mormalize the number
539    // Hebrew keys should have 5 number digits
540    return [SwordUtil padStrongsNumbers:lemmas];
541}
542
543- (NSString *)entryAttributeValuePreverseForKey:(SwordKey *)aKey {
544    [self.moduleLock lock];
545    [self setSwordKey:aKey];
546    swModule->renderText(); // force processing of key
547    NSString *value = [self entryAttributeValuePreverse];
548    [self.moduleLock unlock];
549    return value;
550}
551
552- (NSString *)entryAttributeValueFootnoteOfType:(NSString *)fnType indexValue:(NSString *)index forKey:(SwordKey *)aKey {
553    [self.moduleLock lock];
554    [self setSwordKey:aKey];
555    swModule->renderText(); // force processing of key
556    NSString *value = [self entryAttributeValueFootnoteOfType:fnType indexValue:index];
557    [self.moduleLock unlock];
558    return value;
559}
560
561
562- (NSString *)description {
563    return [self name];
564}
565
566#pragma mark - Module text access
567
568- (NSString *)renderedText {
569    NSString *ret = @"";
570    ret = [NSString stringWithUTF8String:swModule->renderText()];
571    if(!ret) {
572        ret = [NSString stringWithCString:swModule->renderText() encoding:NSISOLatin1StringEncoding];
573    }
574    return ret;
575}
576
577- (NSString *)renderedTextFromString:(NSString *)aString {
578    NSString *ret = @"";
579    ret = [NSString stringWithUTF8String:swModule->renderText([aString UTF8String])];
580    if(!ret) {
581        ret = [NSString stringWithCString:swModule->renderText([aString UTF8String]) encoding:NSISOLatin1StringEncoding];
582    }
583    return ret;
584}
585
586- (NSString *)strippedText {
587    NSString *ret = @"";
588    ret = [NSString stringWithUTF8String:swModule->stripText()];
589    if(!ret) {
590        ret = [NSString stringWithCString:swModule->stripText() encoding:NSISOLatin1StringEncoding];
591    }
592    return ret;
593}
594
595- (NSString *)strippedTextFromString:(NSString *)aString {
596    NSString *ret = @"";
597    ret = [NSString stringWithUTF8String:swModule->renderText([aString UTF8String])];
598    if(!ret) {
599        ret = [NSString stringWithCString:swModule->renderText([aString UTF8String]) encoding:NSISOLatin1StringEncoding];
600    }
601    return ret;
602}
603
604- (NSArray *)strippedTextEntriesForRef:(NSString *)reference {
605    return [self textEntriesForReference:reference textType:TextTypeStripped];
606}
607
608- (NSArray *)renderedTextEntriesForRef:(NSString *)reference {
609    return [self textEntriesForReference:reference textType:TextTypeRendered];
610}
611
612- (NSArray *)textEntriesForReference:(NSString *)aReference textType:(TextPullType)textType {
613    NSArray *ret = nil;
614
615    SwordModuleTextEntry *entry = [self textEntryForKey:[SwordKey swordKeyWithRef:aReference]
616                                               textType:textType];
617    if(entry) {
618        ret = @[entry];
619    }
620
621    return ret;
622}
623
624- (SwordModuleTextEntry *)renderedTextEntryForRef:(NSString *)reference {
625    return [self textEntryForKeyString:reference textType:TextTypeRendered];
626}
627
628- (SwordModuleTextEntry *)strippedTextEntryForRef:(NSString *)reference {
629    return [self textEntryForKeyString:reference textType:TextTypeStripped];
630}
631
632- (SwordModuleTextEntry *)textEntryForKeyString:(NSString *)aKeyString textType:(TextPullType)aType {
633    return [self textEntryForKey:[SwordKey swordKeyWithRef:aKeyString] textType:aType];
634}
635
636- (SwordModuleTextEntry *)textEntryForKey:(SwordKey *)aKey textType:(TextPullType)aType {
637    SwordModuleTextEntry *ret = nil;
638
639    if(aKey) {
640        [self.moduleLock lock];
641        [self setSwordKey:aKey];
642        if(![self error]) {
643            NSString *txt = @"";
644            if(aType == TextTypeRendered) {
645                txt = [self renderedText];
646            } else {
647                txt = [self strippedText];
648            }
649
650            if(txt) {
651                ret = [SwordModuleTextEntry textEntryForKey:[aKey keyText] andText:txt];
652            } else {
653                ALog(@"Nil key");
654            }
655        }
656        [self.moduleLock unlock];
657    }
658
659    return ret;
660}
661
662- (void)writeEntry:(SwordModuleTextEntry *)anEntry {}
663
664- (long)entryCount {
665    return 0;
666}
667
668- (sword::SWModule *)swModule {
669	return swModule;
670}
671
672@end
673