/*
Copyright (C) 2003-2004 Max Berger
- Copyright (C) 2004 OpenGroupware.org
+ Copyright (C) 2004-2005 OpenGroupware.org
This file is part of versitSaxDriver, written for the OpenGroupware.org
project (OGo).
- OGo is free software; you can redistribute it and/or modify it under
+ 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.
- OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+ 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 OGo; see the file COPYING. If not, write to the
+ 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.
*/
-// $Id$
#include "VSSaxDriver.h"
#include "VSStringFormatter.h"
+#include <SaxObjC/SaxException.h>
+#include <NGExtensions/NGQuotedPrintableCoding.h>
#include "common.h"
+@interface VSSaxTag : NSObject
+{
+@private
+ char type;
+ NSString *tagName;
+ NSString *group;
+@public
+ SaxAttributes *attrs;
+ unichar *data;
+ unsigned int datalen;
+}
+
++ (id)beginTag:(NSString *)_tag group:(NSString *)_group
+ attributes:(SaxAttributes *)_attrs;
+- (id)initEndTag:(NSString *)_tag;
+
+- (id)initWithContentString:(NSString *)_data;
+
+- (NSString *)tagName;
+- (NSString *)group;
+- (BOOL)isStartTag;
+- (BOOL)isEndTag;
+- (BOOL)isTag;
+
+@end
+
+@implementation VSSaxTag
+
++ (id)beginTag:(NSString *)_tag group:(NSString *)_group
+ attributes:(SaxAttributes *)_attrs
+{
+ VSSaxTag *tag;
+
+ tag = [[[self alloc] init] autorelease];
+ tag->type = 'B';
+ tag->tagName = [_tag copy];
+ tag->group = [_group copy];
+ tag->attrs = [_attrs retain];
+ return tag;
+}
+- (id)initEndTag:(NSString *)_tag {
+ self->type = 'E';
+ self->tagName = [_tag copy];
+ return self;
+}
+- (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 {
+ 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 */
+
@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;
static BOOL didInit = NO;
NSUserDefaults *ud;
- if(didInit)
+ if (didInit)
return;
didInit = YES;
- (id)init {
if ((self = [super init])) {
self->prefixURI = @"";
- self->cardStack = [[NSMutableArray alloc] init];
- self->elementList = [[NSMutableArray alloc] init];
- self->attributeMapping = [[NSMutableDictionary alloc] init];
- self->subItemMapping = [[NSMutableDictionary alloc] init];
+ self->cardStack = [[NSMutableArray alloc] initWithCapacity:4];
+ self->elementList = [[NSMutableArray alloc] initWithCapacity:8];
+ self->attributeMapping = [[NSMutableDictionary alloc] initWithCapacity:8];
+ self->subItemMapping = [[NSMutableDictionary alloc] initWithCapacity:8];
}
return self;
}
- (void)dealloc {
[self->contentHandler release];
+ [self->errorHandler release];
[self->prefixURI release];
[self->cardStack release];
[self->elementList release];
/* handlers */
- (void)setContentHandler:(id<NSObject,SaxContentHandler>)_handler {
- ASSIGN(self->contentHandler,_handler);
+ ASSIGN(self->contentHandler, _handler);
}
- (void)setDTDHandler:(id<NSObject,SaxDTDHandler>)_handler {
}
- (void)setErrorHandler:(id<NSObject,SaxErrorHandler>)_handler {
- // FIXME
+ ASSIGN(self->errorHandler, _handler);
}
- (void)setEntityResolver:(id<NSObject,SaxEntityResolver>)_handler {
// FIXME
- (id<NSObject,SaxDTDHandler>)dtdHandler {
// FIXME
- return NULL;
+ return nil;
}
- (id<NSObject,SaxErrorHandler>)errorHandler {
- // FIXME
- return NULL;
+ return self->errorHandler;
}
- (id<NSObject,SaxEntityResolver>)entityResolver {
// FIXME
- return NULL;
+ return nil;
}
- (void)setPrefixURI:(NSString *)_uri {
- (void)setAttributeMapping:(NSDictionary *)_mapping
forElement:(NSString *)_element
{
- if (!_element)
+ if (_element == nil)
_element = @"";
[attributeMapping setObject:_mapping forKey:_element];
}
-- (void)setSubItemMapping:(NSArray *)_mapping
- forElement:(NSString *)_element
-{
+- (void)setSubItemMapping:(NSArray *)_mapping forElement:(NSString *)_element {
[subItemMapping setObject:_mapping forKey:_element];
}
/* 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)]];
- }
- }
+ //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping);
+ ret = _tagName;
+
+ /*
+ 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;
}
-- (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 {
- NSArray *element = [cardStack lastObject];
- SaxAttributes *attrs = [element objectAtIndex:2];
- [self _addAttribute:_attribute value:_value toAttrs:attrs];
-}
-
- (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName {
+ NSDictionary *tagMap;
NSString *mappedName;
- mappedName = [[self->attributeMapping objectForKey:_tagName]
- objectForKey:_attrName];
- if (!mappedName) {
- mappedName = [[self->attributeMapping objectForKey:
- [self _mapTagName:_tagName]]
- objectForKey:_attrName];
- }
- if (!mappedName) {
- mappedName = [[self->attributeMapping objectForKey:@""]
- objectForKey:_attrName];
- }
- if (!mappedName)
- mappedName = _attrName;
+ /* 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;
- return mappedName;
+ /* 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 the name as-is */
+ return _attrName;
}
- (void)_parseAttr:(NSString *)_attr
attrName = [[_attr substringToIndex:r.location] uppercaseString];
left = NSMaxRange(r);
right = [_attr length] - 1;
- if(left < right) {
- if(([_attr characterAtIndex:left] == '"') &&
+ if (left < right) {
+ if (([_attr characterAtIndex:left] == '"') &&
([_attr characterAtIndex:right] == '"'))
{
left += 1;
attrValue = [_attr substringFromIndex:left];
}
}
- else if(left == right) {
+ else if (left == right) {
attrValue = [_attr substringFromIndex:left];
}
else {
*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];
+
+ attributes = [[NSMutableDictionary alloc] initWithCapacity:4];
+ retAttrs = [[[SaxAttributes alloc] init] autorelease];
+
attrEnum = [_attrs objectEnumerator];
- while ((curAttr = [attrEnum nextObject])) {
+ while ((curAttr = [attrEnum nextObject]) != nil) {
[self _parseAttr:curAttr
forTag:_tagName
intoAttr:&mappedAttr
intoValue:&mappedValue];
- if ((oldValue = [attributes objectForKey:mappedAttr])) {
+ if ((oldValue = [attributes objectForKey:mappedAttr]) != nil) {
NSString *val;
+
/* ZNeK: duh! */
- val = [NSString stringWithFormat:@"%@ %@",oldValue, mappedValue];
+ // TODO: hh asks: what does 'duh' is supposed to mean?
+ val = [[NSString alloc] initWithFormat:@"%@ %@",oldValue, mappedValue];
[attributes setObject:val forKey:mappedAttr];
+ [val release];
}
else
[attributes setObject:mappedValue forKey:mappedAttr];
}
attrEnum = [attributes keyEnumerator];
- while ((curAttr = [attrEnum nextObject])) {
- [self _addAttribute:curAttr
- value:[attributes objectForKey:curAttr]
- toAttrs:retAttrs];
+ while ((curAttr = [attrEnum nextObject]) != nil) {
+ /*
+ TODO: values are not always mapped to CDATA! Eg in the dawson draft:
+ | TYPE for TEL | tel.type | NMTOKENS | 'VOICE' |
+ | TYPE for EMAIL | email.type | NMTOKENS | 'INTERNET' |
+ | TYPE for PHOTO,| img.type | CDATA | REQUIRED |
+ | and LOGO | | | |
+ | TYPE for SOUND | aud.type | CDATA | REQUIRED |
+ | VALUE | value | NOTATION | See elements |
+ */
+
+ [retAttrs addAttribute:curAttr uri:self->prefixURI rawName:curAttr
+ type:@"CDATA" value:[attributes objectForKey:curAttr]];
}
[attributes release];
return retAttrs;
}
-- (NSArray *)_beginTag:(NSString *)_tagName
- withAttrs:(id<NSObject,SaxAttributes>)_attrs
+- (VSSaxTag *)_beginTag:(NSString *)_tagName group:(NSString *)_group
+ withAttrs:(SaxAttributes *)_attrs
{
- NSArray *tag = [NSArray arrayWithObjects:@"BEGIN",_tagName,_attrs,NULL];
+ VSSaxTag *tag;
+
+ tag = [VSSaxTag beginTag:_tagName group:_group 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 {
+- (void)_addSubItems:(NSArray *)_items group:(NSString *)_group
+ 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]];
+ [self _beginTag:subTag group:_group withAttrs:nil];
+ if ([subContent length] > 0) {
+ VSSaxTag *a;
+
+ a = [(VSSaxTag*)[VSSaxTag alloc] initWithContentString:subContent];
+ if (a != nil) {
+ [self->elementList addObject:a];
+ [a release];
+ }
+ }
[self _endTag:subTag];
}
}
-- (void)_dataTag:(NSString *)_tagName
- withAttrs:(id<NSObject,SaxAttributes>)_attrs
+- (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];
- }
- else {
- [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]];
+ /*
+ 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 <vcard version=""> (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];
+
+ if ([_content length] > 0) {
+ if ((subItems = [self->subItemMapping objectForKey:_tagName]) != nil) {
+ [self _addSubItems:subItems group:_group withData:_content];
+ }
+ else {
+ VSSaxTag *a;
+
+ a = [(VSSaxTag *)[VSSaxTag alloc] initWithContentString:_content];
+ if (a != nil) {
+ [self->elementList addObject:a];
+ [a release];
}
}
- [self _endTag:_tagName];
}
+
+ [self _endTag:_tagName];
+}
+
+/* report events for collected elements */
+
+- (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)_eventsForElements {
+- (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:
+ <vcard version="3.0">
+ */
NSEnumerator *enu;
- NSArray *obj;
- NSString *type;
- NSString *name;
- unichar *chardata;
- id<NSObject,SaxAttributes> attrs;
-
- enu = [elementList objectEnumerator];
- while ((obj = [enu nextObject])) {
- type = [obj objectAtIndex:0];
- name = [obj objectAtIndex:1];
+ VSSaxTag *tagToReport;
+ NSString *lastGroup;
+
+ lastGroup = nil;
+ enu = [self->elementList objectEnumerator];
+ 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 ([obj count] > 2)
- attrs = [obj objectAtIndex:2];
- else
- attrs = nil;
-
- if ([type isEqualToString:@"BEGIN"]) {
- [self->contentHandler startElement:name
+ if ([tagToReport isStartTag]) {
+ [self->contentHandler startElement:[tagToReport tagName]
namespace:self->prefixURI
- rawName:name
- attributes:attrs];
- }
- else if ([type isEqualToString:@"END"]) {
- [self->contentHandler endElement:name
+ rawName:[tagToReport tagName]
+ attributes:tagToReport->attrs];
+ }
+ else if ([tagToReport isEndTag]) {
+ [self->contentHandler endElement:[tagToReport tagName]
namespace:self->prefixURI
- rawName:name];
+ rawName:[tagToReport tagName]];
}
else {
- unsigned len = [name length];
- chardata = malloc(len * sizeof(unichar));
- [name getCharacters:chardata range:NSMakeRange(0, len)];
- [self->contentHandler characters:chardata length:len];
- if (chardata)
- free(chardata);
+ [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;
+
+ e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
+ reason:_warn
+ userInfo:nil];
+ [self->errorHandler warning:e];
+}
+
+/* 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 {
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);
- }
+ if (r.length == 0) {
+ [self error:[@"got an improper content line! ->\n"
+ stringByAppendingString:_line]];
return;
}
-
- tagName = [[_line substringToIndex:r.location] uppercaseString];
- tagAttributes = [[NSMutableArray alloc] init];
-
- /* possible shortcut: if we spotted a ':', we don't have to do "expensive"
+
+ /* 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"
argument scanning/processing.
*/
- if([_line characterAtIndex:r.location] != ':') {
- BOOL isAtEnd = NO, isInDquote = NO;
- unsigned start = NSMaxRange(r);
-
+ if ([_line characterAtIndex:r.location] != ':') {
+ BOOL isAtEnd = NO;
+ BOOL isInDquote = NO;
+ unsigned start;
+
+ start = NSMaxRange(r);
todoRange = NSMakeRange(start, length - start);
while(!isAtEnd) {
BOOL skip = YES;
r = [_line rangeOfCharacterFromSet:colonSemicolonAndDquoteCharSet
options:0
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];
+ if (r.length == 0 || r.location == 0) {
+ [self error:[@"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)] != '\\') {
+ if ([_line characterAtIndex:(r.location - 1)] != '\\') {
unichar delimiter;
NSRange copyRange;
delimiter = [_line characterAtIndex:r.location];
- if(delimiter == '\"') {
+ if (delimiter == '\"') {
/* not a real delimiter - toggle isInDquote for proper escaping */
isInDquote = !isInDquote;
}
else {
- if(!isInDquote) {
+ if (!isInDquote) {
/* is a delimiter, which one? */
skip = NO;
- if(delimiter == ':') {
+ if (delimiter == ':') {
isAtEnd = YES;
}
copyRange = NSMakeRange(start, r.location - start);
[tagAttributes addObject:[_line substringWithRange:copyRange]];
- if(!isAtEnd) {
+ if (!isAtEnd) {
/* adjust start, todoRange */
start = NSMaxRange(r);
todoRange = NSMakeRange(start, length - start);
}
}
}
- if(skip) {
+ if (skip) {
/* adjust todoRange */
unsigned offset = NSMaxRange(r);
todoRange = NSMakeRange(offset, length - offset);
}
}
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"]) {
- id 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 = [[self->cardStack lastObject] objectAtIndex:1];
- 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);
- }
- /* 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 {
- if(debugOn) {
- NSLog(@"%s found end tag '%@' without any open tags left?!",
- __PRETTY_FUNCTION__,
- mtName);
- }
- }
- [self _endTag:mtName];
- [self->cardStack removeLastObject];
- 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]
- withAttrs:[self _mapAttrs:tagAttributes forTag:tagName]
- andContent:tagValue];
+ // TODO: make this more generic, this one is used with Outlook vCards
+ if ([tagAttributes containsObject:@"ENCODING=QUOTED-PRINTABLE"]) {
+ // TODO: QP is charset specific! The one below decodes in Unicode!
+ tagValue = [tagValue stringByDecodingQuotedPrintable];
+ [tagAttributes removeObject:@"ENCODING=QUOTED-PRINTABLE"];
+ }
+
+ [self _reportContentAsTag:[self _mapTagName:tagName]
+ group:[self _groupFromTagName:tagName]
+ 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];
-
- length = [_rawString length];
- /* RFC2445:
+}
+- (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
*/
- r = NSMakeRange(0, 0);
- /* probably too optimistic */
- line = [[NSMutableString alloc] initWithCapacity:75 + 2];
+ NSMutableString *line;
+ unsigned pos, length;
+ NSRange r;
- for(pos = 0; pos < length; pos++) {
- unichar c = [_rawString characterAtIndex:pos];
+ [self reportDocStart];
+
+ /* start parsing */
+
+ length = [_rawString length];
+ r = NSMakeRange(0, 0);
+ line = [[NSMutableString alloc] initWithCapacity:75 + 2];
+
+ 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') {
+ 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;
- if(!isAtEndOfLine) {
+ if (((length - 1) - pos) >= 2) {
+ 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) {
+ if (r.length > 0) {
[line appendString:[_rawString substringWithRange:r]];
}
/* unfold */
r = NSMakeRange(pos + 1, 0); /* begin new range */
}
}
- if(isAtEndOfLine) {
+ if (isAtEndOfLine) {
/* assemble part of line up to pos */
- if(r.length > 0) {
+ if (r.length > 0) {
[line appendString:[_rawString substringWithRange:r]];
}
[self _parseLine:line];
}
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 */
+ 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;
- if(!isAtEndOfLine) {
+ if (((length - 1) - pos) >= 1) {
+ 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) {
[line appendString:[_rawString substringWithRange:r]];
}
/* unfold */
r = NSMakeRange(pos + 1, 0); /* begin new range */
}
}
- if(isAtEndOfLine) {
+ if (isAtEndOfLine) {
/* assemble part of line up to pos */
- if(r.length > 0) {
+ if (r.length > 0) {
[line appendString:[_rawString substringWithRange:r]];
}
[self _parseLine:line];
r.length += 1;
}
}
- if(r.length > 0) {
- if(debugOn) {
- NSLog(@"%s Last line of iCal string is not properly terminated!",
- __PRETTY_FUNCTION__);
- }
+ if (r.length > 0) {
+ [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__);
- }
+
+ if ([self->cardStack count] != 0) {
+ [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]; line = nil;
+
+ [self reportDocEnd];
+}
- [line release];
- [self->contentHandler endPrefixMapping:@""];
- [self->contentHandler endDocument];
+/* 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 {
+- (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
if (debugOn)
- NSLog(@"%s: parse: %@", __PRETTY_FUNCTION__, _source);
+ NSLog(@"%s: parse: %@ (sysid=%@)", __PRETTY_FUNCTION__, _source, _sysId);
if ([_source isKindOfClass:[NSURL class]]) {
- if (debugOn)
- NSLog(@"%s: trying to load URL...",__PRETTY_FUNCTION__);
+ if (_sysId == nil) _sysId = [_source absoluteString];
+
+ if (debugOn) {
+ NSLog(@"%s: trying to load URL: %@ (sysid=%@)",__PRETTY_FUNCTION__,
+ _source, _sysId);
+ }
+
+ // TODO: remember encoding of source
_source = [_source resourceDataUsingCache:NO];
}
if ([_source isKindOfClass:[NSData class]]) {
- // FIXME: Data is not always utf-8.....
- if (debugOn)
- NSLog(@"%s: trying to decode data...",__PRETTY_FUNCTION__);
- _source = [[[NSString alloc]
- initWithData:_source encoding:NSUTF8StringEncoding]
- autorelease];
+ if (_sysId == nil) _sysId = @"<data>";
+ if ((_source = [self sourceForData:_source systemId:_sysId]) == nil)
+ return;
}
-
- if ([_source isKindOfClass:[NSString class]]) {
- if (debugOn)
- NSLog(@"%s: trying to parse string...",__PRETTY_FUNCTION__);
- [self _parseString:_source];
- }
- else {
+
+ if (![_source isKindOfClass:[NSString class]]) {
+ SaxParseException *e;
+ NSString *s;
+
if (debugOn)
NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source);
- // FIXME: Return Error
+
+ s = [@"cannot handle data-source: " stringByAppendingString:
+ [_source description]];
+ e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
+ reason:s
+ userInfo:nil];
+
+ [self->errorHandler fatalError:e];
+ return;
+ }
+
+ /* ensure consistent state */
+
+ [self->cardStack removeAllObjects];
+ [self->elementList removeAllObjects];
+
+ /* start parsing */
+
+ if (debugOn) {
+ NSLog(@"%s: trying to parse string (0x%08X,len=%d) ...",
+ __PRETTY_FUNCTION__, _source, [_source length]);
}
+ if (_sysId == nil) _sysId = @"<string>";
+ [self _parseString:_source];
+
+ /* tear down */
+
+ [self->cardStack removeAllObjects];
+ [self->elementList removeAllObjects];
}
-- (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
- [self parseFromSource:_source];
+- (void)parseFromSource:(id)_source {
+ [self parseFromSource:_source systemId:nil];
}
- (void)parseFromSystemId:(NSString *)_sysId {
NSURL *url;
- if ((url = [NSURL URLWithString:_sysId]))
- [self parseFromSource:url systemId:_sysId];
+ if ([_sysId rangeOfString:@"://"].length == 0) {
+ /* seems to be a path, path to be a proper URL */
+ url = [NSURL fileURLWithPath:_sysId];
+ }
+ else {
+ /* Note: Cocoa NSURL doesn't complain on "/abc/def" like input! */
+ url = [NSURL URLWithString:_sysId];
+ }
+
+ if (url == nil) {
+ SaxParseException *e;
+
+ e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
+ reason:@"cannot handle system-id"
+ userInfo:nil];
+ [self->errorHandler fatalError:e];
+ return;
+ }
+
+ [self parseFromSource:url systemId:_sysId];
}
/* debugging */