diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj
index 0bc6af0..288d887 100644
--- a/HTMLKit.xcodeproj/project.pbxproj
+++ b/HTMLKit.xcodeproj/project.pbxproj
@@ -46,6 +46,11 @@
6238C9851AB8D6330006512E /* HTMLKitExceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 6238C9831AB8D6330006512E /* HTMLKitExceptions.h */; };
6238C9861AB8D6330006512E /* HTMLKitExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238C9841AB8D6330006512E /* HTMLKitExceptions.m */; };
6238C9871AB8D6330006512E /* HTMLKitExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238C9841AB8D6330006512E /* HTMLKitExceptions.m */; };
+ 623916C31AC707250066B4FE /* HTMLNodeTreeEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 623916C11AC707250066B4FE /* HTMLNodeTreeEnumerator.h */; };
+ 623916C41AC707250066B4FE /* HTMLNodeTreeEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C21AC707250066B4FE /* HTMLNodeTreeEnumerator.m */; };
+ 623916C51AC707250066B4FE /* HTMLNodeTreeEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C21AC707250066B4FE /* HTMLNodeTreeEnumerator.m */; };
+ 623916C71AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C61AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m */; };
+ 623916C81AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C61AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m */; };
624493A619CCC54100BCDDF4 /* HTMLTokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 624493A419CCC54100BCDDF4 /* HTMLTokenizer.h */; };
624493A719CCC54100BCDDF4 /* HTMLTokenizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 624493A519CCC54100BCDDF4 /* HTMLTokenizer.m */; };
624493A819CCC54100BCDDF4 /* HTMLTokenizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 624493A519CCC54100BCDDF4 /* HTMLTokenizer.m */; };
@@ -163,6 +168,9 @@
6238579A1A9E8934003A45D9 /* HTMLComment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLComment.m; sourceTree = ""; };
6238C9831AB8D6330006512E /* HTMLKitExceptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLKitExceptions.h; sourceTree = ""; };
6238C9841AB8D6330006512E /* HTMLKitExceptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitExceptions.m; sourceTree = ""; };
+ 623916C11AC707250066B4FE /* HTMLNodeTreeEnumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeTreeEnumerator.h; sourceTree = ""; };
+ 623916C21AC707250066B4FE /* HTMLNodeTreeEnumerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeTreeEnumerator.m; sourceTree = ""; };
+ 623916C61AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitNodeTreeEnumratorTests.m; sourceTree = ""; };
624493A419CCC54100BCDDF4 /* HTMLTokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizer.h; sourceTree = ""; };
624493A519CCC54100BCDDF4 /* HTMLTokenizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTokenizer.m; sourceTree = ""; };
624493A919CCE84A00BCDDF4 /* HTMLTokenizerStates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizerStates.h; sourceTree = ""; };
@@ -282,6 +290,8 @@
6238579A1A9E8934003A45D9 /* HTMLComment.m */,
62362A3A1A9FA70400301989 /* HTMLText.h */,
62362A3B1A9FA70400301989 /* HTMLText.m */,
+ 623916C11AC707250066B4FE /* HTMLNodeTreeEnumerator.h */,
+ 623916C21AC707250066B4FE /* HTMLNodeTreeEnumerator.m */,
);
name = Nodes;
sourceTree = "";
@@ -372,6 +382,7 @@
625A14CA19C7829400AD0C32 /* Tests */ = {
isa = PBXGroup;
children = (
+ 623916C61AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m */,
6236738C1AC0CD2400FF89B3 /* Tokenizer */,
625A14CB19C7829400AD0C32 /* Supporting Files */,
);
@@ -430,6 +441,7 @@
6235CE9B1AA509430026937B /* NSString+HTMLKit.h in Headers */,
62F873EB19E088C90062683C /* HTMLParser.h in Headers */,
624AC8FF19FBF59800BD3C4A /* HTMLTokens.h in Headers */,
+ 623916C31AC707250066B4FE /* HTMLNodeTreeEnumerator.h in Headers */,
62AE594419F992F30043F069 /* HTMLCommentToken.h in Headers */,
624493AC19CD0CBE00BCDDF4 /* HTMLToken.h in Headers */,
6235CEA01AA5170A0026937B /* HTMLMarker.h in Headers */,
@@ -611,6 +623,7 @@
62AE594119F9907C0043F069 /* HTMLTagToken.m in Sources */,
62AE594619F992F30043F069 /* HTMLCommentToken.m in Sources */,
62363C3F1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.m in Sources */,
+ 623916C51AC707250066B4FE /* HTMLNodeTreeEnumerator.m in Sources */,
62AE593C19F97E1C0043F069 /* HTMLDOCTYPEToken.m in Sources */,
6238C9871AB8D6330006512E /* HTMLKitExceptions.m in Sources */,
62F873ED19E088C90062683C /* HTMLParser.m in Sources */,
@@ -634,6 +647,7 @@
files = (
6236738F1AC0CE2500FF89B3 /* HTMLKitTokenizerPerformance.m in Sources */,
624AC90219FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m in Sources */,
+ 623916C81AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m in Sources */,
62D8345A19FB1AC4009205A9 /* HTML5LibTokenizerTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -652,6 +666,7 @@
62AE594019F9907C0043F069 /* HTMLTagToken.m in Sources */,
62AE594519F992F30043F069 /* HTMLCommentToken.m in Sources */,
62363C3E1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.m in Sources */,
+ 623916C41AC707250066B4FE /* HTMLNodeTreeEnumerator.m in Sources */,
62AE593B19F97E1C0043F069 /* HTMLDOCTYPEToken.m in Sources */,
6238C9861AB8D6330006512E /* HTMLKitExceptions.m in Sources */,
62F873EC19E088C90062683C /* HTMLParser.m in Sources */,
@@ -675,6 +690,7 @@
files = (
6236738E1AC0CE2500FF89B3 /* HTMLKitTokenizerPerformance.m in Sources */,
624AC90119FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m in Sources */,
+ 623916C71AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m in Sources */,
62D8345919FB1AC4009205A9 /* HTML5LibTokenizerTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/HTMLKit/HTMLNode.h b/HTMLKit/HTMLNode.h
index 2347e43..70bbdfe 100644
--- a/HTMLKit/HTMLNode.h
+++ b/HTMLKit/HTMLNode.h
@@ -77,4 +77,7 @@ typedef NS_ENUM(short, HTMLNodeType)
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index;
+- (NSEnumerator *)treeEnumerator;
+- (NSEnumerator *)reverseTreeEnumerator;
+
@end
diff --git a/HTMLKit/HTMLNode.m b/HTMLKit/HTMLNode.m
index 8c876e8..46ff4c4 100644
--- a/HTMLKit/HTMLNode.m
+++ b/HTMLKit/HTMLNode.m
@@ -10,6 +10,7 @@
#import "HTMLDocument.h"
#import "HTMLElement.h"
#import "HTMLKitExceptions.h"
+#import "HTMLNodeTreeEnumerator.h"
@interface HTMLNode ()
{
@@ -180,6 +181,18 @@
return [self removeChildNode:node];
}
+#pragma mark - Enumeration
+
+- (NSEnumerator *)treeEnumerator
+{
+ return [[HTMLNodeTreeEnumerator alloc] initWithNode:self reverse:NO];
+}
+
+- (NSEnumerator *)reverseTreeEnumerator
+{
+ return [[HTMLNodeTreeEnumerator alloc] initWithNode:self reverse:YES];
+}
+
#pragma mark - Mutation Algorithms
- (HTMLNode *)preInsertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
diff --git a/HTMLKit/HTMLNodeTreeEnumerator.h b/HTMLKit/HTMLNodeTreeEnumerator.h
new file mode 100644
index 0000000..baf7243
--- /dev/null
+++ b/HTMLKit/HTMLNodeTreeEnumerator.h
@@ -0,0 +1,17 @@
+//
+// HTMLNodeTreeEnumerator.h
+// HTMLKit
+//
+// Created by Iska on 28/03/15.
+// Copyright (c) 2015 BrainCookie. All rights reserved.
+//
+
+#import
+
+@class HTMLNode;
+
+@interface HTMLNodeTreeEnumerator : NSEnumerator
+
+- (instancetype)initWithNode:(HTMLNode *)node reverse:(BOOL)reverse;
+
+@end
diff --git a/HTMLKit/HTMLNodeTreeEnumerator.m b/HTMLKit/HTMLNodeTreeEnumerator.m
new file mode 100644
index 0000000..8f82972
--- /dev/null
+++ b/HTMLKit/HTMLNodeTreeEnumerator.m
@@ -0,0 +1,54 @@
+//
+// HTMLNodeTreeEnumerator.m
+// HTMLKit
+//
+// Created by Iska on 28/03/15.
+// Copyright (c) 2015 BrainCookie. All rights reserved.
+//
+
+#import "HTMLNodeTreeEnumerator.h"
+#import "HTMLNode.h"
+
+@interface HTMLNodeTreeEnumerator ()
+{
+ BOOL _reverse;
+ NSMutableArray *_stack;
+}
+@end
+
+@implementation HTMLNodeTreeEnumerator
+
+- (instancetype)initWithNode:(HTMLNode *)node reverse:(BOOL)reverse
+{
+ self = [super init];
+ if (self) {
+ _reverse = reverse;
+ _stack = [[NSMutableArray alloc] initWithObjects:node, nil];
+ }
+ return self;
+}
+
+- (id)nextObject
+{
+ if (_stack.count == 0) {
+ return nil;
+ }
+
+ HTMLNode *node = _stack.lastObject;
+ [_stack removeLastObject];
+
+ NSArray *childNodes = node.childNodes.array;
+ if (childNodes != nil && childNodes.count > 0) {
+ if (childNodes.count > 1) {
+ NSRange range = NSMakeRange(_reverse ? 0 : 1, childNodes.count - 1);
+ NSArray *rest = [childNodes subarrayWithRange:range];
+
+ [_stack addObjectsFromArray:_reverse ? rest : rest.reverseObjectEnumerator.allObjects];
+ }
+ [_stack addObject:_reverse ? childNodes.lastObject : childNodes.firstObject];
+ }
+
+ return node;
+}
+
+@end
diff --git a/HTMLKitTests/HTMLKitNodeTreeEnumratorTests.m b/HTMLKitTests/HTMLKitNodeTreeEnumratorTests.m
new file mode 100644
index 0000000..654fbcf
--- /dev/null
+++ b/HTMLKitTests/HTMLKitNodeTreeEnumratorTests.m
@@ -0,0 +1,148 @@
+//
+// HTMLKitNodeTreeEnumratorTests.m
+// HTMLKit
+//
+// Created by Iska on 28/03/15.
+// Copyright (c) 2015 BrainCookie. All rights reserved.
+//
+
+#import
+
+#import "HTMLNodes.h"
+
+@interface HTMLKitNodeTreeEnumratorTests : XCTestCase
+
+@end
+
+@implementation HTMLKitNodeTreeEnumratorTests
+
+#pragma mark - Elements
+
+- (HTMLElement *)div
+{
+ return [[HTMLElement alloc] initWithTagName:@"div"];
+}
+
+- (HTMLElement *)simpleTree
+{
+ /*
+ | div
+ | a
+ | b
+ | c
+ */
+ HTMLElement *div = self.div;
+ [div appendNode:[[HTMLElement alloc] initWithTagName:@"a"]];
+ [div appendNode:[[HTMLElement alloc] initWithTagName:@"b"]];
+ [div appendNode:[[HTMLElement alloc] initWithTagName:@"c"]];
+ return div;
+}
+
+- (HTMLElement *)nestedSimpleTree
+{
+ /*
+ | div
+ | div
+ | a
+ | b
+ | c
+ | div
+ | a
+ | b
+ | c
+ */
+ HTMLElement *div = self.div;
+ [div appendNode:self.simpleTree];
+ [div appendNode:self.simpleTree];
+ return div;
+}
+
+- (HTMLElement *)complexTree
+{
+ /*
+ | div
+ | div
+ | div
+ | a
+ | b
+ | c
+ | e
+ | f
+ | div
+ | a
+ | b
+ | c
+ */
+ HTMLElement *root = self.div;
+
+ HTMLElement *div = self.div;
+ [div appendNode:self.simpleTree];
+ [root appendNode:div];
+
+ HTMLElement *e = [[HTMLElement alloc] initWithTagName:@"e"];
+ [e appendNode:[[HTMLElement alloc] initWithTagName:@"f"]];
+ [root appendNode:e];
+ [root appendNode:self.simpleTree];
+
+ return root;
+}
+
+#pragma mark - Tests
+
+- (void)testSingle
+{
+ HTMLElement *div = self.div;
+ NSArray *result = div.treeEnumerator.allObjects;
+ NSArray *expected = @[@"div"];
+ XCTAssertEqualObjects([result valueForKey:@"name"], expected);
+}
+
+- (void)testSimpleTree
+{
+ HTMLElement *tree = self.simpleTree;
+ NSArray *result = tree.treeEnumerator.allObjects;
+ NSArray *expected = @[@"div", @"a", @"b", @"c"];
+ XCTAssertEqualObjects([result valueForKey:@"name"], expected);
+}
+
+- (void)testSimpleTreeReversed
+{
+ HTMLElement *tree = self.simpleTree;
+ NSArray *result = tree.reverseTreeEnumerator.allObjects;
+ NSArray *expected = @[@"div", @"c", @"b", @"a"];
+ XCTAssertEqualObjects([result valueForKey:@"name"], expected);
+}
+
+- (void)testNestedSimpleTree
+{
+ HTMLElement *tree = self.nestedSimpleTree;
+ NSArray *result = tree.treeEnumerator.allObjects;
+ NSArray *expected = @[@"div", @"div", @"a", @"b", @"c", @"div", @"a", @"b", @"c"];
+ XCTAssertEqualObjects([result valueForKey:@"name"], expected);
+}
+
+- (void)testNestedSimpleTreeReversed
+{
+ HTMLElement *tree = self.nestedSimpleTree;
+ NSArray *result = tree.reverseTreeEnumerator.allObjects;
+ NSArray *expected = @[@"div", @"div", @"c", @"b", @"a", @"div", @"c", @"b", @"a"];
+ XCTAssertEqualObjects([result valueForKey:@"name"], expected);
+}
+
+- (void)testComplexSimpleTree
+{
+ HTMLElement *tree = self.complexTree;
+ NSArray *result = tree.treeEnumerator.allObjects;
+ NSArray *expected = @[@"div", @"div",@"div", @"a", @"b", @"c", @"e", @"f", @"div", @"a", @"b", @"c"];
+ XCTAssertEqualObjects([result valueForKey:@"name"], expected);
+}
+
+- (void)testComplexSimpleTreeReversed
+{
+ HTMLElement *tree = self.complexTree;
+ NSArray *result = tree.reverseTreeEnumerator.allObjects;
+ NSArray *expected = @[@"div", @"div", @"c", @"b", @"a", @"e", @"f", @"div", @"div", @"c", @"b", @"a"];
+ XCTAssertEqualObjects([result valueForKey:@"name"], expected);
+}
+
+@end