1/*
2 * GSPropertyListSerialization.m
3 *
4 * Copyright (C) 2003,2004 Free Software Foundation, Inc.
5 * Written by:       Richard Frith-Macdonald <rfm@gnu.org>
6 *                   Fred Kiefer <FredKiefer@gmx.de>
7 * Modifications by: Georg Fleischmann
8 *
9 * This class has been extracted from the GNUstep implementation of
10 * NSPropertyList to achieve best compatibility of the Property List
11 * formats between Mac OS X, GNUstep, OpenStep...
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Library General Public
15 * License as published by the Free Software Foundation; either
16 * version 2 of the License, or (at your option) any later version.
17 *
18 * created:  2008-03-06 (extracted from NSPropertyList.m)
19 * modified: 2008-03-09
20 */
21
22#include "GSPropertyListSerialization.h"
23
24@implementation GSPropertyListSerialization
25
26static const char	*indentStrings[] = {
27    "",
28    "  ",
29    "    ",
30    "      ",
31    "\t",
32    "\t  ",
33    "\t    ",
34    "\t      ",
35    "\t\t",
36    "\t\t  ",
37    "\t\t    ",
38    "\t\t      ",
39    "\t\t\t",
40    "\t\t\t  ",
41    "\t\t\t    ",
42    "\t\t\t      ",
43    "\t\t\t\t",
44    "\t\t\t\t  ",
45    "\t\t\t\t    ",
46    "\t\t\t\t      ",
47    "\t\t\t\t\t",
48    "\t\t\t\t\t  ",
49    "\t\t\t\t\t    ",
50    "\t\t\t\t\t      ",
51    "\t\t\t\t\t\t"
52};
53
54/*
55 * Cache classes and method implementations for speed.
56 */
57static Class    NSDataClass;
58static Class    NSStringClass;
59static Class    NSMutableStringClass;
60
61static Class    plArray;
62static id       (*plAdd)(id, SEL, id) = 0;
63static Class    plDictionary;
64static id       (*plSet)(id, SEL, id, id) = 0;
65
66
67#define IS_BIT_SET(a,i) ((((a) & (1<<(i)))) > 0)
68
69static NSCharacterSet *quotables = nil;
70static NSCharacterSet *oldQuotables = nil;
71static NSCharacterSet *xmlQuotables = nil;
72
73//static unsigned const char *quotablesBitmapRep = NULL;
74//#define GS_IS_QUOTABLE(X) IS_BIT_SET(quotablesBitmapRep[(X)/8], (X) % 8)
75
76static void setupQuotables(void)
77{
78    if (oldQuotables == nil)
79    {   NSMutableCharacterSet   *s;
80        //NSData                  *bitmap;
81
82        s = [[NSCharacterSet characterSetWithCharactersInString:
83                             @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
84                             @"abcdefghijklmnopqrstuvwxyz!#$%&*+-./:?@|~_^"] mutableCopy];
85        [s invert];
86        quotables = [s copy];
87        [s release];
88
89        /*bitmap = RETAIN([quotables bitmapRepresentation]);
90        quotablesBitmapRep = [bitmap bytes];*/
91
92        s = [[NSCharacterSet characterSetWithCharactersInString:
93                             @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
94                             @"abcdefghijklmnopqrstuvwxyz$./_"] mutableCopy];
95        [s invert];
96        oldQuotables = [s copy];
97        [s release];
98
99        s = [[NSCharacterSet characterSetWithCharactersInString:
100                             @"&<>'\\\""] mutableCopy];
101        [s addCharactersInRange: NSMakeRange(0x0001, 0x001f)];
102        [s removeCharactersInRange: NSMakeRange(0x0009, 0x0002)];
103        [s removeCharactersInRange: NSMakeRange(0x000D, 0x0001)];
104        [s addCharactersInRange: NSMakeRange(0xD800, 0x07FF)];
105        [s addCharactersInRange: NSMakeRange(0xFFFE, 0x0002)];
106        xmlQuotables = [s copy];
107        [s release];
108    }
109}
110
111static char base64[]
112= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
113
114static void encodeBase64(NSData *source, NSMutableData *dest)
115{   int		length = [source length];
116    int		enclen = length / 3;
117    int		remlen = length - 3 * enclen;
118    int		destlen = 4 * ((length + 2) / 3);
119    unsigned char *sBuf;
120    unsigned char *dBuf;
121    int		sIndex = 0;
122    int		dIndex = [dest length];
123
124    [dest setLength: dIndex + destlen];
125
126    if (length == 0)
127        return;
128    sBuf = (unsigned char*)[source bytes];
129    dBuf = [dest mutableBytes];
130
131    for (sIndex = 0; sIndex < length - 2; sIndex += 3, dIndex += 4)
132    {
133        dBuf[dIndex] = base64[sBuf[sIndex] >> 2];
134        dBuf[dIndex + 1] = base64[((sBuf[sIndex] << 4) | (sBuf[sIndex + 1] >> 4)) & 0x3f];
135        dBuf[dIndex + 2] = base64[((sBuf[sIndex + 1] << 2) | (sBuf[sIndex + 2] >> 6)) & 0x3f];
136        dBuf[dIndex + 3] = base64[sBuf[sIndex + 2] & 0x3f];
137    }
138
139    if (remlen == 1)
140    {
141        dBuf[dIndex] = base64[sBuf[sIndex] >> 2];
142        dBuf[dIndex + 1] = (sBuf[sIndex] << 4) & 0x30;
143        dBuf[dIndex + 1] = base64[dBuf[dIndex + 1]];
144        dBuf[dIndex + 2] = '=';
145        dBuf[dIndex + 3] = '=';
146    }
147    else if (remlen == 2)
148    {
149        dBuf[dIndex] = base64[sBuf[sIndex] >> 2];
150        dBuf[dIndex + 1] = (sBuf[sIndex] << 4) & 0x30;
151        dBuf[dIndex + 1] |= sBuf[sIndex + 1] >> 4;
152        dBuf[dIndex + 1] = base64[dBuf[dIndex + 1]];
153        dBuf[dIndex + 2] = (sBuf[sIndex + 1] << 2) & 0x3c;
154        dBuf[dIndex + 2] = base64[dBuf[dIndex + 2]];
155        dBuf[dIndex + 3] = '=';
156    }
157}
158
159/*
160 * Output a string escaped for OpenStep style property lists.
161 * The result is ascii data.
162 */
163static void PString(NSString *obj, NSMutableData *output)
164{   unsigned	length;
165
166    if ((length = [obj length]) == 0)
167        [output appendBytes: "\"\"" length: 2];
168    else if (   [obj rangeOfCharacterFromSet: oldQuotables].length > 0
169             || [obj characterAtIndex: 0] == '/')
170    {   unichar         tmp[length <= 1024 ? length : 0];
171        unichar         *ustring;
172        unichar         *from;
173        unichar         *end;
174        unsigned char   *ptr;
175        int             base = [output length];
176        int             len = 0;
177
178        if (length <= 1024)
179            ustring = tmp;
180        else
181            ustring = NSZoneMalloc(NSDefaultMallocZone(), length*sizeof(unichar));
182        end = &ustring[length];
183        [obj getCharacters: ustring];
184        for (from = ustring; from < end; from++)
185        {
186            switch (*from)
187            {
188                case '\t':
189                case '\r':
190                case '\n':
191                    len++;
192                    break;
193
194                case '\a':
195                case '\b':
196                case '\v':
197                case '\f':
198                case '\\':
199                case '"' :
200                    len += 2;
201                    break;
202
203                default:
204                    if (*from < 128)
205                    {
206                        if (isprint(*from) || *from == ' ')
207                            len++;
208                        else
209                            len += 4;
210                    }
211                    else
212                        len += 6;
213                    break;
214            }
215        }
216
217        [output setLength: base + len + 2];
218        ptr = [output mutableBytes] + base;
219        *ptr++ = '"';
220        for (from = ustring; from < end; from++)
221        {
222            switch (*from)
223            {
224                case '\t':
225                case '\r':
226                case '\n':
227                    *ptr++ = *from;
228                    break;
229
230                case '\a': 	*ptr++ = '\\'; *ptr++ = 'a';  break;
231                case '\b': 	*ptr++ = '\\'; *ptr++ = 'b';  break;
232                case '\v': 	*ptr++ = '\\'; *ptr++ = 'v';  break;
233                case '\f': 	*ptr++ = '\\'; *ptr++ = 'f';  break;
234                case '\\': 	*ptr++ = '\\'; *ptr++ = '\\'; break;
235                case '"' : 	*ptr++ = '\\'; *ptr++ = '"';  break;
236
237                default:
238                    if (*from < 128)
239                    {
240                        if (isprint(*from) || *from == ' ')
241                            *ptr++ = *from;
242                        else
243                        {   unichar	c = *from;
244
245                            *ptr++ = '\\';
246                            ptr[2] = (c & 7) + '0';
247                            c >>= 3;
248                            ptr[1] = (c & 7) + '0';
249                            c >>= 3;
250                            ptr[0] = (c & 7) + '0';
251                            ptr += 3;
252                        }
253                    }
254                    else
255                    {   unichar	c = *from;
256
257                        *ptr++ = '\\';
258                        *ptr++ = 'U';
259                        ptr[3] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48;
260                        c >>= 4;
261                        ptr[2] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48;
262                        c >>= 4;
263                        ptr[1] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48;
264                        c >>= 4;
265                        ptr[0] = (c & 15) > 9 ? (c & 15) + 55 : (c & 15) + 48;
266                        ptr += 4;
267                    }
268                    break;
269            }
270        }
271        *ptr++ = '"';
272
273        if (ustring != tmp)
274            NSZoneFree(NSDefaultMallocZone(), ustring);
275    }
276    else
277    {   NSData	*d = [obj dataUsingEncoding: NSASCIIStringEncoding];
278
279        [output appendData: d];
280    }
281}
282
283/*
284 * Output a string escaped for use in xml.
285 * Result is utf8 data.
286 */
287static void
288XString(NSString* obj, NSMutableData *output)
289{   static char	*hexdigits = "0123456789ABCDEF";
290    unsigned	end;
291
292    end = [obj length];
293    if (end == 0)
294        return;
295
296    if ([obj rangeOfCharacterFromSet: xmlQuotables].length > 0)
297    {   unichar	*base;
298        unichar	*map;
299        unichar	c;
300        unsigned	len;
301        unsigned	rpos;
302        unsigned	wpos;
303
304        base = NSZoneMalloc(NSDefaultMallocZone(), end * sizeof(unichar));
305        [obj getCharacters: base];
306        for (len = rpos = 0; rpos < end; rpos++)
307        {
308            c = base[rpos];
309            switch (c)
310            {
311                case '&':
312                    len += 5;
313                    break;
314                case '<':
315                case '>':
316                    len += 4;
317                    break;
318                case '\'':
319                case '"':
320                    len += 6;
321                    break;
322                default:
323                    if (   (c < 0x20 && (c != 0x09 && c != 0x0A && c != 0x0D))
324                        || (c > 0xD7FF && c < 0xE000) || c > 0xFFFD)
325                    {
326                        len += 6;
327                    }
328                    else
329                        len++;
330                    break;
331            }
332        }
333        map = NSZoneMalloc(NSDefaultMallocZone(), len * sizeof(unichar));
334        for (wpos = rpos = 0; rpos < end; rpos++)
335        {
336            c = base[rpos];
337            switch (c)
338            {
339                case '&':
340                    map[wpos++] = '&';
341                    map[wpos++] = 'a';
342                    map[wpos++] = 'm';
343                    map[wpos++] = 'p';
344                    map[wpos++] = ';';
345                    break;
346                case '<':
347                    map[wpos++] = '&';
348                    map[wpos++] = 'l';
349                    map[wpos++] = 't';
350                    map[wpos++] = ';';
351                    break;
352                case '>':
353                    map[wpos++] = '&';
354                    map[wpos++] = 'g';
355                    map[wpos++] = 't';
356                    map[wpos++] = ';';
357                    break;
358                case '\'':
359                    map[wpos++] = '&';
360                    map[wpos++] = 'a';
361                    map[wpos++] = 'p';
362                    map[wpos++] = 'o';
363                    map[wpos++] = 's';
364                    map[wpos++] = ';';
365                    break;
366                case '"':
367                    map[wpos++] = '&';
368                    map[wpos++] = 'q';
369                    map[wpos++] = 'u';
370                    map[wpos++] = 'o';
371                    map[wpos++] = 't';
372                    map[wpos++] = ';';
373                    break;
374                default:
375                    if ((c < 0x20 && (c != 0x09 && c != 0x0A && c != 0x0D))
376                        || (c > 0xD7FF && c < 0xE000) || c > 0xFFFD)
377                    {
378                        map[wpos++] = '\\';
379                        map[wpos++] = 'U';
380                        map[wpos++] = hexdigits[(c>>12) & 0xf];
381                        map[wpos++] = hexdigits[(c>>8) & 0xf];
382                        map[wpos++] = hexdigits[(c>>4) & 0xf];
383                        map[wpos++] = hexdigits[c & 0xf];
384                    }
385                    else
386                        map[wpos++] = c;
387                    break;
388            }
389        }
390        NSZoneFree(NSDefaultMallocZone(), base);
391        obj = [[NSString alloc] initWithCharacters: map length: len];
392        [output appendData: [obj dataUsingEncoding: NSUTF8StringEncoding]];
393        [obj release];
394    }
395    else
396        [output appendData: [obj dataUsingEncoding: NSUTF8StringEncoding]];
397}
398
399/*
400 * obj    the object to be written out
401 * loc    the locale for formatting (or nil to indicate no formatting)
402 * lev    the level of indentation to use
403 * step   the indentation step (0 = 0, 1 = 2, 2 = 4, 3 = 8)
404 * x      an indicator for xml or old/new openstep property list format
405 * dest   the output data
406 */
407static void append(id obj, NSDictionary *loc, unsigned lev, unsigned step,
408                   NSPropertyListFormat format, NSMutableData *dest)
409{
410    if ([obj isKindOfClass: [NSString class]])
411    {
412        if (format == NSPropertyListXMLFormat_v1_0)
413        {
414            [dest appendBytes: "<string>" length: 8];
415            XString(obj, dest);
416            [dest appendBytes: "</string>\n" length: 10];
417        }
418        else
419            PString([obj description], dest);
420    }
421    else if ([obj isKindOfClass: [NSNumber class]])
422    {   const char  *t = [obj objCType];
423
424        if (*t ==  'c' || *t == 'C')
425        {   BOOL	val = [obj boolValue];
426
427            if (val == YES)
428            {
429                if (format == NSPropertyListXMLFormat_v1_0)
430                    [dest appendBytes: "<true/>\n" length: 8];
431                else if (format == NSPropertyListGNUstepFormat)
432                    [dest appendBytes: "<*BY>" length: 5];
433                else
434                    PString([obj description], dest);
435            }
436            else
437            {
438                if (format == NSPropertyListXMLFormat_v1_0)
439                    [dest appendBytes: "<false/>\n" length: 9];
440                else if (format == NSPropertyListGNUstepFormat)
441                    [dest appendBytes: "<*BN>" length: 5];
442                else
443                    PString([obj description], dest);
444            }
445        }
446        else if (strchr("sSiIlLqQ", *t) != 0)
447        {
448            if (format == NSPropertyListXMLFormat_v1_0)
449            {
450                [dest appendBytes: "<integer>" length: 9];
451                XString([obj stringValue], dest);
452                [dest appendBytes: "</integer>\n" length: 11];
453            }
454            else if (format == NSPropertyListGNUstepFormat)
455            {
456                [dest appendBytes: "<*I" length: 3];
457                [dest appendData: [[obj stringValue] dataUsingEncoding: NSASCIIStringEncoding]];
458                [dest appendBytes: ">" length: 1];
459            }
460            else
461                PString([obj description], dest);
462        }
463        else
464        {
465            if (format == NSPropertyListXMLFormat_v1_0)
466            {
467                [dest appendBytes: "<real>" length: 6];
468                XString([obj stringValue], dest);
469                [dest appendBytes: "</real>\n" length: 8];
470            }
471            else if (format == NSPropertyListGNUstepFormat)
472            {
473                [dest appendBytes: "<*R" length: 3];
474                [dest appendData: [[obj stringValue] dataUsingEncoding: NSASCIIStringEncoding]];
475                [dest appendBytes: ">" length: 1];
476            }
477            else
478                PString([obj description], dest);
479        }
480    }
481    else if ([obj isKindOfClass: [NSData class]])
482    {
483        if (format == NSPropertyListXMLFormat_v1_0)
484        {
485            [dest appendBytes: "<data>\n" length: 7];
486            encodeBase64(obj, dest);
487            [dest appendBytes: "</data>\n" length: 8];
488        }
489        else
490        {   const unsigned char *src;
491            unsigned char       *dst;
492            int                 length;
493            int                 i;
494            int                 j;
495
496            src = [obj bytes];
497            length = [obj length];
498#define num2char(num) ((num) < 0xa ? ((num)+'0') : ((num)+0x57))
499
500            j = [dest length];
501            [dest setLength: j + 2*length+(length > 4 ? (length-1)/4+2 : 2)];
502            dst = [dest mutableBytes];
503            dst[j++] = '<';
504            for (i = 0; i < length; i++, j++)
505            {
506                dst[j++] = num2char((src[i]>>4) & 0x0f);
507                dst[j] = num2char(src[i] & 0x0f);
508                if ((i & 3) == 3 && i < length-1)
509                {
510                    /* if we've just finished a 32-bit int, print a space */
511                    dst[++j] = ' ';
512                }
513            }
514            dst[j++] = '>';
515        }
516    }
517    else if ([obj isKindOfClass: [NSDate class]])
518    {   static NSTimeZone	*z = nil;
519
520        if (z == nil)
521            z = [[NSTimeZone timeZoneForSecondsFromGMT: 0] retain];
522        if (format == NSPropertyListXMLFormat_v1_0)
523        {
524            [dest appendBytes: "<date>" length: 6];
525            obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%dT%H:%M:%SZ"
526                                            timeZone: z locale: nil];
527            obj = [obj dataUsingEncoding: NSASCIIStringEncoding];
528            [dest appendData: obj];
529            [dest appendBytes: "</date>\n" length: 8];
530        }
531        else if (format == NSPropertyListGNUstepFormat)
532        {
533            [dest appendBytes: "<*D" length: 3];
534            obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z"
535                                            timeZone: z locale: nil];
536            obj = [obj dataUsingEncoding: NSASCIIStringEncoding];
537            [dest appendData: obj];
538            [dest appendBytes: ">" length: 1];
539        }
540        else
541        {
542            obj = [obj descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S %z"
543                                            timeZone: z locale: nil];
544            PString(obj, dest);
545        }
546    }
547    else if ([obj isKindOfClass: [NSArray class]])
548    {   const char	*iBaseString;
549        const char	*iSizeString;
550        unsigned	level = lev;
551
552        if (level*step < sizeof(indentStrings)/sizeof(id))
553            iBaseString = indentStrings[level*step];
554        else
555            iBaseString = indentStrings[sizeof(indentStrings)/sizeof(id)-1];
556        level++;
557        if (level*step < sizeof(indentStrings)/sizeof(id))
558            iSizeString = indentStrings[level*step];
559        else
560            iSizeString = indentStrings[sizeof(indentStrings)/sizeof(id)-1];
561
562        if (format == NSPropertyListXMLFormat_v1_0)
563        {   NSEnumerator	*e;
564
565            [dest appendBytes: "<array>\n" length: 8];
566            e = [obj objectEnumerator];
567            while ((obj = [e nextObject]))
568            {
569                [dest appendBytes: iSizeString length: strlen(iSizeString)];
570                append(obj, loc, level, step, format, dest);
571            }
572            [dest appendBytes: iBaseString length: strlen(iBaseString)];
573            [dest appendBytes: "</array>\n" length: 9];
574        }
575        else
576        {   unsigned		count = [obj count];
577            unsigned		last = count - 1;
578            NSString		*plists[count];
579            unsigned		i;
580
581            if ([obj isProxy] == YES)
582            {
583                for (i = 0; i < count; i++)
584                    plists[i] = [obj objectAtIndex: i];
585            }
586            else
587                [obj getObjects: plists];
588
589            if (loc == nil)
590            {
591                [dest appendBytes: "(" length: 1];
592                for (i = 0; i < count; i++)
593                {   id	item = plists[i];
594
595                    append(item, nil, 0, step, format, dest);
596                    if (i != last)
597                        [dest appendBytes: ", " length: 2];
598                }
599                [dest appendBytes: ")" length: 1];
600            }
601            else
602            {
603                [dest appendBytes: "(\n" length: 2];
604                for (i = 0; i < count; i++)
605                {   id	item = plists[i];
606
607                    [dest appendBytes: iSizeString length: strlen(iSizeString)];
608                    append(item, loc, level, step, format, dest);
609                    if (i == last)
610                        [dest appendBytes: "\n" length: 1];
611                    else
612                        [dest appendBytes: ",\n" length: 2];
613                }
614                [dest appendBytes: iBaseString length: strlen(iBaseString)];
615                [dest appendBytes: ")" length: 1];
616            }
617        }
618    }
619    else if ([obj isKindOfClass: [NSDictionary class]])
620    {   const char  *iBaseString;
621        const char  *iSizeString;
622        SEL         objSel = @selector(objectForKey:);
623        IMP         myObj = [obj methodForSelector: objSel];
624        unsigned    i;
625        NSArray     *keyArray = [obj allKeys];
626        unsigned    numKeys = [keyArray count];
627        NSString    *plists[numKeys];
628        NSString    *keys[numKeys];
629        BOOL        canCompare = YES;
630        Class       lastClass = 0;
631        unsigned    level = lev;
632        BOOL        isProxy = [obj isProxy];
633
634        if (level*step < sizeof(indentStrings)/sizeof(id))
635            iBaseString = indentStrings[level*step];
636        else
637            iBaseString = indentStrings[sizeof(indentStrings)/sizeof(id)-1];
638        level++;
639        if (level*step < sizeof(indentStrings)/sizeof(id))
640            iSizeString = indentStrings[level*step];
641        else
642            iSizeString = indentStrings[sizeof(indentStrings)/sizeof(id)-1];
643
644        if (isProxy == YES)
645        {
646            for (i = 0; i < numKeys; i++)
647                keys[i] = [keyArray objectAtIndex: i];
648        }
649        else
650            [keyArray getObjects: keys];
651
652        if (format == NSPropertyListXMLFormat_v1_0)
653        {
654            /* This format can only use strings as keys */
655            lastClass = [NSString class];
656            for (i = 0; i < numKeys; i++)
657            {
658                if ([keys[i] isKindOfClass: lastClass] == NO)
659                {
660                    [NSException raise: NSInvalidArgumentException
661                                format: @"Bad key in property list: '%@'", keys[i]];
662                }
663            }
664        }
665        else
666        {
667            /* All keys must respond to -compare: for sorting */
668            for (i = 0; i < numKeys; i++)
669            {   Class   x = [keys[i] class];
670
671                if (x == lastClass) // FIXME: slower as GNUstep - where is the class pointer?
672                    continue;
673                if ([keys[i] respondsToSelector: @selector(compare:)] == NO)
674                {
675                    canCompare = NO;
676                    break;
677                }
678                lastClass = x;
679            }
680        }
681
682        if (canCompare == YES)
683        {
684#define STRIDE_FACTOR 3
685            unsigned	c,d, stride;
686            BOOL		found;
687            NSComparisonResult	(*comp)(id, SEL, id) = 0;
688            unsigned int	count = numKeys;
689#ifdef	GSWARN
690            BOOL		badComparison = NO;
691#endif
692
693            stride = 1;
694            while (stride <= count)
695            {
696                stride = stride * STRIDE_FACTOR + 1;
697            }
698            lastClass = 0;
699            while (stride > (STRIDE_FACTOR - 1))
700            {
701	      // loop to sort for each value of stride
702                stride = stride / STRIDE_FACTOR;
703                for (c = stride; c < count; c++)
704                {
705                    found = NO;
706                    if (stride > c)
707                    {
708                        break;
709                    }
710                    d = c - stride;
711                    while (!found)
712                    {   id                  a = keys[d + stride];
713                        id                  b = keys[d];
714                        Class               x = [a class];
715                        NSComparisonResult  r;
716
717                        if (x != lastClass)
718                        {
719                            lastClass = x;
720                            comp = (NSComparisonResult (*)(id, SEL, id))
721                                [a methodForSelector: @selector(compare:)];
722                        }
723                        r = (*comp)(a, @selector(compare:), b);
724                        if (r < 0)
725                        {
726#ifdef	GSWARN
727                            if (r != NSOrderedAscending)
728                                badComparison = YES;
729#endif
730                            keys[d + stride] = b;
731                            keys[d] = a;
732                            if (stride > d)
733                                break;
734                            d -= stride;
735                        }
736                        else
737                        {
738#ifdef	GSWARN
739                            if (r != NSOrderedDescending && r != NSOrderedSame)
740                                badComparison = YES;
741#endif
742                            found = YES;
743                        }
744                    }
745                }
746            }
747#ifdef	GSWARN
748            if (badComparison == YES)
749                NSWarnFLog(@"Detected bad return value from comparison");
750#endif
751        }
752
753        if (isProxy == YES)
754        {
755            for (i = 0; i < numKeys; i++)
756                plists[i] = [(NSDictionary*)obj objectForKey: keys[i]];
757        }
758        else
759        {
760            for (i = 0; i < numKeys; i++)
761                plists[i] = (*myObj)(obj, objSel, keys[i]);
762        }
763
764        if (format == NSPropertyListXMLFormat_v1_0)
765        {
766            [dest appendBytes: "<dict>\n" length: 7];
767            for (i = 0; i < numKeys; i++)
768            {
769                [dest appendBytes: iSizeString length: strlen(iSizeString)];
770                [dest appendBytes: "<key>" length: 5];
771                XString(keys[i], dest);
772                [dest appendBytes: "</key>\n" length: 7];
773                [dest appendBytes: iSizeString length: strlen(iSizeString)];
774                append(plists[i], loc, level, step, format, dest);
775            }
776            [dest appendBytes: iBaseString length: strlen(iBaseString)];
777            [dest appendBytes: "</dict>\n" length: 8];
778        }
779        else if (loc == nil)
780        {
781            [dest appendBytes: "{" length: 1];
782            for (i = 0; i < numKeys; i++)
783            {
784                append(keys[i],   nil, 0, step, format, dest);
785                [dest appendBytes: " = " length: 3];
786                append(plists[i], nil, 0, step, format, dest);
787                [dest appendBytes: "; " length: 2];
788            }
789            [dest appendBytes: "}" length: 1];
790        }
791        else
792        {
793            [dest appendBytes: "{\n" length: 2];
794            for (i = 0; i < numKeys; i++)
795            {
796                [dest appendBytes: iSizeString length: strlen(iSizeString)];
797                append(keys[i],   loc, level, step, format, dest);
798                [dest appendBytes: " = " length: 3];
799                append(plists[i], loc, level, step, format, dest);
800                [dest appendBytes: ";\n" length: 2];
801            }
802            [dest appendBytes: iBaseString length: strlen(iBaseString)];
803            [dest appendBytes: "}" length: 1];
804        }
805    }
806    else
807    {   NSString	*cls;
808
809        if (obj == nil)
810        {
811            obj = @"(nil)";
812            cls = @"(nil)";
813        }
814        else
815            cls = NSStringFromClass([obj class]);
816
817        if (format == NSPropertyListXMLFormat_v1_0)
818        {
819            NSLog(@"Non-property-list class (%@) encoded as string", cls);
820            [dest appendBytes: "<string>" length: 8];
821            XString([obj description], dest);
822            [dest appendBytes: "</string>" length: 9];
823        }
824        else
825        {
826            NSLog(@"Non-property-list class (%@) encoded as string", cls);
827            PString([obj description], dest);
828        }
829    }
830}
831
832static BOOL	classInitialized = NO;
833
834+ (void) initialize
835{
836    if (classInitialized == NO)
837    {
838        classInitialized = YES;
839
840#if	HAVE_LIBXML
841        /* Cache XML node information */
842        XML_ELEMENT_NODE = [GSXMLNode typeFromDescription: @"XML_ELEMENT_NODE"];
843#endif
844        NSStringClass = [NSString class];
845        NSMutableStringClass = [NSMutableString class];
846        NSDataClass = [NSData class];
847
848        plAdd = (id (*)(id, SEL, id))
849            [plArray instanceMethodForSelector: @selector(addObject:)];
850
851        plSet = (id (*)(id, SEL, id, id))
852            [plDictionary instanceMethodForSelector: @selector(setObject:forKey:)];
853
854        //setupHexdigits();
855        setupQuotables();
856        //setupWhitespace();
857    }
858}
859
860+ (NSData*)dataFromPropertyList:(id)plist
861                         format:(NSPropertyListFormat)format
862               errorDescription:(NSString**)errorString
863{   NSMutableData           *dest = [NSMutableData dataWithCapacity: 1024];
864    NSDictionary            *loc = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
865    int                     step = 2;
866    //NSPropertyListFormat    format = NSPropertyListOpenStepFormat;
867
868    if (format == NSPropertyListXMLFormat_v1_0)
869    {
870        const char	*prefix =
871        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist "
872        "PUBLIC \"-//GNUstep//DTD plist 0.9//EN\" "
873        "\"http://www.gnustep.org/plist-0_9.xml\">\n"
874        "<plist version=\"0.9\">\n";
875
876        [dest appendBytes: prefix length: strlen(prefix)];
877        append(plist, loc, 0, step > 3 ? 3 : step, format, dest);
878        [dest appendBytes: "</plist>" length: 8];
879    }
880    //else if (format == NSPropertyListGNUstepBinaryFormat)
881    //    [NSSerializer serializePropertyList: plist intoData: dest];
882    else if (format == NSPropertyListBinaryFormat_v1_0)
883        NSLog(@"dataFromPropertyList: NSPropertyListBinaryFormat_v1_0 not implemented");
884    //    [BinaryPLGenerator serializePropertyList: plist intoData: dest];
885    else
886        append(plist, loc, 0, step > 3 ? 3 : step, format, dest);
887
888    return dest;
889}
890
891+ (NSString*)stringFromPropertyList:(id)plist
892{   NSMutableString     *mutStr = [NSMutableString string];
893    NSEnumerator        *keyEnum = [plist keyEnumerator];
894    id                  key;
895
896    [mutStr appendString:@"{\n"];
897
898    while ((key = [keyEnum nextObject]))
899    {   id  val = [plist objectForKey:key];
900
901        [mutStr appendFormat:@"  \"%@\" = ", key];
902
903        if ([val isKindOfClass:[NSString class]])
904            [mutStr appendFormat:@"\"%@\"", val];
905        else if ([val isKindOfClass:[NSNumber class]])
906            [mutStr appendFormat:@"\"%@\"", [val stringValue]];
907        else if ([val isKindOfClass:[NSArray class]])
908            NSLog(@"FIXME, VHFPropertyListSerialization: Object of NSArray class not implemented!");
909        else if ([val isKindOfClass:[NSDictionary class]])
910            NSLog(@"FIXME, VHFPropertyListSerialization: Object of NSDictionary class not implemented!");
911        else if ([val isKindOfClass:[NSData class]])
912            NSLog(@"FIXME, VHFPropertyListSerialization: Object of NSData class not implemented!");
913        else
914            NSLog(@"VHFPropertyListSerialization: Object of %@ class not implemented!", [val class]);
915
916        [mutStr appendString:@";\n"];
917    }
918
919    [mutStr appendString:@"}\n"];
920
921    return mutStr;
922}
923@end
924