Move us off of deprecated CFXMLParser and onto NSXMLDocument for parsing chat transcripts.
authorColin Barrett <colin@springsandstruts.com>
Mon Aug 16 23:07:04 2010 -0700 (17 months ago)
changeset 32714dc4e5bceaa9
parent 3270 d5502d6c952a
child 3272 25957834b74c
Move us off of deprecated CFXMLParser and onto NSXMLDocument for parsing chat transcripts.
Source/AIXMLChatlogConverter.h
Source/AIXMLChatlogConverter.m
     1.1 --- a/Source/AIXMLChatlogConverter.h	Mon Aug 16 04:41:00 2010 +0200
     1.2 +++ b/Source/AIXMLChatlogConverter.h	Mon Aug 16 23:07:04 2010 -0700
     1.3 @@ -14,15 +14,6 @@
     1.4   * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     1.5   */
     1.6  
     1.7 -
     1.8 -typedef enum{
     1.9 -	XML_STATE_NONE,
    1.10 -	XML_STATE_CHAT,
    1.11 -	XML_STATE_MESSAGE,
    1.12 -	XML_STATE_EVENT_MESSAGE,
    1.13 -	XML_STATE_STATUS_MESSAGE
    1.14 -} chatLogState;
    1.15 -
    1.16  /*!
    1.17   *	@brief Different ways of formatting display names
    1.18   */
    1.19 @@ -37,34 +28,12 @@
    1.20  @class AIHTMLDecoder;
    1.21  
    1.22  @interface AIXMLChatlogConverter : NSObject {
    1.23 -	CFXMLParserRef	parser;
    1.24 -	NSString		*inputFileString;
    1.25 -	NSDictionary	*eventTranslate;
    1.26 -	
    1.27 -	NSDateFormatter *dateFormatter;
    1.28 -	
    1.29 -	chatLogState	state;
    1.30 -	NSString		*sender;
    1.31 -	NSString		*senderAlias;
    1.32 -	NSString		*mySN;
    1.33 -	NSString		*service;
    1.34 -	NSString		*myDisplayName;
    1.35 -	NSCalendarDate	*date;
    1.36 -	NSInteger				messageStart;
    1.37 -	BOOL			autoResponse;
    1.38 -	BOOL			showTimestamps;
    1.39 -	BOOL			showEmoticons;
    1.40 -	NSString		*status;
    1.41 -	
    1.42 -	NSMutableAttributedString *output;
    1.43 -	NSAttributedString *newlineAttributedString;
    1.44 +	NSDateFormatter *dateFormatter;	
    1.45  	NSDictionary	*statusLookup;
    1.46 +    NSAttributedString *newlineAttributedString;
    1.47  	AIHTMLDecoder	*htmlDecoder;
    1.48 -
    1.49 -	AINameFormat	nameFormat;
    1.50  }
    1.51  
    1.52  + (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options;
    1.53 -- (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options;
    1.54  
    1.55  @end
     2.1 --- a/Source/AIXMLChatlogConverter.m	Mon Aug 16 04:41:00 2010 +0200
     2.2 +++ b/Source/AIXMLChatlogConverter.m	Mon Aug 16 23:07:04 2010 -0700
     2.3 @@ -31,37 +31,98 @@
     2.4  #define KEY_WEBKIT_USE_NAME_FORMAT				@"Use Custom Name Format"
     2.5  #define KEY_WEBKIT_NAME_FORMAT					@"Name Format"
     2.6  
     2.7 -static void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context);
     2.8 -static void addChild(CFXMLParserRef parser, void *parent, void *child, void *context);
     2.9 -static void endStructure(CFXMLParserRef parser, void *xmlType, void *context);
    2.10 +@interface NSMutableString (XMLMethods)
    2.11 +- (void)stripInvalidCharacters;
    2.12 +@end
    2.13 +
    2.14 +@implementation NSMutableString (XMLMethods)
    2.15 +
    2.16 +//Strip invalid XML characters
    2.17 +- (void)stripInvalidCharacters
    2.18 +{
    2.19 +    static NSCharacterSet *invalidXMLCharacterSet;
    2.20 +    
    2.21 +    if (invalidXMLCharacterSet == nil)
    2.22 +    {
    2.23 +        // First, create a character set containing all valid UTF8 characters.
    2.24 +        NSMutableCharacterSet *xmlCharacterSet = [[NSMutableCharacterSet alloc] init];        
    2.25 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0x9, 1)];       
    2.26 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0xA, 1)];        
    2.27 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0xD, 1)];        
    2.28 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0x20, 0xD7FF - 0x20)];
    2.29 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0xE000, 0xFFFD - 0xE000)];        
    2.30 +        [xmlCharacterSet addCharactersInRange:NSMakeRange(0x10000, 0x10FFFF - 0x10000)];        
    2.31 +        // Then create and retain an inverted set, which will thus contain all invalid XML characters.        
    2.32 +        invalidXMLCharacterSet = [[xmlCharacterSet invertedSet] retain];        
    2.33 +        [xmlCharacterSet release];        
    2.34 +    }
    2.35 +    
    2.36 +    // Are there any invalid characters in this string?    
    2.37 +    NSRange range = [self rangeOfCharacterFromSet:invalidXMLCharacterSet];
    2.38 +    
    2.39 +    // Otherwise go through and remove any illegal XML characters from a copy of the string.
    2.40 +    while (range.length > 0)
    2.41 +    {        
    2.42 +        [self deleteCharactersInRange:range];
    2.43 +        range = [self rangeOfCharacterFromSet:invalidXMLCharacterSet                 
    2.44 +                                      options:0                 
    2.45 +                                        range:NSMakeRange(range.location,[self length]-range.location)];        
    2.46 +    }    
    2.47 +}
    2.48 +
    2.49 +@end
    2.50 +
    2.51 +@interface NSXMLElement (AIAttributeDict)
    2.52 +- (NSDictionary *)AIAttributesAsDictionary;
    2.53 +@end
    2.54 +
    2.55 +@implementation NSXMLElement (AIAttributeDict)
    2.56 +
    2.57 +- (NSDictionary *)AIAttributesAsDictionary 
    2.58 +{
    2.59 +    NSArray *attrArray = [self attributes];
    2.60 +    NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
    2.61 +    for (NSXMLNode *attr in attrArray) {
    2.62 +        [attributes setObject:attr forKey:[attr name]];
    2.63 +    }
    2.64 +    return attributes;
    2.65 +}
    2.66 +
    2.67 +@end
    2.68 +
    2.69 +@interface AIXMLChatlogConverter()
    2.70 +- (NSAttributedString *)readData:(NSData *)xmlData withOptions:(NSDictionary *)options retrying:(BOOL)reentrancyFlag;
    2.71 +@end
    2.72  
    2.73  @implementation AIXMLChatlogConverter
    2.74  
    2.75  + (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options
    2.76  {
    2.77 -	AIXMLChatlogConverter *converter = [[AIXMLChatlogConverter alloc] init];
    2.78 -	NSAttributedString *ret = [[converter readFile:filePath withOptions:options] retain];
    2.79 -	[converter release];
    2.80 -	return [ret autorelease];
    2.81 +	static AIXMLChatlogConverter *converter;
    2.82 +    if (!converter) {
    2.83 +        converter = [[AIXMLChatlogConverter alloc] init];
    2.84 +	}
    2.85 +    NSData *xmlData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
    2.86 +    [converter->htmlDecoder setBaseURL:[filePath stringByDeletingLastPathComponent]];
    2.87 +    NSAttributedString *result = nil;
    2.88 +    @try {
    2.89 +        result = [converter readData:xmlData withOptions:options retrying:NO];
    2.90 +    } @catch (NSException *e) {
    2.91 +        NSLog(@"Error \"%@\" parsing log file at %@.", e, filePath);
    2.92 +        return [[[NSAttributedString alloc] initWithString:@"Sorry, there was an error parsing this transcript. It may be corrupt."] autorelease];
    2.93 +    }
    2.94 +    return result;
    2.95  }
    2.96  
    2.97  - (id)init
    2.98  {
    2.99  	if ((self = [super init])) {
   2.100 -	
   2.101 -		state = XML_STATE_NONE;
   2.102 -		
   2.103 -		inputFileString = nil;
   2.104 -		sender = nil;
   2.105 -		mySN = nil;
   2.106 -		myDisplayName = nil;
   2.107 -		date = nil;
   2.108 -		parser = NULL;
   2.109 -		status = nil;
   2.110 -		
   2.111 +        if (!newlineAttributedString) {
   2.112 +            newlineAttributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:nil];
   2.113 +        }
   2.114 +        
   2.115 +        htmlDecoder = [[AIHTMLDecoder alloc] init];
   2.116  		dateFormatter = [[NSDateFormatter localizedDateFormatterShowingSeconds:YES showingAMorPM:YES] retain];
   2.117 -		
   2.118 -		newlineAttributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:nil];
   2.119  
   2.120  		statusLookup = [[NSDictionary alloc] initWithObjectsAndKeys:
   2.121  			AILocalizedString(@"Online", nil), @"online",
   2.122 @@ -80,15 +141,7 @@
   2.123  			[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_DESK], @"notAtMyDesk",
   2.124  			[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_IN_OFFICE], @"notInTheOffice",
   2.125  			[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_STEPPED_OUT], @"steppedOut",
   2.126 -			nil];
   2.127 -			
   2.128 -		if ([[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_NAME_FORMAT
   2.129 -													  group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] boolValue]) {
   2.130 -			nameFormat = [[adium.preferenceController preferenceForKey:KEY_WEBKIT_NAME_FORMAT
   2.131 -																   group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] intValue];
   2.132 -		} else {
   2.133 -			nameFormat = AIDefaultName;
   2.134 -		}
   2.135 +			nil];			
   2.136  	}
   2.137  
   2.138  	return self;
   2.139 @@ -97,317 +150,173 @@
   2.140  - (void)dealloc
   2.141  {
   2.142  	[dateFormatter release];
   2.143 -	[newlineAttributedString release];
   2.144 -	[inputFileString release];
   2.145 -	[eventTranslate release];
   2.146 -	[sender release];
   2.147 -	[senderAlias release];
   2.148 -	[mySN release];
   2.149 -	[myDisplayName release];
   2.150 -	[service release];
   2.151 -	[date release];
   2.152 -	[status release];
   2.153 -	[output release];
   2.154  	[statusLookup release];
   2.155  	[htmlDecoder release];
   2.156  	[super dealloc];
   2.157  }
   2.158  
   2.159 -- (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options
   2.160 +- (NSAttributedString *)readData:(NSData *)xmlData withOptions:(NSDictionary *)options retrying:(BOOL)reentrancyFlag
   2.161  {
   2.162 -	NSData *inputData = [NSData dataWithContentsOfFile:filePath]; 
   2.163 -	inputFileString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding]; 
   2.164 -	NSURL *url = [[NSURL alloc] initFileURLWithPath:filePath];
   2.165 -	output = [[NSMutableAttributedString alloc] init];
   2.166 +    if (!xmlData) {
   2.167 +        return [[[NSAttributedString alloc] initWithString:@""] autorelease];
   2.168 +    }
   2.169 +    AINameFormat nameFormat;
   2.170 +    if ([[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_NAME_FORMAT
   2.171 +                                                group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] boolValue]) {
   2.172 +        nameFormat = [[adium.preferenceController preferenceForKey:KEY_WEBKIT_NAME_FORMAT
   2.173 +                                                             group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] intValue];
   2.174 +    } else {
   2.175 +        nameFormat = AIDefaultName;
   2.176 +    }
   2.177 +    NSMutableAttributedString *output = [[[NSMutableAttributedString alloc] init] autorelease];
   2.178 +    
   2.179 +    NSError *err=nil;
   2.180 +	NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:xmlData
   2.181 +                                                         options:NSXMLNodePreserveCDATA
   2.182 +                                                           error:&err] autorelease];    
   2.183  	
   2.184 -	htmlDecoder = [[AIHTMLDecoder alloc] init];
   2.185 -	[htmlDecoder setBaseURL:[filePath stringByDeletingLastPathComponent]];
   2.186 -	
   2.187 -	showTimestamps = [[options objectForKey:@"showTimestamps"] boolValue];
   2.188 -	showEmoticons = [[options objectForKey:@"showEmoticons"] boolValue];
   2.189 +	if (!xmlDoc)
   2.190 +	{    
   2.191 +        goto ohno;
   2.192 +    }
   2.193 +    
   2.194 +    BOOL showTimestamps = [[options objectForKey:@"showTimestamps"] boolValue];
   2.195 +	BOOL showEmoticons = [[options objectForKey:@"showEmoticons"] boolValue];
   2.196 +    
   2.197 +    NSXMLElement *chatElement = [[xmlDoc nodesForXPath:@"//chat" error:&err] lastObject];
   2.198 +    
   2.199 +    NSDictionary *chatAttributes = [chatElement AIAttributesAsDictionary];
   2.200 +    NSString *mySN = [[chatAttributes objectForKey:@"account"] stringValue];
   2.201 +    NSString *service = [[chatAttributes objectForKey:@"service"] stringValue];
   2.202 +    
   2.203 +    NSString *myDisplayName = nil;
   2.204 +    
   2.205 +    for (AIAccount *account in adium.accountController.accounts) {
   2.206 +        if ([[account.UID compactedString] isEqualToString:[mySN compactedString]] &&
   2.207 +            [account.service.serviceID isEqualToString:service]) {
   2.208 +            myDisplayName = [account.displayName retain];
   2.209 +            break;
   2.210 +        }
   2.211 +    }    
   2.212 +        
   2.213 +    NSArray *elements = [xmlDoc nodesForXPath:@"//message | //status" error:&err];
   2.214 +    if (!elements) {
   2.215 +        goto ohno;
   2.216 +    }
   2.217 +    
   2.218 +    for (NSXMLElement *element in elements) {
   2.219 +        NSString *type = [element name];
   2.220 +     
   2.221 +        NSDictionary *attributes = [element AIAttributesAsDictionary];
   2.222 +        
   2.223 +        if ([type isEqualToString:@"message"]) {
   2.224 +            NSString *senderAlias = [[attributes objectForKey:@"alias"] stringValue];
   2.225 +            NSString *dateStr = [[attributes objectForKey:@"time"] stringValue];
   2.226 +            NSDate *date = dateStr ? [NSCalendarDate calendarDateWithString:dateStr] : nil;
   2.227 +            NSString *sender = [[attributes objectForKey:@"sender"] stringValue];
   2.228 +            NSString *shownSender = (senderAlias ? senderAlias : sender);
   2.229 +            BOOL autoResponse = [[[attributes objectForKey:@"auto"] stringValue] isEqualToString:@"true"];
   2.230  
   2.231 -	CFXMLParserCallBacks callbacks = {
   2.232 -		0,
   2.233 -		createStructure,
   2.234 -		addChild,
   2.235 -		endStructure,
   2.236 -		NULL,
   2.237 -		NULL
   2.238 -	};
   2.239 -	CFXMLParserContext context = {
   2.240 -		0,
   2.241 -		self,
   2.242 -		CFRetain,
   2.243 -		CFRelease,
   2.244 -		NULL
   2.245 -	};
   2.246 -	parser = CFXMLParserCreate(NULL, (CFDataRef)inputData, NULL, kCFXMLParserSkipMetaData | kCFXMLParserSkipWhitespace, kCFXMLNodeCurrentVersion, &callbacks, &context);
   2.247 -	if (!CFXMLParserParse(parser)) {
   2.248 -		NSLog(@"%@: Parser %@ for inputFileString %@ returned false.",
   2.249 -			  [self class], parser, inputFileString);
   2.250 -		[output release];
   2.251 -		output = nil;
   2.252 -	}
   2.253 -	CFRelease(parser);
   2.254 -	parser = nil;
   2.255 -	[url release];
   2.256 -	return output;
   2.257 -}
   2.258 +            NSMutableString *messageXML = [NSMutableString string];
   2.259 +            for (NSXMLNode *node in [element children]) {
   2.260 +                [messageXML appendString:[node XMLString]];
   2.261 +            }
   2.262 +    
   2.263 +            NSString *displayName = nil, *longDisplayName = nil;
   2.264 +            
   2.265 +            BOOL sentMessage = [mySN isEqualToString:sender];
   2.266  
   2.267 -- (void)startedElement:(NSString *)name info:(const CFXMLElementInfo *)info
   2.268 -{
   2.269 -	NSDictionary *attributes = (NSDictionary *)info->attributes;
   2.270 -	
   2.271 -	switch(state){
   2.272 -		case XML_STATE_NONE:
   2.273 -			if([name isEqualToString:@"chat"])
   2.274 -			{
   2.275 -				[mySN release];
   2.276 -				mySN = [[attributes objectForKey:@"account"] retain];
   2.277 +            
   2.278 +            if (sentMessage) {
   2.279 +                //Find an account if one exists, and use its name
   2.280 +                displayName = (myDisplayName ? myDisplayName : sender);
   2.281 +            } else {
   2.282 +                AIListObject *listObject = [adium.contactController existingListObjectWithUniqueID:[AIListObject internalObjectIDForServiceID:service UID:sender]];
   2.283 +                
   2.284 +                displayName = listObject.displayName;
   2.285 +                longDisplayName = [listObject longDisplayName];
   2.286 +            }
   2.287 +                
   2.288 +            if (displayName && !sentMessage) {
   2.289 +                switch (nameFormat) {
   2.290 +                    case AIDefaultName:
   2.291 +                        shownSender = (longDisplayName ? longDisplayName : displayName);
   2.292 +                        break;
   2.293 +                        
   2.294 +                    case AIDisplayName:
   2.295 +                        shownSender = displayName;
   2.296 +                        break;
   2.297 +                        
   2.298 +                    case AIDisplayName_ScreenName:
   2.299 +                        shownSender = [NSString stringWithFormat:@"%@ (%@)",displayName,sender];
   2.300 +                        break;
   2.301 +                        
   2.302 +                    case AIScreenName_DisplayName:
   2.303 +                        shownSender = [NSString stringWithFormat:@"%@ (%@)",sender,displayName];
   2.304 +                        break;
   2.305 +                        
   2.306 +                    case AIScreenName:
   2.307 +                        shownSender = sender;
   2.308 +                        break;	
   2.309 +                }
   2.310 +            }
   2.311  				
   2.312 -				[service release];
   2.313 -				service = [[attributes objectForKey:@"service"] retain];
   2.314 +            NSString *timestampStr = [dateFormatter stringFromDate:date];
   2.315  				
   2.316 -				[myDisplayName release];
   2.317 -				myDisplayName = nil;
   2.318 +            [output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:
   2.319 +                                                                    @"<div class=\"%@\">%@<span class=\"sender\">%@%@:</span></div> ",
   2.320 +                                                                    (sentMessage ? @"send" : @"receive"),
   2.321 +                                                                    (showTimestamps ? [NSString stringWithFormat:@"<span class=\"timestamp\">%@</span> ", timestampStr] : @""),
   2.322 +                                                                    shownSender, (autoResponse ? AILocalizedString(@" (Autoreply)", nil) : @"")]]];
   2.323  				
   2.324 -				for (AIAccount *account in adium.accountController.accounts) {
   2.325 -					if ([[account.UID compactedString] isEqualToString:[mySN compactedString]] &&
   2.326 -						[account.service.serviceID isEqualToString:service]) {
   2.327 -						myDisplayName = [account.displayName retain];
   2.328 -						break;
   2.329 -					}
   2.330 -				}
   2.331 -
   2.332 -				state = XML_STATE_CHAT;
   2.333 -			}
   2.334 -			break;
   2.335 -		case XML_STATE_CHAT:
   2.336 -			if([name isEqualToString:@"message"])
   2.337 -			{
   2.338 -				[sender release];
   2.339 -				[senderAlias release];
   2.340 -				[date release];
   2.341 -				
   2.342 -				NSString *dateStr = [attributes objectForKey:@"time"];
   2.343 -				if(dateStr != nil)
   2.344 -					date = [[NSCalendarDate calendarDateWithString:dateStr] retain];
   2.345 -				else
   2.346 -					date = nil;
   2.347 -				sender = [[attributes objectForKey:@"sender"] retain];
   2.348 -				senderAlias = [[attributes objectForKey:@"alias"] retain];
   2.349 -				autoResponse = [[attributes objectForKey:@"auto"] isEqualToString:@"true"];
   2.350 -
   2.351 -				//Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
   2.352 -				messageStart = CFXMLParserGetLocation(parser) - 1;
   2.353 -				
   2.354 -				state = XML_STATE_MESSAGE;
   2.355 -			}
   2.356 -			else if([name isEqualToString:@"event"])
   2.357 -			{
   2.358 -				//Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
   2.359 -				messageStart = CFXMLParserGetLocation(parser) - 1;
   2.360 -
   2.361 -				state = XML_STATE_EVENT_MESSAGE;
   2.362 -			}
   2.363 -			else if([name isEqualToString:@"status"])
   2.364 -			{
   2.365 -				[status release];
   2.366 -				[date release];
   2.367 -				
   2.368 -				NSString *dateStr = [attributes objectForKey:@"time"];
   2.369 -				if(dateStr != nil)
   2.370 -					date = [[NSCalendarDate calendarDateWithString:dateStr] retain];
   2.371 -				else
   2.372 -					date = nil;
   2.373 -				
   2.374 -				status = [[attributes objectForKey:@"type"] retain];
   2.375 -
   2.376 -				//Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
   2.377 -				messageStart = CFXMLParserGetLocation(parser) - 1;
   2.378 -
   2.379 -				state = XML_STATE_STATUS_MESSAGE;
   2.380 -			}
   2.381 -			break;
   2.382 -		case XML_STATE_MESSAGE:
   2.383 -		case XML_STATE_EVENT_MESSAGE:
   2.384 -		case XML_STATE_STATUS_MESSAGE:
   2.385 -			break;
   2.386 -	}
   2.387 -}
   2.388 -
   2.389 -- (void)endedElement:(NSString *)name empty:(BOOL)empty
   2.390 -{
   2.391 -	switch(state)
   2.392 -	{
   2.393 -		case XML_STATE_EVENT_MESSAGE:
   2.394 -			state = XML_STATE_CHAT;
   2.395 -			break;
   2.396 -
   2.397 -		case XML_STATE_MESSAGE:
   2.398 -			if([name isEqualToString:@"message"])
   2.399 -			{
   2.400 -				CFIndex end = CFXMLParserGetLocation(parser);
   2.401 -				NSString *message = nil;
   2.402 -				if (!empty) {
   2.403 -					// 11 = 10 for </message> and 1 for the index being off
   2.404 -					message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 11)];
   2.405 -				}
   2.406 -				NSString *shownSender = (senderAlias ? senderAlias : sender);
   2.407 -				NSString *cssClass;
   2.408 -				NSString *displayName = nil, *longDisplayName = nil;
   2.409 -				
   2.410 -				if ([mySN isEqualToString:sender]) {
   2.411 -					//Find an account if one exists, and use its name
   2.412 -					displayName = (myDisplayName ? myDisplayName : sender);
   2.413 -					cssClass = @"send";
   2.414 -				} else {
   2.415 -					AIListObject *listObject = [adium.contactController existingListObjectWithUniqueID:[AIListObject internalObjectIDForServiceID:service UID:sender]];
   2.416 -
   2.417 -					cssClass = @"receive";
   2.418 -					displayName = listObject.displayName;
   2.419 -					longDisplayName = [listObject longDisplayName];
   2.420 -				}
   2.421 -
   2.422 -				if (displayName && ![displayName isEqualToString:sender]) {
   2.423 -					switch (nameFormat) {
   2.424 -						case AIDefaultName:
   2.425 -							shownSender = (longDisplayName ? longDisplayName : displayName);
   2.426 -							break;
   2.427 -
   2.428 -						case AIDisplayName:
   2.429 -							shownSender = displayName;
   2.430 -							break;
   2.431 -
   2.432 -						case AIDisplayName_ScreenName:
   2.433 -							shownSender = [NSString stringWithFormat:@"%@ (%@)",displayName,sender];
   2.434 -							break;
   2.435 -
   2.436 -						case AIScreenName_DisplayName:
   2.437 -							shownSender = [NSString stringWithFormat:@"%@ (%@)",sender,displayName];
   2.438 -							break;
   2.439 -
   2.440 -						case AIScreenName:
   2.441 -							shownSender = sender;
   2.442 -							break;	
   2.443 -					}
   2.444 -				}
   2.445 -				
   2.446 -				NSString *timestampStr = [dateFormatter stringFromDate:date];
   2.447 -				
   2.448 -				BOOL sentMessage = [mySN isEqualToString:sender];
   2.449 -				[output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:
   2.450 -										 @"<div class=\"%@\">%@<span class=\"sender\">%@%@:</span></div> ",
   2.451 -										 (sentMessage ? @"send" : @"receive"),
   2.452 -										 (showTimestamps ? [NSString stringWithFormat:@"<span class=\"timestamp\">%@</span> ", timestampStr] : @""),
   2.453 -										 shownSender, (autoResponse ? AILocalizedString(@" (Autoreply)", nil) : @"")]]];
   2.454 -				
   2.455 -				NSAttributedString *attributedMessage = [htmlDecoder decodeHTML:message];
   2.456 -				if (showEmoticons) {
   2.457 -					attributedMessage = [adium.contentController filterAttributedString:attributedMessage
   2.458 -																		  usingFilterType:AIFilterMessageDisplay
   2.459 -																				direction:(sentMessage ? AIFilterOutgoing : AIFilterIncoming)
   2.460 -																				  context:nil];				
   2.461 -				}
   2.462 -				[output appendAttributedString:attributedMessage];
   2.463 -				[output appendAttributedString:newlineAttributedString];
   2.464 -
   2.465 -				state = XML_STATE_CHAT;
   2.466 -			}
   2.467 -			break;
   2.468 -		case XML_STATE_STATUS_MESSAGE:
   2.469 -			if([name isEqualToString:@"status"])
   2.470 -			{
   2.471 -				CFIndex end = CFXMLParserGetLocation(parser);
   2.472 -				NSString *message = nil;
   2.473 -				if(!empty)
   2.474 -					message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 10)];  // 9 for </status> and 1 for the index being off
   2.475 -								
   2.476 -				NSString *displayMessage = nil;
   2.477 -				//Note: I am diverging from what the AILoggerPlugin logs in this case.  It can't handle every case we can have here
   2.478 -				if([message length])
   2.479 -				{
   2.480 -					if([statusLookup objectForKey:status])
   2.481 -						displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@: %@", nil), [statusLookup objectForKey:status], message];
   2.482 -					else
   2.483 -						displayMessage = [NSString stringWithFormat:AILocalizedString(@"%@", nil), message];
   2.484 -				}
   2.485 -				else if([status length] && [statusLookup objectForKey:status])
   2.486 -					displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@", nil), [statusLookup objectForKey:status]];
   2.487 -
   2.488 -				if([displayMessage length])
   2.489 -					[output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:@"<div class=\"status\">%@ (%@)</div>\n",
   2.490 -																			displayMessage,
   2.491 -																			[dateFormatter stringFromDate:date]]]];
   2.492 -					state = XML_STATE_CHAT;
   2.493 -			}			
   2.494 -		case XML_STATE_CHAT:
   2.495 -			if([name isEqualToString:@"chat"])
   2.496 -				state = XML_STATE_NONE;
   2.497 -			break;
   2.498 -		case XML_STATE_NONE:
   2.499 -			break;
   2.500 -	}
   2.501 -}
   2.502 -
   2.503 -typedef struct{
   2.504 -	NSString	*name;
   2.505 -	BOOL		empty;
   2.506 -} element;
   2.507 -
   2.508 -void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context)
   2.509 -{
   2.510 -	element *ret = NULL;
   2.511 -	
   2.512 -    // Use the dataTypeID to determine what to print.
   2.513 -    switch (CFXMLNodeGetTypeCode(node)) {
   2.514 -        case kCFXMLNodeTypeDocument:
   2.515 -            break;
   2.516 -        case kCFXMLNodeTypeElement:
   2.517 -		{
   2.518 -			NSString *name = [NSString stringWithString:(NSString *)CFXMLNodeGetString(node)];
   2.519 -			const CFXMLElementInfo *info = CFXMLNodeGetInfoPtr(node);
   2.520 -			[(AIXMLChatlogConverter *)context startedElement:name info:info];
   2.521 -			ret = (element *)malloc(sizeof(element));
   2.522 -			ret->name = [name retain];
   2.523 -			ret->empty = info->isEmpty;
   2.524 -			break;
   2.525 -		}
   2.526 -        case kCFXMLNodeTypeProcessingInstruction:
   2.527 -        case kCFXMLNodeTypeComment:
   2.528 -        case kCFXMLNodeTypeText:
   2.529 -        case kCFXMLNodeTypeCDATASection:
   2.530 -        case kCFXMLNodeTypeEntityReference:
   2.531 -        case kCFXMLNodeTypeDocumentType:
   2.532 -        case kCFXMLNodeTypeWhitespace:
   2.533 -        default:
   2.534 -			break;
   2.535 -	}
   2.536 -	
   2.537 -    // Return the data string for use by the addChild and 
   2.538 -    // endStructure callbacks.
   2.539 -    return (void *) ret;
   2.540 -}
   2.541 -
   2.542 -void addChild(CFXMLParserRef parser, void *parent, void *child, void *context)
   2.543 -{
   2.544 -}
   2.545 -
   2.546 -void endStructure(CFXMLParserRef parser, void *xmlType, void *context)
   2.547 -{
   2.548 -	NSString *name = nil;
   2.549 -	BOOL empty = NO;
   2.550 -	if(xmlType != NULL)
   2.551 -	{
   2.552 -		name = [NSString stringWithString:((element *)xmlType)->name];
   2.553 -		empty = ((element *)xmlType)->empty;
   2.554 -	}
   2.555 -	[(AIXMLChatlogConverter *)context endedElement:name empty:empty];
   2.556 -	if(xmlType != NULL)
   2.557 -	{
   2.558 -		[((element *)xmlType)->name release];
   2.559 -		free(xmlType);
   2.560 -	}
   2.561 +            NSAttributedString *attributedMessage = [htmlDecoder decodeHTML:messageXML];
   2.562 +            if (showEmoticons) {
   2.563 +                attributedMessage = [adium.contentController filterAttributedString:attributedMessage
   2.564 +                                                                    usingFilterType:AIFilterMessageDisplay
   2.565 +                                                                          direction:(sentMessage ? AIFilterOutgoing : AIFilterIncoming)
   2.566 +                                                                            context:nil];				
   2.567 +            }
   2.568 +            [output appendAttributedString:attributedMessage];
   2.569 +            [output appendAttributedString:newlineAttributedString];
   2.570 +        } else if ([type isEqualToString:@"status"]) {
   2.571 +            NSString *dateStr = [[attributes objectForKey:@"time"] stringValue];
   2.572 +            NSDate *date = dateStr ? [NSCalendarDate calendarDateWithString:dateStr] : nil;
   2.573 +            NSString *status = [[attributes objectForKey:@"type"] stringValue];
   2.574 +            
   2.575 +            NSMutableString *messageXML = [NSMutableString string];
   2.576 +            for (NSXMLNode *node in [element children]) {
   2.577 +                [messageXML appendString:[node XMLString]];
   2.578 +            }            
   2.579 +            
   2.580 +            NSString *displayMessage = nil;
   2.581 +            //Note: I am diverging from what the AILoggerPlugin logs in this case.  It can't handle every case we can have here
   2.582 +            if([messageXML length]) {
   2.583 +                if([statusLookup objectForKey:status]) {
   2.584 +                    displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@: %@", nil), [statusLookup objectForKey:status], messageXML];
   2.585 +                } else {
   2.586 +                    displayMessage = [NSString stringWithFormat:AILocalizedString(@"%@", nil), messageXML];
   2.587 +                }
   2.588 +            } else if([status length] && [statusLookup objectForKey:status]) {
   2.589 +                displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@", nil), [statusLookup objectForKey:status]];
   2.590 +            }
   2.591 +            
   2.592 +            if([displayMessage length]) {
   2.593 +                [output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:@"<div class=\"status\">%@ (%@)</div>\n",
   2.594 +                                                                        displayMessage,
   2.595 +                                                                        [dateFormatter stringFromDate:date]]]];
   2.596 +            }
   2.597 +        }
   2.598 +    }
   2.599 +    
   2.600 +    return output;
   2.601 +    
   2.602 +ohno:
   2.603 +    if (!reentrancyFlag) {
   2.604 +        NSMutableString *xmlString = [NSMutableString stringWithUTF8String:[xmlData bytes]];
   2.605 +        [xmlString stripInvalidCharacters];
   2.606 +        return [self readData:[xmlString dataUsingEncoding:NSUTF8StringEncoding] withOptions:options retrying:YES];
   2.607 +    }
   2.608 +    @throw [NSException exceptionWithName:@"Log File Parsing Error" reason:[err description] userInfo:nil];
   2.609  }
   2.610  
   2.611  @end