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 mettings\"",
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);
199 @implementation GBFireXMLLogImporter
207 state = XML_STATE_NONE;
209 inputFileString = nil;
210 outputFileHandle = nil;
217 eventTranslate = [[NSDictionary alloc] initWithObjectsAndKeys:
218 @"userJoined", @"memberJoined",
219 @"userParted", @"memberParted",
220 @"userFormattedIdChanged", @"newNickname",
221 @"channelTopicChanged", @"topicChanged",
222 @"userPermissions/Promoted", @"memberPromoted",
223 @"userPermissions/Demoted", @"memberDemoted",
224 @"userPermissions/Voiced", @"memberVoiced",
225 @"userPermissions/Devoiced", @"memberDevoiced",
226 @"userKicked", @"memberKicked",
232 - (BOOL)readFile:(NSString *)inFile toFile:(NSString *)outFile account:(NSString * *)account;
234 AILog(@"%@: readFile:%@ toFile:%@",NSStringFromClass([self class]), inFile, outFile);
237 NSData *inputData = [NSData dataWithContentsOfFile:inFile];
238 inputFileString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding];
239 int outfd = open([outFile fileSystemRepresentation], O_CREAT | O_WRONLY, 0644);
240 outputFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:outfd closeOnDealloc:YES];
241 NSURL *url = [[NSURL alloc] initFileURLWithPath:inFile];
243 CFXMLParserCallBacks callbacks = {
251 CFXMLParserContext context = {
258 parser = CFXMLParserCreateWithDataFromURL(NULL, (CFURLRef)url, kCFXMLParserSkipMetaData | kCFXMLParserSkipWhitespace, kCFXMLNodeCurrentVersion, &callbacks, &context);
259 if (!CFXMLParserParse(parser)) {
260 NSLog(@"Fire log import: Parse of %@ failed", inFile);
261 AILog(@"Fire log import: Parse of %@ failed", inFile);
267 [outputFileHandle closeFile];
269 *account = [[mySN retain] autorelease];
275 [inputFileString release];
276 [outputFileHandle release];
277 [eventTranslate release];
279 [htmlMessage release];
282 [encryption release];
286 - (void)startedElement:(NSString *)name info:(const CFXMLElementInfo *)info
288 NSDictionary *attributes = (NSDictionary *)info->attributes;
292 if([name isEqualToString:@"envelope"])
296 state = XML_STATE_ENVELOPE;
298 else if([name isEqualToString:@"event"])
302 NSString *dateStr = [attributes objectForKey:@"occurred"];
304 date = [[NSCalendarDate dateWithString:dateStr] retain];
309 NSString *eventStr = [attributes objectForKey:@"name"];
311 eventName = [[NSString alloc] initWithString:eventStr];
316 [htmlMessage release];
318 state = XML_STATE_EVENT;
320 else if([name isEqualToString:@"log"])
322 NSString *service = [attributes objectForKey:@"service"];
323 NSString *account = [attributes objectForKey:@"accountName"];
326 NSRange range = [account rangeOfString:@"-"];
327 if(range.location != NSNotFound)
329 mySN = [[account substringFromIndex:range.location + 1] retain];
330 range = [mySN rangeOfString:@"@"];
331 NSRange revRange = [mySN rangeOfString:@"@" options:NSBackwardsSearch];
332 if ((revRange.location != range.location) && (revRange.location != NSNotFound))
334 NSString *oldMySN = mySN;
335 mySN = [[mySN substringToIndex:revRange.location] retain];
340 NSMutableString *chatTag = [NSMutableString stringWithFormat:@"%@\n<chat", XML_MARKER];
341 [chatTag appendString:@" xmlns=\"http://purl.org/net/ulf/ns/0.4-02\""];
343 [chatTag appendFormat:@" account=\"%@\"", mySN];
345 [chatTag appendFormat:@" service=\"%@\"", service];
346 [chatTag appendString:@">\n"];
347 [outputFileHandle writeData:[chatTag dataUsingEncoding:NSUTF8StringEncoding]];
350 case XML_STATE_ENVELOPE:
351 if ([name isEqualToString:@"message"])
355 NSString *dateStr = [attributes objectForKey:@"received"];
357 date = [[NSCalendarDate dateWithString:dateStr] retain];
361 //Mark the location of the message... We can copy it directly. Anyone know why it is off by 1?
362 messageStart = CFXMLParserGetLocation(parser) - 1;
364 if([attributes objectForKey:@"action"] != nil)
369 if([attributes objectForKey:@"away"] != nil)
373 [encryption release];
374 encryption = [[attributes objectForKey:@"security"] retain];
376 state = XML_STATE_MESSAGE;
378 else if([name isEqualToString:@"sender"])
382 NSString *nickname = [attributes objectForKey:@"nickname"];
383 NSString *selfSender = [attributes objectForKey:@"self"];
385 sender = [nickname retain];
386 else if ([selfSender isEqualToString:@"yes"])
387 sender = [mySN retain];
390 state = XML_STATE_SENDER;
393 case XML_STATE_SENDER:
394 case XML_STATE_MESSAGE:
395 case XML_STATE_EVENT:
396 if([name isEqualToString:@"Message"])
398 state = XML_STATE_EVENT_ATTRIBUTED_MESSAGE;
400 if([name isEqualToString:@"message"])
402 //Mark the location of the message... same as above
403 messageStart = CFXMLParserGetLocation(parser) - 1;
404 state = XML_STATE_EVENT_MESSAGE;
406 if([name isEqualToString:@"nickname"])
408 state = XML_STATE_EVENT_NICKNAME;
410 case XML_STATE_EVENT_ATTRIBUTED_MESSAGE:
411 case XML_STATE_EVENT_MESSAGE:
412 case XML_STATE_EVENT_NICKNAME:
417 - (void)endedElement:(NSString *)name empty:(BOOL)empty
421 case XML_STATE_ENVELOPE:
422 if([name isEqualToString:@"envelope"])
423 state = XML_STATE_NONE;
425 case XML_STATE_SENDER:
426 if([name isEqualToString:@"sender"])
427 state = XML_STATE_ENVELOPE;
429 case XML_STATE_MESSAGE:
430 if([name isEqualToString:@"message"])
432 CFIndex end = CFXMLParserGetLocation(parser);
433 NSString *message = nil;
435 message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 11)]; // 10 for </message> and 1 for the index being off
437 //Common logging format
438 NSMutableString *outMessage = [NSMutableString stringWithString:@"<message"];
441 [outMessage appendString:@" type=\"action\""];
443 //Hmm... there is a bug in Fire's logging format that logs action messages like <span>username </span>message
445 int index = [message rangeOfString:@"<span>"].location;
448 int endIndex = [message rangeOfString:@"</span>"].location;
450 sender = [[message substringWithRange:NSMakeRange(6, endIndex-7)] retain]; //6 is length of <span>. 7 is length of <span> plus trailing space
451 index = cutIndex = endIndex + 7; //7 is length of </span>
455 while([message characterAtIndex:index] == '<')
457 NSRange searchRange = NSMakeRange(index, [message length] - index);
458 NSRange range = [message rangeOfString:@">" options:0 range:searchRange];
459 index = range.location + 1;
461 NSString *newMessage = nil;
463 newMessage = [[NSString alloc] initWithFormat:@"%@/me %@", [message substringWithRange:NSMakeRange(cutIndex, index-cutIndex)], [message substringFromIndex:index]];
465 newMessage = [[NSString alloc] initWithString:@"/me "];
466 message = [newMessage autorelease];
469 [outMessage appendString:@" auto=\"yes\""];
471 [outMessage appendFormat:@" sender=\"%@\"", sender];
473 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
474 if([encryption length])
475 [outMessage appendFormat:@" encryption=\"%@\"", encryption];
477 [outMessage appendFormat:@">%@</message>\n", message];
479 [outMessage appendString:@"/>\n"];
480 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
481 state = XML_STATE_ENVELOPE;
484 case XML_STATE_EVENT:
485 if([name isEqualToString:@"event"])
486 state = XML_STATE_NONE;
488 case XML_STATE_EVENT_ATTRIBUTED_MESSAGE:
489 if([name isEqualToString:@"Message"])
490 state = XML_STATE_EVENT;
492 case XML_STATE_EVENT_MESSAGE:
493 if([name isEqualToString:@"message"])
495 CFIndex end = CFXMLParserGetLocation(parser);
496 NSString *message = nil;
498 message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 11)]; // 10 for </message> and 1 for the index being off
500 if([eventName isEqualToString:@"loggedOff"] || [eventName isEqualToString:@"memberParted"])
502 NSMutableString *outMessage = [NSMutableString stringWithString:@"<status type=\"offline\""];
504 [outMessage appendFormat:@" sender=\"%@\"", sender];
506 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
507 [outMessage appendString:@"/>"];
508 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
510 else if([eventName isEqualToString:@"loggedOn"] || [eventName isEqualToString:@"memberJoined"])
512 NSMutableString *outMessage = [NSMutableString stringWithString:@"<status type=\"online\""];
514 [outMessage appendFormat:@" sender=\"%@\"", sender];
516 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
517 [outMessage appendString:@"/>"];
518 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
520 else if([eventName isEqualToString:@"StatusChanged"])
522 //now we have to parse all these
523 NSString *type = nil;
524 int parseMessageIndex = 0;
528 if((parseMessageIndex = [message rangeOfString:@"changed status to Idle"].location) != NSNotFound)
533 else if((parseMessageIndex = [message rangeOfString:@"status to Available"].location) != NSNotFound)
535 else if((parseMessageIndex = [message rangeOfString:@"status to Away"].location) != NSNotFound)
537 else if((parseMessageIndex = [message rangeOfString:@"status to Busy"].location) != NSNotFound)
539 else if((parseMessageIndex = [message rangeOfString:@"status to Not at Home"].location) != NSNotFound)
541 else if((parseMessageIndex = [message rangeOfString:@"status to On the Phone"].location) != NSNotFound)
542 type = @"onThePhone";
543 else if((parseMessageIndex = [message rangeOfString:@"status to On Vacation"].location) != NSNotFound)
544 type = @"onVacation";
545 else if((parseMessageIndex = [message rangeOfString:@"status to Do Not Disturb"].location) != NSNotFound)
546 type = @"doNotDisturb";
547 else if((parseMessageIndex = [message rangeOfString:@"status to Extended Away"].location) != NSNotFound)
548 type = @"extendedAway";
549 else if((parseMessageIndex = [message rangeOfString:@"status to Be Right Back"].location) != NSNotFound)
550 type = @"beRightBack";
551 else if((parseMessageIndex = [message rangeOfString:@"status to Be NA"].location) != NSNotFound)
552 type = @"notAvailable";
553 else if((parseMessageIndex = [message rangeOfString:@"status to Be Not at Home"].location) != NSNotFound)
555 else if((parseMessageIndex = [message rangeOfString:@"status to Not at my Desk"].location) != NSNotFound)
556 type = @"notAtMyDesk";
557 else if((parseMessageIndex = [message rangeOfString:@"status to Not in the Office"].location) != NSNotFound)
558 type = @"notInTheOffice";
559 else if((parseMessageIndex = [message rangeOfString:@"status to Stepped Out"].location) != NSNotFound)
560 type = @"steppedOut";
562 NSLog(@"Unknown type %@", message);
564 //if the type is unknown, we can't do anything, drop it!
567 int colonIndex = [message rangeOfString:@":" options:0 range:NSMakeRange(parseMessageIndex, [message length] - parseMessageIndex)].location;
569 NSMutableString *outMessage = [NSMutableString stringWithFormat:@"<status type=\"%@\"", type];
571 [outMessage appendFormat:@" sender=\"%@\"", sender];
573 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
575 [outMessage appendString:@" idleTime=\"10\""];
577 NSString *subStr = nil;
578 if(colonIndex != NSNotFound && [message length] > colonIndex + 2)
579 //Eliminate the "has changed status to: " from the string
580 subStr = [message substringFromIndex:colonIndex + 2];
581 if(![subStr hasPrefix:@"<span"] && [subStr hasSuffix:@"</span>"])
582 //Eliminate the "</span>" at the end if it doesn't start with "<span"
583 subStr = [subStr substringToIndex:[subStr length] - 7];
584 if([htmlMessage length])
585 //Prefer the attributed message
586 [outMessage appendFormat:@">%@</status>\n", htmlMessage];
587 else if([subStr length])
588 [outMessage appendFormat:@">%@</status>\n", subStr];
590 [outMessage appendString:@"/>\n"];
591 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
594 else if([eventName isEqualToString:@"topicChanged"] ||
595 [eventName isEqualToString:@"memberJoined"] ||
596 [eventName isEqualToString:@"memberParted"] ||
597 [eventName isEqualToString:@"memberPromoted"] ||
598 [eventName isEqualToString:@"memberDemoted"] ||
599 [eventName isEqualToString:@"memberVoiced"] ||
600 [eventName isEqualToString:@"memberDevoiced"] ||
601 [eventName isEqualToString:@"memberKicked"] ||
602 [eventName isEqualToString:@"newNickname"])
604 NSMutableString *outMessage = [NSMutableString stringWithFormat:@"<status type=\"%@\"", [eventTranslate objectForKey:eventName]];
606 [outMessage appendFormat:@" sender=\"%@\"", sender];
608 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
611 [outMessage appendFormat:@">%@</status>\n", message];
613 [outMessage appendString:@"/>\n"];
614 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
616 else if(eventName == nil)
619 NSMutableString *outMessage = [NSMutableString stringWithFormat:@"<event type=\"service\"", eventName];
621 [outMessage appendFormat:@" sender=\"%@\"", sender];
623 [outMessage appendFormat:@" time=\"%@\"", [date ISO8601DateString]];
626 [outMessage appendFormat:@">%@</event>\n", message];
628 [outMessage appendString:@"/>\n"];
629 [outputFileHandle writeData:[outMessage dataUsingEncoding:NSUTF8StringEncoding]];
633 //Need to translate these
634 NSLog(@"Got an Event %@ at %@: %@",
639 state = XML_STATE_EVENT;
642 case XML_STATE_EVENT_NICKNAME:
643 state = XML_STATE_EVENT;
646 if([name isEqualToString:@"log"])
647 [outputFileHandle writeData:[[NSString stringWithString:@"\n</chat>"] dataUsingEncoding:NSUTF8StringEncoding]];
657 - (void)text:(NSString *)text
661 case XML_STATE_SENDER:
663 sender = [text retain];
665 case XML_STATE_EVENT_NICKNAME:
667 sender = [text retain];
669 case XML_STATE_EVENT_ATTRIBUTED_MESSAGE:
670 if(htmlMessage == nil)
671 htmlMessage = [text mutableCopy];
673 [htmlMessage appendString:text];
676 case XML_STATE_ENVELOPE:
677 case XML_STATE_MESSAGE:
678 case XML_STATE_EVENT:
679 case XML_STATE_EVENT_MESSAGE:
686 void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context)
690 // Use the dataTypeID to determine what to print.
691 switch (CFXMLNodeGetTypeCode(node)) {
692 case kCFXMLNodeTypeDocument:
694 case kCFXMLNodeTypeElement:
696 NSString *name = [NSString stringWithString:(NSString *)CFXMLNodeGetString(node)];
697 const CFXMLElementInfo *info = CFXMLNodeGetInfoPtr(node);
698 [(GBFireXMLLogImporter *)context startedElement:name info:info];
699 ret = (element *)malloc(sizeof(element));
700 ret->name = [name retain];
701 ret->empty = info->isEmpty;
704 case kCFXMLNodeTypeProcessingInstruction:
705 case kCFXMLNodeTypeComment:
707 case kCFXMLNodeTypeEntityReference:
710 CFXMLEntityReferenceInfo *entityInfo = (CFXMLEntityReferenceInfo *)CFXMLNodeGetInfoPtr(node);
711 NSString *dataString = (NSString *)CFXMLNodeGetString(node);
712 if(entityInfo->entityType == kCFXMLEntityTypeCharacter)
714 if([dataString characterAtIndex:0] == '#')
717 if([dataString characterAtIndex:1] == 'x')
721 for(i = hex + 1; i < [dataString length]; i++)
725 unichar encodedDigit = [dataString characterAtIndex:i];
726 if(encodedDigit <= '9' && encodedDigit >= '0')
727 entity = entity * 16 + (encodedDigit - '0');
728 else if(encodedDigit <= 'F' && encodedDigit >= 'A')
729 entity = entity * 16 + (encodedDigit - 'A' + 10);
730 else if(encodedDigit <= 'f' && encodedDigit >= 'a')
731 entity = entity * 16 + (encodedDigit - 'a' + 10);
735 entity = entity * 10 + ([dataString characterAtIndex:i] - '0');
740 else if(entityInfo->entityType == kCFXMLEntityTypeParsedInternal)
742 if ([dataString isEqualToString:@"lt"])
744 else if ([dataString isEqualToString:@"gt"])
746 else if ([dataString isEqualToString:@"quot"])
748 else if ([dataString isEqualToString:@"amp"])
750 else if ([dataString isEqualToString:@"apos"])
752 else if ([dataString isEqualToString:@"ldquo"])
754 else if ([dataString isEqualToString:@"rdquo"])
757 [(GBFireXMLLogImporter *)context text:[[[NSString alloc] initWithCharacters:&entity length:1] autorelease]];
760 case kCFXMLNodeTypeText:
761 [(GBFireXMLLogImporter *)context text:[NSString stringWithString:(NSString *)CFXMLNodeGetString(node)]];
763 case kCFXMLNodeTypeCDATASection:
764 case kCFXMLNodeTypeDocumentType:
765 case kCFXMLNodeTypeWhitespace:
770 // Return the data string for use by the addChild and
771 // endStructure callbacks.
775 void addChild(CFXMLParserRef parser, void *parent, void *child, void *context)
779 void endStructure(CFXMLParserRef parser, void *xmlType, void *context)
781 NSString *name = nil;
785 name = [NSString stringWithString:((element *)xmlType)->name];
786 empty = ((element *)xmlType)->empty;
788 [(GBFireXMLLogImporter *)context endedElement:name empty:empty];
791 [((element *)xmlType)->name release];