From 174fad9d24d2ee622b18ee865b1eb83273262990 Mon Sep 17 00:00:00 2001 From: helge Date: Thu, 5 May 2005 21:20:58 +0000 Subject: [PATCH] code reorgs support for Unicode encodes vCards embed in vCardSet tag git-svn-id: http://svn.opengroupware.org/SOPE/trunk@798 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- sope-ical/versitSaxDriver/ChangeLog | 4 + sope-ical/versitSaxDriver/VSSaxDriver.m | 408 ++++++++++++++++-------- sope-ical/versitSaxDriver/Version | 2 +- 3 files changed, 278 insertions(+), 136 deletions(-) diff --git a/sope-ical/versitSaxDriver/ChangeLog b/sope-ical/versitSaxDriver/ChangeLog index 734d2b2f..f1c735a0 100644 --- a/sope-ical/versitSaxDriver/ChangeLog +++ b/sope-ical/versitSaxDriver/ChangeLog @@ -1,5 +1,9 @@ 2005-05-05 Helge Hess + * VSSaxDriver.m: code cleanups / reorgs, properly embed reported + contents in tag, added support for vCards in Unicode + 16-bit encoding (v4.5.14) + * VSSaxDriver.m: improved parsing entry methods, added support for SAX error handlers (v4.5.13) diff --git a/sope-ical/versitSaxDriver/VSSaxDriver.m b/sope-ical/versitSaxDriver/VSSaxDriver.m index e740f5bd..0113f2bc 100644 --- a/sope-ical/versitSaxDriver/VSSaxDriver.m +++ b/sope-ical/versitSaxDriver/VSSaxDriver.m @@ -26,16 +26,72 @@ #include #include "common.h" +@interface VSSaxTag : NSObject +{ +@public + NSString *type; + NSString *tagName; + SaxAttributes *attrs; + NSString *data; +} + ++ (id)beginTag:(NSString *)_tag attributes:(SaxAttributes *)_attrs; +- (id)initEndTag:(NSString *)_tag; +- (id)initWithData:(NSString *)_data; + +- (NSString *)tagName; + +@end + +static NSString *VSBeginType = @"BEGIN"; +static NSString *VSEndType = @"END"; +static NSString *VSDataType = @"DATA"; + +@implementation VSSaxTag + ++ (id)beginTag:(NSString *)_tag attributes:(SaxAttributes *)_attrs { + VSSaxTag *tag; + + tag = [[[self alloc] init] autorelease]; + tag->type = VSBeginType; + tag->tagName = [_tag copy]; + tag->attrs = [_attrs retain]; + return tag; +} +- (id)initEndTag:(NSString *)_tag { + self->type = VSEndType; + self->tagName = [_tag copy]; + return self; +} +- (id)initWithData:(NSString *)_data { + self->type = VSDataType; + self->data = [_data retain]; + return self; +} + +- (void)dealloc { + [self->data release]; + [self->tagName release]; + [self->attrs release]; + [super dealloc]; +} + +- (NSString *)tagName { + return self->tagName; +} + +@end /* VSSaxTag */ + @implementation VSSaxDriver static BOOL debugOn = NO; -static NSCharacterSet *dotCharSet = nil; -static NSCharacterSet *equalSignCharSet = nil; -static NSCharacterSet *commaCharSet = nil; -static NSCharacterSet *colonAndSemicolonCharSet = nil; +static NSCharacterSet *dotCharSet = nil; +static NSCharacterSet *equalSignCharSet = nil; +static NSCharacterSet *commaCharSet = nil; +static NSCharacterSet *colonAndSemicolonCharSet = nil; static NSCharacterSet *colonSemicolonAndDquoteCharSet = nil; -static NSCharacterSet *whitespaceCharSet = nil; +static NSCharacterSet *whitespaceCharSet = nil; static VSStringFormatter *stringFormatter = nil; @@ -211,9 +267,10 @@ static VSStringFormatter *stringFormatter = nil; } - (void)_addAttribute:(NSString *)_attribute value:(NSString *)_value { - NSArray *element = [cardStack lastObject]; - SaxAttributes *attrs = [element objectAtIndex:2]; - [self _addAttribute:_attribute value:_value toAttrs:attrs]; + VSSaxTag *element; + + element = [cardStack lastObject]; + [self _addAttribute:_attribute value:_value toAttrs:element->attrs]; } - (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName { @@ -289,21 +346,20 @@ static VSStringFormatter *stringFormatter = nil; *value_ = [stringFormatter stringByUnescapingRFC2445Text:attrValue]; } -- (id)_mapAttrs:(NSArray *)_attrs - forTag:(NSString *)_tagName -{ +- (SaxAttributes *)_mapAttrs:(NSArray *)_attrs forTag:(NSString *)_tagName { SaxAttributes *retAttrs; NSEnumerator *attrEnum; NSString *curAttr, *mappedAttr, *mappedValue, *oldValue; NSMutableDictionary *attributes; - if (!_attrs || [_attrs count] == 0) + if (_attrs == nil || [_attrs count] == 0) return nil; attributes = [[NSMutableDictionary alloc] init]; - retAttrs = [[[SaxAttributes alloc] init] autorelease]; + retAttrs = [[[SaxAttributes alloc] init] autorelease]; + attrEnum = [_attrs objectEnumerator]; - while ((curAttr = [attrEnum nextObject])) { + while ((curAttr = [attrEnum nextObject]) != nil) { [self _parseAttr:curAttr forTag:_tagName intoAttr:&mappedAttr @@ -319,7 +375,7 @@ static VSStringFormatter *stringFormatter = nil; } attrEnum = [attributes keyEnumerator]; - while ((curAttr = [attrEnum nextObject])) { + while ((curAttr = [attrEnum nextObject]) != nil) { [self _addAttribute:curAttr value:[attributes objectForKey:curAttr] toAttrs:retAttrs]; @@ -330,45 +386,55 @@ static VSStringFormatter *stringFormatter = nil; return retAttrs; } -- (NSArray *)_beginTag:(NSString *)_tagName - withAttrs:(id)_attrs -{ - NSArray *tag = [NSArray arrayWithObjects:@"BEGIN",_tagName,_attrs,NULL]; +- (VSSaxTag *)_beginTag:(NSString *)_tagName withAttrs:(SaxAttributes *)_attrs{ + VSSaxTag *tag; + + tag = [VSSaxTag beginTag:_tagName attributes:_attrs]; [self->elementList addObject:tag]; return tag; } - (void)_endTag:(NSString *)_tagName { - [self->elementList addObject: - [NSArray arrayWithObjects:@"END",_tagName,NULL]]; + VSSaxTag *tag; + + tag = [[VSSaxTag alloc] initEndTag:_tagName]; + [self->elementList addObject:tag]; + [tag release]; tag = nil; } - (void)_addSubItems:(NSArray *)_items withData:(NSString *)_content { NSEnumerator *itemEnum, *contentEnum; NSString *subTag; - NSString *subContent; itemEnum = [_items objectEnumerator]; contentEnum = [[_content componentsSeparatedByString:@";"] objectEnumerator]; - while ((subTag=[itemEnum nextObject])) { + while ((subTag = [itemEnum nextObject]) != nil) { + NSString *subContent; + subContent = [contentEnum nextObject]; [self _beginTag:subTag withAttrs:nil]; - if ([subContent length]>0) - [self->elementList addObject: - [NSArray arrayWithObjects:@"DATA", subContent, nil]]; + if ([subContent length] > 0) { + VSSaxTag *a; + + if ((a = [(VSSaxTag*)[VSSaxTag alloc] initWithData:subContent]) != nil) { + [self->elementList addObject:a]; + [a release]; + } + } [self _endTag:subTag]; } } - (void)_dataTag:(NSString *)_tagName - withAttrs:(id)_attrs + withAttrs:(SaxAttributes *)_attrs andContent:(NSString *)_content { NSArray *subItems; _content = [stringFormatter stringByUnescapingRFC2445Text:_content]; + if ([self->attributeElements containsObject:_tagName]) { [self _addAttribute:_tagName value:_content]; return; @@ -376,83 +442,90 @@ static VSStringFormatter *stringFormatter = nil; [self _beginTag:_tagName withAttrs:_attrs]; if ([_content length] > 0) { - if ((subItems = [self->subItemMapping objectForKey:_tagName])) { - [self _addSubItems:subItems withData:_content]; - } - else { - [self->elementList addObject: - [NSArray arrayWithObjects:@"DATA", _content, nil]]; + if ((subItems = [self->subItemMapping objectForKey:_tagName]) != nil) { + [self _addSubItems:subItems withData:_content]; + } + else { + VSSaxTag *a; + + if ((a = [(VSSaxTag *)[VSSaxTag alloc] initWithData:_content]) != nil) { + [self->elementList addObject:a]; + [a release]; } + } } [self _endTag:_tagName]; } +/* report events for collected elements */ + - (void)_eventsForElements { NSEnumerator *enu; - NSArray *obj; + VSSaxTag *obj; - enu = [elementList objectEnumerator]; + enu = [self->elementList objectEnumerator]; while ((obj = [enu nextObject]) != nil) { - id attrs; - NSString *type; - NSString *name; - unichar *chardata; - - type = [obj objectAtIndex:0]; - name = [obj objectAtIndex:1]; - - if ([obj count] > 2) - attrs = [obj objectAtIndex:2]; - else - attrs = nil; - - if ([type isEqualToString:@"BEGIN"]) { - [self->contentHandler startElement:name + if ([obj->type isEqualToString:VSBeginType]) { + [self->contentHandler startElement:obj->tagName namespace:self->prefixURI - rawName:name - attributes:attrs]; + rawName:obj->tagName + attributes:obj->attrs]; } - else if ([type isEqualToString:@"END"]) { - [self->contentHandler endElement:name + else if ([obj->type isEqualToString:VSEndType]) { + [self->contentHandler endElement:obj->tagName namespace:self->prefixURI - rawName:name]; + rawName:obj->tagName]; } else { - unsigned len = [name length]; - chardata = malloc(len * sizeof(unichar)); - [name getCharacters:chardata range:NSMakeRange(0, len)]; + unichar *chardata; + unsigned len; + + // TODO: better move to tag itself? + len = [obj->data length]; + chardata = calloc(len + 1, sizeof(unichar)); + [obj->data getCharacters:chardata range:NSMakeRange(0, len)]; + [self->contentHandler characters:chardata length:len]; - if (chardata) - free(chardata); + if (chardata != NULL) free(chardata); chardata = NULL; } } [elementList removeAllObjects]; } +/* errors */ + +- (void)warn:(NSString *)_warn { + SaxParseException *e; + + e = (id)[SaxParseException exceptionWithName:@"SaxParseException" + reason:_warn + userInfo:nil]; + [self->errorHandler warning:e]; +} + +/* parsing raw string */ + - (void)_parseLine:(NSString *)_line { NSString *tagName, *tagValue; NSMutableArray *tagAttributes; NSRange r, todoRange; unsigned length; - length = [_line length]; + length = [_line length]; todoRange = NSMakeRange(0, length); r = [_line rangeOfCharacterFromSet:colonAndSemicolonCharSet options:0 range:todoRange]; /* is line well-formed? */ if (r.length == 0) { - if (debugOn) { - NSLog(@"%s got an improper content line! ->\n%@", - __PRETTY_FUNCTION__, - _line); - } + [self warn:[@"got an improper content line! ->\n" + stringByAppendingString:_line]]; return; } - tagName = [[_line substringToIndex:r.location] uppercaseString]; - tagAttributes = [[NSMutableArray alloc] init]; - + tagName = [[_line substringToIndex:r.location] uppercaseString]; + tagAttributes = [[NSMutableArray alloc] initWithCapacity:16]; + /* possible shortcut: if we spotted a ':', we don't have to do "expensive" argument scanning/processing. */ @@ -470,14 +543,12 @@ static VSStringFormatter *stringFormatter = nil; range:todoRange]; /* is line well-formed? */ if (r.length == 0 || r.location == 0) { - if (debugOn) { - NSLog(@"%s got an improper content line! ->\n%@", - __PRETTY_FUNCTION__, - _line); - } - [tagAttributes release]; + [self warn:[@"got an improper content line! ->\n" + stringByAppendingString:_line]]; + [tagAttributes release]; tagAttributes = nil; return; } + /* first check if delimiter candidate is escaped */ if ([_line characterAtIndex:(r.location - 1)] != '\\') { unichar delimiter; @@ -514,9 +585,11 @@ static VSStringFormatter *stringFormatter = nil; } tagValue = [_line substringFromIndex:NSMaxRange(r)]; + + /* process tag */ + if ([tagName isEqualToString:@"BEGIN"]) { - id tag; - + VSSaxTag *tag; tag = [self _beginTag:[self _mapTagName:tagValue] withAttrs:[[[SaxAttributes alloc] init] autorelease]]; [self->cardStack addObject:tag]; @@ -527,18 +600,21 @@ static VSStringFormatter *stringFormatter = nil; mtName = [self _mapTagName:tagValue]; if ([self->cardStack count] > 0) { NSString *expectedName; - - expectedName = [[self->cardStack lastObject] objectAtIndex:1]; + + expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName]; if (![expectedName isEqualToString:mtName]) { - if (debugOn) { - NSLog(@"%s found end tag '%@' which doesn't match expected name " - @"'%@'! Tag '%@' hasn't been closed properly. Given iCal " - @"document contains errors!", - __PRETTY_FUNCTION__, - mtName, - expectedName, - expectedName); - } + NSString *s; + + // TODO: rather report an error? + // TODO: setup userinfo dict with details + s = [NSString stringWithFormat: + @"Found end tag '%@' which does not match expected " + @"name '%@'!" + @" Tag '%@' has not been closed properly. Given " + @"document contains errors!", + mtName, expectedName, expectedName]; + [self warn:s]; + /* probably futile attempt to parse anyways */ if (debugOn) { NSLog(@"%s trying to fix previous error by inserting bogus end " @@ -550,32 +626,52 @@ static VSStringFormatter *stringFormatter = nil; } } else { - if (debugOn) { - NSLog(@"%s found end tag '%@' without any open tags left?!", - __PRETTY_FUNCTION__, - mtName); - } + // TOOD: generate error? + [self warn:[@"found end tag without any open tags left: " + stringByAppendingString:mtName]]; } [self _endTag:mtName]; [self->cardStack removeLastObject]; + + /* report parsed elements */ + if ([self->cardStack count] == 0) [self _eventsForElements]; } else { [self _dataTag:[self _mapTagName:tagName] - withAttrs:[self _mapAttrs:tagAttributes forTag:tagName] - andContent:tagValue]; + withAttrs:[self _mapAttrs:tagAttributes forTag:tagName] + andContent:tagValue]; } [tagAttributes release]; } -- (void)_parseString:(NSString *)_rawString { - unsigned pos, length; - NSMutableString *line; - NSRange r; +/* top level parsing method */ + +- (void)_reportDocStart { [self->contentHandler startDocument]; [self->contentHandler startPrefixMapping:@"" uri:self->prefixURI]; + + [self->contentHandler startElement:@"vCardSet" namespace:self->prefixURI + rawName:@"vCardSet" attributes:nil]; +} +- (void)_reportDocEnd { + [self->contentHandler endElement:@"vCardSet" namespace:self->prefixURI + rawName:@"vCardSet"]; + + [self->contentHandler endPrefixMapping:@""]; + [self->contentHandler endDocument]; +} + +- (void)_parseString:(NSString *)_rawString { + NSMutableString *line; + unsigned pos, length; + NSRange r; + + [self _reportDocStart]; + + /* start parsing */ length = [_rawString length]; /* RFC2445: @@ -587,18 +683,22 @@ static VSStringFormatter *stringFormatter = nil; /* probably too optimistic */ line = [[NSMutableString alloc] initWithCapacity:75 + 2]; - for(pos = 0; pos < length; pos++) { - unichar c = [_rawString characterAtIndex:pos]; + for (pos = 0; pos < length; pos++) { + unichar c; + + c = [_rawString characterAtIndex:pos]; if (c == '\r') { if (((length - 1) - pos) >= 1) { if ([_rawString characterAtIndex:pos + 1] == '\n') { BOOL isAtEndOfLine = YES; + /* test for folding first */ if (((length - 1) - pos) >= 2) { - unichar ws = [_rawString characterAtIndex:pos + 2]; - isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO - : YES; + unichar ws; + + ws = [_rawString characterAtIndex:pos + 2]; + isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO :YES; if (!isAtEndOfLine) { /* assemble part of line up to pos */ if (r.length > 0) { @@ -624,19 +724,19 @@ static VSStringFormatter *stringFormatter = nil; } else { /* garbled last line! */ - if (debugOn) { - NSLog(@"%s Last line is truncated, trying to parse anyways!", - __PRETTY_FUNCTION__); - } + [self warn:@"last line is truncated, trying to parse anyways!"]; } } else if (c == '\n') { /* broken, non-standard */ BOOL isAtEndOfLine = YES; + /* test for folding first */ if (((length - 1) - pos) >= 1) { - unichar ws = [_rawString characterAtIndex:pos + 1]; - isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO - : YES; + unichar ws; + + ws = [_rawString characterAtIndex:(pos + 1)]; + + isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO : YES; if (!isAtEndOfLine) { /* assemble part of line up to pos */ if (r.length > 0) { @@ -663,30 +763,72 @@ static VSStringFormatter *stringFormatter = nil; } } if (r.length > 0) { - if (debugOn) { - NSLog(@"%s Last line of iCal string is not properly terminated!", - __PRETTY_FUNCTION__); - } + [self warn:@"Last line of parse string is not properly terminated!"]; [line appendString:[_rawString substringWithRange:r]]; [self _parseLine:line]; } if ([self->cardStack count] != 0) { - if (debugOn) { - NSLog(@"%s found elements on cardStack. This indicates an improper " - @"iCal structure! Not all required events will have been " - @"generated, leading to unpredictable results!", - __PRETTY_FUNCTION__); - } + [self warn:@"found elements on cardStack. This indicates an improper " + @"nesting structure! Not all required events will have been " + @"generated, leading to unpredictable results!"]; + [self->cardStack removeAllObjects]; // clean up } - - [line release]; - [self->contentHandler endPrefixMapping:@""]; - [self->contentHandler endDocument]; + + [line release]; line = nil; + + [self _reportDocEnd]; } /* main entry functions */ +- (id)sourceForData:(NSData *)_data systemId:(NSString *)_sysId { + SaxParseException *e = nil; + NSStringEncoding encoding; + unsigned len; + const unsigned char *bytes; + id source; + + if (debugOn) { + NSLog(@"%s: trying to decode data (0x%08X,len=%d) ...", + __PRETTY_FUNCTION__, _data, [_data length]); + } + + if ((len = [_data length]) == 0) { + e = (id)[SaxParseException exceptionWithName:@"SaxIOException" + reason:@"Got no parsing data!" + userInfo:nil]; + [self->errorHandler fatalError:e]; + return nil; + } + if (len < 10) { + e = (id)[SaxParseException exceptionWithName:@"SaxIOException" + reason:@"Input data to short for vCard!" + userInfo:nil]; + [self->errorHandler fatalError:e]; + return nil; + } + + bytes = [_data bytes]; + if ((bytes[0] == 0xFF && bytes[1] == 0xFE) || + (bytes[0] == 0xFE && bytes[1] == 0xFF)) { + encoding = NSUnicodeStringEncoding; + } + else + encoding = NSUTF8StringEncoding; + + // FIXME: Data is not always utf-8..... + source = [[[NSString alloc] initWithData:_data encoding:encoding] + autorelease]; + if (source == nil) { + e = (id)[SaxParseException exceptionWithName:@"SaxIOException" + reason:@"Could not convert input to string!" + userInfo:nil]; + [self->errorHandler fatalError:e]; + } + return source; +} + - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId { if (debugOn) NSLog(@"%s: parse: %@ (sysid=%@)", __PRETTY_FUNCTION__, _source, _sysId); @@ -704,26 +846,22 @@ static VSStringFormatter *stringFormatter = nil; } if ([_source isKindOfClass:[NSData class]]) { - if (debugOn) { - NSLog(@"%s: trying to decode data (0x%08X,len=%d) ...", - __PRETTY_FUNCTION__, _source, [_source length]); - } if (_sysId == nil) _sysId = @""; - - // FIXME: Data is not always utf-8..... - _source = [[[NSString alloc] - initWithData:_source encoding:NSUTF8StringEncoding] - autorelease]; + if ((_source = [self sourceForData:_source systemId:_sysId]) == nil) + return; } if (![_source isKindOfClass:[NSString class]]) { SaxParseException *e; + NSString *s; if (debugOn) NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source); + s = [@"cannot handle data-source: " stringByAppendingString: + [_source description]]; e = (id)[SaxParseException exceptionWithName:@"SaxIOException" - reason:@"cannot handle data-source" + reason:s userInfo:nil]; [self->errorHandler fatalError:e]; diff --git a/sope-ical/versitSaxDriver/Version b/sope-ical/versitSaxDriver/Version index 76aa6eb9..f61be538 100644 --- a/sope-ical/versitSaxDriver/Version +++ b/sope-ical/versitSaxDriver/Version @@ -1,3 +1,3 @@ # Version file -SUBMINOR_VERSION:=13 +SUBMINOR_VERSION:=14 -- 2.39.2