libs-base/Source/Additions/NSPropertyList+GNUstepBase.m
Richard Frith-MacDonald fb4c91aeb4 Attempt to support OpenStep style property lists on OSX
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@30520 72102866-910b-0410-8b05-ffd578937521
2010-06-01 09:43:28 +00:00

662 lines
14 KiB
Objective-C

/** Permit writing of OpenStep property lists.
Copyright (C) 2010 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
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<br />
* loc is the locale for formatting (or nil to indicate no formatting)<br />
* lev is the level of indentation to use<br />
* step is the indentation step (0 == 0, 1 = 2, 2 = 4, 3 = 8)<br />
* 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