diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj
index 3f3859e..b60e492 100644
--- a/HTMLKit.xcodeproj/project.pbxproj
+++ b/HTMLKit.xcodeproj/project.pbxproj
@@ -25,6 +25,12 @@
624B9FB31AE0313300646C4C /* HTMLKitStringCategoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624B9FB11AE0313300646C4C /* HTMLKitStringCategoryTests.m */; };
624B9FB61AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624B9FB41AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m */; };
624FC37B1AE591D80015DDF9 /* HTMLKitNodesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624FC3791AE591D80015DDF9 /* HTMLKitNodesTests.m */; };
+ 62567F501C0CB5750025D458 /* HTMLDOMTokenList.h in Headers */ = {isa = PBXBuildFile; fileRef = 62567F4E1C0CB5750025D458 /* HTMLDOMTokenList.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 62567F511C0CB5750025D458 /* HTMLDOMTokenList.h in Headers */ = {isa = PBXBuildFile; fileRef = 62567F4E1C0CB5750025D458 /* HTMLDOMTokenList.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 62567F521C0CB5750025D458 /* HTMLDOMTokenList.m in Sources */ = {isa = PBXBuildFile; fileRef = 62567F4F1C0CB5750025D458 /* HTMLDOMTokenList.m */; };
+ 62567F531C0CB5750025D458 /* HTMLDOMTokenList.m in Sources */ = {isa = PBXBuildFile; fileRef = 62567F4F1C0CB5750025D458 /* HTMLDOMTokenList.m */; };
+ 62567F561C0CC3AE0025D458 /* HTMLKitDOMTokenListTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 62567F551C0CC3AE0025D458 /* HTMLKitDOMTokenListTests.m */; };
+ 62567F571C0CC3AE0025D458 /* HTMLKitDOMTokenListTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 62567F551C0CC3AE0025D458 /* HTMLKitDOMTokenListTests.m */; };
625A14B019C7829400AD0C32 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 625A14AF19C7829400AD0C32 /* Cocoa.framework */; };
625A14BA19C7829400AD0C32 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 625A14B819C7829400AD0C32 /* InfoPlist.strings */; };
625A14C519C7829400AD0C32 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 625A14C419C7829400AD0C32 /* XCTest.framework */; };
@@ -362,6 +368,9 @@
624B9FB11AE0313300646C4C /* HTMLKitStringCategoryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitStringCategoryTests.m; sourceTree = ""; };
624B9FB41AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitOrderedDictionaryTests.m; sourceTree = ""; };
624FC3791AE591D80015DDF9 /* HTMLKitNodesTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitNodesTests.m; sourceTree = ""; };
+ 62567F4E1C0CB5750025D458 /* HTMLDOMTokenList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLDOMTokenList.h; sourceTree = ""; };
+ 62567F4F1C0CB5750025D458 /* HTMLDOMTokenList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLDOMTokenList.m; sourceTree = ""; };
+ 62567F551C0CC3AE0025D458 /* HTMLKitDOMTokenListTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitDOMTokenListTests.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; };
@@ -501,6 +510,8 @@
62362A3B1A9FA70400301989 /* HTMLText.m */,
623406DF1ADB04F9004677A3 /* HTMLTemplate.h */,
623406E01ADB04F9004677A3 /* HTMLTemplate.m */,
+ 62567F4E1C0CB5750025D458 /* HTMLDOMTokenList.h */,
+ 62567F4F1C0CB5750025D458 /* HTMLDOMTokenList.m */,
6247A9411B152F4F00CCF25C /* HTMLNodeIterator.h */,
6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */,
624717BA1B22009200B38302 /* HTMLTreeWalker.h */,
@@ -551,6 +562,7 @@
isa = PBXGroup;
children = (
624B9FB41AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m */,
+ 62567F551C0CC3AE0025D458 /* HTMLKitDOMTokenListTests.m */,
);
name = Structures;
sourceTree = "";
@@ -796,6 +808,7 @@
62ECBEEC1C0B69FD00AF847B /* HTMLParseErrorToken.h in Headers */,
62ECBEED1C0B69FD00AF847B /* HTMLTagToken.h in Headers */,
62ECBEEE1C0B69FD00AF847B /* HTMLEOFToken.h in Headers */,
+ 62567F501C0CB5750025D458 /* HTMLDOMTokenList.h in Headers */,
62ECBEEF1C0B69FD00AF847B /* HTMLParser.h in Headers */,
62ECBEF01C0B69FD00AF847B /* HTMLStackOfOpenElements.h in Headers */,
62ECBEF11C0B69FD00AF847B /* HTMLListOfActiveFormattingElements.h in Headers */,
@@ -859,6 +872,7 @@
62ECBFB01C0B6D3C00AF847B /* HTMLTreeWalker.h in Headers */,
62ECBFB11C0B6D3C00AF847B /* HTMLNodeTraversal.h in Headers */,
62ECBFB21C0B6D3C00AF847B /* HTMLNodeFilter.h in Headers */,
+ 62567F511C0CB5750025D458 /* HTMLDOMTokenList.h in Headers */,
62ECBFB31C0B6D3C00AF847B /* HTMLKitDOMExceptions.h in Headers */,
62ECBFB41C0B6D3D00AF847B /* HTMLNamespaces.h in Headers */,
62ECBFB51C0B6D3D00AF847B /* HTMLQuirksMode.h in Headers */,
@@ -1109,6 +1123,7 @@
62ECBF451C0B6B7900AF847B /* CSSPseudoFunctionSelector.m in Sources */,
62ECBF461C0B6B7900AF847B /* CSSCombinatorSelector.m in Sources */,
62ECBF471C0B6B7A00AF847B /* CSSCompoundSelector.m in Sources */,
+ 62567F521C0CB5750025D458 /* HTMLDOMTokenList.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1126,6 +1141,7 @@
624B9FB61AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m in Sources */,
624AB3181B0508AE00F3830D /* CSSTypeSelectorTests.m in Sources */,
6236738F1AC0CE2500FF89B3 /* HTMLKitTokenizerPerformance.m in Sources */,
+ 62567F561C0CC3AE0025D458 /* HTMLKitDOMTokenListTests.m in Sources */,
624AC90219FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m in Sources */,
624B9FB31AE0313300646C4C /* HTMLKitStringCategoryTests.m in Sources */,
623916C81AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m in Sources */,
@@ -1186,6 +1202,7 @@
62ECBF8D1C0B6D2A00AF847B /* CSSPseudoFunctionSelector.m in Sources */,
62ECBF8E1C0B6D2A00AF847B /* CSSCombinatorSelector.m in Sources */,
62ECBF8F1C0B6D2A00AF847B /* CSSCompoundSelector.m in Sources */,
+ 62567F531C0CB5750025D458 /* HTMLDOMTokenList.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1203,6 +1220,7 @@
62ECBFD11C0B6E2E00AF847B /* HTMLTreeWalkerTests.m in Sources */,
62ECBFD21C0B6E2E00AF847B /* HTMLKitNodesTests.m in Sources */,
62ECBFD31C0B6E2E00AF847B /* HTMLKitMutationAlgorithmsTests.m in Sources */,
+ 62567F571C0CC3AE0025D458 /* HTMLKitDOMTokenListTests.m in Sources */,
62ECBFD41C0B6E2E00AF847B /* HTMLKitStringCategoryTests.m in Sources */,
62ECBFD51C0B6E2E00AF847B /* HTMLKitOrderedDictionaryTests.m in Sources */,
62ECBFD61C0B6E2E00AF847B /* CSSSelectorTest.m in Sources */,
diff --git a/HTMLKit/HTMLDOM.h b/HTMLKit/HTMLDOM.h
index 20b494f..50eadbc 100644
--- a/HTMLKit/HTMLDOM.h
+++ b/HTMLKit/HTMLDOM.h
@@ -14,6 +14,7 @@
#import "HTMLComment.h"
#import "HTMLText.h"
#import "HTMLTemplate.h"
+#import "HTMLDOMTokenList.h"
#import "HTMLNodeIterator.h"
#import "HTMLTreeWalker.h"
#import "HTMLNodeFilter.h"
diff --git a/HTMLKit/HTMLDOMTokenList.h b/HTMLKit/HTMLDOMTokenList.h
new file mode 100644
index 0000000..4833c2f
--- /dev/null
+++ b/HTMLKit/HTMLDOMTokenList.h
@@ -0,0 +1,36 @@
+//
+// HTMLDOMTokenList.h
+// HTMLKit
+//
+// Created by Iska on 30/11/15.
+// Copyright © 2015 BrainCookie. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class HTMLElement;
+
+@interface HTMLDOMTokenList : NSObject
+
+@property (nonatomic, strong, readonly) HTMLElement *element;
+@property (nonatomic, strong, readonly) NSString *attribute;
+
+- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value;
+
+- (NSUInteger)length;
+- (BOOL)contains:(NSString *)token;
+- (void)add:(NSArray *)tokens;
+- (void)remove:(NSArray *)tokens;
+- (BOOL)toggle:(NSString *)token;
+- (void)replaceToke:(NSString *)token withToken:(NSString *)newToken;
+
+- (NSString *)objectAtIndexedSubscript:(NSUInteger)index;
+- (void)setObject:(NSString *)obj atIndexedSubscript:(NSUInteger)index;
+
+- (NSString *)stringify;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/HTMLKit/HTMLDOMTokenList.m b/HTMLKit/HTMLDOMTokenList.m
new file mode 100644
index 0000000..e5f31f5
--- /dev/null
+++ b/HTMLKit/HTMLDOMTokenList.m
@@ -0,0 +1,109 @@
+//
+// HTMLDOMTokenList.m
+// HTMLKit
+//
+// Created by Iska on 30/11/15.
+// Copyright © 2015 BrainCookie. All rights reserved.
+//
+
+#import "HTMLDOMTokenList.h"
+#import "HTMLElement.h"
+
+@interface HTMLDOMTokenList ()
+{
+ HTMLElement *_element;
+ NSString *_attribute;
+ NSMutableOrderedSet *_tokens;
+}
+@end
+
+@implementation HTMLDOMTokenList
+@synthesize element = _element;
+@synthesize attribute = _attribute;
+
+#pragma mark - Init
+
+- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value
+{
+ self = [super init];
+ if (self) {
+ _element = element;
+ _attribute = [attribute copy];
+ _tokens = [NSMutableOrderedSet new];
+ [self add:[value componentsSeparatedByString:@" "]];
+ }
+ return self;
+}
+
+#pragma mark - Access
+
+- (void)updateValue
+{
+ _element[_attribute] = self.stringify;
+}
+
+- (NSUInteger)length
+{
+ return _tokens.count;
+}
+
+- (BOOL)contains:(NSString *)token
+{
+ return [_tokens containsObject:token];
+}
+
+- (void)add:(NSArray *)tokens
+{
+ for (NSString *token in tokens) {
+ if (![token isEqualToString:@""]) {
+ [_tokens addObject:token];
+ }
+ }
+ [self updateValue];
+}
+
+- (void)remove:(NSArray *)tokens
+{
+ for (NSString *token in tokens) {
+ [_tokens removeObject:token];
+ }
+ [self updateValue];
+}
+
+- (BOOL)toggle:(NSString *)token
+{
+ if ([_tokens containsObject:token]) {
+ [_tokens removeObject:token];
+ [self updateValue];
+ return NO;
+ } else {
+ [_tokens addObject:token];
+ [self updateValue];
+ return YES;
+ }
+}
+
+- (void)replaceToke:(NSString *)token withToken:(NSString *)newToken
+{
+ NSUInteger index = [_tokens indexOfObject:token];
+ _tokens[index] = newToken;
+ [self updateValue];
+}
+
+- (NSString *)objectAtIndexedSubscript:(NSUInteger)index
+{
+ return _tokens[index];
+}
+
+- (void)setObject:(NSString *)obj atIndexedSubscript:(NSUInteger)index
+{
+ _tokens[index] = obj;
+ [self updateValue];
+}
+
+- (NSString *)stringify
+{
+ return [_tokens.array componentsJoinedByString:@" "];
+}
+
+@end
diff --git a/HTMLKit/HTMLKitDOMExceptions.h b/HTMLKit/HTMLKitDOMExceptions.h
index b8486b4..7d730c2 100644
--- a/HTMLKit/HTMLKitDOMExceptions.h
+++ b/HTMLKit/HTMLKitDOMExceptions.h
@@ -9,3 +9,6 @@
extern NSString * const HTMLKitHierarchyRequestError;
extern NSString * const HTMLKitNotFoundError;
extern NSString * const HTMLKitNotSupportedError;
+
+extern NSString * const HTMLKitSyntaxError;
+extern NSString * const HTMLKitInvalidCharacterError;
diff --git a/HTMLKit/HTMLKitDOMExceptions.m b/HTMLKit/HTMLKitDOMExceptions.m
index f7fcacc..88210f6 100644
--- a/HTMLKit/HTMLKitDOMExceptions.m
+++ b/HTMLKit/HTMLKitDOMExceptions.m
@@ -11,3 +11,5 @@
NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError";
NSString * const HTMLKitNotFoundError = @"NotFoundError";
NSString * const HTMLKitNotSupportedError = @"NotSupportedError";
+NSString * const HTMLKitSyntaxError = @"SyntaxError";
+NSString * const HTMLKitInvalidCharacterError = @"InvalidCharacterError";
diff --git a/HTMLKitTests/HTMLKitDOMTokenListTests.m b/HTMLKitTests/HTMLKitDOMTokenListTests.m
new file mode 100644
index 0000000..8491e7d
--- /dev/null
+++ b/HTMLKitTests/HTMLKitDOMTokenListTests.m
@@ -0,0 +1,52 @@
+//
+// HTMLKitDOMTokenListTests.m
+// HTMLKit
+//
+// Created by Iska on 30/11/15.
+// Copyright © 2015 BrainCookie. All rights reserved.
+//
+
+#import
+#import "HTMLElement.h"
+#import "HTMLDOMTokenList.h"
+
+@interface HTMLKitDOMTokenListTests : XCTestCase
+
+@end
+
+@implementation HTMLKitDOMTokenListTests
+
+- (void)testTokenList
+{
+ HTMLElement *element = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"class": @"red"}];
+
+ HTMLDOMTokenList *list = element.classList;
+
+ XCTAssertEqual(list.length, 1);
+ XCTAssertTrue([list contains:@"red"]);
+ XCTAssertEqualObjects(list.stringify, @"red");
+
+ [list add:@[@"green", @"blue"]];
+
+ XCTAssertEqual(list.length, 3);
+ XCTAssertTrue([list contains:@"green"]);
+ XCTAssertTrue([list contains:@"blue"]);
+ XCTAssertEqualObjects(list.stringify, @"red green blue");
+
+ XCTAssertFalse([list toggle:@"green"]);
+ XCTAssertFalse([list contains:@"green"]);
+ XCTAssertEqual(list.length, 2);
+ XCTAssertEqualObjects(list.stringify, @"red blue");
+
+ XCTAssertTrue([list toggle:@"green"]);
+ XCTAssertTrue([list contains:@"green"]);
+ XCTAssertEqual(list.length, 3);
+ XCTAssertEqualObjects(list.stringify, @"red blue green");
+
+ [list remove:@[@"blue", @"red"]];
+
+ XCTAssertEqual(list.length, 1);
+ XCTAssertEqualObjects(list.stringify, @"green");
+}
+
+@end