From 91a9e8c832128cd68c14634060a6a4d035e2995f Mon Sep 17 00:00:00 2001 From: helge Date: Fri, 6 May 2005 15:46:44 +0000 Subject: [PATCH] added support vcard type groupings git-svn-id: http://svn.opengroupware.org/SOPE/trunk@800 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- sope-ical/samples/GNUmakefile | 12 +- sope-ical/samples/ical3.m | 3 +- sope-ical/samples/vcf2xml.m | 342 +++++++++++++ sope-ical/versitSaxDriver/ChangeLog | 7 +- sope-ical/versitSaxDriver/README | 2 - sope-ical/versitSaxDriver/VSSaxDriver.h | 7 +- sope-ical/versitSaxDriver/VSSaxDriver.m | 482 ++++++++++++------- sope-ical/versitSaxDriver/VSiCalSaxDriver.m | 7 +- sope-ical/versitSaxDriver/VSvCardSaxDriver.h | 2 + sope-ical/versitSaxDriver/VSvCardSaxDriver.m | 21 +- sope-ical/versitSaxDriver/Version | 2 +- 11 files changed, 693 insertions(+), 194 deletions(-) create mode 100644 sope-ical/samples/vcf2xml.m diff --git a/sope-ical/samples/GNUmakefile b/sope-ical/samples/GNUmakefile index b7aed7c3..245e4ccc 100644 --- a/sope-ical/samples/GNUmakefile +++ b/sope-ical/samples/GNUmakefile @@ -3,13 +3,15 @@ -include ../../config.make include $(GNUSTEP_MAKEFILES)/common.make -TOOL_NAME = ical2 ical3 +TOOL_NAME = ical2 ical3 vcf2xml -ical2_OBJC_FILES = ical2.m -ical3_OBJC_FILES = ical3.m +ical2_OBJC_FILES = ical2.m +ical3_OBJC_FILES = ical3.m +vcf2xml_OBJC_FILES = vcf2xml.m -ical2_TOOL_LIBS += -lNGiCal -lSaxObjC -ical3_TOOL_LIBS += -lNGiCal -lSaxObjC +ical2_TOOL_LIBS += -lNGiCal -lSaxObjC +ical3_TOOL_LIBS += -lNGiCal -lSaxObjC +vcf2xml_TOOL_LIBS += -lSaxObjC -include GNUmakefile.preamble include $(GNUSTEP_MAKEFILES)/tool.make diff --git a/sope-ical/samples/ical3.m b/sope-ical/samples/ical3.m index 6c480b44..bcbbb526 100644 --- a/sope-ical/samples/ical3.m +++ b/sope-ical/samples/ical3.m @@ -145,12 +145,11 @@ int main(int argc, char **argv, char **env) { iCal3Tool *tool; int rc; + pool = [[NSAutoreleasePool alloc] init]; #if LIB_FOUNDATION_LIBRARY [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; #endif - pool = [[NSAutoreleasePool alloc] init]; - if ((tool = [[iCal3Tool alloc] init])) { NS_DURING rc = [tool runWithArguments:[[NSProcessInfo processInfo] arguments]]; diff --git a/sope-ical/samples/vcf2xml.m b/sope-ical/samples/vcf2xml.m new file mode 100644 index 00000000..3f56c2c8 --- /dev/null +++ b/sope-ical/samples/vcf2xml.m @@ -0,0 +1,342 @@ +/* + Copyright (C) 2005 Helge Hess + + This file is part of SOPE. + + SOPE is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + SOPE is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with SOPE; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +// Note: this does not yet produce valid XML output + +#import +#include + +@interface vcf2xml : NSObject +{ + id parser; + id sax; +} + +- (int)runWithArguments:(NSArray *)_args; + +@end + +#include "common.h" + +@interface MySAXHandler : SaxDefaultHandler +{ + id locator; + int indent; + + NSString *lastNS; +} + +- (void)indent; + +@end + +@implementation vcf2xml + +- (id)init { + if ((self = [super init]) != nil) { + self->parser = [[[SaxXMLReaderFactory standardXMLReaderFactory] + createXMLReaderForMimeType:@"text/x-vcard"] retain]; + if (parser == nil) { + fprintf(stderr, "Error: could not load a vCard SAX driver bundle!\n"); + exit(2); + } + //NSLog(@"Using parser: %@", self->parser); + + self->sax = [[MySAXHandler alloc] init]; + [parser setContentHandler:self->sax]; + [parser setErrorHandler:self->sax]; + } + return self; +} + +- (void)dealloc { + [self->sax release]; + [self->parser release]; + [super dealloc]; +} + +/* process files */ + +- (void)processFile:(NSString *)_path { + [self->parser parseFromSystemId:_path]; +} + +/* error handling */ + +- (NSException *)handleException:(NSException *)_exc onPath:(NSString *)_p { + fprintf(stderr, "Error: catched exception on path '%s': %s\n", + [_p cString], [[_exc description] cString]); + return nil; +} + +/* main entry */ + +- (int)runWithArguments:(NSArray *)_args { + NSEnumerator *args; + NSString *arg; + + /* begin processing */ + + args = [_args objectEnumerator]; + [args nextObject]; // skip tool name ... + + while ((arg = [args nextObject]) != nil) { + NSAutoreleasePool *pool2; + + if ([arg hasPrefix:@"-"]) { /* consume defaults */ + [args nextObject]; + continue; + } + + pool2 = [[NSAutoreleasePool alloc] init]; + + + if (![arg isAbsolutePath]) { + arg = [[[NSFileManager defaultManager] currentDirectoryPath] + stringByAppendingPathComponent:arg]; + } + + NS_DURING + [self->parser parseFromSystemId:arg]; + NS_HANDLER + [[self handleException:localException onPath:arg] raise]; + NS_ENDHANDLER; + + [pool2 release]; + } + return 0; +} + +@end /* vcf2xml */ + + +@implementation MySAXHandler + +- (void)dealloc { + [self->lastNS release]; + [self->locator release]; + [super dealloc]; +} + +/* output */ + +- (void)indent { + int i; + + for (i = 0; i < (self->indent * 4); i++) + fputc(' ', stdout); +} + +/* documents */ + +- (void)setDocumentLocator:(id)_loc { + [self->locator autorelease]; + self->locator = [_loc retain]; +} + +- (void)startDocument { + //puts("start document .."); + //self->indent++; +} +- (void)endDocument { + //self->indent--; + //puts("end document."); +} + +- (void)startPrefixMapping:(NSString *)_prefix uri:(NSString *)_uri { + [self indent]; + //printf("ns-map: %s=%s\n", [_prefix cString], [_uri cString]); +} +- (void)endPrefixMapping:(NSString *)_prefix { + [self indent]; + //printf("ns-unmap: %s\n", [_prefix cString]); +} + +- (void)startElement:(NSString *)_localName + namespace:(NSString *)_ns + rawName:(NSString *)_rawName + attributes:(id)_attrs +{ + int i, c; + [self indent]; + printf("<%s", [_localName cString]); + + if ([_ns length] > 0) { + if ([_ns isEqualToString:self->lastNS]) + ; + else { + printf(" xmlns='%s'", [_ns cString]); + ASSIGNCOPY(self->lastNS, _ns); + } + } + + for (i = 0, c = [_attrs count]; i < c; i++) { + NSString *type; + NSString *ans; + + ans = [_attrs uriAtIndex:i]; + + printf(" %s=\"%s\"", + [[_attrs nameAtIndex:i] cString], + [[_attrs valueAtIndex:i] cString]); + + if (![_ns isEqualToString:ans]) + printf("(ns=%s)", [ans cString]); + + type = [_attrs typeAtIndex:i]; + if (![type isEqualToString:@"CDATA"] && (type != nil)) + printf("[%s]", [type cString]); + } + puts(">"); + self->indent++; +} +- (void)endElement:(NSString *)_localName + namespace:(NSString *)_ns + rawName:(NSString *)_rawName +{ + self->indent--; + [self indent]; + printf("\n", [_localName cString]); +} + +- (void)characters:(unichar *)_chars length:(int)_len { + NSString *str; + id tmp; + unsigned i, len; + + if (_len == 0) { + [self indent]; + printf("\"\"\n"); + return; + } + + for (i = 0; i < (unsigned)_len; i++) { + if (_chars[i] > 255) { + NSLog(@"detected large char: o%04o d%03i h%04X", + _chars[i], _chars[i], _chars[i]); + } + } + + str = [NSString stringWithCharacters:_chars length:_len]; + len = [str length]; + + tmp = [str componentsSeparatedByString:@"\n"]; + str = [tmp componentsJoinedByString:@"\\n"]; + tmp = [str componentsSeparatedByString:@"\r"]; + str = [tmp componentsJoinedByString:@"\\r"]; + + [self indent]; + printf("\"%s\"\n", [str cString]); +} +- (void)ignorableWhitespace:(unichar *)_chars length:(int)_len { + NSString *data; + id tmp; + + data = [NSString stringWithCharacters:_chars length:_len]; + tmp = [data componentsSeparatedByString:@"\n"]; + data = [tmp componentsJoinedByString:@"\\n"]; + tmp = [data componentsSeparatedByString:@"\r"]; + data = [tmp componentsJoinedByString:@"\\r"]; + + [self indent]; + printf("whitespace: \"%s\"\n", [data cString]); +} + +- (void)processingInstruction:(NSString *)_pi data:(NSString *)_data { + [self indent]; + printf("PI: '%s' '%s'\n", [_pi cString], [_data cString]); +} + +#if 0 +- (xmlEntityPtr)getEntity:(NSString *)_name { + NSLog(@"get entity %@", _name); + return NULL; +} +- (xmlEntityPtr)getParameterEntity:(NSString *)_name { + NSLog(@"get para entity %@", _name); + return NULL; +} +#endif + +/* entities */ + +- (id)resolveEntityWithPublicId:(NSString *)_pubId + systemId:(NSString *)_sysId +{ + [self indent]; + printf("shall resolve entity with '%s' '%s'", + [_pubId cString], [_sysId cString]); + return nil; +} + +/* errors */ + +- (void)warning:(SaxParseException *)_exception { + NSLog(@"warning(%@:%i): %@", + [[_exception userInfo] objectForKey:@"publicId"], + [[[_exception userInfo] objectForKey:@"line"] intValue], + [_exception reason]); +} + +- (void)error:(SaxParseException *)_exception { + NSLog(@"error(%@:%i): %@", + [[_exception userInfo] objectForKey:@"publicId"], + [[[_exception userInfo] objectForKey:@"line"] intValue], + [_exception reason]); +} + +- (void)fatalError:(SaxParseException *)_exception { + NSLog(@"fatal error(%@:%i): %@", + [[_exception userInfo] objectForKey:@"publicId"], + [[[_exception userInfo] objectForKey:@"line"] intValue], + [_exception reason]); + [_exception raise]; +} + +@end /* MySAXHandler */ + + + +int main(int argc, char **argv, char **env) { + NSAutoreleasePool *pool; + vcf2xml *tool; + int rc; + + pool = [[NSAutoreleasePool alloc] init]; +#if LIB_FOUNDATION_LIBRARY + [NSProcessInfo initializeWithArguments:argv count:argc environment:env]; +#endif + + if ((tool = [[vcf2xml alloc] init])) { + NS_DURING + rc = [tool runWithArguments:[[NSProcessInfo processInfo] arguments]]; + NS_HANDLER + abort(); + NS_ENDHANDLER; + + [tool release]; + } + else + rc = 1; + + [pool release]; + return rc; +} diff --git a/sope-ical/versitSaxDriver/ChangeLog b/sope-ical/versitSaxDriver/ChangeLog index f1c735a0..a40da82b 100644 --- a/sope-ical/versitSaxDriver/ChangeLog +++ b/sope-ical/versitSaxDriver/ChangeLog @@ -1,3 +1,8 @@ +2005-05-06 Helge Hess + + * VSSaxDriver.m: more reorganisations, added support for groupings + (v4.5.15) + 2005-05-05 Helge Hess * VSSaxDriver.m: code cleanups / reorgs, properly embed reported @@ -240,8 +245,6 @@ * added unfold method (doesnt do anything yet) - 2003-11-23 Max Berger * Initial Version (v0.1.1) - diff --git a/sope-ical/versitSaxDriver/README b/sope-ical/versitSaxDriver/README index 4fd5253f..85d148e6 100644 --- a/sope-ical/versitSaxDriver/README +++ b/sope-ical/versitSaxDriver/README @@ -1,5 +1,3 @@ -# $Id$ - Overview ======== diff --git a/sope-ical/versitSaxDriver/VSSaxDriver.h b/sope-ical/versitSaxDriver/VSSaxDriver.h index 48ad72ec..9fe490b6 100644 --- a/sope-ical/versitSaxDriver/VSSaxDriver.h +++ b/sope-ical/versitSaxDriver/VSSaxDriver.h @@ -35,7 +35,7 @@ id errorHandler; NSString *prefixURI; NSMutableArray *cardStack; - NSMutableArray *elementList; + NSMutableArray *elementList; /* a list of tags to be rep. */ NSSet *attributeElements; NSDictionary *elementMapping; @@ -60,6 +60,11 @@ - (void)setSubItemMapping:(NSArray *)_mapping forElement:(NSString *)_element; +/* events */ + +- (void)reportDocStart; +- (void)reportDocEnd; + @end #endif /* __versitSaxDriver_VersitSaxDriver_H__ */ diff --git a/sope-ical/versitSaxDriver/VSSaxDriver.m b/sope-ical/versitSaxDriver/VSSaxDriver.m index 0113f2bc..4f0881be 100644 --- a/sope-ical/versitSaxDriver/VSSaxDriver.m +++ b/sope-ical/versitSaxDriver/VSSaxDriver.m @@ -28,57 +28,86 @@ @interface VSSaxTag : NSObject { -@public - NSString *type; +@private + char type; NSString *tagName; + NSString *group; +@public SaxAttributes *attrs; - NSString *data; + unichar *data; + unsigned int datalen; } -+ (id)beginTag:(NSString *)_tag attributes:(SaxAttributes *)_attrs; ++ (id)beginTag:(NSString *)_tag group:(NSString *)_group + attributes:(SaxAttributes *)_attrs; - (id)initEndTag:(NSString *)_tag; -- (id)initWithData:(NSString *)_data; + +- (id)initWithContentString:(NSString *)_data; - (NSString *)tagName; +- (BOOL)isStartTag; +- (BOOL)isEndTag; +- (BOOL)isTag; @end -static NSString *VSBeginType = @"BEGIN"; -static NSString *VSEndType = @"END"; -static NSString *VSDataType = @"DATA"; - @implementation VSSaxTag -+ (id)beginTag:(NSString *)_tag attributes:(SaxAttributes *)_attrs { ++ (id)beginTag:(NSString *)_tag group:(NSString *)_group + attributes:(SaxAttributes *)_attrs +{ VSSaxTag *tag; tag = [[[self alloc] init] autorelease]; - tag->type = VSBeginType; + tag->type = 'B'; tag->tagName = [_tag copy]; + tag->group = [_group copy]; tag->attrs = [_attrs retain]; return tag; } - (id)initEndTag:(NSString *)_tag { - self->type = VSEndType; + self->type = 'E'; self->tagName = [_tag copy]; return self; } -- (id)initWithData:(NSString *)_data { - self->type = VSDataType; - self->data = [_data retain]; +- (id)initWithContentString:(NSString *)_data { + if (_data == nil) { + [self release]; + return nil; + } + + self->datalen = [_data length]; + self->data = calloc(self->datalen + 1, sizeof(unichar)); + [_data getCharacters:self->data range:NSMakeRange(0, self->datalen)]; return self; } - (void)dealloc { - [self->data release]; + if (self->data) free(self->data); + [self->group release]; [self->tagName release]; [self->attrs release]; [super dealloc]; } +/* accessors */ + - (NSString *)tagName { return self->tagName; } +- (NSString *)group { + return self->group; +} + +- (BOOL)isStartTag { + return self->type == 'B' ? YES : NO; +} +- (BOOL)isEndTag { + return self->type == 'E' ? YES : NO; +} +- (BOOL)isTag { + return (self->type == 'B' || self->type == 'E') ? YES : NO; +} @end /* VSSaxTag */ @@ -236,61 +265,59 @@ static VSStringFormatter *stringFormatter = nil; /* parsing */ +- (NSString *)_groupFromTagName:(NSString *)_tagName { + NSRange r; + + r = [_tagName rangeOfCharacterFromSet:dotCharSet]; + if (r.length == 0) + return nil; + + return [_tagName substringToIndex:r.location]; +} + - (NSString *)_mapTagName:(NSString *)_tagName { NSString *ret; NSRange r; - if ((ret = [self->elementMapping objectForKey:_tagName]) == nil) { - //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping); - ret = _tagName; + if ((ret = [self->elementMapping objectForKey:_tagName]) != nil) + return ret; - /* This is to allow parsing of vCards produced by Apple - Addressbook. AFAIK the .dot notation is a non-standard - extension */ - r = [_tagName rangeOfCharacterFromSet:dotCharSet]; - if (r.length > 0) { - ret = [self _mapTagName:[_tagName substringFromIndex:(r.location + 1)]]; - } - } - return ret; -} - -- (void)_addAttribute:(NSString *)_attribute - value:(NSString *)_value - toAttrs:(SaxAttributes *)_attrs -{ - [_attrs addAttribute:_attribute - uri:self->prefixURI - rawName:_attribute - type:@"CDATA" - value:_value]; -} - -- (void)_addAttribute:(NSString *)_attribute value:(NSString *)_value { - VSSaxTag *element; + //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping); + ret = _tagName; - element = [cardStack lastObject]; - [self _addAttribute:_attribute value:_value toAttrs:element->attrs]; + /* + This is to allow parsing of vCards produced by Apple + Addressbook. + The dot-notation is described as 'grouping' in RFC 2425, section 5.8.2. + */ + r = [_tagName rangeOfCharacterFromSet:dotCharSet]; + if (r.length > 0) + ret = [self _mapTagName:[_tagName substringFromIndex:(r.location + 1)]]; + + return ret; } - (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName { + NSDictionary *tagMap; NSString *mappedName; + + /* check whether we have a attr-map stored under the element-name */ + tagMap = [self->attributeMapping objectForKey:_tagName]; + if ((mappedName = [tagMap objectForKey:_attrName]) != nil) + return mappedName; - mappedName = [(NSDictionary *)[self->attributeMapping objectForKey:_tagName] - objectForKey:_attrName]; - if (mappedName == nil) { - mappedName = [(NSDictionary *)[self->attributeMapping objectForKey: - [self _mapTagName:_tagName]] - objectForKey:_attrName]; - } - if (mappedName == nil) { - mappedName = [(NSDictionary *)[self->attributeMapping objectForKey:@""] - objectForKey:_attrName]; - } - if (mappedName == nil) - mappedName = _attrName; + /* check whether we have a attr-map stored under the mapped element-name */ + tagMap = [self->attributeMapping objectForKey:[self _mapTagName:_tagName]]; + if ((mappedName = [tagMap objectForKey:_attrName]) != nil) + return mappedName; + + /* check whether we have a global attr-map */ + tagMap = [self->attributeMapping objectForKey:@""]; + if ((mappedName = [tagMap objectForKey:_attrName]) != nil) + return mappedName; - return mappedName; + /* return the name as-is */ + return _attrName; } - (void)_parseAttr:(NSString *)_attr @@ -376,9 +403,8 @@ static VSStringFormatter *stringFormatter = nil; attrEnum = [attributes keyEnumerator]; while ((curAttr = [attrEnum nextObject]) != nil) { - [self _addAttribute:curAttr - value:[attributes objectForKey:curAttr] - toAttrs:retAttrs]; + [retAttrs addAttribute:curAttr uri:self->prefixURI rawName:curAttr + type:@"CDATA" value:[attributes objectForKey:curAttr]]; } [attributes release]; @@ -386,10 +412,12 @@ static VSStringFormatter *stringFormatter = nil; return retAttrs; } -- (VSSaxTag *)_beginTag:(NSString *)_tagName withAttrs:(SaxAttributes *)_attrs{ +- (VSSaxTag *)_beginTag:(NSString *)_tagName group:(NSString *)_group + withAttrs:(SaxAttributes *)_attrs +{ VSSaxTag *tag; - tag = [VSSaxTag beginTag:_tagName attributes:_attrs]; + tag = [VSSaxTag beginTag:_tagName group:_group attributes:_attrs]; [self->elementList addObject:tag]; return tag; } @@ -402,7 +430,9 @@ static VSStringFormatter *stringFormatter = nil; [tag release]; tag = nil; } -- (void)_addSubItems:(NSArray *)_items withData:(NSString *)_content { +- (void)_addSubItems:(NSArray *)_items group:(NSString *)_group + withData:(NSString *)_content +{ NSEnumerator *itemEnum, *contentEnum; NSString *subTag; @@ -414,11 +444,12 @@ static VSStringFormatter *stringFormatter = nil; subContent = [contentEnum nextObject]; - [self _beginTag:subTag withAttrs:nil]; + [self _beginTag:subTag group:_group withAttrs:nil]; if ([subContent length] > 0) { VSSaxTag *a; - if ((a = [(VSSaxTag*)[VSSaxTag alloc] initWithData:subContent]) != nil) { + a = [(VSSaxTag*)[VSSaxTag alloc] initWithContentString:subContent]; + if (a != nil) { [self->elementList addObject:a]; [a release]; } @@ -427,73 +458,142 @@ static VSStringFormatter *stringFormatter = nil; } } -- (void)_dataTag:(NSString *)_tagName +- (void)_reportContentAsTag:(NSString *)_tagName + group:(NSString *)_group withAttrs:(SaxAttributes *)_attrs andContent:(NSString *)_content { + /* + This is called for all non-BEGIN|END types. + */ NSArray *subItems; _content = [stringFormatter stringByUnescapingRFC2445Text:_content]; + + /* check whether type should be reported as an attribute in XML */ if ([self->attributeElements containsObject:_tagName]) { - [self _addAttribute:_tagName value:_content]; + /* + Add tag as an attribute to last component in the cardstack. This is + stuff like the "VERSION" type contained in a "BEGIN:VCARD" which will + be reported as (as an attribute of the container). + */ + VSSaxTag *element; + + element = [self->cardStack lastObject]; + [element->attrs addAttribute:_tagName uri:self->prefixURI + rawName:_tagName type:@"CDATA" value:_content]; return; - } + } + + /* report type as an XML tag */ + + [self _beginTag:_tagName group:_group withAttrs:_attrs]; - [self _beginTag:_tagName withAttrs:_attrs]; if ([_content length] > 0) { if ((subItems = [self->subItemMapping objectForKey:_tagName]) != nil) { - [self _addSubItems:subItems withData:_content]; + [self _addSubItems:subItems group:_group withData:_content]; } else { VSSaxTag *a; - - if ((a = [(VSSaxTag *)[VSSaxTag alloc] initWithData:_content]) != nil) { + + a = [(VSSaxTag *)[VSSaxTag alloc] initWithContentString:_content]; + if (a != nil) { [self->elementList addObject:a]; [a release]; } } } + [self _endTag:_tagName]; } /* report events for collected elements */ -- (void)_eventsForElements { +- (void)reportStartGroup:(NSString *)_group { + SaxAttributes *attrs; + + attrs = [[SaxAttributes alloc] init]; + [attrs addAttribute:@"name" uri:self->prefixURI rawName:@"name" + type:@"CDATA" value:_group]; + + [self->contentHandler startElement:@"group" namespace:self->prefixURI + rawName:@"group" attributes:attrs]; + [attrs release]; +} +- (void)reportEndGroup { + [self->contentHandler endElement:@"group" namespace:self->prefixURI + rawName:@"group"]; +} + +- (void)reportQueuedTags { + /* + Why does the parser need the list instead of reporting the events + straight away? + + Because some vCard tags like the 'version' are reported as attributes + on the container tag. So we have a sequence like: + BEGIN:VCARD + ... + VERSION:3.0 + which will get reported as: + + */ NSEnumerator *enu; - VSSaxTag *obj; + VSSaxTag *tagToReport; + NSString *lastGroup; + lastGroup = nil; enu = [self->elementList objectEnumerator]; - while ((obj = [enu nextObject]) != nil) { - if ([obj->type isEqualToString:VSBeginType]) { - [self->contentHandler startElement:obj->tagName + while ((tagToReport = [enu nextObject]) != nil) { + if ([tagToReport isStartTag]) { + NSString *tg; + + tg = [tagToReport group]; + if (![lastGroup isEqualToString:tg] && lastGroup != tg) { + if (lastGroup != nil) [self reportEndGroup]; + ASSIGNCOPY(lastGroup, tg); + if (lastGroup != nil) [self reportStartGroup:lastGroup]; + } + } + + if ([tagToReport isStartTag]) { + [self->contentHandler startElement:[tagToReport tagName] namespace:self->prefixURI - rawName:obj->tagName - attributes:obj->attrs]; - } - else if ([obj->type isEqualToString:VSEndType]) { - [self->contentHandler endElement:obj->tagName + rawName:[tagToReport tagName] + attributes:tagToReport->attrs]; + } + else if ([tagToReport isEndTag]) { + [self->contentHandler endElement:[tagToReport tagName] namespace:self->prefixURI - rawName:obj->tagName]; + rawName:[tagToReport tagName]]; } else { - 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 != NULL) free(chardata); chardata = NULL; + [self->contentHandler characters:tagToReport->data + length:tagToReport->datalen]; } } - [elementList removeAllObjects]; + + /* flush event group */ + [self->elementList removeAllObjects]; + + /* close open groups */ + if (lastGroup != nil) { + [self reportEndGroup]; + [lastGroup release]; lastGroup = nil; + } } /* errors */ +- (void)error:(NSString *)_text { + SaxParseException *e; + + e = (id)[SaxParseException exceptionWithName:@"SaxParseException" + reason:_text + userInfo:nil]; + [self->errorHandler error:e]; +} - (void)warn:(NSString *)_warn { SaxParseException *e; @@ -505,6 +605,60 @@ static VSStringFormatter *stringFormatter = nil; /* parsing raw string */ +- (void)_beginComponentWithValue:(NSString *)tagValue { + VSSaxTag *tag; + + tag = [self _beginTag:[self _mapTagName:tagValue] + group:nil + withAttrs:[[[SaxAttributes alloc] init] autorelease]]; + [self->cardStack addObject:tag]; +} + +- (void)_endComponent:(NSString *)tagName value:(NSString *)tagValue { + NSString *mtName; + + mtName = [self _mapTagName:tagValue]; + if ([self->cardStack count] > 0) { + NSString *expectedName; + + expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName]; + if (![expectedName isEqualToString:mtName]) { + 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 error:s]; + + /* probably futile attempt to parse anyways */ + if (debugOn) { + NSLog(@"%s trying to fix previous error by inserting bogus end " + @"tag.", + __PRETTY_FUNCTION__); + } + [self _endTag:expectedName]; + [self->cardStack removeLastObject]; + } + } + else { + // TOOD: generate error? + [self error:[@"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 reportQueuedTags]; +} + - (void)_parseLine:(NSString *)_line { NSString *tagName, *tagValue; NSMutableArray *tagAttributes; @@ -518,21 +672,25 @@ static VSStringFormatter *stringFormatter = nil; range:todoRange]; /* is line well-formed? */ if (r.length == 0) { - [self warn:[@"got an improper content line! ->\n" - stringByAppendingString:_line]]; + [self error:[@"got an improper content line! ->\n" + stringByAppendingString:_line]]; return; } - + + /* tagname is everything up to a ':' or ';' (value or parameter) */ tagName = [[_line substringToIndex:r.location] uppercaseString]; tagAttributes = [[NSMutableArray alloc] initWithCapacity:16]; - /* possible shortcut: if we spotted a ':', we don't have to do "expensive" + /* + possible shortcut: if we spotted a ':', we don't have to do "expensive" argument scanning/processing. */ if ([_line characterAtIndex:r.location] != ':') { - BOOL isAtEnd = NO, isInDquote = NO; - unsigned start = NSMaxRange(r); - + BOOL isAtEnd = NO; + BOOL isInDquote = NO; + unsigned start; + + start = NSMaxRange(r); todoRange = NSMakeRange(start, length - start); while(!isAtEnd) { BOOL skip = YES; @@ -541,10 +699,11 @@ static VSStringFormatter *stringFormatter = nil; r = [_line rangeOfCharacterFromSet:colonSemicolonAndDquoteCharSet options:0 range:todoRange]; + /* is line well-formed? */ if (r.length == 0 || r.location == 0) { - [self warn:[@"got an improper content line! ->\n" - stringByAppendingString:_line]]; + [self error:[@"got an improper content line! ->\n" + stringByAppendingString:_line]]; [tagAttributes release]; tagAttributes = nil; return; } @@ -584,105 +743,70 @@ static VSStringFormatter *stringFormatter = nil; } } tagValue = [_line substringFromIndex:NSMaxRange(r)]; - + + /* + At this point we have: + name: 'BEGIN', 'TEL', 'EMAIL', 'ITEM1.ADR' etc + value: ';;;Magdeburg;;;Germany' + atributes: ("type=INTERNET", "type=HOME", "type=pref") + */ /* process tag */ if ([tagName isEqualToString:@"BEGIN"]) { - VSSaxTag *tag; - tag = [self _beginTag:[self _mapTagName:tagValue] - withAttrs:[[[SaxAttributes alloc] init] autorelease]]; - [self->cardStack addObject:tag]; - } + if ([tagAttributes count] > 0) + [self warn:@"Losing unexpected parameters of BEGIN line."]; + [self _beginComponentWithValue:tagValue]; + } else if ([tagName isEqualToString:@"END"]) { - NSString *mtName; - - mtName = [self _mapTagName:tagValue]; - if ([self->cardStack count] > 0) { - NSString *expectedName; - - expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName]; - if (![expectedName isEqualToString:mtName]) { - 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 " - @"tag.", - __PRETTY_FUNCTION__); - } - [self _endTag:expectedName]; - [self->cardStack removeLastObject]; - } - } - else { - // 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]; + if ([tagAttributes count] > 0) + [self warn:@"Losing unexpected parameters of END line."]; + [self _endComponent:tagName value:tagValue]; } else { - [self _dataTag:[self _mapTagName:tagName] + [self _reportContentAsTag:[self _mapTagName:tagName] + group:[self _groupFromTagName:tagName] withAttrs:[self _mapAttrs:tagAttributes forTag:tagName] andContent:tagValue]; } + [tagAttributes release]; } /* top level parsing method */ -- (void)_reportDocStart { +- (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"]; - +- (void)reportDocEnd { [self->contentHandler endPrefixMapping:@""]; [self->contentHandler endDocument]; } - (void)_parseString:(NSString *)_rawString { + /* + This method split the string into content lines for actual vCard + parsing. + + RFC2445: + contentline = name *(";" param ) ":" value CRLF + ; When parsing a content line, folded lines MUST first + ; be unfolded + */ NSMutableString *line; unsigned pos, length; NSRange r; - [self _reportDocStart]; + [self reportDocStart]; /* start parsing */ length = [_rawString length]; - /* RFC2445: - contentline = name *(";" param ) ":" value CRLF - ; When parsing a content line, folded lines MUST first - ; be unfolded - */ - r = NSMakeRange(0, 0); - /* probably too optimistic */ - line = [[NSMutableString alloc] initWithCapacity:75 + 2]; - + r = NSMakeRange(0, 0); + line = [[NSMutableString alloc] initWithCapacity:75 + 2]; + for (pos = 0; pos < length; pos++) { unichar c; @@ -767,7 +891,7 @@ static VSStringFormatter *stringFormatter = nil; [line appendString:[_rawString substringWithRange:r]]; [self _parseLine:line]; } - + if ([self->cardStack count] != 0) { [self warn:@"found elements on cardStack. This indicates an improper " @"nesting structure! Not all required events will have been " @@ -777,7 +901,7 @@ static VSStringFormatter *stringFormatter = nil; [line release]; line = nil; - [self _reportDocEnd]; + [self reportDocEnd]; } /* main entry functions */ @@ -867,6 +991,11 @@ static VSStringFormatter *stringFormatter = nil; [self->errorHandler fatalError:e]; return; } + + /* ensure consistent state */ + + [self->cardStack removeAllObjects]; + [self->elementList removeAllObjects]; /* start parsing */ @@ -876,6 +1005,11 @@ static VSStringFormatter *stringFormatter = nil; } if (_sysId == nil) _sysId = @""; [self _parseString:_source]; + + /* tear down */ + + [self->cardStack removeAllObjects]; + [self->elementList removeAllObjects]; } - (void)parseFromSource:(id)_source { diff --git a/sope-ical/versitSaxDriver/VSiCalSaxDriver.m b/sope-ical/versitSaxDriver/VSiCalSaxDriver.m index 79f3c1a0..02797a16 100644 --- a/sope-ical/versitSaxDriver/VSiCalSaxDriver.m +++ b/sope-ical/versitSaxDriver/VSiCalSaxDriver.m @@ -51,7 +51,7 @@ static NSArray *defGeoMappings = nil; + (NSDictionary *)xcalMapping { static NSDictionary *dict = nil; - if (!dict) { + if (dict == nil) { NSMutableDictionary *xcal; xcal = [[NSMutableDictionary alloc] initWithCapacity:60]; @@ -290,10 +290,9 @@ static NSArray *defGeoMappings = nil; return dict; } -+ (NSDictionary *)xcalAttrMapping -{ ++ (NSDictionary *)xcalAttrMapping { static NSDictionary *dict = nil; - if (!dict) { + if (dict == nil) { NSMutableDictionary *xcal; xcal = [[NSMutableDictionary alloc] initWithCapacity:20]; diff --git a/sope-ical/versitSaxDriver/VSvCardSaxDriver.h b/sope-ical/versitSaxDriver/VSvCardSaxDriver.h index e8b6116e..cf93a84b 100644 --- a/sope-ical/versitSaxDriver/VSvCardSaxDriver.h +++ b/sope-ical/versitSaxDriver/VSvCardSaxDriver.h @@ -26,6 +26,8 @@ #include "VSSaxDriver.h" +// TODO: photo is reported incorrectly + @interface VSvCardSaxDriver : VSSaxDriver { } diff --git a/sope-ical/versitSaxDriver/VSvCardSaxDriver.m b/sope-ical/versitSaxDriver/VSvCardSaxDriver.m index cdc08b99..3fe0a4c4 100644 --- a/sope-ical/versitSaxDriver/VSvCardSaxDriver.m +++ b/sope-ical/versitSaxDriver/VSvCardSaxDriver.m @@ -308,9 +308,9 @@ static NSSet *defElementNames = nil; } - (id)init { - if ((self = [super init])) { - - [self setPrefixURI:@"http://www.ietf.org/internet-drafts/draft-dawson-vcard-xml-dtd-03.txt"]; + if ((self = [super init]) != nil) { + [self setPrefixURI: + @"http://www.ietf.org/internet-drafts/draft-dawson-vcard-xml-dtd-03.txt"]; [self setElementMapping:[[self class] xcardMapping]]; [self setAttributeElements:defElementNames]; [self _setVCardAttributeMappings]; @@ -319,4 +319,19 @@ static NSSet *defElementNames = nil; return self; } +/* top level parsing method */ + +- (void)reportDocStart { + [super reportDocStart]; + + [self->contentHandler startElement:@"vCardSet" namespace:self->prefixURI + rawName:@"vCardSet" attributes:nil]; +} +- (void)reportDocEnd { + [self->contentHandler endElement:@"vCardSet" namespace:self->prefixURI + rawName:@"vCardSet"]; + + [super reportDocEnd]; +} + @end /* VCardSaxDriver */ diff --git a/sope-ical/versitSaxDriver/Version b/sope-ical/versitSaxDriver/Version index f61be538..cb19d00e 100644 --- a/sope-ical/versitSaxDriver/Version +++ b/sope-ical/versitSaxDriver/Version @@ -1,3 +1,3 @@ # Version file -SUBMINOR_VERSION:=14 +SUBMINOR_VERSION:=15 -- 2.39.5