]> err.no Git - sope/blob - sope-ical/versitSaxDriver/VSSaxDriver.m
a82e1abdc87dd70631a2ea999b573b63f80c778a
[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] initWithCapacity:4];
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]) != nil) {
395       NSString *val;
396       
397       /* ZNeK: duh! */
398       // TODO: hh asks: what does 'duh' is supposed to mean?
399       val = [[NSString alloc] initWithFormat:@"%@ %@",oldValue, mappedValue];
400       [attributes setObject:val forKey:mappedAttr];
401       [val release];
402     }
403     else  
404       [attributes setObject:mappedValue forKey:mappedAttr];
405   }
406
407   attrEnum = [attributes keyEnumerator];
408   while ((curAttr = [attrEnum nextObject]) != nil) {
409     /*
410       TODO: values are not always mapped to CDATA! Eg in the dawson draft:
411         | TYPE for TEL   | tel.type   | NMTOKENS  | 'VOICE'         |
412         | TYPE for EMAIL | email.type | NMTOKENS  | 'INTERNET'      |
413         | TYPE for PHOTO,| img.type   | CDATA     | REQUIRED        |
414         |  and LOGO      |            |           |                 |
415         | TYPE for SOUND | aud.type   | CDATA     | REQUIRED        |
416         | VALUE          | value      | NOTATION  | See elements    |
417     */
418     
419     [retAttrs addAttribute:curAttr uri:self->prefixURI rawName:curAttr
420               type:@"CDATA" value:[attributes objectForKey:curAttr]];
421   }
422   
423   [attributes release];
424   
425   return retAttrs;
426 }
427
428 - (VSSaxTag *)_beginTag:(NSString *)_tagName group:(NSString *)_group
429   withAttrs:(SaxAttributes *)_attrs
430 {
431   VSSaxTag *tag;
432   
433   tag = [VSSaxTag beginTag:_tagName group:_group attributes:_attrs];
434   [self->elementList addObject:tag];
435   return tag;
436 }
437
438 - (void)_endTag:(NSString *)_tagName {
439   VSSaxTag *tag;
440   
441   tag = [[VSSaxTag alloc] initEndTag:_tagName];
442   [self->elementList addObject:tag];
443   [tag release]; tag = nil;
444 }
445
446 - (void)_addSubItems:(NSArray *)_items group:(NSString *)_group
447   withData:(NSString *)_content
448 {
449   NSEnumerator *itemEnum, *contentEnum;
450   NSString *subTag;
451   
452   itemEnum    = [_items objectEnumerator];
453   contentEnum = [[_content componentsSeparatedByString:@";"] objectEnumerator];
454   
455   while ((subTag = [itemEnum nextObject]) != nil) {
456     NSString *subContent;
457     
458     subContent = [contentEnum nextObject];
459     
460     [self _beginTag:subTag group:_group withAttrs:nil];
461     if ([subContent length] > 0) {
462       VSSaxTag *a;
463       
464       a = [(VSSaxTag*)[VSSaxTag alloc] initWithContentString:subContent];
465       if (a != nil) {
466         [self->elementList addObject:a];
467         [a release];
468       }
469     }
470     [self _endTag:subTag];
471   }
472 }
473
474 - (void)_reportContentAsTag:(NSString *)_tagName
475   group:(NSString *)_group
476   withAttrs:(SaxAttributes *)_attrs 
477   andContent:(NSString *)_content 
478 {
479   /*
480     This is called for all non-BEGIN|END types.
481   */
482   NSArray *subItems;
483   
484   _content = [stringFormatter stringByUnescapingRFC2445Text:_content];
485
486   /* check whether type should be reported as an attribute in XML */
487   
488   if ([self->attributeElements containsObject:_tagName]) {
489     /* 
490        Add tag as an attribute to last component in the cardstack. This is
491        stuff like the "VERSION" type contained in a "BEGIN:VCARD" which will
492        be reported as <vcard version=""> (as an attribute of the container).
493     */
494     VSSaxTag *element;
495     
496     element = [self->cardStack lastObject];
497     [element->attrs addAttribute:_tagName uri:self->prefixURI
498                     rawName:_tagName type:@"CDATA" value:_content];
499     return;
500   }
501
502   /* report type as an XML tag */
503   
504   [self _beginTag:_tagName group:_group withAttrs:_attrs];
505   
506   if ([_content length] > 0) {
507     if ((subItems = [self->subItemMapping objectForKey:_tagName]) != nil) {
508       [self _addSubItems:subItems group:_group withData:_content];
509     }
510     else {
511       VSSaxTag *a;
512       
513       a = [(VSSaxTag *)[VSSaxTag alloc] initWithContentString:_content];
514       if (a != nil) {
515         [self->elementList addObject:a];
516         [a release];
517       }
518     }
519   }
520
521   [self _endTag:_tagName];
522 }
523
524 /* report events for collected elements */
525
526 - (void)reportStartGroup:(NSString *)_group {
527   SaxAttributes *attrs;
528   
529   attrs = [[SaxAttributes alloc] init];
530   [attrs addAttribute:@"name" uri:self->prefixURI rawName:@"name"
531          type:@"CDATA" value:_group];
532   
533   [self->contentHandler startElement:@"group" namespace:self->prefixURI
534                         rawName:@"group" attributes:attrs];
535   [attrs release];
536 }
537 - (void)reportEndGroup {
538   [self->contentHandler endElement:@"group" namespace:self->prefixURI
539                         rawName:@"group"];
540 }
541
542 - (void)reportQueuedTags {
543   /*
544     Why does the parser need the list instead of reporting the events
545     straight away?
546     
547     Because some vCard tags like the 'version' are reported as attributes
548     on the container tag. So we have a sequence like:
549       BEGIN:VCARD
550       ...
551       VERSION:3.0
552     which will get reported as:
553       <vcard version="3.0">
554   */
555   NSEnumerator *enu;
556   VSSaxTag *tagToReport;
557   NSString *lastGroup;
558   
559   lastGroup = nil;
560   enu = [self->elementList objectEnumerator];
561   while ((tagToReport = [enu nextObject]) != nil) {
562     if ([tagToReport isStartTag]) {
563       NSString *tg;
564       
565       tg = [tagToReport group];
566       if (![lastGroup isEqualToString:tg] && lastGroup != tg) {
567         if (lastGroup != nil) [self reportEndGroup];
568         ASSIGNCOPY(lastGroup, tg);
569         if (lastGroup != nil) [self reportStartGroup:lastGroup];
570       }
571     }
572     
573     if ([tagToReport isStartTag]) {
574       [self->contentHandler startElement:[tagToReport tagName]
575                             namespace:self->prefixURI
576                             rawName:[tagToReport tagName]
577                             attributes:tagToReport->attrs];
578     }
579     else if ([tagToReport isEndTag]) {
580       [self->contentHandler endElement:[tagToReport tagName]
581                             namespace:self->prefixURI
582                             rawName:[tagToReport tagName]];
583     }
584     else {
585       [self->contentHandler characters:tagToReport->data
586                             length:tagToReport->datalen];
587     }
588   }
589   
590   /* flush event group */
591   [self->elementList removeAllObjects];
592   
593   /* close open groups */
594   if (lastGroup != nil) {
595     [self reportEndGroup];
596     [lastGroup release]; lastGroup = nil;
597   }
598 }
599
600 /* errors */
601
602 - (void)error:(NSString *)_text {
603   SaxParseException *e;
604
605   e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
606                              reason:_text
607                              userInfo:nil];
608   [self->errorHandler error:e];
609 }
610 - (void)warn:(NSString *)_warn {
611   SaxParseException *e;
612
613   e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
614                              reason:_warn
615                              userInfo:nil];
616   [self->errorHandler warning:e];
617 }
618
619 /* parsing raw string */
620
621 - (void)_beginComponentWithValue:(NSString *)tagValue {
622   VSSaxTag *tag;
623   
624   tag = [self _beginTag:[self _mapTagName:tagValue]
625               group:nil
626               withAttrs:[[[SaxAttributes alloc] init] autorelease]];
627   [self->cardStack addObject:tag];
628 }
629
630 - (void)_endComponent:(NSString *)tagName value:(NSString *)tagValue {
631   NSString *mtName;
632     
633   mtName = [self _mapTagName:tagValue];
634   if ([self->cardStack count] > 0) {
635       NSString *expectedName;
636       
637       expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName];
638       if (![expectedName isEqualToString:mtName]) {
639         NSString *s;
640         
641         // TODO: rather report an error?
642         // TODO: setup userinfo dict with details
643         s = [NSString stringWithFormat:
644                         @"Found end tag '%@' which does not match expected "
645                         @"name '%@'!"
646                         @" Tag '%@' has not been closed properly. Given "
647                         @"document contains errors!",
648                         mtName, expectedName, expectedName];
649         [self error:s];
650         
651         /* probably futile attempt to parse anyways */
652         if (debugOn) {
653           NSLog(@"%s trying to fix previous error by inserting bogus end "
654                 @"tag.",
655                 __PRETTY_FUNCTION__);
656         }
657         [self _endTag:expectedName];
658         [self->cardStack removeLastObject];
659       }
660   }
661   else {
662       // TOOD: generate error?
663       [self error:[@"found end tag without any open tags left: "
664                    stringByAppendingString:mtName]];
665   }
666   [self _endTag:mtName];
667   [self->cardStack removeLastObject];
668     
669   /* report parsed elements */
670     
671   if ([self->cardStack count] == 0)
672     [self reportQueuedTags];
673 }
674
675 - (void)_parseLine:(NSString *)_line {
676   NSString       *tagName, *tagValue;
677   NSMutableArray *tagAttributes;
678   NSRange        r, todoRange;
679   unsigned       length;
680
681   length    = [_line length];
682   todoRange = NSMakeRange(0, length);
683   r = [_line rangeOfCharacterFromSet:colonAndSemicolonCharSet
684              options:0
685              range:todoRange];
686   /* is line well-formed? */
687   if (r.length == 0) {
688     [self error:[@"got an improper content line! ->\n" 
689                   stringByAppendingString:_line]];
690     return;
691   }
692   
693   /* tagname is everything up to a ':' or  ';' (value or parameter) */
694   tagName       = [[_line substringToIndex:r.location] uppercaseString];
695   tagAttributes = [[NSMutableArray alloc] initWithCapacity:16];
696   
697   /* 
698      possible shortcut: if we spotted a ':', we don't have to do "expensive"
699      argument scanning/processing.
700   */
701   if ([_line characterAtIndex:r.location] != ':') {
702     BOOL isAtEnd    = NO;
703     BOOL isInDquote = NO;
704     unsigned start;
705     
706     start     = NSMaxRange(r);
707     todoRange = NSMakeRange(start, length - start);
708     while(!isAtEnd) {
709       BOOL skip = YES;
710
711       /* scan for parameters */
712       r = [_line rangeOfCharacterFromSet:colonSemicolonAndDquoteCharSet
713                  options:0
714                  range:todoRange];
715       
716       /* is line well-formed? */
717       if (r.length == 0 || r.location == 0) {
718         [self error:[@"got an improper content line! ->\n" 
719                       stringByAppendingString:_line]];
720         [tagAttributes release]; tagAttributes = nil;
721         return;
722       }
723       
724       /* first check if delimiter candidate is escaped */
725       if ([_line characterAtIndex:(r.location - 1)] != '\\') {
726         unichar delimiter;
727         NSRange copyRange;
728
729         delimiter = [_line characterAtIndex:r.location];
730         if (delimiter == '\"') {
731           /* not a real delimiter - toggle isInDquote for proper escaping */
732           isInDquote = !isInDquote;
733         }
734         else {
735           if (!isInDquote) {
736             /* is a delimiter, which one? */
737             skip = NO;
738             if (delimiter == ':') {
739               isAtEnd = YES;
740             }
741             copyRange = NSMakeRange(start, r.location - start);
742             [tagAttributes addObject:[_line substringWithRange:copyRange]];
743             if (!isAtEnd) {
744               /* adjust start, todoRange */
745               start     = NSMaxRange(r);
746               todoRange = NSMakeRange(start, length - start);
747             }
748           }
749         }
750       }
751       if (skip) {
752         /* adjust todoRange */
753         unsigned offset = NSMaxRange(r);
754         todoRange = NSMakeRange(offset, length - offset);
755       }
756     }
757   }
758   tagValue = [_line substringFromIndex:NSMaxRange(r)];
759   
760   /*
761     At this point we have:
762       name:      'BEGIN', 'TEL', 'EMAIL', 'ITEM1.ADR' etc
763       value:     ';;;Magdeburg;;;Germany'
764       atributes: ("type=INTERNET", "type=HOME", "type=pref")
765   */
766   
767   /* process tag */
768   
769   if ([tagName isEqualToString:@"BEGIN"]) {
770     if ([tagAttributes count] > 0)
771       [self warn:@"Losing unexpected parameters of BEGIN line."];
772     [self _beginComponentWithValue:tagValue];
773   }
774   else if ([tagName isEqualToString:@"END"]) {
775     if ([tagAttributes count] > 0)
776       [self warn:@"Losing unexpected parameters of END line."];
777     [self _endComponent:tagName value:tagValue];
778   }
779   else {
780     [self _reportContentAsTag:[self _mapTagName:tagName]
781           group:[self _groupFromTagName:tagName]
782           withAttrs:[self _mapAttrs:tagAttributes forTag:tagName] 
783           andContent:tagValue];
784   }
785   
786   [tagAttributes release];
787 }
788
789
790 /* top level parsing method */
791
792 - (void)reportDocStart {
793   [self->contentHandler startDocument];
794   [self->contentHandler startPrefixMapping:@"" uri:self->prefixURI];
795 }
796 - (void)reportDocEnd {
797   [self->contentHandler endPrefixMapping:@""];
798   [self->contentHandler endDocument];
799 }
800
801 - (void)_parseString:(NSString *)_rawString {
802   /*
803     This method split the string into content lines for actual vCard
804     parsing.
805
806     RFC2445:
807      contentline        = name *(";" param ) ":" value CRLF
808      ; When parsing a content line, folded lines MUST first
809      ; be unfolded
810   */
811   NSMutableString *line;
812   unsigned pos, length;
813   NSRange  r;
814
815   [self reportDocStart];
816   
817   /* start parsing */
818   
819   length = [_rawString length];
820   r      = NSMakeRange(0, 0);
821   line   = [[NSMutableString alloc] initWithCapacity:75 + 2];
822   
823   for (pos = 0; pos < length; pos++) {
824     unichar c;
825     
826     c = [_rawString characterAtIndex:pos];
827     
828     if (c == '\r') {
829       if (((length - 1) - pos) >= 1) {
830         if ([_rawString characterAtIndex:pos + 1] == '\n') {
831           BOOL isAtEndOfLine = YES;
832           
833           /* test for folding first */
834           if (((length - 1) - pos) >= 2) {
835             unichar ws;
836             
837             ws = [_rawString characterAtIndex:pos + 2];
838             isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO :YES;
839             if (!isAtEndOfLine) {
840               /* assemble part of line up to pos */
841               if (r.length > 0) {
842                 [line appendString:[_rawString substringWithRange:r]];
843               }
844               /* unfold */
845               pos += 2;
846               r = NSMakeRange(pos + 1, 0); /* begin new range */
847             }
848           }
849           if (isAtEndOfLine) {
850             /* assemble part of line up to pos */
851             if (r.length > 0) {
852               [line appendString:[_rawString substringWithRange:r]];
853             }
854             [self _parseLine:line];
855             /* reset line */
856             [line deleteCharactersInRange:NSMakeRange(0, [line length])];
857             pos += 1;
858             r = NSMakeRange(pos + 1, 0); /* begin new range */
859           }
860         }
861       }
862       else {
863         /* garbled last line! */
864         [self warn:@"last line is truncated, trying to parse anyways!"];
865       }
866     }
867     else if (c == '\n') { /* broken, non-standard */
868       BOOL isAtEndOfLine = YES;
869       
870       /* test for folding first */
871       if (((length - 1) - pos) >= 1) {
872         unichar ws;
873         
874         ws = [_rawString characterAtIndex:(pos + 1)];
875         
876         isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO : YES;
877         if (!isAtEndOfLine) {
878           /* assemble part of line up to pos */
879           if (r.length > 0) {
880             [line appendString:[_rawString substringWithRange:r]];
881           }
882           /* unfold */
883           pos += 1;
884           r = NSMakeRange(pos + 1, 0); /* begin new range */
885         }
886       }
887       if (isAtEndOfLine) {
888         /* assemble part of line up to pos */
889         if (r.length > 0) {
890           [line appendString:[_rawString substringWithRange:r]];
891         }
892         [self _parseLine:line];
893         /* reset line */
894         [line deleteCharactersInRange:NSMakeRange(0, [line length])];
895         r = NSMakeRange(pos + 1, 0); /* begin new range */
896       }
897     }
898     else {
899       r.length += 1;
900     }
901   }
902   if (r.length > 0) {
903     [self warn:@"Last line of parse string is not properly terminated!"];
904     [line appendString:[_rawString substringWithRange:r]];
905     [self _parseLine:line];
906   }
907   
908   if ([self->cardStack count] != 0) {
909     [self warn:@"found elements on cardStack. This indicates an improper "
910             @"nesting structure! Not all required events will have been "
911             @"generated, leading to unpredictable results!"];
912     [self->cardStack removeAllObjects]; // clean up
913   }
914   
915   [line release]; line = nil;
916   
917   [self reportDocEnd];
918 }
919
920 /* main entry functions */
921
922 - (id)sourceForData:(NSData *)_data systemId:(NSString *)_sysId {
923   SaxParseException *e = nil;
924   NSStringEncoding encoding;
925   unsigned len;
926   const unsigned char *bytes;
927   id source;
928   
929   if (debugOn) {
930     NSLog(@"%s: trying to decode data (0x%08X,len=%d) ...",
931             __PRETTY_FUNCTION__, _data, [_data length]);
932   }
933   
934   if ((len = [_data length]) == 0) {
935     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
936                                reason:@"Got no parsing data!"
937                                userInfo:nil];
938     [self->errorHandler fatalError:e];
939     return nil;
940   }
941   if (len < 10) {
942     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
943                                reason:@"Input data to short for vCard!"
944                                userInfo:nil];
945     [self->errorHandler fatalError:e];
946     return nil;
947   }
948   
949   bytes = [_data bytes];
950   if ((bytes[0] == 0xFF && bytes[1] == 0xFE) ||
951       (bytes[0] == 0xFE && bytes[1] == 0xFF)) {
952     encoding = NSUnicodeStringEncoding;
953   }
954   else
955     encoding = NSUTF8StringEncoding;
956   
957   // FIXME: Data is not always utf-8.....
958   source = [[[NSString alloc] initWithData:_data encoding:encoding]
959              autorelease];
960   if (source == nil) {
961     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
962                                reason:@"Could not convert input to string!"
963                                userInfo:nil];
964     [self->errorHandler fatalError:e];
965   }
966   return source;
967 }
968
969 - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
970   if (debugOn)
971     NSLog(@"%s: parse: %@ (sysid=%@)", __PRETTY_FUNCTION__, _source, _sysId);
972   
973   if ([_source isKindOfClass:[NSURL class]]) {
974     if (_sysId == nil) _sysId = [_source absoluteString];
975
976     if (debugOn) {
977       NSLog(@"%s: trying to load URL: %@ (sysid=%@)",__PRETTY_FUNCTION__, 
978             _source, _sysId);
979     }
980     
981     // TODO: remember encoding of source
982     _source = [_source resourceDataUsingCache:NO];
983   }
984   
985   if ([_source isKindOfClass:[NSData class]]) {
986     if (_sysId == nil) _sysId = @"<data>";
987     if ((_source = [self sourceForData:_source systemId:_sysId]) == nil)
988       return;
989   }
990
991   if (![_source isKindOfClass:[NSString class]]) {
992     SaxParseException *e;
993     NSString *s;
994     
995     if (debugOn) 
996       NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source);
997     
998     s = [@"cannot handle data-source: " stringByAppendingString:
999             [_source description]];
1000     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
1001                                reason:s
1002                                userInfo:nil];
1003     
1004     [self->errorHandler fatalError:e];
1005     return;
1006   }
1007
1008   /* ensure consistent state */
1009
1010   [self->cardStack   removeAllObjects];
1011   [self->elementList removeAllObjects];
1012   
1013   /* start parsing */
1014   
1015   if (debugOn) {
1016     NSLog(@"%s: trying to parse string (0x%08X,len=%d) ...",
1017           __PRETTY_FUNCTION__, _source, [_source length]);
1018   }
1019   if (_sysId == nil) _sysId = @"<string>";
1020   [self _parseString:_source];
1021   
1022   /* tear down */
1023   
1024   [self->cardStack   removeAllObjects];
1025   [self->elementList removeAllObjects];
1026 }
1027
1028 - (void)parseFromSource:(id)_source {
1029   [self parseFromSource:_source systemId:nil];
1030 }
1031
1032 - (void)parseFromSystemId:(NSString *)_sysId {
1033   NSURL *url;
1034   
1035   if ([_sysId rangeOfString:@"://"].length == 0) {
1036     /* seems to be a path, path to be a proper URL */
1037     url = [NSURL fileURLWithPath:_sysId];
1038   }
1039   else {
1040     /* Note: Cocoa NSURL doesn't complain on "/abc/def" like input! */
1041     url = [NSURL URLWithString:_sysId];
1042   }
1043   
1044   if (url == nil) {
1045     SaxParseException *e;
1046     
1047     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
1048                                reason:@"cannot handle system-id"
1049                                userInfo:nil];
1050     [self->errorHandler fatalError:e];
1051     return;
1052   }
1053   
1054   [self parseFromSource:url systemId:_sysId];
1055 }
1056
1057 /* debugging */
1058
1059 - (BOOL)isDebuggingEnabled {
1060   return debugOn;
1061 }
1062
1063 @end /* VersitSaxDriver */