Move us off of deprecated CFXMLParser and onto NSXMLDocument for parsing chat transcripts.
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