]> err.no Git - sope/blob - sope-core/NGExtensions/FdExt.subproj/NGPropertyListParser.m
fixed a gstep-base issue
[sope] / sope-core / NGExtensions / FdExt.subproj / NGPropertyListParser.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #if !LIB_FOUNDATION_LIBRARY
23
24 //#define HAVE_MMAP
25
26 #ifdef HAVE_MMAP
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/mman.h>
30 #include <unistd.h>
31 #endif
32 #include <ctype.h>
33
34 #import "common.h"
35 #import "NGPropertyListParser.h"
36 #import "NGMemoryAllocation.h"
37 //#import "NSObjectMacros.h"
38 //#import "NSException.h"
39 #import <Foundation/NSData.h>
40
41 #define NoZone NULL
42
43 @interface NSException(UsedPrivates) /* may break on Panther? */
44 - (void)setUserInfo:(NSDictionary *)_ui;
45 - (void)setReason:(NSString *)_reason;
46 @end
47
48 static NSString     *_parseString (NSZone *_zone, const unsigned char *_buffer,
49                                    unsigned *_idx, unsigned _len,
50                                    NSException **_exception);
51 static NSDictionary *_parseDict   (NSZone *_zone, const unsigned char *_buffer,
52                                    unsigned *_idx, unsigned _len,
53                                    NSException **_exception);
54 static NSArray      *_parseArray  (NSZone *_zone, const unsigned char *_buffer,
55                                    unsigned *_idx, unsigned _len,
56                                    NSException **_exception);
57 static NSData       *_parseData   (NSZone *_zone, const unsigned char *_buffer,
58                                    unsigned *_idx, unsigned _len,
59                                    NSException **_exception);
60 static id           _parseProperty(NSZone *_zone, const unsigned char *_buffer,
61                                    unsigned *_idx, unsigned _len,
62                                    NSException **_exception);
63 static NSDictionary *_parseStrings(NSZone *_zone, const unsigned char *_buffer,
64                                    unsigned *_idx, unsigned _len,
65                                    NSException **_exception);
66
67 // public functions
68
69 NSString *NGParseStringFromBuffer(const unsigned char *_buffer, unsigned _len)
70 {
71     NSString    *result    = nil;
72     NSException *exception = nil;
73     unsigned    idx        = 0;
74
75     if (_len >= 2) {
76         if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
77             NSLog(@"WARNING(%s): tried to parse Unicode string (FE/FF) ...",
78                   __PRETTY_FUNCTION__);
79             return nil;
80         }
81         else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
82             NSLog(@"WARNING(%s): tried to parse Unicode string (FF/FE) ...",
83                   __PRETTY_FUNCTION__);
84             return nil;
85         }
86     }
87     
88     result = 
89         [_parseString(NoZone, _buffer, &idx, _len, &exception) autorelease];
90     [exception raise];
91     return result;
92 }
93
94 NSArray *NGParseArrayFromBuffer(const unsigned char *_buffer, unsigned _len)
95 {
96     NSArray     *result    = nil;
97     NSException *exception = nil;
98     unsigned    idx        = 0;
99
100     if (_len >= 2) {
101         if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
102             NSLog(@"WARNING(%s): tried to parse Unicode array (FE/FF) ...",
103                   __PRETTY_FUNCTION__);
104             return nil;
105         }
106         else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
107             NSLog(@"WARNING(%s): tried to parse Unicode array (FF/FE) ...",
108                   __PRETTY_FUNCTION__);
109             return nil;
110         }
111     }
112     
113     result = [_parseArray(NoZone, _buffer, &idx, _len, &exception) autorelease];
114     [exception raise];
115     return result;
116 }
117
118 NSDictionary *NGParseDictionaryFromBuffer(const unsigned char *_buffer, unsigned _len)
119 {
120     NSDictionary *result    = nil;
121     NSException  *exception = nil;
122     unsigned     idx        = 0;
123
124     if (_len >= 2) {
125         if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
126             NSLog(@"WARNING(%s): tried to parse Unicode dict (FE/FF) ...",
127                   __PRETTY_FUNCTION__);
128             return nil;
129         }
130         else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
131             NSLog(@"WARNING(%s): tried to parse Unicode dict (FF/FE) ...",
132                   __PRETTY_FUNCTION__);
133             return nil;
134         }
135     }
136     
137     result = [_parseDict(NoZone, _buffer, &idx, _len, &exception) autorelease];
138     [exception raise];
139     return result;
140 }
141
142 NSString *NGParseStringFromData(NSData *_data)
143 {
144     return NGParseStringFromBuffer([_data bytes], [_data length]);
145 }
146
147 NSArray *NGParseArrayFromData(NSData *_data)
148 {
149     return NGParseArrayFromBuffer([_data bytes], [_data length]);
150 }
151
152 NSDictionary *NGParseDictionaryFromData(NSData *_data)
153 {
154     return NGParseDictionaryFromBuffer([_data bytes], [_data length]);
155 }
156
157 NSString *NGParseStringFromString(NSString *_str)
158 {
159     // TODO: Unicode
160     return NGParseStringFromBuffer((unsigned char *)[_str cString], 
161                                    [_str cStringLength]);
162 }
163
164 NSArray *NGParseArrayFromString(NSString *_str)
165 {
166     // TODO: Unicode
167     return NGParseArrayFromBuffer((unsigned char *)[_str cString], 
168                                   [_str cStringLength]);
169 }
170
171 NSDictionary *NGParseDictionaryFromString(NSString *_str)
172 {
173     // TODO: Unicode
174     return NGParseDictionaryFromBuffer((unsigned char *)[_str cString], 
175                                        [_str cStringLength]);
176 }
177
178 id NGParsePropertyListFromBuffer(const unsigned char *_buffer, unsigned _len)
179 {
180     id          result = nil;
181     NSException *exception = nil;
182     unsigned    idx = 0;
183     
184     if (_len >= 2) {
185         if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
186             NSLog(@"WARNING(%s): tried to parse Unicode plist (FE/FF) ...",
187                   __PRETTY_FUNCTION__);
188             return nil;
189         }
190         else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
191             NSLog(@"WARNING(%s): tried to parse Unicode plist (FF/FE) ...",
192                   __PRETTY_FUNCTION__);
193             return nil;
194         }
195     }
196     
197     result = [_parseProperty(NoZone, _buffer, &idx, _len, &exception) 
198                             autorelease];
199     [exception raise];
200     return result;
201 }
202
203 id NGParsePropertyListFromData(NSData *_data)
204 {
205     return NGParsePropertyListFromBuffer([_data bytes], [_data length]);
206 }
207
208 id NGParsePropertyListFromString(NSString *_string)
209 {
210     return NGParsePropertyListFromBuffer((unsigned char *)[_string cString],
211                                          [_string cStringLength]);
212 }
213
214 id NGParsePropertyListFromFile(NSString *_path)
215 {
216 #ifdef HAVE_MMAP
217     NSException *exception = nil;
218     int         fd         = 0;
219     id          result     = nil;
220
221     fd = open([_path cString], O_RDONLY);
222     if (fd != -1) {
223         struct stat statInfo;
224         if (fstat(fd, &statInfo) == 0) {
225             void *mem = NULL;
226
227             mem = mmap(0, statInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
228             if ((mem != MAP_FAILED) || (mem == NULL)) {
229                 NS_DURING {
230                     unsigned idx = 0;
231                     result = _parseProperty(nil, mem, &idx, statInfo.st_size,
232                                             &exception);
233                 }
234                 NS_HANDLER {
235                     result = nil;
236                     exception = [localException retain];
237                 }
238                 NS_ENDHANDLER;
239
240                 munmap(mem, statInfo.st_size);
241                 mem = NULL;
242             }
243             else {
244                 NSLog(@"Could not map file %@ into virtual memory !", _path);
245             }
246         }
247         else {
248             NSLog(@"File %@ could not be mapped !", _path);
249         }
250         close(fd);
251     }
252     else {
253         NSLog(@"File %@ does not exist !", _path);
254     }
255
256     [result autorelease];
257     if (exception)
258         [exception raise];
259   
260     return result;
261 #else
262     NSData *data = [NSData dataWithContentsOfFile:_path];
263     
264     if (data) {
265         return NGParsePropertyListFromData(data);
266     }
267     else {
268         NSLog(@"%s: Could not parse plist file %@ !", __PRETTY_FUNCTION__,
269               _path);
270         return nil;
271     }
272 #endif
273 }
274
275 NSDictionary *NGParseStringsFromBuffer(const unsigned char *_buffer, 
276                                        unsigned _len)
277 {
278     NSDictionary *result    = nil;
279     NSException  *exception = nil;
280     unsigned     idx        = 0;
281     
282     result = [_parseStrings(NoZone, _buffer, &idx, _len, &exception) autorelease];
283     [exception raise];
284     return result;
285 }
286
287 NSDictionary *NGParseStringsFromData(NSData *_data)
288 {
289     return NGParseStringsFromBuffer([_data bytes], [_data length]);
290 }
291
292 NSDictionary *NGParseStringsFromString(NSString *_string)
293 {
294     return NGParseStringsFromBuffer((unsigned char *)[_string cString],
295                                     [_string cStringLength]);
296 }
297
298 NSDictionary *NGParseStringsFromFile(NSString *_path)
299 {
300 #ifdef HAVE_MMAP
301     struct stat statInfo;
302     NSException *exception = nil;
303     int         fd = 0;
304     id          result = nil;
305
306     fd = open([_path cString], O_RDONLY);
307     if (fd != -1) {
308         if (fstat(fd, &statInfo) == 0) {
309             void *mem = NULL;
310
311             mem = mmap(0, statInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
312             if (mem != MAP_FAILED) {
313                 NS_DURING {
314                     unsigned idx = 0;
315                     result = _parseStrings(nil, mem, &idx, statInfo.st_size,
316                                            &exception);
317                 }
318                 NS_HANDLER {
319                     exception = [localException retain];
320                     result = nil;
321                 }
322                 NS_ENDHANDLER;
323
324                 munmap(mem, statInfo.st_size);
325                 mem = NULL;
326             }
327             else
328                 NSLog(@"Could not map file %@ into virtual memory !", _path);
329         }
330         else {
331             NSLog(@"File %@ could not be mapped !", _path);
332         }
333         close(fd);
334     }
335     else {
336         NSLog(@"File %@ does not exist !", _path);
337     }
338
339     [result autorelease];
340     [exception raise];
341   
342     return result;
343 #else
344     NSData *data = [NSData dataWithContentsOfFile:_path];
345
346     if (data)
347         return NGParseStringsFromData(data);
348     else {
349         NSLog(@"%s: Could not parse strings file %@ !",
350               __PRETTY_FUNCTION__, _path);
351         return nil;
352     }
353 #endif
354 }
355
356 /* ******************* implementation ******************** */
357
358 static inline BOOL _isBreakChar(char _c)
359 {
360     switch (_c) {
361         case ' ': case '\t': case '\n': case '\r':
362         case '/': case '=':  case ';':  case ',':
363         case '{': case '(':  case '"':  case '<':
364         case '}': case ')': case '>':
365             return YES;
366
367         default:
368             return NO;
369     }
370 }
371
372 static inline int _valueOfHexChar(char _c)
373 {
374     switch (_c) {
375         case '0': case '1': case '2': case '3': case '4':
376         case '5': case '6': case '7': case '8': case '9':
377             return (_c - 48); // 0-9 (ascii-char)'0' - 48 => (int)0
378       
379         case 'A': case 'B': case 'C':
380         case 'D': case 'E': case 'F':
381             return (_c - 55); // A-F, A=10..F=15, 'A'=65..'F'=70
382       
383         case 'a': case 'b': case 'c':
384         case 'd': case 'e': case 'f':
385             return (_c - 87); // a-f, a=10..F=15, 'a'=97..'f'=102
386
387         default:
388             return -1;
389     }
390 }
391 static inline BOOL _isHexDigit(char _c)
392 {
393     switch (_c) {
394     case '0': case '1': case '2': case '3': case '4':
395     case '5': case '6': case '7': case '8': case '9':
396     case 'A': case 'B': case 'C':
397     case 'D': case 'E': case 'F':
398     case 'a': case 'b': case 'c':
399     case 'd': case 'e': case 'f':
400         return YES;
401
402     default:
403         return NO;
404     }
405 }
406
407 static inline int _numberOfLines(const unsigned char *_buffer, unsigned _lastIdx)
408 {
409     register int pos, lineCount = 1;
410
411     for (pos = 0; (pos < _lastIdx) && (_buffer[pos] != '\0'); pos++) {
412         if (_buffer[pos] == '\n')
413             lineCount++;
414     }
415     return lineCount;
416 }
417
418 static NSException *_makeException(NSException *_exception,
419                                    const unsigned char *_buffer, unsigned _idx,
420                                    unsigned _len, NSString *_text)
421 {
422     NSMutableDictionary *ui = nil;
423     NSException *exception = nil;
424     int         numLines   = _numberOfLines(_buffer, _idx);
425     BOOL        atEof      = (_idx >= _len) ? YES : NO;
426
427     if (_exception) // error resulted from a previous error (exception already set)
428         return _exception;
429
430     exception = [NSException exceptionWithName:@"SyntaxErrorException"
431                              reason:nil
432                              userInfo:nil];
433
434     _text = atEof
435         ? [NSString stringWithFormat:@"Unexpected end: %@", _text]
436         : [NSString stringWithFormat:@"Syntax error in line %i: %@", numLines,_text];
437   
438     [exception setReason:_text];
439
440     // user info
441     {
442         ui = [[exception userInfo] mutableCopy];
443         if (ui == nil)
444             ui = [[NSMutableDictionary alloc] initWithCapacity:8];
445
446         [ui setObject:[NSNumber numberWithInt:numLines] forKey:@"line"];
447         [ui setObject:[NSNumber numberWithInt:_len]     forKey:@"size"];
448         [ui setObject:[NSNumber numberWithInt:_idx]     forKey:@"position"];
449
450         /*
451           if (_len > 0)
452           [ui setObject:[NSString stringWithCString:_buffer length:_len]
453               forKey:@"text"];
454     
455           if (!atEof && (_idx > 0)) {
456           [ui setObject:[NSString stringWithCString:_buffer length:_idx]
457           forKey:@"consumedText"];
458           }
459         */
460         if (!atEof && (_idx > 0)) {
461             register unsigned pos;
462             const unsigned char *startPos, *endPos;
463
464             for (pos = _idx; (pos >= 0) && (_buffer[pos] != '\n'); pos--)
465                 ;
466             startPos = &(_buffer[pos + 1]);
467
468             for (pos = _idx; ((pos < _len) && (_buffer[pos] != '\n')); pos++)
469                 ;
470             endPos = &(_buffer[pos - 1]);
471
472             if (startPos < endPos) {
473                 NSString *s;
474                 
475                 s = [[NSString alloc] initWithCString:(char *)startPos 
476                                       length:(endPos - startPos)];
477                 if (s != nil) {
478                     [ui setObject:s forKey:@"lastLine"];
479                     [s release];
480                 }
481                 else {
482                     NSLog(@"ERROR(%s): could not get last-line!",
483                           __PRETTY_FUNCTION__);
484                 }
485             }
486             else
487                 NSLog(@"startPos=0x%p endPos=0x%p", startPos, endPos);
488         }
489     
490         [exception setUserInfo:ui];
491     
492         [ui release]; ui = nil;
493     }
494
495     return exception;
496 }
497
498 static BOOL _skipComments(const unsigned char *_buffer, unsigned *_idx, unsigned _len,
499                           BOOL _skipSpaces, NSException **_exception)
500 {
501     register unsigned pos = *_idx;
502     BOOL lookAgain;
503
504     if (pos >= _len)
505         return NO;
506   
507     do { // until all comments are filtered ..
508         lookAgain = NO;
509     
510         if ((_buffer[pos] == '/') && (pos + 1 < _len)) {
511             if (_buffer[pos + 1] == '/') { // single line comments
512                 pos += 2; // skip '//'
513
514                 // search for '\n' ..
515                 while ((pos < _len) && (_buffer[pos] != '\n'))
516                     pos++;
517
518                 if ((pos < _len) && (_buffer[pos] == '\n')) {
519                     pos++; // skip newline, otherwise EOF was reached
520                     lookAgain = YES;
521                 }
522             }
523             else if (_buffer[pos + 1] == '*') { /* multiline comments */
524                 BOOL commentIsClosed = NO;
525       
526                 pos += 2; // skip '/*'
527
528                 do { 
529                     // search for '*/'
530                     while ((pos < _len) && (_buffer[pos] != '*'))
531                         pos++;
532                     
533                     if (pos + 1 >= _len)
534                         // did not find '*' or not enough left for '/'
535                         break;
536                     
537                     pos++; // skip '*'
538                     
539                     if (_buffer[pos] != '/')
540                         // missing slash in '*/'
541                         continue;
542                     
543                     commentIsClosed = YES;
544                     pos++; // skip '/'
545                     lookAgain = YES;
546                     break; // leave loop
547                 }
548                 while (pos < _len);
549
550                 if (!commentIsClosed) {
551                     // EOF found, comment was not closed
552                     *_exception =
553                         _makeException(*_exception, _buffer, *_idx, _len,
554                                        @"comment was not closed (expected '*/')");
555                     return NO;
556                 }
557             }
558         }
559         else if (_skipSpaces && isspace((int)_buffer[pos])) {
560             pos++;
561             lookAgain = YES;
562         }
563     }
564     while (lookAgain && (pos < _len));
565
566     // store position ..
567     *_idx = pos;
568     //NSLog(@"skipped comments, now at '%s'", &(_buffer[*_idx]));
569
570     return (pos < _len);
571 }
572
573 static NSString *_parseString(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
574                               unsigned _len, NSException **_exception)
575 {
576
577     // skip comments and spaces
578     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
579         // EOF reached during comment-skipping
580         *_exception =
581             _makeException(*_exception, _buffer, *_idx, _len,
582                            @"did not find a string !");
583         return nil;
584     }
585
586     if (_buffer[*_idx] == '"') { // a quoted string
587         register unsigned pos = *_idx;
588         register unsigned len = 0;
589         unsigned startPos = pos + 1;
590         BOOL     containsEscaped = NO;
591     
592         pos++; // skip starting quote
593
594         // loop until closing quote
595         while ((_buffer[pos] != '"') && (pos < _len)) {
596             if (_buffer[pos] == '\\') {
597                 containsEscaped = YES;
598                 pos++; // skip following char
599                 if (pos == _len) {
600                     *_exception =
601                         _makeException(*_exception, _buffer, *_idx, _len,
602                                        @"escape in quoted string not finished !");
603                     return nil;
604                 }
605             }
606             pos++;
607             len++;
608         }
609
610         if (pos == _len) { // syntax error, quote not closed
611             *_idx = pos;
612             *_exception =
613                 _makeException(*_exception, _buffer, *_idx, _len,
614                                @"quoted string not closed (expected '\"')");
615             return nil;
616         }
617
618         pos++;       // skip closing quote
619         *_idx = pos; // store pointer
620         pos = 0;
621     
622         if (len == 0) { // empty string
623             return @"";
624         }
625         else if (containsEscaped) {
626             register unsigned pos2;
627             char *str = NGMallocAtomic(len + 1);
628             id   ostr = nil;
629
630             NSCAssert(len > 0, @"invalid length ..");
631
632             for (pos = startPos, pos2 = 0; _buffer[pos] != '"'; pos++, pos2++) {
633                 //NSLog(@"char=%c pos=%i pos2=%i", _buffer[pos], pos2);
634                 if (_buffer[pos] == '\\') {
635                     pos++;
636                     switch (_buffer[pos]) {
637                     case 'a':  str[pos2] = '\a'; break;
638                     case 'b':  str[pos2] = '\b'; break;
639                     case 'f':  str[pos2] = '\f'; break;
640                     case 'n':  str[pos2] = '\n'; break;
641                     case 't':  str[pos2] = '\t'; break;
642                     case 'v':  str[pos2] = '\v'; break;
643                     case '\\': str[pos2] = '\\'; break;
644             
645                     default:
646                         str[pos2] = _buffer[pos];
647                         break;
648                     }
649                 }
650                 else {
651                     str[pos2] = _buffer[pos];
652                 }
653             }
654             str[pos2] = '\0';
655             NSCAssert(pos2 == len, @"invalid unescape ..");
656
657             ostr = [[NSString allocWithZone:_zone]
658                               initWithCString:str length:len];
659             NGFree(str); str = NULL;
660
661             return ostr;
662         }
663         else {
664             NSCAssert(len > 0, @"invalid length ..");
665       
666             return [[NSString allocWithZone:_zone]
667                        initWithCString:(char*)&(_buffer[startPos]) length:len];
668         }
669     }
670     else { // an unquoted string, may not be zero chars long !
671         register unsigned pos = *_idx;
672         register unsigned len = 0;
673         unsigned startPos = pos;
674
675         // loop until break char
676         while (!_isBreakChar(_buffer[pos]) && (pos < _len)) {
677             pos++;
678             len++;
679         }
680
681         if (len == 0) { // was not a string ..
682             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
683                                          @"did not find a string !");
684             return nil;
685         }
686         else {
687             *_idx = pos;
688             return [[NSString allocWithZone:_zone]
689                        initWithCString:(char*)&(_buffer[startPos]) length:len];
690         }
691     }
692 }
693
694 static NSData *_parseData(NSZone *_zone, const unsigned char *_buffer,
695                           unsigned *_idx, unsigned _len, 
696                           NSException **_exception)
697 {
698     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
699         // EOF reached during comment-skipping
700         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
701                                      @"did not find a data (expected '<') !");
702         return nil;
703     }
704
705     if (_buffer[*_idx] != '<') { // it's not a data that's follows
706         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
707                                      @"did not find a data (expected '<') !");
708         return nil;
709     }
710     else {
711         register      unsigned pos = *_idx + 1;
712         register      unsigned len = 0;
713         unsigned      endPos = 0;
714         NSMutableData *data  = nil;
715     
716         *_idx += 1; // skip '<'
717
718         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
719             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
720                                          @"data was not closed (expected '>') ..");
721             return nil; // EOF
722         }
723
724         if (_buffer[*_idx] == '>') { // empty data
725             *_idx += 1; // skip '>'
726             return [[NSData allocWithZone:_zone] init];
727         }
728
729         // count significant chars
730         while ((_buffer[pos] != '>') && (pos < _len)) {
731             if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t'))
732                 ;
733             else if (_isHexDigit(_buffer[pos]))
734                 len++;
735             else {
736                 *_idx = pos;
737                 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
738                                              @"invalid char in data property");
739                 return nil; // abort
740             }
741             pos++;
742         }
743         if (pos == _len) {
744             *_idx = pos;
745             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
746                                          @"data was not closed (expected '>')");
747             return nil; // EOF
748         }
749         endPos = pos; // store position of closing '>'
750
751         // if odd, then add one byte for trailing nibble
752         len = (len % 2 == 1) ? len / 2 + 1 : len / 2;
753         data = [[NSMutableData allocWithZone:_zone] initWithLength:len];
754
755         // now copy bytes ..
756         {
757             register unsigned i;
758             register unsigned pending = -1;
759             char *buf = [data mutableBytes];
760       
761             for (pos = *_idx, i = 0; (pos < endPos) && (i < len); pos++) {
762                 int value = _valueOfHexChar(_buffer[pos]);
763
764                 if (value != -1) {
765                     if (pending == -1)
766                         pending = value;
767                     else {
768                         value = value * 16 + pending;
769                         pending = -1;
770
771                         buf[i] = value;
772                         i++;
773                     }
774                 }
775             }
776             if (pending != -1) { // was odd, now add the trailer ..
777                 NSCAssert(i < len, @"invalid length ..");
778                 buf[i] = pending * 16;
779             }
780         }
781     
782         // update global position
783         *_idx = endPos + 1; // endPos + 1 (*endPos == '>', 1 => skips '>')
784
785         return data;
786     }
787 }
788
789 static NSDictionary *_parseDict(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
790                                 unsigned _len, NSException **_exception)
791 {
792     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
793         // EOF reached during comment-skipping
794         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
795                                      @"did not find dictionary (expected '{')");
796         return nil;
797     }
798   
799     if (_buffer[*_idx] != '{') { // it's not a dict that's follows
800         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
801                                      @"did not find dictionary (expected '{')");
802         return nil;
803     }
804     else {
805         NSMutableDictionary *result = nil;
806         id   key     = nil;
807         id   value   = nil;
808         BOOL didFail = NO;
809     
810         *_idx += 1; // skip '{'
811
812         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
813             *_exception =
814                 _makeException(*_exception, _buffer, *_idx, _len,
815                                @"dictionary was not closed (expected '}')");
816             return nil; // EOF
817         }
818
819         if (_buffer[*_idx] == '}') { // an empty dictionary
820             *_idx += 1; // skip the '}'
821             return [[NSDictionary allocWithZone:_zone] init];
822         }
823
824         result = [[NSMutableDictionary allocWithZone:_zone] init];
825         do {
826             key   = nil;
827             value = nil;
828       
829             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
830                 *_exception =
831                     _makeException(*_exception, _buffer, *_idx, _len,
832                                    @"dictionary was not closed (expected '}')");
833                 didFail = YES;
834                 break; // unexpected EOF
835             }
836
837             if (_buffer[*_idx] == '}') { // dictionary closed
838                 *_idx += 1; // skip the '}'
839                 break;
840             }
841       
842             // read key property
843             key = _parseProperty(_zone, _buffer, _idx, _len, _exception);
844             if (key == nil) { // syntax error
845                 if (*_exception == nil) {
846                     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
847                                                  @"got nil-key in dictionary ..");
848                 }
849                 didFail = YES;
850                 break;
851             }
852
853             /* The following parses:  (comment|space)* '=' (comment|space)* */
854             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
855                 *_exception =
856                     _makeException(*_exception, _buffer, *_idx, _len,
857                                    @"expected '=' after key in dictionary");
858                 didFail = YES;
859                 break; // unexpected EOF
860             }
861             // no we need a '=' assignment
862             if (_buffer[*_idx] != '=') {
863                 *_exception =
864                     _makeException(*_exception, _buffer, *_idx, _len,
865                                    @"expected '=' after key in dictionary");
866                 didFail = YES;
867                 break;
868             }
869             *_idx += 1; // skip '='
870             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
871                 *_exception =
872                     _makeException(*_exception, _buffer, *_idx, _len,
873                                    @"expected value after key '=' in dictionary");
874                 didFail = YES;
875                 break; // unexpected EOF
876             }
877
878             // read value property
879             value = _parseProperty(_zone, _buffer, _idx, _len, _exception);
880             if (value == nil) { // syntax error
881                 if (*_exception == nil) {
882                     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
883                                                  @"got nil-value in dictionary");
884                 }
885                 didFail = YES;
886                 break;
887             }
888
889             NSCAssert(key,   @"invalid key ..");
890             NSCAssert(value, @"invalid value ..");
891
892             [result setObject:value forKey:key];
893
894             // release key and value
895             [key   release]; key   = nil;
896             [value release]; value = nil;
897
898             // read trailing ';' if available
899             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
900                 *_exception =
901                     _makeException(*_exception, _buffer, *_idx, _len,
902                                    @"dictionary was not closed (expected '}')");
903                 didFail = YES;
904                 break; // unexpected EOF
905             }
906             if (_buffer[*_idx] == ';') {
907                 *_idx += 1; // skip ';'
908             }
909             else { // no ';' at end of pair, only allowed at end of dictionary
910                 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
911                     *_exception =
912                         _makeException(*_exception, _buffer, *_idx, _len,
913                                        @"dictionary was not closed (expected '}')");
914                     didFail = YES;
915                     break; // unexpected EOF
916                 }
917
918                 if (_buffer[*_idx] != '}') { // dictionary was not closed
919                     *_exception =
920                         _makeException(*_exception, _buffer, *_idx, _len,
921                                        @"key-value pair without ';' at the end");
922                     didFail = YES;
923                     break;
924                 }
925             }
926         }
927         while ((*_idx < _len) && (result != nil) && !didFail);
928
929         if (didFail) {
930             [key    release]; key    = nil;
931             [value  release]; value  = nil;
932             [result release]; result = nil;
933             return nil;
934         }
935         else
936             return result;
937     }
938 }
939
940 static NSArray *_parseArray(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
941                             unsigned _len, NSException **_exception)
942 {
943     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
944         // EOF reached during comment-skipping
945         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
946                                      @"did not find array (expected '(')");
947         return nil;
948     }
949
950     if (_buffer[*_idx] != '(') { // it's not an array that's follows
951         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
952                                      @"did not find array (expected '(')");
953         return nil;
954     }
955     else {
956         NSMutableArray *result = nil;
957         id element = nil;
958
959         *_idx += 1; // skip '('
960
961         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
962             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
963                                          @"array was not closed (expected ')')");
964             return nil; // EOF
965         }
966
967         if (_buffer[*_idx] == ')') { // an empty array
968             *_idx += 1; // skip the ')'
969             return [[NSArray allocWithZone:_zone] init];
970         }
971
972         result = [[NSMutableArray allocWithZone:_zone] init];
973         do {
974             element = _parseProperty(_zone, _buffer, _idx, _len, _exception);
975             if (element == nil) {
976                 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
977                                              @"expected element in array");
978                 [result release]; result = nil;
979                 break;
980             }
981             [result addObject:element];
982             [element release]; element = nil;
983
984             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
985                 *_exception =
986                     _makeException(*_exception, _buffer, *_idx, _len,
987                                    @"array was not closed (expected ')' or ',')");
988                 [result release]; result = nil;
989                 break;
990             }
991
992             if (_buffer[*_idx] == ')') { // closed array
993                 *_idx += 1; // skip ')'
994                 break;
995             }
996             else if (_buffer[*_idx] == ',') { // next element
997                 *_idx += 1; // skip ','
998
999                 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1000                     *_exception =
1001                         _makeException(*_exception, _buffer, *_idx, _len,
1002                                        @"array was not closed (expected ')')");
1003                     [result release]; result = nil;
1004                     break;
1005                 }
1006                 if (_buffer[*_idx] == ')') { // closed array, like this '(1,2,)'
1007                     *_idx += 1; // skip ')'
1008                     break;
1009                 }
1010             }
1011             else { // syntax error
1012                 *_exception =
1013                     _makeException(*_exception, _buffer, *_idx, _len,
1014                                    @"expected ')' or ',' after array element");
1015                 [result release]; result = nil;
1016                 break;
1017             }
1018         }
1019         while ((*_idx < _len) && (result != nil));
1020
1021         return result;
1022     }
1023 }
1024
1025 static id _parseProperty(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
1026                          unsigned _len, NSException **_exception)
1027 {
1028     id result = nil;
1029     
1030     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1031         // no property found
1032         return nil; // EOF
1033     }
1034
1035     switch (_buffer[*_idx]) {
1036     case '"': // quoted string
1037         result = _parseString(_zone, _buffer, _idx, _len, _exception);
1038         break;
1039     case '{': // dictionary
1040         result = _parseDict(_zone, _buffer, _idx, _len, _exception);
1041         break;
1042     case '(': // array
1043         result = _parseArray(_zone, _buffer, _idx, _len, _exception);
1044         break;
1045     case '<': // data
1046         result = _parseData(_zone, _buffer, _idx, _len, _exception);
1047         break;
1048     default: // an unquoted string
1049         result = _parseString(_zone, _buffer, _idx, _len, _exception);
1050         break;
1051     }
1052
1053     return result;
1054 }
1055
1056 static NSDictionary *_parseStrings(NSZone *_zone, const unsigned char *_buffer,
1057                                    unsigned *_idx, unsigned _len,
1058                                    NSException **_exception)
1059 {
1060     NSMutableDictionary *result = nil;
1061     id   key     = nil;
1062     id   value   = nil;
1063     BOOL didFail = NO;
1064   
1065     result = [[NSMutableDictionary allocWithZone:_zone] init];
1066     while ((*_idx < _len) && (result != nil) && !didFail) {
1067         key   = nil;
1068         value = nil;
1069       
1070         if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1071             break; // expected EOF
1072
1073         // read key string
1074         key = _parseString(_zone, _buffer, _idx, _len, _exception);
1075         if (key == nil) { // syntax error
1076             if (*_exception == nil) {
1077                 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1078                                              @"got nil-key in string table ..");
1079             }
1080             didFail = YES;
1081             break;
1082         }
1083
1084         /* The following parses:  (comment|space)* '=' (comment|space)* */
1085         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1086             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1087                                          @"expected '=' after key in string table");
1088             didFail = YES;
1089             break; // unexpected EOF
1090         }
1091         // no we need a '=' assignment
1092         if (_buffer[*_idx] != '=') {
1093             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1094                                          @"expected '=' after key in string table");
1095             didFail = YES;
1096             break;
1097         }
1098         *_idx += 1; // skip '='
1099         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1100             *_exception =
1101                 _makeException(*_exception, _buffer, *_idx, _len,
1102                                @"expected value after key in string table");
1103             didFail = YES;
1104             break; // unexpected EOF
1105         }
1106
1107         // read value string
1108         value = _parseString(_zone, _buffer, _idx, _len, _exception);
1109         if (value == nil) { // syntax error
1110             if (*_exception == nil) {
1111                 *_exception =
1112                     _makeException(*_exception, _buffer, *_idx, _len,
1113                                    @"got nil-value after key in string table");
1114             }
1115             didFail = YES;
1116             break;
1117         }
1118
1119         NSCAssert(key,   @"invalid key ..");
1120         NSCAssert(value, @"invalid value ..");
1121
1122         [result setObject:value forKey:key];
1123
1124         // release key and value
1125         [key   release]; key   = nil;
1126         [value release]; value = nil;
1127
1128         // read trailing ';' if available
1129         if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1130             break; // expected EOF
1131
1132         if (_buffer[*_idx] == ';') {
1133             *_idx += 1; // skip ';'
1134         }
1135     }
1136
1137     if (didFail) {
1138         [key    release]; key    = nil;
1139         [value  release]; value  = nil;
1140         [result release]; result = nil;
1141         return nil;
1142     }
1143     else
1144         return result;
1145 }
1146
1147 // ******************** categories ********************
1148
1149 #import <Foundation/NSArray.h>
1150 #import <Foundation/NSDictionary.h>
1151 #import <Foundation/NSString.h>
1152 #import <Foundation/NSException.h>
1153
1154 @implementation NSArray(NGPropertyListParser)
1155
1156 + (id)skyArrayWithContentsOfFile:(NSString *)_path {
1157     volatile id plist = nil;
1158     
1159     NSString *format = @"%@: Caught exception %@ with reason %@ ";
1160     
1161     NS_DURING {
1162         plist = NGParsePropertyListFromFile(_path);
1163     
1164         if (![plist isKindOfClass:[NSArray class]])
1165             plist = nil;
1166     }
1167     NS_HANDLER {
1168         NSLog(format, self, [localException name], [localException reason]);
1169         plist = nil;
1170     }
1171     NS_ENDHANDLER;
1172
1173     return plist;
1174 }
1175
1176 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1177     NSArray *plist = [NSArray arrayWithContentsOfFile:_path];
1178
1179     if (plist)
1180         return [self initWithArray:plist];
1181     else {
1182         [self autorelease];
1183         return nil;
1184     }
1185 }
1186
1187 @end /* NSArray(NGPropertyListParser) */
1188
1189 @implementation NSDictionary(NGPropertyListParser)
1190
1191 + (id)skyDictionaryWithContentsOfFile:(NSString *)_path {
1192     volatile id plist = nil;
1193
1194     NSString *format = @"%@: Caught exception %@ with reason %@ ";
1195     
1196     NS_DURING {
1197         plist = NGParsePropertyListFromFile(_path);
1198     
1199         if (![plist isKindOfClass:[NSDictionary class]])
1200             plist = nil;
1201     }
1202     NS_HANDLER {
1203         NSLog(format, self, [localException name], [localException reason]);
1204         plist = nil;
1205     }
1206     NS_ENDHANDLER;
1207
1208     return plist;
1209 }
1210
1211 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1212     NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:_path];
1213
1214     if (plist)
1215         return [self initWithDictionary:plist];
1216     else {
1217         [self autorelease];
1218         return nil;
1219     }
1220 }
1221
1222 @end /* NSDictionary(NGPropertyListParser) */
1223
1224 #else /* LIB_FOUNDATION_LIBRARY */
1225
1226 #import <Foundation/NSArray.h>
1227 #import <Foundation/NSDictionary.h>
1228 #import <Foundation/NSString.h>
1229 #import <Foundation/NSException.h>
1230
1231 @implementation NSArray(NGPropertyListParser)
1232
1233 + (id)skyArrayWithContentsOfFile:(NSString *)_path {
1234     return [self arrayWithContentsOfFile:_path];
1235 }
1236
1237 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1238     [self release];
1239     return [[[self class] skyArrayWithContentsOfFile:_path] retain];
1240 }
1241
1242 @end /* NSArray(NGPropertyListParser) */
1243
1244 @implementation NSDictionary(NGPropertyListParser)
1245
1246 + (id)skyDictionaryWithContentsOfFile:(NSString *)_path {
1247     return [self dictionaryWithContentsOfFile:_path];
1248 }
1249
1250 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1251     [self release];
1252     return [[[self class] skyDictionaryWithContentsOfFile:_path] retain];
1253 }
1254
1255 @end /* NSDictionary(NGPropertyListParser) */
1256
1257 #endif /* LIB_FOUNDATION_LIBRARY */
1258
1259 /*
1260   Local Variables:
1261   c-basic-offset: 4
1262   tab-width: 8
1263   End:
1264 */