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