]> err.no Git - sope/blob - sope-ical/NGiCal/NGVCardSaxHandler.m
added Kolab sample data
[sope] / sope-ical / NGiCal / NGVCardSaxHandler.m
1 /*
2   Copyright (C) 2005 Helge Hess
3
4   This file is part of SOPE.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #include "NGVCardSaxHandler.h"
23 #include "NGVCard.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"
31 #include "common.h"
32
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"
36 #endif
37
38 // TODO: this is wayyy to big and complicated ;->
39
40 @implementation NGVCardSaxHandler
41
42 - (void)dealloc {
43   if (self->content != NULL) free(self->content);
44   [self->subvalues    release];
45   [self->xtags        release];
46   [self->tel          release];
47   [self->adr          release];
48   [self->email        release];
49   [self->label        release];
50   [self->url          release];
51   [self->fburl        release];
52   [self->caluri       release];
53   [self->types        release];
54   [self->args         release];
55   [self->vCards       release];
56   [self->vCard        release];
57   [self->currentGroup release];
58   [super dealloc];
59 }
60
61 /* results */
62
63 - (NSArray *)vCards {
64   return self->vCards;
65 }
66
67 /* state */
68
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;
82 }
83
84 - (void)resetExceptResult {
85   [self->vCard release]; self->vCard = nil;
86   
87   [self resetCardState];
88   
89   if (self->content != NULL) {
90     free(self->content);
91     self->content = NULL;
92   }
93   
94   self->vcs.isInVCardSet   = 0;
95   self->vcs.isInVCard      = 0;
96   self->vcs.isInN          = 0;
97   self->vcs.isInAdr        = 0;
98   self->vcs.isInOrg        = 0;
99   self->vcs.isInGroup      = 0;
100   self->vcs.collectContent = 0;
101 }
102
103 - (void)reset {
104   [self resetExceptResult];
105   [self->vCards removeAllObjects];
106 }
107
108 /* document events */
109
110 - (void)startDocument {
111   [self reset];
112   
113   if (self->vCards == nil)
114     self->vCards = [[NSMutableArray alloc] initWithCapacity:16];
115
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];
124
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];
131   
132   if (self->types == nil)
133     self->types = [[NSMutableArray alloc] initWithCapacity:4];
134   if (self->args == nil)
135     self->args = [[NSMutableDictionary alloc] initWithCapacity:8];
136   
137   if (self->subvalues == nil)
138     self->subvalues = [[NSMutableDictionary alloc] initWithCapacity:16];
139   if (self->xtags == nil)
140     self->xtags = [[NSMutableDictionary alloc] initWithCapacity:32];
141 }
142 - (void)endDocument {
143   [self resetExceptResult];
144 }
145
146 /* common tags */
147
148 - (void)startValueTag:(NSString *)_tag attributes:(id<SaxAttributes>)_attrs {
149   /* a tag with types and attributes */
150   unsigned i, count;
151   
152   [self->types removeAllObjects];
153   [self->args  removeAllObjects];
154   
155   for (i = 0, count = [_attrs count]; i < count; i++) {
156     NSString *n, *v;
157     
158     n = [_attrs nameAtIndex:i];
159     v = [_attrs valueAtIndex:i];
160     
161     if ([n hasSuffix:@".type"] || [n isEqualToString:@"TYPE"]) {
162       /*
163         Note: types cannot be separated by comma! Its indeed always a space,eg
164                 "work pref voice"
165               If you find commas, usually the vCard is broken.
166       */
167       NSEnumerator *e;
168       NSString *k;
169       
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];
175       }
176     }
177     else
178       [self->args setObject:v forKey:n];
179   }
180 }
181 - (void)endValueTag {
182   [self->types removeAllObjects];
183   [self->args  removeAllObjects];
184 }
185
186 /* handle elements */
187
188 - (void)startGroup:(NSString *)_name {
189   self->vcs.isInGroup = 1;
190   ASSIGNCOPY(self->currentGroup, _name);
191 }
192 - (void)endGroup {
193   self->vcs.isInGroup = 0;
194   [self->currentGroup release]; self->currentGroup = nil;
195 }
196
197 - (void)startN {
198   [self->subvalues removeAllObjects];
199   self->vcs.isInN = 1;
200 }
201 - (void)endN {
202   NGVCardName *n;
203   
204   self->vcs.isInN = 0;
205
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];
211   [n release];
212 }
213
214 - (void)startOrg {
215   [self->subvalues removeAllObjects];
216   self->vcs.isInOrg = 1;
217 }
218 - (void)endOrg {
219   NGVCardOrg *o;
220   NSArray *u;
221
222   self->vcs.isInOrg = 0;
223
224   if ((u = [self->subvalues objectForKey:@"orgunit"]) != nil) {
225     if (![u isKindOfClass:[NSArray class]])
226       u = [NSArray arrayWithObjects:&u count:1];
227   }
228   
229   // TODO: pass org values!
230   o = [[NGVCardOrg alloc] initWithName:[self->subvalues objectForKey:@"orgnam"]
231                           units:u
232                           group:self->currentGroup
233                           types:self->types arguments:self->args];
234   [self->vCard setOrg:o];
235   [o release];
236   [self->subvalues removeAllObjects];
237 }
238
239 - (void)startGeo {
240   [self->subvalues removeAllObjects];
241   self->vcs.isInGeo = 1;
242 }
243 - (void)endGeo {
244   // TODO
245   
246   self->vcs.isInGeo = 0;
247   
248   [self logWithFormat:@"WARNING: not supporting geo in vCard."];
249   [self->subvalues removeAllObjects];
250 }
251
252 - (void)startVCard:(id<SaxAttributes>)_attrs {
253   NSString *uid, *version;
254   NSString *t;
255   
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];
264   
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;
270   }
271   
272   if ((uid = [_attrs valueForName:@"uid" uri:XMLNS_VCARD_XML_03]) == nil)
273     uid = [_attrs valueForName:@"X-ABUID" uri:XMLNS_VCARD_XML_03];
274   
275   version = [_attrs valueForName:@"version" uri:XMLNS_VCARD_XML_03];
276   
277   self->vCard = [[NGVCard alloc] initWithUid:uid version:version];
278
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!"];
283     // TODO
284   }
285   if ((t = [_attrs valueForName:@"prodid" uri:XMLNS_VCARD_XML_03]) != nil)
286     [self->vCard setProdID:t];
287   
288   [self debugWithFormat:@"started vCard: %@", self->vCard];
289 }
290 - (void)endVCard {
291   self->vcs.isInVCard = 0;
292
293   /* fill collected objects */
294   
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];
303
304   [self->vCards addObject:self->vCard];
305   //[self debugWithFormat:@"finished vCard: %@", self->vCard];
306   
307   [self resetCardState];
308 }
309
310 - (void)startVCardSet:(id<SaxAttributes>)_attrs {
311   self->vcs.isInVCardSet = 1;
312 }
313 - (void)endVCardSet {
314   self->vcs.isInVCardSet = 0;
315 }
316
317 - (void)endBaseContentTagWithClass:(Class)_clazz andAddTo:(NSMutableArray*)_a {
318   NGVCardSimpleValue *v;
319
320   v = [[_clazz alloc] initWithValue:[self finishCollectingContent]
321                       group:self->currentGroup
322                       types:self->types arguments:self->args];
323   [_a addObject:v];
324   
325   [self endValueTag];
326   [v release];
327 }
328
329 - (void)startTel:(id<SaxAttributes>)_attrs {
330   [self startValueTag:@"tel" attributes:_attrs];
331   [self startCollectingContent];
332 }
333 - (void)endTel {
334   [self endBaseContentTagWithClass:[NGVCardPhone class] andAddTo:self->tel];
335 }
336
337 - (void)startAdr:(id<SaxAttributes>)_attrs {
338   [self->subvalues removeAllObjects];
339
340   self->vcs.isInAdr = 1;
341   [self startValueTag:@"adr" attributes:_attrs];
342 }
343 - (void)endAdr {
344   NGVCardAddress *address;
345
346   self->vcs.isInAdr = 0;
347   
348   address = [[NGVCardAddress alloc] initWithPropertyList:self->subvalues
349                                     group:self->currentGroup
350                                     types:self->types arguments:self->args];
351   [self->adr addObject:address];
352   
353   [self->subvalues removeAllObjects];
354   [self endValueTag];
355   [address release];
356 }
357
358 - (void)startEmail:(id<SaxAttributes>)_attrs {
359   [self startValueTag:@"email" attributes:_attrs];
360   [self startCollectingContent];
361 }
362 - (void)endEmail {
363   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
364         andAddTo:self->email];
365 }
366
367 - (void)startLabel:(id<SaxAttributes>)_attrs {
368   [self startValueTag:@"LABEL" attributes:_attrs];
369   [self startCollectingContent];
370 }
371 - (void)endLabel {
372   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
373         andAddTo:self->label];
374 }
375
376 - (void)startURL:(id<SaxAttributes>)_attrs {
377   [self startValueTag:@"url" attributes:_attrs];
378   [self startCollectingContent];
379 }
380 - (void)endURL {
381   // TODO: use special URL class?
382   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
383         andAddTo:self->url];
384 }
385
386 /* tags with comma separated values */
387
388 - (void)startNickname:(id<SaxAttributes>)_attrs {
389   [self startValueTag:@"nickname" attributes:_attrs];
390   [self startCollectingContent];
391 }
392 - (void)endNickname {
393   NGVCardStrArrayValue *v;
394   NSArray *a;
395   
396   // comma unescaping?
397   a = [[self finishCollectingContent] componentsSeparatedByString:@","];
398   
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;
404   
405   [self endValueTag];
406 }
407
408 - (void)startCategories:(id<SaxAttributes>)_attrs {
409   [self startValueTag:@"categories" attributes:_attrs];
410   [self startCollectingContent];
411 }
412 - (void)endCategories {
413   NGVCardStrArrayValue *v;
414   NSArray *a;
415   
416   // comma unescaping?
417   a = [[self finishCollectingContent] componentsSeparatedByString:@","];
418   
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;
424   
425   [self endValueTag];
426 }
427
428 /* generic processing of tags with subtags */
429
430 - (void)startSubContentTag:(id<SaxAttributes>)_attrs {
431   if ([_attrs count] > 0)
432     [self logWithFormat:@"WARNING: loosing attrs of subtag: %@", _attrs];
433   
434   [self startCollectingContent];
435 }
436 - (void)endSubContentTag:(NSString *)_key {
437   NSString *s;
438   id o;
439   
440   if ((s = [[self finishCollectingContent] copy]) == nil)
441     return;
442   
443   if ((o = [self->subvalues objectForKey:_key]) == nil) {
444     [self->subvalues setObject:s forKey:_key];
445   }
446   else {
447     /* multivalue (eg 'org') */
448     if ([o isKindOfClass:[NSMutableArray class]]) {
449       [o addObject:s];
450     }
451     else {
452       NSMutableArray *a;
453       
454       a = [[NSMutableArray alloc] initWithCapacity:4];
455       [a addObject:o];
456       [a addObject:s];
457       [self->subvalues setObject:a forKey:_key];
458       [a release]; a = nil;
459     }
460   }
461   [s release];
462 }
463
464 /* extended tags (X-) */
465
466 - (void)startX:(NSString *)_name attributes:(id<SaxAttributes>)_attrs {
467   [self startValueTag:_name attributes:_attrs];
468   [self startCollectingContent];
469 }
470 - (void)endX:(NSString *)_name {
471   NGVCardSimpleValue *v;
472   NSString *s;
473   id o;
474   
475   s = [self finishCollectingContent];
476   v = [[NGVCardSimpleValue alloc] initWithValue:s
477                                   group:self->currentGroup
478                                   types:self->types arguments:self->args];
479   
480   if ((o = [self->xtags objectForKey:_name]) == nil)
481     [self->xtags setObject:v forKey:_name];
482   else if ([o isKindOfClass:[NSMutableArray class]])
483     [o addObject:v];
484   else {
485     NSMutableArray *a;
486     
487     a = [[NSMutableArray alloc] initWithCapacity:4];
488     [a addObject:o];
489     [a addObject:v];
490     [self->xtags setObject:a forKey:_name];
491     [a release];
492   }
493   
494   [v release];
495   [self endValueTag];
496 }
497
498 /* flat tags */
499
500 - (void)startFN:(id<SaxAttributes>)_attrs {
501   [self startValueTag:@"" attributes:_attrs];
502   [self startCollectingContent];
503 }
504 - (void)endFN {
505   [self->vCard setFn:[self finishCollectingContent]];
506   [self endValueTag];
507 }
508
509 - (void)startRole:(id<SaxAttributes>)_attrs {
510   [self startValueTag:@"" attributes:_attrs];
511   [self startCollectingContent];
512 }
513 - (void)endRole {
514   [self->vCard setRole:[self finishCollectingContent]];
515   [self endValueTag];
516 }
517
518 - (void)startTitle:(id<SaxAttributes>)_attrs {
519   [self startValueTag:@"" attributes:_attrs];
520   [self startCollectingContent];
521 }
522 - (void)endTitle {
523   [self->vCard setTitle:[self finishCollectingContent]];
524   [self endValueTag];
525 }
526
527 - (void)startBDay:(id<SaxAttributes>)_attrs {
528   [self startValueTag:@"" attributes:_attrs];
529   [self startCollectingContent];
530 }
531 - (void)endBDay {
532   [self->vCard setBday:[self finishCollectingContent]];
533   [self endValueTag];
534 }
535
536 - (void)startNote:(id<SaxAttributes>)_attrs {
537   [self startValueTag:@"" attributes:_attrs];
538   [self startCollectingContent];
539 }
540 - (void)endNote {
541   [self->vCard setNote:[self finishCollectingContent]];
542   [self endValueTag];
543 }
544
545 - (void)startCalURI:(id<SaxAttributes>)_attrs {
546   [self startValueTag:@"CALURI" attributes:_attrs];
547   [self startCollectingContent];
548 }
549 - (void)endCalURI {
550   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
551         andAddTo:self->caluri];
552 }
553
554 - (void)startFreeBusyURL:(id<SaxAttributes>)_attrs {
555   [self startValueTag:@"FBURL" attributes:_attrs];
556   [self startCollectingContent];
557 }
558 - (void)endFreeBusyURL {
559   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
560         andAddTo:self->fburl];
561 }
562
563 /* OGo?? tags */
564
565 - (void)startProfile:(id<SaxAttributes>)_attrs {
566   [self startCollectingContent];
567 }
568 - (void)endProfile {
569   [self->vCard setProfile:[self finishCollectingContent]];
570 }
571
572 - (void)startSource:(id<SaxAttributes>)_attrs {
573   [self startCollectingContent];
574 }
575 - (void)endSource {
576   [self->vCard setSource:[self finishCollectingContent]];
577 }
578
579 - (void)startName:(id<SaxAttributes>)_attrs {
580   [self startCollectingContent];
581 }
582 - (void)endName {
583   [self->vCard setVName:[self finishCollectingContent]];
584 }
585
586
587 /* element events */
588
589 - (void)startElement:(NSString *)_localName
590   namespace:(NSString *)_ns
591   rawName:(NSString *)_rawName
592   attributes:(id<SaxAttributes>)_attrs
593 {
594   unichar c0 = [_localName characterAtIndex:0];
595
596   if (c0 == 'g' && [_localName isEqualToString:@"group"])
597     [self startGroup:[_attrs valueForName:@"name" uri:_ns]];
598   else if (c0 == 'n' && [_localName length] == 1)
599     [self startN];
600   else if (c0 == 'o' && [_localName isEqualToString:@"org"])
601     [self startOrg];
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"])
635     [self startGeo];
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];
643   else {
644     if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr || 
645         self->vcs.isInGeo)
646       [self startSubContentTag:_attrs];
647     else if (c0 == 'X')
648       [self startX:_localName attributes:_attrs];
649     else
650       NSLog(@"U: %@", _localName);
651   }
652 }
653
654 - (void)endElement:(NSString *)_localName
655   namespace:(NSString *)_ns
656   rawName:(NSString *)_rawName
657 {
658   unichar c0 = [_localName characterAtIndex:0];
659
660   if (c0 == 'g' && [_localName isEqualToString:@"group"])
661     [self endGroup];
662   else if (c0 == 'n' && [_localName isEqualToString:@"n"])
663     [self endN];
664   else if (c0 == 'o' && [_localName isEqualToString:@"org"])
665     [self endOrg];
666   else if (c0 == 't' && [_localName isEqualToString:@"tel"])
667     [self endTel];
668   else if (c0 == 'u' && [_localName isEqualToString:@"url"])
669     [self endURL];
670   else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
671     [self endAdr];
672   else if (c0 == 'e' && [_localName isEqualToString:@"email"])
673     [self endEmail];
674   else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
675     [self endLabel];
676   else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
677     [self endVCard];
678   else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
679     [self endVCardSet];
680   else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
681     [self endNickname];
682   else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
683     [self endCategories];
684   else if (c0 == 'r' && [_localName isEqualToString:@"role"])
685     [self endRole];
686   else if (c0 == 't' && [_localName isEqualToString:@"title"])
687     [self endTitle];
688   else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
689     [self endBDay];
690   else if (c0 == 'n' && [_localName isEqualToString:@"note"])
691     [self endNote];
692   else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
693     [self endCalURI];
694   else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
695     [self endFreeBusyURL];
696   else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
697     [self endFN];
698   else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
699     [self endGeo];
700   else if (c0 == 'P' && [_localName isEqualToString:@"PROFILE"])
701     [self endProfile];
702   else if (c0 == 'S' && [_localName isEqualToString:@"SOURCE"])
703     [self endSource];
704   else if (c0 == 'N' && [_localName isEqualToString:@"NAME"])
705     [self endName];
706   else {
707     if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr ||
708         self->vcs.isInGeo)
709       [self endSubContentTag:_localName];
710     else if (c0 == 'X')
711       [self endX:_localName];
712   }
713 }
714
715 /* content */
716
717 - (void)startCollectingContent {
718   if (self->content != NULL) {
719     free(self->content);
720     self->content = NULL;
721   }
722   self->vcs.collectContent = 1;
723 }
724
725 - (NSString *)finishCollectingContent {
726   NSString *s;
727   
728   self->vcs.collectContent = 0;
729   
730   if (self->content == NULL)
731     return nil;
732   
733   if (self->contentLength == 0)
734     return @"";
735   
736   s = [NSString stringWithCharacters:self->content length:self->contentLength];
737   if (self->content != NULL) {
738     free(self->content);
739     self->content = NULL;
740   }
741   return s;
742 }
743
744 - (void)characters:(unichar *)_chars length:(int)_len {
745   if (_len == 0 || _chars == NULL)
746     return;
747   
748   if (self->content == NULL) {
749     /* first content */
750     self->contentLength = _len;
751     self->content       = calloc(_len + 1, sizeof(unichar));
752     memcpy(self->content, _chars, (_len * sizeof(unichar)));
753   }
754   else {
755     /* increase content */
756     self->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;
761   }
762   self->content[self->contentLength] = 0;
763 }
764
765 @end /* NGVCardSaxHandler */