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