]> err.no Git - sope/blob - sope-core/NGExtensions/FdExt.subproj/NGPropertyListParser.m
fixed a bug in isNotEmpty
[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%08X endPos=0x%08X", 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 { // search for '*/'
529                     while ((pos < _len) && (_buffer[pos] != '*'))
530                         pos++;
531
532                     if (pos < _len) { // found '*'
533                         if ((pos + 1) < _len) {
534                             if (_buffer[pos + 1] == '/') { // found '*/'
535                                 commentIsClosed = YES;
536                                 pos += 2; // skip '*/'
537                                 lookAgain = YES;
538                                 break; // leave loop
539                             }
540                         }
541                     }
542                 }
543                 while (pos < _len);
544
545                 if (!commentIsClosed) {
546                     // EOF found, comment was not closed
547                     *_exception =
548                         _makeException(*_exception, _buffer, *_idx, _len,
549                                        @"comment was not closed (expected '*/')");
550                     return NO;
551                 }
552             }
553         }
554         else if (_skipSpaces && isspace((int)_buffer[pos])) {
555             pos++;
556             lookAgain = YES;
557         }
558     }
559     while (lookAgain && (pos < _len));
560
561     // store position ..
562     *_idx = pos;
563     //NSLog(@"skipped comments, now at '%s'", &(_buffer[*_idx]));
564
565     return (pos < _len);
566 }
567
568 static NSString *_parseString(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
569                               unsigned _len, NSException **_exception)
570 {
571
572     // skip comments and spaces
573     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
574         // EOF reached during comment-skipping
575         *_exception =
576             _makeException(*_exception, _buffer, *_idx, _len,
577                            @"did not find a string !");
578         return nil;
579     }
580
581     if (_buffer[*_idx] == '"') { // a quoted string
582         register unsigned pos = *_idx;
583         register unsigned len = 0;
584         unsigned startPos = pos + 1;
585         BOOL     containsEscaped = NO;
586     
587         pos++; // skip starting quote
588
589         // loop until closing quote
590         while ((_buffer[pos] != '"') && (pos < _len)) {
591             if (_buffer[pos] == '\\') {
592                 containsEscaped = YES;
593                 pos++; // skip following char
594                 if (pos == _len) {
595                     *_exception =
596                         _makeException(*_exception, _buffer, *_idx, _len,
597                                        @"escape in quoted string not finished !");
598                     return nil;
599                 }
600             }
601             pos++;
602             len++;
603         }
604
605         if (pos == _len) { // syntax error, quote not closed
606             *_idx = pos;
607             *_exception =
608                 _makeException(*_exception, _buffer, *_idx, _len,
609                                @"quoted string not closed (expected '\"')");
610             return nil;
611         }
612
613         pos++;       // skip closing quote
614         *_idx = pos; // store pointer
615         pos = 0;
616     
617         if (len == 0) { // empty string
618             return @"";
619         }
620         else if (containsEscaped) {
621             register unsigned pos2;
622             char *str = NGMallocAtomic(len + 1);
623             id   ostr = nil;
624
625             NSCAssert(len > 0, @"invalid length ..");
626
627             for (pos = startPos, pos2 = 0; _buffer[pos] != '"'; pos++, pos2++) {
628                 //NSLog(@"char=%c pos=%i pos2=%i", _buffer[pos], pos2);
629                 if (_buffer[pos] == '\\') {
630                     pos++;
631                     switch (_buffer[pos]) {
632                     case 'a':  str[pos2] = '\a'; break;
633                     case 'b':  str[pos2] = '\b'; break;
634                     case 'f':  str[pos2] = '\f'; break;
635                     case 'n':  str[pos2] = '\n'; break;
636                     case 't':  str[pos2] = '\t'; break;
637                     case 'v':  str[pos2] = '\v'; break;
638                     case '\\': str[pos2] = '\\'; break;
639             
640                     default:
641                         str[pos2] = _buffer[pos];
642                         break;
643                     }
644                 }
645                 else {
646                     str[pos2] = _buffer[pos];
647                 }
648             }
649             str[pos2] = '\0';
650             NSCAssert(pos2 == len, @"invalid unescape ..");
651
652             ostr = [[NSString allocWithZone:_zone]
653                               initWithCString:str length:len];
654             NGFree(str); str = NULL;
655
656             return ostr;
657         }
658         else {
659             NSCAssert(len > 0, @"invalid length ..");
660       
661             return [[NSString allocWithZone:_zone]
662                        initWithCString:(char*)&(_buffer[startPos]) length:len];
663         }
664     }
665     else { // an unquoted string, may not be zero chars long !
666         register unsigned pos = *_idx;
667         register unsigned len = 0;
668         unsigned startPos = pos;
669
670         // loop until break char
671         while (!_isBreakChar(_buffer[pos]) && (pos < _len)) {
672             pos++;
673             len++;
674         }
675
676         if (len == 0) { // was not a string ..
677             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
678                                          @"did not find a string !");
679             return nil;
680         }
681         else {
682             *_idx = pos;
683             return [[NSString allocWithZone:_zone]
684                        initWithCString:(char*)&(_buffer[startPos]) length:len];
685         }
686     }
687 }
688
689 static NSData *_parseData(NSZone *_zone, const unsigned char *_buffer,
690                           unsigned *_idx, unsigned _len, 
691                           NSException **_exception)
692 {
693     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
694         // EOF reached during comment-skipping
695         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
696                                      @"did not find a data (expected '<') !");
697         return nil;
698     }
699
700     if (_buffer[*_idx] != '<') { // it's not a data that's follows
701         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
702                                      @"did not find a data (expected '<') !");
703         return nil;
704     }
705     else {
706         register      unsigned pos = *_idx + 1;
707         register      unsigned len = 0;
708         unsigned      endPos = 0;
709         NSMutableData *data  = nil;
710     
711         *_idx += 1; // skip '<'
712
713         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
714             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
715                                          @"data was not closed (expected '>') ..");
716             return nil; // EOF
717         }
718
719         if (_buffer[*_idx] == '>') { // empty data
720             *_idx += 1; // skip '>'
721             return [[NSData allocWithZone:_zone] init];
722         }
723
724         // count significant chars
725         while ((_buffer[pos] != '>') && (pos < _len)) {
726             if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t'))
727                 ;
728             else if (_isHexDigit(_buffer[pos]))
729                 len++;
730             else {
731                 *_idx = pos;
732                 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
733                                              @"invalid char in data property");
734                 return nil; // abort
735             }
736             pos++;
737         }
738         if (pos == _len) {
739             *_idx = pos;
740             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
741                                          @"data was not closed (expected '>')");
742             return nil; // EOF
743         }
744         endPos = pos; // store position of closing '>'
745
746         // if odd, then add one byte for trailing nibble
747         len = (len % 2 == 1) ? len / 2 + 1 : len / 2;
748         data = [[NSMutableData allocWithZone:_zone] initWithLength:len];
749
750         // now copy bytes ..
751         {
752             register unsigned i;
753             register unsigned pending = -1;
754             char *buf = [data mutableBytes];
755       
756             for (pos = *_idx, i = 0; (pos < endPos) && (i < len); pos++) {
757                 int value = _valueOfHexChar(_buffer[pos]);
758
759                 if (value != -1) {
760                     if (pending == -1)
761                         pending = value;
762                     else {
763                         value = value * 16 + pending;
764                         pending = -1;
765
766                         buf[i] = value;
767                         i++;
768                     }
769                 }
770             }
771             if (pending != -1) { // was odd, now add the trailer ..
772                 NSCAssert(i < len, @"invalid length ..");
773                 buf[i] = pending * 16;
774             }
775         }
776     
777         // update global position
778         *_idx = endPos + 1; // endPos + 1 (*endPos == '>', 1 => skips '>')
779
780         return data;
781     }
782 }
783
784 static NSDictionary *_parseDict(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
785                                 unsigned _len, NSException **_exception)
786 {
787     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
788         // EOF reached during comment-skipping
789         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
790                                      @"did not find dictionary (expected '{')");
791         return nil;
792     }
793   
794     if (_buffer[*_idx] != '{') { // it's not a dict that's follows
795         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
796                                      @"did not find dictionary (expected '{')");
797         return nil;
798     }
799     else {
800         NSMutableDictionary *result = nil;
801         id   key     = nil;
802         id   value   = nil;
803         BOOL didFail = NO;
804     
805         *_idx += 1; // skip '{'
806
807         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
808             *_exception =
809                 _makeException(*_exception, _buffer, *_idx, _len,
810                                @"dictionary was not closed (expected '}')");
811             return nil; // EOF
812         }
813
814         if (_buffer[*_idx] == '}') { // an empty dictionary
815             *_idx += 1; // skip the '}'
816             return [[NSDictionary allocWithZone:_zone] init];
817         }
818
819         result = [[NSMutableDictionary allocWithZone:_zone] init];
820         do {
821             key   = nil;
822             value = nil;
823       
824             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
825                 *_exception =
826                     _makeException(*_exception, _buffer, *_idx, _len,
827                                    @"dictionary was not closed (expected '}')");
828                 didFail = YES;
829                 break; // unexpected EOF
830             }
831
832             if (_buffer[*_idx] == '}') { // dictionary closed
833                 *_idx += 1; // skip the '}'
834                 break;
835             }
836       
837             // read key property
838             key = _parseProperty(_zone, _buffer, _idx, _len, _exception);
839             if (key == nil) { // syntax error
840                 if (*_exception == nil) {
841                     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
842                                                  @"got nil-key in dictionary ..");
843                 }
844                 didFail = YES;
845                 break;
846             }
847
848             /* The following parses:  (comment|space)* '=' (comment|space)* */
849             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
850                 *_exception =
851                     _makeException(*_exception, _buffer, *_idx, _len,
852                                    @"expected '=' after key in dictionary");
853                 didFail = YES;
854                 break; // unexpected EOF
855             }
856             // no we need a '=' assignment
857             if (_buffer[*_idx] != '=') {
858                 *_exception =
859                     _makeException(*_exception, _buffer, *_idx, _len,
860                                    @"expected '=' after key in dictionary");
861                 didFail = YES;
862                 break;
863             }
864             *_idx += 1; // skip '='
865             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
866                 *_exception =
867                     _makeException(*_exception, _buffer, *_idx, _len,
868                                    @"expected value after key '=' in dictionary");
869                 didFail = YES;
870                 break; // unexpected EOF
871             }
872
873             // read value property
874             value = _parseProperty(_zone, _buffer, _idx, _len, _exception);
875             if (value == nil) { // syntax error
876                 if (*_exception == nil) {
877                     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
878                                                  @"got nil-value in dictionary");
879                 }
880                 didFail = YES;
881                 break;
882             }
883
884             NSCAssert(key,   @"invalid key ..");
885             NSCAssert(value, @"invalid value ..");
886
887             [result setObject:value forKey:key];
888
889             // release key and value
890             [key   release]; key   = nil;
891             [value release]; value = nil;
892
893             // read trailing ';' if available
894             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
895                 *_exception =
896                     _makeException(*_exception, _buffer, *_idx, _len,
897                                    @"dictionary was not closed (expected '}')");
898                 didFail = YES;
899                 break; // unexpected EOF
900             }
901             if (_buffer[*_idx] == ';') {
902                 *_idx += 1; // skip ';'
903             }
904             else { // no ';' at end of pair, only allowed at end of dictionary
905                 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
906                     *_exception =
907                         _makeException(*_exception, _buffer, *_idx, _len,
908                                        @"dictionary was not closed (expected '}')");
909                     didFail = YES;
910                     break; // unexpected EOF
911                 }
912
913                 if (_buffer[*_idx] != '}') { // dictionary was not closed
914                     *_exception =
915                         _makeException(*_exception, _buffer, *_idx, _len,
916                                        @"key-value pair without ';' at the end");
917                     didFail = YES;
918                     break;
919                 }
920             }
921         }
922         while ((*_idx < _len) && (result != nil) && !didFail);
923
924         if (didFail) {
925             [key    release]; key    = nil;
926             [value  release]; value  = nil;
927             [result release]; result = nil;
928             return nil;
929         }
930         else
931             return result;
932     }
933 }
934
935 static NSArray *_parseArray(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
936                             unsigned _len, NSException **_exception)
937 {
938     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
939         // EOF reached during comment-skipping
940         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
941                                      @"did not find array (expected '(')");
942         return nil;
943     }
944
945     if (_buffer[*_idx] != '(') { // it's not an array that's follows
946         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
947                                      @"did not find array (expected '(')");
948         return nil;
949     }
950     else {
951         NSMutableArray *result = nil;
952         id element = nil;
953
954         *_idx += 1; // skip '('
955
956         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
957             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
958                                          @"array was not closed (expected ')')");
959             return nil; // EOF
960         }
961
962         if (_buffer[*_idx] == ')') { // an empty array
963             *_idx += 1; // skip the ')'
964             return [[NSArray allocWithZone:_zone] init];
965         }
966
967         result = [[NSMutableArray allocWithZone:_zone] init];
968         do {
969             element = _parseProperty(_zone, _buffer, _idx, _len, _exception);
970             if (element == nil) {
971                 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
972                                              @"expected element in array");
973                 [result release]; result = nil;
974                 break;
975             }
976             [result addObject:element];
977             [element release]; element = nil;
978
979             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
980                 *_exception =
981                     _makeException(*_exception, _buffer, *_idx, _len,
982                                    @"array was not closed (expected ')' or ',')");
983                 [result release]; result = nil;
984                 break;
985             }
986
987             if (_buffer[*_idx] == ')') { // closed array
988                 *_idx += 1; // skip ')'
989                 break;
990             }
991             else if (_buffer[*_idx] == ',') { // next element
992                 *_idx += 1; // skip ','
993
994                 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
995                     *_exception =
996                         _makeException(*_exception, _buffer, *_idx, _len,
997                                        @"array was not closed (expected ')')");
998                     [result release]; result = nil;
999                     break;
1000                 }
1001                 if (_buffer[*_idx] == ')') { // closed array, like this '(1,2,)'
1002                     *_idx += 1; // skip ')'
1003                     break;
1004                 }
1005             }
1006             else { // syntax error
1007                 *_exception =
1008                     _makeException(*_exception, _buffer, *_idx, _len,
1009                                    @"expected ')' or ',' after array element");
1010                 [result release]; result = nil;
1011                 break;
1012             }
1013         }
1014         while ((*_idx < _len) && (result != nil));
1015
1016         return result;
1017     }
1018 }
1019
1020 static id _parseProperty(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
1021                          unsigned _len, NSException **_exception)
1022 {
1023     id result = nil;
1024     
1025     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1026         // no property found
1027         return nil; // EOF
1028     }
1029
1030     switch (_buffer[*_idx]) {
1031     case '"': // quoted string
1032         result = _parseString(_zone, _buffer, _idx, _len, _exception);
1033         break;
1034     case '{': // dictionary
1035         result = _parseDict(_zone, _buffer, _idx, _len, _exception);
1036         break;
1037     case '(': // array
1038         result = _parseArray(_zone, _buffer, _idx, _len, _exception);
1039         break;
1040     case '<': // data
1041         result = _parseData(_zone, _buffer, _idx, _len, _exception);
1042         break;
1043     default: // an unquoted string
1044         result = _parseString(_zone, _buffer, _idx, _len, _exception);
1045         break;
1046     }
1047
1048     return result;
1049 }
1050
1051 static NSDictionary *_parseStrings(NSZone *_zone, const unsigned char *_buffer,
1052                                    unsigned *_idx, unsigned _len,
1053                                    NSException **_exception)
1054 {
1055     NSMutableDictionary *result = nil;
1056     id   key     = nil;
1057     id   value   = nil;
1058     BOOL didFail = NO;
1059   
1060     result = [[NSMutableDictionary allocWithZone:_zone] init];
1061     while ((*_idx < _len) && (result != nil) && !didFail) {
1062         key   = nil;
1063         value = nil;
1064       
1065         if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1066             break; // expected EOF
1067
1068         // read key string
1069         key = _parseString(_zone, _buffer, _idx, _len, _exception);
1070         if (key == nil) { // syntax error
1071             if (*_exception == nil) {
1072                 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1073                                              @"got nil-key in string table ..");
1074             }
1075             didFail = YES;
1076             break;
1077         }
1078
1079         /* The following parses:  (comment|space)* '=' (comment|space)* */
1080         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1081             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1082                                          @"expected '=' after key in string table");
1083             didFail = YES;
1084             break; // unexpected EOF
1085         }
1086         // no we need a '=' assignment
1087         if (_buffer[*_idx] != '=') {
1088             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1089                                          @"expected '=' after key in string table");
1090             didFail = YES;
1091             break;
1092         }
1093         *_idx += 1; // skip '='
1094         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1095             *_exception =
1096                 _makeException(*_exception, _buffer, *_idx, _len,
1097                                @"expected value after key in string table");
1098             didFail = YES;
1099             break; // unexpected EOF
1100         }
1101
1102         // read value string
1103         value = _parseString(_zone, _buffer, _idx, _len, _exception);
1104         if (value == nil) { // syntax error
1105             if (*_exception == nil) {
1106                 *_exception =
1107                     _makeException(*_exception, _buffer, *_idx, _len,
1108                                    @"got nil-value after key in string table");
1109             }
1110             didFail = YES;
1111             break;
1112         }
1113
1114         NSCAssert(key,   @"invalid key ..");
1115         NSCAssert(value, @"invalid value ..");
1116
1117         [result setObject:value forKey:key];
1118
1119         // release key and value
1120         [key   release]; key   = nil;
1121         [value release]; value = nil;
1122
1123         // read trailing ';' if available
1124         if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1125             break; // expected EOF
1126
1127         if (_buffer[*_idx] == ';') {
1128             *_idx += 1; // skip ';'
1129         }
1130     }
1131
1132     if (didFail) {
1133         [key    release]; key    = nil;
1134         [value  release]; value  = nil;
1135         [result release]; result = nil;
1136         return nil;
1137     }
1138     else
1139         return result;
1140 }
1141
1142 // ******************** categories ********************
1143
1144 #import <Foundation/NSArray.h>
1145 #import <Foundation/NSDictionary.h>
1146 #import <Foundation/NSString.h>
1147 #import <Foundation/NSException.h>
1148
1149 @implementation NSArray(NGPropertyListParser)
1150
1151 + (id)skyArrayWithContentsOfFile:(NSString *)_path {
1152     volatile id plist = nil;
1153     
1154     NSString *format = @"%@: Caught exception %@ with reason %@ ";
1155     
1156     NS_DURING {
1157         plist = NGParsePropertyListFromFile(_path);
1158     
1159         if (![plist isKindOfClass:[NSArray class]])
1160             plist = nil;
1161     }
1162     NS_HANDLER {
1163         NSLog(format, self, [localException name], [localException reason]);
1164         plist = nil;
1165     }
1166     NS_ENDHANDLER;
1167
1168     return plist;
1169 }
1170
1171 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1172     NSArray *plist = [NSArray arrayWithContentsOfFile:_path];
1173
1174     if (plist)
1175         return [self initWithArray:plist];
1176     else {
1177         [self autorelease];
1178         return nil;
1179     }
1180 }
1181
1182 @end /* NSArray(NGPropertyListParser) */
1183
1184 @implementation NSDictionary(NGPropertyListParser)
1185
1186 + (id)skyDictionaryWithContentsOfFile:(NSString *)_path {
1187     volatile id plist = nil;
1188
1189     NSString *format = @"%@: Caught exception %@ with reason %@ ";
1190     
1191     NS_DURING {
1192         plist = NGParsePropertyListFromFile(_path);
1193     
1194         if (![plist isKindOfClass:[NSDictionary class]])
1195             plist = nil;
1196     }
1197     NS_HANDLER {
1198         NSLog(format, self, [localException name], [localException reason]);
1199         plist = nil;
1200     }
1201     NS_ENDHANDLER;
1202
1203     return plist;
1204 }
1205
1206 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1207     NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:_path];
1208
1209     if (plist)
1210         return [self initWithDictionary:plist];
1211     else {
1212         [self autorelease];
1213         return nil;
1214     }
1215 }
1216
1217 @end /* NSDictionary(NGPropertyListParser) */
1218
1219 #else /* LIB_FOUNDATION_LIBRARY */
1220
1221 #import <Foundation/NSArray.h>
1222 #import <Foundation/NSDictionary.h>
1223 #import <Foundation/NSString.h>
1224 #import <Foundation/NSException.h>
1225
1226 @implementation NSArray(NGPropertyListParser)
1227
1228 + (id)skyArrayWithContentsOfFile:(NSString *)_path {
1229     return [self arrayWithContentsOfFile:_path];
1230 }
1231
1232 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1233     [self release];
1234     return [[[self class] skyArrayWithContentsOfFile:_path] retain];
1235 }
1236
1237 @end /* NSArray(NGPropertyListParser) */
1238
1239 @implementation NSDictionary(NGPropertyListParser)
1240
1241 + (id)skyDictionaryWithContentsOfFile:(NSString *)_path {
1242     return [self dictionaryWithContentsOfFile:_path];
1243 }
1244
1245 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1246     [self release];
1247     return [[[self class] skyDictionaryWithContentsOfFile:_path] retain];
1248 }
1249
1250 @end /* NSDictionary(NGPropertyListParser) */
1251
1252 #endif /* LIB_FOUNDATION_LIBRARY */
1253
1254 /*
1255   Local Variables:
1256   c-basic-offset: 4
1257   tab-width: 8
1258   End:
1259 */