#include <SaxObjC/SaxException.h>
#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;
}
- (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 {
*value_ = [stringFormatter stringByUnescapingRFC2445Text:attrValue];
}
-- (id<NSObject,SaxAttributes>)_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
}
attrEnum = [attributes keyEnumerator];
- while ((curAttr = [attrEnum nextObject])) {
+ while ((curAttr = [attrEnum nextObject]) != nil) {
[self _addAttribute:curAttr
value:[attributes objectForKey:curAttr]
toAttrs:retAttrs];
return retAttrs;
}
-- (NSArray *)_beginTag:(NSString *)_tagName
- withAttrs:(id<NSObject,SaxAttributes>)_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<NSObject,SaxAttributes>)_attrs
+ withAttrs:(SaxAttributes *)_attrs
andContent:(NSString *)_content
{
NSArray *subItems;
_content = [stringFormatter stringByUnescapingRFC2445Text:_content];
+
if ([self->attributeElements containsObject:_tagName]) {
[self _addAttribute:_tagName value:_content];
return;
[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<NSObject,SaxAttributes> 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.
*/
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;
}
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];
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 "
}
}
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:
/* 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) {
}
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) {
}
}
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);
}
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 = @"<data>";
-
- // 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];