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