2 Copyright (C) 2003-2004 Max Berger
3 Copyright (C) 2004-2005 OpenGroupware.org
5 This file is part of versitSaxDriver, written for the OpenGroupware.org
8 SOPE is free software; you can redistribute it and/or modify it under
9 the terms of the GNU Lesser General Public License as published by the
10 Free Software Foundation; either version 2, or (at your option) any
13 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
16 License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with SOPE; see the file COPYING. If not, write to the
20 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
24 #include "VSSaxDriver.h"
25 #include "VSStringFormatter.h"
26 #include <SaxObjC/SaxException.h>
27 #include <NGExtensions/NGQuotedPrintableCoding.h>
30 @interface VSSaxTag : NSObject
42 + (id)beginTag:(NSString *)_tag group:(NSString *)_group
43 attributes:(SaxAttributes *)_attrs;
44 - (id)initEndTag:(NSString *)_tag;
46 - (id)initWithContentString:(NSString *)_data;
48 - (NSString *)tagName;
56 @implementation VSSaxTag
58 + (id)beginTag:(NSString *)_tag group:(NSString *)_group
59 attributes:(SaxAttributes *)_attrs
63 tag = [[[self alloc] init] autorelease];
65 tag->tagName = [_tag copy];
66 tag->group = [_group copy];
67 tag->attrs = [_attrs retain];
70 - (id)initEndTag:(NSString *)_tag {
72 self->tagName = [_tag copy];
75 - (id)initWithContentString:(NSString *)_data {
81 self->datalen = [_data length];
82 self->data = calloc(self->datalen + 1, sizeof(unichar));
83 [_data getCharacters:self->data range:NSMakeRange(0, self->datalen)];
88 if (self->data) free(self->data);
89 [self->group release];
90 [self->tagName release];
91 [self->attrs release];
97 - (NSString *)tagName {
100 - (NSString *)group {
105 return self->type == 'B' ? YES : NO;
108 return self->type == 'E' ? YES : NO;
111 return (self->type == 'B' || self->type == 'E') ? YES : NO;
116 @implementation VSSaxDriver
118 static BOOL debugOn = NO;
120 static NSCharacterSet *dotCharSet = nil;
121 static NSCharacterSet *equalSignCharSet = nil;
122 static NSCharacterSet *commaCharSet = nil;
123 static NSCharacterSet *colonAndSemicolonCharSet = nil;
124 static NSCharacterSet *colonSemicolonAndDquoteCharSet = nil;
125 static NSCharacterSet *whitespaceCharSet = nil;
127 static VSStringFormatter *stringFormatter = nil;
130 static BOOL didInit = NO;
137 ud = [NSUserDefaults standardUserDefaults];
138 debugOn = [ud boolForKey:@"VSSaxDriverDebugEnabled"];
141 [[NSCharacterSet characterSetWithCharactersInString:@"."] retain];
143 [[NSCharacterSet characterSetWithCharactersInString:@"="] retain];
145 [[NSCharacterSet characterSetWithCharactersInString:@","] retain];
146 colonAndSemicolonCharSet =
147 [[NSCharacterSet characterSetWithCharactersInString:@":;"] retain];
148 colonSemicolonAndDquoteCharSet =
149 [[NSCharacterSet characterSetWithCharactersInString:@":;\""] retain];
151 [[NSCharacterSet whitespaceCharacterSet] retain];
153 stringFormatter = [VSStringFormatter sharedFormatter];
158 if ((self = [super init])) {
159 self->prefixURI = @"";
160 self->cardStack = [[NSMutableArray alloc] initWithCapacity:4];
161 self->elementList = [[NSMutableArray alloc] initWithCapacity:8];
162 self->attributeMapping = [[NSMutableDictionary alloc] initWithCapacity:8];
163 self->subItemMapping = [[NSMutableDictionary alloc] initWithCapacity:8];
169 [self->contentHandler release];
170 [self->errorHandler release];
171 [self->prefixURI release];
172 [self->cardStack release];
173 [self->elementList release];
174 [self->attributeElements release];
175 [self->elementMapping release];
176 [self->attributeMapping release];
177 [self->subItemMapping release];
183 - (void)setFeature:(NSString *)_name to:(BOOL)_value {
185 - (BOOL)feature:(NSString *)_name {
189 - (void)setProperty:(NSString *)_name to:(id)_value {
191 - (id)property:(NSString *)_name {
197 - (void)setContentHandler:(id<NSObject,SaxContentHandler>)_handler {
198 ASSIGN(self->contentHandler, _handler);
201 - (void)setDTDHandler:(id<NSObject,SaxDTDHandler>)_handler {
205 - (void)setErrorHandler:(id<NSObject,SaxErrorHandler>)_handler {
206 ASSIGN(self->errorHandler, _handler);
208 - (void)setEntityResolver:(id<NSObject,SaxEntityResolver>)_handler {
212 - (id<NSObject,SaxContentHandler>)contentHandler {
213 return self->contentHandler;
216 - (id<NSObject,SaxDTDHandler>)dtdHandler {
221 - (id<NSObject,SaxErrorHandler>)errorHandler {
222 return self->errorHandler;
224 - (id<NSObject,SaxEntityResolver>)entityResolver {
229 - (void)setPrefixURI:(NSString *)_uri {
230 ASSIGNCOPY(self->prefixURI, _uri);
232 - (NSString *)prefixURI {
233 return self->prefixURI;
236 - (void)setAttributeElements:(NSSet *)_elements {
237 ASSIGNCOPY(self->attributeElements, _elements);
239 - (NSSet *)attributeElements {
240 return self->attributeElements;
243 - (void)setElementMapping:(NSDictionary *)_mapping {
244 ASSIGNCOPY(self->elementMapping, _mapping);
246 - (NSDictionary *)elementMapping {
247 return self->elementMapping;
250 - (void)setAttributeMapping:(NSDictionary *)_mapping {
251 [self setAttributeMapping:_mapping forElement:@""];
254 - (void)setAttributeMapping:(NSDictionary *)_mapping
255 forElement:(NSString *)_element
259 [attributeMapping setObject:_mapping forKey:_element];
262 - (void)setSubItemMapping:(NSArray *)_mapping forElement:(NSString *)_element {
263 [subItemMapping setObject:_mapping forKey:_element];
270 - (NSString *)_groupFromTagName:(NSString *)_tagName {
273 r = [_tagName rangeOfCharacterFromSet:dotCharSet];
277 return [_tagName substringToIndex:r.location];
280 - (NSString *)_mapTagName:(NSString *)_tagName {
284 if ((ret = [self->elementMapping objectForKey:_tagName]) != nil)
287 //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping);
291 This is to allow parsing of vCards produced by Apple
293 The dot-notation is described as 'grouping' in RFC 2425, section 5.8.2.
295 r = [_tagName rangeOfCharacterFromSet:dotCharSet];
297 ret = [self _mapTagName:[_tagName substringFromIndex:(r.location + 1)]];
302 - (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName {
303 NSDictionary *tagMap;
304 NSString *mappedName;
306 /* check whether we have a attr-map stored under the element-name */
307 tagMap = [self->attributeMapping objectForKey:_tagName];
308 if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
311 /* check whether we have a attr-map stored under the mapped element-name */
312 tagMap = [self->attributeMapping objectForKey:[self _mapTagName:_tagName]];
313 if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
316 /* check whether we have a global attr-map */
317 tagMap = [self->attributeMapping objectForKey:@""];
318 if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
321 /* return the name as-is */
325 - (void)_parseAttr:(NSString *)_attr
326 forTag:(NSString *)_tagName
327 intoAttr:(NSString **)attr_
328 intoValue:(NSString **)value_
331 NSString *attrName, *attrValue, *mappedName;
333 r = [_attr rangeOfCharacterFromSet:equalSignCharSet];
335 unsigned left, right;
337 attrName = [[_attr substringToIndex:r.location] uppercaseString];
338 left = NSMaxRange(r);
339 right = [_attr length] - 1;
341 if (([_attr characterAtIndex:left] == '"') &&
342 ([_attr characterAtIndex:right] == '"'))
345 r = NSMakeRange(left, right - left);
346 attrValue = [_attr substringWithRange:r];
349 attrValue = [_attr substringFromIndex:left];
352 else if (left == right) {
353 attrValue = [_attr substringFromIndex:left];
365 // ZNeK: what's this for?
366 r = [attrValue rangeOfCharacterFromSet:commaCharSet];
367 while (r.length > 0) {
368 [attrValue replaceCharactersInRange:r withString:@" "];
369 r = [attrValue rangeOfCharacterFromSet:commaCharSet];
373 mappedName = [self _mapAttrName:attrName forTag:_tagName];
375 *value_ = [stringFormatter stringByUnescapingRFC2445Text:attrValue];
378 - (SaxAttributes *)_mapAttrs:(NSArray *)_attrs forTag:(NSString *)_tagName {
379 SaxAttributes *retAttrs;
380 NSEnumerator *attrEnum;
381 NSString *curAttr, *mappedAttr, *mappedValue, *oldValue;
382 NSMutableDictionary *attributes;
384 if (_attrs == nil || [_attrs count] == 0)
387 attributes = [[NSMutableDictionary alloc] initWithCapacity:4];
388 retAttrs = [[[SaxAttributes alloc] init] autorelease];
390 attrEnum = [_attrs objectEnumerator];
391 while ((curAttr = [attrEnum nextObject]) != nil) {
392 [self _parseAttr:curAttr
395 intoValue:&mappedValue];
396 if ((oldValue = [attributes objectForKey:mappedAttr]) != nil) {
400 // TODO: hh asks: what does 'duh' is supposed to mean?
401 val = [[NSString alloc] initWithFormat:@"%@ %@",oldValue, mappedValue];
402 [attributes setObject:val forKey:mappedAttr];
406 [attributes setObject:mappedValue forKey:mappedAttr];
409 attrEnum = [attributes keyEnumerator];
410 while ((curAttr = [attrEnum nextObject]) != nil) {
412 TODO: values are not always mapped to CDATA! Eg in the dawson draft:
413 | TYPE for TEL | tel.type | NMTOKENS | 'VOICE' |
414 | TYPE for EMAIL | email.type | NMTOKENS | 'INTERNET' |
415 | TYPE for PHOTO,| img.type | CDATA | REQUIRED |
417 | TYPE for SOUND | aud.type | CDATA | REQUIRED |
418 | VALUE | value | NOTATION | See elements |
421 [retAttrs addAttribute:curAttr uri:self->prefixURI rawName:curAttr
422 type:@"CDATA" value:[attributes objectForKey:curAttr]];
425 [attributes release];
430 - (VSSaxTag *)_beginTag:(NSString *)_tagName group:(NSString *)_group
431 withAttrs:(SaxAttributes *)_attrs
435 tag = [VSSaxTag beginTag:_tagName group:_group attributes:_attrs];
436 [self->elementList addObject:tag];
440 - (void)_endTag:(NSString *)_tagName {
443 tag = [[VSSaxTag alloc] initEndTag:_tagName];
444 [self->elementList addObject:tag];
445 [tag release]; tag = nil;
448 - (void)_addSubItems:(NSArray *)_items group:(NSString *)_group
449 withData:(NSString *)_content
451 NSEnumerator *itemEnum, *contentEnum;
454 itemEnum = [_items objectEnumerator];
455 contentEnum = [[_content componentsSeparatedByString:@";"] objectEnumerator];
457 while ((subTag = [itemEnum nextObject]) != nil) {
458 NSString *subContent;
460 subContent = [contentEnum nextObject];
462 [self _beginTag:subTag group:_group withAttrs:nil];
463 if ([subContent length] > 0) {
466 a = [(VSSaxTag*)[VSSaxTag alloc] initWithContentString:subContent];
468 [self->elementList addObject:a];
472 [self _endTag:subTag];
476 - (void)_reportContentAsTag:(NSString *)_tagName
477 group:(NSString *)_group
478 withAttrs:(SaxAttributes *)_attrs
479 andContent:(NSString *)_content
482 This is called for all non-BEGIN|END types.
486 _content = [stringFormatter stringByUnescapingRFC2445Text:_content];
488 /* check whether type should be reported as an attribute in XML */
490 if ([self->attributeElements containsObject:_tagName]) {
492 Add tag as an attribute to last component in the cardstack. This is
493 stuff like the "VERSION" type contained in a "BEGIN:VCARD" which will
494 be reported as <vcard version=""> (as an attribute of the container).
498 element = [self->cardStack lastObject];
499 [element->attrs addAttribute:_tagName uri:self->prefixURI
500 rawName:_tagName type:@"CDATA" value:_content];
504 /* report type as an XML tag */
506 [self _beginTag:_tagName group:_group withAttrs:_attrs];
508 if ([_content length] > 0) {
509 if ((subItems = [self->subItemMapping objectForKey:_tagName]) != nil) {
510 [self _addSubItems:subItems group:_group withData:_content];
515 a = [(VSSaxTag *)[VSSaxTag alloc] initWithContentString:_content];
517 [self->elementList addObject:a];
523 [self _endTag:_tagName];
526 /* report events for collected elements */
528 - (void)reportStartGroup:(NSString *)_group {
529 SaxAttributes *attrs;
531 attrs = [[SaxAttributes alloc] init];
532 [attrs addAttribute:@"name" uri:self->prefixURI rawName:@"name"
533 type:@"CDATA" value:_group];
535 [self->contentHandler startElement:@"group" namespace:self->prefixURI
536 rawName:@"group" attributes:attrs];
539 - (void)reportEndGroup {
540 [self->contentHandler endElement:@"group" namespace:self->prefixURI
544 - (void)reportQueuedTags {
546 Why does the parser need the list instead of reporting the events
549 Because some vCard tags like the 'version' are reported as attributes
550 on the container tag. So we have a sequence like:
554 which will get reported as:
555 <vcard version="3.0">
558 VSSaxTag *tagToReport;
562 enu = [self->elementList objectEnumerator];
563 while ((tagToReport = [enu nextObject]) != nil) {
564 if ([tagToReport isStartTag]) {
567 tg = [tagToReport group];
568 if (![lastGroup isEqualToString:tg] && lastGroup != tg) {
569 if (lastGroup != nil) [self reportEndGroup];
570 ASSIGNCOPY(lastGroup, tg);
571 if (lastGroup != nil) [self reportStartGroup:lastGroup];
575 if ([tagToReport isStartTag]) {
576 [self->contentHandler startElement:[tagToReport tagName]
577 namespace:self->prefixURI
578 rawName:[tagToReport tagName]
579 attributes:tagToReport->attrs];
581 else if ([tagToReport isEndTag]) {
582 [self->contentHandler endElement:[tagToReport tagName]
583 namespace:self->prefixURI
584 rawName:[tagToReport tagName]];
587 [self->contentHandler characters:tagToReport->data
588 length:tagToReport->datalen];
592 /* flush event group */
593 [self->elementList removeAllObjects];
595 /* close open groups */
596 if (lastGroup != nil) {
597 [self reportEndGroup];
598 [lastGroup release]; lastGroup = nil;
604 - (void)reportError:(NSString *)_text {
605 SaxParseException *e;
607 e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
610 [self->errorHandler error:e];
612 - (void)warn:(NSString *)_warn {
613 SaxParseException *e;
615 e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
618 [self->errorHandler warning:e];
621 /* parsing raw string */
623 - (void)_beginComponentWithValue:(NSString *)tagValue {
626 tag = [self _beginTag:[self _mapTagName:tagValue]
628 withAttrs:[[[SaxAttributes alloc] init] autorelease]];
629 [self->cardStack addObject:tag];
632 - (void)_endComponent:(NSString *)tagName value:(NSString *)tagValue {
635 mtName = [self _mapTagName:tagValue];
636 if ([self->cardStack count] > 0) {
637 NSString *expectedName;
639 expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName];
640 if (![expectedName isEqualToString:mtName]) {
643 // TODO: rather report an error?
644 // TODO: setup userinfo dict with details
645 s = [NSString stringWithFormat:
646 @"Found end tag '%@' which does not match expected "
648 @" Tag '%@' has not been closed properly. Given "
649 @"document contains errors!",
650 mtName, expectedName, expectedName];
651 [self reportError:s];
653 /* probably futile attempt to parse anyways */
655 NSLog(@"%s trying to fix previous error by inserting bogus end "
657 __PRETTY_FUNCTION__);
659 [self _endTag:expectedName];
660 [self->cardStack removeLastObject];
664 // TOOD: generate error?
665 [self reportError:[@"found end tag without any open tags left: "
666 stringByAppendingString:mtName]];
668 [self _endTag:mtName];
669 [self->cardStack removeLastObject];
671 /* report parsed elements */
673 if ([self->cardStack count] == 0)
674 [self reportQueuedTags];
677 - (void)_parseLine:(NSString *)_line {
678 NSString *tagName, *tagValue;
679 NSMutableArray *tagAttributes;
680 NSRange r, todoRange;
685 NSLog(@"%s: parse line: '%@'", __PRETTY_FUNCTION__, _line);
688 length = [_line length];
689 todoRange = NSMakeRange(0, length);
690 r = [_line rangeOfCharacterFromSet:colonAndSemicolonCharSet
693 /* is line well-formed? */
694 if (r.length == 0 || r.location == 0) {
696 NSLog(@"todo-range: %i-%i, range: %i-%i, length %i, str-class %@",
697 todoRange.location, todoRange.length,
698 r.location, r.length,
699 length, NSStringFromClass([_line class]));
703 [@"got an improper content line! (did not find colon) ->\n"
704 stringByAppendingString:_line]];
708 /* tagname is everything up to a ':' or ';' (value or parameter) */
709 tagName = [[_line substringToIndex:r.location] uppercaseString];
710 tagAttributes = [[NSMutableArray alloc] initWithCapacity:16];
712 if (debugOn && ([tagName length] == 0)) {
713 [self reportError:[@"got an improper content line! ->\n"
714 stringByAppendingString:_line]];
719 possible shortcut: if we spotted a ':', we don't have to do "expensive"
720 argument scanning/processing.
722 if ([_line characterAtIndex:r.location] != ':') {
724 BOOL isInDquote = NO;
727 start = NSMaxRange(r);
728 todoRange = NSMakeRange(start, length - start);
732 /* scan for parameters */
733 r = [_line rangeOfCharacterFromSet:colonSemicolonAndDquoteCharSet
737 /* is line well-formed? */
738 if (r.length == 0 || r.location == 0) {
739 [self reportError:[@"got an improper content line! ->\n"
740 stringByAppendingString:_line]];
741 [tagAttributes release]; tagAttributes = nil;
745 /* first check if delimiter candidate is escaped */
746 if ([_line characterAtIndex:(r.location - 1)] != '\\') {
750 delimiter = [_line characterAtIndex:r.location];
751 if (delimiter == '\"') {
752 /* not a real delimiter - toggle isInDquote for proper escaping */
753 isInDquote = !isInDquote;
757 /* is a delimiter, which one? */
759 if (delimiter == ':') {
762 copyRange = NSMakeRange(start, r.location - start);
763 [tagAttributes addObject:[_line substringWithRange:copyRange]];
765 /* adjust start, todoRange */
766 start = NSMaxRange(r);
767 todoRange = NSMakeRange(start, length - start);
773 /* adjust todoRange */
774 unsigned offset = NSMaxRange(r);
775 todoRange = NSMakeRange(offset, length - offset);
779 tagValue = [_line substringFromIndex:NSMaxRange(r)];
781 if (debugOn && ([tagName length] == 0)) {
782 NSLog(@"%s: missing tagname in line: '%@'",
783 __PRETTY_FUNCTION__, _line);
787 At this point we have:
788 name: 'BEGIN', 'TEL', 'EMAIL', 'ITEM1.ADR' etc
789 value: ';;;Magdeburg;;;Germany'
790 attributes: ("type=INTERNET", "type=HOME", "type=pref")
794 # warning DEBUG LOG ENABLED
795 NSLog(@"TAG: %@, value %@ attrs %@",
796 tagName, tagValue, tagAttributes);
801 if ([tagName isEqualToString:@"BEGIN"]) {
802 if ([tagAttributes count] > 0)
803 [self warn:@"Losing unexpected parameters of BEGIN line."];
804 [self _beginComponentWithValue:tagValue];
806 else if ([tagName isEqualToString:@"END"]) {
807 if ([tagAttributes count] > 0)
808 [self warn:@"Losing unexpected parameters of END line."];
809 [self _endComponent:tagName value:tagValue];
812 /* a regular content tag */
815 check whether the tga value is encoded in quoted printable,
816 this one is used with Outlook vCards (see data/ for examples)
818 // TODO: make the encoding check more generic
819 if ([tagAttributes containsObject:@"ENCODING=QUOTED-PRINTABLE"]) {
820 // TODO: QP is charset specific! The one below decodes in Unicode!
821 tagValue = [tagValue stringByDecodingQuotedPrintable];
822 [tagAttributes removeObject:@"ENCODING=QUOTED-PRINTABLE"];
825 [self _reportContentAsTag:[self _mapTagName:tagName]
826 group:[self _groupFromTagName:tagName]
827 withAttrs:[self _mapAttrs:tagAttributes forTag:tagName]
828 andContent:tagValue];
831 [tagAttributes release];
835 /* top level parsing method */
837 - (void)reportDocStart {
838 [self->contentHandler startDocument];
839 [self->contentHandler startPrefixMapping:@"" uri:self->prefixURI];
841 - (void)reportDocEnd {
842 [self->contentHandler endPrefixMapping:@""];
843 [self->contentHandler endDocument];
846 - (void)_parseString:(NSString *)_rawString {
848 This method split the string into content lines for actual vCard
852 contentline = name *(";" param ) ":" value CRLF
853 ; When parsing a content line, folded lines MUST first
856 NSMutableString *line;
857 unsigned pos, length;
860 [self reportDocStart];
864 length = [_rawString length];
865 r = NSMakeRange(0, 0);
866 line = [[NSMutableString alloc] initWithCapacity:75 + 2];
868 for (pos = 0; pos < length; pos++) {
871 c = [_rawString characterAtIndex:pos];
874 if (((length - 1) - pos) >= 1) {
875 if ([_rawString characterAtIndex:pos + 1] == '\n') {
876 BOOL isAtEndOfLine = YES;
878 /* test for folding first */
879 if (((length - 1) - pos) >= 2) {
882 ws = [_rawString characterAtIndex:pos + 2];
883 isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO :YES;
884 if (!isAtEndOfLine) {
885 /* assemble part of line up to pos */
887 [line appendString:[_rawString substringWithRange:r]];
891 r = NSMakeRange(pos + 1, 0); /* begin new range */
895 /* assemble part of line up to pos */
897 [line appendString:[_rawString substringWithRange:r]];
899 [self _parseLine:line];
901 [line deleteCharactersInRange:NSMakeRange(0, [line length])];
903 r = NSMakeRange(pos + 1, 0); /* begin new range */
908 /* garbled last line! */
909 [self warn:@"last line is truncated, trying to parse anyways!"];
912 else if (c == '\n') { /* broken, non-standard */
913 BOOL isAtEndOfLine = YES;
915 /* test for folding first */
916 if (((length - 1) - pos) >= 1) {
919 ws = [_rawString characterAtIndex:(pos + 1)];
921 isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO : YES;
922 if (!isAtEndOfLine) {
923 /* assemble part of line up to pos */
925 [line appendString:[_rawString substringWithRange:r]];
929 r = NSMakeRange(pos + 1, 0); /* begin new range */
933 /* assemble part of line up to pos */
935 [line appendString:[_rawString substringWithRange:r]];
937 [self _parseLine:line];
939 [line deleteCharactersInRange:NSMakeRange(0, [line length])];
940 r = NSMakeRange(pos + 1, 0); /* begin new range */
948 [self warn:@"Last line of parse string is not properly terminated!"];
949 [line appendString:[_rawString substringWithRange:r]];
950 [self _parseLine:line];
953 if ([self->cardStack count] != 0) {
954 [self warn:@"found elements on cardStack. This indicates an improper "
955 @"nesting structure! Not all required events will have been "
956 @"generated, leading to unpredictable results!"];
957 [self->cardStack removeAllObjects]; // clean up
960 [line release]; line = nil;
965 /* main entry functions */
967 - (id)sourceForData:(NSData *)_data systemId:(NSString *)_sysId {
968 SaxParseException *e = nil;
969 NSStringEncoding encoding;
971 const unsigned char *bytes;
975 NSLog(@"%s: trying to decode data (0x%08X,len=%d) ...",
976 __PRETTY_FUNCTION__, _data, [_data length]);
979 if ((len = [_data length]) == 0) {
980 e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
981 reason:@"Got no parsing data!"
983 [self->errorHandler fatalError:e];
987 e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
988 reason:@"Input data to short for vCard!"
990 [self->errorHandler fatalError:e];
994 bytes = [_data bytes];
995 if ((bytes[0] == 0xFF && bytes[1] == 0xFE) ||
996 (bytes[0] == 0xFE && bytes[1] == 0xFF)) {
997 encoding = NSUnicodeStringEncoding;
1000 encoding = NSUTF8StringEncoding;
1002 // FIXME: Data is not always utf-8.....
1003 source = [[[NSString alloc] initWithData:_data encoding:encoding]
1005 if (source == nil) {
1006 e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
1007 reason:@"Could not convert input to string!"
1009 [self->errorHandler fatalError:e];
1014 - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
1016 NSLog(@"%s: parse: %@ (sysid=%@)", __PRETTY_FUNCTION__, _source, _sysId);
1018 if ([_source isKindOfClass:[NSURL class]]) {
1022 if (_sysId == nil) _sysId = [url absoluteString];
1025 NSLog(@"%s: trying to load URL: %@ (sysid=%@)",__PRETTY_FUNCTION__,
1029 // TODO: remember encoding of source
1030 _source = [url resourceDataUsingCache:NO];
1031 if (_source == nil || ![_source length]) {
1032 SaxParseException *e;
1036 NSLog(@"%s: got no data from url: %@", __PRETTY_FUNCTION__, url);
1038 s = [NSString stringWithFormat:@"got no data from url: %@", url];
1039 e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
1042 [self->errorHandler fatalError:e];
1047 if ([_source isKindOfClass:[NSData class]]) {
1048 if (_sysId == nil) _sysId = @"<data>";
1049 if ((_source = [self sourceForData:_source systemId:_sysId]) == nil)
1053 if (![_source isKindOfClass:[NSString class]]) {
1054 SaxParseException *e;
1058 NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source);
1060 s = [@"cannot handle data-source: " stringByAppendingString:
1061 [_source description]];
1062 e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
1066 [self->errorHandler fatalError:e];
1070 /* ensure consistent state */
1072 [self->cardStack removeAllObjects];
1073 [self->elementList removeAllObjects];
1078 NSLog(@"%s: trying to parse string (0x%08X,len=%d) ...",
1079 __PRETTY_FUNCTION__, _source, [_source length]);
1081 if (_sysId == nil) _sysId = @"<string>";
1082 [self _parseString:_source];
1086 [self->cardStack removeAllObjects];
1087 [self->elementList removeAllObjects];
1090 - (void)parseFromSource:(id)_source {
1091 [self parseFromSource:_source systemId:nil];
1094 - (void)parseFromSystemId:(NSString *)_sysId {
1097 if ([_sysId rangeOfString:@"://"].length == 0) {
1098 /* seems to be a path, path to be a proper URL */
1099 url = [NSURL fileURLWithPath:_sysId];
1102 /* Note: Cocoa NSURL doesn't complain on "/abc/def" like input! */
1103 url = [NSURL URLWithString:_sysId];
1107 SaxParseException *e;
1109 e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
1110 reason:@"cannot handle system-id"
1112 [self->errorHandler fatalError:e];
1116 [self parseFromSource:url systemId:_sysId];
1121 - (BOOL)isDebuggingEnabled {
1125 @end /* VersitSaxDriver */