4 Copyright (C) 1998 MDlink online service center, Helge Hess
7 Author: Helge Hess (helge@mdlink.de)
9 This file is part of libFoundation.
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
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.
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>
36 #ifdef NeXT /* NeXT Mach map_fd() */
37 # include <mach/mach.h>
39 #elif defined(HAVE_MMAP) /* Posix mmap() */
40 # include <sys/types.h>
41 # include <sys/mman.h>
43 #else /* No file mapping available */
46 #include "PropertyListParser.h"
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,
70 NSString *NSParseStringFromBuffer(const unsigned char *_buffer, unsigned _len)
72 NSString *result = nil;
73 NSException *exception = nil;
77 if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
78 NSLog(@"WARNING(%s): tried to parse Unicode string (FE/FF) ...",
82 else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
83 NSLog(@"WARNING(%s): tried to parse Unicode string (FF/FE) ...",
89 result = _parseString(nil, _buffer, &idx, _len, &exception);
90 result = AUTORELEASE(result);
96 NSArray *NSParseArrayFromBuffer(const unsigned char *_buffer, unsigned _len)
98 NSArray *result = nil;
99 NSException *exception = nil;
103 if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
104 NSLog(@"WARNING(%s): tried to parse Unicode array (FE/FF) ...",
105 __PRETTY_FUNCTION__);
108 else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
109 NSLog(@"WARNING(%s): tried to parse Unicode array (FF/FE) ...",
110 __PRETTY_FUNCTION__);
115 result = _parseArray(nil, _buffer, &idx, _len, &exception);
116 result = AUTORELEASE(result);
122 NSDictionary *NSParseDictionaryFromBuffer(const unsigned char *_buffer, unsigned _len)
124 NSDictionary *result = nil;
125 NSException *exception = nil;
129 if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
130 NSLog(@"WARNING(%s): tried to parse Unicode dict (FE/FF) ...",
131 __PRETTY_FUNCTION__);
134 else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
135 NSLog(@"WARNING(%s): tried to parse Unicode dict (FF/FE) ...",
136 __PRETTY_FUNCTION__);
141 result = _parseDict(nil, _buffer, &idx, _len, &exception);
142 result = AUTORELEASE(result);
148 NSString *NSParseStringFromData(NSData *_data)
150 return NSParseStringFromBuffer([_data bytes], [_data length]);
153 NSArray *NSParseArrayFromData(NSData *_data)
155 return NSParseArrayFromBuffer([_data bytes], [_data length]);
158 NSDictionary *NSParseDictionaryFromData(NSData *_data)
160 return NSParseDictionaryFromBuffer([_data bytes], [_data length]);
163 NSString *NSParseStringFromString(NSString *_str)
169 len = [_str cStringLength];
170 buf = malloc(len + 1);
171 [_str getCString:buf]; buf[len] = '\0';
172 s = NSParseStringFromBuffer(buf, len);
177 NSArray *NSParseArrayFromString(NSString *_str)
183 len = [_str cStringLength];
184 buf = malloc(len + 1);
185 [_str getCString:buf]; buf[len] = '\0';
186 a = NSParseArrayFromBuffer(buf, len);
191 NSDictionary *NSParseDictionaryFromString(NSString *_str)
197 len = [_str cStringLength];
198 buf = malloc(len + 1);
199 [_str getCString:buf]; buf[len] = '\0';
200 d = NSParseDictionaryFromBuffer(buf, len);
205 id NSParsePropertyListFromBuffer(const unsigned char *_buffer, unsigned _len)
208 NSException *exception = nil;
212 if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
213 NSLog(@"WARNING(%s): tried to parse Unicode plist (FE/FF) ...",
214 __PRETTY_FUNCTION__);
217 else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
218 NSLog(@"WARNING(%s): tried to parse Unicode plist (FF/FE) ...",
219 __PRETTY_FUNCTION__);
224 result = _parseProperty(nil, _buffer, &idx, _len, &exception);
225 result = AUTORELEASE(result);
231 id NSParsePropertyListFromData(NSData *_data)
233 return NSParsePropertyListFromBuffer([_data bytes], [_data length]);
236 id NSParsePropertyListFromString(NSString *_string)
242 len = [_string cStringLength];
243 buf = malloc(len + 1);
244 [_string getCString:buf]; buf[len] = '\0';
246 p = NSParsePropertyListFromBuffer(buf, len);
251 id NSParsePropertyListFromFile(NSString *_path)
258 NSException *exception = nil;
261 unsigned plen = [_path cStringLength];
264 path = malloc(plen + 1);
265 [_path getCString:path]; path[plen] = '\0';
267 if ((fd = open(path, O_RDONLY, 0)) != -1) {
268 struct stat statInfo;
270 if (fstat(fd, &statInfo) == 0) {
273 mem = mmap(0, statInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
274 if ((mem != MAP_FAILED) || (mem == NULL)) {
277 result = _parseProperty(nil, mem, &idx, statInfo.st_size,
282 exception = [localException retain];
286 munmap(mem, statInfo.st_size);
290 NSLog(@"%s: Couldn't map file '%@' into virtual memory !",
291 __PRETTY_FUNCTION__, _path);
295 NSLog(@"%s: File '%@' couldn't be mapped !",
296 __PRETTY_FUNCTION__,_path);
302 NSLog(@"%s: File %@ does not exist !", __PRETTY_FUNCTION__, _path);
311 result = AUTORELEASE(result);
313 NSMutableDictionary *ui;
314 ui = [[exception userInfo] mutableCopy];
315 [ui setObject:_path forKey:@"path"];
316 [exception setUserInfo:ui];
317 RELEASE(ui); ui = nil;
324 if ((data = [NSData dataWithContentsOfFile:_path])) {
326 NSException *exception = nil;
331 _buffer = (void *)[data bytes];
332 _len = [data length];
334 result = _parseProperty(nil, _buffer, &idx, _len, &exception);
335 result = AUTORELEASE(result);
338 NSMutableDictionary *ui;
339 ui = [[exception userInfo] mutableCopy];
340 [ui setObject:_path forKey:@"path"];
341 [exception setUserInfo:ui];
342 RELEASE(ui); ui = nil;
349 NSLog(@"%s: couldn't read file %@ !", __PRETTY_FUNCTION__, _path);
356 id NSParseStringsFromBuffer(const unsigned char *_buffer, unsigned _len)
358 NSDictionary *result = nil;
359 NSException *exception = nil;
362 result = _parseStrings(nil, _buffer, &idx, _len, &exception, @"<buffer>");
363 result = AUTORELEASE(result);
369 id NSParseStringsFromData(NSData *_data)
371 NSDictionary *result = nil;
372 NSException *exception = nil;
375 result = _parseStrings(nil, [_data bytes], &idx, [_data length],
376 &exception, @"<data>");
377 result = AUTORELEASE(result);
383 id NSParseStringsFromString(NSString *_string)
388 NSException *exception = nil;
391 len = [_string cStringLength];
392 buf = malloc(len + 1);
393 [_string getCString:buf]; buf[len] = '\0';
395 o = _parseStrings(nil, buf, &idx, len, &exception, @"<string>");
406 id NSParseStringsFromFile(NSString *_path)
409 struct stat statInfo;
410 NSException *exception = nil;
413 unsigned plen = [_path cStringLength];
416 path = malloc(plen + 1);
417 [_path getCString:path]; path[plen] = '\0';
419 fd = open(path, O_RDONLY, 0);
420 free(path); path = NULL;
423 if (fstat(fd, &statInfo) == 0) {
426 mem = mmap(0, statInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
427 if (mem != MAP_FAILED) {
430 result = _parseStrings(nil, mem, &idx, statInfo.st_size,
435 exception = [localException retain];
440 munmap(mem, statInfo.st_size);
444 NSLog(@"Couldn't map file %@ into virtual memory !", _path);
447 NSLog(@"File %@ couldn't be mapped !", _path);
453 NSLog(@"File %@ does not exist !", _path);
457 result = AUTORELEASE(result);
459 NSMutableDictionary *ui;
460 ui = [[exception userInfo] mutableCopy];
461 [ui setObject:_path forKey:@"path"];
462 [exception setUserInfo:ui];
463 RELEASE(ui); ui = nil;
471 if ((data = [NSData dataWithContentsOfFile:_path])) {
472 NSDictionary *result = nil;
473 NSException *exception = nil;
478 _buffer = (void *)[data bytes];
479 _len = [data length];
480 result = _parseStrings(nil, _buffer, &idx, _len, &exception, _path);
481 result = AUTORELEASE(result);
484 NSMutableDictionary *ui;
485 ui = [[exception userInfo] mutableCopy];
486 [ui setObject:_path forKey:@"path"];
487 [exception setUserInfo:ui];
488 RELEASE(ui); ui = nil;
495 NSLog(@"%s: couldn't read file %@ !", __PRETTY_FUNCTION__, _path);
501 /* ******************* implementation ******************** */
503 static inline BOOL _isBreakChar(unsigned char _c)
506 case ' ': case '\t': case '\n': case '\r':
507 case '/': case '=': case ';': case ',':
508 case '{': case '(': case '"': case '<':
509 case ')': case '}': case '>':
516 static inline BOOL _isUnquotedStringEndChar(unsigned char _c) {
518 case ' ': case '\t': case '\n': case '\r':
519 case '=': case ';': case ',': case '"':
520 case ')': case '}': case '>':
528 static inline int _valueOfHexChar(char _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
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
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
547 static inline BOOL _isHexDigit(char _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':
563 static inline int _numberOfLines(const unsigned char *_buffer, unsigned _lastIdx)
565 register unsigned int pos, lineCount = 1;
567 for (pos = 0; (pos < _lastIdx) && (_buffer[pos] != '\0'); pos++) {
568 if (_buffer[pos] == '\n')
574 static NSException *_makeException(NSException *_exception,
575 const unsigned char *_buffer, unsigned _idx,
576 unsigned _len, NSString *_text)
578 NSMutableDictionary *ui = nil;
579 NSException *exception = nil;
583 numLines = _numberOfLines(_buffer, _idx);
584 atEof = (_idx >= _len) ? YES : NO;
586 /* error resulted from a previous error (exception already set) */
590 exception = [[SyntaxErrorException alloc] init];
593 ? [NSString stringWithFormat:@"Unexpected end: %@", _text]
594 : [NSString stringWithFormat:@"Syntax error in line %i: %@",
597 [exception setReason:_text];
601 ui = [[exception userInfo] mutableCopy];
603 ui = [[NSMutableDictionary alloc] initWithCapacity:8];
605 [ui setObject:[NSNumber numberWithInt:numLines] forKey:@"line"];
606 [ui setObject:[NSNumber numberWithInt:_len] forKey:@"size"];
607 [ui setObject:[NSNumber numberWithInt:_idx] forKey:@"position"];
611 [ui setObject:[NSString stringWithCString:_buffer length:_len]
614 if (!atEof && (_idx > 0)) {
615 [ui setObject:[NSString stringWithCString:_buffer length:_idx]
616 forKey:@"consumedText"];
619 if (!atEof && (_idx > 0)) {
620 register unsigned pos;
621 const unsigned char *startPos, *endPos;
623 for (pos = _idx; (pos >= 0) && (_buffer[pos] != '\n'); pos--)
625 startPos = &(_buffer[pos + 1]);
627 for (pos = _idx; ((pos < _len) && (_buffer[pos] != '\n')); pos++)
629 endPos = &(_buffer[pos - 1]);
631 if (startPos < endPos) {
632 [ui setObject:[NSString stringWithCString:startPos
633 length:(endPos - startPos)]
637 NSLog(@"%s: startPos=0x%p endPos=0x%p",
638 __PRETTY_FUNCTION__, startPos, endPos);
642 [exception setUserInfo:ui];
644 RELEASE(ui); ui = nil;
650 static BOOL _skipComments(const unsigned char *_buffer,
651 unsigned *_idx, unsigned _len,
652 BOOL _skipSpaces, NSException **_exception)
654 register unsigned pos = *_idx;
660 do { // until all comments are filtered ..
663 if ((_buffer[pos] == '/') && ((pos + 1) < _len)) {
664 if (_buffer[pos + 1] == '/') { // single line comments
665 pos += 2; // skip '//'
667 // search for '\n' ..
668 while ((pos < _len) && (_buffer[pos] != '\n'))
671 if ((pos < _len) && (_buffer[pos] == '\n')) {
672 pos++; // skip newline, otherwise EOF was reached
676 else if (_buffer[pos + 1] == '*') { /* multiline comments */
677 BOOL commentIsClosed = NO;
679 pos += 2; // skip '/*'
681 do { // search for '*/'
682 while ((pos < _len) && (_buffer[pos] != '*'))
685 if (pos < _len) { // found '*'
689 if (_buffer[pos] == '/') { // found '*/'
690 commentIsClosed = YES;
697 printf("'*' inside multiline comment !\n");
704 if (!commentIsClosed) {
705 // EOF found, comment wasn't closed
707 _makeException(*_exception, _buffer, *_idx, _len,
708 @"comment was not closed "
714 else if (_skipSpaces && isspace((int)_buffer[pos])) {
719 while (lookAgain && (pos < _len));
723 //NSLog(@"skipped comments, now at '%s'", &(_buffer[*_idx]));
728 static NSString *_parseString(NSZone *_zone, const unsigned char *_buffer,
730 unsigned _len, NSException **_exception)
733 // skip comments and spaces
734 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
735 // EOF reached during comment-skipping
737 _makeException(*_exception, _buffer, *_idx, _len,
738 @"did not find a string !");
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;
748 pos++; // skip starting quote
750 // loop until closing quote
751 while ((_buffer[pos] != '"') && (pos < _len)) {
752 if (_buffer[pos] == '\\') {
753 containsEscaped = YES;
754 pos++; // skip following char
757 _makeException(*_exception, _buffer, *_idx, _len,
758 @"escape in quoted string not finished !");
766 if (pos == _len) { // syntax error, quote not closed
769 _makeException(*_exception, _buffer, *_idx, _len,
770 @"quoted string not closed (expected '\"')");
774 pos++; // skip closing quote
775 *_idx = pos; // store pointer
778 if (len == 0) { // empty string
781 else if (containsEscaped) {
782 register unsigned pos2;
783 char *str = MallocAtomic(len + 1);
786 NSCAssert(len > 0, @"invalid length ..");
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 */
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;
803 str[pos2] = _buffer[pos];
808 str[pos2] = _buffer[pos];
812 NSCAssert(pos2 == len, @"invalid unescape ..");
814 ostr = [[NSString allocWithZone:_zone]
815 initWithCString:str length:len];
816 lfFree(str); str = NULL;
821 NSCAssert(len > 0, @"invalid length ..");
823 return [[NSString allocWithZone:_zone]
824 initWithCString:&(_buffer[startPos]) length:len];
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;
832 // loop until break char
833 while (!_isUnquotedStringEndChar(_buffer[pos]) && (pos < _len)) {
838 if (len == 0) { // wasn't a string ..
839 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
840 @"did not find a string !");
845 return [[NSString allocWithZone:_zone]
846 initWithCString:&(_buffer[startPos]) length:len];
851 static NSData *_parseData(NSZone *_zone, const unsigned char *_buffer,
852 unsigned *_idx, unsigned _len, NSException **_exception)
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 '<') !");
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 '<') !");
867 register unsigned pos = *_idx + 1;
868 register unsigned len = 0;
870 NSMutableData *data = nil;
872 *_idx += 1; // skip '<'
874 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
875 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
876 @"data was not closed (expected '>') ..");
880 if (_buffer[*_idx] == '>') { // empty data
881 *_idx += 1; // skip '>'
882 return [[NSData allocWithZone:_zone] init];
885 // count significant chars
886 while ((_buffer[pos] != '>') && (pos < _len)) {
887 if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t'))
889 else if (_isHexDigit(_buffer[pos]))
893 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
894 @"invalid char in data property");
901 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
902 @"data was not closed (expected '>')");
905 endPos = pos; // store position of closing '>'
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];
914 register int pending = -1;
915 char *buf = [data mutableBytes];
917 for (pos = *_idx, i = 0; (pos < endPos) && (i < len); pos++) {
918 int value = _valueOfHexChar(_buffer[pos]);
924 value = pending * 16 + value;
932 if (pending != -1) { // was odd, now add the trailer ..
933 NSCAssert(i < len, @"invalid length ..");
934 buf[i] = pending * 16;
938 // update global position
939 *_idx = endPos + 1; // endPos + 1 (*endPos == '>', 1 => skips '>')
945 static NSDictionary *_parseDict
946 (NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
947 unsigned _len, NSException **_exception)
949 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
950 // EOF reached during comment-skipping
952 _makeException(*_exception, _buffer, *_idx, _len,
953 @"did not find dictionary (expected '{')");
957 if (_buffer[*_idx] != '{') { // it's not a dict that's follows
959 _makeException(*_exception, _buffer, *_idx, _len,
960 @"did not find dictionary (expected '{')");
964 NSMutableDictionary *result = nil;
969 *_idx += 1; // skip '{'
971 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
973 _makeException(*_exception, _buffer, *_idx, _len,
974 @"dictionary was not closed (expected '}')");
978 if (_buffer[*_idx] == '}') { // an empty dictionary
979 *_idx += 1; // skip the '}'
980 return [[NSDictionary allocWithZone:_zone] init];
983 result = [[NSMutableDictionary allocWithZone:_zone] init];
988 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
990 _makeException(*_exception, _buffer, *_idx, _len,
991 @"dictionary was not closed (expected '}')");
993 break; // unexpected EOF
996 if (_buffer[*_idx] == '}') { // dictionary closed
997 *_idx += 1; // skip the '}'
1001 // read key property
1002 key = _parseProperty(_zone, _buffer, _idx, _len, _exception);
1003 if (key == nil) { // syntax error
1004 if (*_exception == nil) {
1006 _makeException(*_exception,
1007 _buffer, *_idx, _len,
1008 @"got nil-key in dictionary ..");
1014 /* The following parses: (comment|space)* '=' (comment|space)* */
1015 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1017 _makeException(*_exception, _buffer, *_idx, _len,
1018 @"expected '=' after key in dictionary");
1020 break; // unexpected EOF
1022 // no we need a '=' assignment
1023 if (_buffer[*_idx] != '=') {
1025 _makeException(*_exception, _buffer, *_idx, _len,
1026 @"expected '=' after key in dictionary");
1030 *_idx += 1; // skip '='
1031 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1033 _makeException(*_exception, _buffer, *_idx, _len,
1034 @"expected value after key '=' in dictionary");
1036 break; // unexpected EOF
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");
1051 NSCAssert(key, @"invalid key ..");
1052 NSCAssert(value, @"invalid value ..");
1054 if ([result objectForKey:key]) {
1055 #if !RAISE_ON_DUPLICATE_KEYS
1056 NSLog(@"WARNING: duplicate key '%@' found in dictionary !", key);
1059 r = [NSString stringWithFormat:
1060 @"duplicate key '%@' found in dictionary !",
1063 _makeException(*_exception, _buffer, *_idx, _len, r);
1065 break; // unexpected EOF
1069 [result setObject:value forKey:key];
1071 // release key and value
1072 RELEASE(key); key = nil;
1073 RELEASE(value); value = nil;
1075 // read trailing ';' if available
1076 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1078 _makeException(*_exception, _buffer, *_idx, _len,
1079 @"dictionary was not closed (expected '}')");
1081 break; // unexpected EOF
1083 if (_buffer[*_idx] == ';') {
1084 *_idx += 1; // skip ';'
1086 else { // no ';' at end of pair, only allowed at end of dictionary
1087 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1089 _makeException(*_exception, _buffer, *_idx, _len,
1090 @"dictionary was not closed (expected '}')");
1092 break; // unexpected EOF
1095 if (_buffer[*_idx] != '}') { // dictionary wasn't closed
1097 _makeException(*_exception, _buffer, *_idx, _len,
1098 @"key-value pair without ';' at the end");
1104 while ((*_idx < _len) && (result != nil) && !didFail);
1107 RELEASE(key); key = nil;
1108 RELEASE(value); value = nil;
1109 RELEASE(result); result = nil;
1117 d = [result copyWithZone:_zone];
1125 static NSArray *_parseArray(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
1126 unsigned _len, NSException **_exception)
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 '(')");
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 '(')");
1141 NSMutableArray *result = nil;
1144 *_idx += 1; // skip '('
1146 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1147 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1148 @"array was not closed (expected ')')");
1152 if (_buffer[*_idx] == ')') { // an empty array
1153 *_idx += 1; // skip the ')'
1154 return [[NSArray allocWithZone:_zone] init];
1157 result = [[NSMutableArray allocWithZone:_zone] init];
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;
1166 [result addObject:element];
1167 RELEASE(element); element = nil;
1169 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1171 _makeException(*_exception, _buffer, *_idx, _len,
1172 @"array was not closed (expected ')' or ',')");
1173 RELEASE(result); result = nil;
1177 if (_buffer[*_idx] == ')') { // closed array
1178 *_idx += 1; // skip ')'
1181 else if (_buffer[*_idx] == ',') { // next element
1182 *_idx += 1; // skip ','
1184 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1186 _makeException(*_exception, _buffer, *_idx, _len,
1187 @"array was not closed (expected ')')");
1188 RELEASE(result); result = nil;
1191 if (_buffer[*_idx] == ')') { // closed array, like this '(1,2,)'
1192 *_idx += 1; // skip ')'
1196 else { // syntax error
1198 _makeException(*_exception, _buffer, *_idx, _len,
1199 @"expected ')' or ',' after array element");
1200 RELEASE(result); result = nil;
1204 while ((*_idx < _len) && (result != nil));
1210 static id _parseProperty(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
1211 unsigned _len, NSException **_exception)
1215 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1216 // no property found
1220 switch (_buffer[*_idx]) {
1221 case '"': // quoted string
1222 result = _parseString(_zone, _buffer, _idx, _len, _exception);
1224 case '{': // dictionary
1225 result = _parseDict(_zone, _buffer, _idx, _len, _exception);
1228 result = _parseArray(_zone, _buffer, _idx, _len, _exception);
1231 result = _parseData(_zone, _buffer, _idx, _len, _exception);
1233 default: // an unquoted string
1234 result = _parseString(_zone, _buffer, _idx, _len, _exception);
1241 static NSDictionary *_parseStrings(NSZone *_zone, const unsigned char *_buffer,
1242 unsigned *_idx, unsigned _len,
1243 NSException **_exception,
1246 NSMutableDictionary *result = nil;
1251 result = [[NSMutableDictionary allocWithZone:_zone] init];
1252 while ((*_idx < _len) && (result != nil) && !didFail) {
1256 if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1257 break; // expected EOF
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";
1265 txt = [txt stringByAppendingFormat:@" (path=%@)", fname];
1267 _makeException(*_exception, _buffer, *_idx, _len, txt);
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";
1277 txt = [txt stringByAppendingFormat:@" (path=%@)", fname];
1279 _makeException(*_exception, _buffer, *_idx, _len, txt);
1281 break; // unexpected EOF
1283 // now we need a '=' assignment
1284 if (_buffer[*_idx] != '=') {
1285 NSString *txt = @"expected '=' after key in string table";
1287 txt = [txt stringByAppendingFormat:@" (path=%@)", fname];
1288 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1293 *_idx += 1; // skip '='
1294 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1295 NSString *txt = @"expected value after key in string table";
1297 txt = [txt stringByAppendingFormat:@" (path=%@)", fname];
1299 _makeException(*_exception, _buffer, *_idx, _len, txt);
1301 break; // unexpected EOF
1304 // read value string
1305 value = _parseString(_zone, _buffer, _idx, _len, _exception);
1306 if (value == nil) { // syntax error
1307 if (*_exception == nil) {
1309 _makeException(*_exception, _buffer, *_idx, _len,
1310 @"got nil-value after key in string table");
1316 NSCAssert(key, @"invalid key ..");
1317 NSCAssert(value, @"invalid value ..");
1319 if ([result objectForKey:key]) {
1323 ? [NSString stringWithFormat:
1324 @"duplicate key '%@' found in strings file '%@'",
1326 : [NSString stringWithFormat:
1327 @"duplicate key '%@' found in strings file", key];
1329 #if !RAISE_ON_DUPLICATE_KEYS
1330 NSLog(@"WARNING: %@ !", txt);
1333 _makeException(*_exception, _buffer, *_idx, _len, txt);
1335 break; // unexpected EOF
1339 [result setObject:value forKey:key];
1341 // release key and value
1342 RELEASE(key); key = nil;
1343 RELEASE(value); value = nil;
1345 // read trailing ';' if available
1346 if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1347 break; // expected EOF
1349 if (_buffer[*_idx] == ';') {
1350 *_idx += 1; // skip ';'
1355 RELEASE(key); key = nil;
1356 RELEASE(value); value = nil;
1357 RELEASE(result); result = nil;