]> err.no Git - sope/blob - libFoundation/Foundation/PropertyListParser.m
Drop apache 1 build-dependency
[sope] / libFoundation / Foundation / PropertyListParser.m
1 /* 
2    PropertyListParser.m
3
4    Copyright (C) 1998 MDlink online service center, Helge Hess
5    All rights reserved.
6
7    Author: Helge Hess (helge@mdlink.de)
8
9    This file is part of libFoundation.
10
11    Permission to use, copy, modify, and distribute this software and its
12    documentation for any purpose and without fee is hereby granted, provided
13    that the above copyright notice appear in all copies and that both that
14    copyright notice and this permission notice appear in supporting
15    documentation.
16
17    We disclaim all warranties with regard to this software, including all
18    implied warranties of merchantability and fitness, in no event shall
19    we be liable for any special, indirect or consequential damages or any
20    damages whatsoever resulting from loss of use, data or profits, whether in
21    an action of contract, negligence or other tortious action, arising out of
22    or in connection with the use or performance of this software.
23 */
24
25 #include <ctype.h>
26 #include <Foundation/common.h>
27 #include <Foundation/NSException.h>
28 #include <Foundation/NSArray.h>
29 #include <Foundation/NSData.h>
30 #include <Foundation/NSDictionary.h>
31 #include <Foundation/NSString.h>
32 #include <Foundation/NSValue.h>
33 #include <Foundation/NSNull.h>
34 #include <Foundation/exceptions/GeneralExceptions.h>
35
36 #ifdef NeXT /* NeXT Mach map_fd() */
37 #  include <mach/mach.h>
38 #  include <libc.h>
39 #elif defined(HAVE_MMAP) /* Posix mmap() */
40 #  include <sys/types.h>
41 #  include <sys/mman.h>
42 #  include <unistd.h>
43 #else  /* No file mapping available */
44 #endif
45
46 #include "PropertyListParser.h"
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                                    NSString *fname);
67
68 // public functions
69
70 NSString *NSParseStringFromBuffer(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(nil, _buffer, &idx, _len, &exception);
90     result = AUTORELEASE(result);
91     if (exception)
92         [exception raise];
93     return result;
94 }
95
96 NSArray *NSParseArrayFromBuffer(const unsigned char *_buffer, unsigned _len)
97 {
98     NSArray     *result    = nil;
99     NSException *exception = nil;
100     unsigned    idx        = 0;
101
102     if (_len >= 2) {
103         if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
104             NSLog(@"WARNING(%s): tried to parse Unicode array (FE/FF) ...",
105                   __PRETTY_FUNCTION__);
106             return nil;
107         }
108         else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
109             NSLog(@"WARNING(%s): tried to parse Unicode array (FF/FE) ...",
110                   __PRETTY_FUNCTION__);
111             return nil;
112         }
113     }
114     
115     result = _parseArray(nil, _buffer, &idx, _len, &exception);
116     result = AUTORELEASE(result);
117     if (exception)
118         [exception raise];
119     return result;
120 }
121
122 NSDictionary *NSParseDictionaryFromBuffer(const unsigned char *_buffer, unsigned _len)
123 {
124     NSDictionary *result    = nil;
125     NSException  *exception = nil;
126     unsigned     idx        = 0;
127
128     if (_len >= 2) {
129         if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
130             NSLog(@"WARNING(%s): tried to parse Unicode dict (FE/FF) ...",
131                   __PRETTY_FUNCTION__);
132             return nil;
133         }
134         else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
135             NSLog(@"WARNING(%s): tried to parse Unicode dict (FF/FE) ...",
136                   __PRETTY_FUNCTION__);
137             return nil;
138         }
139     }
140     
141     result = _parseDict(nil, _buffer, &idx, _len, &exception);
142     result = AUTORELEASE(result);
143     if (exception)
144         [exception raise];
145     return result;
146 }
147
148 NSString *NSParseStringFromData(NSData *_data)
149 {
150     return NSParseStringFromBuffer([_data bytes], [_data length]);
151 }
152
153 NSArray *NSParseArrayFromData(NSData *_data)
154 {
155     return NSParseArrayFromBuffer([_data bytes], [_data length]);
156 }
157
158 NSDictionary *NSParseDictionaryFromData(NSData *_data)
159 {
160     return NSParseDictionaryFromBuffer([_data bytes], [_data length]);
161 }
162
163 NSString *NSParseStringFromString(NSString *_str)
164 {
165     NSString *s;
166     char     *buf;
167     unsigned len;
168     
169     len = [_str cStringLength];
170     buf = malloc(len + 1);
171     [_str getCString:buf]; buf[len] = '\0';
172     s = NSParseStringFromBuffer(buf, len);
173     free(buf);
174     return s;
175 }
176
177 NSArray *NSParseArrayFromString(NSString *_str)
178 {
179     NSArray  *a;
180     char     *buf;
181     unsigned len;
182     
183     len = [_str cStringLength];
184     buf = malloc(len + 1);
185     [_str getCString:buf]; buf[len] = '\0';
186     a = NSParseArrayFromBuffer(buf, len);
187     free(buf);
188     return a;
189 }
190
191 NSDictionary *NSParseDictionaryFromString(NSString *_str)
192 {
193     NSDictionary *d;
194     char         *buf;
195     unsigned     len;
196     
197     len = [_str cStringLength];
198     buf = malloc(len + 1);
199     [_str getCString:buf]; buf[len] = '\0';
200     d = NSParseDictionaryFromBuffer(buf, len);
201     free(buf);
202     return d;
203 }
204
205 id NSParsePropertyListFromBuffer(const unsigned char *_buffer, unsigned _len)
206 {
207     id          result = nil;
208     NSException *exception = nil;
209     unsigned    idx = 0;
210
211     if (_len >= 2) {
212         if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
213             NSLog(@"WARNING(%s): tried to parse Unicode plist (FE/FF) ...",
214                   __PRETTY_FUNCTION__);
215             return nil;
216         }
217         else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
218             NSLog(@"WARNING(%s): tried to parse Unicode plist (FF/FE) ...",
219                   __PRETTY_FUNCTION__);
220             return nil;
221         }
222     }
223     
224     result = _parseProperty(nil, _buffer, &idx, _len, &exception);
225     result = AUTORELEASE(result);
226     if (exception)
227         [exception raise];
228     return result;
229 }
230
231 id NSParsePropertyListFromData(NSData *_data)
232 {
233     return NSParsePropertyListFromBuffer([_data bytes], [_data length]);
234 }
235
236 id NSParsePropertyListFromString(NSString *_string)
237 {
238     unsigned len;
239     char     *buf;
240     id       p;
241
242     len = [_string cStringLength];
243     buf = malloc(len + 1);
244     [_string getCString:buf]; buf[len] = '\0';
245     
246     p = NSParsePropertyListFromBuffer(buf, len);
247     free(buf);
248     return p;
249 }
250
251 id NSParsePropertyListFromFile(NSString *_path)
252 {
253     if (_path == nil) {
254         return nil;
255     }
256     else {
257 #if HAVE_MMAP
258         NSException *exception = nil;
259         int         fd         = 0;
260         id          result     = nil;
261         unsigned    plen       = [_path cStringLength];
262         char        *path;
263         
264         path = malloc(plen + 1);
265         [_path getCString:path]; path[plen] = '\0';
266         
267         if ((fd = open(path, O_RDONLY, 0)) != -1) {
268             struct stat statInfo;
269             
270             if (fstat(fd, &statInfo) == 0) {
271                 void *mem = NULL;
272
273                 mem = mmap(0, statInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
274                 if ((mem != MAP_FAILED) || (mem == NULL)) {
275                     NS_DURING {
276                         unsigned idx = 0;
277                         result = _parseProperty(nil, mem, &idx, statInfo.st_size,
278                                                 &exception);
279                     }
280                     NS_HANDLER {
281                         result = nil;
282                         exception = [localException retain];
283                     }
284                     NS_ENDHANDLER;
285                     
286                     munmap(mem, statInfo.st_size);
287                     mem = NULL;
288                 }
289                 else {
290                     NSLog(@"%s: Couldn't map file '%@' into virtual memory !",
291                           __PRETTY_FUNCTION__, _path);
292                 }
293             }
294             else {
295                 NSLog(@"%s: File '%@' couldn't be mapped !",
296                       __PRETTY_FUNCTION__,_path);
297             }
298             close(fd);
299         }
300 #if 0
301         else {
302             NSLog(@"%s: File %@ does not exist !", __PRETTY_FUNCTION__, _path);
303         }
304 #endif
305
306         if (path) {
307             free(path);
308             path = NULL;
309         }
310         
311         result = AUTORELEASE(result);
312         if (exception) {
313             NSMutableDictionary *ui;
314             ui = [[exception userInfo] mutableCopy];
315             [ui setObject:_path forKey:@"path"];
316             [exception setUserInfo:ui];
317             RELEASE(ui); ui = nil;
318             [exception raise];
319         }
320         return result;
321 #else
322         NSData *data;
323         
324         if ((data = [NSData dataWithContentsOfFile:_path])) {
325             id          result = nil;
326             NSException *exception = nil;
327             unsigned    idx = 0;
328             void        *_buffer;
329             unsigned    _len;
330
331             _buffer = (void *)[data bytes];
332             _len    = [data length];
333             
334             result = _parseProperty(nil, _buffer, &idx, _len, &exception);
335             result = AUTORELEASE(result);
336             
337             if (exception) {
338                 NSMutableDictionary *ui;
339                 ui = [[exception userInfo] mutableCopy];
340                 [ui setObject:_path forKey:@"path"];
341                 [exception setUserInfo:ui];
342                 RELEASE(ui); ui = nil;
343                 
344                 [exception raise];
345             }
346             return result;
347         }
348         else {
349             NSLog(@"%s: couldn't read file %@ !", __PRETTY_FUNCTION__, _path);
350             return nil;
351         }
352 #endif
353     }
354 }
355
356 id NSParseStringsFromBuffer(const unsigned char *_buffer, unsigned _len)
357 {
358     NSDictionary *result    = nil;
359     NSException  *exception = nil;
360     unsigned     idx        = 0;
361
362     result = _parseStrings(nil, _buffer, &idx, _len, &exception, @"<buffer>");
363     result = AUTORELEASE(result);
364     if (exception)
365         [exception raise];
366     return result;
367 }
368
369 id NSParseStringsFromData(NSData *_data)
370 {
371     NSDictionary *result    = nil;
372     NSException  *exception = nil;
373     unsigned     idx        = 0;
374
375     result = _parseStrings(nil, [_data bytes], &idx, [_data length],
376                            &exception, @"<data>");
377     result = AUTORELEASE(result);
378     if (exception)
379         [exception raise];
380     return result;
381 }
382
383 id NSParseStringsFromString(NSString *_string)
384 {
385     NSDictionary *o;
386     unsigned     len;
387     char         *buf;
388     NSException  *exception = nil;
389     unsigned     idx        = 0;
390     
391     len = [_string cStringLength];
392     buf = malloc(len + 1);
393     [_string getCString:buf]; buf[len] = '\0';
394     
395     o = _parseStrings(nil, buf, &idx, len, &exception, @"<string>");
396     o = AUTORELEASE(o);
397     
398     free(buf);
399
400     if (exception)
401         [exception raise];
402     
403     return o;
404 }
405
406 id NSParseStringsFromFile(NSString *_path)
407 {
408 #ifdef HAVE_MMAP
409     struct stat statInfo;
410     NSException *exception = nil;
411     int         fd = 0;
412     id          result = nil;
413     unsigned    plen = [_path cStringLength];
414     char        *path;
415     
416     path = malloc(plen + 1);
417     [_path getCString:path]; path[plen] = '\0';
418     
419     fd = open(path, O_RDONLY, 0);
420     free(path); path = NULL;
421     
422     if (fd != -1) {
423         if (fstat(fd, &statInfo) == 0) {
424             void *mem = NULL;
425
426             mem = mmap(0, statInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
427             if (mem != MAP_FAILED) {
428                 NS_DURING {
429                     unsigned idx = 0;
430                     result = _parseStrings(nil, mem, &idx, statInfo.st_size,
431                                            &exception,
432                                            _path);
433                 }
434                 NS_HANDLER {
435                     exception = [localException retain];
436                     result = nil;
437                 }
438                 NS_ENDHANDLER;
439                 
440                 munmap(mem, statInfo.st_size);
441                 mem = NULL;
442             }
443             else
444                 NSLog(@"Couldn't map file %@ into virtual memory !", _path);
445         }
446         else {
447             NSLog(@"File %@ couldn't be mapped !", _path);
448         }
449         close(fd);
450     }
451 #if 0
452     else {
453         NSLog(@"File %@ does not exist !", _path);
454     }
455 #endif
456
457     result = AUTORELEASE(result);
458     if (exception) {
459         NSMutableDictionary *ui;
460         ui = [[exception userInfo] mutableCopy];
461         [ui setObject:_path forKey:@"path"];
462         [exception setUserInfo:ui];
463         RELEASE(ui); ui = nil;
464         
465         [exception raise];
466     }
467     return result;
468 #else
469     NSData *data;
470
471     if ((data = [NSData dataWithContentsOfFile:_path])) {
472         NSDictionary *result    = nil;
473         NSException  *exception = nil;
474         unsigned     idx        = 0;
475         void         *_buffer;
476         unsigned     _len;
477
478         _buffer = (void *)[data bytes];
479         _len    = [data length];
480         result  = _parseStrings(nil, _buffer, &idx, _len, &exception, _path);
481         result  = AUTORELEASE(result);
482         
483         if (exception) {
484             NSMutableDictionary *ui;
485             ui = [[exception userInfo] mutableCopy];
486             [ui setObject:_path forKey:@"path"];
487             [exception setUserInfo:ui];
488             RELEASE(ui); ui = nil;
489                 
490             [exception raise];
491         }
492         return result;
493     }
494     else {
495         NSLog(@"%s: couldn't read file %@ !", __PRETTY_FUNCTION__, _path);
496         return nil;
497     }
498 #endif
499 }
500
501 /* ******************* implementation ******************** */
502
503 static inline BOOL _isBreakChar(unsigned char _c)
504 {
505     switch (_c) {
506         case ' ': case '\t': case '\n': case '\r':
507         case '/': case '=':  case ';':  case ',':
508         case '{': case '(':  case '"':  case '<':
509         case ')': case '}':  case '>':
510             return YES;
511
512         default:
513             return NO;
514     }
515 }
516 static inline BOOL _isUnquotedStringEndChar(unsigned char _c) {
517     switch (_c) {
518         case ' ': case '\t': case '\n': case '\r':
519         case '=':  case ';':  case ',': case '"':
520         case ')': case '}':  case '>':
521             return YES;
522         
523         default:
524             return NO;
525     }
526 }
527
528 static inline int _valueOfHexChar(char _c)
529 {
530     switch (_c) {
531         case '0': case '1': case '2': case '3': case '4':
532         case '5': case '6': case '7': case '8': case '9':
533             return (_c - '0'); // 0-9 (ascii-char)'0' - 48 => (int)0
534       
535         case 'A': case 'B': case 'C':
536         case 'D': case 'E': case 'F':
537             return (_c - 'A' + 10); // A-F, A=10..F=15, 'A'=65..'F'=70
538       
539         case 'a': case 'b': case 'c':
540         case 'd': case 'e': case 'f':
541             return (_c - 'a' + 10); // a-f, a=10..F=15, 'a'=97..'f'=102
542
543         default:
544             return -1;
545     }
546 }
547 static inline BOOL _isHexDigit(char _c)
548 {
549     switch (_c) {
550         case '0': case '1': case '2': case '3': case '4':
551         case '5': case '6': case '7': case '8': case '9':
552         case 'A': case 'B': case 'C':
553         case 'D': case 'E': case 'F':
554         case 'a': case 'b': case 'c':
555         case 'd': case 'e': case 'f':
556             return YES;
557
558         default:
559             return NO;
560     }
561 }
562
563 static inline int _numberOfLines(const unsigned char *_buffer, unsigned _lastIdx)
564 {
565     register unsigned int pos, lineCount = 1;
566
567     for (pos = 0; (pos < _lastIdx) && (_buffer[pos] != '\0'); pos++) {
568         if (_buffer[pos] == '\n')
569             lineCount++;
570     }
571     return lineCount;
572 }
573
574 static NSException *_makeException(NSException *_exception,
575                                    const unsigned char *_buffer, unsigned _idx,
576                                    unsigned _len, NSString *_text)
577 {
578     NSMutableDictionary *ui = nil;
579     NSException *exception = nil;
580     int         numLines;
581     BOOL        atEof;
582     
583     numLines   = _numberOfLines(_buffer, _idx);
584     atEof      = (_idx >= _len) ? YES : NO;
585     
586     /* error resulted from a previous error (exception already set) */
587     if (_exception) 
588         return _exception;
589     
590     exception = [[SyntaxErrorException alloc] init];
591     
592     _text = atEof
593         ? [NSString stringWithFormat:@"Unexpected end: %@", _text]
594         : [NSString stringWithFormat:@"Syntax error in line %i: %@",
595                       numLines,_text];
596   
597     [exception setReason:_text];
598
599     // user info
600     {
601         ui = [[exception userInfo] mutableCopy];
602         if (ui == nil)
603             ui = [[NSMutableDictionary alloc] initWithCapacity:8];
604
605         [ui setObject:[NSNumber numberWithInt:numLines] forKey:@"line"];
606         [ui setObject:[NSNumber numberWithInt:_len]     forKey:@"size"];
607         [ui setObject:[NSNumber numberWithInt:_idx]     forKey:@"position"];
608
609         /*
610           if (_len > 0)
611           [ui setObject:[NSString stringWithCString:_buffer length:_len]
612               forKey:@"text"];
613     
614           if (!atEof && (_idx > 0)) {
615           [ui setObject:[NSString stringWithCString:_buffer length:_idx]
616           forKey:@"consumedText"];
617           }
618         */
619         if (!atEof && (_idx > 0)) {
620             register unsigned pos;
621             const unsigned char *startPos, *endPos;
622
623             for (pos = _idx; (pos >= 0) && (_buffer[pos] != '\n'); pos--)
624                 ;
625             startPos = &(_buffer[pos + 1]);
626
627             for (pos = _idx; ((pos < _len) && (_buffer[pos] != '\n')); pos++)
628                 ;
629             endPos = &(_buffer[pos - 1]);
630             
631             if (startPos < endPos) {
632                 [ui setObject:[NSString stringWithCString:startPos
633                                         length:(endPos - startPos)]
634                     forKey:@"lastLine"];
635             }
636             else {
637                 NSLog(@"%s: startPos=0x%p endPos=0x%p",
638                       __PRETTY_FUNCTION__, startPos, endPos);
639             }
640         }
641     
642         [exception setUserInfo:ui];
643     
644         RELEASE(ui); ui = nil;
645     }
646
647     return exception;
648 }
649
650 static BOOL _skipComments(const unsigned char *_buffer,
651                           unsigned *_idx, unsigned _len,
652                           BOOL _skipSpaces, NSException **_exception)
653 {
654     register unsigned pos = *_idx;
655     BOOL lookAgain;
656
657     if (pos >= _len)
658         return NO;
659     
660     do { // until all comments are filtered ..
661         lookAgain = NO;
662         
663         if ((_buffer[pos] == '/') && ((pos + 1) < _len)) {
664             if (_buffer[pos + 1] == '/') { // single line comments
665                 pos += 2; // skip '//'
666                 
667                 // search for '\n' ..
668                 while ((pos < _len) && (_buffer[pos] != '\n'))
669                     pos++;
670
671                 if ((pos < _len) && (_buffer[pos] == '\n')) {
672                     pos++; // skip newline, otherwise EOF was reached
673                     lookAgain = YES;
674                 }
675             }
676             else if (_buffer[pos + 1] == '*') { /* multiline comments */
677                 BOOL commentIsClosed = NO;
678                 
679                 pos += 2; // skip '/*'
680                 
681                 do { // search for '*/'
682                     while ((pos < _len) && (_buffer[pos] != '*'))
683                         pos++;
684
685                     if (pos < _len) { // found '*'
686                         pos++; // skip '*'
687                         
688                         if (pos < _len) {
689                             if (_buffer[pos] == '/') { // found '*/'
690                                 commentIsClosed = YES;
691                                 pos ++; // skip '/'
692                                 lookAgain = YES;
693                                 break; // leave loop
694                             }
695 #if DEBUG_PLIST
696                             else
697                                 printf("'*' inside multiline comment !\n");
698 #endif
699                         }
700                     }
701                 }
702                 while (pos < _len);
703                 
704                 if (!commentIsClosed) {
705                     // EOF found, comment wasn't closed
706                     *_exception =
707                         _makeException(*_exception, _buffer, *_idx, _len,
708                                        @"comment was not closed "
709                                        @"(expected '*/')");
710                     return NO;
711                 }
712             }
713         }
714         else if (_skipSpaces && isspace((int)_buffer[pos])) {
715             pos++;
716             lookAgain = YES;
717         }
718     }
719     while (lookAgain && (pos < _len));
720     
721     // store position ..
722     *_idx = pos;
723     //NSLog(@"skipped comments, now at '%s'", &(_buffer[*_idx]));
724
725     return (pos < _len);
726 }
727
728 static NSString *_parseString(NSZone *_zone, const unsigned char *_buffer,
729                               unsigned *_idx,
730                               unsigned _len, NSException **_exception)
731 {
732
733     // skip comments and spaces
734     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
735         // EOF reached during comment-skipping
736         *_exception =
737             _makeException(*_exception, _buffer, *_idx, _len,
738                            @"did not find a string !");
739         return nil;
740     }
741
742     if (_buffer[*_idx] == '"') { // a quoted string
743         register unsigned pos = *_idx;
744         register unsigned len = 0;
745         unsigned startPos = pos + 1;
746         BOOL     containsEscaped = NO;
747     
748         pos++; // skip starting quote
749
750         // loop until closing quote
751         while ((_buffer[pos] != '"') && (pos < _len)) {
752             if (_buffer[pos] == '\\') {
753                 containsEscaped = YES;
754                 pos++; // skip following char
755                 if (pos == _len) {
756                     *_exception =
757                         _makeException(*_exception, _buffer, *_idx, _len,
758                                        @"escape in quoted string not finished !");
759                     return nil;
760                 }
761             }
762             pos++;
763             len++;
764         }
765
766         if (pos == _len) { // syntax error, quote not closed
767             *_idx = pos;
768             *_exception =
769                 _makeException(*_exception, _buffer, *_idx, _len,
770                                @"quoted string not closed (expected '\"')");
771             return nil;
772         }
773
774         pos++;       // skip closing quote
775         *_idx = pos; // store pointer
776         pos = 0;
777     
778         if (len == 0) { // empty string
779             return @"";
780         }
781         else if (containsEscaped) {
782             register unsigned pos2;
783             char *str = MallocAtomic(len + 1);
784             id   ostr = nil;
785
786             NSCAssert(len > 0, @"invalid length ..");
787
788             for (pos = startPos, pos2 = 0; _buffer[pos] != '"'; pos++, pos2++) {
789                 //NSLog(@"char=%c pos=%i pos2=%i", _buffer[pos], pos2);
790                 if (_buffer[pos] == '\\') { /* a quoted char */
791                     pos++;
792                     switch (_buffer[pos]) {
793                     case 'a':  str[pos2] = '\a'; break;
794                     case 'b':  str[pos2] = '\b'; break;
795                     case 'f':  str[pos2] = '\f'; break;
796                     case 'n':  str[pos2] = '\n'; break;
797                     case 'r':  str[pos2] = '\r'; break;
798                     case 't':  str[pos2] = '\t'; break;
799                     case 'v':  str[pos2] = '\v'; break;
800                     case '\\': str[pos2] = '\\'; break;
801             
802                     default:
803                         str[pos2] = _buffer[pos];
804                         break;
805                     }
806                 }
807                 else {
808                     str[pos2] = _buffer[pos];
809                 }
810             }
811             str[pos2] = '\0';
812             NSCAssert(pos2 == len, @"invalid unescape ..");
813
814             ostr = [[NSString allocWithZone:_zone]
815                               initWithCString:str length:len];
816             lfFree(str); str = NULL;
817
818             return ostr;
819         }
820         else {
821             NSCAssert(len > 0, @"invalid length ..");
822       
823             return [[NSString allocWithZone:_zone]
824                               initWithCString:&(_buffer[startPos]) length:len];
825         }
826     }
827     else { /* an unquoted string, may not be zero chars long ! */
828         register unsigned pos = *_idx;
829         register unsigned len = 0;
830         unsigned startPos = pos;
831         
832         // loop until break char
833         while (!_isUnquotedStringEndChar(_buffer[pos]) && (pos < _len)) {
834             pos++;
835             len++;
836         }
837         
838         if (len == 0) { // wasn't a string ..
839             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
840                                          @"did not find a string !");
841             return nil;
842         }
843         else {
844             *_idx = pos;
845             return [[NSString allocWithZone:_zone]
846                               initWithCString:&(_buffer[startPos]) length:len];
847         }
848     }
849 }
850
851 static NSData *_parseData(NSZone *_zone, const unsigned char *_buffer,
852                           unsigned *_idx, unsigned _len, NSException **_exception)
853 {
854     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
855         // EOF reached during comment-skipping
856         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
857                                      @"did not find a data (expected '<') !");
858         return nil;
859     }
860
861     if (_buffer[*_idx] != '<') { // it's not a data that's follows
862         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
863                                      @"did not find a data (expected '<') !");
864         return nil;
865     }
866     else {
867         register      unsigned pos = *_idx + 1;
868         register      unsigned len = 0;
869         unsigned      endPos = 0;
870         NSMutableData *data  = nil;
871     
872         *_idx += 1; // skip '<'
873
874         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
875             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
876                                          @"data was not closed (expected '>') ..");
877             return nil; // EOF
878         }
879
880         if (_buffer[*_idx] == '>') { // empty data
881             *_idx += 1; // skip '>'
882             return [[NSData allocWithZone:_zone] init];
883         }
884
885         // count significant chars
886         while ((_buffer[pos] != '>') && (pos < _len)) {
887             if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t'))
888                 ;
889             else if (_isHexDigit(_buffer[pos]))
890                 len++;
891             else {
892                 *_idx = pos;
893                 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
894                                              @"invalid char in data property");
895                 return nil; // abort
896             }
897             pos++;
898         }
899         if (pos == _len) {
900             *_idx = pos;
901             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
902                                          @"data was not closed (expected '>')");
903             return nil; // EOF
904         }
905         endPos = pos; // store position of closing '>'
906
907         // if odd, then add one byte for trailing nibble
908         len = (len % 2 == 1) ? len / 2 + 1 : len / 2;
909         data = [[NSMutableData allocWithZone:_zone] initWithLength:len];
910
911         // now copy bytes ..
912         {
913             register unsigned i;
914             register int pending = -1;
915             char *buf = [data mutableBytes];
916       
917             for (pos = *_idx, i = 0; (pos < endPos) && (i < len); pos++) {
918                 int value = _valueOfHexChar(_buffer[pos]);
919
920                 if (value != -1) {
921                     if (pending == -1)
922                         pending = value;
923                     else {
924                         value = pending * 16 + value;
925                         pending = -1;
926
927                         buf[i] = value;
928                         i++;
929                     }
930                 }
931             }
932             if (pending != -1) { // was odd, now add the trailer ..
933                 NSCAssert(i < len, @"invalid length ..");
934                 buf[i] = pending * 16;
935             }
936         }
937     
938         // update global position
939         *_idx = endPos + 1; // endPos + 1 (*endPos == '>', 1 => skips '>')
940
941         return data;
942     }
943 }
944
945 static NSDictionary *_parseDict
946 (NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
947  unsigned _len, NSException **_exception)
948 {
949     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
950         // EOF reached during comment-skipping
951         *_exception =
952             _makeException(*_exception, _buffer, *_idx, _len,
953                            @"did not find dictionary (expected '{')");
954         return nil;
955     }
956     
957     if (_buffer[*_idx] != '{') { // it's not a dict that's follows
958         *_exception =
959             _makeException(*_exception, _buffer, *_idx, _len,
960                            @"did not find dictionary (expected '{')");
961         return nil;
962     }
963     else {
964         NSMutableDictionary *result = nil;
965         id   key     = nil;
966         id   value   = nil;
967         BOOL didFail = NO;
968     
969         *_idx += 1; // skip '{'
970
971         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
972             *_exception =
973                 _makeException(*_exception, _buffer, *_idx, _len,
974                                @"dictionary was not closed (expected '}')");
975             return nil; // EOF
976         }
977
978         if (_buffer[*_idx] == '}') { // an empty dictionary
979             *_idx += 1; // skip the '}'
980             return [[NSDictionary allocWithZone:_zone] init];
981         }
982         
983         result = [[NSMutableDictionary allocWithZone:_zone] init];
984         do {
985             key   = nil;
986             value = nil;
987       
988             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
989                 *_exception =
990                     _makeException(*_exception, _buffer, *_idx, _len,
991                                    @"dictionary was not closed (expected '}')");
992                 didFail = YES;
993                 break; // unexpected EOF
994             }
995
996             if (_buffer[*_idx] == '}') { // dictionary closed
997                 *_idx += 1; // skip the '}'
998                 break;
999             }
1000       
1001             // read key property
1002             key = _parseProperty(_zone, _buffer, _idx, _len, _exception);
1003             if (key == nil) { // syntax error
1004                 if (*_exception == nil) {
1005                     *_exception =
1006                         _makeException(*_exception,
1007                                        _buffer, *_idx, _len,
1008                                        @"got nil-key in dictionary ..");
1009                 }
1010                 didFail = YES;
1011                 break;
1012             }
1013
1014             /* The following parses:  (comment|space)* '=' (comment|space)* */
1015             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1016                 *_exception =
1017                     _makeException(*_exception, _buffer, *_idx, _len,
1018                                    @"expected '=' after key in dictionary");
1019                 didFail = YES;
1020                 break; // unexpected EOF
1021             }
1022             // no we need a '=' assignment
1023             if (_buffer[*_idx] != '=') {
1024                 *_exception =
1025                     _makeException(*_exception, _buffer, *_idx, _len,
1026                                    @"expected '=' after key in dictionary");
1027                 didFail = YES;
1028                 break;
1029             }
1030             *_idx += 1; // skip '='
1031             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1032                 *_exception =
1033                     _makeException(*_exception, _buffer, *_idx, _len,
1034                                    @"expected value after key '=' in dictionary");
1035                 didFail = YES;
1036                 break; // unexpected EOF
1037             }
1038
1039             // read value property
1040             value = _parseProperty(_zone, _buffer, _idx, _len, _exception);
1041             if (value == nil) { // syntax error
1042                 value = [NSNull null];
1043                 if (*_exception == nil) {
1044                     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1045                                                  @"got nil-value in dictionary");
1046                 }
1047                 didFail = YES;
1048                 break;
1049             }
1050             
1051             NSCAssert(key,   @"invalid key ..");
1052             NSCAssert(value, @"invalid value ..");
1053
1054             if ([result objectForKey:key]) {
1055 #if !RAISE_ON_DUPLICATE_KEYS
1056                 NSLog(@"WARNING: duplicate key '%@' found in dictionary !", key);
1057 #else
1058                 NSString *r;
1059                 r = [NSString stringWithFormat:
1060                                 @"duplicate key '%@' found in dictionary !",
1061                                 key];
1062                 *_exception =
1063                     _makeException(*_exception, _buffer, *_idx, _len, r);
1064                 didFail = YES;
1065                 break; // unexpected EOF
1066 #endif
1067             }
1068             
1069             [result setObject:value forKey:key];
1070
1071             // release key and value
1072             RELEASE(key);   key   = nil;
1073             RELEASE(value); value = nil;
1074
1075             // read trailing ';' if available
1076             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1077                 *_exception =
1078                     _makeException(*_exception, _buffer, *_idx, _len,
1079                                    @"dictionary was not closed (expected '}')");
1080                 didFail = YES;
1081                 break; // unexpected EOF
1082             }
1083             if (_buffer[*_idx] == ';') {
1084                 *_idx += 1; // skip ';'
1085             }
1086             else { // no ';' at end of pair, only allowed at end of dictionary
1087                 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1088                     *_exception =
1089                         _makeException(*_exception, _buffer, *_idx, _len,
1090                                        @"dictionary was not closed (expected '}')");
1091                     didFail = YES;
1092                     break; // unexpected EOF
1093                 }
1094
1095                 if (_buffer[*_idx] != '}') { // dictionary wasn't closed
1096                     *_exception =
1097                         _makeException(*_exception, _buffer, *_idx, _len,
1098                                        @"key-value pair without ';' at the end");
1099                     didFail = YES;
1100                     break;
1101                 }
1102             }
1103         }
1104         while ((*_idx < _len) && (result != nil) && !didFail);
1105
1106         if (didFail) {
1107             RELEASE(key);    key    = nil;
1108             RELEASE(value);  value  = nil;
1109             RELEASE(result); result = nil;
1110             return nil;
1111         }
1112         else {
1113 #if 1
1114             return result;
1115 #else
1116             NSDictionary *d;
1117             d = [result copyWithZone:_zone];
1118             RELEASE(result);
1119             return d;
1120 #endif
1121         }
1122     }
1123 }
1124
1125 static NSArray *_parseArray(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
1126                             unsigned _len, NSException **_exception)
1127 {
1128     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1129         // EOF reached during comment-skipping
1130         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1131                                      @"did not find array (expected '(')");
1132         return nil;
1133     }
1134
1135     if (_buffer[*_idx] != '(') { // it's not an array that's follows
1136         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1137                                      @"did not find array (expected '(')");
1138         return nil;
1139     }
1140     else {
1141         NSMutableArray *result = nil;
1142         id element = nil;
1143
1144         *_idx += 1; // skip '('
1145
1146         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1147             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1148                                          @"array was not closed (expected ')')");
1149             return nil; // EOF
1150         }
1151
1152         if (_buffer[*_idx] == ')') { // an empty array
1153             *_idx += 1; // skip the ')'
1154             return [[NSArray allocWithZone:_zone] init];
1155         }
1156
1157         result = [[NSMutableArray allocWithZone:_zone] init];
1158         do {
1159             element = _parseProperty(_zone, _buffer, _idx, _len, _exception);
1160             if (element == nil) {
1161                 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1162                                              @"expected element in array");
1163                 RELEASE(result); result = nil;
1164                 break;
1165             }
1166             [result addObject:element];
1167             RELEASE(element); element = nil;
1168
1169             if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1170                 *_exception =
1171                     _makeException(*_exception, _buffer, *_idx, _len,
1172                                    @"array was not closed (expected ')' or ',')");
1173                 RELEASE(result); result = nil;
1174                 break;
1175             }
1176
1177             if (_buffer[*_idx] == ')') { // closed array
1178                 *_idx += 1; // skip ')'
1179                 break;
1180             }
1181             else if (_buffer[*_idx] == ',') { // next element
1182                 *_idx += 1; // skip ','
1183
1184                 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1185                     *_exception =
1186                         _makeException(*_exception, _buffer, *_idx, _len,
1187                                        @"array was not closed (expected ')')");
1188                     RELEASE(result); result = nil;
1189                     break;
1190                 }
1191                 if (_buffer[*_idx] == ')') { // closed array, like this '(1,2,)'
1192                     *_idx += 1; // skip ')'
1193                     break;
1194                 }
1195             }
1196             else { // syntax error
1197                 *_exception =
1198                     _makeException(*_exception, _buffer, *_idx, _len,
1199                                    @"expected ')' or ',' after array element");
1200                 RELEASE(result); result = nil;
1201                 break;
1202             }
1203         }
1204         while ((*_idx < _len) && (result != nil));
1205
1206         return result;
1207     }
1208 }
1209
1210 static id _parseProperty(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
1211                          unsigned _len, NSException **_exception)
1212 {
1213     id result = nil;
1214     
1215     if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1216         // no property found
1217         return nil; // EOF
1218     }
1219
1220     switch (_buffer[*_idx]) {
1221     case '"': // quoted string
1222         result = _parseString(_zone, _buffer, _idx, _len, _exception);
1223         break;
1224     case '{': // dictionary
1225         result = _parseDict(_zone, _buffer, _idx, _len, _exception);
1226         break;
1227     case '(': // array
1228         result = _parseArray(_zone, _buffer, _idx, _len, _exception);
1229         break;
1230     case '<': // data
1231         result = _parseData(_zone, _buffer, _idx, _len, _exception);
1232         break;
1233     default: // an unquoted string
1234         result = _parseString(_zone, _buffer, _idx, _len, _exception);
1235         break;
1236     }
1237     
1238     return result;
1239 }
1240
1241 static NSDictionary *_parseStrings(NSZone *_zone, const unsigned char *_buffer,
1242                                    unsigned *_idx, unsigned _len,
1243                                    NSException **_exception,
1244                                    NSString *fname)
1245 {
1246     NSMutableDictionary *result = nil;
1247     id   key     = nil;
1248     id   value   = nil;
1249     BOOL didFail = NO;
1250   
1251     result = [[NSMutableDictionary allocWithZone:_zone] init];
1252     while ((*_idx < _len) && (result != nil) && !didFail) {
1253         key   = nil;
1254         value = nil;
1255       
1256         if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1257             break; // expected EOF
1258
1259         // read key string
1260         key = _parseString(_zone, _buffer, _idx, _len, _exception);
1261         if (key == nil) { // syntax error
1262             if (*_exception == nil) {
1263                 NSString *txt = @"got nil-key in string table";
1264                 if (fname)
1265                     txt = [txt stringByAppendingFormat:@" (path=%@)", fname];
1266                 *_exception =
1267                     _makeException(*_exception, _buffer, *_idx, _len, txt);
1268             }
1269             didFail = YES;
1270             break;
1271         }
1272
1273         /* The following parses:  (comment|space)* '=' (comment|space)* */
1274         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1275             NSString *txt = @"expected '=' after key in string table";
1276             if (fname)
1277                 txt = [txt stringByAppendingFormat:@" (path=%@)", fname];
1278             *_exception =
1279                 _makeException(*_exception, _buffer, *_idx, _len, txt);
1280             didFail = YES;
1281             break; // unexpected EOF
1282         }
1283         // now we need a '=' assignment
1284         if (_buffer[*_idx] != '=') {
1285             NSString *txt = @"expected '=' after key in string table";
1286             if (fname)
1287                 txt = [txt stringByAppendingFormat:@" (path=%@)", fname];
1288             *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1289                                          txt);
1290             didFail = YES;
1291             break;
1292         }
1293         *_idx += 1; // skip '='
1294         if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1295             NSString *txt = @"expected value after key in string table";
1296             if (fname)
1297                 txt = [txt stringByAppendingFormat:@" (path=%@)", fname];
1298             *_exception =
1299                 _makeException(*_exception, _buffer, *_idx, _len, txt);
1300             didFail = YES;
1301             break; // unexpected EOF
1302         }
1303
1304         // read value string
1305         value = _parseString(_zone, _buffer, _idx, _len, _exception);
1306         if (value == nil) { // syntax error
1307             if (*_exception == nil) {
1308                 *_exception =
1309                     _makeException(*_exception, _buffer, *_idx, _len,
1310                                    @"got nil-value after key in string table");
1311             }
1312             didFail = YES;
1313             break;
1314         }
1315
1316         NSCAssert(key,   @"invalid key ..");
1317         NSCAssert(value, @"invalid value ..");
1318
1319         if ([result objectForKey:key]) {
1320             NSString *txt;
1321
1322             txt = fname
1323                 ? [NSString stringWithFormat:
1324                             @"duplicate key '%@' found in strings file '%@'",
1325                               key, fname]
1326                 : [NSString stringWithFormat:
1327                             @"duplicate key '%@' found in strings file", key];
1328             
1329 #if !RAISE_ON_DUPLICATE_KEYS
1330             NSLog(@"WARNING: %@ !", txt);
1331 #else
1332             *_exception =
1333                 _makeException(*_exception, _buffer, *_idx, _len, txt);
1334             didFail = YES;
1335             break; // unexpected EOF
1336 #endif
1337         }
1338         else
1339             [result setObject:value forKey:key];
1340         
1341         // release key and value
1342         RELEASE(key);   key   = nil;
1343         RELEASE(value); value = nil;
1344
1345         // read trailing ';' if available
1346         if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1347             break; // expected EOF
1348
1349         if (_buffer[*_idx] == ';') {
1350             *_idx += 1; // skip ';'
1351         }
1352     }
1353
1354     if (didFail) {
1355         RELEASE(key);    key    = nil;
1356         RELEASE(value);  value  = nil;
1357         RELEASE(result); result = nil;
1358         return nil;
1359     }
1360     else
1361         return result;
1362 }
1363
1364 /*
1365   Local Variables:
1366   c-basic-offset: 4
1367   tab-width: 8
1368   End:
1369 */