sogo/SOPE/NGCards/versitCardsSaxDriver/VSSaxDriver.m

1221 lines
31 KiB
Objective-C

/*
Copyright (C) 2003-2004 Max Berger
Copyright (C) 2004-2005 OpenGroupware.org
This file is part of versitCardsSaxDriver, written for the OpenGroupware.org
project (OGo).
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.
*/
/* FIXME: this class is badly designed. It is expected to feed
NGVCardSaxHandler with correct values but it won't handle escaped
commas. Also, it should handle CardGroups and CardElements
correctly: treat the former as open/close tags and the latter as
simple tags. Wrt that, the methods startGroupElement/endGroupElement
are not expected in a sax handler... this is all wrong. */
#import "VSSaxDriver.h"
#import <SaxObjC/SaxException.h>
#import <NGExtensions/NGQuotedPrintableCoding.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+Encoding.h>
#import <NGCards/NSString+NGCards.h>
#import "common.h"
@interface VSSaxTag : NSObject
{
@private;
char type;
NSString *tagName;
NSString *group;
@public;
SaxAttributes *attrs;
unichar *data;
unsigned int datalen;
BOOL groupElement;
}
+ (id) beginTag: (NSString *) _tag
group: (NSString *) _group
attributes: (SaxAttributes *) _attrs;
- (id) initEndTag: (NSString *) _tag;
- (id) initWithContentString: (NSString *) _data;
- (void) setGroupElement: (BOOL) aBool;
- (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 new] autorelease];
tag->type = 'B';
tag->tagName = [_tag copy];
tag->group = [_group copy];
tag->attrs = [_attrs retain];
return tag;
}
- (id) init
{
if ((self = [super init]))
{
groupElement = NO;
data = NULL;
}
return self;
}
- (id) initEndTag: (NSString *) _tag
{
type = 'E';
ASSIGN (tagName, _tag);
return self;
}
- (id) initWithContentString: (NSString *) _data
{
if (!_data)
{
[self release];
return nil;
}
datalen = [_data length];
data = NSZoneCalloc (NULL, datalen + 1, sizeof(unichar));
[_data getCharacters: data range: NSMakeRange(0, datalen)];
return self;
}
- (void) setGroupElement: (BOOL) aBool
{
groupElement = aBool;
}
- (void) dealloc {
if (data) NSZoneFree (NULL, data);
[group release];
[tagName release];
[attrs release];
[super dealloc];
}
- (char) tagType
{
return type;
}
/* accessors */
- (NSString *) tagName {
return tagName;
}
- (NSString *) group {
return group;
}
- (BOOL) isStartTag {
return type == 'B' ? YES : NO;
}
- (BOOL) isEndTag {
return type == 'E' ? YES : NO;
}
- (BOOL) isTag {
return (type == 'B' || 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 *colonSemicolonAndDquoteCharSet = nil;
static NSCharacterSet *whitespaceCharSet = nil;
+ (void) initialize
{
static BOOL didInit = NO;
NSUserDefaults *ud;
if (didInit)
return;
didInit = YES;
ud = [NSUserDefaults standardUserDefaults];
debugOn = [ud boolForKey: @"VSSaxDriverDebugEnabled"];
dotCharSet =
[[NSCharacterSet characterSetWithCharactersInString: @"."] retain];
equalSignCharSet =
[[NSCharacterSet characterSetWithCharactersInString: @"="] retain];
commaCharSet =
[[NSCharacterSet characterSetWithCharactersInString: @","] retain];
colonAndSemicolonCharSet =
[[NSCharacterSet characterSetWithCharactersInString: @":;"] retain];
colonSemicolonAndDquoteCharSet =
[[NSCharacterSet characterSetWithCharactersInString: @":;\""] retain];
whitespaceCharSet =
[[NSCharacterSet whitespaceCharacterSet] retain];
}
- (id) init {
if ((self = [super init]))
{
prefixURI = @"";
cardStack = [[NSMutableArray alloc] initWithCapacity: 4];
elementList = [[NSMutableArray alloc] initWithCapacity: 8];
}
return self;
}
- (void) dealloc
{
[contentHandler release];
[errorHandler release];
[prefixURI release];
[cardStack release];
[elementList release];
[super dealloc];
}
/* accessors */
- (void) setFeature: (NSString *) _name to: (BOOL) _value
{
}
- (BOOL) feature: (NSString *) _name
{
return NO;
}
- (void) setProperty: (NSString *) _name to: (id) _value
{
}
- (id) property: (NSString *) _name
{
return nil;
}
/* handlers */
- (void) setContentHandler: (id<NSObject,SaxContentHandler>) _handler
{
ASSIGN(contentHandler, _handler);
}
- (void) setDTDHandler: (id<NSObject,SaxDTDHandler>) _handler
{
// FIXME
}
- (void) setErrorHandler: (id<NSObject,SaxErrorHandler>) _handler
{
ASSIGN(errorHandler, _handler);
}
- (void) setEntityResolver: (id<NSObject,SaxEntityResolver>) _handler
{
// FIXME
}
- (id<NSObject,SaxContentHandler>) contentHandler
{
return contentHandler;
}
- (id<NSObject,SaxDTDHandler>) dtdHandler
{
// FIXME
return nil;
}
- (id<NSObject,SaxErrorHandler>) errorHandler
{
return errorHandler;
}
- (id<NSObject,SaxEntityResolver>) entityResolver
{
// FIXME
return nil;
}
- (void) setPrefixURI: (NSString *) _uri
{
ASSIGNCOPY(prefixURI, _uri);
}
- (NSString *) prefixURI
{
return prefixURI;
}
/* parsing */
- (NSString *) _groupFromTagName: (NSString *) _tagName
{
NSRange r;
r = [_tagName rangeOfCharacterFromSet: dotCharSet];
if (!r.length)
return nil;
return [_tagName substringToIndex: r.location];
}
- (NSString *) _mapTagName: (NSString *) _tagName
{
NSString *ret;
NSRange r;
//NSLog(@"Unknown Key: %@ in %@",_tagName,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) _parseAttr: (NSString *) _attr
intoAttr: (NSString **) attr_
intoValue: (NSString **) value_
{
NSRange r;
NSString *attrName, *attrValue, *upperAttr;
r = [_attr rangeOfCharacterFromSet: equalSignCharSet];
if (r.length > 0)
{
attrName = [[_attr substringToIndex: r.location] uppercaseString];
attrValue = [_attr substringFromIndex: NSMaxRange(r)];
}
else
{
upperAttr = [_attr uppercaseString];
if ([upperAttr isEqualToString: @"QUOTED-PRINTABLE"]
|| [upperAttr isEqualToString: @"BASE64"]
|| [upperAttr isEqualToString: @"B"])
attrName = @"ENCODING";
else
attrName = @"TYPE";
attrValue = _attr;
}
#if 0
// ZNeK: what's this for?
r = [attrValue rangeOfCharacterFromSet: commaCharSet];
while (r.length > 0)
{
[attrValue replaceCharactersInRange: r withString: @" "];
r = [attrValue rangeOfCharacterFromSet: commaCharSet];
}
#endif
*attr_ = attrName;
*value_ = attrValue;
}
- (SaxAttributes *) _mapAttrs: (NSArray *) _attrs
forTag: (NSString *) _tagName
{
SaxAttributes *retAttrs;
NSEnumerator *attrEnum, *values;
NSString *curAttr, *mappedAttr, *mappedValue, *curValue;
/*
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 |
*/
if (_attrs && [_attrs count] > 0)
{
retAttrs = [[SaxAttributes alloc] init];
[retAttrs autorelease];
attrEnum = [_attrs objectEnumerator];
curAttr = [attrEnum nextObject];
while (curAttr)
{
[self _parseAttr: curAttr
intoAttr: &mappedAttr intoValue: &mappedValue];
values = [[mappedValue asCardAttributeValues] objectEnumerator];
curValue = [values nextObject];
while (curValue)
{
[retAttrs addAttribute: mappedAttr
uri: prefixURI
rawName: mappedAttr
type: @"CDATA"
value: curValue];
curValue = [values nextObject];
}
curAttr = [attrEnum nextObject];
}
}
else
retAttrs = nil;
return retAttrs;
}
- (VSSaxTag *) _beginTag: (NSString *) _tagName
group: (NSString *) _group
withAttrs: (SaxAttributes *) _attrs
{
VSSaxTag *tag;
tag = [VSSaxTag beginTag: [_tagName uppercaseString]
group: _group attributes: _attrs];
[elementList addObject: tag];
return tag;
}
- (void) _endTag: (NSString *) _tagName
{
VSSaxTag *tag;
tag = [[VSSaxTag alloc] initEndTag: _tagName];
[elementList addObject: tag];
[tag release]; tag = nil;
}
- (void) _endGroupElementTag: (NSString *) _tagName
{
VSSaxTag *tag;
tag = [[VSSaxTag alloc] initEndTag: _tagName];
[elementList addObject: tag];
[tag setGroupElement: YES];
[tag release]; tag = nil;
}
- (void) _reportContentAsTag: (NSString *) _tagName
group: (NSString *) _group
withAttrs: (SaxAttributes *) _attrs
andContent: (NSString *) _content
{
VSSaxTag *a;
NSString *testContent;
/* This is called for all non-BEGIN|END types. */
testContent = [_content stringByReplacingString: @";" withString: @""];
if ([[testContent stringByTrimmingSpaces] length] > 0)
{
[self _beginTag: _tagName group: _group withAttrs: _attrs];
a = [(VSSaxTag *)[VSSaxTag alloc] initWithContentString: _content];
if (a)
{
[elementList addObject: a];
[a release];
}
[self _endTag: _tagName];
}
}
/* report events for collected elements */
- (void) reportStartGroup: (NSString *) _group
{
SaxAttributes *attrs;
attrs = [[SaxAttributes alloc] init];
[attrs addAttribute: @"name" uri: prefixURI rawName: @"name"
type: @"CDATA" value: _group];
[contentHandler startElement: @"group"
namespace: prefixURI
rawName: @"group"
attributes: attrs];
[attrs release];
}
- (void) reportEndGroup
{
[contentHandler endElement: @"group"
namespace: prefixURI
rawName: @"group"];
}
- (void) reportStartContainer: (NSString *) _container
{
SaxAttributes *attrs;
attrs = [[SaxAttributes alloc] init];
[attrs addAttribute: @"name"
uri: prefixURI
rawName: @"name"
type: @"CDATA"
value: _container];
[contentHandler startElement: @"container"
namespace: prefixURI
rawName: @"container"
attributes: attrs];
[attrs release];
}
- (void) reportEndContainer
{
[contentHandler endElement: @"container"
namespace: prefixURI
rawName: @"container"];
}
- (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;
VSSaxTag *tagToReport;
NSString *lastGroup, *tg, *tagName;
lastGroup = nil;
enu = [elementList objectEnumerator];
tagToReport = [enu nextObject];
while (tagToReport)
{
tagName = [tagToReport tagName];
if ([tagToReport isStartTag])
{
tg = [tagToReport group];
if (![lastGroup isEqualToString: tg]
&& lastGroup != tg)
{
if (lastGroup) [self reportEndGroup];
ASSIGNCOPY(lastGroup, tg);
if (lastGroup) [self reportStartGroup: lastGroup];
}
}
if ([tagToReport isStartTag])
{
if (tagToReport->groupElement)
[self reportStartContainer: tagName];
else
[contentHandler startElement: tagName
namespace: prefixURI
rawName: tagName
attributes: tagToReport->attrs];
}
else if ([tagToReport isEndTag])
{
if (tagToReport->groupElement)
[self reportEndContainer];
else
[contentHandler endElement: tagName
namespace: prefixURI
rawName: tagName];
}
else
[contentHandler characters: tagToReport->data
length: tagToReport->datalen];
tagToReport = [enu nextObject];
}
/* flush event group */
[elementList removeAllObjects];
/* close open groups */
if (lastGroup)
{
[self reportEndGroup];
[lastGroup release];
lastGroup = nil;
}
}
/* errors */
- (void) reportError: (NSString *) _text
{
SaxParseException *e;
e = (id)[SaxParseException exceptionWithName: @"SaxParseException"
reason: _text
userInfo: nil];
[errorHandler error: e];
}
- (void) warn: (NSString *) _warn
{
SaxParseException *e;
e = (id)[SaxParseException exceptionWithName: @"SaxParseException"
reason: _warn
userInfo: nil];
[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]];
[tag setGroupElement: YES];
[cardStack addObject: tag];
}
- (void) _endComponent: (NSString *) tagName
value: (NSString *) tagValue
{
NSString *mtName;
int max;
mtName = [[self _mapTagName: tagValue] uppercaseString];
if ([cardStack count] > 0)
{
NSString *expectedName;
expectedName = [(VSSaxTag *)[cardStack lastObject] tagName];
if (![mtName isEqualToString: 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 reportError: s];
/* probably futile attempt to parse anyways */
if (debugOn)
{
NSLog(@"%s trying to fix previous error by inserting bogus end "
@"tag.",
__PRETTY_FUNCTION__);
}
[self _endGroupElementTag: expectedName];
[cardStack removeLastObject];
}
}
else
{
// TOOD: generate error?
[self reportError: [@"found end tag without any open tags left: "
stringByAppendingString: mtName]];
}
[self _endGroupElementTag: mtName];
max = [cardStack count];
if (max > 0)
{
[cardStack removeLastObject];
max--;
}
else
[self errorWithFormat: @"serious inconsistency among begin/end tags"];
/* report parsed elements */
if (max == 0)
[self reportQueuedTags];
}
- (void) _parseLine: (NSString *) _line
{
NSString *tagName, *tagValue;
NSMutableArray *tagAttributes;
NSRange r, todoRange;
unsigned length;
#if 0
if (debugOn)
NSLog(@"%s: parse line: '%@'", __PRETTY_FUNCTION__, _line);
#endif
length = [_line length];
todoRange = NSMakeRange(0, length);
r = [_line rangeOfCharacterFromSet: colonAndSemicolonCharSet
options: 0
range: todoRange];
/* is line well-formed? */
if (!r.length || !r.location)
{
#if 0
NSLog(@"todo-range: %i-%i, range: %i-%i, length %i, str-class %@",
todoRange.location, todoRange.length,
r.location, r.length,
length, NSStringFromClass([_line class]));
#endif
[self reportError:
[@"got an improper content line! (did not find colon) ->\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];
if (debugOn && ([tagName length] == 0))
{
[self reportError: [@"got an improper content line! ->\n"
stringByAppendingString: _line]];
return;
}
/*
possible shortcut: if we spotted a ': ', we don't have to do "expensive"
argument scanning/processing.
*/
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;
/* scan for parameters */
r = [_line rangeOfCharacterFromSet: colonSemicolonAndDquoteCharSet
options: 0
range: todoRange];
/* is line well-formed? */
if (!r.length || !r.location)
{
[self reportError: [@"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;
NSRange copyRange;
delimiter = [_line characterAtIndex: r.location];
if (delimiter == '\"')
{
/* not a real delimiter - toggle isInDquote for proper escaping */
isInDquote = !isInDquote;
}
else
{
if (!isInDquote)
{
/* is a delimiter, which one? */
skip = NO;
if (delimiter == ':')
{
isAtEnd = YES;
}
copyRange = NSMakeRange(start, r.location - start);
[tagAttributes addObject: [_line substringWithRange: copyRange]];
if (!isAtEnd)
{
/* adjust start, todoRange */
start = NSMaxRange(r);
todoRange = NSMakeRange(start, length - start);
}
}
}
}
if (skip)
{
/* adjust todoRange */
unsigned offset = NSMaxRange(r);
todoRange = NSMakeRange(offset, length - offset);
}
}
}
tagValue = [_line substringFromIndex: NSMaxRange(r)];
if (debugOn && ([tagName length] == 0))
{
NSLog(@"%s: missing tagname in line: '%@'",
__PRETTY_FUNCTION__, _line);
}
/*
At this point we have:
name: 'BEGIN', 'TEL', 'EMAIL', 'ITEM1.ADR' etc
value: ';;;Magdeburg;;;Germany'
attributes: ("type=INTERNET", "type=HOME", "type=pref")
*/
#if 0
# warning DEBUG LOG ENABLED
NSLog(@"TAG: %@, value %@ attrs %@",
tagName, tagValue, tagAttributes);
#endif
/* process tag */
if ([tagName isEqualToString: @"BEGIN"])
{
if ([tagAttributes count] > 0)
[self warn: @"Losing unexpected parameters of BEGIN line."];
[self _beginComponentWithValue: tagValue];
}
else if ([tagName isEqualToString: @"END"])
{
if ([tagAttributes count] > 0)
[self warn: @"Losing unexpected parameters of END line."];
[self _endComponent: tagName value: tagValue];
}
else
{
/* a regular content tag */
/*
check whether the tga value is encoded in quoted printable,
this one is used with Outlook vCards (see data/ for examples)
*/
// TODO: make the encoding check more generic
if ([tagAttributes containsObject: @"ENCODING=QUOTED-PRINTABLE"])
{
NSString *charset, *v;
NSData *d;
int i;
d = [[tagValue dataUsingEncoding: NSASCIIStringEncoding]
dataByDecodingQuotedPrintable];
// Let's find the charset.
charset = @"utf-8";
for (i = 0; i < [tagAttributes count]; i++)
{
v = [[tagAttributes objectAtIndex: i] lowercaseString];
if ([v hasPrefix: @"charset"])
{
charset = [v substringFromIndex: 8];
break;
}
}
tagValue = [NSString stringWithData: d usingEncodingNamed: charset];
[tagAttributes removeObject: @"ENCODING=QUOTED-PRINTABLE"];
}
[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
{
[contentHandler startDocument];
[contentHandler startPrefixMapping: @"" uri: prefixURI];
}
- (void) reportDocEnd
{
[contentHandler endPrefixMapping: @""];
[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];
/* 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')
{
BOOL isAtEndOfLine = YES;
/* test for folding first */
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)
{
[line appendString: [_rawString substringWithRange: r]];
}
/* unfold */
pos += 2;
r = NSMakeRange(pos + 1, 0); /* begin new range */
}
}
if (isAtEndOfLine)
{
/* assemble part of line up to pos */
if (r.length > 0)
{
[line appendString: [_rawString substringWithRange: r]];
}
[self _parseLine: line];
/* reset line */
[line deleteCharactersInRange: NSMakeRange(0, [line length])];
pos += 1;
r = NSMakeRange(pos + 1, 0); /* begin new range */
}
}
}
else
{
/* garbled last line! */
[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;
ws = [_rawString characterAtIndex: (pos + 1)];
isAtEndOfLine = [whitespaceCharSet characterIsMember: ws] ? NO : YES;
if (!isAtEndOfLine)
{
/* assemble part of line up to pos */
if (r.length > 0)
{
[line appendString: [_rawString substringWithRange: r]];
}
/* unfold */
pos += 1;
r = NSMakeRange(pos + 1, 0); /* begin new range */
}
}
if (isAtEndOfLine)
{
/* assemble part of line up to pos */
if (r.length > 0)
{
[line appendString: [_rawString substringWithRange: r]];
}
[self _parseLine: line];
/* reset line */
[line deleteCharactersInRange: NSMakeRange(0, [line length])];
r = NSMakeRange(pos + 1, 0); /* begin new range */
}
}
else
{
r.length += 1;
}
}
if (r.length > 0)
{
[self warn: @"Last line of parse string is not properly terminated!"];
[line appendString: [_rawString substringWithRange: r]];
[self _parseLine: line];
}
if ([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!"];
[cardStack removeAllObjects]; // clean up
}
[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%p,len=%d) ...",
__PRETTY_FUNCTION__, _data, (int)[_data length]);
}
if ((len = [_data length]) == 0)
{
e = (id)[SaxParseException exceptionWithName: @"SaxIOException"
reason: @"Got no parsing data!"
userInfo: nil];
[errorHandler fatalError: e];
return nil;
}
if (len < 10)
{
e = (id)[SaxParseException exceptionWithName: @"SaxIOException"
reason: @"Input data to short for vCard!"
userInfo: nil];
[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];
// We fallback to ISO-8859-1 string encoding
if (!source)
source = [[[NSString alloc] initWithData: _data encoding: NSISOLatin1StringEncoding]
autorelease];
if (!source)
{
e = (id)[SaxParseException exceptionWithName: @"SaxIOException"
reason: @"Could not convert input to string!"
userInfo: nil];
[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: [NSURL class]])
{
NSURL *url;
url = _source;
if (!_sysId) _sysId = [url absoluteString];
if (debugOn)
{
NSLog(@"%s: trying to load URL: %@ (sysid=%@)",__PRETTY_FUNCTION__,
url, _sysId);
}
// TODO: remember encoding of source
_source = [url resourceDataUsingCache: NO];
if (!_source || ![_source length])
{
SaxParseException *e;
NSString *s;
if (debugOn)
NSLog(@"%s: got no data from url: %@", __PRETTY_FUNCTION__, url);
s = [NSString stringWithFormat: @"got no data from url: %@", url];
e = (id)[SaxParseException exceptionWithName: @"SaxIOException"
reason: s
userInfo: nil];
[errorHandler fatalError: e];
return;
}
}
if ([_source isKindOfClass: [NSData class]])
{
if (!_sysId) _sysId = @"<data>";
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: s
userInfo: nil];
[errorHandler fatalError: e];
return;
}
/* ensure consistent state */
[cardStack removeAllObjects];
[elementList removeAllObjects];
/* start parsing */
if (debugOn)
{
NSLog(@"%s: trying to parse string (0x%p,len=%d) ...",
__PRETTY_FUNCTION__, _source, (int)[_source length]);
}
if (!_sysId) _sysId = @"<string>";
[self _parseString: _source];
/* tear down */
[cardStack removeAllObjects];
[elementList removeAllObjects];
}
- (void) parseFromSource: (id) _source
{
[self parseFromSource: _source systemId: nil];
}
- (void) parseFromSystemId: (NSString *) _sysId
{
NSURL *url;
if (![_sysId rangeOfString: @"://"].length)
{
/* 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)
{
SaxParseException *e;
e = (id)[SaxParseException exceptionWithName: @"SaxIOException"
reason: @"cannot handle system-id"
userInfo: nil];
[errorHandler fatalError: e];
return;
}
[self parseFromSource: url systemId: _sysId];
}
/* debugging */
- (BOOL) isDebuggingEnabled
{
return debugOn;
}
@end /* VersitCardsSaxDriver */