From f37b00aa8fc14911e453a14b705f02939b0db2ef Mon Sep 17 00:00:00 2001 From: rfm Date: Wed, 27 Dec 2006 08:16:37 +0000 Subject: [PATCH] Support xml property list parsing when libxml2 is not available. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@24266 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 9 ++ Source/NSPropertyList.m | 322 ++++++++++++++++++++++++++++++++++++++-- Source/NSUserDefaults.m | 6 +- Source/NSXMLParser.m | 132 ++++++++-------- 4 files changed, 393 insertions(+), 76 deletions(-) diff --git a/ChangeLog b/ChangeLog index d2b51dae2..5c869aec6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2006-12-27 Richard Frith-Macdonald + + * Source/NSUserDefaults.m: remove old defaults as housekeeping + observer when we reset. + * Source/NSXMLParser.m: deactivate some log messages + * Source/NSPropertyList.m: add proplist parser using NSXMLParser + to support parsing of MacOS-X property lists even when libxml2 + is not available. + 2006-12-26 Dr. H. Nikolaus Schaller, Richard Frith-Macdonald * Source/NSXMLParser.m: Implement reduced functionality parser if diff --git a/Source/NSPropertyList.m b/Source/NSPropertyList.m index 5fe2aef17..9d7767ac7 100644 --- a/Source/NSPropertyList.m +++ b/Source/NSPropertyList.m @@ -44,6 +44,7 @@ #include "Foundation/NSUserDefaults.h" #include "Foundation/NSValue.h" #include "Foundation/NSDebug.h" +#include "Foundation/NSXMLParser.h" #include "GNUstepBase/Unicode.h" #include "GSPrivate.h" @@ -54,6 +55,270 @@ extern BOOL GSScanDouble(unichar*, unsigned, double*); @interface GSMutableDictionary : NSObject // Help the compiler @end + +@interface GSXMLPListParser : NSObject +{ + NSXMLParser *theParser; + NSMutableString *value; + NSMutableArray *stack; + NSString *key; + BOOL inArray; + BOOL inDictionary; + id plist; + NSPropertyListMutabilityOptions opts; +} + +- (id) initWithData: (NSData*)data + mutability: (NSPropertyListMutabilityOptions)options; +- (BOOL) parse; +- (void) parser: (NSXMLParser *)parser + foundCharacters: (NSString *)string; +- (void) parser: (NSXMLParser *)parser + didStartElement: (NSString *)elementName + namespaceURI: (NSString *)namespaceURI + qualifiedName: (NSString *)qualifiedName + attributes: (NSDictionary *)attributeDict; +- (void) parser: (NSXMLParser *)parser + didEndElement: (NSString *)elementName + namespaceURI: (NSString *)namespaceURI + qualifiedName: (NSString *)qName; +- (id) result; +@end + +@implementation GSXMLPListParser + +- (void) dealloc +{ + RELEASE(key); + RELEASE(stack); + RELEASE(plist); + RELEASE(value); + RELEASE(theParser); + [super dealloc]; +} + +- (id) initWithData: (NSData*)data + mutability: (NSPropertyListMutabilityOptions)options +{ + if ((self = [super init]) != nil) + { + stack = [[NSMutableArray alloc] initWithCapacity: 10]; + theParser = [[NSXMLParser alloc] initWithData: data]; + [theParser setDelegate: self]; + opts = options; + } + return self; +} + +- (void) parser: (NSXMLParser *)parser + foundCharacters: (NSString *)string +{ + if (value == nil) + { + value = [[NSMutableString alloc] initWithCapacity: 50]; + } + [value appendString: string]; +} + +- (void) parser: (NSXMLParser *)parser + didStartElement: (NSString *)elementName + namespaceURI: (NSString *)namespaceURI + qualifiedName: (NSString *)qualifiedName + attributes: (NSDictionary *)attributeDict +{ + if ([elementName isEqualToString: @"dict"] == YES) + { + NSMutableDictionary *d; + + d = [[NSMutableDictionary alloc] initWithCapacity: 10]; + [stack addObject: d]; + RELEASE(d); + inDictionary = YES; + inArray = NO; + } + else if ([elementName isEqualToString: @"array"] == YES) + { + NSMutableArray *a; + + a = [[NSMutableArray alloc] initWithCapacity: 10]; + [stack addObject: a]; + RELEASE(a); + inArray = YES; + inDictionary = NO; + } +} + +- (void) parser: (NSXMLParser *)parser + didEndElement: (NSString *)elementName + namespaceURI: (NSString *)namespaceURI + qualifiedName: (NSString *)qName +{ + BOOL inContainer = NO; + + if ([elementName isEqualToString: @"dict"] == YES) + { + inContainer = YES; + } + if ([elementName isEqualToString: @"array"] == YES) + { + inContainer = YES; + } + + if (inContainer) + { + if (opts != NSPropertyListImmutable) + { + ASSIGN(plist, [stack lastObject]); + } + else + { + ASSIGN(plist, [[stack lastObject] makeImmutableCopyOnFail: NO]); + } + inArray = NO; + inDictionary = NO; + if ([stack count] > 0) + { + id last; + + [stack removeLastObject]; + last = [stack lastObject]; + if ([last isKindOfClass: [NSArray class]] == YES) + { + inArray = YES; + } + else if ([last isKindOfClass: [NSDictionary class]] == YES) + { + inDictionary = YES; + } + } + } + else if ([elementName isEqualToString: @"key"] == YES) + { + ASSIGN(key, [value makeImmutableCopyOnFail: NO]); + DESTROY(value); + return; + } + else if ([elementName isEqualToString: @"data"]) + { + NSData *d; + + d = [GSMimeDocument decodeBase64: + [value dataUsingEncoding: NSASCIIStringEncoding]]; + if (opts == NSPropertyListMutableContainersAndLeaves) + { + d = AUTORELEASE([d mutableCopy]); + } + ASSIGN(plist, d); + if (d == nil) + { + [parser abortParsing]; + return; + } + } + else if ([elementName isEqualToString: @"date"]) + { + id result; + + if ([value hasSuffix: @"Z"] == YES && [value length] == 20) + { + result = [NSCalendarDate dateWithString: value + calendarFormat: @"%Y-%m-%dT%H:%M:%SZ"]; + } + else + { + result = [NSCalendarDate dateWithString: value + calendarFormat: @"%Y-%m-%d %H:%M:%S %z"]; + } + ASSIGN(plist, result); + } + else if ([elementName isEqualToString: @"string"]) + { + id o; + + if (opts == NSPropertyListMutableContainersAndLeaves) + { + if (value == nil) + { + o = [NSMutableString string]; + } + else + { + o = value; + } + } + else + { + if (value == nil) + { + o = @""; + } + else + { + o = [value makeImmutableCopyOnFail: NO]; + } + } + ASSIGN(plist, o); + } + else if ([elementName isEqualToString: @"integer"]) + { + ASSIGN(plist, [NSNumber numberWithInt: [value intValue]]); + } + else if ([elementName isEqualToString: @"real"]) + { + ASSIGN(plist, [NSNumber numberWithDouble: [value doubleValue]]); + } + else if ([elementName isEqualToString: @"true"]) + { + ASSIGN(plist, [NSNumber numberWithBool: YES]); + } + else if ([elementName isEqualToString: @"false"]) + { + ASSIGN(plist, [NSNumber numberWithBool: NO]); + } + else if ([elementName isEqualToString: @"plist"]) + { + DESTROY(value); + return; + } + else // invalid tag + { + NSLog(@"unrecognized tag <%@>", elementName); + [parser abortParsing]; + return; + } + + if (inArray == YES) + { + [[stack lastObject] addObject: plist]; + } + else if (inDictionary == YES) + { + if (key == nil) + { + [parser abortParsing]; + return; + } + [[stack lastObject] setObject: plist forKey: key]; + DESTROY(key); + } + DESTROY(value); +} + +- (BOOL) parse +{ + return [theParser parse]; +} + +- (id) result +{ + return plist; +} + +@end + + + + @interface GSBinaryPLParser : NSObject { NSPropertyListMutabilityOptions mutability; @@ -1877,16 +2142,35 @@ OAppend(id obj, NSDictionary *loc, unsigned lev, unsigned step, [keyArray getObjects: keys]; } - for (i = 0; i < numKeys; i++) - { - if (GSObjCClass(keys[i]) == lastClass) - continue; - if ([keys[i] respondsToSelector: @selector(compare:)] == NO) + if (x == NSPropertyListXMLFormat_v1_0) + { + /* This format can only use strings as keys. + */ + lastClass = [NSString class]; + for (i = 0; i < numKeys; i++) { - canCompare = NO; - break; + if ([keys[i] isKindOfClass: lastClass] == NO) + { + [NSException raise: NSInvalidArgumentException + format: @"Bad key in property list: '%@'", keys[i]]; + } + } + } + else + { + /* All keys must respond to -compare: for sorting. + */ + for (i = 0; i < numKeys; i++) + { + if (GSObjCClass(keys[i]) == lastClass) + continue; + if ([keys[i] respondsToSelector: @selector(compare:)] == NO) + { + canCompare = NO; + break; + } + lastClass = GSObjCClass(keys[i]); } - lastClass = GSObjCClass(keys[i]); } if (canCompare == YES) @@ -2070,7 +2354,7 @@ static BOOL classInitialized = NO; { classInitialized = YES; -#ifdef HAVE_LIBXML +#if HAVE_LIBXML /* * Cache XML node information. */ @@ -2278,9 +2562,6 @@ GSPropertyListMake(id obj, NSDictionary *loc, BOOL xml, { // It begins with '_tempDomains objectForKey: NSRegistrationDomain]); - + /* To ensure that we don't try to synchronise the old defaults to disk + * after creating the new ones, remove as housekeeping notification + * observer. + */ + [[NSNotificationCenter defaultCenter] removeObserver: sharedDefaults]; setSharedDefaults = NO; DESTROY(sharedDefaults); if (regDefs != nil) diff --git a/Source/NSXMLParser.m b/Source/NSXMLParser.m index b8fcf3235..74557ed04 100644 --- a/Source/NSXMLParser.m +++ b/Source/NSXMLParser.m @@ -584,83 +584,93 @@ typedef struct NSXMLParserIvarsType isEnd: (BOOL)flag withAttributes: (NSDictionary *)attributes { -#if 0 - NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); -#endif if (this->acceptHTML) tag = [tag lowercaseString]; // not case sensitive if (!flag) { if ([tag isEqualToString: @"?xml"]) { - // parse, i.e. check for UTF8 encoding and other attributes - #if 0 - NSLog(@"parserDidStartDocument: "); - #endif - if ([_del respondsToSelector: @selector(parserDidStartDocument:)]) - [_del parserDidStartDocument: self]; - return; +#if 0 +NSLog(@"parserDidStartDocument: "); +#endif + if ([_del respondsToSelector: @selector(parserDidStartDocument:)]) + [_del parserDidStartDocument: self]; + return; } if ([tag hasPrefix: @"?"]) { - #if 1 - NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); - #endif - // parser: foundProcessingInstructionWithTarget: data: - return; +#if 0 +NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); +#endif + // parser: foundProcessingInstructionWithTarget: data: + return; } if ([tag isEqualToString: @"!DOCTYPE"]) { - // parse and might load - #if 1 - NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); - #endif - return; +#if 0 +NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); +#endif + return; } if ([tag isEqualToString: @"!ENTITY"]) { - // parse - #if 1 - NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); - #endif - return; +#if 0 +NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); +#endif + return; } if ([tag isEqualToString: @"!CDATA"]) { // pass through as NSData // parser: foundCDATA: - #if 1 - NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); - #endif +#if 0 +NSLog(@"_processTag <%@%@ %@>", flag?@"/": @"", tag, attributes); +#endif return; } [this->tagPath addObject: tag]; // push on stack - if ([_del respondsToSelector:@selector(parser:didStartElement:namespaceURI:qualifiedName:attributes:)]) - [_del parser: self didStartElement: tag namespaceURI: nil qualifiedName: nil attributes: attributes]; - } + if ([_del respondsToSelector: + @selector(parser:didStartElement:namespaceURI:qualifiedName:attributes:)]) + [_del parser: self + didStartElement: tag + namespaceURI: nil + qualifiedName: nil + attributes: attributes]; + } else { // closing tag - if (this->acceptHTML) - { -// lazily close any missing tags on stack - while([this->tagPath count] > 0 && ![[this->tagPath lastObject] isEqualToString: tag]) // must be literally equal! - { - if ([_del respondsToSelector: @selector(parser: didEndElement: namespaceURI: qualifiedName: )]) - [_del parser: self didEndElement: [this->tagPath lastObject] namespaceURI: nil qualifiedName: nil]; - [this->tagPath removeLastObject]; // pop from stack - } - if ([this->tagPath count] == 0) - return; // ignore closing tag without matching open... - } - else if (![[this->tagPath lastObject] isEqualToString: tag]) // must be literally equal! - { - [self _parseError: [NSString stringWithFormat: @"tag nesting error ( expected, found)", [this->tagPath lastObject], tag]]; - return; - } - if ([_del respondsToSelector: @selector(parser: didEndElement: namespaceURI: qualifiedName: )]) - [_del parser: self didEndElement: tag namespaceURI: nil qualifiedName: nil]; - [this->tagPath removeLastObject]; // pop from stack + if (this->acceptHTML) + { + // lazily close any missing tags on stack + while ([this->tagPath count] > 0 + && ![[this->tagPath lastObject] isEqualToString: tag]) + { + if ([_del respondsToSelector: + @selector(parser:didEndElement:namespaceURI:qualifiedName:)]) + [_del parser: self + didEndElement: [this->tagPath lastObject] + namespaceURI: nil + qualifiedName: nil]; + [this->tagPath removeLastObject]; // pop from stack + } + if ([this->tagPath count] == 0) + return; // ignore closing tag without matching open... + } + else if (![[this->tagPath lastObject] isEqualToString: tag]) + { + [self _parseError: [NSString stringWithFormat: + @"tag nesting error ( expected, found)", + [this->tagPath lastObject], tag]]; + return; + } + if ([_del respondsToSelector: + @selector(parser:didEndElement:namespaceURI:qualifiedName:)]) + [_del parser: self + didEndElement: tag + namespaceURI: nil + qualifiedName: nil]; + [this->tagPath removeLastObject]; // pop from stack } } @@ -675,7 +685,7 @@ typedef struct NSXMLParserIvarsType do { c = cget(); - } while(c != EOF && c != '<' && c != ';'); + } while (c != EOF && c != '<' && c != ';'); if (c != ';') return nil; // invalid sequence - end of file or missing ; before next tag @@ -729,7 +739,7 @@ typedef struct NSXMLParserIvarsType c = cget(); if (c == EOF) return nil; // unterminated! - } while(c != '\"'); + } while (c != '\"'); return UTF8STR(ap + 1, this->cp - ap - 2); } if (c == '\'') @@ -739,12 +749,12 @@ typedef struct NSXMLParserIvarsType c = cget(); if (c == EOF) return nil; // unterminated! - } while(c != '\''); + } while (c != '\''); return UTF8STR(ap + 1, this->cp - ap - 2); } if (!this->acceptHTML) ; // strict XML requires quoting (?) - while(!isspace(c) && c != '>' && c != '/' && c != '?' && c != '=' &&c != EOF) + while (!isspace(c) && c != '>' && c != '/' && c != '?' && c != '=' &&c != EOF) c = cget(); this->cp--; // go back to terminating character return UTF8STR(ap, this->cp - ap); @@ -764,7 +774,7 @@ typedef struct NSXMLParserIvarsType return [self _parseError: @"missing preamble"]; } c = cget(); // get first character - while(!this->abort) + while (!this->abort) { // parse next element #if 0 @@ -803,7 +813,7 @@ typedef struct NSXMLParserIvarsType { if (!this->acceptHTML) return [self _parseError: @"unexpected end of file"]; // strict XML nesting error - while([this->tagPath count] > 0) + while ([this->tagPath count] > 0) { // lazily close all open tags if ([_del respondsToSelector: @selector(parser: didEndElement: namespaceURI: qualifiedName: )]) @@ -842,7 +852,7 @@ typedef struct NSXMLParserIvarsType { // start of comment skip all characters until "-->" this->cp+=3; - while(this->cp < this->cend-3 && strncmp((char *)this->cp, "-->", 3) != 0) + while (this->cp < this->cend-3 && strncmp((char *)this->cp, "-->", 3) != 0) this->cp++; // search // if _del responds to parser: foundComment: // convert to string (tp+4 ... cp) @@ -862,7 +872,7 @@ typedef struct NSXMLParserIvarsType // FIXME: this->should process this tag in a special way so that e.g. is read as a single tag! // to do this properly, we need a notion of comments and quoted string constants... } - while(!isspace(c) && c != '>' && (c != '/') && (c != '?')) + while (!isspace(c) && c != '>' && (c != '/') && (c != '?')) c = cget(); // scan tag until we find a delimiting character if (*tp == '/') tag = UTF8STR(tp + 1, this->cp - tp - 2); // don't include / and delimiting character @@ -872,7 +882,7 @@ typedef struct NSXMLParserIvarsType NSLog(@"tag=%@ - %02x %c", tag, c, isprint(c)?c: ' '); #endif parameters=[NSMutableDictionary dictionaryWithCapacity: 5]; - while(c != EOF) + while (c != EOF) { // collect arguments if (c == '/' && *tp != '/') @@ -895,7 +905,7 @@ typedef struct NSXMLParserIvarsType [self _processTag: tag isEnd: NO withAttributes: parameters]; // single break; // done } - while(isspace(c)) // this->should also allow for line break and tab + while (isspace(c)) // this->should also allow for line break and tab c = cget(); if (c == '>') {