diff options
Diffstat (limited to 'Source/SPXMLExporter.m')
-rw-r--r-- | Source/SPXMLExporter.m | 313 |
1 files changed, 205 insertions, 108 deletions
diff --git a/Source/SPXMLExporter.m b/Source/SPXMLExporter.m index 1ff32018..962d5a53 100644 --- a/Source/SPXMLExporter.m +++ b/Source/SPXMLExporter.m @@ -35,6 +35,9 @@ @synthesize xmlDataArray; @synthesize xmlTableName; @synthesize xmlNULLString; +@synthesize xmlOutputIncludeStructure; +@synthesize xmlOutputIncludeContent; +@synthesize xmlFormat; /** * Initialise an instance of SPXMLExporter using the supplied delegate. @@ -62,13 +65,20 @@ { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + BOOL isTableExport = NO; + NSArray *xmlRow = nil; NSArray *fieldNames = nil; NSString *dataConversionString = nil; + + // Result sets + MCPResult *statusResult = nil; + MCPResult *structureResult = nil; MCPStreamingResult *streamingResult = nil; + NSMutableArray *xmlTags = [NSMutableArray array]; NSMutableString *xmlString = [NSMutableString string]; - NSMutableString *xmlItem = [NSMutableString string]; + NSMutableString *xmlItem = [NSMutableString string]; NSUInteger xmlRowCount = 0; NSUInteger i, totalRows, currentRowIndex, lastProgressValue, currentPoolDataLength; @@ -76,7 +86,8 @@ // Check to see if we have at least a table name or data array if ((![self xmlTableName]) && (![self xmlDataArray]) || ([[self xmlTableName] length] == 0) && ([[self xmlDataArray] count] == 0) || - (![self xmlNULLString])) + (([self xmlFormat] == SPXMLExportMySQLFormat) && ((![self xmlOutputIncludeStructure]) && (![self xmlOutputIncludeContent]))) || + (([self xmlFormat] == SPXMLExportPlainFormat) && (![self xmlNULLString]))) { [pool release]; return; @@ -92,67 +103,105 @@ // 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]; + + isTableExport = YES; + + 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]]; - [xmlString appendFormat:@"\t<table_data name=\"%@\">\n\n", [self xmlTableName]]; + // Only include the structure if necessary + if (([self xmlFormat] == SPXMLExportMySQLFormat) && [self xmlOutputIncludeStructure]) { + + structureResult = [connection queryString:[NSString stringWithFormat:@"SHOW COLUMNS FROM %@", [[self xmlTableName] backtickQuotedString]]]; + statusResult = [connection queryString:[NSString stringWithFormat:@"SHOW TABLE STATUS LIKE %@", [[self xmlTableName] tickQuotedString]]]; + + if ([structureResult numOfRows] && [statusResult numOfRows]) { + + [statusResult dataSeek:0]; + [structureResult dataSeek:0]; + + [xmlString appendFormat:@"\t<table_structure name=\"%@\">\n", [self xmlTableName]]; + + for (i = 0; i < [structureResult numOfRows]; i++) + { + NSDictionary *row = [structureResult fetchRowAsDictionary]; + + [xmlString appendFormat:@"\t\t<field field=\"%@\" type=\"%@\" null=\"%@\" key=\"%@\" default=\"%@\" extra=\"%@\" />\n", + [row objectForKey:@"Field"], + [row objectForKey:@"Type"], + [row objectForKey:@"Null"], + [row objectForKey:@"Key"], + [row objectForKey:@"Default"], + [row objectForKey:@"Extra"]]; + } + + NSDictionary *row = [statusResult fetchRowAsDictionary]; + + [xmlString appendFormat:@"\n\t\t<options name=\"%@\" engine=\"%@\" version=\"%@\" row_format=\"%@\" rows=\"%@\" avg_row_length=\"%@\" data_length=\"%@\" max_data_length=\"%@\" index_length=\"%@\" data_free=\"%@\" create_time=\"%@\" update_time=\"%@\" collation=\"%@\" create_options=\"%@\" comment=\"%@\" />\n", + [row objectForKey:@"Name"], + [row objectForKey:@"Engine"], + [row objectForKey:@"Version"], + [row objectForKey:@"Row_format"], + [row objectForKey:@"Rows"], + [row objectForKey:@"Avg_row_length"], + [row objectForKey:@"Data_length"], + [row objectForKey:@"Max_data_length"], + [row objectForKey:@"Index_length"], + [row objectForKey:@"Data_free"], + [row objectForKey:@"Create_time"], + [row objectForKey:@"Update_time"], + [row objectForKey:@"Collation"], + [row objectForKey:@"Create_options"], + [row objectForKey:@"Comment"]]; + + [xmlString appendFormat:@"\t</table_structure>\n\n"]; + } + } + + if (([self xmlFormat] == SPXMLExportMySQLFormat) && [self xmlOutputIncludeContent]) { + [xmlString appendFormat:@"\t<table_data name=\"%@\">\n\n", [self xmlTableName]]; + } + + [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; } else { totalRows = [[self xmlDataArray] count]; } - // Set up an array of encoded field names as opening and closing tags - fieldNames = ([self xmlDataArray]) ? [[self xmlDataArray] objectAtIndex:0] : [streamingResult fetchFieldNames]; + // Only proceed to export the content if this is not a table export or it is and include content is selected + if ((!isTableExport) || (isTableExport && [self xmlOutputIncludeContent])) { - [[self exportOutputFile] writeData:[xmlString 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 - NSAutoreleasePool *xmlExportPool = [[NSAutoreleasePool alloc] init]; - - currentPoolDataLength = 0; - - // Inform the delegate that we are about to start writing the data to disk - [delegate performSelectorOnMainThread:@selector(xmlExportProcessWillBeginWritingData:) withObject:self waitUntilDone:NO]; - - while (1) - { - // Check for cancellation flag - if ([self isCancelled]) { - if (streamingResult) { - [connection cancelCurrentQuery]; - [streamingResult cancelResultLoad]; - } - - [xmlExportPool release]; - [pool release]; + // Set up an array of encoded field names as opening and closing tags + fieldNames = ([self xmlDataArray]) ? NSArrayObjectAtIndex([self xmlDataArray], 0) : [streamingResult fetchFieldNames]; + + for (i = 0; i < [fieldNames count]; i++) + { + [xmlTags addObject:[NSMutableArray array]]; - return; + [NSArrayObjectAtIndex(xmlTags, i) addObject:[NSString stringWithFormat:@"\t\t<%@>", [[NSArrayObjectAtIndex(fieldNames, i) description] HTMLEscapeString]]]; + [NSArrayObjectAtIndex(xmlTags, i) addObject:[NSString stringWithFormat:@"</%@>\n", [[NSArrayObjectAtIndex(fieldNames, i) description] HTMLEscapeString]]]; } - // 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; + // If required, write an opening tag in the form of the table name + if ([self xmlFormat] == SPXMLExportPlainFormat) { + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"\t<%@>\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; } - // Get the cell count if we don't already have it stored - if (!xmlRowCount) xmlRowCount = [xmlRow count]; + // 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 + NSAutoreleasePool *xmlExportPool = [[NSAutoreleasePool alloc] init]; + + currentPoolDataLength = 0; - // Construct the row - [xmlString setString:@"\t<row>\n"]; + // Inform the delegate that we are about to start writing the data to disk + [delegate performSelectorOnMainThread:@selector(xmlExportProcessWillBeginWritingData:) withObject:self waitUntilDone:NO]; - for (i = 0; i < xmlRowCount; i++) + while (1) { // Check for cancellation flag if ([self isCancelled]) { @@ -167,76 +216,126 @@ return; } - BOOL dataIsNULL = NO; - id data = NSArrayObjectAtIndex(xmlRow, i); + // 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; + } - // Retrieve the contents of this tag - if ([data isKindOfClass:[NSData class]]) { - dataConversionString = [[NSString alloc] initWithData:data encoding:[self exportOutputEncoding]]; + // 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; + } - if (dataConversionString == nil) { - dataConversionString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + BOOL dataIsNULL = NO; + id data = NSArrayObjectAtIndex(xmlRow, i); + + // Retrieve the contents of this tag + if ([data isKindOfClass:[NSData class]]) { + dataConversionString = [[NSString alloc] initWithData:data encoding:[self exportOutputEncoding]]; + + if (dataConversionString == nil) { + dataConversionString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; + } + + [xmlItem setString:[NSString stringWithString:dataConversionString]]; + [dataConversionString release]; + } + else if ([data isKindOfClass:[NSNull class]]) { + dataIsNULL = YES; + + if ([self xmlFormat] == SPXMLExportPlainFormat) { + [xmlItem setString:[self xmlNULLString]]; + } + } + else if ([data isKindOfClass:[MCPGeometryData class]]) { + [xmlItem setString:[data wktString]]; + } + else { + [xmlItem setString:[data description]]; } - [xmlItem setString:[NSString stringWithString:dataConversionString]]; - [dataConversionString release]; - } - else if ([data isKindOfClass:[NSNull class]]) { - dataIsNULL = YES; - } - else if ([data isKindOfClass:[MCPGeometryData class]]) { - [xmlItem setString:[data wktString]]; - } - else { - [xmlItem setString:[data description]]; + if ([self xmlFormat] == SPXMLExportMySQLFormat) { + [xmlString appendFormat:@"\t\t<field name=\"%@\"", [[NSArrayObjectAtIndex(fieldNames, i) description] HTMLEscapeString]]; + + if (dataIsNULL) { + [xmlString appendString:@" xsi:nil=\"true\" \\>\n"]; + } + else { + [xmlString appendFormat:@">%@</field>\n", [xmlItem HTMLEscapeString]]; + } + } + else if ([self xmlFormat] == SPXMLExportPlainFormat) { + // 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 appendFormat:@"\t\t<field name=\"%@\"", [[NSArrayObjectAtIndex(fieldNames, i) description] HTMLEscapeString]]; - if (dataIsNULL) { - [xmlString appendString:@" xsi:nil=\"true\" \\>\n"]; - } - else { - [xmlString appendFormat:@">%@</field>\n", [xmlItem HTMLEscapeString]]; - } - } - - [xmlString appendString:@"\t</row>\n\n"]; + [xmlString appendString:@"\t</row>\n\n"]; + + // Record the total length for use with pool flushing + currentPoolDataLength += [xmlString length]; + + // Write the row to the filehandle + [[self exportOutputFile] writeData:[xmlString dataUsingEncoding:[self exportOutputEncoding]]]; + + // Update the progress counter and progress bar + currentRowIndex++; + + // Update the progress + if (totalRows && (currentRowIndex * ([self exportMaxProgress] / totalRows)) > lastProgressValue) { - // Record the total length for use with pool flushing - currentPoolDataLength += [xmlString length]; - - // Write the row to the filehandle - [[self exportOutputFile] 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; + } - NSInteger progress = (currentRowIndex * ([self exportMaxProgress] / totalRows)); + // Inform the delegate that the export's progress has been updated + [delegate performSelectorOnMainThread:@selector(xmlExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; - [self setExportProgressValue:progress]; - - lastProgressValue = progress; + // Drain the autorelease pool as required to keep memory usage low + if (currentPoolDataLength > 250000) { + [xmlExportPool release]; + xmlExportPool = [[NSAutoreleasePool alloc] init]; + } + + // If an array was supplied and we've processed all rows, break + if ([self xmlDataArray] && totalRows == currentRowIndex) break; } - // Inform the delegate that the export's progress has been updated - [delegate performSelectorOnMainThread:@selector(xmlExportProcessProgressUpdated:) withObject:self waitUntilDone:NO]; - - // Drain the autorelease pool as required to keep memory usage low - if (currentPoolDataLength > 250000) { - [xmlExportPool release]; - xmlExportPool = [[NSAutoreleasePool alloc] init]; + if (([self xmlFormat] == SPXMLExportMySQLFormat) && isTableExport) { + [[self exportOutputFile] writeData:[@"\t</table_data>\n\n" dataUsingEncoding:[self exportOutputEncoding]]]; + } + else if ([self xmlFormat] == SPXMLExportPlainFormat) { + [[self exportOutputFile] writeData:[[NSString stringWithFormat:@"\t</%@>\n\n", ([self xmlTableName]) ? [[self xmlTableName] HTMLEscapeString] : @"custom"] dataUsingEncoding:[self exportOutputEncoding]]]; } - // If an array was supplied and we've processed all rows, break - if ([self xmlDataArray] && totalRows == currentRowIndex) break; - } - - if ((![self xmlDataArray]) && [self xmlTableName]) { - [[self exportOutputFile] writeData:[@"\t</table_data>\n\n" dataUsingEncoding:[self exportOutputEncoding]]]; + [xmlExportPool release]; } // Write data to disk @@ -248,7 +347,6 @@ // Inform the delegate that the export process is complete [delegate performSelectorOnMainThread:@selector(xmlExportProcessComplete:) withObject:self waitUntilDone:NO]; - [xmlExportPool release]; [pool release]; } @@ -259,8 +357,7 @@ { if (xmlDataArray) [xmlDataArray release], xmlDataArray = nil; if (xmlTableName) [xmlTableName release], xmlTableName = nil; - - [xmlNULLString release], xmlNULLString = nil; + if (xmlNULLString) [xmlNULLString release], xmlNULLString = nil; [super dealloc]; } |