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