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