diff --git a/ChangeLog b/ChangeLog index 74f42c0be..4a509c156 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2004-01-19 Richard Frith-Macdonald + + * Headers/Foundation/NSPropertyList.h: serialisation/deserialisation. + * Source/NSPropertyList.m: serialisation/deserialisation code partly + implemented. + * Headers/Foundation/NSCoder.h: Add keyed coding methods. + * Source/NSCoder.m: ditto + * Headers/Foundation/NSKeyedArchiver.h: new keyed archiver/unarchiver. + * Source/NSKeyedArchiver.m: New MacOS-X compatible class implementation. + * Source/NSKeyedUnarchiver.m: Stub for new class implementation. + * Source/GNUmakefile: build new keyed archiving stuff. + * Source/GSCompatibility.m: fix indentation error in xml output. + improve output of numeric values in xml format. + * Source/Additions/GSMime.m: ([-setContentType:) make argument more + strongly typed. ([-encodeBase64:])tweak buffer size. + 2004-01-18 Richard Frith-Macdonald * Source/NSDistantObject.m: All code conditional on USE_FFCALL should diff --git a/Headers/Foundation/NSCoder.h b/Headers/Foundation/NSCoder.h index 3a804da36..0f50fc232 100644 --- a/Headers/Foundation/NSCoder.h +++ b/Headers/Foundation/NSCoder.h @@ -77,6 +77,137 @@ - (unsigned int) systemVersion; - (unsigned int) versionForClassName: (NSString*)className; +#ifndef STRICT_OPENSTEP +/* + * MacOS-X adds some typedefs that GNUstep already has by another name. + */ +#include +#define uint8_t gsu8 +#define int32_t gss32 +#define int64_t gss64 + + +/** + * Returns a flag indicating whether the receiver supported keyed coding. + * the default implementation returns NO. Subclasses supporting keyed + * coding must override this to return YES. + */ +- (BOOL) allowsKeyedCoding; + +/** + * Returns a class indicating whether an encoded value corresponding + * to aKey exists. + */ +- (BOOL) containsValueForKey: (NSString*)aKey; + +/** + * Returns a boolean value associated with aKey. This value must previously + * have been encoded using -encodeBool:forKey: + */ +- (BOOL) decodeBoolForKey: (NSString*)aKey; + +/** + * Returns a pointer to a byte array associated with aKey.
+ * Returns the length of the data in aLength.
+ * This value must previously have been encoded using + * -encodeBytes:length:forKey: + */ +- (const uint8_t*) decodeBytesForKey: (NSString*)aKey + returnedLength: (unsigned*)alength; + +/** + * Returns a double value associated with aKey. This value must previously + * have been encoded using -encodeDouble:forKey: or -encodeFloat:forKey: + */ +- (double) decodeDoubleForKey: (NSString*)aKey; + +/** + * Returns a float value associated with aKey. This value must previously + * have been encoded using -encodeFloat:forKey: or -encodeDouble:forKey:
+ * Precision may be lost (or an exception raised if the value will not fit + * in a float) if the value was encoded using -encodeDouble:forKey:, + */ +- (float) decodeFloatForKey: (NSString*)aKey; + +/** + * Returns an integer value associated with aKey. This value must previously + * have been encoded using -encodeInt:forKey:, -encodeInt32:forKey:, or + * -encodeInt64:forKey:.
+ * An exception will be raised if the value does not fit in an integer. + */ +- (int) decodeIntForKey: (NSString*)aKey; + +/** + * Returns a 32-bit integer value associated with aKey. This value must + * previously have been encoded using -encodeInt:forKey:, + * -encodeInt32:forKey:, or -encodeInt64:forKey:.
+ * An exception will be raised if the value does not fit in a 32-bit integer. + */ +- (int32_t) decodeInt32ForKey: (NSString*)aKey; + +/** + * Returns a 64-bit integer value associated with aKey. This value must + * previously have been encoded using -encodeInt:forKey:, + * -encodeInt32:forKey:, or -encodeInt64:forKey:. + */ +- (int64_t) decodeInt64ForKey: (NSString*)aKey; + +/** + * Returns an object value associated with aKey. This value must + * previously have been encoded using -encodeObject:forKey: or + * -encodeConditionalObject:forKey: + */ +- (id) decodeObjectForKey: (NSString*)aKey; + +/** + * Encodes aBool and associates the encoded value with aKey. + */ +- (void) encodeBool: (BOOL) aBool forKey: (NSString*)aKey; + +/** + * Encodes the data of the specified length and pointed to by aPointeraBool, + * and associates the encoded value with aKey. + */ +- (void) encodeBytes: (const uint8_t*)aPointer + length: (unsigned)length + forKey: (NSString*)aKey; + +/** + * Encodes anObject and associates the encoded value with aKey, but only + * if anObject has already been encoded using -encodeObject:forKey: + */ +- (void) encodeConditionalObject: (id)anObject forKey: (NSString*)aKey; + +/** + * Encodes aDouble and associates the encoded value with aKey. + */ +- (void) encodeDouble: (double)aDouble forKey: (NSString*)aKey; + +/** + * Encodes aFloat and associates the encoded value with aKey. + */ +- (void) encodeFloat: (float)aFloat forKey: (NSString*)aKey; + +/** + * Encodes anInteger and associates the encoded value with aKey. + */ +- (void) encodeInt: (int)anInteger forKey: (NSString*)aKey; + +/** + * Encodes anInteger and associates the encoded value with aKey. + */ +- (void) encodeInt32: (int32_t)anInteger forKey: (NSString*)aKey; + +/** + * Encodes anInteger and associates the encoded value with aKey. + */ +- (void) encodeInt64: (int64_t)anInteger forKey: (NSString*)aKey; + +/** + * Encodes anObject and associates the encoded value with aKey. + */ +- (void) encodeObject: (id)anObject forKey: (NSString*)aKey; +#endif @end #ifndef NO_GNUSTEP diff --git a/Headers/Foundation/NSKeyedArchiver.h b/Headers/Foundation/NSKeyedArchiver.h new file mode 100644 index 000000000..47e0c46da --- /dev/null +++ b/Headers/Foundation/NSKeyedArchiver.h @@ -0,0 +1,353 @@ +/** Interface for NSKeyedArchiver for GNUStep + Copyright (C) 2004 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: January 2004 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + AutogsdocSource: NSKeyedArchiver.m + AutogsdocSource: NSKeyedUnarchiver.m + + */ + +#ifndef __NSKeyedArchiver_h_GNUSTEP_BASE_INCLUDE +#define __NSKeyedArchiver_h_GNUSTEP_BASE_INCLUDE + +#ifndef STRICT_OPENSTEP + +#include +#include +#include + +@class NSMutableDictionary, NSMutableData, NSData, NSString; + +/** + * Keyed archiving class, NOT YET IMPLEMENTED + */ +@interface NSKeyedArchiver : NSCoder +{ +@private + NSMutableData *_data; /* Data to write into. */ + id _delegate; /* Delegate controls operation. */ + NSMapTable *_clsMap; /* Map classes to names. */ +#ifndef _IN_NSKEYEDARCHIVER_M +#define GSIMapTable void* +#endif + GSIMapTable _cIdMap; /* Conditionally coded. */ + GSIMapTable _uIdMap; /* Unconditionally coded. */ + GSIMapTable _repMap; /* Mappings for objects. */ +#ifndef _IN_NSKEYEDARCHIVER_M +#undef GSIMapTable +#endif + unsigned _keyNum; /* Counter for keys in object. */ + NSMutableDictionary *_enc; /* Object being encoded. */ + NSMutableArray *_obj; /* Array of objects. */ + NSPropertyListFormat _format; +} + +/** + * Encodes anObject and returns the resulting data object. + */ ++ (NSData*) archivedDataWithRootObject: (id)anObject; + +/** + * Encodes anObject and writes the resulting data ti aPath. + */ ++ (BOOL) archiveRootObject: (id)anObject toFile: (NSString*)aPath; + +/** + * Returns the class name with which the NSKeyedArchiver class will encode + * instances of aClass, or nil if no name mapping has been set using the + * +setClassName:forClass: method. + */ ++ (NSString*) classNameForClass: (Class)aClass; + +/** + * Sets the class name with which the NSKeyedArchiver class will encode + * instances of aClass. This mapping is used only if no class name + * mapping has been set for the individual instance of NSKeyedArchiver + * being used.
+ * The value of aString must be the name of an existing class.
+ * If the value of aString is nil, any mapping for aClass is removed. + */ ++ (void) setClassName: (NSString*)aString forClass: (Class)aClass; + +/** + * Returns any mapping for the name of aClass which was previously set + * for the receiver using the -setClassName:forClass: method.
+ * Returns nil if no such mapping exists, even if one has been set + * using the class method +setClassName:forClass: + */ +- (NSString*) classNameForClass: (Class)aClass; + +/** + * Returns the delegate set for the receiver, or nil of none is set. + */ +- (id) delegate; + +- (void) encodeBool: (BOOL)aBool forKey: (NSString*)aKey; +- (void) encodeBytes: (const uint8_t*)aPointer length: (unsigned)length forKey: (NSString*)aKey; +- (void) encodeConditionalObject: (id)anObject forKey: (NSString*)aKey; +- (void) encodeDouble: (double)aDouble forKey: (NSString*)aKey; +- (void) encodeFloat: (float)aFloat forKey: (NSString*)aKey; +- (void) encodeInt: (int)anInteger forKey: (NSString*)aKey; +- (void) encodeInt32: (int32_t)anInteger forKey: (NSString*)aKey; +- (void) encodeInt64: (int64_t)anInteger forKey: (NSString*)aKey; +- (void) encodeObject: (id)anObject forKey: (NSString*)aKey; + +/** + * Ends the encoding process and causes the encoded archive to be placed + * in the mutable data object supplied when the receiver was initialised.
+ * This method must be called at the end of encoding, and nothing may be + * encoded after this method is called. + */ +- (void) finishEncoding; + +/** + * Initialise the receiver to encode an archive into the supplied + * data object. + */ +- (id) initForWritingWithMutableData: (NSMutableData*)data; + +/** + * Returns the output format of the archived data ... this should default + * to the MacOS-X binary format, but we don't support that yet, so the + * -setOutputFormat: method should be used to set a supported format. + */ +- (NSPropertyListFormat) outputFormat; + +/** + * Sets the name with which instances of aClass are encoded.
+ * The value of aString must be the anme of an existing clas. + */ +- (void) setClassName: (NSString*)aString forClass: (Class)aClass; + +/** + * Sets the receivers delegate. The delegate should conform to the + * NSObject(NSKeyedArchiverDelegate) informal protocol.
+ * NB. the delegate is not retained, so you must ensure that it is not + * deallocated before the archiver has finished with it. + */ +- (void) setDelegate: (id)anObject; + +/** + * Specifies the output format of the archived data ... this should default + * to the MacOS-X binary format, but we don't support that yet, so the + * -setOutputFormat: method should be used to set a supported format. + */ +- (void) setOutputFormat: (NSPropertyListFormat)format; + +@end + + + +/** + * Keyed unarchiving class, NOT YET IMPLEMENTED + */ +@interface NSKeyedUnarchiver : NSCoder +{ +@private + id _delegate; +#ifndef _IN_NSKEYEDUNARCHIVER_M +#define GSIArray void* +#endif + GSIArray clsMap; /* Class crossreference map. */ + GSIArray objMap; /* Object crossreference map. */ + GSIArray ptrMap; /* Pointer crossreference map. */ +#ifndef _IN_NSKEYEDUNARCHIVER_M +#undef GSUnarchiverArray +#endif + unsigned cursor; /* Position in data buffer. */ + NSZone *zone; /* Zone for allocating objs. */ + NSMutableDictionary *objDict; /* Class information store. */ +} + ++ (Class) classForClassName: (NSString*)aString; ++ (void) setClass: (Class)aClass forClassName: (NSString*)aString; ++ (id) unarchiveObjectWithData: (NSData*)data; ++ (id) unarchiveObjectWithFile: (NSString*)aPath; + +- (Class) classForClassName: (NSString*)aString; +- (BOOL) containsValueForKey: (NSString*)aKey; +- (BOOL) decodeBoolForKey: (NSString*)aKey; +- (const uint8_t*) decodeBytesForKey: (NSString*)aKey + returnedLength: (unsigned*)length; +- (double) decodeDoubleForKey: (NSString*)aKey; +- (float) decodeFloatForKey: (NSString*)aKey; +- (int) decodeIntForKey: (NSString*)aKey; +- (int32_t) decodeInt32ForKey: (NSString*)aKey; +- (int64_t) decodeInt64ForKey: (NSString*)aKey; +- (id) decodeObjectForKey: (NSString*)aKey; +/** + * returns the delegate of the unarchiver. + */ +- (id) delegate; +- (void) finishDecoding; +- (id) initForReadingWithData: (NSData*)data; +- (void) setClass: (Class)aClass forClassName: (NSString*)aString; +/** + * Sets the receivers delegate. The delegate should conform to the + * NSObject(NSKeyedUnarchiverDelegate) informal protocol.
+ * NB. the delegate is not retained, so you must ensure that it is not + * deallocated before the unarchiver has finished with it. + */ +- (void) setDelegate: (id)delegate; + +@end + + +/* Exceptions */ +GS_EXPORT NSString * const NSInvalidArchiveOperationException; +GS_EXPORT NSString * const NSInvalidUnarchiveOperationException; + + +/** + * Informal protocol implemented by delegates of [NSKeyedArchiver] + */ +@interface NSObject (NSKeyedArchiverDelegate) + +/** + * Sent when encoding of anObject has completed except in the case + * of conditional encoding. + */ +- (void) archiver: (NSKeyedArchiver*)anArchiver didEncodeObject: (id)anObject; + +/** + * Sent when anObject is about to be encoded (or conditionally encoded) + * and provides the receiver with an opportunity to change the actual + * object stored into the archive by returning a different value (otherwise + * it should return anObject).
+ * The method is not called for encoding of nil or for encoding of any + * object for which has already been called.
+ * The method is called after the -replacementObjectForKeyedArchiver: + * method. + */ +- (id) archiver: (NSKeyedArchiver*)anArchiver willEncodeObject: (id)anObject; + +/** + * Sent when the encoding process is complete. + */ +- (void) archiverDidFinish: (NSKeyedArchiver*)anArchiver; + +/** + * Sent when the encoding process is about to finish. + */ +- (void) archiverWillFinish: (NSKeyedArchiver*)anArchiver; + +/** + * Sent whenever object replacement occurs during encoding, either by the + * -replacementObjectForKeyedArchiver: method or because the delegate has + * returned a changed value using the -archiver:willEncodeObject: method. + */ +- (void) archiver: (NSKeyedArchiver*)anArchiver +willReplaceObject: (id)anObject + withObject: (id)newObject; + +@end + + + +/** + * Informal protocol implemented by delegates of [NSKeyedUnarchiver] + */ +@interface NSObject (NSKeyedUnarchiverDelegate) + +/** + * Sent if the named class is not available during decoding.
+ * The value of aName is the class name being decoded (after any name mapping + * has been applied).
+ * The classNames arraay contains the original name of the class encoded + * in the archive, and is followed by eqach of its superclasses in turn.
+ * The delegate may either return a class object for the unarchiver to use + * to continue decoding, or may return nil to abort the decoding process. + */ +- (Class) unarchiver: (NSKeyedUnarchiver*)anUnarchiver + cannotDecodeObjectOfClassName: (NSString*)aName + originalClasses: (NSArray*)classNames; + +/** + * Sent when anObject is decoded. The receiver may return either anObject + * or some other object (including nil). If a value other than anObject is + * returned, it is used to replace anObject. + */ +- (id) unarchiver: (NSKeyedUnarchiver*)anUnarchiver + didDecodeObject: (id)anObject; + +/** + * Sent when unarchiving is about to complete. + */ +- (void) unarchiverDidFinish: (NSKeyedUnarchiver*)anUnarchiver; + +/** + * Sent when unarchiving has been completed. + */ +- (void) unarchiverWillFinish: (NSKeyedUnarchiver*)anUnarchiver; + +/** + * Sent whenever object replacement occurs during decoding, eg by the + * -replacementObjectForKeyedArchiver: method. + */ +- (void) unarchiver: (NSKeyedUnarchiver*)anUnarchiver + willReplaceObject: (id)anObject + withObject: (id)newObject; + +@end + + + +/** + * Methods by which a class may control its archiving by the NSKeyedArchiver + */ +@interface NSObject (NSKeyedArchiverObjectSubstitution) + +/** + * This message is sent to objects being encoded, to allow them to choose + * to be encoded a different class. If this returns nil it is treated as + * if it returned the class of the object.
+ * After this method is applied, any class name mapping set in the archiver + * is applied to its result.
+ * The default implementation returns the result of the -classForArchiver + * method. + */ +- (Class) classForKeyedArchiver; + +/** + * This message is sent to objects being encoded, to allow them to choose + * to be encoded a different object by returning the alternative object.
+ * The default implementation returns the result of calling + * the -replacementObjectForArchiver: method with a nil argument.
+ * This is called only if no mapping has been set up in the archiver already. + */ +- (id) replacementObjectForKeyedArchiver: (NSKeyedArchiver*)archiver; + +@end + +@interface NSObject (NSKeyedUnarchiverObjectSubstitution) + +/** + * Sent during unarchiving to permit classes to substitute a different + * class for decoded instances of themselves.
+ * Default implementation returns the receiver.
+ * Overrides the mappings set up within the receiver. + */ ++ (Class) classForKeyedUnarchiver; + +@end + +#endif /* STRICT_OPENSTEP */ +#endif /* __NSKeyedArchiver_h_GNUSTEP_BASE_INCLUDE*/ diff --git a/Headers/Foundation/NSPropertyList.h b/Headers/Foundation/NSPropertyList.h new file mode 100644 index 000000000..97ee414e1 --- /dev/null +++ b/Headers/Foundation/NSPropertyList.h @@ -0,0 +1,125 @@ +/** Interface for NSPropertyList for GNUStep + Copyright (C) 2004 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: January 2004 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + AutogsdocSource: NSPropertyList.m + + */ + +#ifndef __NSPropertyList_h_GNUSTEP_BASE_INCLUDE +#define __NSPropertyList_h_GNUSTEP_BASE_INCLUDE + +#ifndef STRICT_OPENSTEP + +#include + +@class NSData, NSString; + +/** + * Describes the mutability to use when generating objects during + * deserialisation of a property list. + */ +typedef enum { + NSPropertyListImmutable, +/** NSPropertyListImmutable + * all objects in created list are immutable + */ + NSPropertyListMutableContainers, +/** NSPropertyListMutableContainers + * dictionaries and arrays are mutable + */ + NSPropertyListMutableContainersAndLeaves +/** NSPropertyListMutableContainersAndLeaves + * dictionaries, arrays, strings and data objects are mutable + */ +} NSPropertyListMutabilityOptions; + +/** + * Specifies the serialisation format for a serialised property list. + */ +typedef enum { + NSPropertyListGNUStepFormat, +/** NSPropertyListGNUStepFormat + * extension of OpenStep format */ + NSPropertyListGNUstepBinaryFormat, +/** NSPropertyListGNUstepBinaryFormat + * efficient, hardware independent */ + NSPropertyListOpenStepFormat, +/** NSPropertyListOpenStepFormat + * the most human-readable format */ + NSPropertyListXMLFormat_v1_0, +/** NSPropertyListXMLFormat_v1_0 + * portable and readable */ + NSPropertyListBinaryFormat_v1_0, +/** NSPropertyListBinaryFormat_v1_0 + * not yet supported */ +} NSPropertyListFormat; + +/** + *

The NSPropertyListSerialization class provides facilities for + * serialising and deserializing property list data in a number of + * formats. + *

+ *

You do not work with instances of this class, instead you use a + * small number of claass methods to serialized and deserialize + * property lists. + *

+ */ +@interface NSPropertyListSerialization : NSObject +{ +} + +/** + * Creates and returns a data object containing a serialized representation + * of plist. The argument aFormat is used to determine the way in which the + * data is serialised, and the anErrorString argument is a pointer in which + * an error message is returned on failure (nil is returned on success). + */ ++ (NSData*) dataFromPropertyList: (id)aPropertyList + format: (NSPropertyListFormat)aFormat + errorDescription: (NSString**)anErrorString; + +/** + * Returns a flag indicating whether it is possible to serialize aPropertyList + * in the format aFormat. + */ ++ (BOOL) propertyList: (id)aPropertyList + isValidForFormat: (NSPropertyListFormat)aFormat; + +/** + * Deserialises dataItem and returns the resulting property list + * (or nil if the data does not contain a property list serialised + * in a supported format).
+ * The argument anOption is ised to control whether the objects making + * up the deserialized property list are mutable or not.
+ * The argument aFormat is either null or a pointer to a location + * in which the format of the serialized property list will be returned.
+ * Either nil or an error message will be returned in anErrorString. + */ ++ (id) propertyListFromData: (NSData*)data + mutabilityOption: (NSPropertyListMutabilityOptions)anOption + format: (NSPropertyListFormat*)aFormat + errorDescription: (NSString**)anErrorString; + +@end + +#endif /* STRICT_OPENSTEP */ +#endif /* __NSPropertyList_h_GNUSTEP_BASE_INCLUDE*/ diff --git a/Source/Additions/GSMime.m b/Source/Additions/GSMime.m index b1229e049..bbe637f61 100644 --- a/Source/Additions/GSMime.m +++ b/Source/Additions/GSMime.m @@ -108,7 +108,6 @@ encodebase64(char *dst, const unsigned char *src, int length) dst[dIndex - 1] = '='; dst[dIndex - 2] = '='; } - dst[dIndex] = '\0'; return dIndex; } @@ -3155,10 +3154,9 @@ static NSCharacterSet *tokenSet = nil; { return [NSData data]; } - destlen = 4 * ((length - 1) / 3) + 5; + destlen = 4 * ((length + 2) / 3); sBuf = (unsigned char*)[source bytes]; dBuf = NSZoneMalloc(NSDefaultMallocZone(), destlen); - dBuf[destlen - 1] = '\0'; destlen = encodebase64(dBuf, sBuf, length); @@ -4539,7 +4537,7 @@ static NSCharacterSet *tokenSet = nil; * and parameters as found after the colon in a mime Content-Type header. *

*/ -- (void) setContentType: (id)newType +- (void) setContentType: (NSString *)newType { CREATE_AUTORELEASE_POOL(arp); GSMimeHeader *hdr = nil; diff --git a/Source/DocMakefile b/Source/DocMakefile index 6c6225d1c..18fc75eff 100644 --- a/Source/DocMakefile +++ b/Source/DocMakefile @@ -64,6 +64,7 @@ NSGeometry.h \ NSHashTable.h \ NSHost.h \ NSInvocation.h \ +NSKeyedArchiver.h \ NSKeyValueCoding.h \ NSLock.h \ NSMapTable.h \ @@ -79,6 +80,7 @@ NSPort.h \ NSPortCoder.h \ NSPortMessage.h \ NSPortNameServer.h \ +NSPropertyList.h \ NSProcessInfo.h \ NSProtocolChecker.h \ NSProxy.h \ diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 2596365a2..d18e45efc 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -178,6 +178,8 @@ NSGeometry.m \ NSHashTable.m \ NSHost.m \ NSInvocation.m \ +NSKeyedArchiver.m \ +NSKeyedUnarchiver.m \ NSKeyValueCoding.m \ NSLock.m \ NSLog.m \ @@ -198,6 +200,7 @@ NSPortCoder.m \ NSPortMessage.m \ NSPortNameServer.m \ NSProcessInfo.m \ +NSPropertyList.m \ NSProtocolChecker.m \ NSProxy.m \ NSRange.m \ @@ -291,6 +294,7 @@ NSGeometry.h \ NSHashTable.h \ NSHost.h \ NSInvocation.h \ +NSKeyedArchiver.h \ NSKeyValueCoding.h \ NSLock.h \ NSMapTable.h \ @@ -307,6 +311,7 @@ NSPortCoder.h \ NSPortMessage.h \ NSPortNameServer.h \ NSProcessInfo.h \ +NSPropertyList.h \ NSProtocolChecker.h \ NSProxy.h \ NSRange.h \ diff --git a/Source/GSCompatibility.m b/Source/GSCompatibility.m index 50b101f52..202cde7c2 100644 --- a/Source/GSCompatibility.m +++ b/Source/GSCompatibility.m @@ -436,39 +436,44 @@ OAppend(id obj, NSDictionary *loc, unsigned lev, unsigned step, } else if ([obj isKindOfClass: [NSNumber class]]) { - double val = [obj doubleValue]; + const char *t = [obj objCType]; - if (val == 1.0) + if (*t == 'c' || *t == 'C') { - if (x == PLXML) + BOOL val = [obj boolValue]; + + if (val == YES) { - Append(@"\n", dest); - } - else if (x == PLNEW) - { - Append(@"<*BY>", dest); + if (x == PLXML) + { + Append(@"\n", dest); + } + else if (x == PLNEW) + { + Append(@"<*BY>", dest); + } + else + { + PString([obj description], dest); + } } else { - PString([obj description], dest); + if (x == PLXML) + { + Append(@"\n", dest); + } + else if (x == PLNEW) + { + Append(@"<*BN>", dest); + } + else + { + PString([obj description], dest); + } } } - else if (val == 0.0) - { - if (x == PLXML) - { - Append(@"\n", dest); - } - else if (x == PLNEW) - { - Append(@"<*BN>", dest); - } - else - { - PString([obj description], dest); - } - } - else if (rint(val) == val) + else if (strchr("sSiIlLqQ", *t) != 0) { if (x == PLXML) { @@ -601,7 +606,7 @@ OAppend(id obj, NSDictionary *loc, unsigned lev, unsigned step, { NSEnumerator *e; - Append(@"", dest); + Append(@"\n", dest); e = [obj objectEnumerator]; while ((obj = [e nextObject])) { @@ -700,6 +705,7 @@ OAppend(id obj, NSDictionary *loc, unsigned lev, unsigned step, Append(@"", dest); XString(key, dest); Append(@"\n", dest); + Append(iSizeString, dest); OAppend(val, loc, level, step, x, dest); } Append(iBaseString, dest); diff --git a/Source/NSCoder.m b/Source/NSCoder.m index 54d0e8c27..f28f73755 100644 --- a/Source/NSCoder.m +++ b/Source/NSCoder.m @@ -294,6 +294,116 @@ + GNUSTEP_BASE_MINOR_VERSION) * 100) + GNUSTEP_BASE_SUBMINOR_VERSION; } + +// Keyed archiving extensions + +- (BOOL) allowsKeyedCoding +{ + return NO; +} + +- (BOOL) containsValueForKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; + return NO; +} + +- (BOOL) decodeBoolForKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; + return NO; +} + +- (const uint8_t*) decodeBytesForKey: (NSString*)aKey + returnedLength: (unsigned*)alength +{ + [self subclassResponsibility: _cmd]; + return 0; +} + +- (double) decodeDoubleForKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; + return 0.0; +} + +- (float) decodeFloatForKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; + return 0.0; +} + +- (int) decodeIntForKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; + return 0; +} + +- (int32_t) decodeInt32ForKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; + return 0; +} + +- (int64_t) decodeInt64ForKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; + return 0; +} + +- (id) decodeObjectForKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; + return nil; +} + +- (void) encodeBool: (BOOL) aBool forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + +- (void) encodeBytes: (const uint8_t*)aPointer + length: (unsigned)length + forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + +- (void) encodeConditionalObject: (id)anObject forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + +- (void) encodeDouble: (double)aDouble forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + +- (void) encodeFloat: (float)aFloat forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + +- (void) encodeInt: (int)anInteger forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + +- (void) encodeInt32: (int32_t)anInteger forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + +- (void) encodeInt64: (int64_t)anInteger forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + +- (void) encodeObject: (id)anObject forKey: (NSString*)aKey +{ + [self subclassResponsibility: _cmd]; +} + @end @implementation NSCoder (GNUstep) @@ -378,4 +488,6 @@ { [self encodeValueOfObjCType: @encode(id) at: anObject withName: name]; } + + @end diff --git a/Source/NSKeyedArchiver.m b/Source/NSKeyedArchiver.m new file mode 100644 index 000000000..772b8c5ea --- /dev/null +++ b/Source/NSKeyedArchiver.m @@ -0,0 +1,741 @@ +/** Implementation for NSKeyedArchiver for GNUStep + Copyright (C) 2004 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: January 2004 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + */ + +#include +#include +#include +#include +#include + +/* + * Setup for inline operation of pointer map tables. + */ +#define GSI_MAP_RETAIN_KEY(M, X) +#define GSI_MAP_RELEASE_KEY(M, X) +#define GSI_MAP_RETAIN_VAL(M, X) +#define GSI_MAP_RELEASE_VAL(M, X) +#define GSI_MAP_HASH(M, X) ((X).uint) +#define GSI_MAP_EQUAL(M, X,Y) ((X).uint == (Y).uint) +#define GSI_MAP_NOCLEAN 1 + +#include + + +#define _IN_NSKEYEDARCHIVER_M 1 +#include +#undef _IN_NSKEYEDARCHIVER_M + +/* Exceptions */ +NSString * const NSInvalidArchiveOperationException += @"NSInvalidArchiveOperationException"; + +static NSMapTable *globalClassMap = 0; + +#define CHECKKEY \ + if ([aKey isKindOfClass: [NSString class]] == NO) \ + { \ + [NSException raise: NSInvalidArgumentException \ + format: @"%@, bad key '%@' in %@", \ + NSStringFromClass([self class]), aKey, NSStringFromSelector(_cmd)]; \ + } \ + if ([aKey hasPrefix: @"$"] == YES) \ + { \ + aKey = [@"$" stringByAppendingString: aKey]; \ + } \ + if ([_enc objectForKey: aKey] != nil) \ + { \ + [NSException raise: NSInvalidArgumentException \ + format: @"%@, duplicate key '%@' in %@", \ + NSStringFromClass([self class]), aKey, NSStringFromSelector(_cmd)]; \ + } + +@interface NSKeyedArchiver (Private) +- (NSDictionary*) _buildObjectReference: (id)anObject; +- (void) _encodeObject: (id)anObject + forKey: (NSString*)aKey + conditional: (BOOL)conditional; +@end + +@implementation NSKeyedArchiver (Private) +/* + * Add an object to the table off all encoded objects, and return a reference. + */ +- (NSDictionary*) _buildObjectReference: (id)anObject +{ + unsigned ref = 0; + + if (anObject != nil) + { + ref = [_obj count]; + [_obj addObject: anObject]; + } + return [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: ref] + forKey: @"CF$UID"]; +} + +/* + * The real workhorse of the archiving process ... this deals with all + * archiving of objects. + */ +- (void) _encodeObject: (id)anObject + forKey: (NSString*)aKey + conditional: (BOOL)conditional +{ + id original = anObject; + GSIMapNode node; + id objectInfo = nil; // Encoded object + NSMutableDictionary *m = nil; + NSNumber *refNum; + NSDictionary *keyDict; + unsigned ref = 0; + + if (anObject != nil) + { + /* + * Obtain replacement object for the value being encoded. + * Notify delegate of progress and set up new mapping if necessary. + */ + node = GSIMapNodeForKey(_repMap, (GSIMapKey)anObject); + if (node == 0) + { + anObject = [original replacementObjectForKeyedArchiver: self]; + if (_delegate != nil) + { + if (anObject != nil) + { + anObject = [_delegate archiver: self + willEncodeObject: anObject]; + } + if (original != anObject) + { + [_delegate archiver: self + willReplaceObject: original + withObject: anObject]; + } + } + GSIMapAddPair(_repMap, (GSIMapKey)original, (GSIMapVal)anObject); + } + } + + if (anObject != nil) + { + node = GSIMapNodeForKey(_uIdMap, (GSIMapKey)anObject); + if (node == 0) + { + if (conditional == YES) + { + node = GSIMapNodeForKey(_cIdMap, (GSIMapKey)anObject); + if (node == 0) + { + ref = [_obj count]; + GSIMapAddPair(_cIdMap, (GSIMapKey)anObject, (GSIMapVal)ref); + /* + * Use the null object as a placeholder for a conditionally + * encoded object. + */ + [_obj addObject: [_obj objectAtIndex: 0]]; + } + else + { + /* + * This object has already been conditionally encoded. + */ + ref = node->value.uint; + } + } + else + { +// FIXME ... exactly what classes are stored directly??? + if ([anObject isKindOfClass: [NSString class]] == YES) + { + // We will store the string object directly. + objectInfo = anObject; + } + else + { + // We store a dictionary describing the object. + m = [NSMutableDictionary new]; + objectInfo = m; + } + + node = GSIMapNodeForKey(_cIdMap, (GSIMapKey)anObject); + if (node == 0) + { + /* + * Not encoded ... create dictionary for it. + */ + ref = [_obj count]; + GSIMapAddPair(_uIdMap, (GSIMapKey)anObject, (GSIMapVal)ref); + [_obj addObject: objectInfo]; + } + else + { + /* + * Conditionally encoded ... replace with actual value. + */ + ref = node->value.uint; + GSIMapAddPair(_uIdMap, (GSIMapKey)anObject, (GSIMapVal)ref); + GSIMapRemoveKey(_cIdMap, (GSIMapKey)anObject); + [_obj replaceObjectAtIndex: ref withObject: objectInfo]; + } + RELEASE(m); + } + } + else + { + ref = node->value.uint; + } + } + + /* + * Store the mapping from aKey to the appropriate entry in _obj + */ + refNum = [[NSNumber alloc] initWithInt: ref]; + keyDict = [NSDictionary dictionaryWithObject: refNum forKey: @"CF$UID"]; + [_enc setObject: keyDict forKey: aKey]; + RELEASE(refNum); + + /* + * objectInfo is a dictionary describing the object. + */ + if (objectInfo != nil && m == objectInfo) + { + NSMutableDictionary *savedEnc = _enc; + unsigned savedKeyNum = _keyNum; + Class c = [anObject class]; + NSString *classname; + Class mapped; + + /* + * Map the class of the object to the actual class it is encoded as. + * First ask the object, then apply any name mappings to that value. + */ + mapped = [anObject classForKeyedArchiver]; + if (mapped != nil) + { + c = mapped; + } + + classname = [self classNameForClass: c]; + if (classname == nil) + { + classname = [[self class] classNameForClass: c]; + } + if (classname == nil) + { + classname = NSStringFromClass(c); + } + else + { + c = NSClassFromString(classname); + } + + /* + * At last, get the object to encode itsself. Save and restore the + * current object scope of course. + */ + _enc = m; + _keyNum = 0; + [anObject encodeWithCoder: self]; + _keyNum = savedKeyNum; + _enc = savedEnc; + + /* + * This is ugly, but it seems to be the way MacOS-X does it ... + * We create class information by storing it directly into the + * table of all objects, and making a reference so we can look + * up the table entry by class pointer. + * A much cleaner way to do it would be by encoding the class + * normally, but we are trying to be compatible. + * + * Also ... we encode the class *after* encoding the instance, + * simply because that seems to be the way MacOS-X does it and + * we want to maximise compatibility (perhaps they had good reason?) + */ + node = GSIMapNodeForKey(_uIdMap, (GSIMapKey)c); + if (node == 0) + { + NSMutableDictionary *cDict; + NSMutableArray *hierarchy; + + ref = [_obj count]; + GSIMapAddPair(_uIdMap, (GSIMapKey)c, (GSIMapVal)ref); + cDict = [[NSMutableDictionary alloc] initWithCapacity: 2]; + + /* + * record class name + */ + [cDict setObject: classname forKey: @"$classname"]; + + /* + * Record the class hierarchy for this object. + */ + hierarchy = [NSMutableArray new]; + while (c != 0) + { + Class next = [c superClass]; + + [hierarchy addObject: NSStringFromClass(c)]; + if (next == c) + { + break; + } + c = next; + } + [cDict setObject: hierarchy forKey: @"$classes"]; + RELEASE(hierarchy); + [_obj addObject: cDict]; + RELEASE(cDict); + } + else + { + ref = node->value.uint; + } + + /* + * Now create a reference to the class information and store it + * in the object description dictionary for the object we just encoded. + */ + refNum = [[NSNumber alloc] initWithInt: ref]; + keyDict = [NSDictionary dictionaryWithObject: refNum forKey: @"CF$UID"]; + [m setObject: keyDict forKey: @"$class"]; + RELEASE(refNum); + } + + /* + * If we have encoded the object information, tell the delegaate. + */ + if (objectInfo != nil && _delegate != nil) + { + [_delegate archiver: self didEncodeObject: anObject]; + } +} +@end + +@implementation NSKeyedArchiver + +/* + * When I tried this on MacOS 10.3 it encoded the object with the key 'root', + * so this implementation does the same. + */ ++ (NSData*) archivedDataWithRootObject: (id)anObject +{ + NSMutableData *m = nil; + NSKeyedArchiver *a = nil; + NSData *d = nil; + + NS_DURING + { + m = [[NSMutableData alloc] initWithCapacity: 10240]; + a = [[NSKeyedArchiver alloc] initForWritingWithMutableData: m]; + [a encodeObject: anObject forKey: @"root"]; + [a finishEncoding]; + d = [m copy]; + DESTROY(m); + DESTROY(a); + } + NS_HANDLER + { + DESTROY(m); + DESTROY(a); + [localException raise]; + } + NS_ENDHANDLER + return AUTORELEASE(d); +} + ++ (BOOL) archiveRootObject: (id)anObject toFile: (NSString*)aPath +{ + CREATE_AUTORELEASE_POOL(pool); + NSData *d; + BOOL result; + + d = [self archivedDataWithRootObject: anObject]; + result = [d writeToFile: aPath atomically: YES]; + RELEASE(pool); + return result; +} + ++ (NSString*) classNameForClass: (Class)aClass +{ + return (NSString*)NSMapGet(globalClassMap, (void*)aClass); +} + ++ (void) initialize +{ + if (globalClassMap == 0) + { + globalClassMap = + NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, + NSObjectMapValueCallBacks, 0); + } +} + ++ (void) setClassName: (NSString*)aString forClass: (Class)aClass +{ + if (aString == nil) + { + NSMapRemove(globalClassMap, (void*)aClass); + } + else + { + NSMapInsert(globalClassMap, (void*)aClass, aString); + } +} + +- (NSString*) classNameForClass: (Class)aClass +{ + return (NSString*)NSMapGet(_clsMap, (void*)aClass); +} + +- (void) dealloc +{ + RELEASE(_enc); + RELEASE(_obj); + RELEASE(_data); + if (_clsMap != 0) + { + NSFreeMapTable(_clsMap); + _clsMap = 0; + } + if (_cIdMap) + { + GSIMapEmptyMap(_cIdMap); + if (_uIdMap) + { + GSIMapEmptyMap(_uIdMap); + } + if (_repMap) + { + GSIMapEmptyMap(_repMap); + } + NSZoneFree(_cIdMap->zone, (void*)_cIdMap); + } + [super dealloc]; +} + +- (id) delegate +{ + return _delegate; +} + +- (void) encodeBool: (BOOL)aBool forKey: (NSString*)aKey +{ + CHECKKEY + + [_enc setObject: [NSNumber numberWithBool: aBool] forKey: aKey]; +} + +- (void) encodeBytes: (const uint8_t*)aPointer length: (unsigned)length forKey: (NSString*)aKey +{ + CHECKKEY + + [_enc setObject: [NSData dataWithBytes: aPointer length: length] + forKey: aKey]; +} + +- (void) encodeConditionalObject: (id)anObject +{ + [self _encodeObject: anObject + forKey: [NSString stringWithFormat: @"$%u", _keyNum++] + conditional: YES]; +} + +- (void) encodeConditionalObject: (id)anObject forKey: (NSString*)aKey +{ + CHECKKEY + + [self _encodeObject: anObject forKey: aKey conditional: YES]; +} + +- (void) encodeDouble: (double)aDouble forKey: (NSString*)aKey +{ + CHECKKEY + + [_enc setObject: [NSNumber numberWithDouble: aDouble] forKey: aKey]; +} + +- (void) encodeFloat: (float)aFloat forKey: (NSString*)aKey +{ + CHECKKEY + + [_enc setObject: [NSNumber numberWithFloat: aFloat] forKey: aKey]; +} + +- (void) encodeInt: (int)anInteger forKey: (NSString*)aKey +{ + CHECKKEY + + [_enc setObject: [NSNumber numberWithInt: anInteger] forKey: aKey]; +} + +- (void) encodeInt32: (int32_t)anInteger forKey: (NSString*)aKey +{ + CHECKKEY + + [_enc setObject: [NSNumber numberWithLong: anInteger] forKey: aKey]; +} + +- (void) encodeInt64: (int64_t)anInteger forKey: (NSString*)aKey +{ + CHECKKEY + + [_enc setObject: [NSNumber numberWithLongLong: anInteger] forKey: aKey]; +} + +- (void) encodeObject: (id)anObject +{ + [self _encodeObject: anObject + forKey: [NSString stringWithFormat: @"$%u", _keyNum++] + conditional: NO]; +} + +- (void) encodeObject: (id)anObject forKey: (NSString*)aKey +{ + CHECKKEY + + [self _encodeObject: anObject forKey: aKey conditional: NO]; +} + +- (void) encodeValueOfObjCType: (const char*)type + at: (const void*)address +{ + NSString *aKey; + id o; + + if (*type == _C_ID || *type == _C_CLASS) + { + [self encodeObject: *(id*)address]; + return; + } + + aKey = [NSString stringWithFormat: @"$%u", _keyNum++]; + switch (*type) + { + case _C_SEL: + { + // Selectors are encoded by name as strings. + o = NSStringFromSelector(*(SEL*)address); + [self encodeObject: o]; + } + return; + + case _C_CHARPTR: + { + /* + * Bizzarely MacOS-X seems to encode char* values by creating + * string objects and encoding those objects! + */ + o = [NSString stringWithCString: (char*)address]; + [self encodeObject: o]; + } + return; + + case _C_CHR: + o = [NSNumber numberWithInt: (int)*(char*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_UCHR: + o = [NSNumber numberWithInt: (int)*(unsigned char*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_SHT: + o = [NSNumber numberWithInt: (int)*(short*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_USHT: + o = [NSNumber numberWithLong: (long)*(unsigned short*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_INT: + o = [NSNumber numberWithInt: *(int*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_UINT: + o = [NSNumber numberWithUnsignedInt: *(unsigned int*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_LNG: + o = [NSNumber numberWithLong: *(long*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_ULNG: + o = [NSNumber numberWithUnsignedLong: *(unsigned long*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_LNG_LNG: + o = [NSNumber numberWithLongLong: *(long long*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_ULNG_LNG: + o = [NSNumber numberWithUnsignedLongLong: + *(unsigned long long*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_FLT: + o = [NSNumber numberWithFloat: *(float*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_DBL: + o = [NSNumber numberWithDouble: *(double*)address]; + [_enc setObject: o forKey: aKey]; + return; + + case _C_STRUCT_B: + [NSException raise: NSInvalidArgumentException + format: @"-[%@ %@]: this archiver cannote encode structs", + NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + return; + + default: /* Types that can be ignored in first pass. */ + [NSException raise: NSInvalidArgumentException + format: @"-[%@ %@]: unknown type encoding ('%c')", + NSStringFromClass([self class]), NSStringFromSelector(_cmd), *type]; + break; + } +} + +- (void) finishEncoding +{ + NSMutableDictionary *final; + NSData *data; + NSString *error; + + [_delegate archiverWillFinish: self]; + + final = [NSMutableDictionary new]; + [final setObject: NSStringFromClass([self class]) forKey: @"$archiver"]; + [final setObject: @"100000" forKey: @"$version"]; + [final setObject: _enc forKey: @"$top"]; + [final setObject: _obj forKey: @"$objects"]; + data = [NSPropertyListSerialization dataFromPropertyList: final + format: _format + errorDescription: &error]; + RELEASE(final); + [_data setData: data]; + [_delegate archiverDidFinish: self]; +} + +- (id) initForWritingWithMutableData: (NSMutableData*)data +{ + self = [super init]; + if (self) + { + NSZone *zone = [self zone]; + + _keyNum = 0; + _data = RETAIN(data); + + _clsMap = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, + NSObjectMapValueCallBacks, 0); + /* + * Set up map tables. + */ + _cIdMap = (GSIMapTable)NSZoneMalloc(zone, sizeof(GSIMapTable_t)*5); + _uIdMap = &_cIdMap[1]; + _repMap = &_cIdMap[2]; + GSIMapInitWithZoneAndCapacity(_cIdMap, zone, 10); + GSIMapInitWithZoneAndCapacity(_uIdMap, zone, 200); + GSIMapInitWithZoneAndCapacity(_repMap, zone, 1); + + _enc = [NSMutableDictionary new]; // Top level mapping dict + _obj = [NSMutableArray new]; // Array of objects. + [_obj addObject: @"$null"]; // Placeholder. + + } + return self; +} + +- (NSPropertyListFormat) outputFormat +{ + return _format; +} + +- (void) setClassName: (NSString*)aString forClass: (Class)aClass +{ + if (aString == nil) + { + NSMapRemove(_clsMap, (void*)aClass); + } + else + { + NSMapInsert(_clsMap, (void*)aClass, aString); + } +} + +- (void) setDelegate: (id)anObject +{ + _delegate = anObject; // Not retained. +} + +- (void) setOutputFormat: (NSPropertyListFormat)format +{ + _format = format; +} + +@end + +@implementation NSObject (NSKeyedArchiverDelegate) +- (void) archiver: (NSKeyedArchiver*)anArchiver didEncodeObject: (id)anObject +{ +} +- (id) archiver: (NSKeyedArchiver*)anArchiver willEncodeObject: (id)anObject +{ + return anObject; +} +- (void) archiverDidFinish: (NSKeyedArchiver*)anArchiver +{ +} +- (void) archiverWillFinish: (NSKeyedArchiver*)anArchiver +{ +} +- (void) archiver: (NSKeyedArchiver*)anArchiver +willReplaceObject: (id)anObject + withObject: (id)newObject +{ +} +@end + +@implementation NSObject (NSKeyedArchiverObjectSubstitution) +- (Class) classForKeyedArchiver +{ + return [self classForArchiver]; +} +- (id) replacementObjectForKeyedArchiver: (NSKeyedArchiver*)archiver +{ + return [self replacementObjectForArchiver: nil]; +} +@end + diff --git a/Source/NSKeyedUnarchiver.m b/Source/NSKeyedUnarchiver.m new file mode 100644 index 000000000..872f1ba98 --- /dev/null +++ b/Source/NSKeyedUnarchiver.m @@ -0,0 +1,230 @@ +/** Implementation for NSKeyedUnarchiver for GNUStep + Copyright (C) 2004 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + Date: January 2004 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + */ + +#include +#include +#include +#include + +#include +#include + +#define _IN_NSKEYEDUNARCHIVER_M 1 +#include +#undef _IN_NSKEYEDUNARCHIVER_M + +static NSMapTable globalClassMap = 0; + +NSString * const NSInvalidUnarchiveOperationException + = @"NSInvalidUnarchiveOperationException"; + +@implementation NSKeyedUnarchiver + ++ (Class) classForClassName: (NSString*)aString +{ + return (Class)NSMapGet(globalClassMap, (void*)aString); +} + ++ (void) initialize +{ + if (globalClassMap == 0) + { + globalClassMap = + NSCreateMapTable(NSObjectMapKeyCallBacks, + NSNonOwnedPointerMapValueCallBacks, 0); + } +} + ++ (void) setClass: (Class)aClass forClassName: (NSString*)aString +{ + if (aClass == nil) + { + NSMapRemove(globalClassMap, (void*)aString); + } + else + { + NSMapInsert(globalClassMap, (void*)aString, aClass); + } +} + +/* + * When I tried this on MacOS 10.3 it encoded the object with the key 'root', + * so this implementation does the same. + */ ++ (id) unarchiveObjectWithData: (NSData*)data +{ + NSKeyedUnarchiver *u = nil; + id o = nil; + + NS_DURING + { + u = [[NSKeyedUnarchiver alloc] initForReadingWithData: data]; + o = RETAIN([u decodeObjectForKey: @"root"]); + [u finishDecoding]; + DESTROY(u); + } + NS_HANDLER + { + DESTROY(u); + [localException raise]; + } + NS_ENDHANDLER + return AUTORELEASE(o); +} + ++ (id) unarchiveObjectWithFile: (NSString*)aPath +{ + CREATE_AUTORELEASE_POOL(pool); + NSData *d; + id o; + + d = [NSData dataWithContentsOfFile: aPath]; + o = [self unarchiveObjectWithData: d]; + RETAIN(o); + RELEASE(pool); + return AUTORELEASE(o); +} + +- (Class) classForClassName: (NSString*)aString +{ + [self notImplemented: _cmd]; + return nil; +} + +- (BOOL) containsValueForKey: (NSString*)aKey +{ + [self notImplemented: _cmd]; + return NO; +} + +- (BOOL) decodeBoolForKey: (NSString*)aKey +{ + [self notImplemented: _cmd]; + return NO; +} + +- (const uint8_t*) decodeBytesForKey: (NSString*)aKey + returnedLength: (unsigned*)length +{ + [self notImplemented: _cmd]; + *length = 0; + return 0; +} + +- (double) decodeDoubleForKey: (NSString*)aKey +{ + [self notImplemented: _cmd]; + return 0.0; +} + +- (float) decodeFloatForKey: (NSString*)aKey +{ + [self notImplemented: _cmd]; + return 0.0; +} + +- (int) decodeIntForKey: (NSString*)aKey +{ + [self notImplemented: _cmd]; + return 0; +} + +- (int32_t) decodeInt32ForKey: (NSString*)aKey +{ + [self notImplemented: _cmd]; + return 0; +} + +- (int64_t) decodeInt64ForKey: (NSString*)aKey +{ + [self notImplemented: _cmd]; + return 0; +} + +- (id) decodeObjectForKey: (NSString*)aKey +{ + [self notImplemented: _cmd]; + return nil; +} + +- (id) delegate +{ + return _delegate; +} + +- (void) finishDecoding +{ + [self notImplemented: _cmd]; +} + +- (id) initForReadingWithData: (NSData*)data +{ + [self notImplemented: _cmd]; + return self; +} + +- (void) setClass: (Class)aClass forClassName: (NSString*)aString +{ + [self notImplemented: _cmd]; +} + +- (void) setDelegate: (id)delegate +{ + _delegate = delegate; // Not retained. +} + +@end + +@implementation NSObject (NSKeyedUnarchiverDelegate) +- (Class) unarchiver: (NSKeyedUnarchiver*)anUnarchiver + cannotDecodeObjectOfClassName: (NSString*)aName + originalClasses: (NSArray*)classNames +{ + return nil; +} +- (id) unarchiver: (NSKeyedUnarchiver*)anUnarchiver + didDecodeObject: (id)anObject +{ + return anObject; +} +- (void) unarchiverDidFinish: (NSKeyedUnarchiver*)anUnarchiver +{ +} +- (void) unarchiverWillFinish: (NSKeyedUnarchiver*)anUnarchiver +{ +} +- (void) unarchiver: (NSKeyedUnarchiver*)anUnarchiver + willReplaceObject: (id)anObject + withObject: (id)newObject +{ +} +@end + +@implementation NSObject (NSKeyedUnarchiverObjectSubstitution) ++ (Class) classForKeyedUnarchiver +{ + return self; +} +@end + diff --git a/Source/NSPropertyList.m b/Source/NSPropertyList.m new file mode 100644 index 000000000..87c1958a4 --- /dev/null +++ b/Source/NSPropertyList.m @@ -0,0 +1,2178 @@ +/** Interface for NSPropertyList for GNUStep + Copyright (C) 2003,2004 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + + */ + +#include "config.h" +#include +#include "GNUstepBase/preface.h" +#include "GNUstepBase/GSMime.h" + +#include "Foundation/NSArray.h" +#include "Foundation/NSAutoreleasePool.h" +#include "Foundation/NSCalendarDate.h" +#include "Foundation/NSCharacterSet.h" +#include "Foundation/NSData.h" +#include "Foundation/NSDictionary.h" +#include "Foundation/NSException.h" +#include "Foundation/NSPropertyList.h" +#include "Foundation/NSSerialization.h" +#include "Foundation/NSString.h" +#include "Foundation/NSUserDefaults.h" +#include "Foundation/NSValue.h" +#include "Foundation/NSDebug.h" + +extern BOOL GSScanDouble(unichar*, unsigned, double*); + +@class GSString; +@class GSMutableString; +@class GSMutableArray; +@class GSMutableDictionary; + + +/* + * Cache classes and method implementations for speed. + */ +static Class NSDataClass; +static Class NSStringClass; +static Class NSMutableStringClass; +static Class GSStringClass; +static Class GSMutableStringClass; + +static Class plArray; +static id (*plAdd)(id, SEL, id) = 0; + +static Class plDictionary; +static id (*plSet)(id, SEL, id, id) = 0; + + +#define IS_BIT_SET(a,i) ((((a) & (1<<(i)))) > 0) + +static unsigned const char *hexdigitsBitmapRep = NULL; +#define GS_IS_HEXDIGIT(X) IS_BIT_SET(hexdigitsBitmapRep[(X)/8], (X) % 8) + +static void setupHexdigits(void) +{ + if (hexdigitsBitmapRep == NULL) + { + NSCharacterSet *hexdigits; + NSData *bitmap; + + hexdigits = [NSCharacterSet characterSetWithCharactersInString: + @"0123456789abcdefABCDEF"]; + bitmap = RETAIN([hexdigits bitmapRepresentation]); + hexdigitsBitmapRep = [bitmap bytes]; + } +} + +static NSCharacterSet *quotables = nil; +static NSCharacterSet *oldQuotables = nil; +static NSCharacterSet *xmlQuotables = nil; + +static unsigned const char *quotablesBitmapRep = NULL; +#define GS_IS_QUOTABLE(X) IS_BIT_SET(quotablesBitmapRep[(X)/8], (X) % 8) + +static void setupQuotables(void) +{ + if (quotablesBitmapRep == NULL) + { + NSMutableCharacterSet *s; + NSData *bitmap; + + s = [[NSCharacterSet characterSetWithCharactersInString: + @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + @"abcdefghijklmnopqrstuvwxyz!#$%&*+-./:?@|~_^"] + mutableCopy]; + [s invert]; + quotables = [s copy]; + RELEASE(s); + bitmap = RETAIN([quotables bitmapRepresentation]); + quotablesBitmapRep = [bitmap bytes]; + s = [[NSCharacterSet characterSetWithCharactersInString: + @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + @"abcdefghijklmnopqrstuvwxyz$./_"] + mutableCopy]; + [s invert]; + oldQuotables = [s copy]; + RELEASE(s); + + s = [[NSCharacterSet characterSetWithCharactersInString: + @"&<>'\\\""] mutableCopy]; + [s addCharactersInRange: NSMakeRange(0x0001, 0x001f)]; + [s removeCharactersInRange: NSMakeRange(0x0009, 0x0002)]; + [s removeCharactersInRange: NSMakeRange(0x000D, 0x0001)]; + [s addCharactersInRange: NSMakeRange(0xD800, 0x07FF)]; + [s addCharactersInRange: NSMakeRange(0xFFFE, 0x0002)]; + xmlQuotables = [s copy]; + RELEASE(s); + } +} + +static unsigned const char *whitespaceBitmapRep = NULL; +#define GS_IS_WHITESPACE(X) IS_BIT_SET(whitespaceBitmapRep[(X)/8], (X) % 8) + +static void setupWhitespace(void) +{ + if (whitespaceBitmapRep == NULL) + { + NSCharacterSet *whitespace; + NSData *bitmap; + +/* + We can not use whitespaceAndNewlineCharacterSet here as this would lead + to a recursion, as this also reads in a property list. + whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; +*/ + whitespace = [NSCharacterSet characterSetWithCharactersInString: + @" \t\r\n\f\b"]; + + bitmap = RETAIN([whitespace bitmapRepresentation]); + whitespaceBitmapRep = [bitmap bytes]; + } +} + +static id GSPropertyListFromStringsFormat(NSString *string); + + +#ifdef HAVE_LIBXML +#include "GNUstepBase/GSXML.h" +static int XML_ELEMENT_NODE; +#endif + +#define inrange(ch,min,max) ((ch)>=(min) && (ch)<=(max)) +#define char2num(ch) \ +inrange(ch,'0','9') \ +? ((ch)-0x30) \ +: (inrange(ch,'a','f') \ +? ((ch)-0x57) : ((ch)-0x37)) + +typedef struct { + const unsigned char *ptr; + unsigned end; + unsigned pos; + unsigned lin; + NSString *err; + NSPropertyListMutabilityOptions opt; + BOOL key; + BOOL old; +} pldata; + +/* + * Property list parsing - skip whitespace keeping count of lines and + * regarding objective-c style comments as whitespace. + * Returns YES if there is any non-whitespace text remaining. + */ +static BOOL skipSpace(pldata *pld) +{ + unsigned char c; + + while (pld->pos < pld->end) + { + c = pld->ptr[pld->pos]; + + if (GS_IS_WHITESPACE(c) == NO) + { + if (c == '/' && pld->pos < pld->end - 1) + { + /* + * Check for comments beginning '/' followed by '/' or '*' + */ + if (pld->ptr[pld->pos + 1] == '/') + { + pld->pos += 2; + while (pld->pos < pld->end) + { + c = pld->ptr[pld->pos]; + if (c == '\n') + { + break; + } + pld->pos++; + } + if (pld->pos >= pld->end) + { + pld->err = @"reached end of string in comment"; + return NO; + } + } + else if (pld->ptr[pld->pos + 1] == '*') + { + pld->pos += 2; + while (pld->pos < pld->end) + { + c = pld->ptr[pld->pos]; + if (c == '\n') + { + pld->lin++; + } + else if (c == '*' && pld->pos < pld->end - 1 + && pld->ptr[pld->pos+1] == '/') + { + pld->pos++; /* Skip past '*' */ + break; + } + pld->pos++; + } + if (pld->pos >= pld->end) + { + pld->err = @"reached end of string in comment"; + return NO; + } + } + else + { + return YES; + } + } + else + { + return YES; + } + } + if (c == '\n') + { + pld->lin++; + } + pld->pos++; + } + pld->err = @"reached end of string"; + return NO; +} + +static inline id parseQuotedString(pldata* pld) +{ + unsigned start = ++pld->pos; + unsigned escaped = 0; + unsigned shrink = 0; + BOOL hex = NO; + NSString *obj; + + while (pld->pos < pld->end) + { + unsigned char c = pld->ptr[pld->pos]; + + if (escaped) + { + if (escaped == 1 && c >= '0' && c <= '7') + { + escaped = 2; + hex = NO; + } + else if (escaped == 1 && (c == 'u' || c == 'U')) + { + escaped = 2; + hex = YES; + } + else if (escaped > 1) + { + if (hex && GS_IS_HEXDIGIT(c)) + { + shrink++; + escaped++; + if (escaped == 6) + { + escaped = 0; + } + } + else if (c >= '0' && c <= '7') + { + shrink++; + escaped++; + if (escaped == 4) + { + escaped = 0; + } + } + else + { + pld->pos--; + escaped = 0; + } + } + else + { + escaped = 0; + } + } + else + { + if (c == '\\') + { + escaped = 1; + shrink++; + } + else if (c == '"') + { + break; + } + } + if (c == '\n') + pld->lin++; + pld->pos++; + } + if (pld->pos >= pld->end) + { + pld->err = @"reached end of string while parsing quoted string"; + return nil; + } + if (pld->pos - start - shrink == 0) + { + obj = @""; + } + else + { + unsigned length = pld->pos - start - shrink; + unichar *chars; + unsigned j; + unsigned k; + + chars = NSZoneMalloc(NSDefaultMallocZone(), sizeof(unichar) * length); + escaped = 0; + hex = NO; + for (j = start, k = 0; j < pld->pos; j++) + { + unsigned char c = pld->ptr[j]; + + if (escaped) + { + if (escaped == 1 && c >= '0' && c <= '7') + { + chars[k] = c - '0'; + hex = NO; + escaped++; + } + else if (escaped == 1 && (c == 'u' || c == 'U')) + { + chars[k] = 0; + hex = YES; + escaped++; + } + else if (escaped > 1) + { + if (hex && GS_IS_HEXDIGIT(c)) + { + chars[k] <<= 4; + chars[k] |= char2num(c); + escaped++; + if (escaped == 6) + { + escaped = 0; + k++; + } + } + else if (c >= '0' && c <= '7') + { + chars[k] <<= 3; + chars[k] |= (c - '0'); + escaped++; + if (escaped == 4) + { + escaped = 0; + k++; + } + } + else + { + escaped = 0; + j--; + k++; + } + } + else + { + escaped = 0; + switch (c) + { + case 'a' : chars[k] = '\a'; break; + case 'b' : chars[k] = '\b'; break; + case 't' : chars[k] = '\t'; break; + case 'r' : chars[k] = '\r'; break; + case 'n' : chars[k] = '\n'; break; + case 'v' : chars[k] = '\v'; break; + case 'f' : chars[k] = '\f'; break; + default : chars[k] = c; break; + } + k++; + } + } + else + { + chars[k] = c; + if (c == '\\') + { + escaped = 1; + } + else + { + k++; + } + } + } + + if (pld->key == + NO && pld->opt == NSPropertyListMutableContainersAndLeaves) + { + obj = [GSMutableString alloc]; + obj = [obj initWithCharactersNoCopy: chars + length: length + freeWhenDone: YES]; + } + else + { + obj = [GSMutableString alloc]; + obj = [obj initWithCharactersNoCopy: chars + length: length + freeWhenDone: YES]; + } + } + pld->pos++; + return obj; +} + +static inline id parseUnquotedString(pldata *pld) +{ + unsigned start = pld->pos; + unsigned i; + unsigned length; + id obj; + unichar *chars; + + while (pld->pos < pld->end) + { + if (GS_IS_QUOTABLE(pld->ptr[pld->pos]) == YES) + break; + pld->pos++; + } + + length = pld->pos - start; + chars = NSZoneMalloc(NSDefaultMallocZone(), sizeof(unichar) * length); + for (i = 0; i < length; i++) + { + chars[i] = pld->ptr[start + i]; + } + + if (pld->key == NO && pld->opt == NSPropertyListMutableContainersAndLeaves) + { + obj = [GSMutableString alloc]; + obj = [obj initWithCharactersNoCopy: chars + length: length + freeWhenDone: YES]; + } + else + { + obj = [GSMutableString alloc]; + obj = [obj initWithCharactersNoCopy: chars + length: length + freeWhenDone: YES]; + } + return obj; +} + +static id parsePlItem(pldata* pld) +{ + id result = nil; + BOOL start = (pld->pos == 0 ? YES : NO); + + if (skipSpace(pld) == NO) + { + return nil; + } + switch (pld->ptr[pld->pos]) + { + case '{': + { + NSMutableDictionary *dict; + + dict = [[plDictionary allocWithZone: NSDefaultMallocZone()] + initWithCapacity: 0]; + pld->pos++; + while (skipSpace(pld) == YES && pld->ptr[pld->pos] != '}') + { + id key; + id val; + + pld->key = YES; + key = parsePlItem(pld); + pld->key = NO; + if (key == nil) + { + return nil; + } + if (skipSpace(pld) == NO) + { + RELEASE(key); + RELEASE(dict); + return nil; + } + if (pld->ptr[pld->pos] != '=') + { + pld->err = @"unexpected character (wanted '=')"; + RELEASE(key); + RELEASE(dict); + return nil; + } + pld->pos++; + val = parsePlItem(pld); + if (val == nil) + { + RELEASE(key); + RELEASE(dict); + return nil; + } + if (skipSpace(pld) == NO) + { + RELEASE(key); + RELEASE(val); + RELEASE(dict); + return nil; + } + if (pld->ptr[pld->pos] == ';') + { + pld->pos++; + } + else if (pld->ptr[pld->pos] != '}') + { + pld->err = @"unexpected character (wanted ';' or '}')"; + RELEASE(key); + RELEASE(val); + RELEASE(dict); + return nil; + } + (*plSet)(dict, @selector(setObject:forKey:), val, key); + RELEASE(key); + RELEASE(val); + } + if (pld->pos >= pld->end) + { + pld->err = @"unexpected end of string when parsing dictionary"; + RELEASE(dict); + return nil; + } + pld->pos++; + result = dict; + if (pld->opt == NSPropertyListImmutable) + { + [result makeImmutableCopyOnFail: NO]; + } + } + break; + + case '(': + { + NSMutableArray *array; + + array = [[plArray allocWithZone: NSDefaultMallocZone()] + initWithCapacity: 0]; + pld->pos++; + while (skipSpace(pld) == YES && pld->ptr[pld->pos] != ')') + { + id val; + + val = parsePlItem(pld); + if (val == nil) + { + RELEASE(array); + return nil; + } + if (skipSpace(pld) == NO) + { + RELEASE(val); + RELEASE(array); + return nil; + } + if (pld->ptr[pld->pos] == ',') + { + pld->pos++; + } + else if (pld->ptr[pld->pos] != ')') + { + pld->err = @"unexpected character (wanted ',' or ')')"; + RELEASE(val); + RELEASE(array); + return nil; + } + (*plAdd)(array, @selector(addObject:), val); + RELEASE(val); + } + if (pld->pos >= pld->end) + { + pld->err = @"unexpected end of string when parsing array"; + RELEASE(array); + return nil; + } + pld->pos++; + result = array; + if (pld->opt == NSPropertyListImmutable) + { + [result makeImmutableCopyOnFail: NO]; + } + } + break; + + case '<': + pld->pos++; + if (pld->pos < pld->end && pld->ptr[pld->pos] == '*') + { + const unsigned char *ptr; + unsigned min; + unsigned len = 0; + unsigned i; + + pld->old = NO; + pld->pos++; + min = pld->pos; + ptr = &(pld->ptr[min]); + while (pld->pos < pld->end && pld->ptr[pld->pos] != '>') + { + pld->pos++; + } + len = pld->pos - min; + if (len > 1) + { + unsigned char type = *ptr++; + + len--; + if (type == 'I') + { + char buf[len+1]; + + for (i = 0; i < len; i++) buf[i] = (char)ptr[i]; + buf[len] = '\0'; + result = [[NSNumber alloc] initWithLong: atol(buf)]; + } + else if (type == 'B') + { + if (ptr[0] == 'Y') + { + result = [[NSNumber alloc] initWithBool: YES]; + } + else if (ptr[0] == 'N') + { + result = [[NSNumber alloc] initWithBool: NO]; + } + else + { + pld->err = @"bad value for bool"; + return nil; + } + } + else if (type == 'D') + { + unichar buf[len]; + unsigned i; + NSString *str; + + for (i = 0; i < len; i++) buf[i] = ptr[i]; + str = [[NSString alloc] initWithCharacters: buf + length: len]; + result = [[NSCalendarDate alloc] initWithString: str + calendarFormat: @"%Y-%m-%d %H:%M:%S %z"]; + RELEASE(str); + } + else if (type == 'R') + { + unichar buf[len]; + double d = 0.0; + + for (i = 0; i < len; i++) buf[i] = ptr[i]; + GSScanDouble(buf, len, &d); + result = [[NSNumber alloc] initWithDouble: d]; + } + else + { + pld->err = @"unrecognized type code after '<*'"; + return nil; + } + } + else + { + pld->err = @"missing type code after '<*'"; + return nil; + } + if (pld->pos >= pld->end) + { + pld->err = @"unexpected end of string when parsing data"; + return nil; + } + if (pld->ptr[pld->pos] != '>') + { + pld->err = @"unexpected character in string"; + return nil; + } + pld->pos++; + } + else + { + NSMutableData *data; + unsigned max = pld->end - 1; + unsigned char buf[BUFSIZ]; + unsigned len = 0; + + data = [[NSMutableData alloc] initWithCapacity: 0]; + skipSpace(pld); + while (pld->pos < max + && GS_IS_HEXDIGIT(pld->ptr[pld->pos]) + && GS_IS_HEXDIGIT(pld->ptr[pld->pos+1])) + { + unsigned char byte; + + byte = (char2num(pld->ptr[pld->pos])) << 4; + pld->pos++; + byte |= char2num(pld->ptr[pld->pos]); + pld->pos++; + buf[len++] = byte; + if (len == sizeof(buf)) + { + [data appendBytes: buf length: len]; + len = 0; + } + skipSpace(pld); + } + if (pld->pos >= pld->end) + { + pld->err = @"unexpected end of string when parsing data"; + RELEASE(data); + return nil; + } + if (pld->ptr[pld->pos] != '>') + { + pld->err = @"unexpected character in string"; + RELEASE(data); + return nil; + } + if (len > 0) + { + [data appendBytes: buf length: len]; + } + pld->pos++; + // FIXME ... should be immutable sometimes. + result = data; + } + break; + + case '"': + result = parseQuotedString(pld); + break; + + default: + result = parseUnquotedString(pld); + break; + } + if (start == YES && result != nil) + { + if (skipSpace(pld) == YES) + { + pld->err = @"extra data after parsed string"; + result = nil; // Not at end of string. + } + } + return result; +} + +#ifdef HAVE_LIBXML +static GSXMLNode* +elementNode(GSXMLNode* node) +{ + while (node != nil) + { + if ([node type] == XML_ELEMENT_NODE) + { + break; + } + node = [node next]; + } + return node; +} + +static id +nodeToObject(GSXMLNode* node, NSPropertyListMutabilityOptions o, NSString **e) +{ + CREATE_AUTORELEASE_POOL(arp); + id result = nil; + + node = elementNode(node); + if (node != nil) + { + NSString *name; + NSString *content; + GSXMLNode *children; + BOOL isKey = NO; + + name = [node name]; + children = [node firstChild]; + content = [children content]; + children = elementNode(children); + + isKey = [name isEqualToString: @"key"]; + if (isKey == YES || [name isEqualToString: @"string"] == YES) + { + if (content == nil) + { + content = @""; + } + else + { + NSRange r; + + r = [content rangeOfString: @"\\"]; + if (r.length == 1) + { + unsigned len = [content length]; + unichar buf[len]; + unsigned pos = r.location; + + [content getCharacters: buf]; + while (pos < len) + { + if (++pos < len) + { + if ((buf[pos] == 'u' || buf[pos] == 'U') + && (len >= pos + 4)) + { + unichar val = 0; + unsigned i; + BOOL ok = YES; + + for (i = 1; i < 5; i++) + { + unichar c = buf[pos + i]; + + if (c >= '0' && c <= '9') + { + val = (val << 4) + c - '0'; + } + else if (c >= 'A' && c <= 'F') + { + val = (val << 4) + c - 'A' + 10; + } + else if (c >= 'a' && c <= 'f') + { + val = (val << 4) + c - 'a' + 10; + } + else + { + ok = NO; + } + } + if (ok == YES) + { + len -= 5; + memcpy(&buf[pos], &buf[pos+5], + (len - pos) * sizeof(unichar)); + buf[pos - 1] = val; + } + } + while (pos < len && buf[pos] != '\\') + { + pos++; + } + } + } + if (isKey == NO + && o == NSPropertyListMutableContainersAndLeaves) + { + content = [NSMutableString stringWithCharacters: buf + length: len]; + } + else + { + content = [NSString stringWithCharacters: buf + length: len]; + } + } + } + result = content; + } + else if ([name isEqualToString: @"true"]) + { + result = [NSNumber numberWithBool: YES]; + } + else if ([name isEqualToString: @"false"]) + { + result = [NSNumber numberWithBool: NO]; + } + else if ([name isEqualToString: @"integer"]) + { + if (content == nil) + { + content = @"0"; + } + result = [NSNumber numberWithInt: [content intValue]]; + } + else if ([name isEqualToString: @"real"]) + { + if (content == nil) + { + content = @"0.0"; + } + result = [NSNumber numberWithDouble: [content doubleValue]]; + } + else if ([name isEqualToString: @"date"]) + { + if (content == nil) + { + content = @""; + } + result = [NSCalendarDate dateWithString: content + calendarFormat: @"%Y-%m-%d %H:%M:%S %z"]; + } + else if ([name isEqualToString: @"data"]) + { + result = [GSMimeDocument decodeBase64String: content]; + if (o == NSPropertyListMutableContainersAndLeaves) + { + result = AUTORELEASE([result mutableCopy]); + } + } + // container class + else if ([name isEqualToString: @"array"]) + { + NSMutableArray *container = [plArray array]; + + while (children != nil) + { + id val; + + val = nodeToObject(children, o, e); + [container addObject: val]; + children = [children nextElement]; + } + result = container; + if (o == NSPropertyListImmutable) + { + [result makeImmutableCopyOnFail: NO]; + } + } + else if ([name isEqualToString: @"dict"]) + { + NSMutableDictionary *container = [plDictionary dictionary]; + + while (children != nil) + { + NSString *key; + id val; + + key = nodeToObject(children, o, e); + children = [children nextElement]; + val = nodeToObject(children, o, e); + children = [children nextElement]; + [container setObject: val forKey: key]; + } + result = container; + if (o == NSPropertyListImmutable) + { + [result makeImmutableCopyOnFail: NO]; + } + } + } + RETAIN(result); + RELEASE(arp); + return AUTORELEASE(result); +} +#endif + +static id +GSPropertyListFromStringsFormat(NSString *string) +{ + NSMutableDictionary *dict; + pldata _pld; + pldata *pld = &_pld; + unsigned length = [string length]; + NSData *d; + + /* + * An empty string is a nil property list. + */ + if (length == 0) + { + return nil; + } + + d = [string dataUsingEncoding: NSUnicodeStringEncoding]; + _pld.ptr = (unsigned char*)[d bytes]; + _pld.pos = 1; + _pld.end = length + 1; + _pld.err = nil; + _pld.lin = 1; + _pld.opt = NSPropertyListImmutable; + _pld.key = NO; + _pld.old = YES; // OpenStep style + [NSPropertyListSerialization class]; // initialise + + dict = [[plDictionary allocWithZone: NSDefaultMallocZone()] + initWithCapacity: 0]; + while (skipSpace(pld) == YES) + { + id key; + id val; + + if (pld->ptr[pld->pos] == '"') + { + key = parseQuotedString(pld); + } + else + { + key = parseUnquotedString(pld); + } + if (key == nil) + { + DESTROY(dict); + break; + } + if (skipSpace(pld) == NO) + { + pld->err = @"incomplete final entry (no semicolon?)"; + RELEASE(key); + DESTROY(dict); + break; + } + if (pld->ptr[pld->pos] == ';') + { + pld->pos++; + (*plSet)(dict, @selector(setObject:forKey:), @"", key); + RELEASE(key); + } + else if (pld->ptr[pld->pos] == '=') + { + pld->pos++; + if (skipSpace(pld) == NO) + { + RELEASE(key); + DESTROY(dict); + break; + } + if (pld->ptr[pld->pos] == '"') + { + val = parseQuotedString(pld); + } + else + { + val = parseUnquotedString(pld); + } + if (val == nil) + { + RELEASE(key); + DESTROY(dict); + break; + } + if (skipSpace(pld) == NO) + { + pld->err = @"missing final semicolon"; + RELEASE(key); + RELEASE(val); + DESTROY(dict); + break; + } + (*plSet)(dict, @selector(setObject:forKey:), val, key); + RELEASE(key); + RELEASE(val); + if (pld->ptr[pld->pos] == ';') + { + pld->pos++; + } + else + { + pld->err = @"unexpected character (wanted ';')"; + DESTROY(dict); + break; + } + } + else + { + pld->err = @"unexpected character (wanted '=' or ';')"; + RELEASE(key); + DESTROY(dict); + break; + } + } + if (dict == nil && _pld.err != nil) + { + RELEASE(dict); + [NSException raise: NSGenericException + format: @"Parse failed at line %d (char %d) - %@", + _pld.lin, _pld.pos, _pld.err]; + } + return AUTORELEASE(dict); +} + + + +#include + +static char base64[] + = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static void +encodeBase64(NSData *source, NSMutableData *dest) +{ + int length = [source length]; + int enclen = length / 3; + int remlen = length - 3 * enclen; + int destlen = 4 * ((length + 2) / 3); + unsigned char *sBuf; + unsigned char *dBuf; + int sIndex = 0; + int dIndex = [dest length]; + + [dest setLength: dIndex + destlen]; + + if (length == 0) + { + return; + } + sBuf = (unsigned char*)[source bytes]; + dBuf = [dest mutableBytes]; + + for (sIndex = 0; sIndex < length - 2; sIndex += 3, dIndex += 4) + { + dBuf[dIndex] = base64[sBuf[sIndex] >> 2]; + dBuf[dIndex + 1] + = base64[((sBuf[sIndex] << 4) | (sBuf[sIndex + 1] >> 4)) & 0x3f]; + dBuf[dIndex + 2] + = base64[((sBuf[sIndex + 1] << 2) | (sBuf[sIndex + 2] >> 6)) & 0x3f]; + dBuf[dIndex + 3] = base64[sBuf[sIndex + 2] & 0x3f]; + } + + if (remlen == 1) + { + dBuf[dIndex] = base64[sBuf[sIndex] >> 2]; + dBuf[dIndex + 1] = (sBuf[sIndex] << 4) & 0x30; + dBuf[dIndex + 1] = base64[dBuf[dIndex + 1]]; + dBuf[dIndex + 2] = '='; + dBuf[dIndex + 3] = '='; + } + else if (remlen == 2) + { + dBuf[dIndex] = base64[sBuf[sIndex] >> 2]; + dBuf[dIndex + 1] = (sBuf[sIndex] << 4) & 0x30; + dBuf[dIndex + 1] |= sBuf[sIndex + 1] >> 4; + dBuf[dIndex + 1] = base64[dBuf[dIndex + 1]]; + dBuf[dIndex + 2] = (sBuf[sIndex + 1] << 2) & 0x3c; + dBuf[dIndex + 2] = base64[dBuf[dIndex + 2]]; + dBuf[dIndex + 3] = '='; + } +} + +static inline void Append(void *bytes, unsigned length, NSMutableData *dst) +{ + [dst appendBytes: bytes length: length]; +} + +/* + * Output a string escaped for OpenStep style property lists. + * The result is ascii data. + */ +static void +PString(NSString *obj, NSMutableData *output) +{ + unsigned length; + + if ((length = [obj length]) == 0) + { + [output appendBytes: "\"\"" length: 2]; + } + else if ([obj rangeOfCharacterFromSet: oldQuotables].length > 0 + || [obj characterAtIndex: 0] == '/') + { + unichar tmp[length <= 1024 ? length : 0]; + unichar *ustring; + unichar *from; + unichar *end; + unsigned char *ptr; + int len = 0; + + if (length <= 1024) + { + ustring = tmp; + } + else + { + ustring = NSZoneMalloc(NSDefaultMallocZone(), length*sizeof(unichar)); + } + end = &ustring[length]; + [obj getCharacters: ustring]; + for (from = ustring; from < end; from++) + { + switch (*from) + { + case '\a': + case '\b': + case '\t': + case '\r': + case '\n': + case '\v': + case '\f': + case '\\': + case '\'' : + case '"' : + len += 2; + break; + + default: + if (*from < 128) + { + if (isprint(*from) || *from == ' ') + { + len++; + } + else + { + len += 4; + } + } + else + { + len += 6; + } + break; + } + } + + [output setLength: [output length] + len + 2]; + ptr = [output mutableBytes] + [output length]; + *ptr++ = '"'; + for (from = ustring; from < end; from++) + { + switch (*from) + { + case '\a': *ptr++ = '\\'; *ptr++ = 'a'; break; + case '\b': *ptr++ = '\\'; *ptr++ = 'b'; break; + case '\t': *ptr++ = '\\'; *ptr++ = 't'; break; + case '\r': *ptr++ = '\\'; *ptr++ = 'r'; break; + case '\n': *ptr++ = '\\'; *ptr++ = 'n'; break; + case '\v': *ptr++ = '\\'; *ptr++ = 'v'; break; + case '\f': *ptr++ = '\\'; *ptr++ = 'f'; break; + case '\\': *ptr++ = '\\'; *ptr++ = '\\'; break; + case '\'': *ptr++ = '\\'; *ptr++ = '\''; break; + case '"' : *ptr++ = '\\'; *ptr++ = '"'; break; + + default: + if (*from < 128) + { + if (isprint(*from) || *from == ' ') + { + *ptr++ = *from; + } + else + { + unichar c = *from; + + sprintf(ptr, "\\%03o", c); + ptr = &ptr[4]; + } + } + else + { + sprintf(ptr, "\\u%04x", *from); + ptr = &ptr[6]; + } + break; + } + } + *ptr++ = '"'; + + if (ustring != tmp) + { + NSZoneFree(NSDefaultMallocZone(), ustring); + } + } + else + { + NSData *d = [obj dataUsingEncoding: NSASCIIStringEncoding]; + + [output appendData: d]; + } +} + +/* + * Output a string escaped for use in xml. + * Result is utf8 data. + */ +static void +XString(NSString* obj, NSMutableData *output) +{ + static char *hexdigits = "0123456789ABCDEF"; + unsigned end; + + end = [obj length]; + if (end == 0) + { + return; + } + + if ([obj rangeOfCharacterFromSet: xmlQuotables].length > 0) + { + unichar *base; + unichar *map; + unichar c; + unsigned len; + unsigned rpos; + unsigned wpos; + + base = NSZoneMalloc(NSDefaultMallocZone(), end * sizeof(unichar)); + [obj getCharacters: base]; + for (len = rpos = 0; rpos < end; rpos++) + { + c = base[rpos]; + switch (c) + { + case '&': + len += 5; + break; + case '<': + case '>': + len += 4; + break; + case '\'': + case '"': + len += 6; + break; + + default: + if ((c < 0x20 && (c != 0x09 && c != 0x0A && c != 0x0D)) + || (c > 0xD7FF && c < 0xE000) || c > 0xFFFD) + { + len += 6; + } + else + { + len++; + } + break; + } + } + map = NSZoneMalloc(NSDefaultMallocZone(), len * sizeof(unichar)); + for (wpos = rpos = 0; rpos < end; rpos++) + { + c = base[rpos]; + switch (c) + { + case '&': + map[wpos++] = '&'; + map[wpos++] = 'a'; + map[wpos++] = 'm'; + map[wpos++] = 'p'; + map[wpos++] = ';'; + break; + case '<': + map[wpos++] = '&'; + map[wpos++] = 'l'; + map[wpos++] = 't'; + map[wpos++] = ';'; + break; + case '>': + map[wpos++] = '&'; + map[wpos++] = 'g'; + map[wpos++] = 't'; + map[wpos++] = ';'; + break; + case '\'': + map[wpos++] = '&'; + map[wpos++] = 'a'; + map[wpos++] = 'p'; + map[wpos++] = 'o'; + map[wpos++] = 's'; + map[wpos++] = ';'; + break; + case '"': + map[wpos++] = '&'; + map[wpos++] = 'q'; + map[wpos++] = 'u'; + map[wpos++] = 'o'; + map[wpos++] = 't'; + map[wpos++] = ';'; + break; + + default: + if ((c < 0x20 && (c != 0x09 && c != 0x0A && c != 0x0D)) + || (c > 0xD7FF && c < 0xE000) || c > 0xFFFD) + { + map[wpos++] = '\\'; + map[wpos++] = 'U'; + map[wpos++] = hexdigits[(c>>12) & 0xf]; + map[wpos++] = hexdigits[(c>>8) & 0xf]; + map[wpos++] = hexdigits[(c>>4) & 0xf]; + map[wpos++] = hexdigits[c & 0xf]; + } + else + { + map[wpos++] = c; + } + break; + } + } + NSZoneFree(NSDefaultMallocZone(), base); + obj = [[NSString alloc] initWithCharacters: map length: len]; + [output appendData: [obj dataUsingEncoding: NSUTF8StringEncoding]]; + RELEASE(obj); + } + else + { + [output appendData: [obj dataUsingEncoding: NSUTF8StringEncoding]]; + } +} + + +static const char *indentStrings[] = { + "", + " ", + " ", + " ", + "\t", + "\t ", + "\t ", + "\t ", + "\t\t", + "\t\t ", + "\t\t ", + "\t\t ", + "\t\t\t", + "\t\t\t ", + "\t\t\t ", + "\t\t\t ", + "\t\t\t\t", + "\t\t\t\t ", + "\t\t\t\t ", + "\t\t\t\t ", + "\t\t\t\t\t", + "\t\t\t\t\t ", + "\t\t\t\t\t ", + "\t\t\t\t\t ", + "\t\t\t\t\t\t" +}; + +#define PLNEW 0 // New extended OpenStep property list +#define PLXML 1 // New MacOS-X XML property list +#define PLOLD 2 // Old (standard) OpenStep property list +#define PLDSC 3 // Just a description +/** + * obj is the object to be written out
+ * loc is the locale for formatting (or nil to indicate no formatting)
+ * lev is the level of indentation to use
+ * step is the indentation step (0 == 0, 1 = 2, 2 = 4, 3 = 8)
+ * x is an indicator for xml or old/new openstep property list format
+ * dest is the output buffer. + */ +static void +OAppend(id obj, NSDictionary *loc, unsigned lev, unsigned step, + NSPropertyListFormat x, NSMutableData *dest) +{ + if ([obj isKindOfClass: [NSString class]]) + { + if (x == NSPropertyListXMLFormat_v1_0) + { + [dest appendBytes: "" length: 8]; + XString(obj, dest); + [dest appendBytes: "\n" length: 10]; + } + else + { + PString(obj, dest); + } + } + else if ([obj isKindOfClass: [NSNumber class]]) + { + const char *t = [obj objCType]; + + if (*t == 'c' || *t == 'C') + { + BOOL val = [obj boolValue]; + + if (val == YES) + { + if (x == NSPropertyListXMLFormat_v1_0) + { + [dest appendBytes: "\n" length: 8]; + } + else if (x == NSPropertyListGNUStepFormat) + { + [dest appendBytes: "<*BY>\n" length: 6]; + } + else + { + PString([obj description], dest); + } + } + else + { + if (x == NSPropertyListXMLFormat_v1_0) + { + [dest appendBytes: "\n" length: 9]; + } + else if (x == NSPropertyListGNUStepFormat) + { + [dest appendBytes: "<*BN>\n" length: 6]; + } + else + { + PString([obj description], dest); + } + } + } + else if (strchr("sSiIlLqQ", *t) != 0) + { + if (x == NSPropertyListXMLFormat_v1_0) + { + [dest appendBytes: "" length: 8]; + XString([obj stringValue], dest); + [dest appendBytes: "\n" length: 11]; + } + else if (x == NSPropertyListGNUStepFormat) + { + [dest appendBytes: "<*I" length: 3]; + PString([obj stringValue], dest); + [dest appendBytes: ">" length: 1]; + } + else + { + PString([obj description], dest); + } + } + else + { + if (x == NSPropertyListXMLFormat_v1_0) + { + [dest appendBytes: "" length: 6]; + XString([obj stringValue], dest); + [dest appendBytes: "" length: 7]; + } + else if (x == NSPropertyListGNUStepFormat) + { + [dest appendBytes: "<*R" length: 3]; + PString([obj stringValue], dest); + [dest appendBytes: ">" length: 1]; + } + else + { + PString([obj description], dest); + } + } + } + else if ([obj isKindOfClass: [NSData class]]) + { + if (x == NSPropertyListXMLFormat_v1_0) + { + [dest appendBytes: "\n" length: 7]; + encodeBase64(obj, dest); + [dest appendBytes: "\n" length: 8]; + } + else + { + const unsigned char *src; + unsigned char *dst; + int length; + int i; + int j; + + src = [obj bytes]; + length = [obj length]; + #define num2char(num) ((num) < 0xa ? ((num)+'0') : ((num)+0x57)) + + j = [dest length]; + [dest setLength: j + 2*length+length/4+3]; + dst = [dest mutableBytes]; + dst[j++] = '<'; + for (i = 0; i < length; i++, j++) + { + dst[j++] = num2char((src[i]>>4) & 0x0f); + dst[j] = num2char(src[i] & 0x0f); + if ((i&0x3) == 3 && i != length-1) + { + /* if we've just finished a 32-bit int, print a space */ + dst[++j] = ' '; + } + } + dst[j++] = '>'; + } + } + else if ([obj isKindOfClass: [NSDate class]]) + { + if (x == NSPropertyListXMLFormat_v1_0) + { + [dest appendBytes: "" length: 6]; + obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z" + timeZone: nil locale: nil]; + obj = [obj dataUsingEncoding: NSASCIIStringEncoding]; + [dest appendData: obj]; + [dest appendBytes: "\n" length: 8]; + } + else if (x == NSPropertyListGNUStepFormat) + { + [dest appendBytes: "<*D" length: 3]; + obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z" + timeZone: nil locale: nil]; + obj = [obj dataUsingEncoding: NSASCIIStringEncoding]; + [dest appendData: obj]; + [dest appendBytes: ">" length: 1]; + } + else + { + obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z" + timeZone: nil locale: nil]; + PString(obj, dest); + } + } + else if ([obj isKindOfClass: [NSArray class]]) + { + const char *iBaseString; + const char *iSizeString; + unsigned level = lev; + + if (level*step < sizeof(indentStrings)/sizeof(id)) + { + iBaseString = indentStrings[level*step]; + } + else + { + iBaseString + = indentStrings[sizeof(indentStrings)/sizeof(id)-1]; + } + level++; + if (level*step < sizeof(indentStrings)/sizeof(id)) + { + iSizeString = indentStrings[level*step]; + } + else + { + iSizeString + = indentStrings[sizeof(indentStrings)/sizeof(id)-1]; + } + + if (x == NSPropertyListXMLFormat_v1_0) + { + NSEnumerator *e; + + [dest appendBytes: "\n" length: 8]; + e = [obj objectEnumerator]; + while ((obj = [e nextObject])) + { + [dest appendBytes: iSizeString length: strlen(iSizeString)]; + OAppend(obj, loc, level, step, x, dest); + } + [dest appendBytes: iBaseString length: strlen(iBaseString)]; + [dest appendBytes: "\n" length: 9]; + } + else + { + unsigned count = [obj count]; + unsigned last = count - 1; + NSString *plists[count]; + unsigned i; + + [obj getObjects: plists]; + + if (loc == nil) + { + [dest appendBytes: "(" length: 1]; + for (i = 0; i < count; i++) + { + id item = plists[i]; + + OAppend(item, nil, 0, step, x, dest); + if (i != last) + { + [dest appendBytes: ", " length: 2]; + } + } + [dest appendBytes: ")" length: 1]; + } + else + { + [dest appendBytes: "(\n" length: 2]; + for (i = 0; i < count; i++) + { + id item = plists[i]; + + [dest appendBytes: iSizeString length: strlen(iSizeString)]; + OAppend(item, loc, level, step, x, dest); + if (i == last) + { + [dest appendBytes: "\n" length: 1]; + } + else + { + [dest appendBytes: ",\n" length: 2]; + } + } + [dest appendBytes: iBaseString length: strlen(iBaseString)]; + [dest appendBytes: ")" length: 1]; + } + } + } + else if ([obj isKindOfClass: [NSDictionary class]]) + { + const char *iBaseString; + const char *iSizeString; + unsigned level = lev; + + if (level*step < sizeof(indentStrings)/sizeof(id)) + { + iBaseString = indentStrings[level*step]; + } + else + { + iBaseString + = indentStrings[sizeof(indentStrings)/sizeof(id)-1]; + } + level++; + if (level*step < sizeof(indentStrings)/sizeof(id)) + { + iSizeString = indentStrings[level*step]; + } + else + { + iSizeString + = indentStrings[sizeof(indentStrings)/sizeof(id)-1]; + } + + if (x == NSPropertyListXMLFormat_v1_0) + { + NSEnumerator *e; + id key; + + [dest appendBytes: "\n" length: 7]; + e = [obj keyEnumerator]; + while ((key = [e nextObject])) + { + id val; + + val = [obj objectForKey: key]; + [dest appendBytes: iSizeString length: strlen(iSizeString)]; + [dest appendBytes: "" length: 5]; + XString(key, dest); + [dest appendBytes: "\n" length: 7]; + [dest appendBytes: iSizeString length: strlen(iSizeString)]; + OAppend(val, loc, level, step, x, dest); + } + [dest appendBytes: iBaseString length: strlen(iBaseString)]; + [dest appendBytes: "\n" length: 8]; + } + else + { + SEL objSel = @selector(objectForKey:); + IMP myObj = [obj methodForSelector: objSel]; + unsigned i; + NSArray *keyArray = [obj allKeys]; + unsigned numKeys = [keyArray count]; + NSString *plists[numKeys]; + NSString *keys[numKeys]; + BOOL canCompare = YES; + Class lastClass = 0; + + [keyArray getObjects: keys]; + + for (i = 0; i < numKeys; i++) + { + if (GSObjCClass(keys[i]) == lastClass) + continue; + if ([keys[i] respondsToSelector: @selector(compare:)] == NO) + { + canCompare = NO; + break; + } + lastClass = GSObjCClass(keys[i]); + } + + if (canCompare == YES) + { + #define STRIDE_FACTOR 3 + unsigned c,d, stride; + BOOL found; + NSComparisonResult (*comp)(id, SEL, id) = 0; + unsigned int count = numKeys; + #ifdef GSWARN + BOOL badComparison = NO; + #endif + + stride = 1; + while (stride <= count) + { + stride = stride * STRIDE_FACTOR + 1; + } + lastClass = 0; + while (stride > (STRIDE_FACTOR - 1)) + { + // loop to sort for each value of stride + stride = stride / STRIDE_FACTOR; + for (c = stride; c < count; c++) + { + found = NO; + if (stride > c) + { + break; + } + d = c - stride; + while (!found) + { + id a = keys[d + stride]; + id b = keys[d]; + Class x; + NSComparisonResult r; + + x = GSObjCClass(a); + if (x != lastClass) + { + lastClass = x; + comp = (NSComparisonResult (*)(id, SEL, id)) + [a methodForSelector: @selector(compare:)]; + } + r = (*comp)(a, @selector(compare:), b); + if (r < 0) + { + #ifdef GSWARN + if (r != NSOrderedAscending) + { + badComparison = YES; + } + #endif + keys[d + stride] = b; + keys[d] = a; + if (stride > d) + { + break; + } + d -= stride; + } + else + { + #ifdef GSWARN + if (r != NSOrderedDescending + && r != NSOrderedSame) + { + badComparison = YES; + } + #endif + found = YES; + } + } + } + } + #ifdef GSWARN + if (badComparison == YES) + { + NSWarnFLog(@"Detected bad return value from comparison"); + } + #endif + } + + for (i = 0; i < numKeys; i++) + { + plists[i] = (*myObj)(obj, objSel, keys[i]); + } + + if (loc == nil) + { + [dest appendBytes: "{" length: 1]; + for (i = 0; i < numKeys; i++) + { + OAppend(keys[i], nil, 0, step, x, dest); + [dest appendBytes: " = " length: 3]; + OAppend(plists[i], nil, 0, step, x, dest); + [dest appendBytes: "; " length: 2]; + } + [dest appendBytes: "}" length: 1]; + } + else + { + [dest appendBytes: "{\n" length: 2]; + for (i = 0; i < numKeys; i++) + { + [dest appendBytes: iSizeString length: strlen(iSizeString)]; + OAppend(keys[i], loc, level, step, x, dest); + [dest appendBytes: " = " length: 3]; + OAppend(plists[i], loc, level, step, x, dest); + [dest appendBytes: ";\n" length: 2]; + } + [dest appendBytes: iBaseString length: strlen(iBaseString)]; + [dest appendBytes: "}" length: 1]; + } + } + } + else + { + if (x == NSPropertyListXMLFormat_v1_0) + { + NSDebugLog(@"Non-property-list class (%@) encoded as string", + NSStringFromClass([obj class])); + [dest appendBytes: "" length: 8]; + XString([obj description], dest); + [dest appendBytes: "" length: 9]; + } + else + { + NSDebugLog(@"Non-property-list class (%@) encoded as string", + NSStringFromClass([obj class])); + PString([obj description], dest); + } + } +} + + + + +@implementation NSPropertyListSerialization + ++ (void) initialize +{ + static BOOL beenHere = NO; + + if (beenHere == NO) + { + beenHere = YES; + +#ifdef HAVE_LIBXML + /* + * Cache XML node information. + */ + XML_ELEMENT_NODE = [GSXMLNode typeFromDescription: @"XML_ELEMENT_NODE"]; +#endif + + NSStringClass = [NSString class]; + NSMutableStringClass = [NSMutableString class]; + NSDataClass = [NSData class]; + GSStringClass = [GSString class]; + GSMutableStringClass = [GSMutableString class]; + + plArray = [GSMutableArray class]; + plAdd = (id (*)(id, SEL, id)) + [plArray instanceMethodForSelector: @selector(addObject:)]; + + plDictionary = [GSMutableDictionary class]; + plSet = (id (*)(id, SEL, id, id)) + [plDictionary instanceMethodForSelector: @selector(setObject:forKey:)]; + + setupHexdigits(); + setupQuotables(); + setupWhitespace(); + + } +} + ++ (NSData*) dataFromPropertyList: (id)aPropertyList + format: (NSPropertyListFormat)aFormat + errorDescription: (NSString**)anErrorString +{ + NSMutableData *dest; + NSDictionary *loc; + int step = 2; + + loc = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; + dest = [NSMutableData dataWithCapacity: 1024]; + + if (aFormat == NSPropertyListXMLFormat_v1_0) + { + const char *prefix = + "\n\n" + "\n"; + + [dest appendBytes: prefix length: strlen(prefix)]; + OAppend(aPropertyList, loc, 0, step > 3 ? 3 : step, aFormat, dest); + [dest appendBytes: "" length: 8]; + } + else if (aFormat == NSPropertyListGNUstepBinaryFormat) + { + [NSSerializer serializePropertyList: aPropertyList intoData: dest]; + } + else + { + OAppend(aPropertyList, loc, 0, step > 3 ? 3 : step, aFormat, dest); + } + return dest; +} + ++ (BOOL) propertyList: (id)aPropertyList + isValidForFormat: (NSPropertyListFormat)aFormat +{ +// FIXME ... need to check properly. + switch (aFormat) + { + case NSPropertyListGNUStepFormat: + return YES; + + case NSPropertyListGNUstepBinaryFormat: + return YES; + + case NSPropertyListOpenStepFormat: + return YES; + + case NSPropertyListXMLFormat_v1_0: + return YES; + + case NSPropertyListBinaryFormat_v1_0: + default: + [NSException raise: NSInvalidArgumentException + format: @"[%@ +%@]: unsupported format", + NSStringFromClass(self), NSStringFromSelector(_cmd)]; + return NO; + } +} + ++ (id) propertyListFromData: (NSData*)data + mutabilityOption: (NSPropertyListMutabilityOptions)anOption + format: (NSPropertyListFormat*)aFormat + errorDescription: (NSString**)anErrorString +{ + NSPropertyListFormat format; + NSString *error = nil; + id result = nil; + const unsigned char *bytes = 0; + unsigned int length = 0; + + if (data == nil) + { + error = @"nil data argument passed to method"; + } + else if ([data isKindOfClass: [NSData class]] == NO) + { + error = @"non-NSData data argument passed to method"; + } + else if ([data length] == 0) + { + error = @"empty data argument passed to method"; + } + else + { + bytes = [data bytes]; + length = [data length]; + if (bytes[0] == 0 || bytes[0] == 1) + { + format = NSPropertyListGNUstepBinaryFormat; + } + else + { + unsigned int index = 0; + + // Skip any leading white space. + while (index < length && GS_IS_WHITESPACE(bytes[index]) == YES) + { + index++; + } + + if (length - index > 2 + && bytes[index] == '<' && bytes[index+1] == '?') + { +#ifdef HAVE_LIBXML + // It begins with '