]> err.no Git - sope/blob - sope-ical/NGiCal/NGVCardSaxHandler.m
added some properties
[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       if (![self->types containsObject:v]) 
163         [self->types addObjectsFromArray:[v componentsSeparatedByString:@" "]];
164     }
165     else
166       [self->args setObject:v forKey:n];
167   }
168 }
169 - (void)endValueTag {
170   [self->types removeAllObjects];
171   [self->args  removeAllObjects];
172 }
173
174 /* handle elements */
175
176 - (void)startGroup:(NSString *)_name {
177   self->vcs.isInGroup = 1;
178   ASSIGNCOPY(self->currentGroup, _name);
179 }
180 - (void)endGroup {
181   self->vcs.isInGroup = 0;
182   [self->currentGroup release]; self->currentGroup = nil;
183 }
184
185 - (void)startN {
186   [self->subvalues removeAllObjects];
187   self->vcs.isInN = 1;
188 }
189 - (void)endN {
190   NGVCardName *n;
191   
192   self->vcs.isInN = 0;
193
194   n = [[NGVCardName alloc] initWithPropertyList:self->subvalues
195                            group:self->currentGroup
196                            types:self->types arguments:self->args];
197   [self->vCard setN:n];
198   [self->subvalues removeAllObjects];
199   [n release];
200 }
201
202 - (void)startOrg {
203   [self->subvalues removeAllObjects];
204   self->vcs.isInOrg = 1;
205 }
206 - (void)endOrg {
207   NGVCardOrg *o;
208   NSArray *u;
209
210   self->vcs.isInOrg = 0;
211
212   if ((u = [self->subvalues objectForKey:@"orgunit"]) != nil) {
213     if (![u isKindOfClass:[NSArray class]])
214       u = [NSArray arrayWithObjects:&u count:1];
215   }
216   
217   // TODO: pass org values!
218   o = [[NGVCardOrg alloc] initWithName:[self->subvalues objectForKey:@"orgnam"]
219                           units:u
220                           group:self->currentGroup
221                           types:self->types arguments:self->args];
222   [self->vCard setOrg:o];
223   [o release];
224   [self->subvalues removeAllObjects];
225 }
226
227 - (void)startGeo {
228   [self->subvalues removeAllObjects];
229   self->vcs.isInGeo = 1;
230 }
231 - (void)endGeo {
232   // TODO
233   
234   self->vcs.isInGeo = 0;
235   
236   [self logWithFormat:@"WARNING: not supporting geo in vCard."];
237   [self->subvalues removeAllObjects];
238 }
239
240 - (void)startVCard:(id<SaxAttributes>)_attrs {
241   NSString *uid, *version;
242   NSString *t;
243   
244   [self->tel    removeAllObjects];
245   [self->adr    removeAllObjects];
246   [self->email  removeAllObjects];
247   [self->label  removeAllObjects];
248   [self->url    removeAllObjects];
249   [self->fburl  removeAllObjects];
250   [self->caluri removeAllObjects];
251   [self->xtags  removeAllObjects];
252   
253   self->vcs.isInVCard = 1;
254   if (self->vCard != nil) {
255     [self->vCards addObject:self->vCard];
256     [self logWithFormat:@"ERROR: vCard nesting not supported!"];
257     [self->vCard release]; self->vCard = nil;
258   }
259   
260   if ((uid = [_attrs valueForName:@"uid" uri:XMLNS_VCARD_XML_03]) == nil)
261     uid = [_attrs valueForName:@"X-ABUID" uri:XMLNS_VCARD_XML_03];
262   
263   version = [_attrs valueForName:@"version" uri:XMLNS_VCARD_XML_03];
264   
265   self->vCard = [[NGVCard alloc] initWithUid:uid version:version];
266
267   if ((t = [_attrs valueForName:@"class" uri:XMLNS_VCARD_XML_03]) != nil)
268     [self->vCard setVClass:t];
269   if ((t = [_attrs valueForName:@"rev" uri:XMLNS_VCARD_XML_03]) != nil) {
270     [self logWithFormat:@"WARNING: vCard revision not yet supported!"];
271     // TODO
272   }
273   if ((t = [_attrs valueForName:@"prodid" uri:XMLNS_VCARD_XML_03]) != nil)
274     [self->vCard setProdID:t];
275   
276   [self debugWithFormat:@"started vCard: %@", self->vCard];
277 }
278 - (void)endVCard {
279   self->vcs.isInVCard = 0;
280
281   /* fill collected objects */
282   
283   if ([self->tel    count] > 0) [self->vCard setTel:self->tel];
284   if ([self->adr    count] > 0) [self->vCard setAdr:self->adr];
285   if ([self->email  count] > 0) [self->vCard setEmail:self->email];
286   if ([self->label  count] > 0) [self->vCard setLabel:self->label];
287   if ([self->url    count] > 0) [self->vCard setUrl:self->url];
288   if ([self->fburl  count] > 0) [self->vCard setFreeBusyURL:self->fburl];
289   if ([self->caluri count] > 0) [self->vCard setCalURI:self->caluri];
290   if ([self->xtags  count] > 0) [self->vCard setX:self->xtags];
291
292   [self->vCards addObject:self->vCard];
293   //[self debugWithFormat:@"finished vCard: %@", self->vCard];
294   
295   [self resetCardState];
296 }
297
298 - (void)startVCardSet:(id<SaxAttributes>)_attrs {
299   self->vcs.isInVCardSet = 1;
300 }
301 - (void)endVCardSet {
302   self->vcs.isInVCardSet = 0;
303 }
304
305 - (void)endBaseContentTagWithClass:(Class)_clazz andAddTo:(NSMutableArray*)_a {
306   NGVCardSimpleValue *v;
307
308   v = [[_clazz alloc] initWithValue:[self finishCollectingContent]
309                       group:self->currentGroup
310                       types:self->types arguments:self->args];
311   [_a addObject:v];
312   
313   [self endValueTag];
314   [v release];
315 }
316
317 - (void)startTel:(id<SaxAttributes>)_attrs {
318   [self startValueTag:@"tel" attributes:_attrs];
319   [self startCollectingContent];
320 }
321 - (void)endTel {
322   [self endBaseContentTagWithClass:[NGVCardPhone class] andAddTo:self->tel];
323 }
324
325 - (void)startAdr:(id<SaxAttributes>)_attrs {
326   [self->subvalues removeAllObjects];
327
328   self->vcs.isInAdr = 1;
329   [self startValueTag:@"adr" attributes:_attrs];
330 }
331 - (void)endAdr {
332   NGVCardAddress *address;
333
334   self->vcs.isInAdr = 0;
335   
336   address = [[NGVCardAddress alloc] initWithPropertyList:self->subvalues
337                                     group:self->currentGroup
338                                     types:self->types arguments:self->args];
339   [self->adr addObject:address];
340   
341   [self->subvalues removeAllObjects];
342   [self endValueTag];
343   [address release];
344 }
345
346 - (void)startEmail:(id<SaxAttributes>)_attrs {
347   [self startValueTag:@"email" attributes:_attrs];
348   [self startCollectingContent];
349 }
350 - (void)endEmail {
351   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
352         andAddTo:self->email];
353 }
354
355 - (void)startLabel:(id<SaxAttributes>)_attrs {
356   [self startValueTag:@"LABEL" attributes:_attrs];
357   [self startCollectingContent];
358 }
359 - (void)endLabel {
360   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
361         andAddTo:self->label];
362 }
363
364 - (void)startURL:(id<SaxAttributes>)_attrs {
365   [self startValueTag:@"url" attributes:_attrs];
366   [self startCollectingContent];
367 }
368 - (void)endURL {
369   // TODO: use special URL class?
370   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
371         andAddTo:self->url];
372 }
373
374 /* tags with comma separated values */
375
376 - (void)startNickname:(id<SaxAttributes>)_attrs {
377   [self startValueTag:@"nickname" attributes:_attrs];
378   [self startCollectingContent];
379 }
380 - (void)endNickname {
381   NGVCardStrArrayValue *v;
382   NSArray *a;
383   
384   // comma unescaping?
385   a = [[self finishCollectingContent] componentsSeparatedByString:@","];
386   
387   v = [[NGVCardStrArrayValue alloc] initWithArray:a
388                                     group:self->currentGroup
389                                     types:self->types arguments:self->args];
390   [self->vCard setNickname:v];
391   [v release]; v = nil;
392   
393   [self endValueTag];
394 }
395
396 - (void)startCategories:(id<SaxAttributes>)_attrs {
397   [self startValueTag:@"categories" attributes:_attrs];
398   [self startCollectingContent];
399 }
400 - (void)endCategories {
401   NGVCardStrArrayValue *v;
402   NSArray *a;
403   
404   // comma unescaping?
405   a = [[self finishCollectingContent] componentsSeparatedByString:@","];
406   
407   v = [[NGVCardStrArrayValue alloc] initWithArray:a
408                                     group:self->currentGroup
409                                     types:self->types arguments:self->args];
410   [self->vCard setCategories:v];
411   [v release]; v = nil;
412   
413   [self endValueTag];
414 }
415
416 /* generic processing of tags with subtags */
417
418 - (void)startSubContentTag:(id<SaxAttributes>)_attrs {
419   if ([_attrs count] > 0)
420     [self logWithFormat:@"WARNING: loosing attrs of subtag: %@", _attrs];
421   
422   [self startCollectingContent];
423 }
424 - (void)endSubContentTag:(NSString *)_key {
425   NSString *s;
426   id o;
427   
428   if ((s = [[self finishCollectingContent] copy]) == nil)
429     return;
430   
431   if ((o = [self->subvalues objectForKey:_key]) == nil) {
432     [self->subvalues setObject:s forKey:_key];
433   }
434   else {
435     /* multivalue (eg 'org') */
436     if ([o isKindOfClass:[NSMutableArray class]]) {
437       [o addObject:s];
438     }
439     else {
440       NSMutableArray *a;
441       
442       a = [[NSMutableArray alloc] initWithCapacity:4];
443       [a addObject:o];
444       [a addObject:s];
445       [self->subvalues setObject:a forKey:_key];
446       [a release]; a = nil;
447     }
448   }
449   [s release];
450 }
451
452 /* extended tags (X-) */
453
454 - (void)startX:(NSString *)_name attributes:(id<SaxAttributes>)_attrs {
455   [self startValueTag:_name attributes:_attrs];
456   [self startCollectingContent];
457 }
458 - (void)endX:(NSString *)_name {
459   NGVCardSimpleValue *v;
460   NSString *s;
461   id o;
462   
463   s = [self finishCollectingContent];
464   v = [[NGVCardSimpleValue alloc] initWithValue:s
465                                   group:self->currentGroup
466                                   types:self->types arguments:self->args];
467   
468   if ((o = [self->xtags objectForKey:_name]) == nil)
469     [self->xtags setObject:v forKey:_name];
470   else if ([o isKindOfClass:[NSMutableArray class]])
471     [o addObject:v];
472   else {
473     NSMutableArray *a;
474     
475     a = [[NSMutableArray alloc] initWithCapacity:4];
476     [a addObject:o];
477     [a addObject:v];
478     [self->xtags setObject:a forKey:_name];
479     [a release];
480   }
481   
482   [v release];
483   [self endValueTag];
484 }
485
486 /* flat tags */
487
488 - (void)startFN:(id<SaxAttributes>)_attrs {
489   [self startValueTag:@"" attributes:_attrs];
490   [self startCollectingContent];
491 }
492 - (void)endFN {
493   [self->vCard setFn:[self finishCollectingContent]];
494   [self endValueTag];
495 }
496
497 - (void)startRole:(id<SaxAttributes>)_attrs {
498   [self startValueTag:@"" attributes:_attrs];
499   [self startCollectingContent];
500 }
501 - (void)endRole {
502   [self->vCard setRole:[self finishCollectingContent]];
503   [self endValueTag];
504 }
505
506 - (void)startTitle:(id<SaxAttributes>)_attrs {
507   [self startValueTag:@"" attributes:_attrs];
508   [self startCollectingContent];
509 }
510 - (void)endTitle {
511   [self->vCard setTitle:[self finishCollectingContent]];
512   [self endValueTag];
513 }
514
515 - (void)startBDay:(id<SaxAttributes>)_attrs {
516   [self startValueTag:@"" attributes:_attrs];
517   [self startCollectingContent];
518 }
519 - (void)endBDay {
520   [self->vCard setBday:[self finishCollectingContent]];
521   [self endValueTag];
522 }
523
524 - (void)startNote:(id<SaxAttributes>)_attrs {
525   [self startValueTag:@"" attributes:_attrs];
526   [self startCollectingContent];
527 }
528 - (void)endNote {
529   [self->vCard setNote:[self finishCollectingContent]];
530   [self endValueTag];
531 }
532
533 - (void)startCalURI:(id<SaxAttributes>)_attrs {
534   [self startValueTag:@"CALURI" attributes:_attrs];
535   [self startCollectingContent];
536 }
537 - (void)endCalURI {
538   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
539         andAddTo:self->caluri];
540 }
541
542 - (void)startFreeBusyURL:(id<SaxAttributes>)_attrs {
543   [self startValueTag:@"FBURL" attributes:_attrs];
544   [self startCollectingContent];
545 }
546 - (void)endFreeBusyURL {
547   [self endBaseContentTagWithClass:[NGVCardSimpleValue class]
548         andAddTo:self->fburl];
549 }
550
551 /* OGo?? tags */
552
553 - (void)startProfile:(id<SaxAttributes>)_attrs {
554   [self startCollectingContent];
555 }
556 - (void)endProfile {
557   [self->vCard setProfile:[self finishCollectingContent]];
558 }
559
560 - (void)startSource:(id<SaxAttributes>)_attrs {
561   [self startCollectingContent];
562 }
563 - (void)endSource {
564   [self->vCard setSource:[self finishCollectingContent]];
565 }
566
567 - (void)startName:(id<SaxAttributes>)_attrs {
568   [self startCollectingContent];
569 }
570 - (void)endName {
571   [self->vCard setVName:[self finishCollectingContent]];
572 }
573
574
575 /* element events */
576
577 - (void)startElement:(NSString *)_localName
578   namespace:(NSString *)_ns
579   rawName:(NSString *)_rawName
580   attributes:(id<SaxAttributes>)_attrs
581 {
582   unichar c0 = [_localName characterAtIndex:0];
583
584   if (c0 == 'g' && [_localName isEqualToString:@"group"])
585     [self startGroup:[_attrs valueForName:@"name" uri:_ns]];
586   else if (c0 == 'n' && [_localName length] == 1)
587     [self startN];
588   else if (c0 == 'o' && [_localName isEqualToString:@"org"])
589     [self startOrg];
590   else if (c0 == 't' && [_localName isEqualToString:@"tel"])
591     [self startTel:_attrs];
592   else if (c0 == 'u' && [_localName isEqualToString:@"url"])
593     [self startURL:_attrs];
594   else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
595     [self startAdr:_attrs];
596   else if (c0 == 'e' && [_localName isEqualToString:@"email"])
597     [self startEmail:_attrs];
598   else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
599     [self startLabel:_attrs];
600   else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
601     [self startVCard:_attrs];
602   else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
603     [self startVCardSet:_attrs];
604   else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
605     [self startNickname:_attrs];
606   else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
607     [self startCategories:_attrs];
608   else if (c0 == 'r' && [_localName isEqualToString:@"role"])
609     [self startRole:_attrs];
610   else if (c0 == 't' && [_localName isEqualToString:@"title"])
611     [self startTitle:_attrs];
612   else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
613     [self startBDay:_attrs];
614   else if (c0 == 'n' && [_localName isEqualToString:@"note"])
615     [self startNote:_attrs];
616   else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
617     [self startCalURI:_attrs];
618   else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
619     [self startFreeBusyURL:_attrs];
620   else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
621     [self startFN:_attrs];
622   else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
623     [self startGeo];
624   // TODO: following are generated by LSAddress, but not in spec?
625   else if (c0 == 'P' && [_localName isEqualToString:@"PROFILE"])
626     [self startProfile:_attrs];
627   else if (c0 == 'S' && [_localName isEqualToString:@"SOURCE"])
628     [self startSource:_attrs];
629   else if (c0 == 'N' && [_localName isEqualToString:@"NAME"])
630     [self startName:_attrs];
631   else {
632     if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr || 
633         self->vcs.isInGeo)
634       [self startSubContentTag:_attrs];
635     else if (c0 == 'X')
636       [self startX:_localName attributes:_attrs];
637     else
638       NSLog(@"U: %@", _localName);
639   }
640 }
641
642 - (void)endElement:(NSString *)_localName
643   namespace:(NSString *)_ns
644   rawName:(NSString *)_rawName
645 {
646   unichar c0 = [_localName characterAtIndex:0];
647
648   if (c0 == 'g' && [_localName isEqualToString:@"group"])
649     [self endGroup];
650   else if (c0 == 'n' && [_localName isEqualToString:@"n"])
651     [self endN];
652   else if (c0 == 'o' && [_localName isEqualToString:@"org"])
653     [self endOrg];
654   else if (c0 == 't' && [_localName isEqualToString:@"tel"])
655     [self endTel];
656   else if (c0 == 'u' && [_localName isEqualToString:@"url"])
657     [self endURL];
658   else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
659     [self endAdr];
660   else if (c0 == 'e' && [_localName isEqualToString:@"email"])
661     [self endEmail];
662   else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
663     [self endLabel];
664   else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
665     [self endVCard];
666   else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
667     [self endVCardSet];
668   else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
669     [self endNickname];
670   else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
671     [self endCategories];
672   else if (c0 == 'r' && [_localName isEqualToString:@"role"])
673     [self endRole];
674   else if (c0 == 't' && [_localName isEqualToString:@"title"])
675     [self endTitle];
676   else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
677     [self endBDay];
678   else if (c0 == 'n' && [_localName isEqualToString:@"note"])
679     [self endNote];
680   else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
681     [self endCalURI];
682   else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
683     [self endFreeBusyURL];
684   else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
685     [self endFN];
686   else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
687     [self endGeo];
688   else if (c0 == 'P' && [_localName isEqualToString:@"PROFILE"])
689     [self endProfile];
690   else if (c0 == 'S' && [_localName isEqualToString:@"SOURCE"])
691     [self endSource];
692   else if (c0 == 'N' && [_localName isEqualToString:@"NAME"])
693     [self endName];
694   else {
695     if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr ||
696         self->vcs.isInGeo)
697       [self endSubContentTag:_localName];
698     else if (c0 == 'X')
699       [self endX:_localName];
700   }
701 }
702
703 /* content */
704
705 - (void)startCollectingContent {
706   if (self->content != NULL) {
707     free(self->content);
708     self->content = NULL;
709   }
710   self->vcs.collectContent = 1;
711 }
712
713 - (NSString *)finishCollectingContent {
714   NSString *s;
715   
716   self->vcs.collectContent = 0;
717   
718   if (self->content == NULL)
719     return nil;
720   
721   if (self->contentLength == 0)
722     return @"";
723   
724   s = [NSString stringWithCharacters:self->content length:self->contentLength];
725   if (self->content != NULL) {
726     free(self->content);
727     self->content = NULL;
728   }
729   return s;
730 }
731
732 - (void)characters:(unichar *)_chars length:(int)_len {
733   if (_len == 0 || _chars == NULL)
734     return;
735   
736   if (self->content == NULL) {
737     /* first content */
738     self->contentLength = _len;
739     self->content       = calloc(_len + 1, sizeof(unichar));
740     memcpy(self->content, _chars, (_len * sizeof(unichar)));
741   }
742   else {
743     /* increase content */
744     self->content = 
745       realloc(self->content, (self->contentLength + _len+2) * sizeof(unichar));
746     memcpy(&(self->content[self->contentLength]), _chars, 
747            (_len * sizeof(unichar)));
748     self->contentLength += _len;
749   }
750   self->content[self->contentLength] = 0;
751 }
752
753 @end /* NGVCardSaxHandler */