Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

640 lines (548 sloc) 24.717 kB
//
// XMLBasedOutputGenerator.m
// appledoc
//
// Created by Tomaz Kragelj on 28.5.09.
// Copyright (C) 2009, Tomaz Kragelj. All rights reserved.
//
#import "OutputGenerator.h"
#import "XMLBasedOutputGenerator+GeneralParsingAPI.h"
#import "XMLBasedOutputGenerator+ObjectParsingAPI.h"
#import "XMLBasedOutputGenerator+ObjectSubclassAPI.h"
#import "XMLBasedOutputGenerator+IndexParsingAPI.h"
#import "XMLBasedOutputGenerator+IndexSubclassAPI.h"
#import "XMLBasedOutputGenerator+HierarchyParsingAPI.h"
#import "XMLBasedOutputGenerator+HierarchySubclassAPI.h"
#import "CommandLineParser.h"
#import "LoggingProvider.h"
#import "Systemator.h"
#import "Constants.h"
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
/** Defines private methods for use within @c XMLBasedOutputGenerator class only.
These are helper methods to make the main @c generateOutput() less cluttered. The methods
should only be used internaly by the @c XMLBasedOutputGenerator, they are not intended to
be used by the subclasses. Therefore the parameters are closely coupled to the underlying
clean object markup which is XML.
*/
@interface XMLBasedOutputGenerator ()
//////////////////////////////////////////////////////////////////////////////////////////
/// @name Object generation helpers
//////////////////////////////////////////////////////////////////////////////////////////
/** Generates the object info section if necessary.
From here the following messages are sent to the subclass:
- @c appendObjectInfoHeaderToData:()
- @c appendObjectInfoFooterToData:()
@param data The @c NSMutableData to append to.
@exception NSException Thrown if generation fails.
@see generateOutputForObject
@see generateOverviewSectionToData:
@see generateTasksSectionToData:
@see generateObjectMembersSectionToData:
@see generateObjectInfoSectionToData:fromNodes:index:type:
*/
- (void) generateObjectInfoSectionToData:(NSMutableData*) data;
/** Generates the given object info section if necessary.
This is sent from @c generateObjectInfoSectionToData:() for each info section item. From here
the following message is sent to the subclass.
- @c appendObjectInfoItemToData:fromItems:index:type:()
The message is only send if the given @c nodes array is not empty. The @c type parameter
can be one of the following:
- @c kTKObjectInfoItemInherits: The @c nodes contain inherit from information.
- @c kTKObjectInfoItemConforms: The @c nodes contain conforms to information.
- @c kTKObjectInfoItemDeclared: The @c nodex contain declared in information.
@param data The @c NSMutableData to append to.
@param nodes The array of @c NSXMLElement instances to append to.
@param index Pointer to zero based index of the section item. The method will increment
if the given @c nodes is not empty.
@param type Type of the section item.
@exception NSException Thrown if generation fails.
@see generateObjectInfoSectionToData:
*/
- (void) generateObjectInfoSectionToData:(NSMutableData*) data
fromNodes:(NSArray*) nodes
index:(int*) index
type:(int) type;
/** Generates the object overview data if necessary.
This is where the following messages are sent to the subclass:
- @c appendObjectOverviewToData:fromItem:()
@param data The @c NSMutableData to append to.
@exception NSException Thrown if generation fails.
@see generateOutputForObject
@see generateObjectInfoSectionToData:
@see generateTasksSectionToData:
@see generateObjectMembersSectionToData:
*/
- (void) generateObjectOverviewSectionToData:(NSMutableData*) data;
/** Generates the tasks section data if necessary.
This is where the following messages are sent to the subclass:
- @c appendObjectTasksHeaderToData:()
- @c appendObjectTaskHeaderToData:fromItem:index:()
- @c appendObjectTaskMemberToData:fromItem:index:()
- @c appendObjectTaskFooterToData:fromItem:index:()
- @c appendObjectTasksFooterToData:()
@param data The @c NSMutableData to append to.
@exception NSException Thrown if generation fails.
@see generateOutputForObject
@see generateObjectInfoSectionToData:
@see generateOverviewSectionToData:
@see generateObjectMembersSectionToData:
*/
- (void) generateObjectTasksSectionToData:(NSMutableData*) data;
/** Generates the main members documentation section if necessary.
This is where the following messages are sent to the subclass:
- @c appendObjectMembersHeaderToData:()
- @c appendObjectMembersFooterToData:()
@param data The @c NSMutableData to append to.
@exception NSException Thrown if generation fails.
@see generateOutputForObject
@see generateObjectInfoSectionToData:
@see generateOverviewSectionToData:
@see generateTasksSectionToData:
@see generateMemberSectionToData:fromItems:type:
*/
- (void) generateObjectMembersSectionToData:(NSMutableData*) data;
/** Generates the given main members documentation section.
This is sent from @c generateObjectMembersSectionToData:() for each group of members that
has at least one documented entry. This is where the following messages are sent to the
subclass:
- @c appendIndexGroupHeaderToData:type:()
- @c appendIndexGroupItemToData:fromItem:index:type:()
- @c appendIndexGroupFooterToData:type:()
The @c type parameter can be one of the following:
- @c kTKIndexGroupClasses: This group will append all classes.
- @c kTKIndexGroupProtocols: This group will append all protocols.
- @c kTKIndexGroupCategories: This group will append all categories.
@param data The @c NSMutableData to append to.
@param nodes The array of @c NSXMLElement instances representing individual members.
@param type The type of the instances.
@exception NSException Thrown if generation fails.
@see generateObjectMembersSectionToData:
*/
- (void) generateObjectMemberSectionToData:(NSMutableData*) data
fromNodes:(NSArray*) nodes
type:(int) type;
//////////////////////////////////////////////////////////////////////////////////////////
/// @name Index generation helpers
//////////////////////////////////////////////////////////////////////////////////////////
/** Generates the index groups documentation sections.
This is sent from @c generateIndexGroupSectionsToData:(). It collects the group data
and then sends @c generateIndexGroupSectionToData:fromNodes:type:() for each detected
group.
@param data The @c NSMutableData to append to.
@exception NSException Thrown if generation fails.
@see generateOutputForIndex
@see generateIndexGroupSectionToData:fromNodes:type:
*/
- (void) generateIndexGroupSectionsToData:(NSMutableData*) data;
/** Generates the given main members documentation section.
This is sent from @c generateIndexGroupSectionsToData:() for each group that has at
least one member. This is where the following messages are sent to the subclass:
- @c appendIndexGroupHeaderToData:type:()
- @c appendIndexGroupItemToData:fromItem:index:type:()
- @c appendIndexGroupFooterToData:type:()
The @c type parameter can be one of the following:
- @c kTKObjectMemberTypeClass: The @c nodes describes class members.
- @c kTKObjectMemberTypeInstance: The @c nodes describes instance members.
- @c kTKObjectMemberTypeProperty: The @c nodes describes properties.
@param data The @c NSMutableData to append to.
@param nodes The array of @c NSXMLElement instances representing individual members.
@param type The type of the instances.
@exception NSException Thrown if generation fails.
@see generateObjectMembersSectionToData:
*/
- (void) generateIndexGroupSectionToData:(NSMutableData*) data
fromNodes:(NSArray*) nodes
type:(int) type;
//////////////////////////////////////////////////////////////////////////////////////////
/// @name Various helper methods
//////////////////////////////////////////////////////////////////////////////////////////
/** Converts all common placeholders in the given clean XML.
The method eventually returns new @c NSXMLDocument instance which can be used for
output generation.
@param document The original clean XML.
@return Returns new autoreleased @c NSXMLDocument with all common placeholders replaced.
@exception NSException Thrown if convertion fails.
*/
- (NSXMLDocument*) markupByConvertingPlaceholdersInTemplate:(NSXMLDocument*) document;
@end
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
//////////////////////////////////////////////////////////////////////////////////////////
@implementation XMLBasedOutputGenerator
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Output generation entry points
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (void) outputGenerationStarting
{
outputFileWasCreated = NO;
}
//----------------------------------------------------------------------------------------
- (void) generateSpecificOutput
{
NSAutoreleasePool* loopAutoreleasePool = nil;
@try
{
// Convert the objects markup to final product.
NSDictionary* objects = [database objectForKey:kTKDataMainObjectsKey];
for (NSString* objectName in objects)
{
[loopAutoreleasePool drain];
loopAutoreleasePool = [[NSAutoreleasePool alloc] init];
NSDictionary* data = [objects objectForKey:objectName];
[self generateOutputForObject:data];
}
// Convert the index and hierarchy markup to final product.
[self generateOutputForIndex];
[self generateOutputForHierarchy];
}
@finally
{
[loopAutoreleasePool drain];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Objects output generation
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (void) generateOutputForObject:(NSDictionary*) data
{
NSParameterAssert(data != nil);
@try
{
// Assign the parameters and replace all placeholder templates before starting
// parsing. Note that this will generate a new NSXMLDocument which we retain
// through the parsing loop and release at the end.
objectData = data;
objectMarkup = [objectData objectForKey:kTKDataObjectMarkupKey];
objectMarkup = [[self markupByConvertingPlaceholdersInTemplate:objectMarkup] retain];
// Generate the data.
logVerbose(@"- Generating output for object '%@'...", self.objectName);
NSMutableData* result = [NSMutableData data];
[self appendObjectHeaderToData:result];
[self generateObjectInfoSectionToData:result];
[self generateObjectOverviewSectionToData:result];
[self generateObjectTasksSectionToData:result];
[self generateObjectMembersSectionToData:result];
[self appendObjectFooterToData:result];
// Save the data.
if (result && [result length] > 0)
{
NSString* relativePath = [objectData objectForKey:kTKDataObjectRelPathKey];
NSString* filename = [[self outputBasePath] stringByAppendingPathComponent:relativePath];
NSString* extension = [self outputFilesExtension];
filename = [filename stringByReplacingOccurrencesOfString:kTKPlaceholderExtension withString:extension];
logDebug(@" - Saving object output to '%@'...", filename);
if (![result writeToFile:filename atomically:NO])
{
NSString* message = [NSString stringWithFormat:@"Failed saving object output to '%@'!", filename];
logError(@"Failed saving clean object output!");
[Systemator throwExceptionWithName:kTKConverterException withDescription:message];
}
outputFileWasCreated = YES;
}
}
@finally
{
[objectMarkup release];
objectData = nil;
}
}
//----------------------------------------------------------------------------------------
- (void) generateObjectInfoSectionToData:(NSMutableData*) data
{
// Get all nodes that describe the object information.
NSArray* baseNodes = [self.objectMarkup nodesForXPath:@"object/base" error:nil];
NSArray* protocolNodes = [self.objectMarkup nodesForXPath:@"object/protocol" error:nil];
NSArray* fileNodes = [self.objectMarkup nodesForXPath:@"object/file" error:nil];
// If at least one is present, continue.
if ([baseNodes count] > 0 || [protocolNodes count] > 0 || [fileNodes count] > 0)
{
int index = 0;
[self appendObjectInfoHeaderToData:data];
[self generateObjectInfoSectionToData:data fromNodes:baseNodes index:&index type:kTKObjectInfoItemInherits];
[self generateObjectInfoSectionToData:data fromNodes:protocolNodes index:&index type:kTKObjectInfoItemConforms];
[self generateObjectInfoSectionToData:data fromNodes:fileNodes index:&index type:kTKObjectInfoItemDeclared];
[self appendObjectInfoFooterToData:data];
}
}
//----------------------------------------------------------------------------------------
- (void) generateObjectInfoSectionToData:(NSMutableData*) data
fromNodes:(NSArray*) items
index:(int*) index
type:(int) type;
{
if ([items count] > 0)
{
[self appendObjectInfoItemToData:data fromItems:items index:*index type:type];
(*index)++;
}
}
//----------------------------------------------------------------------------------------
- (void) generateObjectOverviewSectionToData:(NSMutableData*) data
{
// Append the object overview if the object is documented. Note that we only take
// the first description node if there are more (shouldn't happen, but just in case).
NSArray* overviewNodes = [self.objectMarkup nodesForXPath:@"object/description" error:nil];
if ([overviewNodes count] > 0)
{
NSXMLElement* overviewNode = [overviewNodes objectAtIndex:0];
[self appendObjectOverviewToData:data fromItem:overviewNode];
}
}
//----------------------------------------------------------------------------------------
- (void) generateObjectTasksSectionToData:(NSMutableData*) data
{
// Appends the tasks with short method descriptions if at least one section is
// found with at least one member. Note that the sections are only handled if there's
// at least one member, otherwise these messages are not sent, even if (empty)
// sections are encountered.
NSArray* sectionNodes = [self.objectMarkup nodesForXPath:@"object/sections/section" error:nil];
if ([sectionNodes count] > 0)
{
BOOL sectionsHandled = NO;
for (int i = 0; i < [sectionNodes count]; i++)
{
NSXMLElement* sectionNode = [sectionNodes objectAtIndex:i];
NSArray* memberNodes = [sectionNode nodesForXPath:@"member" error:nil];
if ([memberNodes count] > 0)
{
// Append the sections header if not yet.
if (!sectionsHandled)
{
[self appendObjectTasksHeaderToData:data];
sectionsHandled = YES;
}
// Append section header.
[self appendObjectTaskHeaderToData:data fromItem:sectionNode index:i];
// Process all section members.
for (int n = 0; n < [memberNodes count]; n++)
{
NSXMLElement* memberNode = [memberNodes objectAtIndex:n];
[self appendObjectTaskMemberToData:data fromItem:memberNode index:n];
}
// Append section footer.
[self appendObjectTaskFooterToData:data fromItem:sectionNode index:i];
}
}
// Append sections footer.
if (sectionsHandled) [self appendObjectTasksFooterToData:data];
}
}
//----------------------------------------------------------------------------------------
- (void) generateObjectMembersSectionToData:(NSMutableData*) data
{
// Get the lists of all member nodes for each member type.
NSArray* classMethodNodes = [self.objectMarkup
nodesForXPath:@"object/sections/section/member[@kind='class-method']"
error:nil];
NSArray* instanceMethodNodes = [self.objectMarkup
nodesForXPath:@"object/sections/section/member[@kind='instance-method']"
error:nil];
NSArray* propertyNodes = [self.objectMarkup
nodesForXPath:@"object/sections/section/member[@kind='property']"
error:nil];
// Append the main members documentation descriptions if at least one member group
// is found with at least one documented member.
if ([classMethodNodes count] > 0 ||
[instanceMethodNodes count] > 0 ||
[propertyNodes count] > 0)
{
// Ask the subclass to append members documentation header.
[self appendObjectMembersHeaderToData:data];
// Process all lists.
[self generateObjectMemberSectionToData:data fromNodes:classMethodNodes type:kTKObjectMemberTypeClass];
[self generateObjectMemberSectionToData:data fromNodes:instanceMethodNodes type:kTKObjectMemberTypeInstance];
[self generateObjectMemberSectionToData:data fromNodes:propertyNodes type:kTKObjectMemberTypeProperty];
// Ask the subclass to append members documentation footer.
[self appendObjectMembersFooterToData:data];
}
}
//----------------------------------------------------------------------------------------
- (void) generateObjectMemberSectionToData:(NSMutableData*) data
fromNodes:(NSArray*) nodes
type:(int) type
{
if ([nodes count] > 0)
{
// Ask the subclass to append members group header.
[self appendObjectMemberGroupHeaderToData:data type:type];
// Ask the subclass to document all members of this group.
for (int i = 0; i < [nodes count]; i++)
{
NSXMLElement* node = [nodes objectAtIndex:i];
[self appendObjectMemberToData:data fromItem:node index:i];
}
// Ask the subclass to append members group footer.
[self appendObjectMemberGroupFooterToData:data type:type];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Index output generation handling
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (void) generateOutputForIndex
{
@try
{
// Assign the parameters and replace all placeholder templates.
indexMarkup = [database objectForKey:kTKDataMainIndexKey];
indexMarkup = [[self markupByConvertingPlaceholdersInTemplate:indexMarkup] retain];
// Generate the data.
logVerbose(@"- Generating output for index...");
NSMutableData* result = [NSMutableData data];
[self appendIndexHeaderToData:result];
[self generateIndexGroupSectionsToData:result];
[self appendIndexFooterToData:result];
// Save the data.
if (result && [result length] > 0)
{
NSString* filename = [[self outputBasePath] stringByAppendingPathComponent:@"index"];
filename = [filename stringByAppendingString:[self outputFilesExtension]];
logDebug(@" - Saving index output to '%@'...", filename);
if (![result writeToFile:filename atomically:NO])
{
NSString* message = [NSString stringWithFormat:@"Failed saving index output to '%@'!", filename];
logError(@"Failed saving clean index output!");
[Systemator throwExceptionWithName:kTKConverterException withDescription:message];
}
outputFileWasCreated = YES;
}
}
@finally
{
[indexMarkup release];
}
}
//----------------------------------------------------------------------------------------
- (void) generateIndexGroupSectionsToData:(NSMutableData*) data
{
NSArray* classNodes = [self.indexMarkup nodesForXPath:@"project/object[@kind='class']" error:nil];
NSArray* categoryNodes = [self.indexMarkup nodesForXPath:@"project/object[@kind='category']" error:nil];
NSArray* protocolNodes = [self.indexMarkup nodesForXPath:@"project/object[@kind='protocol']" error:nil];
[self generateIndexGroupSectionToData:data
fromNodes:classNodes
type:kTKIndexGroupClasses];
[self generateIndexGroupSectionToData:data
fromNodes:protocolNodes
type:kTKIndexGroupProtocols];
[self generateIndexGroupSectionToData:data
fromNodes:categoryNodes
type:kTKIndexGroupCategories];
}
//----------------------------------------------------------------------------------------
- (void) generateIndexGroupSectionToData:(NSMutableData*) data
fromNodes:(NSArray*) nodes
type:(int) type
{
if ([nodes count] > 0)
{
[self appendIndexGroupHeaderToData:data type:type];
for (int i = 0; i < [nodes count]; i++)
{
NSXMLElement* node = [nodes objectAtIndex:i];
[self appendIndexGroupItemToData:data
fromItem:node
index:i
type:type];
}
[self appendIndexGroupFooterToData:data type:type];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Hierarchy output generation handling
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (void) generateOutputForHierarchy
{
@try
{
// Assign the parameters and replace all placeholder templates.
hierarchyMarkup = [database objectForKey:kTKDataMainHierarchyKey];
hierarchyMarkup = [[self markupByConvertingPlaceholdersInTemplate:hierarchyMarkup] retain];
// Generate the data. Note that we could use the hierarchies data directly
// instead of relying on the generated XML and it would probably be more optimized
// too. However using XML ensures consistent parsing code with the rest of the
// generation methods and perhaps even more important - it uses the same data
// that external utilities might use if they are only interested in generating
// the XML from appledoc.
logVerbose(@"- Generating output for hierarchy...");
NSMutableData* result = [NSMutableData data];
NSArray* rootNodes = [self.hierarchyMarkup nodesForXPath:@"project/object" error:nil];
if ([rootNodes count] > 0)
{
// Generate the data.
[self appendHierarchyHeaderToData:result];
[self appendHierarchyGroupHeaderToData:result];
for (int i = 0; i < [rootNodes count]; i++)
{
NSXMLElement* rootNode = [rootNodes objectAtIndex:i];
[self appendHierarchyGroupItemToData:result
fromItem:rootNode
index:i];
}
[self appendHierarchyGroupFooterToData:result];
[self appendHierarchyFooterToData:result];
// Save the data.
if (result && [result length] > 0)
{
NSString* filename = [[self outputBasePath] stringByAppendingPathComponent:@"hierarchy"];
filename = [filename stringByAppendingString:[self outputFilesExtension]];
logDebug(@" - Saving hierarchy output to '%@'...", filename);
if (![result writeToFile:filename atomically:NO])
{
NSString* message = [NSString stringWithFormat:@"Failed saving hierarchy output to '%@'!", filename];
logError(@"Failed saving clean hierarchy output!");
[Systemator throwExceptionWithName:kTKConverterException withDescription:message];
}
outputFileWasCreated = YES;
}
}
}
@finally
{
[hierarchyMarkup release];
}
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Common helper methods
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (NSXMLDocument*) markupByConvertingPlaceholdersInTemplate:(NSXMLDocument*) document
{
// The handling is simple, but resource intensive - convert the XML to string,
// replace all placeholder occurences and re-create the new document from the
// resulting string.
NSString* string = [document XMLString];
string = [self pathByReplacingTemplatePlaceholdersInPath:string];
// Create the new XMl document.
NSError* error = nil;
NSXMLDocument* result = [[[NSXMLDocument alloc] initWithXMLString:string
options:0
error:&error] autorelease];
if (error)
{
logError(@"Converting placeholders XML failed!");
[Systemator throwExceptionWithName:kTKConverterException basedOnError:error];
}
// Return the new document.
return result;
}
//////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Properties
//////////////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------------------
- (NSString*) projectName
{
return cmd.projectName;
}
//----------------------------------------------------------------------------------------
- (NSString*) lastUpdated
{
NSCalendarDate* now = [NSCalendarDate date];
return [now descriptionWithCalendarFormat:cmd.dateTimeTemplate];
}
//----------------------------------------------------------------------------------------
@synthesize outputFileWasCreated;
@end
Jump to Line
Something went wrong with that request. Please try again.