aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPXMLExporter.m
diff options
context:
space:
mode:
authorstuconnolly <stuart02@gmail.com>2010-05-24 18:07:43 +0000
committerstuconnolly <stuart02@gmail.com>2010-05-24 18:07:43 +0000
commitbbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d (patch)
tree1cf7d41f091854e8e2288946684267ce0f8ceaf4 /Source/SPXMLExporter.m
parentd48005bd9b34f2fb1afd31f7487b7bbf8b9b978f (diff)
downloadsequelpro-bbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d.tar.gz
sequelpro-bbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d.tar.bz2
sequelpro-bbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d.zip
Merge export redesign branch back into trunk.
Includes a completely redesign approach to all export data types based on the use of NSOperation subclasses. CSV, SQL, XML and dot export types are currently functional, while the source files for PDF and HTML export types exist they are to be implemented, but are currently hidden from the interface. Also includes the following: - Completely redesigned export interface. - The ability to customize CSV NULL values. - The ability to specify whether the UTF-8 BOM should be used in SQL dumps. - The ability to specify whether BLOB fields are output as hex or plain text during SQL dumps. Defaults to hex. - Exporting currently selected tables via the tables list context menu. Outstanding issues: - Not all progress indicators for all export types are functional (or functioning correctly). - A few issues related to the introduction of only exporting the content and create and drop syntax of specific tables during SQL dumps. Needs some serious testing and benchmarking to ensure it replicates the current export functionality.
Diffstat (limited to 'Source/SPXMLExporter.m')
-rw-r--r--Source/SPXMLExporter.m218
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