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