]> err.no Git - sope/blobdiff - sope-ical/versitSaxDriver/VSSaxDriver.m
code reorgs
[sope] / sope-ical / versitSaxDriver / VSSaxDriver.m
index e740f5bdfd4248eb7a0983579d01d47c52bd1014..0113f2bccca698db6e29594d64c063e3b1316687 100644 (file)
 #include <SaxObjC/SaxException.h>
 #include "common.h"
 
+@interface VSSaxTag : NSObject
+{
+@public
+  NSString      *type;
+  NSString      *tagName;
+  SaxAttributes *attrs;
+  NSString      *data;
+}
+
++ (id)beginTag:(NSString *)_tag attributes:(SaxAttributes *)_attrs;
+- (id)initEndTag:(NSString *)_tag;
+- (id)initWithData:(NSString *)_data;
+
+- (NSString *)tagName;
+
+@end
+
+static NSString *VSBeginType = @"BEGIN";
+static NSString *VSEndType   = @"END";
+static NSString *VSDataType  = @"DATA";
+
+@implementation VSSaxTag
+
++ (id)beginTag:(NSString *)_tag attributes:(SaxAttributes *)_attrs {
+  VSSaxTag *tag;
+
+  tag = [[[self alloc] init] autorelease];
+  tag->type    = VSBeginType;
+  tag->tagName = [_tag copy];
+  tag->attrs   = [_attrs retain];
+  return tag;
+}
+- (id)initEndTag:(NSString *)_tag {
+  self->type    = VSEndType;
+  self->tagName = [_tag copy];
+  return self;
+}
+- (id)initWithData:(NSString *)_data {
+  self->type = VSDataType;
+  self->data = [_data retain];
+  return self;
+}
+
+- (void)dealloc {
+  [self->data    release];
+  [self->tagName release];
+  [self->attrs   release];
+  [super dealloc];
+}
+
+- (NSString *)tagName {
+  return self->tagName;
+}
+
+@end /* VSSaxTag */
+
 @implementation VSSaxDriver
 
 static BOOL debugOn = NO;
 
-static NSCharacterSet *dotCharSet = nil;
-static NSCharacterSet *equalSignCharSet = nil;
-static NSCharacterSet *commaCharSet = nil;
-static NSCharacterSet *colonAndSemicolonCharSet = nil;
+static NSCharacterSet *dotCharSet                     = nil;
+static NSCharacterSet *equalSignCharSet               = nil;
+static NSCharacterSet *commaCharSet                   = nil;
+static NSCharacterSet *colonAndSemicolonCharSet       = nil;
 static NSCharacterSet *colonSemicolonAndDquoteCharSet = nil;
-static NSCharacterSet *whitespaceCharSet = nil;
+static NSCharacterSet *whitespaceCharSet              = nil;
 
 static VSStringFormatter *stringFormatter = nil;
 
@@ -211,9 +267,10 @@ static VSStringFormatter *stringFormatter = nil;
 }
 
 - (void)_addAttribute:(NSString *)_attribute value:(NSString *)_value {
-  NSArray *element = [cardStack lastObject];
-  SaxAttributes *attrs = [element objectAtIndex:2];
-  [self _addAttribute:_attribute value:_value toAttrs:attrs];
+  VSSaxTag *element;
+  
+  element = [cardStack lastObject];
+  [self _addAttribute:_attribute value:_value toAttrs:element->attrs];
 }
 
 - (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName {
@@ -289,21 +346,20 @@ static VSStringFormatter *stringFormatter = nil;
   *value_ = [stringFormatter stringByUnescapingRFC2445Text:attrValue];
 }
 
-- (id<NSObject,SaxAttributes>)_mapAttrs:(NSArray *)_attrs 
-  forTag:(NSString *)_tagName 
-{
+- (SaxAttributes *)_mapAttrs:(NSArray *)_attrs forTag:(NSString *)_tagName {
   SaxAttributes       *retAttrs;
   NSEnumerator        *attrEnum;
   NSString            *curAttr, *mappedAttr, *mappedValue, *oldValue;
   NSMutableDictionary *attributes;
 
-  if (!_attrs || [_attrs count] == 0)
+  if (_attrs == nil || [_attrs count] == 0)
     return nil;
 
   attributes = [[NSMutableDictionary alloc] init];
-  retAttrs = [[[SaxAttributes alloc] init] autorelease];
+  retAttrs   = [[[SaxAttributes alloc] init] autorelease];
+  
   attrEnum = [_attrs objectEnumerator];
-  while ((curAttr = [attrEnum nextObject])) {
+  while ((curAttr = [attrEnum nextObject]) != nil) {
     [self _parseAttr:curAttr
           forTag:_tagName
           intoAttr:&mappedAttr
@@ -319,7 +375,7 @@ static VSStringFormatter *stringFormatter = nil;
   }
 
   attrEnum = [attributes keyEnumerator];
-  while ((curAttr = [attrEnum nextObject])) {
+  while ((curAttr = [attrEnum nextObject]) != nil) {
     [self _addAttribute:curAttr
           value:[attributes objectForKey:curAttr]
           toAttrs:retAttrs];
@@ -330,45 +386,55 @@ static VSStringFormatter *stringFormatter = nil;
   return retAttrs;
 }
 
-- (NSArray *)_beginTag:(NSString *)_tagName 
-  withAttrs:(id<NSObject,SaxAttributes>)_attrs 
-{
-  NSArray *tag = [NSArray arrayWithObjects:@"BEGIN",_tagName,_attrs,NULL];
+- (VSSaxTag *)_beginTag:(NSString *)_tagName withAttrs:(SaxAttributes *)_attrs{
+  VSSaxTag *tag;
+  
+  tag = [VSSaxTag beginTag:_tagName attributes:_attrs];
   [self->elementList addObject:tag];
   return tag;
 }
 
 - (void)_endTag:(NSString *)_tagName {
-  [self->elementList addObject:
-        [NSArray arrayWithObjects:@"END",_tagName,NULL]];
+  VSSaxTag *tag;
+  
+  tag = [[VSSaxTag alloc] initEndTag:_tagName];
+  [self->elementList addObject:tag];
+  [tag release]; tag = nil;
 }
 
 - (void)_addSubItems:(NSArray *)_items withData:(NSString *)_content {
   NSEnumerator *itemEnum, *contentEnum;
   NSString *subTag;
-  NSString *subContent;
   
   itemEnum    = [_items objectEnumerator];
   contentEnum = [[_content componentsSeparatedByString:@";"] objectEnumerator];
   
-  while ((subTag=[itemEnum nextObject])) {
+  while ((subTag = [itemEnum nextObject]) != nil) {
+    NSString *subContent;
+    
     subContent = [contentEnum nextObject];
     
     [self _beginTag:subTag withAttrs:nil];
-    if ([subContent length]>0) 
-      [self->elementList addObject:
-        [NSArray arrayWithObjects:@"DATA", subContent, nil]];  
+    if ([subContent length] > 0) {
+      VSSaxTag *a;
+      
+      if ((a = [(VSSaxTag*)[VSSaxTag alloc] initWithData:subContent]) != nil) {
+       [self->elementList addObject:a];
+       [a release];
+      }
+    }
     [self _endTag:subTag];
   }
 }
 
 - (void)_dataTag:(NSString *)_tagName 
-  withAttrs:(id<NSObject,SaxAttributes>)_attrs 
+  withAttrs:(SaxAttributes *)_attrs 
   andContent:(NSString *)_content 
 {
   NSArray *subItems;
   
   _content = [stringFormatter stringByUnescapingRFC2445Text:_content];
+  
   if ([self->attributeElements containsObject:_tagName]) {
     [self _addAttribute:_tagName value:_content];
     return;
@@ -376,83 +442,90 @@ static VSStringFormatter *stringFormatter = nil;
   
   [self _beginTag:_tagName withAttrs:_attrs];
   if ([_content length] > 0) {
-      if ((subItems = [self->subItemMapping objectForKey:_tagName])) {
-        [self _addSubItems:subItems withData:_content];
-      }
-      else {
-        [self->elementList addObject:
-          [NSArray arrayWithObjects:@"DATA", _content, nil]];  
+    if ((subItems = [self->subItemMapping objectForKey:_tagName]) != nil) {
+      [self _addSubItems:subItems withData:_content];
+    }
+    else {
+      VSSaxTag *a;
+       
+      if ((a = [(VSSaxTag *)[VSSaxTag alloc] initWithData:_content]) != nil) {
+       [self->elementList addObject:a];
+       [a release];
       }
+    }
   }
   [self _endTag:_tagName];
 }
 
+/* report events for collected elements */
+
 - (void)_eventsForElements {
   NSEnumerator *enu;
-  NSArray  *obj;
+  VSSaxTag *obj;
   
-  enu = [elementList objectEnumerator];
+  enu = [self->elementList objectEnumerator];
   while ((obj = [enu nextObject]) != nil) {
-    id<NSObject,SaxAttributes> attrs;
-    NSString *type;
-    NSString *name;
-    unichar  *chardata;
-    
-    type = [obj objectAtIndex:0];
-    name = [obj objectAtIndex:1];
-    
-    if ([obj count] > 2) 
-      attrs = [obj objectAtIndex:2];
-    else
-      attrs = nil;
-
-    if ([type isEqualToString:@"BEGIN"]) {
-      [self->contentHandler startElement:name
+    if ([obj->type isEqualToString:VSBeginType]) {
+      [self->contentHandler startElement:obj->tagName
                             namespace:self->prefixURI
-                            rawName:name
-                            attributes:attrs];
+                            rawName:obj->tagName
+                            attributes:obj->attrs];
     } 
-    else if ([type isEqualToString:@"END"]) {
-      [self->contentHandler endElement:name
+    else if ([obj->type isEqualToString:VSEndType]) {
+      [self->contentHandler endElement:obj->tagName
                             namespace:self->prefixURI
-                            rawName:name];
+                            rawName:obj->tagName];
     }
     else {
-      unsigned len = [name length];
-      chardata = malloc(len * sizeof(unichar));
-      [name getCharacters:chardata range:NSMakeRange(0, len)];
+      unichar  *chardata;
+      unsigned len;
+      
+      // TODO: better move to tag itself?
+      len      = [obj->data length];
+      chardata = calloc(len + 1, sizeof(unichar));
+      [obj->data getCharacters:chardata range:NSMakeRange(0, len)];
+      
       [self->contentHandler characters:chardata length:len];
-      if (chardata)
-        free(chardata);
+      if (chardata != NULL) free(chardata); chardata = NULL;
     }
   }
   [elementList removeAllObjects];
 }
 
+/* errors */
+
+- (void)warn:(NSString *)_warn {
+  SaxParseException *e;
+
+  e = (id)[SaxParseException exceptionWithName:@"SaxParseException"
+                            reason:_warn
+                            userInfo:nil];
+  [self->errorHandler warning:e];
+}
+
+/* parsing raw string */
+
 - (void)_parseLine:(NSString *)_line {
   NSString       *tagName, *tagValue;
   NSMutableArray *tagAttributes;
   NSRange        r, todoRange;
   unsigned       length;
 
-  length = [_line length];
+  length    = [_line length];
   todoRange = NSMakeRange(0, length);
   r = [_line rangeOfCharacterFromSet:colonAndSemicolonCharSet
              options:0
              range:todoRange];
   /* is line well-formed? */
   if (r.length == 0) {
-    if (debugOn) {
-      NSLog(@"%s got an improper content line! ->\n%@",
-            __PRETTY_FUNCTION__,
-            _line);
-    }
+    [self warn:[@"got an improper content line! ->\n" 
+                stringByAppendingString:_line]];
     return;
   }
 
-  tagName = [[_line substringToIndex:r.location] uppercaseString];
-  tagAttributes = [[NSMutableArray alloc] init];
-
+  tagName       = [[_line substringToIndex:r.location] uppercaseString];
+  tagAttributes = [[NSMutableArray alloc] initWithCapacity:16];
+  
   /* possible shortcut: if we spotted a ':', we don't have to do "expensive"
      argument scanning/processing.
   */
@@ -470,14 +543,12 @@ static VSStringFormatter *stringFormatter = nil;
                  range:todoRange];
       /* is line well-formed? */
       if (r.length == 0 || r.location == 0) {
-        if (debugOn) {
-          NSLog(@"%s got an improper content line! ->\n%@",
-                __PRETTY_FUNCTION__,
-                _line);
-        }
-        [tagAttributes release];
+       [self warn:[@"got an improper content line! ->\n" 
+                    stringByAppendingString:_line]];
+        [tagAttributes release]; tagAttributes = nil;
         return;
       }
+      
       /* first check if delimiter candidate is escaped */
       if ([_line characterAtIndex:(r.location - 1)] != '\\') {
         unichar delimiter;
@@ -514,9 +585,11 @@ static VSStringFormatter *stringFormatter = nil;
   }
   tagValue = [_line substringFromIndex:NSMaxRange(r)];
 
+  
+  /* process tag */
+  
   if ([tagName isEqualToString:@"BEGIN"]) {
-    id tag;
-    
+    VSSaxTag *tag;
     tag = [self _beginTag:[self _mapTagName:tagValue] 
                 withAttrs:[[[SaxAttributes alloc] init] autorelease]];
     [self->cardStack addObject:tag];
@@ -527,18 +600,21 @@ static VSStringFormatter *stringFormatter = nil;
     mtName = [self _mapTagName:tagValue];
     if ([self->cardStack count] > 0) {
       NSString *expectedName;
-
-      expectedName = [[self->cardStack lastObject] objectAtIndex:1];
+      
+      expectedName = [(VSSaxTag *)[self->cardStack lastObject] tagName];
       if (![expectedName isEqualToString:mtName]) {
-        if (debugOn) {
-          NSLog(@"%s found end tag '%@' which doesn't match expected name "
-                @"'%@'! Tag '%@' hasn't been closed properly. Given iCal "
-                @"document contains errors!",
-                __PRETTY_FUNCTION__,
-                mtName,
-                expectedName,
-                expectedName);
-        }
+       NSString *s;
+       
+       // TODO: rather report an error?
+       // TODO: setup userinfo dict with details
+       s = [NSString stringWithFormat:
+                       @"Found end tag '%@' which does not match expected "
+                       @"name '%@'!"
+                       @" Tag '%@' has not been closed properly. Given "
+                       @"document contains errors!",
+                       mtName, expectedName, expectedName];
+       [self warn:s];
+       
         /* probably futile attempt to parse anyways */
         if (debugOn) {
           NSLog(@"%s trying to fix previous error by inserting bogus end "
@@ -550,32 +626,52 @@ static VSStringFormatter *stringFormatter = nil;
       }
     }
     else {
-      if (debugOn) {
-        NSLog(@"%s found end tag '%@' without any open tags left?!",
-              __PRETTY_FUNCTION__,
-              mtName);
-      }
+      // TOOD: generate error?
+      [self warn:[@"found end tag without any open tags left: "
+                  stringByAppendingString:mtName]];
     }
     [self _endTag:mtName];
     [self->cardStack removeLastObject];
+    
+    /* report parsed elements */
+    
     if ([self->cardStack count] == 0)
       [self _eventsForElements];
   }
   else {
     [self _dataTag:[self _mapTagName:tagName]
-         withAttrs:[self _mapAttrs:tagAttributes forTag:tagName] 
-        andContent:tagValue];
+         withAttrs:[self _mapAttrs:tagAttributes forTag:tagName] 
+         andContent:tagValue];
   }
   [tagAttributes release];
 }
 
-- (void)_parseString:(NSString *)_rawString {
-  unsigned pos, length;
-  NSMutableString *line;
-  NSRange r;
 
+/* top level parsing method */
+
+- (void)_reportDocStart {
   [self->contentHandler startDocument];
   [self->contentHandler startPrefixMapping:@"" uri:self->prefixURI];
+
+  [self->contentHandler startElement:@"vCardSet" namespace:self->prefixURI
+                        rawName:@"vCardSet" attributes:nil];
+}
+- (void)_reportDocEnd {
+  [self->contentHandler endElement:@"vCardSet" namespace:self->prefixURI
+                        rawName:@"vCardSet"];
+
+  [self->contentHandler endPrefixMapping:@""];
+  [self->contentHandler endDocument];
+}
+
+- (void)_parseString:(NSString *)_rawString {
+  NSMutableString *line;
+  unsigned pos, length;
+  NSRange  r;
+
+  [self _reportDocStart];
+  
+  /* start parsing */
   
   length = [_rawString length];
   /* RFC2445:
@@ -587,18 +683,22 @@ static VSStringFormatter *stringFormatter = nil;
   /* probably too optimistic */ 
   line = [[NSMutableString alloc] initWithCapacity:75 + 2];
 
-  for(pos = 0; pos < length; pos++) {
-    unichar c = [_rawString characterAtIndex:pos];
+  for (pos = 0; pos < length; pos++) {
+    unichar c;
+    
+    c = [_rawString characterAtIndex:pos];
     
     if (c == '\r') {
       if (((length - 1) - pos) >= 1) {
         if ([_rawString characterAtIndex:pos + 1] == '\n') {
           BOOL isAtEndOfLine = YES;
+         
           /* test for folding first */
           if (((length - 1) - pos) >= 2) {
-            unichar ws = [_rawString characterAtIndex:pos + 2];
-            isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO
-                                                                     : YES;
+            unichar ws;
+           
+           ws = [_rawString characterAtIndex:pos + 2];
+            isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO :YES;
             if (!isAtEndOfLine) {
               /* assemble part of line up to pos */
               if (r.length > 0) {
@@ -624,19 +724,19 @@ static VSStringFormatter *stringFormatter = nil;
       }
       else {
         /* garbled last line! */
-        if (debugOn) {
-          NSLog(@"%s Last line is truncated, trying to parse anyways!",
-                __PRETTY_FUNCTION__);
-        }
+       [self warn:@"last line is truncated, trying to parse anyways!"];
       }
     }
     else if (c == '\n') { /* broken, non-standard */
       BOOL isAtEndOfLine = YES;
+      
       /* test for folding first */
       if (((length - 1) - pos) >= 1) {
-        unichar ws = [_rawString characterAtIndex:pos + 1];
-        isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO
-                                                                 : YES;
+        unichar ws;
+       
+       ws = [_rawString characterAtIndex:(pos + 1)];
+       
+        isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO : YES;
         if (!isAtEndOfLine) {
           /* assemble part of line up to pos */
           if (r.length > 0) {
@@ -663,30 +763,72 @@ static VSStringFormatter *stringFormatter = nil;
     }
   }
   if (r.length > 0) {
-    if (debugOn) {
-      NSLog(@"%s Last line of iCal string is not properly terminated!",
-            __PRETTY_FUNCTION__);
-    }
+    [self warn:@"Last line of parse string is not properly terminated!"];
     [line appendString:[_rawString substringWithRange:r]];
     [self _parseLine:line];
   }
 
   if ([self->cardStack count] != 0) {
-    if (debugOn) {
-      NSLog(@"%s found elements on cardStack. This indicates an improper "
-            @"iCal structure! Not all required events will have been "
-            @"generated, leading to unpredictable results!",
-            __PRETTY_FUNCTION__);
-    }
+    [self warn:@"found elements on cardStack. This indicates an improper "
+            @"nesting structure! Not all required events will have been "
+            @"generated, leading to unpredictable results!"];
+    [self->cardStack removeAllObjects]; // clean up
   }
-
-  [line release];
-  [self->contentHandler endPrefixMapping:@""];
-  [self->contentHandler endDocument];
+  
+  [line release]; line = nil;
+  
+  [self _reportDocEnd];
 }
 
 /* main entry functions */
 
+- (id)sourceForData:(NSData *)_data systemId:(NSString *)_sysId {
+  SaxParseException *e = nil;
+  NSStringEncoding encoding;
+  unsigned len;
+  const unsigned char *bytes;
+  id source;
+  
+  if (debugOn) {
+    NSLog(@"%s: trying to decode data (0x%08X,len=%d) ...",
+           __PRETTY_FUNCTION__, _data, [_data length]);
+  }
+  
+  if ((len = [_data length]) == 0) {
+    e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
+                              reason:@"Got no parsing data!"
+                              userInfo:nil];
+    [self->errorHandler fatalError:e];
+    return nil;
+  }
+  if (len < 10) {
+    e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
+                              reason:@"Input data to short for vCard!"
+                              userInfo:nil];
+    [self->errorHandler fatalError:e];
+    return nil;
+  }
+  
+  bytes = [_data bytes];
+  if ((bytes[0] == 0xFF && bytes[1] == 0xFE) ||
+      (bytes[0] == 0xFE && bytes[1] == 0xFF)) {
+    encoding = NSUnicodeStringEncoding;
+  }
+  else
+    encoding = NSUTF8StringEncoding;
+  
+  // FIXME: Data is not always utf-8.....
+  source = [[[NSString alloc] initWithData:_data encoding:encoding]
+            autorelease];
+  if (source == nil) {
+    e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
+                              reason:@"Could not convert input to string!"
+                              userInfo:nil];
+    [self->errorHandler fatalError:e];
+  }
+  return source;
+}
+
 - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
   if (debugOn)
     NSLog(@"%s: parse: %@ (sysid=%@)", __PRETTY_FUNCTION__, _source, _sysId);
@@ -704,26 +846,22 @@ static VSStringFormatter *stringFormatter = nil;
   }
   
   if ([_source isKindOfClass:[NSData class]]) {
-    if (debugOn) {
-      NSLog(@"%s: trying to decode data (0x%08X,len=%d) ...",
-           __PRETTY_FUNCTION__, _source, [_source length]);
-    }
     if (_sysId == nil) _sysId = @"<data>";
-    
-    // FIXME: Data is not always utf-8.....
-    _source = [[[NSString alloc]
-                initWithData:_source encoding:NSUTF8StringEncoding]
-                autorelease];
+    if ((_source = [self sourceForData:_source systemId:_sysId]) == nil)
+      return;
   }
 
   if (![_source isKindOfClass:[NSString class]]) {
     SaxParseException *e;
+    NSString *s;
     
     if (debugOn) 
       NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source);
     
+    s = [@"cannot handle data-source: " stringByAppendingString:
+           [_source description]];
     e = (id)[SaxParseException exceptionWithName:@"SaxIOException"
-                               reason:@"cannot handle data-source"
+                               reason:s
                                userInfo:nil];
     
     [self->errorHandler fatalError:e];