2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #if !LIB_FOUNDATION_LIBRARY
27 #include <sys/types.h>
35 #import "NGPropertyListParser.h"
36 #import "NGMemoryAllocation.h"
37 //#import "NSObjectMacros.h"
38 //#import "NSException.h"
39 #import <Foundation/NSData.h>
43 @interface NSException(UsedPrivates) /* may break on Panther? */
44 - (void)setUserInfo:(NSDictionary *)_ui;
45 - (void)setReason:(NSString *)_reason;
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);
69 NSString *NGParseStringFromBuffer(const unsigned char *_buffer, unsigned _len)
71 NSString *result = nil;
72 NSException *exception = nil;
76 if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
77 NSLog(@"WARNING(%s): tried to parse Unicode string (FE/FF) ...",
81 else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
82 NSLog(@"WARNING(%s): tried to parse Unicode string (FF/FE) ...",
89 [_parseString(NoZone, _buffer, &idx, _len, &exception) autorelease];
94 NSArray *NGParseArrayFromBuffer(const unsigned char *_buffer, unsigned _len)
96 NSArray *result = nil;
97 NSException *exception = nil;
101 if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
102 NSLog(@"WARNING(%s): tried to parse Unicode array (FE/FF) ...",
103 __PRETTY_FUNCTION__);
106 else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
107 NSLog(@"WARNING(%s): tried to parse Unicode array (FF/FE) ...",
108 __PRETTY_FUNCTION__);
113 result = [_parseArray(NoZone, _buffer, &idx, _len, &exception) autorelease];
118 NSDictionary *NGParseDictionaryFromBuffer(const unsigned char *_buffer, unsigned _len)
120 NSDictionary *result = nil;
121 NSException *exception = nil;
125 if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
126 NSLog(@"WARNING(%s): tried to parse Unicode dict (FE/FF) ...",
127 __PRETTY_FUNCTION__);
130 else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
131 NSLog(@"WARNING(%s): tried to parse Unicode dict (FF/FE) ...",
132 __PRETTY_FUNCTION__);
137 result = [_parseDict(NoZone, _buffer, &idx, _len, &exception) autorelease];
142 NSString *NGParseStringFromData(NSData *_data)
144 return NGParseStringFromBuffer([_data bytes], [_data length]);
147 NSArray *NGParseArrayFromData(NSData *_data)
149 return NGParseArrayFromBuffer([_data bytes], [_data length]);
152 NSDictionary *NGParseDictionaryFromData(NSData *_data)
154 return NGParseDictionaryFromBuffer([_data bytes], [_data length]);
157 NSString *NGParseStringFromString(NSString *_str)
160 return NGParseStringFromBuffer((unsigned char *)[_str cString],
161 [_str cStringLength]);
164 NSArray *NGParseArrayFromString(NSString *_str)
167 return NGParseArrayFromBuffer((unsigned char *)[_str cString],
168 [_str cStringLength]);
171 NSDictionary *NGParseDictionaryFromString(NSString *_str)
174 return NGParseDictionaryFromBuffer((unsigned char *)[_str cString],
175 [_str cStringLength]);
178 id NGParsePropertyListFromBuffer(const unsigned char *_buffer, unsigned _len)
181 NSException *exception = nil;
185 if (_buffer[0] == 0xFE && _buffer[1] == 0xFF) {
186 NSLog(@"WARNING(%s): tried to parse Unicode plist (FE/FF) ...",
187 __PRETTY_FUNCTION__);
190 else if (_buffer[0] == 0xFF && _buffer[1] == 0xFE) {
191 NSLog(@"WARNING(%s): tried to parse Unicode plist (FF/FE) ...",
192 __PRETTY_FUNCTION__);
197 result = [_parseProperty(NoZone, _buffer, &idx, _len, &exception)
203 id NGParsePropertyListFromData(NSData *_data)
205 return NGParsePropertyListFromBuffer([_data bytes], [_data length]);
208 id NGParsePropertyListFromString(NSString *_string)
210 return NGParsePropertyListFromBuffer((unsigned char *)[_string cString],
211 [_string cStringLength]);
214 id NGParsePropertyListFromFile(NSString *_path)
217 NSException *exception = nil;
221 fd = open([_path cString], O_RDONLY);
223 struct stat statInfo;
224 if (fstat(fd, &statInfo) == 0) {
227 mem = mmap(0, statInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
228 if ((mem != MAP_FAILED) || (mem == NULL)) {
231 result = _parseProperty(nil, mem, &idx, statInfo.st_size,
236 exception = [localException retain];
240 munmap(mem, statInfo.st_size);
244 NSLog(@"Could not map file %@ into virtual memory !", _path);
248 NSLog(@"File %@ could not be mapped !", _path);
253 NSLog(@"File %@ does not exist !", _path);
256 [result autorelease];
262 NSData *data = [NSData dataWithContentsOfFile:_path];
265 return NGParsePropertyListFromData(data);
268 NSLog(@"%s: Could not parse plist file %@ !", __PRETTY_FUNCTION__,
275 NSDictionary *NGParseStringsFromBuffer(const unsigned char *_buffer,
278 NSDictionary *result = nil;
279 NSException *exception = nil;
282 result = [_parseStrings(NoZone, _buffer, &idx, _len, &exception) autorelease];
287 NSDictionary *NGParseStringsFromData(NSData *_data)
289 return NGParseStringsFromBuffer([_data bytes], [_data length]);
292 NSDictionary *NGParseStringsFromString(NSString *_string)
294 return NGParseStringsFromBuffer((unsigned char *)[_string cString],
295 [_string cStringLength]);
298 NSDictionary *NGParseStringsFromFile(NSString *_path)
301 struct stat statInfo;
302 NSException *exception = nil;
306 fd = open([_path cString], O_RDONLY);
308 if (fstat(fd, &statInfo) == 0) {
311 mem = mmap(0, statInfo.st_size, PROT_READ, MAP_SHARED, fd, 0);
312 if (mem != MAP_FAILED) {
315 result = _parseStrings(nil, mem, &idx, statInfo.st_size,
319 exception = [localException retain];
324 munmap(mem, statInfo.st_size);
328 NSLog(@"Could not map file %@ into virtual memory !", _path);
331 NSLog(@"File %@ could not be mapped !", _path);
336 NSLog(@"File %@ does not exist !", _path);
339 [result autorelease];
344 NSData *data = [NSData dataWithContentsOfFile:_path];
347 return NGParseStringsFromData(data);
349 NSLog(@"%s: Could not parse strings file %@ !",
350 __PRETTY_FUNCTION__, _path);
356 /* ******************* implementation ******************** */
358 static inline BOOL _isBreakChar(char _c)
361 case ' ': case '\t': case '\n': case '\r':
362 case '/': case '=': case ';': case ',':
363 case '{': case '(': case '"': case '<':
364 case '}': case ')': case '>':
372 static inline int _valueOfHexChar(char _c)
375 case '0': case '1': case '2': case '3': case '4':
376 case '5': case '6': case '7': case '8': case '9':
377 return (_c - 48); // 0-9 (ascii-char)'0' - 48 => (int)0
379 case 'A': case 'B': case 'C':
380 case 'D': case 'E': case 'F':
381 return (_c - 55); // A-F, A=10..F=15, 'A'=65..'F'=70
383 case 'a': case 'b': case 'c':
384 case 'd': case 'e': case 'f':
385 return (_c - 87); // a-f, a=10..F=15, 'a'=97..'f'=102
391 static inline BOOL _isHexDigit(char _c)
394 case '0': case '1': case '2': case '3': case '4':
395 case '5': case '6': case '7': case '8': case '9':
396 case 'A': case 'B': case 'C':
397 case 'D': case 'E': case 'F':
398 case 'a': case 'b': case 'c':
399 case 'd': case 'e': case 'f':
407 static inline int _numberOfLines(const unsigned char *_buffer, unsigned _lastIdx)
409 register int pos, lineCount = 1;
411 for (pos = 0; (pos < _lastIdx) && (_buffer[pos] != '\0'); pos++) {
412 if (_buffer[pos] == '\n')
418 static NSException *_makeException(NSException *_exception,
419 const unsigned char *_buffer, unsigned _idx,
420 unsigned _len, NSString *_text)
422 NSMutableDictionary *ui = nil;
423 NSException *exception = nil;
424 int numLines = _numberOfLines(_buffer, _idx);
425 BOOL atEof = (_idx >= _len) ? YES : NO;
427 if (_exception) // error resulted from a previous error (exception already set)
430 exception = [NSException exceptionWithName:@"SyntaxErrorException"
435 ? [NSString stringWithFormat:@"Unexpected end: %@", _text]
436 : [NSString stringWithFormat:@"Syntax error in line %i: %@", numLines,_text];
438 [exception setReason:_text];
442 ui = [[exception userInfo] mutableCopy];
444 ui = [[NSMutableDictionary alloc] initWithCapacity:8];
446 [ui setObject:[NSNumber numberWithInt:numLines] forKey:@"line"];
447 [ui setObject:[NSNumber numberWithInt:_len] forKey:@"size"];
448 [ui setObject:[NSNumber numberWithInt:_idx] forKey:@"position"];
452 [ui setObject:[NSString stringWithCString:_buffer length:_len]
455 if (!atEof && (_idx > 0)) {
456 [ui setObject:[NSString stringWithCString:_buffer length:_idx]
457 forKey:@"consumedText"];
460 if (!atEof && (_idx > 0)) {
461 register unsigned pos;
462 const unsigned char *startPos, *endPos;
464 for (pos = _idx; (pos >= 0) && (_buffer[pos] != '\n'); pos--)
466 startPos = &(_buffer[pos + 1]);
468 for (pos = _idx; ((pos < _len) && (_buffer[pos] != '\n')); pos++)
470 endPos = &(_buffer[pos - 1]);
472 if (startPos < endPos) {
475 s = [[NSString alloc] initWithCString:(char *)startPos
476 length:(endPos - startPos)];
478 [ui setObject:s forKey:@"lastLine"];
482 NSLog(@"ERROR(%s): could not get last-line!",
483 __PRETTY_FUNCTION__);
487 NSLog(@"startPos=0x%08X endPos=0x%08X", startPos, endPos);
490 [exception setUserInfo:ui];
492 [ui release]; ui = nil;
498 static BOOL _skipComments(const unsigned char *_buffer, unsigned *_idx, unsigned _len,
499 BOOL _skipSpaces, NSException **_exception)
501 register unsigned pos = *_idx;
507 do { // until all comments are filtered ..
510 if ((_buffer[pos] == '/') && (pos + 1 < _len)) {
511 if (_buffer[pos + 1] == '/') { // single line comments
512 pos += 2; // skip '//'
514 // search for '\n' ..
515 while ((pos < _len) && (_buffer[pos] != '\n'))
518 if ((pos < _len) && (_buffer[pos] == '\n')) {
519 pos++; // skip newline, otherwise EOF was reached
523 else if (_buffer[pos + 1] == '*') { /* multiline comments */
524 BOOL commentIsClosed = NO;
526 pos += 2; // skip '/*'
528 do { // search for '*/'
529 while ((pos < _len) && (_buffer[pos] != '*'))
532 if (pos < _len) { // found '*'
533 if ((pos + 1) < _len) {
534 if (_buffer[pos + 1] == '/') { // found '*/'
535 commentIsClosed = YES;
536 pos += 2; // skip '*/'
545 if (!commentIsClosed) {
546 // EOF found, comment was not closed
548 _makeException(*_exception, _buffer, *_idx, _len,
549 @"comment was not closed (expected '*/')");
554 else if (_skipSpaces && isspace((int)_buffer[pos])) {
559 while (lookAgain && (pos < _len));
563 //NSLog(@"skipped comments, now at '%s'", &(_buffer[*_idx]));
568 static NSString *_parseString(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
569 unsigned _len, NSException **_exception)
572 // skip comments and spaces
573 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
574 // EOF reached during comment-skipping
576 _makeException(*_exception, _buffer, *_idx, _len,
577 @"did not find a string !");
581 if (_buffer[*_idx] == '"') { // a quoted string
582 register unsigned pos = *_idx;
583 register unsigned len = 0;
584 unsigned startPos = pos + 1;
585 BOOL containsEscaped = NO;
587 pos++; // skip starting quote
589 // loop until closing quote
590 while ((_buffer[pos] != '"') && (pos < _len)) {
591 if (_buffer[pos] == '\\') {
592 containsEscaped = YES;
593 pos++; // skip following char
596 _makeException(*_exception, _buffer, *_idx, _len,
597 @"escape in quoted string not finished !");
605 if (pos == _len) { // syntax error, quote not closed
608 _makeException(*_exception, _buffer, *_idx, _len,
609 @"quoted string not closed (expected '\"')");
613 pos++; // skip closing quote
614 *_idx = pos; // store pointer
617 if (len == 0) { // empty string
620 else if (containsEscaped) {
621 register unsigned pos2;
622 char *str = NGMallocAtomic(len + 1);
625 NSCAssert(len > 0, @"invalid length ..");
627 for (pos = startPos, pos2 = 0; _buffer[pos] != '"'; pos++, pos2++) {
628 //NSLog(@"char=%c pos=%i pos2=%i", _buffer[pos], pos2);
629 if (_buffer[pos] == '\\') {
631 switch (_buffer[pos]) {
632 case 'a': str[pos2] = '\a'; break;
633 case 'b': str[pos2] = '\b'; break;
634 case 'f': str[pos2] = '\f'; break;
635 case 'n': str[pos2] = '\n'; break;
636 case 't': str[pos2] = '\t'; break;
637 case 'v': str[pos2] = '\v'; break;
638 case '\\': str[pos2] = '\\'; break;
641 str[pos2] = _buffer[pos];
646 str[pos2] = _buffer[pos];
650 NSCAssert(pos2 == len, @"invalid unescape ..");
652 ostr = [[NSString allocWithZone:_zone]
653 initWithCString:str length:len];
654 NGFree(str); str = NULL;
659 NSCAssert(len > 0, @"invalid length ..");
661 return [[NSString allocWithZone:_zone]
662 initWithCString:(char*)&(_buffer[startPos]) length:len];
665 else { // an unquoted string, may not be zero chars long !
666 register unsigned pos = *_idx;
667 register unsigned len = 0;
668 unsigned startPos = pos;
670 // loop until break char
671 while (!_isBreakChar(_buffer[pos]) && (pos < _len)) {
676 if (len == 0) { // was not a string ..
677 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
678 @"did not find a string !");
683 return [[NSString allocWithZone:_zone]
684 initWithCString:(char*)&(_buffer[startPos]) length:len];
689 static NSData *_parseData(NSZone *_zone, const unsigned char *_buffer,
690 unsigned *_idx, unsigned _len,
691 NSException **_exception)
693 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
694 // EOF reached during comment-skipping
695 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
696 @"did not find a data (expected '<') !");
700 if (_buffer[*_idx] != '<') { // it's not a data that's follows
701 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
702 @"did not find a data (expected '<') !");
706 register unsigned pos = *_idx + 1;
707 register unsigned len = 0;
709 NSMutableData *data = nil;
711 *_idx += 1; // skip '<'
713 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
714 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
715 @"data was not closed (expected '>') ..");
719 if (_buffer[*_idx] == '>') { // empty data
720 *_idx += 1; // skip '>'
721 return [[NSData allocWithZone:_zone] init];
724 // count significant chars
725 while ((_buffer[pos] != '>') && (pos < _len)) {
726 if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t'))
728 else if (_isHexDigit(_buffer[pos]))
732 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
733 @"invalid char in data property");
740 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
741 @"data was not closed (expected '>')");
744 endPos = pos; // store position of closing '>'
746 // if odd, then add one byte for trailing nibble
747 len = (len % 2 == 1) ? len / 2 + 1 : len / 2;
748 data = [[NSMutableData allocWithZone:_zone] initWithLength:len];
753 register unsigned pending = -1;
754 char *buf = [data mutableBytes];
756 for (pos = *_idx, i = 0; (pos < endPos) && (i < len); pos++) {
757 int value = _valueOfHexChar(_buffer[pos]);
763 value = value * 16 + pending;
771 if (pending != -1) { // was odd, now add the trailer ..
772 NSCAssert(i < len, @"invalid length ..");
773 buf[i] = pending * 16;
777 // update global position
778 *_idx = endPos + 1; // endPos + 1 (*endPos == '>', 1 => skips '>')
784 static NSDictionary *_parseDict(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
785 unsigned _len, NSException **_exception)
787 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
788 // EOF reached during comment-skipping
789 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
790 @"did not find dictionary (expected '{')");
794 if (_buffer[*_idx] != '{') { // it's not a dict that's follows
795 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
796 @"did not find dictionary (expected '{')");
800 NSMutableDictionary *result = nil;
805 *_idx += 1; // skip '{'
807 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
809 _makeException(*_exception, _buffer, *_idx, _len,
810 @"dictionary was not closed (expected '}')");
814 if (_buffer[*_idx] == '}') { // an empty dictionary
815 *_idx += 1; // skip the '}'
816 return [[NSDictionary allocWithZone:_zone] init];
819 result = [[NSMutableDictionary allocWithZone:_zone] init];
824 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
826 _makeException(*_exception, _buffer, *_idx, _len,
827 @"dictionary was not closed (expected '}')");
829 break; // unexpected EOF
832 if (_buffer[*_idx] == '}') { // dictionary closed
833 *_idx += 1; // skip the '}'
838 key = _parseProperty(_zone, _buffer, _idx, _len, _exception);
839 if (key == nil) { // syntax error
840 if (*_exception == nil) {
841 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
842 @"got nil-key in dictionary ..");
848 /* The following parses: (comment|space)* '=' (comment|space)* */
849 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
851 _makeException(*_exception, _buffer, *_idx, _len,
852 @"expected '=' after key in dictionary");
854 break; // unexpected EOF
856 // no we need a '=' assignment
857 if (_buffer[*_idx] != '=') {
859 _makeException(*_exception, _buffer, *_idx, _len,
860 @"expected '=' after key in dictionary");
864 *_idx += 1; // skip '='
865 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
867 _makeException(*_exception, _buffer, *_idx, _len,
868 @"expected value after key '=' in dictionary");
870 break; // unexpected EOF
873 // read value property
874 value = _parseProperty(_zone, _buffer, _idx, _len, _exception);
875 if (value == nil) { // syntax error
876 if (*_exception == nil) {
877 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
878 @"got nil-value in dictionary");
884 NSCAssert(key, @"invalid key ..");
885 NSCAssert(value, @"invalid value ..");
887 [result setObject:value forKey:key];
889 // release key and value
890 [key release]; key = nil;
891 [value release]; value = nil;
893 // read trailing ';' if available
894 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
896 _makeException(*_exception, _buffer, *_idx, _len,
897 @"dictionary was not closed (expected '}')");
899 break; // unexpected EOF
901 if (_buffer[*_idx] == ';') {
902 *_idx += 1; // skip ';'
904 else { // no ';' at end of pair, only allowed at end of dictionary
905 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
907 _makeException(*_exception, _buffer, *_idx, _len,
908 @"dictionary was not closed (expected '}')");
910 break; // unexpected EOF
913 if (_buffer[*_idx] != '}') { // dictionary was not closed
915 _makeException(*_exception, _buffer, *_idx, _len,
916 @"key-value pair without ';' at the end");
922 while ((*_idx < _len) && (result != nil) && !didFail);
925 [key release]; key = nil;
926 [value release]; value = nil;
927 [result release]; result = nil;
935 static NSArray *_parseArray(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
936 unsigned _len, NSException **_exception)
938 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
939 // EOF reached during comment-skipping
940 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
941 @"did not find array (expected '(')");
945 if (_buffer[*_idx] != '(') { // it's not an array that's follows
946 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
947 @"did not find array (expected '(')");
951 NSMutableArray *result = nil;
954 *_idx += 1; // skip '('
956 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
957 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
958 @"array was not closed (expected ')')");
962 if (_buffer[*_idx] == ')') { // an empty array
963 *_idx += 1; // skip the ')'
964 return [[NSArray allocWithZone:_zone] init];
967 result = [[NSMutableArray allocWithZone:_zone] init];
969 element = _parseProperty(_zone, _buffer, _idx, _len, _exception);
970 if (element == nil) {
971 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
972 @"expected element in array");
973 [result release]; result = nil;
976 [result addObject:element];
977 [element release]; element = nil;
979 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
981 _makeException(*_exception, _buffer, *_idx, _len,
982 @"array was not closed (expected ')' or ',')");
983 [result release]; result = nil;
987 if (_buffer[*_idx] == ')') { // closed array
988 *_idx += 1; // skip ')'
991 else if (_buffer[*_idx] == ',') { // next element
992 *_idx += 1; // skip ','
994 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
996 _makeException(*_exception, _buffer, *_idx, _len,
997 @"array was not closed (expected ')')");
998 [result release]; result = nil;
1001 if (_buffer[*_idx] == ')') { // closed array, like this '(1,2,)'
1002 *_idx += 1; // skip ')'
1006 else { // syntax error
1008 _makeException(*_exception, _buffer, *_idx, _len,
1009 @"expected ')' or ',' after array element");
1010 [result release]; result = nil;
1014 while ((*_idx < _len) && (result != nil));
1020 static id _parseProperty(NSZone *_zone, const unsigned char *_buffer, unsigned *_idx,
1021 unsigned _len, NSException **_exception)
1025 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1026 // no property found
1030 switch (_buffer[*_idx]) {
1031 case '"': // quoted string
1032 result = _parseString(_zone, _buffer, _idx, _len, _exception);
1034 case '{': // dictionary
1035 result = _parseDict(_zone, _buffer, _idx, _len, _exception);
1038 result = _parseArray(_zone, _buffer, _idx, _len, _exception);
1041 result = _parseData(_zone, _buffer, _idx, _len, _exception);
1043 default: // an unquoted string
1044 result = _parseString(_zone, _buffer, _idx, _len, _exception);
1051 static NSDictionary *_parseStrings(NSZone *_zone, const unsigned char *_buffer,
1052 unsigned *_idx, unsigned _len,
1053 NSException **_exception)
1055 NSMutableDictionary *result = nil;
1060 result = [[NSMutableDictionary allocWithZone:_zone] init];
1061 while ((*_idx < _len) && (result != nil) && !didFail) {
1065 if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1066 break; // expected EOF
1069 key = _parseString(_zone, _buffer, _idx, _len, _exception);
1070 if (key == nil) { // syntax error
1071 if (*_exception == nil) {
1072 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1073 @"got nil-key in string table ..");
1079 /* The following parses: (comment|space)* '=' (comment|space)* */
1080 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1081 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1082 @"expected '=' after key in string table");
1084 break; // unexpected EOF
1086 // no we need a '=' assignment
1087 if (_buffer[*_idx] != '=') {
1088 *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1089 @"expected '=' after key in string table");
1093 *_idx += 1; // skip '='
1094 if (!_skipComments(_buffer, _idx, _len, YES, _exception)) {
1096 _makeException(*_exception, _buffer, *_idx, _len,
1097 @"expected value after key in string table");
1099 break; // unexpected EOF
1102 // read value string
1103 value = _parseString(_zone, _buffer, _idx, _len, _exception);
1104 if (value == nil) { // syntax error
1105 if (*_exception == nil) {
1107 _makeException(*_exception, _buffer, *_idx, _len,
1108 @"got nil-value after key in string table");
1114 NSCAssert(key, @"invalid key ..");
1115 NSCAssert(value, @"invalid value ..");
1117 [result setObject:value forKey:key];
1119 // release key and value
1120 [key release]; key = nil;
1121 [value release]; value = nil;
1123 // read trailing ';' if available
1124 if (!_skipComments(_buffer, _idx, _len, YES, _exception))
1125 break; // expected EOF
1127 if (_buffer[*_idx] == ';') {
1128 *_idx += 1; // skip ';'
1133 [key release]; key = nil;
1134 [value release]; value = nil;
1135 [result release]; result = nil;
1142 // ******************** categories ********************
1144 #import <Foundation/NSArray.h>
1145 #import <Foundation/NSDictionary.h>
1146 #import <Foundation/NSString.h>
1147 #import <Foundation/NSException.h>
1149 @implementation NSArray(NGPropertyListParser)
1151 + (id)skyArrayWithContentsOfFile:(NSString *)_path {
1152 volatile id plist = nil;
1154 NSString *format = @"%@: Caught exception %@ with reason %@ ";
1157 plist = NGParsePropertyListFromFile(_path);
1159 if (![plist isKindOfClass:[NSArray class]])
1163 NSLog(format, self, [localException name], [localException reason]);
1171 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1172 NSArray *plist = [NSArray arrayWithContentsOfFile:_path];
1175 return [self initWithArray:plist];
1182 @end /* NSArray(NGPropertyListParser) */
1184 @implementation NSDictionary(NGPropertyListParser)
1186 + (id)skyDictionaryWithContentsOfFile:(NSString *)_path {
1187 volatile id plist = nil;
1189 NSString *format = @"%@: Caught exception %@ with reason %@ ";
1192 plist = NGParsePropertyListFromFile(_path);
1194 if (![plist isKindOfClass:[NSDictionary class]])
1198 NSLog(format, self, [localException name], [localException reason]);
1206 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1207 NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:_path];
1210 return [self initWithDictionary:plist];
1217 @end /* NSDictionary(NGPropertyListParser) */
1219 #else /* LIB_FOUNDATION_LIBRARY */
1221 #import <Foundation/NSArray.h>
1222 #import <Foundation/NSDictionary.h>
1223 #import <Foundation/NSString.h>
1224 #import <Foundation/NSException.h>
1226 @implementation NSArray(NGPropertyListParser)
1228 + (id)skyArrayWithContentsOfFile:(NSString *)_path {
1229 return [self arrayWithContentsOfFile:_path];
1232 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1234 return [[[self class] skyArrayWithContentsOfFile:_path] retain];
1237 @end /* NSArray(NGPropertyListParser) */
1239 @implementation NSDictionary(NGPropertyListParser)
1241 + (id)skyDictionaryWithContentsOfFile:(NSString *)_path {
1242 return [self dictionaryWithContentsOfFile:_path];
1245 - (id)skyInitWithContentsOfFile:(NSString *)_path {
1247 return [[[self class] skyDictionaryWithContentsOfFile:_path] retain];
1250 @end /* NSDictionary(NGPropertyListParser) */
1252 #endif /* LIB_FOUNDATION_LIBRARY */