Created
June 18, 2014 22:17
-
-
Save 0xced/5c36d5d008054c47ede5 to your computer and use it in GitHub Desktop.
Uncruft iOS 8.0 and OS X 10.10 API diffs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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