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