]> err.no Git - sope/blob - sope-ical/NGiCal/NGVCardSaxHandler.m
aec3c3d4c57fb7f32f15d8f43f2908a99fe51419
[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
552 /* element events */
553
554 - (void)startElement:(NSString *)_localName
555   namespace:(NSString *)_ns
556   rawName:(NSString *)_rawName
557   attributes:(id<SaxAttributes>)_attrs
558 {
559   unichar c0 = [_localName characterAtIndex:0];
560
561   if (c0 == 'g' && [_localName isEqualToString:@"group"])
562     [self startGroup:[_attrs valueForName:@"name" uri:_ns]];
563   else if (c0 == 'n' && [_localName length] == 1)
564     [self startN];
565   else if (c0 == 'o' && [_localName isEqualToString:@"org"])
566     [self startOrg];
567   else if (c0 == 't' && [_localName isEqualToString:@"tel"])
568     [self startTel:_attrs];
569   else if (c0 == 'u' && [_localName isEqualToString:@"url"])
570     [self startURL:_attrs];
571   else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
572     [self startAdr:_attrs];
573   else if (c0 == 'e' && [_localName isEqualToString:@"email"])
574     [self startEmail:_attrs];
575   else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
576     [self startLabel:_attrs];
577   else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
578     [self startVCard:_attrs];
579   else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
580     [self startVCardSet:_attrs];
581   else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
582     [self startNickname:_attrs];
583   else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
584     [self startCategories:_attrs];
585   else if (c0 == 'r' && [_localName isEqualToString:@"role"])
586     [self startRole:_attrs];
587   else if (c0 == 't' && [_localName isEqualToString:@"title"])
588     [self startTitle:_attrs];
589   else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
590     [self startBDay:_attrs];
591   else if (c0 == 'n' && [_localName isEqualToString:@"note"])
592     [self startNote:_attrs];
593   else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
594     [self startCalURI:_attrs];
595   else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
596     [self startFreeBusyURL:_attrs];
597   else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
598     [self startFN:_attrs];
599   else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
600     [self startGeo];
601   else {
602     if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr || 
603         self->vcs.isInGeo)
604       [self startSubContentTag:_attrs];
605     else if (c0 == 'X')
606       [self startX:_localName attributes:_attrs];
607     else
608       NSLog(@"U: %@", _localName);
609   }
610 }
611
612 - (void)endElement:(NSString *)_localName
613   namespace:(NSString *)_ns
614   rawName:(NSString *)_rawName
615 {
616   unichar c0 = [_localName characterAtIndex:0];
617
618   if (c0 == 'g' && [_localName isEqualToString:@"group"])
619     [self endGroup];
620   else if (c0 == 'n' && [_localName isEqualToString:@"n"])
621     [self endN];
622   else if (c0 == 'o' && [_localName isEqualToString:@"org"])
623     [self endOrg];
624   else if (c0 == 't' && [_localName isEqualToString:@"tel"])
625     [self endTel];
626   else if (c0 == 'u' && [_localName isEqualToString:@"url"])
627     [self endURL];
628   else if (c0 == 'a' && [_localName isEqualToString:@"adr"])
629     [self endAdr];
630   else if (c0 == 'e' && [_localName isEqualToString:@"email"])
631     [self endEmail];
632   else if (c0 == 'L' && [_localName isEqualToString:@"LABEL"])
633     [self endLabel];
634   else if (c0 == 'v' && [_localName isEqualToString:@"vCard"])
635     [self endVCard];
636   else if (c0 == 'v' && [_localName isEqualToString:@"vCardSet"])
637     [self endVCardSet];
638   else if (c0 == 'n' && [_localName isEqualToString:@"nickname"])
639     [self endNickname];
640   else if (c0 == 'c' && [_localName isEqualToString:@"categories"])
641     [self endCategories];
642   else if (c0 == 'r' && [_localName isEqualToString:@"role"])
643     [self endRole];
644   else if (c0 == 't' && [_localName isEqualToString:@"title"])
645     [self endTitle];
646   else if (c0 == 'b' && [_localName isEqualToString:@"bday"])
647     [self endBDay];
648   else if (c0 == 'n' && [_localName isEqualToString:@"note"])
649     [self endNote];
650   else if (c0 == 'C' && [_localName isEqualToString:@"CALURI"])
651     [self endCalURI];
652   else if (c0 == 'F' && [_localName isEqualToString:@"FBURL"])
653     [self endFreeBusyURL];
654   else if (c0 == 'f' && [_localName isEqualToString:@"fn"])
655     [self endFN];
656   else if (c0 == 'g' && [_localName isEqualToString:@"geo"])
657     [self endGeo];
658   else {
659     if (self->vcs.isInN || self->vcs.isInOrg || self->vcs.isInAdr ||
660         self->vcs.isInGeo)
661       [self endSubContentTag:_localName];
662     else if (c0 == 'X')
663       [self endX:_localName];
664   }
665 }
666
667 /* content */
668
669 - (void)startCollectingContent {
670   if (self->content != NULL) {
671     free(self->content);
672     self->content = NULL;
673   }
674   self->vcs.collectContent = 1;
675 }
676
677 - (NSString *)finishCollectingContent {
678   NSString *s;
679   
680   self->vcs.collectContent = 0;
681   
682   if (self->content == NULL)
683     return nil;
684   
685   if (self->contentLength == 0)
686     return @"";
687   
688   s = [NSString stringWithCharacters:self->content length:self->contentLength];
689   if (self->content != NULL) {
690     free(self->content);
691     self->content = NULL;
692   }
693   return s;
694 }
695
696 - (void)characters:(unichar *)_chars length:(int)_len {
697   if (_len == 0 || _chars == NULL)
698     return;
699   
700   if (self->content == NULL) {
701     /* first content */
702     self->contentLength = _len;
703     self->content       = calloc(_len + 1, sizeof(unichar));
704     memcpy(self->content, _chars, (_len * sizeof(unichar)));
705   }
706   else {
707     /* increase content */
708     self->content = 
709       realloc(self->content, (self->contentLength + _len+2) * sizeof(unichar));
710     memcpy(&(self->content[self->contentLength]), _chars, 
711            (_len * sizeof(unichar)));
712     self->contentLength += _len;
713   }
714   self->content[self->contentLength] = 0;
715 }
716
717 @end /* NGVCardSaxHandler */