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