Skip to content

Instantly share code, notes, and snippets.

@0xced
Created June 18, 2014 22:17
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xced/5c36d5d008054c47ede5 to your computer and use it in GitHub Desktop.
Save 0xced/5c36d5d008054c47ede5 to your computer and use it in GitHub Desktop.
Uncruft iOS 8.0 and OS X 10.10 API diffs
//
// Copyright (c) 2014 Cédric Luthi “0xced”
//
// ./uncruftapidiff https://developer.apple.com/library/prerelease/ios/releasenotes/General/iOS80APIDiffs/index.html iOS8_0APIDiffs.html
// ./uncruftapidiff https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/index.html OSX10_10APIDiffs.html
//
#import <Foundation/Foundation.h>
#import <sysexits.h>
void TerminateIf(BOOL condition, int exitCode, NSString *message)
{
if (condition)
{
fprintf(stderr, "%s\n", [message.description UTF8String]);
exit(exitCode);
}
}
@interface NSXMLNode (removeFromParent)
- (void) removeFromParent;
@end
@implementation NSXMLNode (removeFromParent)
- (void) removeFromParent
{
NSUInteger childIndex = [self.parent.children indexOfObjectIdenticalTo:self];
if ([self.parent isKindOfClass:[NSXMLElement class]] && childIndex != NSNotFound)
[(NSXMLElement *)self.parent removeChildAtIndex:childIndex];
else
TerminateIf(YES, EX_SOFTWARE, @"Trying to remove a non element node");
}
@end
void RemoveNodesMatchingXPath(NSXMLDocument *document, NSString *xpath)
{
NSError *error;
NSArray *nodes = [document nodesForXPath:xpath error:&error];
TerminateIf(nodes == nil, EX_SOFTWARE, error.description);
for (NSXMLNode *node in nodes)
[node removeFromParent];
}
void RemoveMethodToProperties(NSXMLDocument *document)
{
NSArray *addedElements = [document nodesForXPath:@"//div[@class='headerFile']/div[@class='added']/div[@class='symbolName']" error:NULL];
for (NSXMLElement *addedElement in addedElements)
{
BOOL removed = NO;
NSString *addedTitle = [[[addedElement attributeForName:@"title"] stringValue] lowercaseString];
if ([addedTitle hasPrefix:@"//apple_ref/occ/intfp"] || [addedTitle hasPrefix:@"//apple_ref/occ/instp"])
{
NSArray *removedElements = [addedElement.parent.parent nodesForXPath:@"div[@class='removed']/div[@class='symbolName']" error:NULL];
for (NSXMLElement *removedElement in removedElements)
{
NSString *removedTitle = [[[removedElement attributeForName:@"title"] stringValue] lowercaseString];
for (NSString *inx in @[ @"/intf", @"/inst" ])
{
NSString *p = [inx stringByAppendingString:@"p/"];
NSString *m = [inx stringByAppendingString:@"m/"];
NSString *getter = [addedTitle stringByReplacingOccurrencesOfString:p withString:m];
NSString *isgetter = [NSString stringWithFormat:@"/%@/is%@", [getter stringByDeletingLastPathComponent], [getter lastPathComponent]];
NSString *setter = [NSString stringWithFormat:@"/%@/set%@:", [getter stringByDeletingLastPathComponent], [getter lastPathComponent]];
if ([getter isEqualToString:removedTitle] || [isgetter isEqualToString:removedTitle] || [setter isEqualToString:removedTitle])
{
[removedElement removeFromParent];
removed = YES;
}
}
}
}
if (removed)
[addedElement removeFromParent];
}
}
NSXMLElement *ElementWithAttributes(NSString *elementName, NSDictionary *attributes)
{
NSXMLElement *element = [[NSXMLElement alloc] initWithName:elementName];
[attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSXMLNode *attribute = [[NSXMLNode alloc] initWithKind:NSXMLAttributeKind];
attribute.name = key;
attribute.stringValue = obj;
[element addAttribute:attribute];
}];
return element;
}
void GroupNodesInElementWithAttributes(NSXMLDocument *document, NSString *xpath, NSString *elementName, NSDictionary *attributes)
{
NSError *error;
NSArray *nodes = [document nodesForXPath:xpath error:&error];
TerminateIf(nodes == nil, EX_SOFTWARE, error.description);
NSXMLElement *parent;
NSXMLElement *element = ElementWithAttributes(elementName, attributes);
NSUInteger idx = NSNotFound;
for (NSXMLNode *node in nodes)
{
if (idx == NSNotFound)
{
parent = (NSXMLElement *)node.parent;
idx = [parent.children indexOfObjectIdenticalTo:node];
}
[node removeFromParent];
[element addChild:node];
}
[parent insertChild:element atIndex:idx];
}
int main(int argc, const char * argv[])
{
@autoreleasepool
{
TerminateIf(argc != 3, EX_USAGE, [NSString stringWithFormat:@"Usage: %@ input output", [@(argv[0]) lastPathComponent]]);
NSString *input = @(argv[1]);
NSURL *inputURL = [input hasPrefix:@"http"] ? [NSURL URLWithString:input] : [NSURL fileURLWithPath:input];
NSString *output = @(argv[2]);
NSURL *outputURL = [NSURL fileURLWithPath:output];
NSError *error;
NSData *inputData = [NSData dataWithContentsOfURL:inputURL options:NSDataReadingMapped error:&error];
TerminateIf(inputData == nil, EX_NOINPUT, error.description);
NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:inputData options:NSXMLDocumentTidyHTML error:&error];
TerminateIf(document == nil, EX_DATAERR, error.description);
RemoveNodesMatchingXPath(document, @"//div[@class='modified']");
RemoveNodesMatchingXPath(document, @"//div[@class='headerName']/(span[@class='added']|span[@class='removed'])/span[@class='diffStatus']");
RemoveMethodToProperties(document);
RemoveNodesMatchingXPath(document, @"//div[@class='added' and count(*)=0]");
RemoveNodesMatchingXPath(document, @"//div[@class='removed' and count(*)=0]");
RemoveNodesMatchingXPath(document, @"//div[@class='headerFile' and count(div)=1]");
NSArray *emptyFrameworkNodes = [document nodesForXPath:@"//h2[following-sibling::*[1][self::h2]][not(span)]" error:&error];
TerminateIf(emptyFrameworkNodes == nil, EX_SOFTWARE, error.description);
for (NSXMLElement *emptyFrameworkNode in emptyFrameworkNodes)
{
NSXMLElement *div = ElementWithAttributes(@"div", @{ @"class": @"nochange" });
div.stringValue = @"No changes";
NSUInteger idx = [emptyFrameworkNode.parent.children indexOfObjectIdenticalTo:emptyFrameworkNode];
[(NSXMLElement *)emptyFrameworkNode.parent insertChild:div atIndex:idx + 1];
}
// `header`, `nav` and `article` elements are stripped during tidying
GroupNodesInElementWithAttributes(document, @"//(div[@id='title']|ul[@id='headerButtons'])", @"header", @{ @"id": @"header" });
GroupNodesInElementWithAttributes(document, @"//ul[@id='toc']", @"nav", @{ @"id": @"tocContainer", @"tabindex": @"7" });
GroupNodesInElementWithAttributes(document, @"//nav/following-sibling::div", @"article", @{ @"id": @"contents" });
NSXMLElement *head = [[document nodesForXPath:@"//head" error:NULL] firstObject];
[head insertChild:ElementWithAttributes(@"meta", @{ @"http-equiv": @"Content-Type", @"content": @"text/html; charset=UTF-8" }) atIndex:0];
if (![inputURL isFileURL])
[head insertChild:ElementWithAttributes(@"base", @{ @"href": [[inputURL URLByDeletingLastPathComponent] absoluteString] }) atIndex:0];
NSData *outputData = [document XMLDataWithOptions:NSXMLDocumentIncludeContentTypeDeclaration];
BOOL success = [outputData writeToURL:outputURL options:NSDataWritingAtomic error:&error];
TerminateIf(!success, EX_CANTCREAT, error.description);
}
return EX_OK;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment