2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 #import "GBFireLogImporter.h"
18 #import <Adium/AIAccount.h>
19 #import <Adium/AIAccountControllerProtocol.h>
20 #import <Adium/AIInterfaceControllerProtocol.h>
21 #import <Adium/AILoginControllerProtocol.h>
22 #import <Adium/ESTextAndButtonsWindowController.h>
23 #import <AIUtilities/AIFileManagerAdditions.h>
24 #import <AIUtilities/NSCalendarDate+ISO8601Unparsing.h>
26 #define PATH_LOGS @"/Logs"
27 #define XML_MARKER @"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
29 @interface GBFireLogImporter (private)
30 - (void)askBeforeImport;
31 - (void)importFireLogs;
34 @implementation GBFireLogImporter
38 GBFireLogImporter *importer = [[GBFireLogImporter alloc] init];
39 [importer askBeforeImport];
49 [NSBundle loadNibNamed:@"FireLogImporter" owner:self];
54 - (void)askBeforeImport
56 [[adium interfaceController] displayQuestion:AILocalizedString(@"Import Fire Logs?",nil)
57 withDescription:AILocalizedString(@"For some older log formats, the importer cannot properly determine which account was used for conversation. In such cases, the importer will guess which account to use based upon the order of the accounts. Before importing Fire's logs, arrange your account order within the Preferences.",nil)
59 defaultButton:AILocalizedString(@"Import", nil)
60 alternateButton:AILocalizedString(@"Cancel", nil)
63 selector:@selector(importQuestionResponse:userInfo:)
67 - (void)importQuestionResponse:(NSNumber *)response userInfo:(id)info
69 if([response intValue] == AITextAndButtonsDefaultReturn)
70 [NSThread detachNewThreadSelector:@selector(importFireLogs) toTarget:self withObject:nil];
73 NSString *quotes[] = {
74 @"\"I have gotten into the habit of recording important meetings\"",
75 @"\"One never knows when an inconvenient truth will fall between the cracks and vanish\"",
79 - (void)importFireLogs
81 NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
82 [window orderFront:self];
83 [progressIndicator startAnimation:nil];
84 [textField_quote setStringValue:quotes[0]];
85 NSFileManager *fm = [NSFileManager defaultManager];
86 NSString *inputLogDir = [[[fm userApplicationSupportFolder] stringByAppendingPathComponent:@"Fire"] stringByAppendingPathComponent:@"Sessions"];
89 if(![fm fileExistsAtPath:inputLogDir isDirectory:&isDir] || !isDir)
93 NSArray *subPaths = [fm subpathsAtPath:inputLogDir];
94 NSString *outputBasePath = [[[[adium loginController] userDirectory] stringByAppendingPathComponent:PATH_LOGS] stringByExpandingTildeInPath];
96 NSArray *accounts = [[adium accountController] accounts];
98 NSMutableDictionary *defaultScreenname = [NSMutableDictionary dictionary];
99 for(current = [accounts count] - 1; current >= 0; current--)
101 AIAccount *acct = [accounts objectAtIndex:current];
102 [defaultScreenname setObject:[acct UID] forKey:[acct serviceID]];
105 [progressIndicator setDoubleValue:0.0];
106 [progressIndicator setIndeterminate:NO];
107 int total = [subPaths count], currentQuote = 0;
108 for(current = 0; current < total; current++)
110 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //A lot of temporary memory is used here
111 [progressIndicator setDoubleValue:(double)current/(double)total];
112 int nextQuote = current*sizeof(quotes)/sizeof(NSString *)/total;
113 if(nextQuote != currentQuote)
115 currentQuote = nextQuote;
116 [textField_quote setStringValue:quotes[currentQuote]];
118 NSString *logPath = [subPaths objectAtIndex:current];
119 NSString *fullInputPath = [inputLogDir stringByAppendingPathComponent:logPath];
120 if(![fm fileExistsAtPath:fullInputPath isDirectory:&isDir] || isDir)
126 NSString *extension = [logPath pathExtension];
127 NSArray *pathComponents = [logPath pathComponents];
128 if([pathComponents count] != 2)
130 //Incorrect directory structure, likely a .DS_Store or something like that
135 NSString *userAndService = [pathComponents objectAtIndex:[pathComponents count] - 2];
136 NSRange range = [userAndService rangeOfString:@"-" options:NSBackwardsSearch];
137 if (range.location == NSNotFound) {
138 NSLog(@"Warning: [%@ importFireLogs] could not find '-'.", self);
139 //Incorrect directory structure
143 NSString *user = [userAndService substringToIndex:range.location];
144 NSString *service = [userAndService substringFromIndex:range.location + 1];
145 NSDate *date = [NSDate dateWithNaturalLanguageString:[[pathComponents lastObject] stringByDeletingPathExtension]];
147 if([extension isEqualToString:@"session"])
149 NSString *account = [defaultScreenname objectForKey:service];
152 NSString *outputFileDir = [[outputBasePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", service, account]] stringByAppendingPathComponent:user];
153 NSString *outputFile = [outputFileDir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@).adiumLog", user, [date descriptionWithCalendarFormat:@"%Y-%m-%dT%H.%M.%S%z" timeZone:nil locale:nil]]];
154 [fm createDirectoriesForPath:outputFileDir];
155 [fm copyPath:fullInputPath toPath:outputFile handler:self];
157 else if([extension isEqualToString:@"session2"])
159 NSString *account = [defaultScreenname objectForKey:service];
162 NSString *outputFileDir = [[outputBasePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", service, account]] stringByAppendingPathComponent:user];
163 NSString *outputFile = [outputFileDir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@).AdiumHTMLLog", user, [date descriptionWithCalendarFormat:@"%Y-%m-%dT%H.%M.%S%z" timeZone:nil locale:nil]]];
164 [fm createDirectoriesForPath:outputFileDir];
165 [fm copyPath:fullInputPath toPath:outputFile handler:self];
167 else if([extension isEqualToString:@"xhtml"])
169 NSString *outputFile = [outputBasePath stringByAppendingPathComponent:@"tempLogImport"];
170 [fm createDirectoriesForPath:outputBasePath];
171 GBFireXMLLogImporter *xmlLog = [[GBFireXMLLogImporter alloc] init];
172 NSString *account = nil;
173 if([xmlLog readFile:fullInputPath toFile:outputFile account:&account])
176 account = [defaultScreenname objectForKey:service];
180 NSString *realOutputFileDir = [[outputBasePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", service, account]] stringByAppendingPathComponent:user];
181 NSString *realOutputFile = [realOutputFileDir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ (%@).chatlog", user, [date descriptionWithCalendarFormat:@"%Y-%m-%dT%H.%M.%S%z" timeZone:nil locale:nil]]];
182 [fm createDirectoriesForPath:realOutputFileDir];
183 [fm movePath:outputFile toPath:realOutputFile handler:self];
195 static void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context);
196 static void addChild(CFXMLParserRef parser, void *parent, void *child, void *context);
197 static void endStructure(CFXMLParserRef parser, void *xmlType, void *context);
198 static Boolean errorStructure (CFXMLParserRef parser, CFXMLParserStatusCode error, void *info);
200 @implementation GBFireXMLLogImporter
208 state = XML_STATE_NONE;
210 inputFileString = nil;
211 outputFileHandle = nil;
218 eventTranslate = [[NSDictionary alloc] initWithObjectsAndKeys:
219 @"userJoined", @"memberJoined",
220 @"userParted", @"memberParted",
221 @"userFormattedIdChanged", @"newNickname",
222 @"channelTopicChanged", @"topicChanged",
223 @"userPermissions/Promoted", @"memberPromoted",
224 @"userPermissions/Demoted", @"memberDemoted",
225 @"userPermissions/Voiced", @"memberVoiced",
226 @"userPermissions/Devoiced", @"memberDevoiced",
227 @"userKicked", @"memberKicked",
233 - (BOOL)readFile:(NSString *)inFile toFile:(NSString *)outFile account:(NSString * *)account;
235 AILog(@"%@: readFile:%@ toFile:%@",NSStringFromClass([self class]), inFile, outFile);
238 NSData *inputData = [NSData dataWithContentsOfFile:inFile];
239 inputFileString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding];
240 int outfd = open([outFile fileSystemRepresentation], O_CREAT | O_WRONLY, 0644);
241 outputFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:outfd closeOnDealloc:YES];
243 CFXMLParserCallBacks callbacks = {
251 CFXMLParserContext context = {
258 NSMutableString *newStr = [NSMutableString stringWithContentsOfFile:inFile];
259 int endOffset = [newStr rangeOfString:@">" options:NSBackwardsSearch].location;
260 int startOffset = [newStr rangeOfString:@"<" options:NSBackwardsSearch].location;
261 if((endOffset == NSNotFound || endOffset < startOffset) && startOffset != NSNotFound)
263 //Broken XML can crash the importer, attempt a repair, but most likely the parse will fail
264 [newStr appendString:@">"];
265 AILog(@"Fire log import: %@ has broken XML, you should fix this and re-import it", inFile);
266 NSLog(@"Fire log import: %@ has broken XML, you should fix this and re-import it", inFile);
267 #warning Surely we shouldn't log both, but this should be rare. Maybe put up a dialog?, thoughts? Same for below too
269 NSData *data = [newStr dataUsingEncoding:NSUTF8StringEncoding];
270 parser = CFXMLParserCreate(NULL, (CFDataRef)data, NULL,kCFXMLParserSkipMetaData | kCFXMLParserSkipWhitespace, kCFXMLNodeCurrentVersion, &callbacks, &context);
271 if (!CFXMLParserParse(parser)) {
272 NSLog(@"Fire log import: Parse of %@ failed", inFile);
273 AILog(@"Fire log import: Parse of %@ failed", inFile);
278 [outputFileHandle closeFile];
280 *account = [[mySN retain] autorelease];
286 [inputFileString release];
287 [outputFileHandle release];
288 [eventTranslate release];
290 [htmlMessage release];
293 [encryption release];
297 - (void)startedElement:(NSString *)name info:(const CFXMLElementInfo *)info
299 NSDictionary *attributes = (NSDictionary *)info->attributes;
303 if([name isEqualToString:@"envelope"])
307 state = XML_STATE_ENVELOPE;
309 else if([name isEqualToString:@"event"])
313 NSString *dateStr = [attributes objectForKey:@"occurred"];
315 date = [[NSCalendarDate dateWithString:dateStr] retain];
320 NSString *eventStr = [attributes objectForKey:@"name"];
322 eventName = [[NSString alloc] initWithString:eventStr];
327 [htmlMessage release];
329 state = XML_STATE_EVENT;
331 else if([name isEqualToString:@"log"])
333 NSString *service = [attributes objectForKey:@"service"];
334 NSString *account = [attributes objectForKey:@"accountName"];
337 NSRange range = [account rangeOfString:@"-"];
338 if(range.location != NSNotFound)
340 mySN = [[account substringFromIndex:range.location + 1] retain];
341 range = [mySN rangeOfString:@"@"];
342 NSRange revRange = [mySN rangeOfString:@"@" options:NSBackwardsSearch];
343 if ((revRange.location != range.location) && (revRange.location != NSNotFound))
345 NSString *oldMySN = mySN;
346 mySN = [[mySN substringToIndex:revRange.location] retain];
351 NSMutableString *chatTag = [NSMutableString stringWithFormat:@"%@\n<chat", XML_MARKER];
352 [chatTag appendString:@" xmlns=\"http://purl.org/net/ulf/ns/0.4-02\""];
354 [chatTag appendFormat:@" account=\"%@\"", mySN];
356 [chatTag appendFormat:@" service=\"%@\"", service];
357 [chatTag appendString:@">\n"];
358 [outputFileHandle writeData:[chatTag dataUsingEncoding:NSUTF8StringEncoding]];
361 case XML_STATE_ENVELOPE:
362 if ([name isEqualToString:@"message"])
366 NSString *dateStr = [attributes objectForKey:@"received"];
368 date = [[NSCalendarDate dateWithString:dateStr] retain];
372 //Mark the location of the message... We can copy it directly. Anyone know why it is off by 1?
373 messageStart = CFXMLParserGetLocation(parser) - 1;
375 if([attributes objectForKey:@"action"] != nil)
380 if([attributes objectForKey:@"away"] != nil)
384 [encryption release];
385 encryption = [[attributes objectForKey:@"security"] retain];
387 state = XML_STATE_MESSAGE;
389 else if([name isEqualToString:@"sender"])
393 NSString *nickname = [attributes objectForKey:@"nickname"];
394 NSString *selfSender = [attributes objectForKey:@"self"];
396 sender = [nickname retain];
397 else if ([selfSender isEqualToString:@"yes"])
398 sender = [mySN retain];
401 state = XML_STATE_SENDER;
404 case XML_STATE_SENDER:
405 case XML_STATE_MESSAGE:
406 case XML_STATE_EVENT:
407 if([name isEqualToString:@"Message"])
409 state = XML_STATE_EVENT_ATTRIBUTED_MESSAGE;
411 if([name isEqualToString:@"message"])
413 //Mark the location of the message... same as above
414 messageStart = CFXMLParserGetLocation(parser) - 1;
415 state = XML_STATE_EVENT_MESSAGE;
417 if([name isEqualToString:@"nickname"])
419 state = XML_STATE_EVENT_NICKNAME;
421 case XML_STATE_EVENT_ATTRIBUTED_MESSAGE:
422 case XML_STATE_EVENT_MESSAGE:
423 case XML_STATE_EVENT_NICKNAME:
428 - (void)endedElement:(NSString *)name empty:(BOOL)empty
432 case XML_STATE_ENVELOPE:
433 if([name isEqualToString:@"envelope"])
434 state = XML_STATE_NONE;
436 case XML_STATE_SENDER:
437 if([name isEqualToString:@"sender"])
438 state = XML_STATE_ENVELOPE;
440 case XML_STATE_MESSAGE:
441 if([name isEqualToString:@"message"])
443 CFIndex end = CFXMLParserGetLocation(parser);
444 NSString *message = nil;
446 message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 11)]; // 10 for </message> and 1 for the index being off
448 //Common logging format
449 NSMutableString *outMessage = [NSMutableString stringWithString:@"<message"];
452 [outMessage appendString:@" type=\"action\""];
454 //Hmm... there is a bug in Fire's logging format that logs action messages like <span>username </span>message
456 int index = [message rangeOfString:@"<span>"].location;
459 int endIndex = [message rangeOfString:@"</span>"].location;
461 sender = [[message substringWithRange:NSMakeRange(6, endIndex-7)] retain]; //6 is length of <span>. 7 is length of <span> plus trailing space
462 index = cutIndex = endIndex + 7; //7 is length of </span>
466 while([message characterAtIndex:index] == '<')
468 NSRange searchRange = NSMakeRange(index, [message length] - index);
469 NSRange range = [message rangeOfString:@">" options:0 range:searchRange];
470 index = range.location + 1;
472 NSString *newMessage = nil;
474 newMessage = [[NSString alloc] initWithFormat:@"%@/me %@", [message substringWithRange:NSMakeRange(cutIndex, index-cutIndex)], [message substringFromIndex:index]];
476 newMessage = [[NSString alloc] initWithString:@"/me "];
477 message = [newMessage autorelease];
480 [outMessage appendString:@" auto=\"yes\""];
482 [outMessage appendFormat:@" sender=\"%@\"", sender];
484 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
485 if([encryption length])
486 [outMessage appendFormat:@" encryption=\"%@\"", encryption];
488 [outMessage appendFormat:@">%@</message>\n", message];
490 [outMessage appendString:@"/>\n"];
491 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
492 state = XML_STATE_ENVELOPE;
495 case XML_STATE_EVENT:
496 if([name isEqualToString:@"event"])
497 state = XML_STATE_NONE;
499 case XML_STATE_EVENT_ATTRIBUTED_MESSAGE:
500 if([name isEqualToString:@"Message"])
501 state = XML_STATE_EVENT;
503 case XML_STATE_EVENT_MESSAGE:
504 if([name isEqualToString:@"message"])
506 CFIndex end = CFXMLParserGetLocation(parser);
507 NSString *message = nil;
509 message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 11)]; // 10 for </message> and 1 for the index being off
511 if([eventName isEqualToString:@"loggedOff"] || [eventName isEqualToString:@"memberParted"])
513 NSMutableString *outMessage = [NSMutableString stringWithString:@"<status type=\"offline\""];
515 [outMessage appendFormat:@" sender=\"%@\"", sender];
517 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
518 [outMessage appendString:@"/>"];
519 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
521 else if([eventName isEqualToString:@"loggedOn"] || [eventName isEqualToString:@"memberJoined"])
523 NSMutableString *outMessage = [NSMutableString stringWithString:@"<status type=\"online\""];
525 [outMessage appendFormat:@" sender=\"%@\"", sender];
527 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
528 [outMessage appendString:@"/>"];
529 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
531 else if([eventName isEqualToString:@"StatusChanged"])
533 //now we have to parse all these
534 NSString *type = nil;
535 int parseMessageIndex = 0;
539 if((parseMessageIndex = [message rangeOfString:@"changed status to Idle"].location) != NSNotFound)
544 else if((parseMessageIndex = [message rangeOfString:@"status to Available"].location) != NSNotFound)
546 else if((parseMessageIndex = [message rangeOfString:@"status to Away"].location) != NSNotFound)
548 else if((parseMessageIndex = [message rangeOfString:@"status to Busy"].location) != NSNotFound)
550 else if((parseMessageIndex = [message rangeOfString:@"status to Not at Home"].location) != NSNotFound)
552 else if((parseMessageIndex = [message rangeOfString:@"status to On the Phone"].location) != NSNotFound)
553 type = @"onThePhone";
554 else if((parseMessageIndex = [message rangeOfString:@"status to On Vacation"].location) != NSNotFound)
555 type = @"onVacation";
556 else if((parseMessageIndex = [message rangeOfString:@"status to Do Not Disturb"].location) != NSNotFound)
557 type = @"doNotDisturb";
558 else if((parseMessageIndex = [message rangeOfString:@"status to Extended Away"].location) != NSNotFound)
559 type = @"extendedAway";
560 else if((parseMessageIndex = [message rangeOfString:@"status to Be Right Back"].location) != NSNotFound)
561 type = @"beRightBack";
562 else if((parseMessageIndex = [message rangeOfString:@"status to Be NA"].location) != NSNotFound)
563 type = @"notAvailable";
564 else if((parseMessageIndex = [message rangeOfString:@"status to Be Not at Home"].location) != NSNotFound)
566 else if((parseMessageIndex = [message rangeOfString:@"status to Not at my Desk"].location) != NSNotFound)
567 type = @"notAtMyDesk";
568 else if((parseMessageIndex = [message rangeOfString:@"status to Not in the Office"].location) != NSNotFound)
569 type = @"notInTheOffice";
570 else if((parseMessageIndex = [message rangeOfString:@"status to Stepped Out"].location) != NSNotFound)
571 type = @"steppedOut";
573 NSLog(@"Unknown type %@", message);
575 //if the type is unknown, we can't do anything, drop it!
578 int colonIndex = [message rangeOfString:@":" options:0 range:NSMakeRange(parseMessageIndex, [message length] - parseMessageIndex)].location;
580 NSMutableString *outMessage = [NSMutableString stringWithFormat:@"<status type=\"%@\"", type];
582 [outMessage appendFormat:@" sender=\"%@\"", sender];
584 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
586 [outMessage appendString:@" idleTime=\"10\""];
588 NSString *subStr = nil;
589 if(colonIndex != NSNotFound && [message length] > colonIndex + 2)
590 //Eliminate the "has changed status to: " from the string
591 subStr = [message substringFromIndex:colonIndex + 2];
592 if(![subStr hasPrefix:@"<span"] && [subStr hasSuffix:@"</span>"])
593 //Eliminate the "</span>" at the end if it doesn't start with "<span"
594 subStr = [subStr substringToIndex:[subStr length] - 7];
595 if([htmlMessage length])
596 //Prefer the attributed message
597 [outMessage appendFormat:@">%@</status>\n", htmlMessage];
598 else if([subStr length])
599 [outMessage appendFormat:@">%@</status>\n", subStr];
601 [outMessage appendString:@"/>\n"];
602 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
605 else if([eventName isEqualToString:@"topicChanged"] ||
606 [eventName isEqualToString:@"memberJoined"] ||
607 [eventName isEqualToString:@"memberParted"] ||
608 [eventName isEqualToString:@"memberPromoted"] ||
609 [eventName isEqualToString:@"memberDemoted"] ||
610 [eventName isEqualToString:@"memberVoiced"] ||
611 [eventName isEqualToString:@"memberDevoiced"] ||
612 [eventName isEqualToString:@"memberKicked"] ||
613 [eventName isEqualToString:@"newNickname"])
615 NSMutableString *outMessage = [NSMutableString stringWithFormat:@"<status type=\"%@\"", [eventTranslate objectForKey:eventName]];
617 [outMessage appendFormat:@" sender=\"%@\"", sender];
619 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
622 [outMessage appendFormat:@">%@</status>\n", message];
624 [outMessage appendString:@"/>\n"];
625 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
627 else if(eventName == nil)
630 NSMutableString *outMessage = [NSMutableString stringWithFormat:@"<event type=\"service\"", eventName];
632 [outMessage appendFormat:@" sender=\"%@\"", sender];
634 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
637 [outMessage appendFormat:@">%@</event>\n", message];
639 [outMessage appendString:@"/>\n"];
640 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
644 //Need to translate these
645 NSLog(@"Got an Event %@ at %@: %@",
650 state = XML_STATE_EVENT;
653 case XML_STATE_EVENT_NICKNAME:
654 state = XML_STATE_EVENT;
657 if([name isEqualToString:@"log"])
658 [outputFileHandle writeData:[[NSString stringWithString:@"\n</chat>"] dataUsingEncoding:NSUTF8StringEncoding]];
668 - (void)text:(NSString *)text
672 case XML_STATE_SENDER:
674 sender = [text retain];
676 case XML_STATE_EVENT_NICKNAME:
678 sender = [text retain];
680 case XML_STATE_EVENT_ATTRIBUTED_MESSAGE:
681 if(htmlMessage == nil)
682 htmlMessage = [text mutableCopy];
684 [htmlMessage appendString:text];
687 case XML_STATE_ENVELOPE:
688 case XML_STATE_MESSAGE:
689 case XML_STATE_EVENT:
690 case XML_STATE_EVENT_MESSAGE:
697 static void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context)
701 // Use the dataTypeID to determine what to print.
702 switch (CFXMLNodeGetTypeCode(node)) {
703 case kCFXMLNodeTypeDocument:
705 case kCFXMLNodeTypeElement:
707 NSString *name = [NSString stringWithString:(NSString *)CFXMLNodeGetString(node)];
708 const CFXMLElementInfo *info = CFXMLNodeGetInfoPtr(node);
709 [(GBFireXMLLogImporter *)context startedElement:name info:info];
710 ret = (element *)malloc(sizeof(element));
711 ret->name = [name retain];
712 ret->empty = info->isEmpty;
715 case kCFXMLNodeTypeProcessingInstruction:
716 case kCFXMLNodeTypeComment:
718 case kCFXMLNodeTypeEntityReference:
721 CFXMLEntityReferenceInfo *entityInfo = (CFXMLEntityReferenceInfo *)CFXMLNodeGetInfoPtr(node);
722 NSString *dataString = (NSString *)CFXMLNodeGetString(node);
723 if(entityInfo->entityType == kCFXMLEntityTypeCharacter)
725 if([dataString characterAtIndex:0] == '#')
728 if([dataString characterAtIndex:1] == 'x')
732 for(i = hex + 1; i < [dataString length]; i++)
736 unichar encodedDigit = [dataString characterAtIndex:i];
737 if(encodedDigit <= '9' && encodedDigit >= '0')
738 entity = entity * 16 + (encodedDigit - '0');
739 else if(encodedDigit <= 'F' && encodedDigit >= 'A')
740 entity = entity * 16 + (encodedDigit - 'A' + 10);
741 else if(encodedDigit <= 'f' && encodedDigit >= 'a')
742 entity = entity * 16 + (encodedDigit - 'a' + 10);
746 entity = entity * 10 + ([dataString characterAtIndex:i] - '0');
751 else if(entityInfo->entityType == kCFXMLEntityTypeParsedInternal)
753 if ([dataString isEqualToString:@"lt"])
755 else if ([dataString isEqualToString:@"gt"])
757 else if ([dataString isEqualToString:@"quot"])
759 else if ([dataString isEqualToString:@"amp"])
761 else if ([dataString isEqualToString:@"apos"])
763 else if ([dataString isEqualToString:@"ldquo"])
765 else if ([dataString isEqualToString:@"rdquo"])
768 [(GBFireXMLLogImporter *)context text:[[[NSString alloc] initWithCharacters:&entity length:1] autorelease]];
771 case kCFXMLNodeTypeText:
772 [(GBFireXMLLogImporter *)context text:[NSString stringWithString:(NSString *)CFXMLNodeGetString(node)]];
774 case kCFXMLNodeTypeCDATASection:
775 case kCFXMLNodeTypeDocumentType:
776 case kCFXMLNodeTypeWhitespace:
781 // Return the data string for use by the addChild and
782 // endStructure callbacks.
786 static void addChild(CFXMLParserRef parser, void *parent, void *child, void *context)
790 static void endStructure(CFXMLParserRef parser, void *xmlType, void *context)
792 NSString *name = nil;
796 name = [NSString stringWithString:((element *)xmlType)->name];
797 empty = ((element *)xmlType)->empty;
799 [(GBFireXMLLogImporter *)context endedElement:name empty:empty];
802 [((element *)xmlType)->name release];
807 static Boolean errorStructure (CFXMLParserRef parser, CFXMLParserStatusCode error, void *info)