diff options
Diffstat (limited to 'Source/SPXMLExporter.m')
-rw-r--r-- | Source/SPXMLExporter.m | 218 |
1 files changed, 214 insertions, 4 deletions
diff --git a/Source/SPXMLExporter.m b/Source/SPXMLExporter.m index dabfe263..94cfec05 100644 --- a/Source/SPXMLExporter.m +++ b/Source/SPXMLExporter.m @@ -23,26 +23,236 @@ // // More info at <http://code.google.com/p/sequel-pro/> +#import <MCPKit/MCPKit.h> + #import "SPXMLExporter.h" +#import "SPArrayAdditions.h" +#import "SPStringAdditions.h" +#import "SPFileHandle.h" +#import "SPConstants.h" +#import "SPExportUtilities.h" @implementation SPXMLExporter +@synthesize delegate; +@synthesize xmlDataArray; +@synthesize xmlTableName; + +/** + * Initialise an instance of SPXMLExporter using the supplied delegate. + */ +- (id)initWithDelegate:(NSObject *)exportDelegate +{ + if ((self = [super init])) { + SPExportDelegateConformsToProtocol(exportDelegate, @protocol(SPXMLExporterProtocol)); + + [self setDelegate:exportDelegate]; + } + + return self; +} + /** - * Start the SQL data conversion process. This method is automatically called when an instance of this object + * Start the XML export process. This method is automatically called when an instance of this class * is placed on an NSOperationQueue. Do not call it directly as there is no manual multithreading. */ - (void)main { @try { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSAutoreleasePool *xmlExportPool = [[NSAutoreleasePool alloc] init]; + NSArray *xmlRow = nil; + NSString *dataConversionString = nil; + MCPStreamingResult *streamingResult = nil; + NSMutableArray *xmlTags = [NSMutableArray array]; + NSMutableString *xmlString = [NSMutableString string]; + NSMutableString *xmlItem = [NSMutableString string]; - [pool release]; - } - @catch (NSException *e) { + NSUInteger xmlRowCount = 0; + NSUInteger i, totalRows, currentRowIndex, lastProgressValue, currentPoolDataLength; + + // Check to see if we have at least a table name or data array + if ((![self xmlTableName]) && (![self xmlDataArray]) || + ([[self xmlTableName] isEqualToString:@""]) && ([[self xmlDataArray] count] == 0)) + { + [pool release]; + return; + } + + // Inform the delegate that the export process is about to begin + [delegate performSelectorOnMainThread:@selector(xmlExportProcessWillBegin:) withObject:self waitUntilDone:NO]; + + // Mark the process as running + [self setExportProcessIsRunning:YES]; + + lastProgressValue = 0; + + // Make a streaming request for the data if the data array isn't set + if ((![self xmlDataArray]) && [self xmlTableName]) { + totalRows = [[[[connection queryString:[NSString stringWithFormat:@"SELECT COUNT(1) FROM %@", [[self xmlTableName] backtickQuotedString]]] fetchRowAsArray] objectAtIndex:0] integerValue]; + streamingResult = [connection streamingQueryString:[NSString stringWithFormat:@"SELECT * FROM %@", [[self xmlTableName] backtickQuotedString]] useLowMemoryBlockingStreaming:[self exportUsingLowMemoryBlockingStreaming]]; + } + else { + totalRows = [[self xmlDataArray] count]; + } + + // Set up an array of encoded field names as opening and closing tags + xmlRow = ([self xmlDataArray]) ? [[self xmlDataArray] objectAtIndex:0] : [streamingResult fetchFieldNames]; + + for (i = 0; i < [xmlRow count]; i++) + { + [xmlTags addObject:[NSMutableArray array]]; + + [[xmlTags objectAtIndex:i] addObject:[NSString stringWithFormat:@"\t\t<%@>", [[[xmlRow objectAtIndex:i] description] HTMLEscapeString]]]; + [[xmlTags objectAtIndex:i] addObject:[NSString stringWithFormat:@"</%@>\n", [[[xmlRow objectAtIndex:i] description] HTMLEscapeString]]]; + } + [[self exportOutputFileHandle] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; + + // Write an opening tag in the form of the table name + [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"\t<%@>\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; + + // Set up the starting row, which is 0 for streaming result sets and + // 1 for supplied arrays which include the column headers as the first row. + currentRowIndex = 0; + + if ([self xmlDataArray]) currentRowIndex++; + + // Drop into the processing loop + xmlExportPool = [[NSAutoreleasePool alloc] init]; + + currentPoolDataLength = 0; + + while (1) + { + // Check for cancellation flag + if ([self isCancelled]) { + if (streamingResult) { + [connection cancelCurrentQuery]; + [streamingResult cancelResultLoad]; + } + + [xmlExportPool release]; + [pool release]; + + return; + } + + // Retrieve the next row from the supplied data, either directly from the array... + if ([self xmlDataArray]) { + xmlRow = NSArrayObjectAtIndex([self xmlDataArray], currentRowIndex); + } + // Or by reading an appropriate row from the streaming result + else { + xmlRow = [streamingResult fetchNextRowAsArray]; + + if (!xmlRow) break; + } + + // Get the cell count if we don't already have it stored + if (!xmlRowCount) xmlRowCount = [xmlRow count]; + + // Construct the row + [xmlString setString:@"\t<row>\n"]; + + for (i = 0; i < xmlRowCount; i++) + { + // Check for cancellation flag + if ([self isCancelled]) { + if (streamingResult) { + [connection cancelCurrentQuery]; + [streamingResult cancelResultLoad]; + } + + [xmlExportPool release]; + [pool release]; + + return; + } + + // Retrieve the contents of this tag + if ([NSArrayObjectAtIndex(xmlRow, i) isKindOfClass:[NSData class]]) { + dataConversionString = [[NSString alloc] initWithData:NSArrayObjectAtIndex(xmlRow, i) encoding:[self exportOutputEncoding]]; + + if (dataConversionString == nil) { + dataConversionString = [[NSString alloc] initWithData:NSArrayObjectAtIndex(xmlRow, i) encoding:NSASCIIStringEncoding]; + } + + [xmlItem setString:[NSString stringWithString:dataConversionString]]; + [dataConversionString release]; + } + else { + [xmlItem setString:[NSArrayObjectAtIndex(xmlRow, i) description]]; + } + + // Add the opening and closing tag and the contents to the XML string + [xmlString appendString:NSArrayObjectAtIndex(NSArrayObjectAtIndex(xmlTags, i), 0)]; + [xmlString appendString:[xmlItem HTMLEscapeString]]; + [xmlString appendString:NSArrayObjectAtIndex(NSArrayObjectAtIndex(xmlTags, i), 1)]; + } + + [xmlString appendString:@"\t</row>\n"]; + + // Record the total length for use with pool flushing + currentPoolDataLength += [xmlString length]; + + // Write the row to the filehandle + [[self exportOutputFileHandle] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; + + // Update the progress counter and progress bar + currentRowIndex++; + + // Update the progress + if (totalRows && (currentRowIndex * ([self exportMaxProgress] / totalRows)) > lastProgressValue) { + + NSInteger progress = (currentRowIndex * ([self exportMaxProgress] / totalRows)); + + [self setExportProgressValue:progress]; + + lastProgressValue = progress; + } + + // Inform the delegate that the export's progress has been updated + [delegate performSelectorOnMainThread:@selector(xmlExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; + + // If an array was supplied and we've processed all rows, break + if ([self xmlDataArray] && totalRows == currentRowIndex) break; + + // Drain the autorelease pool as required to keep memory usage low + if (currentPoolDataLength > 250000) { + [xmlExportPool release]; + xmlExportPool = [[NSAutoreleasePool alloc] init]; + } + } + + // Write the closing tag for the table + [[self exportOutputFileHandle] writeData:[[NSString stringWithFormat:@"\t</%@>\n\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; + + // Write data to disk + [[self exportOutputFileHandle] synchronizeFile]; + + // Mark the process as not running + [self setExportProcessIsRunning:NO]; + + // Inform the delegate that the export process is complete + [delegate performSelectorOnMainThread:@selector(xmlExportProcessComplete:) withObject:self waitUntilDone:NO]; + + [pool release]; } + @catch (NSException *e) { } +} + +/** + * Dealloc + */ +- (void)dealloc +{ + if (xmlDataArray) [xmlDataArray release], xmlDataArray = nil; + if (xmlTableName) [xmlTableName release], xmlTableName = nil; + + [super dealloc]; } @end |