2 Copyright (C) 2005 Helge Hess
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "NGVCardSaxHandler.h"
24 #include "NGVCardValue.h"
25 #include "NGVCardSimpleValue.h"
26 #include "NGVCardAddress.h"
27 #include "NGVCardPhone.h"
28 #include "NGVCardName.h"
29 #include "NGVCardOrg.h"
30 #include "NGVCardStrArrayValue.h"
34 #ifndef XMLNS_VCARD_XML_03
35 # define XMLNS_VCARD_XML_03 \
36 @"http://www.ietf.org/internet-drafts/draft-dawson-vcard-xml-dtd-03.txt"
39 // TODO: this is wayyy to big and complicated ;->
41 @implementation NGVCardSaxHandler
44 if (self->content != NULL) free(self->content);
45 [self->subvalues release];
46 [self->xtags release];
49 [self->email release];
50 [self->label release];
52 [self->fburl release];
53 [self->caluri release];
54 [self->types release];
56 [self->vCards release];
57 [self->vCard release];
58 [self->currentGroup release];
70 - (void)resetCardState {
71 [self->tel removeAllObjects];
72 [self->adr removeAllObjects];
73 [self->email removeAllObjects];
74 [self->label removeAllObjects];
75 [self->url removeAllObjects];
76 [self->fburl removeAllObjects];
77 [self->caluri removeAllObjects];
78 [self->xtags removeAllObjects];
79 [self->currentGroup release]; self->currentGroup = nil;
80 [self->types removeAllObjects];
81 [self->args removeAllObjects];
82 [self->vCard release]; self->vCard = nil;
85 - (void)resetExceptResult {
86 [self->vCard release]; self->vCard = nil;
88 [self resetCardState];
90 if (self->content != NULL) {
95 self->vcs.isInVCardSet = 0;
96 self->vcs.isInVCard = 0;
98 self->vcs.isInAdr = 0;
99 self->vcs.isInOrg = 0;
100 self->vcs.isInGroup = 0;
101 self->vcs.collectContent = 0;
105 [self resetExceptResult];
106 [self->vCards removeAllObjects];
109 /* document events */
111 - (void)startDocument {
114 if (self->vCards == nil)
115 self->vCards = [[NSMutableArray alloc] initWithCapacity:16];
117 if (self->tel == nil)
118 self->tel = [[NSMutableArray alloc] initWithCapacity:8];
119 if (self->adr == nil)
120 self->adr = [[NSMutableArray alloc] initWithCapacity:8];
121 if (self->email == nil)
122 self->email = [[NSMutableArray alloc] initWithCapacity:8];
123 if (self->label == nil)
124 self->label = [[NSMutableArray alloc] initWithCapacity:8];
126 if (self->url == nil)
127 self->url = [[NSMutableArray alloc] initWithCapacity:8];
128 if (self->fburl == nil)
129 self->fburl = [[NSMutableArray alloc] initWithCapacity:1];
130 if (self->caluri == nil)
131 self->caluri = [[NSMutableArray alloc] initWithCapacity:1];
133 if (self->types == nil)
134 self->types = [[NSMutableArray alloc] initWithCapacity:4];
135 if (self->args == nil)
136 self->args = [[NSMutableDictionary alloc] initWithCapacity:8];
138 if (self->subvalues == nil)
139 self->subvalues = [[NSMutableDictionary alloc] initWithCapacity:16];
140 if (self->xtags == nil)
141 self->xtags = [[NSMutableDictionary alloc] initWithCapacity:32];
143 - (void)endDocument {
144 [self resetExceptResult];
149 - (void)startValueTag:(NSString *)_tag attributes:(id<SaxAttributes>)_attrs {
150 /* a tag with types and attributes */
153 [self->types removeAllObjects];
154 [self->args removeAllObjects];
156 for (i = 0, count = [_attrs count]; i < count; i++) {
159 n = [_attrs nameAtIndex:i];
160 v = [_attrs valueAtIndex:i];
162 if ([n hasSuffix:@".type"] || [n isEqualToString:@"TYPE"]) {
164 Note: types cannot be separated by comma! Its indeed always a space,eg
166 If you find commas, usually the vCard is broken.
171 e = [[v componentsSeparatedByString:@" "] objectEnumerator];
172 while ((k = [e nextObject]) != nil) {
173 k = [k uppercaseString];
174 if ([self->types containsObject:k]) continue;
175 [self->types addObject:k];
179 [self->args setObject:v forKey:n];
182 - (void)endValueTag {
183 [self->types removeAllObjects];
184 [self->args removeAllObjects];
187 /* handle elements */
189 - (void)startGroup:(NSString *)_name {
190 self->vcs.isInGroup = 1;
191 ASSIGNCOPY(self->currentGroup, _name);
194 self->vcs.isInGroup = 0;
195 [self->currentGroup release]; self->currentGroup = nil;
199 [self->subvalues removeAllObjects];
207 n = [[NGVCardName alloc] initWithPropertyList:self->subvalues
208 group:self->currentGroup
209 types:self->types arguments:self->args];
210 [self->vCard setN:n];
211 [self->subvalues removeAllObjects];
216 [self->subvalues removeAllObjects];
217 self->vcs.isInOrg = 1;
223 self->vcs.isInOrg = 0;
225 if ((u = [self->subvalues objectForKey:@"orgunit"]) != nil) {
226 if (![u isKindOfClass:[NSArray class]])
227 u = [NSArray arrayWithObjects:&u count:1];
230 // TODO: pass org values!
231 o = [[NGVCardOrg alloc] initWithName:[self->subvalues objectForKey:@"orgnam"]
233 group:self->currentGroup
234 types:self->types arguments:self->args];
235 [self->vCard setOrg:o];
237 [self->subvalues removeAllObjects];
241 [self->subvalues removeAllObjects];
242 self->vcs.isInGeo = 1;
247 self->vcs.isInGeo = 0;
249 [self logWithFormat:@"WARNING: not supporting geo in vCard."];
250 [self->subvalues removeAllObjects];
253 - (void)startVCard:(id<SaxAttributes>)_attrs {
254 NSString *uid, *version;
257 [self->tel removeAllObjects];
258 [self->adr removeAllObjects];
259 [self->email removeAllObjects];
260 [self->label removeAllObjects];
261 [self->url removeAllObjects];
262 [self->fburl removeAllObjects];
263 [self->caluri removeAllObjects];
264 [self->xtags removeAllObjects];
266 self->vcs.isInVCard = 1;
267 if (self->vCard != nil) {
268 [self->vCards addObject:self->vCard];
269 [self logWithFormat:@"ERROR: vCard nesting not supported!"];
270 [self->vCard release]; self->vCard = nil;
273 if ((uid = [_attrs valueForName:@"uid" uri:XMLNS_VCARD_XML_03]) == nil)
274 uid = [_attrs valueForName:@"X-ABUID" uri:XMLNS_VCARD_XML_03];
276 version = [_attrs valueForName:@"version" uri:XMLNS_VCARD_XML_03];
278 self->vCard = [[NGVCard alloc] initWithUid:uid version:version];
280 if ((t = [_attrs valueForName:@"class" uri:XMLNS_VCARD_XML_03]) != nil)
281 [self->vCard setVClass:t];
282 if ((t = [_attrs valueForName:@"rev" uri:XMLNS_VCARD_XML_03]) != nil) {
283 [self logWithFormat:@"WARNING: vCard revision not yet supported!"];
286 if ((t = [_attrs valueForName:@"prodid" uri:XMLNS_VCARD_XML_03]) != nil)
287 [self->vCard setProdID:t];
289 [self debugWithFormat:@"started vCard: %@", self->vCard];
292 self->vcs.isInVCard = 0;
294 /* fill collected objects */
296 if ([self->tel count] > 0) [self->vCard setTel:self->tel];
297 if ([self->adr count] > 0) [self->vCard setAdr:self->adr];
298 if ([self->email count] > 0) [self->vCard setEmail:self->email];
299 if ([self->label count] > 0) [self->vCard setLabel:self->label];
300 if ([self->url count] > 0) [self->vCard setUrl:self->url];
301 if ([self->fburl count] > 0) [self->vCard setFreeBusyURL:self->fburl];
302 if ([self->caluri count] > 0) [self->vCard setCalURI:self->caluri];
303 if ([self->xtags count] > 0) [self->vCard setX:self->xtags];
305 [self->vCards addObject:self->vCard];
306 //[self debugWithFormat:@"finished vCard: %@", self->vCard];
308 [self resetCardState];
311 - (void)startVCardSet:(id<SaxAttributes>)_attrs {
312 self->vcs.isInVCardSet = 1;
314 - (void)endVCardSet {
315 self->vcs.isInVCardSet = 0;
318 - (void)endBaseContentTagWithClass:(Class)_clazz andAddTo:(NSMutableArray*)_a {
319 NGVCardSimpleValue *v;
321 v = [[_clazz alloc] initWithValue:[self finishCollectingContent]
322 group:self->currentGroup
323 types:self->types arguments:self->args];
330 - (void)startTel:(id<SaxAttributes>)_attrs {
331 [self startValueTag:@"tel" attributes:_attrs];
332 [self startCollectingContent];
335 [self endBaseContentTagWithClass:[NGVCardPhone class] andAddTo:self->tel];
338 - (void)startAdr:(id<SaxAttributes>)_attrs {
339 [self->subvalues removeAllObjects];
341 self->vcs.isInAdr = 1;
342 [self startValueTag:@"adr" attributes:_attrs];
345 NGVCardAddress *address;
347 self->vcs.isInAdr = 0;
349 address = [[NGVCardAddress alloc] initWithPropertyList:self->subvalues
350 group:self->currentGroup
351 types:self->types arguments:self->args];
352 [self->adr addObject:address];
354 [self->subvalues removeAllObjects];
359 - (void)startEmail:(id<SaxAttributes>)_attrs {
360 [self startValueTag:@"email" attributes:_attrs];
361 [self startCollectingContent];
364 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
365 andAddTo:self->email];
368 - (void)startLabel:(id<SaxAttributes>)_attrs {
369 [self startValueTag:@"LABEL" attributes:_attrs];
370 [self startCollectingContent];
373 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
374 andAddTo:self->label];
377 - (void)startURL:(id<SaxAttributes>)_attrs {
378 [self startValueTag:@"url" attributes:_attrs];
379 [self startCollectingContent];
382 // TODO: use special URL class?
383 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
387 /* tags with comma separated values */
389 - (void)startNickname:(id<SaxAttributes>)_attrs {
390 [self startValueTag:@"nickname" attributes:_attrs];
391 [self startCollectingContent];
393 - (void)endNickname {
394 NGVCardStrArrayValue *v;
398 a = [[self finishCollectingContent] componentsSeparatedByString:@","];
400 v = [[NGVCardStrArrayValue alloc] initWithArray:a
401 group:self->currentGroup
402 types:self->types arguments:self->args];
403 [self->vCard setNickname:v];
404 [v release]; v = nil;
409 - (void)startCategories:(id<SaxAttributes>)_attrs {
410 [self startValueTag:@"categories" attributes:_attrs];
411 [self startCollectingContent];
413 - (void)endCategories {
414 NGVCardStrArrayValue *v;
418 a = [[self finishCollectingContent] componentsSeparatedByString:@","];
420 v = [[NGVCardStrArrayValue alloc] initWithArray:a
421 group:self->currentGroup
422 types:self->types arguments:self->args];
423 [self->vCard setCategories:v];
424 [v release]; v = nil;
429 /* generic processing of tags with subtags */
431 - (void)startSubContentTag:(id<SaxAttributes>)_attrs {
432 if ([_attrs count] > 0)
433 [self logWithFormat:@"WARNING: loosing attrs of subtag: %@", _attrs];
435 [self startCollectingContent];
437 - (void)endSubContentTag:(NSString *)_key {
441 if ((s = [[self finishCollectingContent] copy]) == nil)
444 if ((o = [self->subvalues objectForKey:_key]) == nil) {
445 [self->subvalues setObject:s forKey:_key];
448 /* multivalue (eg 'org') */
449 if ([o isKindOfClass:[NSMutableArray class]]) {
455 a = [[NSMutableArray alloc] initWithCapacity:4];
458 [self->subvalues setObject:a forKey:_key];
459 [a release]; a = nil;
465 /* extended tags (X-) */
467 - (void)startX:(NSString *)_name attributes:(id<SaxAttributes>)_attrs {
468 [self startValueTag:_name attributes:_attrs];
469 [self startCollectingContent];
471 - (void)endX:(NSString *)_name {
472 NGVCardSimpleValue *v;
476 s = [self finishCollectingContent];
477 v = [[NGVCardSimpleValue alloc] initWithValue:s
478 group:self->currentGroup
479 types:self->types arguments:self->args];
481 if ((o = [self->xtags objectForKey:_name]) == nil)
482 [self->xtags setObject:v forKey:_name];
483 else if ([o isKindOfClass:[NSMutableArray class]])
488 a = [[NSMutableArray alloc] initWithCapacity:4];
491 [self->xtags setObject:a forKey:_name];
501 - (void)startFN:(id<SaxAttributes>)_attrs {
502 [self startValueTag:@"" attributes:_attrs];
503 [self startCollectingContent];
506 [self->vCard setFn:[self finishCollectingContent]];
510 - (void)startRole:(id<SaxAttributes>)_attrs {
511 [self startValueTag:@"" attributes:_attrs];
512 [self startCollectingContent];
515 [self->vCard setRole:[self finishCollectingContent]];
519 - (void)startTitle:(id<SaxAttributes>)_attrs {
520 [self startValueTag:@"" attributes:_attrs];
521 [self startCollectingContent];
524 [self->vCard setTitle:[self finishCollectingContent]];
528 - (void)startBDay:(id<SaxAttributes>)_attrs {
529 [self startValueTag:@"" attributes:_attrs];
530 [self startCollectingContent];
533 [self->vCard setBday:[self finishCollectingContent]];
537 - (void)startNote:(id<SaxAttributes>)_attrs {
538 [self startValueTag:@"" attributes:_attrs];
539 [self startCollectingContent];
542 [self->vCard setNote:[self finishCollectingContent]];
546 - (void)startCalURI:(id<SaxAttributes>)_attrs {
547 [self startValueTag:@"CALURI" attributes:_attrs];
548 [self startCollectingContent];
551 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
552 andAddTo:self->caluri];
555 - (void)startFreeBusyURL:(id<SaxAttributes>)_attrs {
556 [self startValueTag:@"FBURL" attributes:_attrs];
557 [self startCollectingContent];
559 - (void)endFreeBusyURL {
560 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
561 andAddTo:self->fburl];
566 - (void)startProfile:(id<SaxAttributes>)_attrs {
567 [self startCollectingContent];
570 [self->vCard setProfile:[self finishCollectingContent]];
573 - (void)startSource:(id<SaxAttributes>)_attrs {
574 [self startCollectingContent];
577 [self->vCard setSource:[self finishCollectingContent]];
580 - (void)startName:(id<SaxAttributes>)_attrs {
581 [self startCollectingContent];
584 [self->vCard setVName:[self finishCollectingContent]];
590 - (void)startElement:(NSString *)_localName
591 namespace:(NSString *)_ns
592 rawName:(NSString *)_rawName
593 attributes:(id<SaxAttributes>)_attrs
595 unichar c0 = [_localName characterAtIndex:0];
597 if (c0 == 'g' && [_localName isEqualToString:@"group"])
598 [self startGroup:[_attrs valueForName:@"name" uri:_ns]];
599 else if (c0 == 'n' && [_localName length] == 1)
601 else if (c0 == 'o' && [_localName isEqualToString:@"org"])
603 else if (c0 == 't' && [_localName isEqualToString:@"tel"])
604 [self startTel:_attrs];
605 else if (c0 == 'u' && [_localName isEqualToString:@"url"])
606 [self startURL:_attrs];
607 else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
608 [self startAdr:_attrs];
609 else if (c0 == 'e' && [_localName isEqualToString:@"email"])
610 [self startEmail:_attrs];
611 else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
612 [self startLabel:_attrs];
613 else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
614 [self startVCard:_attrs];
615 else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
616 [self startVCardSet:_attrs];
617 else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
618 [self startNickname:_attrs];
619 else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
620 [self startCategories:_attrs];
621 else if (c0 == 'r' && [_localName isEqualToString:@"role"])
622 [self startRole:_attrs];
623 else if (c0 == 't' && [_localName isEqualToString:@"title"])
624 [self startTitle:_attrs];
625 else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
626 [self startBDay:_attrs];
627 else if (c0 == 'n' && [_localName isEqualToString:@"note"])
628 [self startNote:_attrs];
629 else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
630 [self startCalURI:_attrs];
631 else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
632 [self startFreeBusyURL:_attrs];
633 else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
634 [self startFN:_attrs];
635 else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
637 // TODO: following are generated by LSAddress, but not in spec?
638 else if (c0 == 'P' && [_localName isEqualToString:@"PROFILE"])
639 [self startProfile:_attrs];
640 else if (c0 == 'S' && [_localName isEqualToString:@"SOURCE"])
641 [self startSource:_attrs];
642 else if (c0 == 'N' && [_localName isEqualToString:@"NAME"])
643 [self startName:_attrs];
645 if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr ||
647 [self startSubContentTag:_attrs];
649 [self startX:_localName attributes:_attrs];
651 NSLog(@"U: %@", _localName);
655 - (void)endElement:(NSString *)_localName
656 namespace:(NSString *)_ns
657 rawName:(NSString *)_rawName
659 unichar c0 = [_localName characterAtIndex:0];
661 if (c0 == 'g' && [_localName isEqualToString:@"group"])
663 else if (c0 == 'n' && [_localName isEqualToString:@"n"])
665 else if (c0 == 'o' && [_localName isEqualToString:@"org"])
667 else if (c0 == 't' && [_localName isEqualToString:@"tel"])
669 else if (c0 == 'u' && [_localName isEqualToString:@"url"])
671 else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
673 else if (c0 == 'e' && [_localName isEqualToString:@"email"])
675 else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
677 else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
679 else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
681 else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
683 else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
684 [self endCategories];
685 else if (c0 == 'r' && [_localName isEqualToString:@"role"])
687 else if (c0 == 't' && [_localName isEqualToString:@"title"])
689 else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
691 else if (c0 == 'n' && [_localName isEqualToString:@"note"])
693 else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
695 else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
696 [self endFreeBusyURL];
697 else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
699 else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
701 else if (c0 == 'P' && [_localName isEqualToString:@"PROFILE"])
703 else if (c0 == 'S' && [_localName isEqualToString:@"SOURCE"])
705 else if (c0 == 'N' && [_localName isEqualToString:@"NAME"])
708 if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr ||
710 [self endSubContentTag:_localName];
712 [self endX:_localName];
718 - (void)startCollectingContent {
719 if (self->content != NULL) {
721 self->content = NULL;
723 self->vcs.collectContent = 1;
726 - (NSString *)finishCollectingContent {
729 self->vcs.collectContent = 0;
731 if (self->content == NULL)
734 if (self->contentLength == 0)
737 s = [NSString stringWithCharacters:self->content length:self->contentLength];
738 if (self->content != NULL) {
740 self->content = NULL;
745 - (void)characters:(unichar *)_chars length:(int)_len {
746 if (_len == 0 || _chars == NULL)
749 if (self->content == NULL) {
751 self->contentLength = _len;
752 self->content = calloc(_len + 1, sizeof(unichar));
753 memcpy(self->content, _chars, (_len * sizeof(unichar)));
756 /* increase content */
758 realloc(self->content, (self->contentLength + _len+2) * sizeof(unichar));
759 memcpy(&(self->content[self->contentLength]), _chars,
760 (_len * sizeof(unichar)));
761 self->contentLength += _len;
763 self->content[self->contentLength] = 0;
766 @end /* NGVCardSaxHandler */