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"
33 #ifndef XMLNS_VCARD_XML_03
34 # define XMLNS_VCARD_XML_03 \
35 @"http://www.ietf.org/internet-drafts/draft-dawson-vcard-xml-dtd-03.txt"
38 // TODO: this is wayyy to big and complicated ;->
40 @implementation NGVCardSaxHandler
43 if (self->content != NULL) free(self->content);
44 [self->subvalues release];
45 [self->xtags release];
48 [self->email release];
49 [self->label release];
51 [self->fburl release];
52 [self->caluri release];
53 [self->types release];
55 [self->vCards release];
56 [self->vCard release];
57 [self->currentGroup release];
69 - (void)resetCardState {
70 [self->tel removeAllObjects];
71 [self->adr removeAllObjects];
72 [self->email removeAllObjects];
73 [self->label removeAllObjects];
74 [self->url removeAllObjects];
75 [self->fburl removeAllObjects];
76 [self->caluri removeAllObjects];
77 [self->xtags removeAllObjects];
78 [self->currentGroup release]; self->currentGroup = nil;
79 [self->types removeAllObjects];
80 [self->args removeAllObjects];
81 [self->vCard release]; self->vCard = nil;
84 - (void)resetExceptResult {
85 [self->vCard release]; self->vCard = nil;
87 [self resetCardState];
89 if (self->content != NULL) {
94 self->vcs.isInVCardSet = 0;
95 self->vcs.isInVCard = 0;
97 self->vcs.isInAdr = 0;
98 self->vcs.isInOrg = 0;
99 self->vcs.isInGroup = 0;
100 self->vcs.collectContent = 0;
104 [self resetExceptResult];
105 [self->vCards removeAllObjects];
108 /* document events */
110 - (void)startDocument {
113 if (self->vCards == nil)
114 self->vCards = [[NSMutableArray alloc] initWithCapacity:16];
116 if (self->tel == nil)
117 self->tel = [[NSMutableArray alloc] initWithCapacity:8];
118 if (self->adr == nil)
119 self->adr = [[NSMutableArray alloc] initWithCapacity:8];
120 if (self->email == nil)
121 self->email = [[NSMutableArray alloc] initWithCapacity:8];
122 if (self->label == nil)
123 self->label = [[NSMutableArray alloc] initWithCapacity:8];
125 if (self->url == nil)
126 self->url = [[NSMutableArray alloc] initWithCapacity:8];
127 if (self->fburl == nil)
128 self->fburl = [[NSMutableArray alloc] initWithCapacity:1];
129 if (self->caluri == nil)
130 self->caluri = [[NSMutableArray alloc] initWithCapacity:1];
132 if (self->types == nil)
133 self->types = [[NSMutableArray alloc] initWithCapacity:4];
134 if (self->args == nil)
135 self->args = [[NSMutableDictionary alloc] initWithCapacity:8];
137 if (self->subvalues == nil)
138 self->subvalues = [[NSMutableDictionary alloc] initWithCapacity:16];
139 if (self->xtags == nil)
140 self->xtags = [[NSMutableDictionary alloc] initWithCapacity:32];
142 - (void)endDocument {
143 [self resetExceptResult];
148 - (void)startValueTag:(NSString *)_tag attributes:(id<SaxAttributes>)_attrs {
149 /* a tag with types and attributes */
152 [self->types removeAllObjects];
153 [self->args removeAllObjects];
155 for (i = 0, count = [_attrs count]; i < count; i++) {
158 n = [_attrs nameAtIndex:i];
159 v = [_attrs valueAtIndex:i];
161 if ([n hasSuffix:@".type"] || [n isEqualToString:@"TYPE"]) {
163 Note: types cannot be separated by comma! Its indeed always a space,eg
165 If you find commas, usually the vCard is broken.
170 e = [[v componentsSeparatedByString:@" "] objectEnumerator];
171 while ((k = [e nextObject]) != nil) {
172 k = [k uppercaseString];
173 if ([self->types containsObject:k]) continue;
174 [self->types addObject:k];
178 [self->args setObject:v forKey:n];
181 - (void)endValueTag {
182 [self->types removeAllObjects];
183 [self->args removeAllObjects];
186 /* handle elements */
188 - (void)startGroup:(NSString *)_name {
189 self->vcs.isInGroup = 1;
190 ASSIGNCOPY(self->currentGroup, _name);
193 self->vcs.isInGroup = 0;
194 [self->currentGroup release]; self->currentGroup = nil;
198 [self->subvalues removeAllObjects];
206 n = [[NGVCardName alloc] initWithPropertyList:self->subvalues
207 group:self->currentGroup
208 types:self->types arguments:self->args];
209 [self->vCard setN:n];
210 [self->subvalues removeAllObjects];
215 [self->subvalues removeAllObjects];
216 self->vcs.isInOrg = 1;
222 self->vcs.isInOrg = 0;
224 if ((u = [self->subvalues objectForKey:@"orgunit"]) != nil) {
225 if (![u isKindOfClass:[NSArray class]])
226 u = [NSArray arrayWithObjects:&u count:1];
229 // TODO: pass org values!
230 o = [[NGVCardOrg alloc] initWithName:[self->subvalues objectForKey:@"orgnam"]
232 group:self->currentGroup
233 types:self->types arguments:self->args];
234 [self->vCard setOrg:o];
236 [self->subvalues removeAllObjects];
240 [self->subvalues removeAllObjects];
241 self->vcs.isInGeo = 1;
246 self->vcs.isInGeo = 0;
248 [self logWithFormat:@"WARNING: not supporting geo in vCard."];
249 [self->subvalues removeAllObjects];
252 - (void)startVCard:(id<SaxAttributes>)_attrs {
253 NSString *uid, *version;
256 [self->tel removeAllObjects];
257 [self->adr removeAllObjects];
258 [self->email removeAllObjects];
259 [self->label removeAllObjects];
260 [self->url removeAllObjects];
261 [self->fburl removeAllObjects];
262 [self->caluri removeAllObjects];
263 [self->xtags removeAllObjects];
265 self->vcs.isInVCard = 1;
266 if (self->vCard != nil) {
267 [self->vCards addObject:self->vCard];
268 [self logWithFormat:@"ERROR: vCard nesting not supported!"];
269 [self->vCard release]; self->vCard = nil;
272 if ((uid = [_attrs valueForName:@"uid" uri:XMLNS_VCARD_XML_03]) == nil)
273 uid = [_attrs valueForName:@"X-ABUID" uri:XMLNS_VCARD_XML_03];
275 version = [_attrs valueForName:@"version" uri:XMLNS_VCARD_XML_03];
277 self->vCard = [[NGVCard alloc] initWithUid:uid version:version];
279 if ((t = [_attrs valueForName:@"class" uri:XMLNS_VCARD_XML_03]) != nil)
280 [self->vCard setVClass:t];
281 if ((t = [_attrs valueForName:@"rev" uri:XMLNS_VCARD_XML_03]) != nil) {
282 [self logWithFormat:@"WARNING: vCard revision not yet supported!"];
285 if ((t = [_attrs valueForName:@"prodid" uri:XMLNS_VCARD_XML_03]) != nil)
286 [self->vCard setProdID:t];
288 [self debugWithFormat:@"started vCard: %@", self->vCard];
291 self->vcs.isInVCard = 0;
293 /* fill collected objects */
295 if ([self->tel count] > 0) [self->vCard setTel:self->tel];
296 if ([self->adr count] > 0) [self->vCard setAdr:self->adr];
297 if ([self->email count] > 0) [self->vCard setEmail:self->email];
298 if ([self->label count] > 0) [self->vCard setLabel:self->label];
299 if ([self->url count] > 0) [self->vCard setUrl:self->url];
300 if ([self->fburl count] > 0) [self->vCard setFreeBusyURL:self->fburl];
301 if ([self->caluri count] > 0) [self->vCard setCalURI:self->caluri];
302 if ([self->xtags count] > 0) [self->vCard setX:self->xtags];
304 [self->vCards addObject:self->vCard];
305 //[self debugWithFormat:@"finished vCard: %@", self->vCard];
307 [self resetCardState];
310 - (void)startVCardSet:(id<SaxAttributes>)_attrs {
311 self->vcs.isInVCardSet = 1;
313 - (void)endVCardSet {
314 self->vcs.isInVCardSet = 0;
317 - (void)endBaseContentTagWithClass:(Class)_clazz andAddTo:(NSMutableArray*)_a {
318 NGVCardSimpleValue *v;
320 v = [[_clazz alloc] initWithValue:[self finishCollectingContent]
321 group:self->currentGroup
322 types:self->types arguments:self->args];
329 - (void)startTel:(id<SaxAttributes>)_attrs {
330 [self startValueTag:@"tel" attributes:_attrs];
331 [self startCollectingContent];
334 [self endBaseContentTagWithClass:[NGVCardPhone class] andAddTo:self->tel];
337 - (void)startAdr:(id<SaxAttributes>)_attrs {
338 [self->subvalues removeAllObjects];
340 self->vcs.isInAdr = 1;
341 [self startValueTag:@"adr" attributes:_attrs];
344 NGVCardAddress *address;
346 self->vcs.isInAdr = 0;
348 address = [[NGVCardAddress alloc] initWithPropertyList:self->subvalues
349 group:self->currentGroup
350 types:self->types arguments:self->args];
351 [self->adr addObject:address];
353 [self->subvalues removeAllObjects];
358 - (void)startEmail:(id<SaxAttributes>)_attrs {
359 [self startValueTag:@"email" attributes:_attrs];
360 [self startCollectingContent];
363 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
364 andAddTo:self->email];
367 - (void)startLabel:(id<SaxAttributes>)_attrs {
368 [self startValueTag:@"LABEL" attributes:_attrs];
369 [self startCollectingContent];
372 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
373 andAddTo:self->label];
376 - (void)startURL:(id<SaxAttributes>)_attrs {
377 [self startValueTag:@"url" attributes:_attrs];
378 [self startCollectingContent];
381 // TODO: use special URL class?
382 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
386 /* tags with comma separated values */
388 - (void)startNickname:(id<SaxAttributes>)_attrs {
389 [self startValueTag:@"nickname" attributes:_attrs];
390 [self startCollectingContent];
392 - (void)endNickname {
393 NGVCardStrArrayValue *v;
397 a = [[self finishCollectingContent] componentsSeparatedByString:@","];
399 v = [[NGVCardStrArrayValue alloc] initWithArray:a
400 group:self->currentGroup
401 types:self->types arguments:self->args];
402 [self->vCard setNickname:v];
403 [v release]; v = nil;
408 - (void)startCategories:(id<SaxAttributes>)_attrs {
409 [self startValueTag:@"categories" attributes:_attrs];
410 [self startCollectingContent];
412 - (void)endCategories {
413 NGVCardStrArrayValue *v;
417 a = [[self finishCollectingContent] componentsSeparatedByString:@","];
419 v = [[NGVCardStrArrayValue alloc] initWithArray:a
420 group:self->currentGroup
421 types:self->types arguments:self->args];
422 [self->vCard setCategories:v];
423 [v release]; v = nil;
428 /* generic processing of tags with subtags */
430 - (void)startSubContentTag:(id<SaxAttributes>)_attrs {
431 if ([_attrs count] > 0)
432 [self logWithFormat:@"WARNING: loosing attrs of subtag: %@", _attrs];
434 [self startCollectingContent];
436 - (void)endSubContentTag:(NSString *)_key {
440 if ((s = [[self finishCollectingContent] copy]) == nil)
443 if ((o = [self->subvalues objectForKey:_key]) == nil) {
444 [self->subvalues setObject:s forKey:_key];
447 /* multivalue (eg 'org') */
448 if ([o isKindOfClass:[NSMutableArray class]]) {
454 a = [[NSMutableArray alloc] initWithCapacity:4];
457 [self->subvalues setObject:a forKey:_key];
458 [a release]; a = nil;
464 /* extended tags (X-) */
466 - (void)startX:(NSString *)_name attributes:(id<SaxAttributes>)_attrs {
467 [self startValueTag:_name attributes:_attrs];
468 [self startCollectingContent];
470 - (void)endX:(NSString *)_name {
471 NGVCardSimpleValue *v;
475 s = [self finishCollectingContent];
476 v = [[NGVCardSimpleValue alloc] initWithValue:s
477 group:self->currentGroup
478 types:self->types arguments:self->args];
480 if ((o = [self->xtags objectForKey:_name]) == nil)
481 [self->xtags setObject:v forKey:_name];
482 else if ([o isKindOfClass:[NSMutableArray class]])
487 a = [[NSMutableArray alloc] initWithCapacity:4];
490 [self->xtags setObject:a forKey:_name];
500 - (void)startFN:(id<SaxAttributes>)_attrs {
501 [self startValueTag:@"" attributes:_attrs];
502 [self startCollectingContent];
505 [self->vCard setFn:[self finishCollectingContent]];
509 - (void)startRole:(id<SaxAttributes>)_attrs {
510 [self startValueTag:@"" attributes:_attrs];
511 [self startCollectingContent];
514 [self->vCard setRole:[self finishCollectingContent]];
518 - (void)startTitle:(id<SaxAttributes>)_attrs {
519 [self startValueTag:@"" attributes:_attrs];
520 [self startCollectingContent];
523 [self->vCard setTitle:[self finishCollectingContent]];
527 - (void)startBDay:(id<SaxAttributes>)_attrs {
528 [self startValueTag:@"" attributes:_attrs];
529 [self startCollectingContent];
532 [self->vCard setBday:[self finishCollectingContent]];
536 - (void)startNote:(id<SaxAttributes>)_attrs {
537 [self startValueTag:@"" attributes:_attrs];
538 [self startCollectingContent];
541 [self->vCard setNote:[self finishCollectingContent]];
545 - (void)startCalURI:(id<SaxAttributes>)_attrs {
546 [self startValueTag:@"CALURI" attributes:_attrs];
547 [self startCollectingContent];
550 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
551 andAddTo:self->caluri];
554 - (void)startFreeBusyURL:(id<SaxAttributes>)_attrs {
555 [self startValueTag:@"FBURL" attributes:_attrs];
556 [self startCollectingContent];
558 - (void)endFreeBusyURL {
559 [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
560 andAddTo:self->fburl];
565 - (void)startProfile:(id<SaxAttributes>)_attrs {
566 [self startCollectingContent];
569 [self->vCard setProfile:[self finishCollectingContent]];
572 - (void)startSource:(id<SaxAttributes>)_attrs {
573 [self startCollectingContent];
576 [self->vCard setSource:[self finishCollectingContent]];
579 - (void)startName:(id<SaxAttributes>)_attrs {
580 [self startCollectingContent];
583 [self->vCard setVName:[self finishCollectingContent]];
589 - (void)startElement:(NSString *)_localName
590 namespace:(NSString *)_ns
591 rawName:(NSString *)_rawName
592 attributes:(id<SaxAttributes>)_attrs
594 unichar c0 = [_localName characterAtIndex:0];
596 if (c0 == 'g' && [_localName isEqualToString:@"group"])
597 [self startGroup:[_attrs valueForName:@"name" uri:_ns]];
598 else if (c0 == 'n' && [_localName length] == 1)
600 else if (c0 == 'o' && [_localName isEqualToString:@"org"])
602 else if (c0 == 't' && [_localName isEqualToString:@"tel"])
603 [self startTel:_attrs];
604 else if (c0 == 'u' && [_localName isEqualToString:@"url"])
605 [self startURL:_attrs];
606 else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
607 [self startAdr:_attrs];
608 else if (c0 == 'e' && [_localName isEqualToString:@"email"])
609 [self startEmail:_attrs];
610 else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
611 [self startLabel:_attrs];
612 else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
613 [self startVCard:_attrs];
614 else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
615 [self startVCardSet:_attrs];
616 else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
617 [self startNickname:_attrs];
618 else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
619 [self startCategories:_attrs];
620 else if (c0 == 'r' && [_localName isEqualToString:@"role"])
621 [self startRole:_attrs];
622 else if (c0 == 't' && [_localName isEqualToString:@"title"])
623 [self startTitle:_attrs];
624 else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
625 [self startBDay:_attrs];
626 else if (c0 == 'n' && [_localName isEqualToString:@"note"])
627 [self startNote:_attrs];
628 else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
629 [self startCalURI:_attrs];
630 else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
631 [self startFreeBusyURL:_attrs];
632 else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
633 [self startFN:_attrs];
634 else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
636 // TODO: following are generated by LSAddress, but not in spec?
637 else if (c0 == 'P' && [_localName isEqualToString:@"PROFILE"])
638 [self startProfile:_attrs];
639 else if (c0 == 'S' && [_localName isEqualToString:@"SOURCE"])
640 [self startSource:_attrs];
641 else if (c0 == 'N' && [_localName isEqualToString:@"NAME"])
642 [self startName:_attrs];
644 if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr ||
646 [self startSubContentTag:_attrs];
648 [self startX:_localName attributes:_attrs];
650 NSLog(@"U: %@", _localName);
654 - (void)endElement:(NSString *)_localName
655 namespace:(NSString *)_ns
656 rawName:(NSString *)_rawName
658 unichar c0 = [_localName characterAtIndex:0];
660 if (c0 == 'g' && [_localName isEqualToString:@"group"])
662 else if (c0 == 'n' && [_localName isEqualToString:@"n"])
664 else if (c0 == 'o' && [_localName isEqualToString:@"org"])
666 else if (c0 == 't' && [_localName isEqualToString:@"tel"])
668 else if (c0 == 'u' && [_localName isEqualToString:@"url"])
670 else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
672 else if (c0 == 'e' && [_localName isEqualToString:@"email"])
674 else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
676 else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
678 else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
680 else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
682 else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
683 [self endCategories];
684 else if (c0 == 'r' && [_localName isEqualToString:@"role"])
686 else if (c0 == 't' && [_localName isEqualToString:@"title"])
688 else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
690 else if (c0 == 'n' && [_localName isEqualToString:@"note"])
692 else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
694 else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
695 [self endFreeBusyURL];
696 else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
698 else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
700 else if (c0 == 'P' && [_localName isEqualToString:@"PROFILE"])
702 else if (c0 == 'S' && [_localName isEqualToString:@"SOURCE"])
704 else if (c0 == 'N' && [_localName isEqualToString:@"NAME"])
707 if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr ||
709 [self endSubContentTag:_localName];
711 [self endX:_localName];
717 - (void)startCollectingContent {
718 if (self->content != NULL) {
720 self->content = NULL;
722 self->vcs.collectContent = 1;
725 - (NSString *)finishCollectingContent {
728 self->vcs.collectContent = 0;
730 if (self->content == NULL)
733 if (self->contentLength == 0)
736 s = [NSString stringWithCharacters:self->content length:self->contentLength];
737 if (self->content != NULL) {
739 self->content = NULL;
744 - (void)characters:(unichar *)_chars length:(int)_len {
745 if (_len == 0 || _chars == NULL)
748 if (self->content == NULL) {
750 self->contentLength = _len;
751 self->content = calloc(_len + 1, sizeof(unichar));
752 memcpy(self->content, _chars, (_len * sizeof(unichar)));
755 /* increase content */
757 realloc(self->content, (self->contentLength + _len+2) * sizeof(unichar));
758 memcpy(&(self->content[self->contentLength]), _chars,
759 (_len * sizeof(unichar)));
760 self->contentLength += _len;
762 self->content[self->contentLength] = 0;
765 @end /* NGVCardSaxHandler */