Source/AIXMLChatlogConverter.m
changeset 3271 4dc4e5bceaa9
parent 3087 30703336e5b6
child 3679 f4294bb53b0f
     1.1 --- a/Source/AIXMLChatlogConverter.m	Tue Dec 29 00:36:28 2009 -0500
     1.2 +++ b/Source/AIXMLChatlogConverter.m	Mon Aug 16 23:07:04 2010 -0700
     1.3 @@ -31,37 +31,98 @@
     1.4  #define KEY_WEBKIT_USE_NAME_FORMAT				@"Use Custom Name Format"
     1.5  #define KEY_WEBKIT_NAME_FORMAT					@"Name Format"
     1.6  
     1.7 -static void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context);
     1.8 -static void addChild(CFXMLParserRef parser, void *parent, void *child, void *context);
     1.9 -static void endStructure(CFXMLParserRef parser, void *xmlType, void *context);
    1.10 +@interface NSMutableString (XMLMethods)
    1.11 +- (void)stripInvalidCharacters;
    1.12 +@end
    1.13 +
    1.14 +@implementation NSMutableString (XMLMethods)
    1.15 +
    1.16 +//Strip invalid XML characters
    1.17 +- (void)stripInvalidCharacters
    1.18 +{
    1.19 +    static NSCharacterSet *invalidXMLCharacterSet;
    1.20 +    
    1.21 +    if (invalidXMLCharacterSet == nil)
    1.22 +    {
    1.23 +        // First, create a character set containing all valid UTF8 characters.
    1.24 +        NSMutableCharacterSet *xmlCharacterSet = [[NSMutableCharacterSet alloc] init];        
    1.25 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0x9, 1)];       
    1.26 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0xA, 1)];        
    1.27 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0xD, 1)];        
    1.28 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0x20, 0xD7FF - 0x20)];
    1.29 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0xE000, 0xFFFD - 0xE000)];        
    1.30 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0x10000, 0x10FFFF - 0x10000)];        
    1.31 +        // Then create and retain an inverted set, which will thus contain all invalid XML characters.        
    1.32 +        invalidXMLCharacterSet = [[xmlCharacterSet invertedSet] retain];        
    1.33 +        [xmlCharacterSet release];        
    1.34 +    }
    1.35 +    
    1.36 +    // Are there any invalid characters in this string?    
    1.37 +    NSRange range = [self rangeOfCharacterFromSet:invalidXMLCharacterSet];
    1.38 +    
    1.39 +    // Otherwise go through and remove any illegal XML characters from a copy of the string.
    1.40 +    while (range.length > 0)
    1.41 +    {        
    1.42 +        [self deleteCharactersInRange:range];
    1.43 +        range = [self rangeOfCharacterFromSet:invalidXMLCharacterSet                 
    1.44 +                                      options:0                 
    1.45 +                                        range:NSMakeRange(range.location,[self length]-range.location)];        
    1.46 +    }    
    1.47 +}
    1.48 +
    1.49 +@end
    1.50 +
    1.51 +@interface NSXMLElement (AIAttributeDict)
    1.52 +- (NSDictionary *)AIAttributesAsDictionary;
    1.53 +@end
    1.54 +
    1.55 +@implementation NSXMLElement (AIAttributeDict)
    1.56 +
    1.57 +- (NSDictionary *)AIAttributesAsDictionary 
    1.58 +{
    1.59 +    NSArray *attrArray = [self attributes];
    1.60 +    NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
    1.61 +    for (NSXMLNode *attr in attrArray) {
    1.62 +        [attributes setObject:attr forKey:[attr name]];
    1.63 +    }
    1.64 +    return attributes;
    1.65 +}
    1.66 +
    1.67 +@end
    1.68 +
    1.69 +@interface AIXMLChatlogConverter()
    1.70 +- (NSAttributedString *)readData:(NSData *)xmlData withOptions:(NSDictionary *)options retrying:(BOOL)reentrancyFlag;
    1.71 +@end
    1.72  
    1.73  @implementation AIXMLChatlogConverter
    1.74  
    1.75  + (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options
    1.76  {
    1.77 -	AIXMLChatlogConverter *converter = [[AIXMLChatlogConverter alloc] init];
    1.78 -	NSAttributedString *ret = [[converter readFile:filePath withOptions:options] retain];
    1.79 -	[converter release];
    1.80 -	return [ret autorelease];
    1.81 +	static AIXMLChatlogConverter *converter;
    1.82 +    if (!converter) {
    1.83 +        converter = [[AIXMLChatlogConverter alloc] init];
    1.84 +	}
    1.85 +    NSData *xmlData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
    1.86 +    [converter->htmlDecoder setBaseURL:[filePath stringByDeletingLastPathComponent]];
    1.87 +    NSAttributedString *result = nil;
    1.88 +    @try {
    1.89 +        result = [converter readData:xmlData withOptions:options retrying:NO];
    1.90 +    } @catch (NSException *e) {
    1.91 +        NSLog(@"Error \"%@\" parsing log file at %@.", e, filePath);
    1.92 +        return [[[NSAttributedString alloc] initWithString:@"Sorry, there was an error parsing this transcript. It may be corrupt."] autorelease];
    1.93 +    }
    1.94 +    return result;
    1.95  }
    1.96  
    1.97  - (id)init
    1.98  {
    1.99  	if ((self = [super init])) {
   1.100 -	
   1.101 -		state = XML_STATE_NONE;
   1.102 -		
   1.103 -		inputFileString = nil;
   1.104 -		sender = nil;
   1.105 -		mySN = nil;
   1.106 -		myDisplayName = nil;
   1.107 -		date = nil;
   1.108 -		parser = NULL;
   1.109 -		status = nil;
   1.110 -		
   1.111 +        if (!newlineAttributedString) {
   1.112 +            newlineAttributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:nil];
   1.113 +        }
   1.114 +        
   1.115 +        htmlDecoder = [[AIHTMLDecoder alloc] init];
   1.116  		dateFormatter = [[NSDateFormatter localizedDateFormatterShowingSeconds:YES showingAMorPM:YES] retain];
   1.117 -		
   1.118 -		newlineAttributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:nil];
   1.119  
   1.120  		statusLookup = [[NSDictionary alloc] initWithObjectsAndKeys:
   1.121  			AILocalizedString(@"Online", nil), @"online",
   1.122 @@ -80,15 +141,7 @@
   1.123  			[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_DESK], @"notAtMyDesk",
   1.124  			[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_IN_OFFICE], @"notInTheOffice",
   1.125  			[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_STEPPED_OUT], @"steppedOut",
   1.126 -			nil];
   1.127 -			
   1.128 -		if ([[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_NAME_FORMAT
   1.129 -													  group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] boolValue]) {
   1.130 -			nameFormat = [[adium.preferenceController preferenceForKey:KEY_WEBKIT_NAME_FORMAT
   1.131 -																   group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] intValue];
   1.132 -		} else {
   1.133 -			nameFormat = AIDefaultName;
   1.134 -		}
   1.135 +			nil];			
   1.136  	}
   1.137  
   1.138  	return self;
   1.139 @@ -97,317 +150,173 @@
   1.140  - (void)dealloc
   1.141  {
   1.142  	[dateFormatter release];
   1.143 -	[newlineAttributedString release];
   1.144 -	[inputFileString release];
   1.145 -	[eventTranslate release];
   1.146 -	[sender release];
   1.147 -	[senderAlias release];
   1.148 -	[mySN release];
   1.149 -	[myDisplayName release];
   1.150 -	[service release];
   1.151 -	[date release];
   1.152 -	[status release];
   1.153 -	[output release];
   1.154  	[statusLookup release];
   1.155  	[htmlDecoder release];
   1.156  	[super dealloc];
   1.157  }
   1.158  
   1.159 -- (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options
   1.160 +- (NSAttributedString *)readData:(NSData *)xmlData withOptions:(NSDictionary *)options retrying:(BOOL)reentrancyFlag
   1.161  {
   1.162 -	NSData *inputData = [NSData dataWithContentsOfFile:filePath]; 
   1.163 -	inputFileString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding]; 
   1.164 -	NSURL *url = [[NSURL alloc] initFileURLWithPath:filePath];
   1.165 -	output = [[NSMutableAttributedString alloc] init];
   1.166 +    if (!xmlData) {
   1.167 +        return [[[NSAttributedString alloc] initWithString:@""] autorelease];
   1.168 +    }
   1.169 +    AINameFormat nameFormat;
   1.170 +    if ([[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_NAME_FORMAT
   1.171 +                                                group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] boolValue]) {
   1.172 +        nameFormat = [[adium.preferenceController preferenceForKey:KEY_WEBKIT_NAME_FORMAT
   1.173 +                                                             group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] intValue];
   1.174 +    } else {
   1.175 +        nameFormat = AIDefaultName;
   1.176 +    }
   1.177 +    NSMutableAttributedString *output = [[[NSMutableAttributedString alloc] init] autorelease];
   1.178 +    
   1.179 +    NSError *err=nil;
   1.180 +	NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:xmlData
   1.181 +                                                         options:NSXMLNodePreserveCDATA
   1.182 +                                                           error:&err] autorelease];    
   1.183  	
   1.184 -	htmlDecoder = [[AIHTMLDecoder alloc] init];
   1.185 -	[htmlDecoder setBaseURL:[filePath stringByDeletingLastPathComponent]];
   1.186 -	
   1.187 -	showTimestamps = [[options objectForKey:@"showTimestamps"] boolValue];
   1.188 -	showEmoticons = [[options objectForKey:@"showEmoticons"] boolValue];
   1.189 +	if (!xmlDoc)
   1.190 +	{    
   1.191 +        goto ohno;
   1.192 +    }
   1.193 +    
   1.194 +    BOOL showTimestamps = [[options objectForKey:@"showTimestamps"] boolValue];
   1.195 +	BOOL showEmoticons = [[options objectForKey:@"showEmoticons"] boolValue];
   1.196 +    
   1.197 +    NSXMLElement *chatElement = [[xmlDoc nodesForXPath:@"//chat" error:&err] lastObject];
   1.198 +    
   1.199 +    NSDictionary *chatAttributes = [chatElement AIAttributesAsDictionary];
   1.200 +    NSString *mySN = [[chatAttributes objectForKey:@"account"] stringValue];
   1.201 +    NSString *service = [[chatAttributes objectForKey:@"service"] stringValue];
   1.202 +    
   1.203 +    NSString *myDisplayName = nil;
   1.204 +    
   1.205 +    for (AIAccount *account in adium.accountController.accounts) {
   1.206 +        if ([[account.UID compactedString] isEqualToString:[mySN compactedString]] &&
   1.207 +            [account.service.serviceID isEqualToString:service]) {
   1.208 +            myDisplayName = [account.displayName retain];
   1.209 +            break;
   1.210 +        }
   1.211 +    }    
   1.212 +        
   1.213 +    NSArray *elements = [xmlDoc nodesForXPath:@"//message | //status" error:&err];
   1.214 +    if (!elements) {
   1.215 +        goto ohno;
   1.216 +    }
   1.217 +    
   1.218 +    for (NSXMLElement *element in elements) {
   1.219 +        NSString *type = [element name];
   1.220 +     
   1.221 +        NSDictionary *attributes = [element AIAttributesAsDictionary];
   1.222 +        
   1.223 +        if ([type isEqualToString:@"message"]) {
   1.224 +            NSString *senderAlias = [[attributes objectForKey:@"alias"] stringValue];
   1.225 +            NSString *dateStr = [[attributes objectForKey:@"time"] stringValue];
   1.226 +            NSDate *date = dateStr ? [NSCalendarDate calendarDateWithString:dateStr] : nil;
   1.227 +            NSString *sender = [[attributes objectForKey:@"sender"] stringValue];
   1.228 +            NSString *shownSender = (senderAlias ? senderAlias : sender);
   1.229 +            BOOL autoResponse = [[[attributes objectForKey:@"auto"] stringValue] isEqualToString:@"true"];
   1.230  
   1.231 -	CFXMLParserCallBacks callbacks = {
   1.232 -		0,
   1.233 -		createStructure,
   1.234 -		addChild,
   1.235 -		endStructure,
   1.236 -		NULL,
   1.237 -		NULL
   1.238 -	};
   1.239 -	CFXMLParserContext context = {
   1.240 -		0,
   1.241 -		self,
   1.242 -		CFRetain,
   1.243 -		CFRelease,
   1.244 -		NULL
   1.245 -	};
   1.246 -	parser = CFXMLParserCreate(NULL, (CFDataRef)inputData, NULL, kCFXMLParserSkipMetaData | kCFXMLParserSkipWhitespace, kCFXMLNodeCurrentVersion, &callbacks, &context);
   1.247 -	if (!CFXMLParserParse(parser)) {
   1.248 -		NSLog(@"%@: Parser %@ for inputFileString %@ returned false.",
   1.249 -			  [self class], parser, inputFileString);
   1.250 -		[output release];
   1.251 -		output = nil;
   1.252 -	}
   1.253 -	CFRelease(parser);
   1.254 -	parser = nil;
   1.255 -	[url release];
   1.256 -	return output;
   1.257 -}
   1.258 +            NSMutableString *messageXML = [NSMutableString string];
   1.259 +            for (NSXMLNode *node in [element children]) {
   1.260 +                [messageXML appendString:[node XMLString]];
   1.261 +            }
   1.262 +    
   1.263 +            NSString *displayName = nil, *longDisplayName = nil;
   1.264 +            
   1.265 +            BOOL sentMessage = [mySN isEqualToString:sender];
   1.266  
   1.267 -- (void)startedElement:(NSString *)name info:(const CFXMLElementInfo *)info
   1.268 -{
   1.269 -	NSDictionary *attributes = (NSDictionary *)info->attributes;
   1.270 -	
   1.271 -	switch(state){
   1.272 -		case XML_STATE_NONE:
   1.273 -			if([name isEqualToString:@"chat"])
   1.274 -			{
   1.275 -				[mySN release];
   1.276 -				mySN = [[attributes objectForKey:@"account"] retain];
   1.277 +            
   1.278 +            if (sentMessage) {
   1.279 +                //Find an account if one exists, and use its name
   1.280 +                displayName = (myDisplayName ? myDisplayName : sender);
   1.281 +            } else {
   1.282 +                AIListObject *listObject = [adium.contactController existingListObjectWithUniqueID:[AIListObject internalObjectIDForServiceID:service UID:sender]];
   1.283 +                
   1.284 +                displayName = listObject.displayName;
   1.285 +                longDisplayName = [listObject longDisplayName];
   1.286 +            }
   1.287 +                
   1.288 +            if (displayName && !sentMessage) {
   1.289 +                switch (nameFormat) {
   1.290 +                    case AIDefaultName:
   1.291 +                        shownSender = (longDisplayName ? longDisplayName : displayName);
   1.292 +                        break;
   1.293 +                        
   1.294 +                    case AIDisplayName:
   1.295 +                        shownSender = displayName;
   1.296 +                        break;
   1.297 +                        
   1.298 +                    case AIDisplayName_ScreenName:
   1.299 +                        shownSender = [NSString stringWithFormat:@"%@ (%@)",displayName,sender];
   1.300 +                        break;
   1.301 +                        
   1.302 +                    case AIScreenName_DisplayName:
   1.303 +                        shownSender = [NSString stringWithFormat:@"%@ (%@)",sender,displayName];
   1.304 +                        break;
   1.305 +                        
   1.306 +                    case AIScreenName:
   1.307 +                        shownSender = sender;
   1.308 +                        break;	
   1.309 +                }
   1.310 +            }
   1.311  				
   1.312 -				[service release];
   1.313 -				service = [[attributes objectForKey:@"service"] retain];
   1.314 +            NSString *timestampStr = [dateFormatter stringFromDate:date];
   1.315  				
   1.316 -				[myDisplayName release];
   1.317 -				myDisplayName = nil;
   1.318 +            [output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:
   1.319 +                                                                    @"<div class=\"%@\">%@<span class=\"sender\">%@%@:</span></div> ",
   1.320 +                                                                    (sentMessage ? @"send" : @"receive"),
   1.321 +                                                                    (showTimestamps ? [NSString stringWithFormat:@"<span class=\"timestamp\">%@</span> ", timestampStr] : @""),
   1.322 +                                                                    shownSender, (autoResponse ? AILocalizedString(@" (Autoreply)", nil) : @"")]]];
   1.323  				
   1.324 -				for (AIAccount *account in adium.accountController.accounts) {
   1.325 -					if ([[account.UID compactedString] isEqualToString:[mySN compactedString]] &&
   1.326 -						[account.service.serviceID isEqualToString:service]) {
   1.327 -						myDisplayName = [account.displayName retain];
   1.328 -						break;
   1.329 -					}
   1.330 -				}
   1.331 -
   1.332 -				state = XML_STATE_CHAT;
   1.333 -			}
   1.334 -			break;
   1.335 -		case XML_STATE_CHAT:
   1.336 -			if([name isEqualToString:@"message"])
   1.337 -			{
   1.338 -				[sender release];
   1.339 -				[senderAlias release];
   1.340 -				[date release];
   1.341 -				
   1.342 -				NSString *dateStr = [attributes objectForKey:@"time"];
   1.343 -				if(dateStr != nil)
   1.344 -					date = [[NSCalendarDate calendarDateWithString:dateStr] retain];
   1.345 -				else
   1.346 -					date = nil;
   1.347 -				sender = [[attributes objectForKey:@"sender"] retain];
   1.348 -				senderAlias = [[attributes objectForKey:@"alias"] retain];
   1.349 -				autoResponse = [[attributes objectForKey:@"auto"] isEqualToString:@"true"];
   1.350 -
   1.351 -				//Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
   1.352 -				messageStart = CFXMLParserGetLocation(parser) - 1;
   1.353 -				
   1.354 -				state = XML_STATE_MESSAGE;
   1.355 -			}
   1.356 -			else if([name isEqualToString:@"event"])
   1.357 -			{
   1.358 -				//Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
   1.359 -				messageStart = CFXMLParserGetLocation(parser) - 1;
   1.360 -
   1.361 -				state = XML_STATE_EVENT_MESSAGE;
   1.362 -			}
   1.363 -			else if([name isEqualToString:@"status"])
   1.364 -			{
   1.365 -				[status release];
   1.366 -				[date release];
   1.367 -				
   1.368 -				NSString *dateStr = [attributes objectForKey:@"time"];
   1.369 -				if(dateStr != nil)
   1.370 -					date = [[NSCalendarDate calendarDateWithString:dateStr] retain];
   1.371 -				else
   1.372 -					date = nil;
   1.373 -				
   1.374 -				status = [[attributes objectForKey:@"type"] retain];
   1.375 -
   1.376 -				//Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
   1.377 -				messageStart = CFXMLParserGetLocation(parser) - 1;
   1.378 -
   1.379 -				state = XML_STATE_STATUS_MESSAGE;
   1.380 -			}
   1.381 -			break;
   1.382 -		case XML_STATE_MESSAGE:
   1.383 -		case XML_STATE_EVENT_MESSAGE:
   1.384 -		case XML_STATE_STATUS_MESSAGE:
   1.385 -			break;
   1.386 -	}
   1.387 -}
   1.388 -
   1.389 -- (void)endedElement:(NSString *)name empty:(BOOL)empty
   1.390 -{
   1.391 -	switch(state)
   1.392 -	{
   1.393 -		case XML_STATE_EVENT_MESSAGE:
   1.394 -			state = XML_STATE_CHAT;
   1.395 -			break;
   1.396 -
   1.397 -		case XML_STATE_MESSAGE:
   1.398 -			if([name isEqualToString:@"message"])
   1.399 -			{
   1.400 -				CFIndex end = CFXMLParserGetLocation(parser);
   1.401 -				NSString *message = nil;
   1.402 -				if (!empty) {
   1.403 -					// 11 = 10 for </message> and 1 for the index being off
   1.404 -					message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 11)];
   1.405 -				}
   1.406 -				NSString *shownSender = (senderAlias ? senderAlias : sender);
   1.407 -				NSString *cssClass;
   1.408 -				NSString *displayName = nil, *longDisplayName = nil;
   1.409 -				
   1.410 -				if ([mySN isEqualToString:sender]) {
   1.411 -					//Find an account if one exists, and use its name
   1.412 -					displayName = (myDisplayName ? myDisplayName : sender);
   1.413 -					cssClass = @"send";
   1.414 -				} else {
   1.415 -					AIListObject *listObject = [adium.contactController existingListObjectWithUniqueID:[AIListObject internalObjectIDForServiceID:service UID:sender]];
   1.416 -
   1.417 -					cssClass = @"receive";
   1.418 -					displayName = listObject.displayName;
   1.419 -					longDisplayName = [listObject longDisplayName];
   1.420 -				}
   1.421 -
   1.422 -				if (displayName && ![displayName isEqualToString:sender]) {
   1.423 -					switch (nameFormat) {
   1.424 -						case AIDefaultName:
   1.425 -							shownSender = (longDisplayName ? longDisplayName : displayName);
   1.426 -							break;
   1.427 -
   1.428 -						case AIDisplayName:
   1.429 -							shownSender = displayName;
   1.430 -							break;
   1.431 -
   1.432 -						case AIDisplayName_ScreenName:
   1.433 -							shownSender = [NSString stringWithFormat:@"%@ (%@)",displayName,sender];
   1.434 -							break;
   1.435 -
   1.436 -						case AIScreenName_DisplayName:
   1.437 -							shownSender = [NSString stringWithFormat:@"%@ (%@)",sender,displayName];
   1.438 -							break;
   1.439 -
   1.440 -						case AIScreenName:
   1.441 -							shownSender = sender;
   1.442 -							break;	
   1.443 -					}
   1.444 -				}
   1.445 -				
   1.446 -				NSString *timestampStr = [dateFormatter stringFromDate:date];
   1.447 -				
   1.448 -				BOOL sentMessage = [mySN isEqualToString:sender];
   1.449 -				[output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:
   1.450 -										 @"<div class=\"%@\">%@<span class=\"sender\">%@%@:</span></div> ",
   1.451 -										 (sentMessage ? @"send" : @"receive"),
   1.452 -										 (showTimestamps ? [NSString stringWithFormat:@"<span class=\"timestamp\">%@</span> ", timestampStr] : @""),
   1.453 -										 shownSender, (autoResponse ? AILocalizedString(@" (Autoreply)", nil) : @"")]]];
   1.454 -				
   1.455 -				NSAttributedString *attributedMessage = [htmlDecoder decodeHTML:message];
   1.456 -				if (showEmoticons) {
   1.457 -					attributedMessage = [adium.contentController filterAttributedString:attributedMessage
   1.458 -																		  usingFilterType:AIFilterMessageDisplay
   1.459 -																				direction:(sentMessage ? AIFilterOutgoing : AIFilterIncoming)
   1.460 -																				  context:nil];				
   1.461 -				}
   1.462 -				[output appendAttributedString:attributedMessage];
   1.463 -				[output appendAttributedString:newlineAttributedString];
   1.464 -
   1.465 -				state = XML_STATE_CHAT;
   1.466 -			}
   1.467 -			break;
   1.468 -		case XML_STATE_STATUS_MESSAGE:
   1.469 -			if([name isEqualToString:@"status"])
   1.470 -			{
   1.471 -				CFIndex end = CFXMLParserGetLocation(parser);
   1.472 -				NSString *message = nil;
   1.473 -				if(!empty)
   1.474 -					message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 10)];  // 9 for </status> and 1 for the index being off
   1.475 -								
   1.476 -				NSString *displayMessage = nil;
   1.477 -				//Note: I am diverging from what the AILoggerPlugin logs in this case.  It can't handle every case we can have here
   1.478 -				if([message length])
   1.479 -				{
   1.480 -					if([statusLookup objectForKey:status])
   1.481 -						displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@: %@", nil), [statusLookup objectForKey:status], message];
   1.482 -					else
   1.483 -						displayMessage = [NSString stringWithFormat:AILocalizedString(@"%@", nil), message];
   1.484 -				}
   1.485 -				else if([status length] && [statusLookup objectForKey:status])
   1.486 -					displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@", nil), [statusLookup objectForKey:status]];
   1.487 -
   1.488 -				if([displayMessage length])
   1.489 -					[output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:@"<div class=\"status\">%@ (%@)</div>\n",
   1.490 -																			displayMessage,
   1.491 -																			[dateFormatter stringFromDate:date]]]];
   1.492 -					state = XML_STATE_CHAT;
   1.493 -			}			
   1.494 -		case XML_STATE_CHAT:
   1.495 -			if([name isEqualToString:@"chat"])
   1.496 -				state = XML_STATE_NONE;
   1.497 -			break;
   1.498 -		case XML_STATE_NONE:
   1.499 -			break;
   1.500 -	}
   1.501 -}
   1.502 -
   1.503 -typedef struct{
   1.504 -	NSString	*name;
   1.505 -	BOOL		empty;
   1.506 -} element;
   1.507 -
   1.508 -void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context)
   1.509 -{
   1.510 -	element *ret = NULL;
   1.511 -	
   1.512 -    // Use the dataTypeID to determine what to print.
   1.513 -    switch (CFXMLNodeGetTypeCode(node)) {
   1.514 -        case kCFXMLNodeTypeDocument:
   1.515 -            break;
   1.516 -        case kCFXMLNodeTypeElement:
   1.517 -		{
   1.518 -			NSString *name = [NSString stringWithString:(NSString *)CFXMLNodeGetString(node)];
   1.519 -			const CFXMLElementInfo *info = CFXMLNodeGetInfoPtr(node);
   1.520 -			[(AIXMLChatlogConverter *)context startedElement:name info:info];
   1.521 -			ret = (element *)malloc(sizeof(element));
   1.522 -			ret->name = [name retain];
   1.523 -			ret->empty = info->isEmpty;
   1.524 -			break;
   1.525 -		}
   1.526 -        case kCFXMLNodeTypeProcessingInstruction:
   1.527 -        case kCFXMLNodeTypeComment:
   1.528 -        case kCFXMLNodeTypeText:
   1.529 -        case kCFXMLNodeTypeCDATASection:
   1.530 -        case kCFXMLNodeTypeEntityReference:
   1.531 -        case kCFXMLNodeTypeDocumentType:
   1.532 -        case kCFXMLNodeTypeWhitespace:
   1.533 -        default:
   1.534 -			break;
   1.535 -	}
   1.536 -	
   1.537 -    // Return the data string for use by the addChild and 
   1.538 -    // endStructure callbacks.
   1.539 -    return (void *) ret;
   1.540 -}
   1.541 -
   1.542 -void addChild(CFXMLParserRef parser, void *parent, void *child, void *context)
   1.543 -{
   1.544 -}
   1.545 -
   1.546 -void endStructure(CFXMLParserRef parser, void *xmlType, void *context)
   1.547 -{
   1.548 -	NSString *name = nil;
   1.549 -	BOOL empty = NO;
   1.550 -	if(xmlType != NULL)
   1.551 -	{
   1.552 -		name = [NSString stringWithString:((element *)xmlType)->name];
   1.553 -		empty = ((element *)xmlType)->empty;
   1.554 -	}
   1.555 -	[(AIXMLChatlogConverter *)context endedElement:name empty:empty];
   1.556 -	if(xmlType != NULL)
   1.557 -	{
   1.558 -		[((element *)xmlType)->name release];
   1.559 -		free(xmlType);
   1.560 -	}
   1.561 +            NSAttributedString *attributedMessage = [htmlDecoder decodeHTML:messageXML];
   1.562 +            if (showEmoticons) {
   1.563 +                attributedMessage = [adium.contentController filterAttributedString:attributedMessage
   1.564 +                                                                    usingFilterType:AIFilterMessageDisplay
   1.565 +                                                                          direction:(sentMessage ? AIFilterOutgoing : AIFilterIncoming)
   1.566 +                                                                            context:nil];				
   1.567 +            }
   1.568 +            [output appendAttributedString:attributedMessage];
   1.569 +            [output appendAttributedString:newlineAttributedString];
   1.570 +        } else if ([type isEqualToString:@"status"]) {
   1.571 +            NSString *dateStr = [[attributes objectForKey:@"time"] stringValue];
   1.572 +            NSDate *date = dateStr ? [NSCalendarDate calendarDateWithString:dateStr] : nil;
   1.573 +            NSString *status = [[attributes objectForKey:@"type"] stringValue];
   1.574 +            
   1.575 +            NSMutableString *messageXML = [NSMutableString string];
   1.576 +            for (NSXMLNode *node in [element children]) {
   1.577 +                [messageXML appendString:[node XMLString]];
   1.578 +            }            
   1.579 +            
   1.580 +            NSString *displayMessage = nil;
   1.581 +            //Note: I am diverging from what the AILoggerPlugin logs in this case.  It can't handle every case we can have here
   1.582 +            if([messageXML length]) {
   1.583 +                if([statusLookup objectForKey:status]) {
   1.584 +                    displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@: %@", nil), [statusLookup objectForKey:status], messageXML];
   1.585 +                } else {
   1.586 +                    displayMessage = [NSString stringWithFormat:AILocalizedString(@"%@", nil), messageXML];
   1.587 +                }
   1.588 +            } else if([status length] && [statusLookup objectForKey:status]) {
   1.589 +                displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@", nil), [statusLookup objectForKey:status]];
   1.590 +            }
   1.591 +            
   1.592 +            if([displayMessage length]) {
   1.593 +                [output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:@"<div class=\"status\">%@ (%@)</div>\n",
   1.594 +                                                                        displayMessage,
   1.595 +                                                                        [dateFormatter stringFromDate:date]]]];
   1.596 +            }
   1.597 +        }
   1.598 +    }
   1.599 +    
   1.600 +    return output;
   1.601 +    
   1.602 +ohno:
   1.603 +    if (!reentrancyFlag) {
   1.604 +        NSMutableString *xmlString = [NSMutableString stringWithUTF8String:[xmlData bytes]];
   1.605 +        [xmlString stripInvalidCharacters];
   1.606 +        return [self readData:[xmlString dataUsingEncoding:NSUTF8StringEncoding] withOptions:options retrying:YES];
   1.607 +    }
   1.608 +    @throw [NSException exceptionWithName:@"Log File Parsing Error" reason:[err description] userInfo:nil];
   1.609  }
   1.610  
   1.611  @end