/* Copyright (C) 2000-2004 Max Berger This file is part of versitSaxDriver, written for the OpenGroupware.org project (OGo). OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // $Id$ #include "VersitSaxDriver.h" #include "common.h" @implementation VersitSaxDriver static BOOL doDebug = NO; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; static BOOL didInit = NO; if (didInit) return; didInit = YES; doDebug = [ud boolForKey:@"OGoDebugVersitSaxDriver"]; } + (NSCharacterSet *)newlineCharacterSet { return [NSCharacterSet characterSetWithCharactersInString:@"\x0a\x0d\x85"]; } - (id)init { [VersitSaxDriver initialize]; if ((self = [super init])) { self->prefixURI = @""; self->cardStack = [[NSMutableArray alloc] init]; self->elementList = [[NSMutableArray alloc] init]; self->attributeMapping = [[NSMutableDictionary alloc] init]; self->subItemMapping = [[NSMutableDictionary alloc] init]; } return self; } - (void)dealloc { [self->contentHandler release]; [self->prefixURI release]; [self->cardStack release]; [self->elementList release]; [self->attributeElements release]; [self->elementMapping release]; [self->attributeMapping release]; [self->subItemMapping release]; [super dealloc]; } /* accessors */ - (void)setFeature:(NSString *)_name to:(BOOL)_value { } - (BOOL)feature:(NSString *)_name { return NO; } - (void)setProperty:(NSString *)_name to:(id)_value { } - (id)property:(NSString *)_name { return nil; } /* handlers */ - (void)setContentHandler:(id)_handler { ASSIGN(self->contentHandler,_handler); } - (void)setDTDHandler:(id)_handler { // FIXME } - (void)setErrorHandler:(id)_handler { // FIXME } - (void)setEntityResolver:(id)_handler { // FIXME } - (id)contentHandler { return self->contentHandler; } - (id)dtdHandler { // FIXME return NULL; } - (id)errorHandler { // FIXME return NULL; } - (id)entityResolver { // FIXME return NULL; } - (void)setPrefixURI:(NSString *)_uri { ASSIGNCOPY(self->prefixURI, _uri); } - (NSString *)prefixURI { return self->prefixURI; } - (void)setAttributeElements:(NSSet *)_elements { ASSIGNCOPY(self->attributeElements, _elements); } - (NSSet *)attributeElements { return self->attributeElements; } - (void)setElementMapping:(NSDictionary *)_mapping { ASSIGNCOPY(self->elementMapping, _mapping); } - (NSDictionary *)elementMapping { return self->elementMapping; } - (void)setAttributeMapping:(NSDictionary *)_mapping { [self setAttributeMapping:_mapping forElement:@""]; } - (void)setAttributeMapping:(NSDictionary *)_mapping forElement:(NSString *)_element { if (_element==NULL) _element = @""; [attributeMapping setObject:_mapping forKey:_element]; } - (void)setSubItemMapping:(NSArray *)_mapping forElement:(NSString *)_element { [subItemMapping setObject:_mapping forKey:_element]; } /* parsing */ - (NSString *)_mapTagName:(NSString *)_tagName { NSString *ret; NSRange range; NSCharacterSet *dotSet; if ((ret = [self->elementMapping objectForKey:_tagName]) == nil) { //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping); ret = _tagName; /* This is to allow parsing of vCards produced by Apple Addressbook. AFAIK the .dot notation is a non-standard extension */ dotSet = [NSCharacterSet characterSetWithCharactersInString:@"."]; range = [_tagName rangeOfCharacterFromSet:dotSet]; if ((range.location > 0) && (range.location < [_tagName length])) { NSString *s; s = [_tagName substringFromIndex:(range.location + 1)]; ret = [self _mapTagName:s]; } } return ret; } - (void)_addAttribute:(NSString *)_attribute value:(NSString *)_value toAttrs:(SaxAttributes *)_attrs { [_attrs addAttribute:_attribute uri:self->prefixURI rawName:_attribute type:@"CDATA" value:_value]; } - (void)_addAttribute:(NSString *)_attribute value:(NSString *)_value { NSArray *element = [cardStack lastObject]; SaxAttributes *attrs = [element objectAtIndex:2]; [self _addAttribute:_attribute value:_value toAttrs:attrs]; } - (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName { NSDictionary *map; NSString *mappedName; map = [self->attributeMapping objectForKey:_tagName]; mappedName = [map objectForKey:_attrName]; if (mappedName == nil) { map = [self->attributeMapping objectForKey:[self _mapTagName:_tagName]]; mappedName = [map objectForKey:_attrName]; } if (mappedName == nil) { map = [self->attributeMapping objectForKey:@""]; mappedName = [map objectForKey:_attrName]; } if (mappedName == nil) mappedName = _attrName; return mappedName; } - (void)_parseAttr:(NSString *)_attr forTag:(NSString *)_tagName intoAttr:(NSString **)attr_ intoValue:(NSString **)value_ { NSCharacterSet *equalSign = [NSCharacterSet characterSetWithCharactersInString:@"="]; NSCharacterSet *commaSign = [NSCharacterSet characterSetWithCharactersInString:@","]; NSRange range; NSString *attrName = nil; NSMutableString *attrValue = nil; NSString *mappedName = nil; range = [_attr rangeOfCharacterFromSet:equalSign]; /* Unfortunately this is the only way it works with OSX and libFondation */ if ((range.location > 0) && (range.location < [_attr length])){ attrName = [[_attr substringToIndex:range.location] uppercaseString]; attrValue = [[_attr substringFromIndex:(range.location + 1)] mutableCopy]; } else { attrName = @"TYPE"; attrValue = [[NSMutableString alloc]initWithString:_attr]; } mappedName = [self _mapAttrName:attrName forTag:_tagName]; range = [attrValue rangeOfCharacterFromSet:commaSign]; while ((range.location > 0) && (range.location < [attrValue length])){ [attrValue replaceCharactersInRange:range withString:@" "]; range = [attrValue rangeOfCharacterFromSet:commaSign]; } *attr_ = attrName; *value_ = [NSString stringWithString:attrValue]; [attrValue release]; } - (id)_mapAttrs:(NSArray *)_attrs forTag:(NSString *)_tagName { SaxAttributes *retAttrs; NSEnumerator *enu; NSString *curAttr=nil; NSString *mappedAttr=nil; NSString *mappedValue=nil; NSString *oldValue=nil; NSMutableDictionary *attributes=[[NSMutableDictionary alloc]init]; if ([_attrs count]==0) return NULL; retAttrs = [[SaxAttributes alloc]init]; enu = [_attrs objectEnumerator]; while ((curAttr=[enu nextObject])) { [self _parseAttr:curAttr forTag:_tagName intoAttr:&mappedAttr intoValue:&mappedValue]; if ((oldValue=[attributes objectForKey:mappedAttr])) { [attributes setObject: [NSString stringWithFormat:@"%@ %@",oldValue,mappedValue] forKey:mappedAttr]; } else [attributes setObject:mappedValue forKey:mappedAttr]; } enu = [attributes keyEnumerator]; while ((curAttr=[enu nextObject])) { [self _addAttribute:curAttr value:[attributes objectForKey:curAttr] toAttrs:retAttrs]; } [attributes release]; return [retAttrs autorelease]; } - (NSArray *)_beginTag:(NSString *)_tagName withAttrs:(id)_attrs { NSArray *tag; tag = [NSArray arrayWithObjects:@"BEGIN" ,_tagName, _attrs, NULL]; [self->elementList addObject:tag]; return tag; } - (void)_endTag:(NSString *)_tagName { NSArray *tag; tag = [NSArray arrayWithObjects:@"END",_tagName,NULL]; [self->elementList addObject:tag]; } - (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])) { subContent = [contentEnum nextObject]; [self _beginTag:subTag withAttrs:nil]; if ([subContent length]>0) [self->elementList addObject: [NSArray arrayWithObjects:@"DATA", subContent, nil]]; [self _endTag:subTag]; } } - (void)_dataTag:(NSString *)_tagName withAttrs:(id)_attrs andContent:(NSString *)_content { NSArray *subItems; if ([self->attributeElements containsObject:_tagName]) { [self _addAttribute:_tagName value:_content]; } else { [self _beginTag:_tagName withAttrs:_attrs]; if ([_content length] > 0) { if ((subItems=[self->subItemMapping objectForKey:_tagName])) { [self _addSubItems:subItems withData:_content]; } else { NSArray *tag; tag = [[NSArray alloc] initWithObjects:@"DATA", _content, nil]; [self->elementList addObject:tag]; [tag release]; } } [self _endTag:_tagName]; } } - (void)_eventsForElements { NSEnumerator *enu; NSArray *obj; NSString *type; NSString *name; unichar *chardata; id attrs = nil; enu = [elementList objectEnumerator]; while ((obj = [enu nextObject])) { type = [obj objectAtIndex:0]; name = [obj objectAtIndex:1]; attrs = nil; if ([obj count] > 2) attrs = [obj objectAtIndex:2]; if ([type isEqual:@"BEGIN"]) { [self->contentHandler startElement:name namespace:self->prefixURI rawName:name attributes:attrs]; } else if ([type isEqual:@"END"]) { [self->contentHandler endElement:name namespace:self->prefixURI rawName:name]; } else { chardata = malloc(([name length] + 2) * sizeof(unichar)); [name getCharacters:chardata]; [self->contentHandler characters:chardata length:[name length]]; if (chardata) free(chardata); } } [elementList removeAllObjects]; } - (void)_parseLine:(NSString *)_line { NSCharacterSet *colonAndSemicolon; NSScanner *theScanner; NSString *tagName; NSString *tagAttribute; NSString *tagValue; NSMutableArray *tagAttributes; colonAndSemicolon = [NSCharacterSet characterSetWithCharactersInString:@":;"]; theScanner = [NSScanner scannerWithString:_line]; tagAttributes = [[NSMutableArray alloc] init]; tagName = @""; [theScanner scanUpToCharactersFromSet:colonAndSemicolon intoString:&tagName]; while ([theScanner scanString:@";" intoString:NULL]) { [theScanner scanUpToCharactersFromSet:colonAndSemicolon intoString:&tagAttribute]; [tagAttributes addObject:tagAttribute]; } [theScanner scanString:@":" intoString:NULL]; tagValue = [_line substringFromIndex:[theScanner scanLocation]]; //NSLog (@"%@ %@ %@",tagName,tagAttributes, tagValue); tagName = [tagName uppercaseString]; if ([tagName isEqual:@"BEGIN"]) { id obj; obj = [self _beginTag:[self _mapTagName:tagValue] withAttrs:[[[SaxAttributes alloc] init] autorelease]]; [self->cardStack addObject:obj]; } else if ([tagName isEqual:@"END"]) { [self _endTag:[self _mapTagName:tagValue]]; [self->cardStack removeLastObject]; if ([self->cardStack count]==0) [self _eventsForElements]; } else { [self _dataTag:[self _mapTagName:tagName] withAttrs:[self _mapAttrs:tagAttributes forTag:tagName] andContent:tagValue]; } [tagAttributes release]; } - (void)_parseString:(NSString *)_stringData { int position; BOOL newline = NO; unichar c; int lineLength = 1024; unichar *currentLine; int linePos = 0; currentLine = malloc( (lineLength + 1 ) * (sizeof(unichar))); [self->contentHandler startDocument]; [self->contentHandler startPrefixMapping:@"" uri:self->prefixURI]; for (position = 0; position < [_stringData length]; position++) { c = [_stringData characterAtIndex:position]; if ([[VersitSaxDriver newlineCharacterSet] characterIsMember:c]) { newline = YES; continue; } if (newline) { if ([[NSCharacterSet whitespaceCharacterSet]characterIsMember:c]) { // unfold } else { NSString *line; line = [[NSString alloc] initWithCharacters:currentLine length:linePos]; [self _parseLine:line]; [line release]; currentLine[0] = c; linePos = 1; } newline = NO; } else { currentLine[linePos] = c; linePos++; if (linePos >= lineLength) { lineLength += 1024; currentLine = realloc(currentLine, ( lineLength + 2 ) * (sizeof(unichar))); } } } [self _parseLine:[NSString stringWithCharacters:currentLine length:linePos]]; if (currentLine) free(currentLine); [self->contentHandler endPrefixMapping:@""]; [self->contentHandler endDocument]; } - (void)parseFromSource:(id)_source { if (doDebug) NSLog(@"%s: parse: %@", __PRETTY_FUNCTION__, _source); if ([_source isKindOfClass:[NSURL class]]) { if (doDebug) NSLog(@"%s: trying to load URL...",__PRETTY_FUNCTION__); _source = [_source resourceDataUsingCache:NO]; } if ([_source isKindOfClass:[NSData class]]) { // FIXME: Data is not always utf-8..... if (doDebug) NSLog(@"%s: trying to decode data...",__PRETTY_FUNCTION__); _source = [[[NSString alloc] initWithData:_source encoding:NSUTF8StringEncoding] autorelease]; } if ([_source isKindOfClass:[NSString class]]) { if (doDebug) NSLog(@"%s: trying to parse string...",__PRETTY_FUNCTION__); [self _parseString:_source]; } else { if (doDebug) NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source); // FIXME: Return Error } } - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId { [self parseFromSource:_source]; } - (void)parseFromSystemId:(NSString *)_sysId { NSURL *url; if ((url = [NSURL URLWithString:_sysId])) [self parseFromSource:url systemId:_sysId]; } /* debugging */ - (BOOL)isDebuggingEnabled { return doDebug; } @end /* VersitSaxDriver */