/** GSXibKeyedUnarchiver.m These are templates for use with OSX XIB files. These classes are the templates and other things which are needed for reading XIB files. Copyright (C) 2010, 2012, 2017 Free Software Foundation, Inc. File created by Marcian Lytwyn on 12/30/16 from original code by: Author: Fred Kiefer Date: March 2010 Author: Gregory John Casamento Date: 2012 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. If not, see or write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import "GNUstepGUI/GSXibKeyedUnarchiver.h" #import "GNUstepGUI/GSXibElement.h" #import "GNUstepGUI/GSNibLoading.h" #import "GSXib5KeyedUnarchiver.h" @implementation GSXibKeyedUnarchiver + (BOOL) checkXib5: (NSData *)data { #if GNUSTEP_BASE_HAVE_LIBXML // Ensure we have a XIB 5 version...first see if we can parse the XML... NSXMLDocument *document = [[NSXMLDocument alloc] initWithData: data options: 0 error: NULL]; if (document == nil) { return NO; } else { // Test to see if this is an Xcode 5 XIB... NSArray *documentNodes = [document nodesForXPath: @"/document" error: NULL]; // Need at LEAST ONE document node...we should find something a bit more // specific to check here... return [documentNodes count] != 0; } #else // We now default to checking XIB 5 versions return YES; #endif } + (NSKeyedUnarchiver *) unarchiverForReadingWithData: (NSData *)data { NSKeyedUnarchiver *unarchiver = nil; if ([self checkXib5: data]) { unarchiver = [[GSXib5KeyedUnarchiver alloc] initForReadingWithData: data]; } else { unarchiver = [[GSXibKeyedUnarchiver alloc] initForReadingWithData: data]; } return AUTORELEASE(unarchiver); } - (NSString *) _substituteClassForClassName: (NSString *)className { NSString *result = className; NSDictionary *dict = [_customClasses objectForKey: className]; if (dict != nil) { result = [dict objectForKey: @"parentClassName"]; } return result; } - (NSData *) _preProcessXib: (NSData *)data { NSData *result = data; #if GNUSTEP_BASE_HAVE_LIBXML NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:data options:0 error:NULL]; if (document == nil) { NSLog(@"%s:DOCUMENT IS NIL: %@\n", __PRETTY_FUNCTION__, document); } else { NSArray *customClassNodes = [document nodesForXPath:@"//dictionary[@key=\"flattenedProperties\"]/" @"string[contains(@key,\"CustomClassName\")]" error:NULL]; NSMutableDictionary *customClassDict = [NSMutableDictionary dictionary]; if (customClassNodes) { NSDebugLLog(@"PREXIB", @"%s:customClassNodes: %@\n", __PRETTY_FUNCTION__, customClassNodes); // Replace the NSXMLNodes with a dictionary... NSInteger index = 0; for (index = 0; index < [customClassNodes count]; ++index) { id node = [customClassNodes objectAtIndex:index]; if ([node isMemberOfClass:[NSXMLElement class]]) { NSString *key = [[node attributeForName:@"key"] stringValue]; if ([key rangeOfString:@"CustomClassName"].location != NSNotFound) { [customClassDict setObject:[node stringValue] forKey:key]; } } } } else { NSArray *flatProps = [document nodesForXPath:@"//object[@key=\"flattenedProperties\"]" error:NULL]; if ([flatProps count] == 1) { NSInteger index = 0; NSArray *xmlKeys = [[flatProps objectAtIndex:0] nodesForXPath: @"//object[@key=\"flattenedProperties\"]/object[@key=\"dict.sortedKeys\"]/*" error:NULL]; NSArray *xmlObjs = [[flatProps objectAtIndex:0] nodesForXPath: @"//object[@key=\"flattenedProperties\"]/object[@key=\"dict.values\"]/*" error:NULL]; if ([xmlKeys count] != [xmlObjs count]) { NSLog(@"%s:keys to objs count mismatch - keys: %d objs: %d\n", __PRETTY_FUNCTION__, (int)[xmlKeys count], (int)[xmlObjs count]); } else { for (index = 0; index < [xmlKeys count]; ++index) { id key = [[xmlKeys objectAtIndex:index] stringValue]; if ([key rangeOfString:@"CustomClassName"].location != NSNotFound) { NSString *cn = [[xmlObjs objectAtIndex: index] stringValue]; // className [customClassDict setObject:cn forKey:key]; // // If we are in IB/Gorm build the custom classes map so that we don't instantiate // classes which don't exist (yet) in IB/Gorm. This allows editing of the model // in IB/Gorm. If we are in the live app, don't bother as it's a waste of memory. // if ([NSClassSwapper isInInterfaceBuilder] == YES) { NSUInteger idx = [key rangeOfString: @"."].location; if (idx != NSNotFound) // unlikely to be NSNotFound... { NSString *xpath = [NSString stringWithFormat: @"//object[@class=\"IBClassDescriber\"]" @"//string[@key=\"className\"][text()=\"%@\"]" @"/../string[@key=\"superclassName\"]", cn]; NSArray *descriptionObjs = [document nodesForXPath: xpath error: NULL]; if ([descriptionObjs count] > 0) { NSString *num = [key substringToIndex: idx]; NSXMLNode *descriptionNode = [descriptionObjs objectAtIndex: 0]; NSString *sc = [descriptionNode stringValue]; // superclassName NSString *refXPath = [NSString stringWithFormat: @"//object[@class=\"IBMutableOrderedSet\"][@key=\"objectRecords\"]" @"/object/object[@class=\"IBObjectRecord\"]/int[@key=\"objectID\"]" @"[text()=\"%@\"]/../reference[@key=\"object\"]/@ref", num]; NSArray *refNodes = [document nodesForXPath: refXPath error: NULL]; if ([refNodes count] > 0) { NSXMLElement *refNode = [refNodes objectAtIndex: 0]; NSString *refId = [refNode stringValue]; [self createCustomClassRecordForId: refId withParentClass: sc forCustomClass: cn]; } } } } } } } } } NSDebugLLog(@"PREXIB", @"%s:customClassDict: %@\n", __PRETTY_FUNCTION__, customClassDict); if ([customClassDict count] > 0) { NSArray *objectRecords = nil; NSEnumerator *en = [[customClassDict allKeys] objectEnumerator]; NSString *key = nil; while ((key = [en nextObject]) != nil) { NSString *keyValue = [key stringByReplacingOccurrencesOfString:@".CustomClassName" withString:@""]; NSString *className = [customClassDict objectForKey: key]; NSString *objectRecordXpath = nil; objectRecordXpath = [NSString stringWithFormat: @"//object[@class=\"IBObjectRecord\"]/" @"int[@key=\"objectID\"][text()=\"%@\"]/../reference", keyValue]; objectRecords = [document nodesForXPath: objectRecordXpath error: NULL]; if (objectRecords == nil) { // If that didn't work then it could be a 4.6+ XIB... objectRecordXpath = [NSString stringWithFormat: @"//object[@class=\"IBObjectRecord\"]/" @"string[@key=\"id\"][text()=\"%@\"]/../reference", keyValue]; objectRecords = [document nodesForXPath: objectRecordXpath error: NULL]; } NSString *refId = nil; if ([objectRecords count] > 0) { id record = nil; NSEnumerator *oen = [objectRecords objectEnumerator]; while ((record = [oen nextObject]) != nil) { if ([record isMemberOfClass:[NSXMLElement class]]) { if([[[record attributeForName:@"key"] stringValue] isEqualToString:@"object"]) { NSArray *classNodes = nil; id classNode = nil; NSString *refXpath = nil; refId = [[record attributeForName:@"ref"] stringValue]; refXpath = [NSString stringWithFormat:@"//object[@id=\"%@\"]",refId]; classNodes = [document nodesForXPath:refXpath error:NULL]; if([classNodes count] > 0) { id classAttr = nil; Class cls = nil; // If we are in the interface builder app, do not replace // the existing classes with their custom subclasses. NSString *clsName = className; if ([NSClassSwapper isInInterfaceBuilder] == YES) { clsName = [self _substituteClassForClassName: className]; } cls = NSClassFromString(clsName); classNode = [classNodes objectAtIndex:0]; classAttr = [classNode attributeForName:@"class"]; [classAttr setStringValue: className]; if (cls != nil) { if ([cls respondsToSelector:@selector(cellClass)]) { NSArray *cellNodes = nil; id cellNode = nil; id cellClass = [cls cellClass]; NSString *cellXpath = [NSString stringWithFormat: @"//object[@id=\"%@\"]/object[@key=\"NSCell\"]",refId]; cellNodes = [document nodesForXPath:cellXpath error:NULL]; if ([cellNodes count] > 0) { NSString *cellClassString = NSStringFromClass(cellClass); id cellAttr = nil; cellNode = [cellNodes objectAtIndex:0]; cellAttr = [cellNode attributeForName:@"class"]; [cellAttr setStringValue: cellClassString]; } } } } } } } } } } result = [document XMLData]; RELEASE(document); } #endif return result; } - (void) _initCommon { objects = [[NSMutableDictionary alloc] init]; stack = [[NSMutableArray alloc] init]; decoded = [[NSMutableDictionary alloc] init]; } - (id) initForReadingWithData: (NSData*)data { #if GNUSTEP_BASE_HAVE_LIBXML NSXMLParser *theParser; NSData *theData = data; // Dictionary which contains custom class information for Gorm/IB. _customClasses = [[NSMutableDictionary alloc] init]; theData = [self _preProcessXib: data]; if (theData == nil) { return nil; } // Initialize... [self _initCommon]; theParser = [[NSXMLParser alloc] initWithData: theData]; [theParser setDelegate: self]; NS_DURING { // Parse the XML data [theParser parse]; } NS_HANDLER { NSLog(@"Exception occurred while parsing Xib: %@",[localException reason]); DESTROY(self); } NS_ENDHANDLER DESTROY(theParser); #endif return self; } - (void) dealloc { DESTROY(objects); DESTROY(stack); DESTROY(decoded); DESTROY(_customClasses); [super dealloc]; } - (void) parser: (NSXMLParser*)parser foundCharacters: (NSString*)string { [currentElement setValue: string]; } - (void) parser: (NSXMLParser*)parser didStartElement: (NSString*)elementName namespaceURI: (NSString*)namespaceURI qualifiedName: (NSString*)qualifiedName attributes: (NSDictionary*)attributeDict { GSXibElement *element = [[GSXibElement alloc] initWithType: elementName andAttributes: attributeDict]; NSString *key = [attributeDict objectForKey: @"key"]; NSString *ref = [attributeDict objectForKey: @"id"]; // FIXME: We should use proper memory management here AUTORELEASE(element); if (key != nil) { [currentElement setElement: element forKey: key]; } else { // For Arrays [currentElement addElement: element]; } if (ref != nil) { [objects setObject: element forKey: ref]; } if (![@"archive" isEqualToString: elementName] && ![@"data" isEqualToString: elementName]) { // only used for the root element // push [stack addObject: currentElement]; } if (![@"archive" isEqualToString: elementName]) { currentElement = element; } } - (void) parser: (NSXMLParser*)parser didEndElement: (NSString*)elementName namespaceURI: (NSString*)namespaceURI qualifiedName: (NSString*)qName { if (![@"archive" isEqualToString: elementName] && ![@"data" isEqualToString: elementName]) { // pop currentElement = [stack lastObject]; [stack removeLastObject]; } } - (id) allocObjectForClassName: (NSString*)clsname { Class c = nil; id delegate = [self delegate]; NSString *classname = clsname; if ([NSClassSwapper isInInterfaceBuilder] == YES) { classname = [self _substituteClassForClassName: classname]; } c = [self classForClassName: classname]; if (c == nil) { c = [[self class] classForClassName: classname]; if (c == nil) { c = NSClassFromString(classname); if (c == nil) { c = [delegate unarchiver: self cannotDecodeObjectOfClassName: classname originalClasses: nil]; if (c == nil) { [NSException raise: NSInvalidUnarchiveOperationException format: @"[%@ -%@]: no class for name '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), classname]; } } } } // Create instance. return [c allocWithZone: [self zone]]; } - (BOOL) replaceObject: (id)oldObj withObject: (id)newObj { NSEnumerator *keyEnumerator = [decoded keyEnumerator]; id key; BOOL found = NO; while ((key = [keyEnumerator nextObject]) != nil) { id obj = [decoded objectForKey: key]; if (obj == oldObj) { found = YES; break; } } if (found) { [decoded setObject: newObj forKey: key]; } return found; } - (id) decodeObjectForXib: (GSXibElement*)element forClassName: (NSString*)classname withID: (NSString*)objID { GSXibElement *last; id o, r; id delegate = [self delegate]; // Create instance. o = [self allocObjectForClassName: classname]; // Make sure the object stays around, even when replaced. RETAIN(o); if (objID != nil) [decoded setObject: o forKey: objID]; // push last = currentElement; currentElement = element; r = [o initWithCoder: self]; // pop currentElement = last; if (r != o) { [delegate unarchiver: self willReplaceObject: o withObject: r]; ASSIGN(o, r); if (objID != nil) [decoded setObject: o forKey: objID]; } r = [o awakeAfterUsingCoder: self]; if (r != o) { [delegate unarchiver: self willReplaceObject: o withObject: r]; ASSIGN(o, r); if (objID != nil) [decoded setObject: o forKey: objID]; } if (delegate != nil) { r = [delegate unarchiver: self didDecodeObject: o]; if (r != o) { [delegate unarchiver: self willReplaceObject: o withObject: r]; ASSIGN(o, r); if (objID != nil) [decoded setObject: o forKey: objID]; } } // Balance the retain above RELEASE(o); if (objID != nil) { NSDebugLLog(@"XIB", @"decoded object %@ for id %@", o, objID); } return AUTORELEASE(o); } /* This method is a copy of decodeObjectForXib:forClassName:withKey: The only difference being in the way we decode the object and the missing context switch. */ - (id) decodeDictionaryForXib: (GSXibElement*)element forClassName: (NSString*)classname withID: (NSString*)objID { id o, r; id delegate = [self delegate]; // Create instance. o = [self allocObjectForClassName: classname]; // Make sure the object stays around, even when replaced. RETAIN(o); if (objID != nil) [decoded setObject: o forKey: objID]; r = [o initWithDictionary: [self _decodeDictionaryOfObjectsForElement: element]]; if (r != o) { [delegate unarchiver: self willReplaceObject: o withObject: r]; ASSIGN(o, r); if (objID != nil) [decoded setObject: o forKey: objID]; } r = [o awakeAfterUsingCoder: self]; if (r != o) { [delegate unarchiver: self willReplaceObject: o withObject: r]; ASSIGN(o, r); if (objID != nil) [decoded setObject: o forKey: objID]; } if (delegate != nil) { r = [delegate unarchiver: self didDecodeObject: o]; if (r != o) { [delegate unarchiver: self willReplaceObject: o withObject: r]; ASSIGN(o, r); if (objID != nil) [decoded setObject: o forKey: objID]; } } // Balance the retain above RELEASE(o); if (objID != nil) { NSDebugLLog(@"XIB", @"decoded object %@ for id %@", o, objID); } return AUTORELEASE(o); } - (id) objectForXib: (GSXibElement*)element { NSString *elementName; NSString *objID; if (element == nil) return nil; NSDebugLLog(@"XIB", @"decoding element %@", element); objID = [element attributeForKey: @"id"]; if (objID) { id new = [decoded objectForKey: objID]; if (new != nil) { // The object was already decoded as a reference return new; } } elementName = [element type]; if ([@"object" isEqualToString: elementName]) { NSString *classname = [element attributeForKey: @"class"]; return [self decodeObjectForXib: element forClassName: classname withID: objID]; } else if ([@"string" isEqualToString: elementName]) { id new = [element value]; // Handle newer format as well if ([[element attributeForKey: @"type"] isEqualToString: @"base64-UTF8"] || [[element attributeForKey: @"base64-UTF8"] boolValue]) { NSData *d = [[NSData alloc] initWithBase64EncodedString: new options: NSDataBase64DecodingIgnoreUnknownCharacters]; new = AUTORELEASE([[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]); RELEASE(d); } // empty strings are not nil! if (new == nil) new = @""; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"int" isEqualToString: elementName]) { id new = [NSNumber numberWithInt: [[element value] intValue]]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"double" isEqualToString: elementName]) { id new = [NSNumber numberWithDouble: [[element value] doubleValue]]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"bool" isEqualToString: elementName]) { id new = [NSNumber numberWithBool: [[element value] boolValue]]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"integer" isEqualToString: elementName]) { NSString *value = [element attributeForKey: @"value"]; id new = [NSNumber numberWithInteger: [value integerValue]]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"real" isEqualToString: elementName]) { NSString *value = [element attributeForKey: @"value"]; id new = [NSNumber numberWithFloat: [value floatValue]]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"boolean" isEqualToString: elementName]) { NSString *value = [element attributeForKey: @"value"]; id new = [NSNumber numberWithBool: [value boolValue]]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"point" isEqualToString: elementName]) { NSPoint point = [self decodePointForKey: [element attributeForKey: @"key"]]; id new = [NSValue valueWithPoint: point]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"size" isEqualToString: elementName]) { NSSize size = [self decodeSizeForKey: [element attributeForKey: @"key"]]; id new = [NSValue valueWithSize: size]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"rect" isEqualToString: elementName]) { NSRect rect = [self decodeRectForKey: [element attributeForKey: @"key"]]; id new = [NSValue valueWithRect: rect]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"reference" isEqualToString: elementName]) { NSString *ref = [element attributeForKey: @"ref"]; if (ref == nil) { return nil; } else { id new = [decoded objectForKey: ref]; // FIXME: We need a marker for nil if (new == nil) { //NSLog(@"Decoding reference %@", ref); element = [objects objectForKey: ref]; if (element != nil) { // Decode the real object new = [self objectForXib: element]; } } return new; } } else if ([@"nil" isEqualToString: elementName]) { return nil; } else if ([@"characters" isEqualToString: elementName]) { id new = [element value]; if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"bytes" isEqualToString: elementName]) { id new = AUTORELEASE([[NSData alloc] initWithBase64EncodedString: [element value] options: NSDataBase64DecodingIgnoreUnknownCharacters]); if (objID != nil) [decoded setObject: new forKey: objID]; return new; } else if ([@"array" isEqualToString: elementName]) { NSString *classname = [element attributeForKey: @"class"]; if (classname == nil) { classname = @"NSArray"; } return [self decodeObjectForXib: element forClassName: classname withID: objID]; } else if ([@"dictionary" isEqualToString: elementName]) { NSString *classname = [element attributeForKey: @"class"]; if (classname == nil) { classname = @"NSDictionary"; } return [self decodeDictionaryForXib: element forClassName: classname withID: objID]; } else { //NSLog(@"Unknown element type %@", elementName); } return nil; } - (id) _decodeArrayOfObjectsForKey: (NSString*)aKey { // FIXME: This is wrong but the only way to keep the code for // [NSArray-initWithCoder:] working return [self _decodeArrayOfObjectsForElement: currentElement]; } - (id) _decodeArrayOfObjectsForElement: (GSXibElement*)element { NSArray *values = [element values]; int max = [values count]; id list[max]; int i; for (i = 0; i < max; i++) { list[i] = [self objectForXib: [values objectAtIndex: i]]; if (list[i] == nil) NSLog(@"No object for %@ at index %d", [values objectAtIndex: i], i); } return [NSArray arrayWithObjects: list count: max]; } - (id) _decodeDictionaryOfObjectsForElement: (GSXibElement*)element { NSDictionary *elements = [element elements]; NSEnumerator *en; NSString *key; NSMutableDictionary *dict; dict = [[NSMutableDictionary alloc] init]; en = [elements keyEnumerator]; while ((key = [en nextObject]) != nil) { id obj = [self objectForXib: [elements objectForKey: key]]; if (obj == nil) NSLog(@"No object for %@ at key %@", [elements objectForKey: key], key); else [dict setObject: obj forKey: key]; } return AUTORELEASE(dict); } /* Extension method to decode the object id of an object referenced by its key. */ - (NSString *) decodeReferenceForKey: (NSString*)aKey { GSXibElement *element = [currentElement elementForKey: aKey]; NSString *objID; if (element == nil) return nil; objID = [element attributeForKey: @"id"]; if (objID) { return objID; } objID = [element attributeForKey: @"ref"]; if (objID) { return objID; } return nil; } - (BOOL) containsValueForKey: (NSString*)aKey { GSXibElement *element = [currentElement elementForKey: aKey]; return (element != nil); } - (id) decodeObjectForKey: (NSString*)aKey { GSXibElement *element = [currentElement elementForKey: aKey]; if (element == nil) return nil; return [self objectForXib: element]; } - (BOOL) decodeBoolForKey: (NSString*)aKey { id o = [self decodeObjectForKey: aKey]; if (o != nil) { if (([o isKindOfClass: [NSNumber class]] == YES) || ([o isKindOfClass: [NSString class]] == YES)) { return [o boolValue]; } else { [NSException raise: NSInvalidUnarchiveOperationException format: @"[%@ +%@]: value for key(%@) is '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey, o]; } } return NO; } - (const uint8_t*) decodeBytesForKey: (NSString*)aKey returnedLength: (NSUInteger*)length { id o = [self decodeObjectForKey: aKey]; if (o != nil) { if ([o isKindOfClass: [NSData class]] == YES) { *length = [o length]; return [o bytes]; } else { [NSException raise: NSInvalidUnarchiveOperationException format: @"[%@ +%@]: value for key(%@) is '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey, o]; } } *length = 0; return 0; } - (double) decodeDoubleForKey: (NSString*)aKey { id o = [self decodeObjectForKey: aKey]; if (o != nil) { if (([o isKindOfClass: [NSNumber class]] == YES) || ([o isKindOfClass: [NSString class]] == YES)) { return [o doubleValue]; } else { [NSException raise: NSInvalidUnarchiveOperationException format: @"[%@ +%@]: value for key(%@) is '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey, o]; } } return 0.0; } - (float) decodeFloatForKey: (NSString*)aKey { id o = [self decodeObjectForKey: aKey]; if (o != nil) { if (([o isKindOfClass: [NSNumber class]] == YES) || ([o isKindOfClass: [NSString class]] == YES)) { return [o floatValue]; } else { [NSException raise: NSInvalidUnarchiveOperationException format: @"[%@ +%@]: value for key(%@) is '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey, o]; } } return 0.0; } - (int) decodeIntForKey: (NSString*)aKey { id o = [self decodeObjectForKey: aKey]; if (o != nil) { if (([o isKindOfClass: [NSNumber class]] == YES) || ([o isKindOfClass: [NSString class]] == YES)) { long long l = [o longLongValue]; return l; } else { [NSException raise: NSInvalidUnarchiveOperationException format: @"[%@ +%@]: value for key(%@) is '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey, o]; } } return 0; } - (int32_t) decodeInt32ForKey: (NSString*)aKey { id o = [self decodeObjectForKey: aKey]; if (o != nil) { if (([o isKindOfClass: [NSNumber class]] == YES) || ([o isKindOfClass: [NSString class]] == YES)) { long long l = [o longLongValue]; return l; } else { [NSException raise: NSInvalidUnarchiveOperationException format: @"[%@ +%@]: value for key(%@) is '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey, o]; } } return 0; } - (int64_t) decodeInt64ForKey: (NSString*)aKey { id o = [self decodeObjectForKey: aKey]; if (o != nil) { if (([o isKindOfClass: [NSNumber class]] == YES) || ([o isKindOfClass: [NSString class]] == YES)) { long long l = [o longLongValue]; return l; } else { [NSException raise: NSInvalidUnarchiveOperationException format: @"[%@ +%@]: value for key(%@) is '%@'", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey, o]; } } return 0; } - (NSDictionary *) decoded { return decoded; } - (NSDictionary *) customClasses { return _customClasses; } - (void) createCustomClassRecordForId: (NSString *)theId withParentClass: (NSString *)parentClassName forCustomClass: (NSString *)customClassName { if (theId == nil || customClassName == nil) return; NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: parentClassName, @"parentClassName", theId, @"id",nil]; [_customClasses setObject: dict forKey: customClassName]; } @end