]> err.no Git - sope/blob - sope-ical/versitSaxDriver/VSSaxDriver.m
added support vcard type groupings
[sope] / sope-ical / versitSaxDriver / VSSaxDriver.m
1 /*
2  Copyright (C) 2003-2004 Max Berger
3  Copyright (C) 2004-2005 OpenGroupware.org
4
5  This file is part of versitSaxDriver, written for the OpenGroupware.org 
6  project (OGo).
7  
8  SOPE is free software; you can redistribute it and/or modify it under
9  the terms of the GNU Lesser General Public License as published by the
10  Free Software Foundation; either version 2, or (at your option) any
11  later version.
12  
13  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
14  WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
16  License for more details.
17  
18  You should have received a copy of the GNU Lesser General Public
19  License along with SOPE; see the file COPYING.  If not, write to the
20  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
21  02111-1307, USA.
22 */
23
24 #include "VSSaxDriver.h"
25 #include "VSStringFormatter.h"
26 #include <SaxObjC/SaxException.h>
27 #include "common.h"
28
29 @interface VSSaxTag : NSObject
30 {
31 @private
32   char          type;
33   NSString      *tagName;
34   NSString      *group;
35 @public
36   SaxAttributes *attrs;
37   unichar       *data;
38   unsigned int  datalen;
39 }
40
41 + (id)beginTag:(NSString *)_tag group:(NSString *)_group
42   attributes:(SaxAttributes *)_attrs;
43 - (id)initEndTag:(NSString *)_tag;
44
45 - (id)initWithContentString:(NSString *)_data;
46
47 - (NSString *)tagName;
48 - (BOOL)isStartTag;
49 - (BOOL)isEndTag;
50 - (BOOL)isTag;
51
52 @end
53
54 @implementation VSSaxTag
55
56 + (id)beginTag:(NSString *)_tag group:(NSString *)_group
57   attributes:(SaxAttributes *)_attrs
58 {
59   VSSaxTag *tag;
60
61   tag = [[[self alloc] init] autorelease];
62   tag->type    = 'B';
63   tag->tagName = [_tag copy];
64   tag->group   = [_group copy];
65   tag->attrs   = [_attrs retain];
66   return tag;
67 }
68 - (id)initEndTag:(NSString *)_tag {
69   self->type    = 'E';
70   self->tagName = [_tag copy];
71   return self;
72 }
73 - (id)initWithContentString:(NSString *)_data {
74   if (_data == nil) {
75     [self release];
76     return nil;
77   }
78   
79   self->datalen = [_data length];
80   self->data    = calloc(self->datalen + 1, sizeof(unichar));
81   [_data getCharacters:self->data range:NSMakeRange(0, self->datalen)];
82   return self;
83 }
84
85 - (void)dealloc {
86   if (self->data) free(self->data);
87   [self->group   release];
88   [self->tagName release];
89   [self->attrs   release];
90   [super dealloc];
91 }
92
93 /* accessors */
94
95 - (NSString *)tagName {
96   return self->tagName;
97 }
98 - (NSString *)group {
99   return self->group;
100 }
101
102 - (BOOL)isStartTag {
103   return self->type == 'B' ? YES : NO;
104 }
105 - (BOOL)isEndTag {
106   return self->type == 'E' ? YES : NO;
107 }
108 - (BOOL)isTag {
109   return (self->type == 'B' || self->type == 'E') ? YES : NO;
110 }
111
112 @end /* VSSaxTag */
113
114 @implementation VSSaxDriver
115
116 static BOOL debugOn = NO;
117
118 static NSCharacterSet *dotCharSet                     = nil;
119 static NSCharacterSet *equalSignCharSet               = nil;
120 static NSCharacterSet *commaCharSet                   = nil;
121 static NSCharacterSet *colonAndSemicolonCharSet       = nil;
122 static NSCharacterSet *colonSemicolonAndDquoteCharSet = nil;
123 static NSCharacterSet *whitespaceCharSet              = nil;
124
125 static VSStringFormatter *stringFormatter = nil;
126
127 + (void)initialize {
128   static BOOL didInit = NO;
129   NSUserDefaults *ud;
130
131   if (didInit)
132     return;
133   didInit = YES;
134
135   ud      = [NSUserDefaults standardUserDefaults];
136   debugOn = [ud boolForKey:@"VSSaxDriverDebugEnabled"];
137
138   dotCharSet =
139     [[NSCharacterSet characterSetWithCharactersInString:@"."] retain];
140   equalSignCharSet =
141     [[NSCharacterSet characterSetWithCharactersInString:@"="] retain];
142   commaCharSet =
143     [[NSCharacterSet characterSetWithCharactersInString:@","] retain];
144   colonAndSemicolonCharSet =
145     [[NSCharacterSet characterSetWithCharactersInString:@":;"] retain];
146   colonSemicolonAndDquoteCharSet =
147     [[NSCharacterSet characterSetWithCharactersInString:@":;\""] retain];
148   whitespaceCharSet =
149     [[NSCharacterSet whitespaceCharacterSet] retain];
150
151   stringFormatter = [VSStringFormatter sharedFormatter];
152 }
153
154
155 - (id)init {
156   if ((self = [super init])) {
157     self->prefixURI         = @"";
158     self->cardStack         = [[NSMutableArray alloc]      initWithCapacity:4];
159     self->elementList       = [[NSMutableArray alloc]      initWithCapacity:8];
160     self->attributeMapping  = [[NSMutableDictionary alloc] initWithCapacity:8];
161     self->subItemMapping    = [[NSMutableDictionary alloc] initWithCapacity:8];
162   }
163   return self;
164 }
165
166 - (void)dealloc {
167   [self->contentHandler    release];
168   [self->errorHandler      release];
169   [self->prefixURI         release];
170   [self->cardStack         release];
171   [self->elementList       release];
172   [self->attributeElements release];
173   [self->elementMapping    release];
174   [self->attributeMapping  release];
175   [self->subItemMapping    release];
176   [super dealloc];
177 }
178
179 /* accessors */
180
181 - (void)setFeature:(NSString *)_name to:(BOOL)_value {
182 }
183 - (BOOL)feature:(NSString *)_name {
184   return NO;
185 }
186
187 - (void)setProperty:(NSString *)_name to:(id)_value {
188 }
189 - (id)property:(NSString *)_name {
190   return nil;
191 }
192
193 /* handlers */
194
195 - (void)setContentHandler:(id<NSObject,SaxContentHandler>)_handler {
196   ASSIGN(self->contentHandler, _handler);
197 }
198
199 - (void)setDTDHandler:(id<NSObject,SaxDTDHandler>)_handler {
200   // FIXME
201 }
202
203 - (void)setErrorHandler:(id<NSObject,SaxErrorHandler>)_handler {
204   ASSIGN(self->errorHandler, _handler);
205 }
206 - (void)setEntityResolver:(id<NSObject,SaxEntityResolver>)_handler {
207   // FIXME
208 }
209
210 - (id<NSObject,SaxContentHandler>)contentHandler {
211   return self->contentHandler;
212 }
213
214 - (id<NSObject,SaxDTDHandler>)dtdHandler {
215   // FIXME
216   return nil;
217 }
218
219 - (id<NSObject,SaxErrorHandler>)errorHandler {
220   return self->errorHandler;
221 }
222 - (id<NSObject,SaxEntityResolver>)entityResolver {
223   // FIXME
224   return nil;
225 }
226
227 - (void)setPrefixURI:(NSString *)_uri {
228   ASSIGNCOPY(self->prefixURI, _uri);
229 }
230 - (NSString *)prefixURI {
231   return self->prefixURI;
232 }
233
234 - (void)setAttributeElements:(NSSet *)_elements {
235   ASSIGNCOPY(self->attributeElements, _elements);
236 }
237 - (NSSet *)attributeElements {
238   return self->attributeElements;
239 }
240
241 - (void)setElementMapping:(NSDictionary *)_mapping {
242   ASSIGNCOPY(self->elementMapping, _mapping);
243 }
244 - (NSDictionary *)elementMapping {
245   return self->elementMapping;
246 }
247
248 - (void)setAttributeMapping:(NSDictionary *)_mapping {
249   [self setAttributeMapping:_mapping forElement:@""];
250 }
251
252 - (void)setAttributeMapping:(NSDictionary *)_mapping 
253   forElement:(NSString *)_element 
254 {
255   if (_element == nil)
256     _element = @"";
257   [attributeMapping setObject:_mapping forKey:_element];
258 }
259
260 - (void)setSubItemMapping:(NSArray *)_mapping forElement:(NSString *)_element {
261   [subItemMapping setObject:_mapping forKey:_element];  
262 }
263
264
265
266 /* parsing */
267
268 - (NSString *)_groupFromTagName:(NSString *)_tagName {
269   NSRange  r;
270   
271   r = [_tagName rangeOfCharacterFromSet:dotCharSet];
272   if (r.length == 0)
273     return nil;
274   
275   return [_tagName substringToIndex:r.location];
276 }
277
278 - (NSString *)_mapTagName:(NSString *)_tagName {
279   NSString *ret;
280   NSRange  r;
281
282   if ((ret = [self->elementMapping objectForKey:_tagName]) != nil)
283     return ret;
284
285   //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping);
286   ret = _tagName;
287   
288   /*
289     This is to allow parsing of vCards produced by Apple
290     Addressbook.
291     The dot-notation is described as 'grouping' in RFC 2425, section 5.8.2.
292   */
293   r = [_tagName rangeOfCharacterFromSet:dotCharSet];
294   if (r.length > 0)
295     ret = [self _mapTagName:[_tagName substringFromIndex:(r.location + 1)]];
296   
297   return ret;
298 }
299
300 - (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName {
301   NSDictionary *tagMap;
302   NSString *mappedName;
303
304   /* check whether we have a attr-map stored under the element-name */
305   tagMap = [self->attributeMapping objectForKey:_tagName];
306   if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
307     return mappedName;
308   
309   /* check whether we have a attr-map stored under the mapped element-name */
310   tagMap = [self->attributeMapping objectForKey:[self _mapTagName:_tagName]];
311   if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
312     return mappedName;
313
314   /* check whether we have a global attr-map */
315   tagMap = [self->attributeMapping objectForKey:@""];
316   if ((mappedName = [tagMap objectForKey:_attrName]) != nil)
317     return mappedName;
318   
319   /* return the name as-is */
320   return _attrName;
321 }
322
323 - (void)_parseAttr:(NSString *)_attr 
324   forTag:(NSString *)_tagName
325   intoAttr:(NSString **)attr_
326   intoValue:(NSString **)value_
327 {
328   NSRange  r;
329   NSString *attrName, *attrValue, *mappedName;
330   
331   r = [_attr rangeOfCharacterFromSet:equalSignCharSet];
332   if (r.length > 0) {
333     unsigned left, right;
334
335     attrName  = [[_attr substringToIndex:r.location] uppercaseString];
336     left = NSMaxRange(r);
337     right = [_attr length] - 1;
338     if (left < right) {
339       if (([_attr characterAtIndex:left]  == '"') &&
340          ([_attr characterAtIndex:right] == '"'))
341       {
342         left += 1;
343         r = NSMakeRange(left, right - left);
344         attrValue = [_attr substringWithRange:r];
345       }
346       else {
347         attrValue = [_attr substringFromIndex:left];
348       }
349     }
350     else if (left == right) {
351       attrValue = [_attr substringFromIndex:left];
352     }
353     else {
354       attrValue = @"";
355     }
356   }
357   else {
358     attrName  = @"TYPE";
359     attrValue = _attr;
360   }
361   
362 #if 0
363   // ZNeK: what's this for?
364   r = [attrValue rangeOfCharacterFromSet:commaCharSet];
365   while (r.length > 0) {
366     [attrValue replaceCharactersInRange:r withString:@" "];
367     r = [attrValue rangeOfCharacterFromSet:commaCharSet];
368   }
369 #endif
370
371   mappedName = [self _mapAttrName:attrName forTag:_tagName];
372   *attr_ = mappedName;
373   *value_ = [stringFormatter stringByUnescapingRFC2445Text:attrValue];
374 }
375
376 - (SaxAttributes *)_mapAttrs:(NSArray *)_attrs forTag:(NSString *)_tagName {
377   SaxAttributes       *retAttrs;
378   NSEnumerator        *attrEnum;
379   NSString            *curAttr, *mappedAttr, *mappedValue, *oldValue;
380   NSMutableDictionary *attributes;
381
382   if (_attrs == nil || [_attrs count] == 0)
383     return nil;
384
385   attributes = [[NSMutableDictionary alloc] init];
386   retAttrs   = [[[SaxAttributes alloc] init] autorelease];
387   
388   attrEnum = [_attrs objectEnumerator];
389   while ((curAttr = [attrEnum nextObject]) != nil) {
390     [self _parseAttr:curAttr
391           forTag:_tagName
392           intoAttr:&mappedAttr
393           intoValue:&mappedValue];
394     if ((oldValue = [attributes objectForKey:mappedAttr])) {
395       NSString *val;
396       /* ZNeK: duh! */
397       val = [NSString stringWithFormat:@"%@ %@",oldValue, mappedValue];
398       [attributes setObject:val forKey:mappedAttr];
399     }
400     else  
401       [attributes setObject:mappedValue forKey:mappedAttr];
402   }
403
404   attrEnum = [attributes keyEnumerator];
405   while ((curAttr = [attrEnum nextObject]) != nil) {
406     [retAttrs addAttribute:curAttr uri:self->prefixURI rawName:curAttr
407               type:@"CDATA" value:[attributes objectForKey:curAttr]];
408   }
409   
410   [attributes release];
411   
412   return retAttrs;
413 }
414
415 - (VSSaxTag *)_beginTag:(NSString *)_tagName group:(NSString *)_group
416   withAttrs:(SaxAttributes *)_attrs
417 {
418   VSSaxTag *tag;
419   
420   tag = [VSSaxTag beginTag:_tagName group:_group attributes:_attrs];
421   [self->elementList addObject:tag];
422   return tag;
423 }
424
425 - (void)_endTag:(NSString *)_tagName {
426   VSSaxTag *tag;
427   
428   tag = [[VSSaxTag alloc] initEndTag:_tagName];
429   [self->elementList addObject:tag];
430   [tag release]; tag = nil;
431 }
432
433 - (void)_addSubItems:(NSArray *)_items group:(NSString *)_group
434   withData:(NSString *)_content
435 {
436   NSEnumerator *itemEnum, *contentEnum;
437   NSString *subTag;
438   
439   itemEnum    = [_items objectEnumerator];
440   contentEnum = [[_content componentsSeparatedByString:@";"] objectEnumerator];
441   
442   while ((subTag = [itemEnum nextObject]) != nil) {
443     NSString *subContent;
444     
445     subContent = [contentEnum nextObject];
446     
447     [self _beginTag:subTag group:_group withAttrs:nil];
448     if ([subContent length] > 0) {
449       VSSaxTag *a;
450       
451       a = [(VSSaxTag*)[VSSaxTag alloc] initWithContentString:subContent];
452       if (a != nil) {
453         [self->elementList addObject:a];
454         [a release];
455       }
456     }
457     [self _endTag:subTag];
458   }
459 }
460
461 - (void)_reportContentAsTag:(NSString *)_tagName
462   group:(NSString *)_group
463   withAttrs:(SaxAttributes *)_attrs 
464   andContent:(NSString *)_content 
465 {
466   /*
467     This is called for all non-BEGIN|END types.
468   */
469   NSArray *subItems;
470   
471   _content = [stringFormatter stringByUnescapingRFC2445Text:_content];
472
473   /* check whether type should be reported as an attribute in XML */
474   
475   if ([self->attributeElements containsObject:_tagName]) {
476     /* 
477        Add tag as an attribute to last component in the cardstack. This is
478        stuff like the "VERSION" type contained in a "BEGIN:VCARD" which will
479        be reported as <vcard version=""> (as an attribute of the container).
480     */
481     VSSaxTag *element;
482     
483     element = [self->cardStack lastObject];
484     [element->attrs addAttribute:_tagName uri:self->prefixURI
485                     rawName:_tagName type:@"CDATA" value:_content];
486     return;
487   }
488
489   /* report type as an XML tag */
490   
491   [self _beginTag:_tagName group:_group withAttrs:_attrs];
492   
493   if ([_content length] > 0) {
494     if ((subItems = [self->subItemMapping objectForKey:_tagName]) != nil) {
495       [self _addSubItems:subItems group:_group withData:_content];
496     }
497     else {
498       VSSaxTag *a;
499       
500       a = [(VSSaxTag *)[VSSaxTag alloc] initWithContentString:_content];
501       if (a != nil) {
502         [self->elementList addObject:a];
503         [a release];
504       }
505     }
506   }
507
508   [self _endTag:_tagName];
509 }
510
511 /* report events for collected elements */
512
513 - (void)reportStartGroup:(NSString *)_group {
514   SaxAttributes *attrs;
515   
516   attrs = [[SaxAttributes alloc] init];
517   [attrs addAttribute:@"name" uri:self->prefixURI rawName:@"name"
518          type:@"CDATA" value:_group];
519   
520   [self->contentHandler startElement:@"group" namespace:self->prefixURI
521                         rawName:@"group" attributes:attrs];
522   [attrs release];
523 }
524 - (void)reportEndGroup {
525   [self->contentHandler endElement:@"group" namespace:self->prefixURI
526                         rawName:@"group"];
527 }
528
529 - (void)reportQueuedTags {
530   /*
531     Why does the parser need the list instead of reporting the events
532     straight away?
533     
534     Because some vCard tags like the 'version' are reported as attributes
535     on the container tag. So we have a sequence like:
536       BEGIN:VCARD
537       ...
538       VERSION:3.0
539     which will get reported as:
540       <vcard version="3.0">
541   */
542   NSEnumerator *enu;
543   VSSaxTag *tagToReport;
544   NSString *lastGroup;
545   
546   lastGroup = nil;
547   enu = [self->elementList objectEnumerator];
548   while ((tagToReport = [enu nextObject]) != nil) {
549     if ([tagToReport isStartTag]) {
550       NSString *tg;
551       
552       tg = [tagToReport group];
553       if (![lastGroup isEqualToString:tg] && lastGroup != tg) {
554         if (lastGroup != nil) [self reportEndGroup];
555         ASSIGNCOPY(lastGroup, tg);
556         if (lastGroup != nil) [self reportStartGroup:lastGroup];
557       }
558     }
559     
560     if ([tagToReport isStartTag]) {
561       [self->contentHandler startElement:[tagToReport tagName]
562                             namespace:self->prefixURI
563                             rawName:[tagToReport tagName]
564                             attributes:tagToReport->attrs];
565     }
566     else if ([tagToReport isEndTag]) {
567       [self->contentHandler endElement:[tagToReport tagName]
568                             namespace:self->prefixURI
569                             rawName:[tagToReport tagName]];
570     }
571     else {
572       [self->contentHandler characters:tagToReport->data
573                             length:tagToReport->datalen];
574     }
575   }
576   
577   /* flush event group */
578   [self->elementList removeAllObjects];
579   
580   /* close open groups */
581   if (lastGroup != nil) {
582     [self reportEndGroup];
583     [lastGroup release]; lastGroup = nil;
584   }
585 }
586
587 /* errors */
588
589 - (void)error:(NSString *)_text {
590   SaxParseException *e;
591
592   e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
593                              reason:_text
594                              userInfo:nil];
595   [self->errorHandler error:e];
596 }
597 - (void)warn:(NSString *)_warn {
598   SaxParseException *e;
599
600   e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
601                              reason:_warn
602                              userInfo:nil];
603   [self->errorHandler warning:e];
604 }
605
606 /* parsing raw string */
607
608 - (void)_beginComponentWithValue:(NSString *)tagValue {
609   VSSaxTag *tag;
610   
611   tag = [self _beginTag:[self _mapTagName:tagValue]
612               group:nil
613               withAttrs:[[[SaxAttributes alloc] init] autorelease]];
614   [self->cardStack addObject:tag];
615 }
616
617 - (void)_endComponent:(NSString *)tagName value:(NSString *)tagValue {
618   NSString *mtName;
619     
620   mtName = [self _mapTagName:tagValue];
621   if ([self->cardStack count] > 0) {
622       NSString *expectedName;
623       
624       expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName];
625       if (![expectedName isEqualToString:mtName]) {
626         NSString *s;
627         
628         // TODO: rather report an error?
629         // TODO: setup userinfo dict with details
630         s = [NSString stringWithFormat:
631                         @"Found end tag '%@' which does not match expected "
632                         @"name '%@'!"
633                         @" Tag '%@' has not been closed properly. Given "
634                         @"document contains errors!",
635                         mtName, expectedName, expectedName];
636         [self error:s];
637         
638         /* probably futile attempt to parse anyways */
639         if (debugOn) {
640           NSLog(@"%s trying to fix previous error by inserting bogus end "
641                 @"tag.",
642                 __PRETTY_FUNCTION__);
643         }
644         [self _endTag:expectedName];
645         [self->cardStack removeLastObject];
646       }
647   }
648   else {
649       // TOOD: generate error?
650       [self error:[@"found end tag without any open tags left: "
651                    stringByAppendingString:mtName]];
652   }
653   [self _endTag:mtName];
654   [self->cardStack removeLastObject];
655     
656   /* report parsed elements */
657     
658   if ([self->cardStack count] == 0)
659     [self reportQueuedTags];
660 }
661
662 - (void)_parseLine:(NSString *)_line {
663   NSString       *tagName, *tagValue;
664   NSMutableArray *tagAttributes;
665   NSRange        r, todoRange;
666   unsigned       length;
667
668   length    = [_line length];
669   todoRange = NSMakeRange(0, length);
670   r = [_line rangeOfCharacterFromSet:colonAndSemicolonCharSet
671              options:0
672              range:todoRange];
673   /* is line well-formed? */
674   if (r.length == 0) {
675     [self error:[@"got an improper content line! ->\n" 
676                   stringByAppendingString:_line]];
677     return;
678   }
679   
680   /* tagname is everything up to a ':' or  ';' (value or parameter) */
681   tagName       = [[_line substringToIndex:r.location] uppercaseString];
682   tagAttributes = [[NSMutableArray alloc] initWithCapacity:16];
683   
684   /* 
685      possible shortcut: if we spotted a ':', we don't have to do "expensive"
686      argument scanning/processing.
687   */
688   if ([_line characterAtIndex:r.location] != ':') {
689     BOOL isAtEnd    = NO;
690     BOOL isInDquote = NO;
691     unsigned start;
692     
693     start     = NSMaxRange(r);
694     todoRange = NSMakeRange(start, length - start);
695     while(!isAtEnd) {
696       BOOL skip = YES;
697
698       /* scan for parameters */
699       r = [_line rangeOfCharacterFromSet:colonSemicolonAndDquoteCharSet
700                  options:0
701                  range:todoRange];
702       
703       /* is line well-formed? */
704       if (r.length == 0 || r.location == 0) {
705         [self error:[@"got an improper content line! ->\n" 
706                       stringByAppendingString:_line]];
707         [tagAttributes release]; tagAttributes = nil;
708         return;
709       }
710       
711       /* first check if delimiter candidate is escaped */
712       if ([_line characterAtIndex:(r.location - 1)] != '\\') {
713         unichar delimiter;
714         NSRange copyRange;
715
716         delimiter = [_line characterAtIndex:r.location];
717         if (delimiter == '\"') {
718           /* not a real delimiter - toggle isInDquote for proper escaping */
719           isInDquote = !isInDquote;
720         }
721         else {
722           if (!isInDquote) {
723             /* is a delimiter, which one? */
724             skip = NO;
725             if (delimiter == ':') {
726               isAtEnd = YES;
727             }
728             copyRange = NSMakeRange(start, r.location - start);
729             [tagAttributes addObject:[_line substringWithRange:copyRange]];
730             if (!isAtEnd) {
731               /* adjust start, todoRange */
732               start     = NSMaxRange(r);
733               todoRange = NSMakeRange(start, length - start);
734             }
735           }
736         }
737       }
738       if (skip) {
739         /* adjust todoRange */
740         unsigned offset = NSMaxRange(r);
741         todoRange = NSMakeRange(offset, length - offset);
742       }
743     }
744   }
745   tagValue = [_line substringFromIndex:NSMaxRange(r)];
746   
747   /*
748     At this point we have:
749       name:      'BEGIN', 'TEL', 'EMAIL', 'ITEM1.ADR' etc
750       value:     ';;;Magdeburg;;;Germany'
751       atributes: ("type=INTERNET", "type=HOME", "type=pref")
752   */
753   
754   /* process tag */
755   
756   if ([tagName isEqualToString:@"BEGIN"]) {
757     if ([tagAttributes count] > 0)
758       [self warn:@"Losing unexpected parameters of BEGIN line."];
759     [self _beginComponentWithValue:tagValue];
760   }
761   else if ([tagName isEqualToString:@"END"]) {
762     if ([tagAttributes count] > 0)
763       [self warn:@"Losing unexpected parameters of END line."];
764     [self _endComponent:tagName value:tagValue];
765   }
766   else {
767     [self _reportContentAsTag:[self _mapTagName:tagName]
768           group:[self _groupFromTagName:tagName]
769           withAttrs:[self _mapAttrs:tagAttributes forTag:tagName] 
770           andContent:tagValue];
771   }
772   
773   [tagAttributes release];
774 }
775
776
777 /* top level parsing method */
778
779 - (void)reportDocStart {
780   [self->contentHandler startDocument];
781   [self->contentHandler startPrefixMapping:@"" uri:self->prefixURI];
782 }
783 - (void)reportDocEnd {
784   [self->contentHandler endPrefixMapping:@""];
785   [self->contentHandler endDocument];
786 }
787
788 - (void)_parseString:(NSString *)_rawString {
789   /*
790     This method split the string into content lines for actual vCard
791     parsing.
792
793     RFC2445:
794      contentline        = name *(";" param ) ":" value CRLF
795      ; When parsing a content line, folded lines MUST first
796      ; be unfolded
797   */
798   NSMutableString *line;
799   unsigned pos, length;
800   NSRange  r;
801
802   [self reportDocStart];
803   
804   /* start parsing */
805   
806   length = [_rawString length];
807   r      = NSMakeRange(0, 0);
808   line   = [[NSMutableString alloc] initWithCapacity:75 + 2];
809   
810   for (pos = 0; pos < length; pos++) {
811     unichar c;
812     
813     c = [_rawString characterAtIndex:pos];
814     
815     if (c == '\r') {
816       if (((length - 1) - pos) >= 1) {
817         if ([_rawString characterAtIndex:pos + 1] == '\n') {
818           BOOL isAtEndOfLine = YES;
819           
820           /* test for folding first */
821           if (((length - 1) - pos) >= 2) {
822             unichar ws;
823             
824             ws = [_rawString characterAtIndex:pos + 2];
825             isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO :YES;
826             if (!isAtEndOfLine) {
827               /* assemble part of line up to pos */
828               if (r.length > 0) {
829                 [line appendString:[_rawString substringWithRange:r]];
830               }
831               /* unfold */
832               pos += 2;
833               r = NSMakeRange(pos + 1, 0); /* begin new range */
834             }
835           }
836           if (isAtEndOfLine) {
837             /* assemble part of line up to pos */
838             if (r.length > 0) {
839               [line appendString:[_rawString substringWithRange:r]];
840             }
841             [self _parseLine:line];
842             /* reset line */
843             [line deleteCharactersInRange:NSMakeRange(0, [line length])];
844             pos += 1;
845             r = NSMakeRange(pos + 1, 0); /* begin new range */
846           }
847         }
848       }
849       else {
850         /* garbled last line! */
851         [self warn:@"last line is truncated, trying to parse anyways!"];
852       }
853     }
854     else if (c == '\n') { /* broken, non-standard */
855       BOOL isAtEndOfLine = YES;
856       
857       /* test for folding first */
858       if (((length - 1) - pos) >= 1) {
859         unichar ws;
860         
861         ws = [_rawString characterAtIndex:(pos + 1)];
862         
863         isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO : YES;
864         if (!isAtEndOfLine) {
865           /* assemble part of line up to pos */
866           if (r.length > 0) {
867             [line appendString:[_rawString substringWithRange:r]];
868           }
869           /* unfold */
870           pos += 1;
871           r = NSMakeRange(pos + 1, 0); /* begin new range */
872         }
873       }
874       if (isAtEndOfLine) {
875         /* assemble part of line up to pos */
876         if (r.length > 0) {
877           [line appendString:[_rawString substringWithRange:r]];
878         }
879         [self _parseLine:line];
880         /* reset line */
881         [line deleteCharactersInRange:NSMakeRange(0, [line length])];
882         r = NSMakeRange(pos + 1, 0); /* begin new range */
883       }
884     }
885     else {
886       r.length += 1;
887     }
888   }
889   if (r.length > 0) {
890     [self warn:@"Last line of parse string is not properly terminated!"];
891     [line appendString:[_rawString substringWithRange:r]];
892     [self _parseLine:line];
893   }
894   
895   if ([self->cardStack count] != 0) {
896     [self warn:@"found elements on cardStack. This indicates an improper "
897             @"nesting structure! Not all required events will have been "
898             @"generated, leading to unpredictable results!"];
899     [self->cardStack removeAllObjects]; // clean up
900   }
901   
902   [line release]; line = nil;
903   
904   [self reportDocEnd];
905 }
906
907 /* main entry functions */
908
909 - (id)sourceForData:(NSData *)_data systemId:(NSString *)_sysId {
910   SaxParseException *e = nil;
911   NSStringEncoding encoding;
912   unsigned len;
913   const unsigned char *bytes;
914   id source;
915   
916   if (debugOn) {
917     NSLog(@"%s: trying to decode data (0x%08X,len=%d) ...",
918             __PRETTY_FUNCTION__, _data, [_data length]);
919   }
920   
921   if ((len = [_data length]) == 0) {
922     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
923                                reason:@"Got no parsing data!"
924                                userInfo:nil];
925     [self->errorHandler fatalError:e];
926     return nil;
927   }
928   if (len < 10) {
929     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
930                                reason:@"Input data to short for vCard!"
931                                userInfo:nil];
932     [self->errorHandler fatalError:e];
933     return nil;
934   }
935   
936   bytes = [_data bytes];
937   if ((bytes[0] == 0xFF && bytes[1] == 0xFE) ||
938       (bytes[0] == 0xFE && bytes[1] == 0xFF)) {
939     encoding = NSUnicodeStringEncoding;
940   }
941   else
942     encoding = NSUTF8StringEncoding;
943   
944   // FIXME: Data is not always utf-8.....
945   source = [[[NSString alloc] initWithData:_data encoding:encoding]
946              autorelease];
947   if (source == nil) {
948     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
949                                reason:@"Could not convert input to string!"
950                                userInfo:nil];
951     [self->errorHandler fatalError:e];
952   }
953   return source;
954 }
955
956 - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
957   if (debugOn)
958     NSLog(@"%s: parse: %@ (sysid=%@)", __PRETTY_FUNCTION__, _source, _sysId);
959   
960   if ([_source isKindOfClass:[NSURL class]]) {
961     if (_sysId == nil) _sysId = [_source absoluteString];
962
963     if (debugOn) {
964       NSLog(@"%s: trying to load URL: %@ (sysid=%@)",__PRETTY_FUNCTION__, 
965             _source, _sysId);
966     }
967     
968     // TODO: remember encoding of source
969     _source = [_source resourceDataUsingCache:NO];
970   }
971   
972   if ([_source isKindOfClass:[NSData class]]) {
973     if (_sysId == nil) _sysId = @"<data>";
974     if ((_source = [self sourceForData:_source systemId:_sysId]) == nil)
975       return;
976   }
977
978   if (![_source isKindOfClass:[NSString class]]) {
979     SaxParseException *e;
980     NSString *s;
981     
982     if (debugOn) 
983       NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source);
984     
985     s = [@"cannot handle data-source: " stringByAppendingString:
986             [_source description]];
987     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
988                                reason:s
989                                userInfo:nil];
990     
991     [self->errorHandler fatalError:e];
992     return;
993   }
994
995   /* ensure consistent state */
996
997   [self->cardStack   removeAllObjects];
998   [self->elementList removeAllObjects];
999   
1000   /* start parsing */
1001   
1002   if (debugOn) {
1003     NSLog(@"%s: trying to parse string (0x%08X,len=%d) ...",
1004           __PRETTY_FUNCTION__, _source, [_source length]);
1005   }
1006   if (_sysId == nil) _sysId = @"<string>";
1007   [self _parseString:_source];
1008   
1009   /* tear down */
1010   
1011   [self->cardStack   removeAllObjects];
1012   [self->elementList removeAllObjects];
1013 }
1014
1015 - (void)parseFromSource:(id)_source {
1016   [self parseFromSource:_source systemId:nil];
1017 }
1018
1019 - (void)parseFromSystemId:(NSString *)_sysId {
1020   NSURL *url;
1021   
1022   if ([_sysId rangeOfString:@"://"].length == 0) {
1023     /* seems to be a path, path to be a proper URL */
1024     url = [NSURL fileURLWithPath:_sysId];
1025   }
1026   else {
1027     /* Note: Cocoa NSURL doesn't complain on "/abc/def" like input! */
1028     url = [NSURL URLWithString:_sysId];
1029   }
1030   
1031   if (url == nil) {
1032     SaxParseException *e;
1033     
1034     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
1035                                reason:@"cannot handle system-id"
1036                                userInfo:nil];
1037     [self->errorHandler fatalError:e];
1038     return;
1039   }
1040   
1041   [self parseFromSource:url systemId:_sysId];
1042 }
1043
1044 /* debugging */
1045
1046 - (BOOL)isDebuggingEnabled {
1047   return debugOn;
1048 }
1049
1050 @end /* VersitSaxDriver */