From 0910290ef2478f46a2e90ad0d6e99f517cd101bb Mon Sep 17 00:00:00 2001 From: iska Date: Sat, 25 Oct 2014 17:35:58 +0200 Subject: [PATCH] Add generic HTML5LibTest class for performing HTML5lib tests https://github.com/html5lib/html5lib-tests --- HTMLKit.xcodeproj/project.pbxproj | 18 ++++ HTMLKit/HTMLTokens.h | 15 ++++ HTMLKitTests/HTML5LibTest.h | 22 +++++ HTMLKitTests/HTML5LibTest.m | 138 ++++++++++++++++++++++++++++++ HTMLKitTests/HTMLKitTests.m | 38 ++++++++ HTMLKitTests/HTMLTokenizerTests.m | 41 +++++++++ 6 files changed, 272 insertions(+) create mode 100644 HTMLKit/HTMLTokens.h create mode 100644 HTMLKitTests/HTML5LibTest.h create mode 100644 HTMLKitTests/HTML5LibTest.m create mode 100644 HTMLKitTests/HTMLTokenizerTests.m diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj index 89003b6..a259468 100644 --- a/HTMLKit.xcodeproj/project.pbxproj +++ b/HTMLKit.xcodeproj/project.pbxproj @@ -13,6 +13,9 @@ 624493AC19CD0CBE00BCDDF4 /* HTMLToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 624493AA19CD0CBE00BCDDF4 /* HTMLToken.h */; }; 624493AD19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 624493AB19CD0CBE00BCDDF4 /* HTMLToken.m */; }; 624493AE19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 624493AB19CD0CBE00BCDDF4 /* HTMLToken.m */; }; + 624AC8FF19FBF59800BD3C4A /* HTMLTokens.h in Headers */ = {isa = PBXBuildFile; fileRef = 624AC8FE19FBF4F700BD3C4A /* HTMLTokens.h */; }; + 624AC90119FBF9ED00BD3C4A /* HTMLTokenizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624AC90019FBF9ED00BD3C4A /* HTMLTokenizerTests.m */; }; + 624AC90219FBF9ED00BD3C4A /* HTMLTokenizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624AC90019FBF9ED00BD3C4A /* HTMLTokenizerTests.m */; }; 625A14B019C7829400AD0C32 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 625A14AF19C7829400AD0C32 /* Cocoa.framework */; }; 625A14BA19C7829400AD0C32 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 625A14B819C7829400AD0C32 /* InfoPlist.strings */; }; 625A14BE19C7829400AD0C32 /* HTMLKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 625A14BD19C7829400AD0C32 /* HTMLKit.m */; }; @@ -56,6 +59,8 @@ 62AE594E19F9A17A0043F069 /* HTMLEOFToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 62AE594C19F9A17A0043F069 /* HTMLEOFToken.h */; }; 62AE594F19F9A17A0043F069 /* HTMLEOFToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 62AE594D19F9A17A0043F069 /* HTMLEOFToken.m */; }; 62AE595019F9A17A0043F069 /* HTMLEOFToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 62AE594D19F9A17A0043F069 /* HTMLEOFToken.m */; }; + 62D8345919FB1AC4009205A9 /* HTML5LibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 62D8345819FB1AC4009205A9 /* HTML5LibTest.m */; }; + 62D8345A19FB1AC4009205A9 /* HTML5LibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 62D8345819FB1AC4009205A9 /* HTML5LibTest.m */; }; 62F31FDD19E9DCCF007F0657 /* HTMLTokenizerEntities.h in Headers */ = {isa = PBXBuildFile; fileRef = 62F31FDB19E9DCCF007F0657 /* HTMLTokenizerEntities.h */; }; 62F31FDE19E9DCCF007F0657 /* HTMLTokenizerEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F31FDC19E9DCCF007F0657 /* HTMLTokenizerEntities.m */; }; 62F31FDF19E9DCCF007F0657 /* HTMLTokenizerEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F31FDC19E9DCCF007F0657 /* HTMLTokenizerEntities.m */; }; @@ -100,6 +105,8 @@ 624493A919CCE84A00BCDDF4 /* HTMLTokenizerStates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizerStates.h; sourceTree = ""; }; 624493AA19CD0CBE00BCDDF4 /* HTMLToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLToken.h; sourceTree = ""; }; 624493AB19CD0CBE00BCDDF4 /* HTMLToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLToken.m; sourceTree = ""; }; + 624AC8FE19FBF4F700BD3C4A /* HTMLTokens.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLTokens.h; sourceTree = ""; }; + 624AC90019FBF9ED00BD3C4A /* HTMLTokenizerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTokenizerTests.m; sourceTree = ""; }; 625A14AC19C7829400AD0C32 /* HTMLKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HTMLKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 625A14AF19C7829400AD0C32 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 625A14B219C7829400AD0C32 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -137,6 +144,8 @@ 62AE594819F9948A0043F069 /* HTMLCharacterToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLCharacterToken.m; sourceTree = ""; }; 62AE594C19F9A17A0043F069 /* HTMLEOFToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLEOFToken.h; sourceTree = ""; }; 62AE594D19F9A17A0043F069 /* HTMLEOFToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLEOFToken.m; sourceTree = ""; }; + 62D8345719FB1AC4009205A9 /* HTML5LibTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTML5LibTest.h; sourceTree = ""; }; + 62D8345819FB1AC4009205A9 /* HTML5LibTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTML5LibTest.m; sourceTree = ""; }; 62E7CAAE19CDFFB500465A83 /* HTMLTokenizerCharacters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizerCharacters.h; sourceTree = ""; }; 62F31FDB19E9DCCF007F0657 /* HTMLTokenizerEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizerEntities.h; sourceTree = ""; }; 62F31FDC19E9DCCF007F0657 /* HTMLTokenizerEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTokenizerEntities.m; sourceTree = ""; }; @@ -256,6 +265,9 @@ 625A14CA19C7829400AD0C32 /* Tests */ = { isa = PBXGroup; children = ( + 62D8345719FB1AC4009205A9 /* HTML5LibTest.h */, + 62D8345819FB1AC4009205A9 /* HTML5LibTest.m */, + 624AC90019FBF9ED00BD3C4A /* HTMLTokenizerTests.m */, 625A14D019C7829400AD0C32 /* HTMLKitTests.m */, 625A14CB19C7829400AD0C32 /* Supporting Files */, ); @@ -285,6 +297,7 @@ 62E7CAAE19CDFFB500465A83 /* HTMLTokenizerCharacters.h */, 62F31FDB19E9DCCF007F0657 /* HTMLTokenizerEntities.h */, 62F31FDC19E9DCCF007F0657 /* HTMLTokenizerEntities.m */, + 624AC8FE19FBF4F700BD3C4A /* HTMLTokens.h */, 624493AA19CD0CBE00BCDDF4 /* HTMLToken.h */, 624493AB19CD0CBE00BCDDF4 /* HTMLToken.m */, 62AE594719F9948A0043F069 /* HTMLCharacterToken.h */, @@ -311,6 +324,7 @@ buildActionMask = 2147483647; files = ( 62F873EB19E088C90062683C /* HTMLParser.h in Headers */, + 624AC8FF19FBF59800BD3C4A /* HTMLTokens.h in Headers */, 62AE594419F992F30043F069 /* HTMLCommentToken.h in Headers */, 62AE594E19F9A17A0043F069 /* HTMLEOFToken.h in Headers */, 624493AC19CD0CBE00BCDDF4 /* HTMLToken.h in Headers */, @@ -490,6 +504,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 624AC90219FBF9ED00BD3C4A /* HTMLTokenizerTests.m in Sources */, + 62D8345A19FB1AC4009205A9 /* HTML5LibTest.m in Sources */, 625A14D119C7829400AD0C32 /* HTMLKitTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -519,6 +535,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 624AC90119FBF9ED00BD3C4A /* HTMLTokenizerTests.m in Sources */, + 62D8345919FB1AC4009205A9 /* HTML5LibTest.m in Sources */, 625A150519C783FD00AD0C32 /* HTMLKitTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/HTMLKit/HTMLTokens.h b/HTMLKit/HTMLTokens.h new file mode 100644 index 0000000..6f3a931 --- /dev/null +++ b/HTMLKit/HTMLTokens.h @@ -0,0 +1,15 @@ +// +// HTMLTokens.h +// HTMLKit +// +// Created by Iska on 25/10/14. +// Copyright (c) 2014 BrainCookie. All rights reserved. +// + +#import "HTMLToken.h" +#import "HTMLCharacterToken.h" +#import "HTMLCommentToken.h" +#import "HTMLDOCTYPEToken.h" +#import "HTMLEOFToken.h" +#import "HTMLParseErrorToken.h" +#import "HTMLTagToken.h" diff --git a/HTMLKitTests/HTML5LibTest.h b/HTMLKitTests/HTML5LibTest.h new file mode 100644 index 0000000..aaee3d0 --- /dev/null +++ b/HTMLKitTests/HTML5LibTest.h @@ -0,0 +1,22 @@ +// +// HTML5LibTest.h +// HTMLKit +// +// Created by Iska on 25/10/14. +// Copyright (c) 2014 BrainCookie. All rights reserved. +// + +#import + +@interface HTML5LibTest : NSObject + +@property (nonatomic, strong) NSString *description; +@property (nonatomic, strong) NSString *input; +@property (nonatomic, strong) NSArray *output; +@property (nonatomic, strong) NSArray *initialStates; +@property (nonatomic, strong) NSString *lastStartTag; +@property (nonatomic, assign) BOOL ignoreErrorOrder; + +- (instancetype)initWithFixture:(NSDictionary *)fixture; + +@end diff --git a/HTMLKitTests/HTML5LibTest.m b/HTMLKitTests/HTML5LibTest.m new file mode 100644 index 0000000..662f4e4 --- /dev/null +++ b/HTMLKitTests/HTML5LibTest.m @@ -0,0 +1,138 @@ +// +// HTML5LibTest.m +// HTMLKit +// +// Created by Iska on 25/10/14. +// Copyright (c) 2014 BrainCookie. All rights reserved. +// + +#import "HTML5LibTest.h" +#import "HTMLTokenizerStates.h" +#import "HTMLTokens.h" + +@implementation HTML5LibTest + +- (instancetype)initWithFixture:(NSDictionary *)fixture +{ + self = [super init]; + if (self) { + [self loadFixture:fixture]; + } + return self; +} + +- (void)loadFixture:(NSDictionary *)fixture +{ + BOOL doubleEscaped = [fixture[@"doubleEscaped"] boolValue]; + + // Test Description + self.description = fixture[@"description"]; + + // Test Input + self.input = fixture[@"input"]; + if (doubleEscaped) { + self.input = [self processDoubleEscaped:self.input]; + } + + // Test Output + NSMutableArray *tokens = [NSMutableArray array]; + NSArray *outputs = fixture[@"output"]; + for (NSArray *output in outputs) { + HTMLToken *token = [self processOutputToken:output doubleEscaped:doubleEscaped]; + [tokens addObject:token]; + } + self.output = tokens; + + // Test Initial States + NSMutableArray *initialStates = [NSMutableArray array]; + + NSArray *states = fixture[@"initialStates"]; + for (NSString *name in states) { + HTMLTokenizerState state = HTMLTokenizerStateData; + if ([name isEqualToString:@"PLAINTEXT state"]) { + state = HTMLTokenizerStatePLAINTEXT; + } else if ([name isEqualToString:@"RCDATA state"]) { + state = HTMLTokenizerStateRCDATA; + } else if ([name isEqualToString:@"RAWTEXT state"]) { + state = HTMLTokenizerStateRAWTEXT; + } + [initialStates addObject:@(state)]; + } + if (initialStates.count == 0) { + [initialStates addObject:@(HTMLTokenizerStateData)]; + } + + self.initialStates = initialStates; + + // Test Last Start Tag + self.lastStartTag = fixture[@"lastStartTag"]; + + // Ignore Error Order + self.ignoreErrorOrder = [fixture[@"ignoreErrorOrder"] boolValue]; +} + +- (HTMLToken *)processOutputToken:(NSArray *)output doubleEscaped:(BOOL)doubleEscaped +{ + NSString *type = [output firstObject]; + + NSString *data = nil; + if (output.count > 1) { + NSString *data = [output lastObject]; + if (doubleEscaped) { + data = [self processDoubleEscaped:data]; + } + } + + if ([type isEqualToString:@"Character"]) { + return [[HTMLCharacterToken alloc] initWithString:data]; + } else if ([type isEqualToString:@"Comment"]) { + return [[HTMLCommentToken alloc] initWithData:data]; + } else if ([type isEqualToString:@"DOCTYPE"]) { + HTMLDOCTYPEToken *token = [[HTMLDOCTYPEToken alloc] initWithName:data]; + token.publicIdentifier = output[2]; + token.systemIdentifier = output[3]; + token.forceQuirks = ([output[4] boolValue] == NO); + return token; + } else if ([type isEqualToString:@"EndTag"]) { + return [[HTMLEndTagToken alloc] initWithTagName:data]; + } else if ([type isEqualToString:@"ParseError"]) { + return [HTMLParseErrorToken new]; + } else if ([type isEqualToString:@"StartTag"]) { + HTMLStartTagToken *token = [[HTMLStartTagToken alloc] initWithTagName:data]; + NSDictionary *attributes = output[2]; + for (NSString *name in attributes) { + NSString *value = [attributes objectForKey:name]; + [token.attributes setObject:value forKey:name]; + } + token.selfClosing = (output.count == 4); + return token; + } + return nil; +} + +- (NSString *)processDoubleEscaped:(NSString *)string +{ + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\\\u([0-9a-f]{4})" + options:NSRegularExpressionCaseInsensitive + error:&error]; + + NSArray *matches = [regex matchesInString:string options:0 range:NSMakeRange(0, string.length)]; + + for(NSTextCheckingResult *match in [matches reverseObjectEnumerator]) { + + NSRange hexRange = [match rangeAtIndex:2]; + NSString *hexString = [string substringWithRange:hexRange]; + NSScanner *scanner = [NSScanner scannerWithString:hexString]; + unsigned int codepint; + [scanner scanHexInt:&codepint]; + NSString *replacement = [NSString stringWithFormat:@"%C", (unichar)codepint]; + + NSRange matchRange = [match rangeAtIndex:1]; + string = [string stringByReplacingCharactersInRange:matchRange withString:replacement]; + } + + return nil; +} + +@end diff --git a/HTMLKitTests/HTMLKitTests.m b/HTMLKitTests/HTMLKitTests.m index fe8270b..5b601f8 100644 --- a/HTMLKitTests/HTMLKitTests.m +++ b/HTMLKitTests/HTMLKitTests.m @@ -7,6 +7,7 @@ // #import +#import "HTMLTokenizer.h" @interface HTMLKitTests : XCTestCase @@ -31,4 +32,41 @@ XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); } +- (void)testX +{ + NSString *s = @"\n\n\n\n \n\n The HTML5 Herald\n \n \n\n \n\n \n\n\n\n \n\n"; + + HTMLTokenizer *t = [[HTMLTokenizer alloc] initWithString:s]; + + HTMLToken *token = nil; + do { + token = [t nextToken]; + NSLog(@"%@", token); + } while (token !=nil && ![token isEOFToken]); +} + +- (void)testY +{ + NSString *string = @"\\u003Efoo\\uFEFFbar"; + + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(\\\\u([0-9a-f]{4}))" + options:NSRegularExpressionCaseInsensitive + error:&error]; + + NSArray *matches = [regex matchesInString:string options:0 range:NSMakeRange(0, string.length)]; + + for(NSTextCheckingResult *match in [matches reverseObjectEnumerator]) { + NSRange matchRange = [match rangeAtIndex:1]; + NSRange hexRange = [match rangeAtIndex:2]; + NSString *hexString = [string substringWithRange:hexRange]; + NSScanner *scanner = [NSScanner scannerWithString:hexString]; + unsigned int codepint; + [scanner scanHexInt:&codepint]; + NSString *replacement = [NSString stringWithFormat:@"%C", (unichar)codepint]; + string = [string stringByReplacingCharactersInRange:matchRange withString:replacement]; + } + NSLog(@"%@", string); +} + @end diff --git a/HTMLKitTests/HTMLTokenizerTests.m b/HTMLKitTests/HTMLTokenizerTests.m new file mode 100644 index 0000000..ae79952 --- /dev/null +++ b/HTMLKitTests/HTMLTokenizerTests.m @@ -0,0 +1,41 @@ +// +// HTMLTokenizerTests.m +// HTMLKit +// +// Created by Iska on 25/10/14. +// Copyright (c) 2014 BrainCookie. All rights reserved. +// + +#import +#import +#import "HTML5LibTest.h" + +@interface HTMLTokenizerTests : XCTestCase + +@end + +@implementation HTMLTokenizerTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample { + // This is an example of a functional test case. + XCTAssert(YES, @"Pass"); +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end