/** Permit writing of OpenStep property lists. Copyright (C) 2010 Free Software Foundation, Inc. Written by: Richard Frith-Macdonald This file is part of the GNUstep Objective-C Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. $Date: 2010-02-17 11:47:06 +0000 (Wed, 17 Feb 2010) $ $Revision: 29657 $ */ #import "common.h" #import "Foundation/NSArray.h" #import "Foundation/NSCharacterSet.h" #import "Foundation/NSData.h" #import "Foundation/NSDate.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSPropertyList.h" #import "Foundation/NSString.h" #import "Foundation/NSTimeZone.h" #import "Foundation/NSUserDefaults.h" #import "Foundation/NSValue.h" #import "GNUstepBase/GSObjCRuntime.h" #if defined(NeXT_Foundation_LIBRARY) static IMP originalImp = 0; static NSCharacterSet *quotables = nil; static Class NSArrayClass; static Class NSDataClass; static Class NSDateClass; static Class NSDictionaryClass; static Class NSNumberClass; static Class NSStringClass; static void setup() { if (quotables == nil) { NSMutableCharacterSet *s; s = [[NSCharacterSet characterSetWithCharactersInString: @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" @"abcdefghijklmnopqrstuvwxyz$./_"] mutableCopy]; [s invert]; quotables = [s copy]; [s release]; NSArrayClass = [NSArray class]; NSDataClass = [NSData class]; NSDateClass = [NSDate class]; NSDictionaryClass = [NSDictionary class]; NSNumberClass = [NSNumber class]; NSStringClass = [NSString class]; } } /* * 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: quotables].length > 0 || [obj characterAtIndex: 0] == '/') { unichar tmp[length <= 1024 ? length : 0]; unichar *ustring; unichar *from; unichar *end; unsigned char *ptr; int base = [output length]; int len = 0; if (length <= 1024) { ustring = tmp; } else { ustring = NSAllocateCollectable(sizeof(unichar) * length, 0); } end = &ustring[length]; [obj getCharacters: ustring]; for (from = ustring; from < end; from++) { switch (*from) { case '\t': case '\r': case '\n': len++; break; case '\a': case '\b': case '\v': case '\f': case '\\': case '"' : len += 2; break; default: if (*from < 128) { if (isprint(*from) || *from == ' ') { len++; } else { len += 4; } } else { len += 6; } break; } } [output setLength: base + len + 2]; ptr = [output mutableBytes] + base; *ptr++ = '"'; for (from = ustring; from < end; from++) { switch (*from) { case '\t': case '\r': case '\n': *ptr++ = *from; break; case '\a': *ptr++ = '\\'; *ptr++ = 'a'; break; case '\b': *ptr++ = '\\'; *ptr++ = 'b'; break; case '\v': *ptr++ = '\\'; *ptr++ = 'v'; break; case '\f': *ptr++ = '\\'; *ptr++ = 'f'; break; case '\\': *ptr++ = '\\'; *ptr++ = '\\'; break; case '"' : *ptr++ = '\\'; *ptr++ = '"'; break; default: if (*from < 128) { if (isprint(*from) || *from == ' ') { *ptr++ = *from; } else { unichar c = *from; *ptr++ = '\\'; ptr[2] = (c & 7) + '0'; c >>= 3; ptr[1] = (c & 7) + '0'; c >>= 3; ptr[0] = (c & 7) + '0'; ptr += 3; } } else { unichar c = *from; *ptr++ = '\\'; *ptr++ = 'U'; ptr[3] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48; c >>= 4; ptr[2] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48; c >>= 4; ptr[1] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48; c >>= 4; ptr[0] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48; ptr += 4; } break; } } *ptr++ = '"'; if (ustring != tmp) { NSZoneFree(NSDefaultMallocZone(), ustring); } } else { NSData *d = [obj dataUsingEncoding: NSASCIIStringEncoding]; [output appendData: d]; } } 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" }; /** * 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)
* dest is the output buffer. */ static void OAppend(id obj, NSDictionary *loc, unsigned lev, unsigned step, NSMutableData *dest) { if ([obj isKindOfClass: NSStringClass]) { PString(obj, dest); } else if ([obj isKindOfClass: NSNumberClass]) { const char *t = [obj objCType]; if (*t == 'c' || *t == 'C') { BOOL val = [obj boolValue]; if (val == YES) { PString([obj description], dest); } else { PString([obj description], dest); } } else if (strchr("sSiIlLqQ", *t) != 0) { PString([obj description], dest); } else { PString([obj description], dest); } } else if ([obj isKindOfClass: NSDataClass]) { 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 ? (length-1)/4+2 : 2)]; 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 & 3) == 3 && i < length-1) { /* if we've just finished a 32-bit int, print a space */ dst[++j] = ' '; } } dst[j++] = '>'; } else if ([obj isKindOfClass: NSDateClass]) { static NSTimeZone *z = nil; if (z == nil) { z = RETAIN([NSTimeZone timeZoneForSecondsFromGMT: 0]); } obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z" timeZone: z locale: nil]; PString(obj, dest); } else if ([obj isKindOfClass: NSArrayClass]) { 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]; } { unsigned count = [obj count]; unsigned last = count - 1; NSString *plists[count]; unsigned i; if ([obj isProxy] == YES) { for (i = 0; i < count; i++) { plists[i] = [obj objectAtIndex: i]; } } else { [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, 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, 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: NSDictionaryClass]) { const char *iBaseString; const char *iSizeString; 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; unsigned level = lev; BOOL isProxy = [obj isProxy]; 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 (isProxy == YES) { for (i = 0; i < numKeys; i++) { keys[i] = [keyArray objectAtIndex: i]; plists[i] = [(NSDictionary*)obj objectForKey: keys[i]]; } } else { [keyArray getObjects: keys]; for (i = 0; i < numKeys; i++) { plists[i] = (*myObj)(obj, objSel, keys[i]); } } if (numKeys == 0) { canCompare = NO; } else { /* All keys must respond to -compare: for sorting. */ lastClass = NSStringClass; for (i = 0; i < numKeys; i++) { if (object_getClass(keys[i]) == lastClass) continue; if ([keys[i] isKindOfClass: NSStringClass] == NO) { canCompare = NO; break; } lastClass = object_getClass(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 = object_getClass(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 /* Swap keys and values. */ keys[d + stride] = b; keys[d] = a; a = plists[d + stride]; b = plists[d]; plists[d + stride] = b; plists[d] = a; if (stride > d) { break; } d -= stride; } else { #ifdef GSWARN if (r != NSOrderedDescending && r != NSOrderedSame) { badComparison = YES; } #endif found = YES; } } } } } if (loc == nil) { [dest appendBytes: "{" length: 1]; for (i = 0; i < numKeys; i++) { OAppend(keys[i], nil, 0, step, dest); [dest appendBytes: " = " length: 3]; OAppend(plists[i], nil, 0, step, 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, dest); [dest appendBytes: " = " length: 3]; OAppend(plists[i], loc, level, step, dest); [dest appendBytes: ";\n" length: 2]; } [dest appendBytes: iBaseString length: strlen(iBaseString)]; [dest appendBytes: "}" length: 1]; } } else { NSString *cls; if (obj == nil) { obj = @"(nil)"; cls = @"(nil)"; } else { cls = NSStringFromClass([obj class]); } PString([obj description], dest); } } @interface NSPropertyListSerialization (GNUstepAdditions) + (NSData*) _dataFromPropertyList: (id)aPropertyList format: (NSPropertyListFormat)aFormat errorDescription: (NSString**)anErrorString; + (void) load; @end @implementation NSPropertyListSerialization (GNUstepAdditions) + (NSData*) _dataFromPropertyList: (id)aPropertyList format: (NSPropertyListFormat)aFormat errorDescription: (NSString**)anErrorString { if (aFormat == NSPropertyListOpenStepFormat) { NSMutableData *dest; NSDictionary *loc; int step = 2; loc = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]; dest = [NSMutableData dataWithCapacity: 1024]; if (nil == quotables) { setup(); } OAppend(aPropertyList, loc, 0, step > 3 ? 3 : step, dest); return dest; } return (*originalImp)(self, _cmd, aPropertyList, aFormat, anErrorString); } + (void) load { Method replacementMethod; replacementMethod = class_getClassMethod(self, @selector(_dataFromPropertyList:format:errorDescription:)); originalImp = class_replaceMethod(object_getClass(self), @selector(dataFromPropertyList:format:errorDescription:), method_getImplementation(replacementMethod), method_getTypeEncoding(replacementMethod)); } @end #endif