diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj
index 4e109d3..ff7ddb5 100644
--- a/HTMLKit.xcodeproj/project.pbxproj
+++ b/HTMLKit.xcodeproj/project.pbxproj
@@ -71,6 +71,9 @@
6247171A1B22333200C11912 /* HTMLNodeTraversal.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717171B22333200C11912 /* HTMLNodeTraversal.m */; };
624717B81B21FE5400B38302 /* HTMLNodeFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */; };
624717B91B21FE5400B38302 /* HTMLNodeFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */; };
+ 624717BC1B22009200B38302 /* HTMLTreeWalker.h in Headers */ = {isa = PBXBuildFile; fileRef = 624717BA1B22009200B38302 /* HTMLTreeWalker.h */; };
+ 624717BD1B22009200B38302 /* HTMLTreeWalker.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717BB1B22009200B38302 /* HTMLTreeWalker.m */; };
+ 624717BE1B22009200B38302 /* HTMLTreeWalker.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717BB1B22009200B38302 /* HTMLTreeWalker.m */; };
6247A9431B152F4F00CCF25C /* HTMLNodeIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = 6247A9411B152F4F00CCF25C /* HTMLNodeIterator.h */; };
6247A9441B152F4F00CCF25C /* HTMLNodeIterator.m in Sources */ = {isa = PBXBuildFile; fileRef = 6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */; };
6247A9451B152F4F00CCF25C /* HTMLNodeIterator.m in Sources */ = {isa = PBXBuildFile; fileRef = 6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */; };
@@ -219,6 +222,8 @@
624717161B22333200C11912 /* HTMLNodeTraversal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeTraversal.h; sourceTree = ""; };
624717171B22333200C11912 /* HTMLNodeTraversal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeTraversal.m; sourceTree = ""; };
624717B71B21FE5400B38302 /* HTMLNodeFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeFilter.m; sourceTree = ""; };
+ 624717BA1B22009200B38302 /* HTMLTreeWalker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLTreeWalker.h; sourceTree = ""; };
+ 624717BB1B22009200B38302 /* HTMLTreeWalker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTreeWalker.m; sourceTree = ""; };
6247A9411B152F4F00CCF25C /* HTMLNodeIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeIterator.h; sourceTree = ""; };
6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeIterator.m; sourceTree = ""; };
6247A9461B152F8C00CCF25C /* HTMLNodeFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeFilter.h; sourceTree = ""; };
@@ -345,6 +350,8 @@
623406E01ADB04F9004677A3 /* HTMLTemplate.m */,
6247A9411B152F4F00CCF25C /* HTMLNodeIterator.h */,
6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */,
+ 624717BA1B22009200B38302 /* HTMLTreeWalker.h */,
+ 624717BB1B22009200B38302 /* HTMLTreeWalker.m */,
624717161B22333200C11912 /* HTMLNodeTraversal.h */,
624717171B22333200C11912 /* HTMLNodeTraversal.m */,
6247A9461B152F8C00CCF25C /* HTMLNodeFilter.h */,
@@ -596,6 +603,7 @@
623857961A9E8606003A45D9 /* HTMLDocumentType.h in Headers */,
623406E61ADB05AD004677A3 /* HTMLDocumentFragment.h in Headers */,
62363C3D1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.h in Headers */,
+ 624717BC1B22009200B38302 /* HTMLTreeWalker.h in Headers */,
62362A3C1A9FA70400301989 /* HTMLText.h in Headers */,
6234584E1A9D2FA4009BD491 /* HTMLNode.h in Headers */,
);
@@ -755,6 +763,7 @@
624493AE19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */,
6235CEA21AA5170A0026937B /* HTMLMarker.m in Sources */,
6234248A1AB467B200726190 /* HTMLOrderedDictionary.m in Sources */,
+ 624717BE1B22009200B38302 /* HTMLTreeWalker.m in Sources */,
62AE594119F9907C0043F069 /* HTMLTagToken.m in Sources */,
62AE594619F992F30043F069 /* HTMLCommentToken.m in Sources */,
62363C3F1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.m in Sources */,
@@ -809,6 +818,7 @@
624493AD19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */,
6235CEA11AA5170A0026937B /* HTMLMarker.m in Sources */,
623424891AB467B200726190 /* HTMLOrderedDictionary.m in Sources */,
+ 624717BD1B22009200B38302 /* HTMLTreeWalker.m in Sources */,
62AE594019F9907C0043F069 /* HTMLTagToken.m in Sources */,
62AE594519F992F30043F069 /* HTMLCommentToken.m in Sources */,
62363C3E1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.m in Sources */,
diff --git a/HTMLKit/HTMLNodeTraversal.h b/HTMLKit/HTMLNodeTraversal.h
index b3fe2f6..610700f 100644
--- a/HTMLKit/HTMLNodeTraversal.h
+++ b/HTMLKit/HTMLNodeTraversal.h
@@ -13,4 +13,5 @@
extern HTMLNode * PrecedingNode(HTMLNode *node, HTMLNode *root);
extern HTMLNode * FollowingNode(HTMLNode *node, HTMLNode *root);
+extern HTMLNode * FollowingNodeSkippingChildren(HTMLNode *node, HTMLNode *root);
extern BOOL FilterNode(id filter, HTMLNodeFilterShowOptions whatToShow, HTMLNode *node);
diff --git a/HTMLKit/HTMLNodeTraversal.m b/HTMLKit/HTMLNodeTraversal.m
index 6f8b5eb..1ef2de8 100644
--- a/HTMLKit/HTMLNodeTraversal.m
+++ b/HTMLKit/HTMLNodeTraversal.m
@@ -46,7 +46,22 @@ HTMLNode * FollowingNode(HTMLNode *node, HTMLNode *root)
return nil;
}
-extern BOOL FilterNode(id filter, HTMLNodeFilterShowOptions whatToShow, HTMLNode *node)
+HTMLNode * FollowingNodeSkippingChildren(HTMLNode *node, HTMLNode *root)
+{
+ do {
+ if (node == root) {
+ return nil;
+ }
+ if (node.nextSibling != nil) {
+ return node.nextSibling;
+ }
+ node = node.parentNode;
+ } while (node != nil);
+
+ return nil;
+}
+
+BOOL FilterNode(id filter, HTMLNodeFilterShowOptions whatToShow, HTMLNode *node)
{
unsigned long nthBit = (1 << (node.nodeType - 1)) & whatToShow;
if (!nthBit) {
diff --git a/HTMLKit/HTMLTreeWalker.h b/HTMLKit/HTMLTreeWalker.h
new file mode 100644
index 0000000..8b05394
--- /dev/null
+++ b/HTMLKit/HTMLTreeWalker.h
@@ -0,0 +1,29 @@
+//
+// HTMLTreeWalker.h
+// HTMLKit
+//
+// Created by Iska on 05/06/15.
+// Copyright (c) 2015 BrainCookie. All rights reserved.
+//
+
+#import
+#import "HTMLNodeFilter.h"
+
+@class HTMLNode;
+
+@interface HTMLTreeWalker : NSObject
+
+@property (nonatomic, strong, readonly) HTMLNode *root;
+@property (nonatomic, assign, readonly) HTMLNodeFilterShowOptions whatToShow;
+@property (nonatomic, strong, readonly) id filter;
+@property (nonatomic, strong) HTMLNode *currentNode;
+
+- (HTMLNode *)parentNode;
+- (HTMLNode *)firstChild;
+- (HTMLNode *)lastChild;
+- (HTMLNode *)previousSibling;
+- (HTMLNode *)nextSibling;
+- (HTMLNode *)previousNode;
+- (HTMLNode *)nextNode;
+
+@end
diff --git a/HTMLKit/HTMLTreeWalker.m b/HTMLKit/HTMLTreeWalker.m
new file mode 100644
index 0000000..d4a90c5
--- /dev/null
+++ b/HTMLKit/HTMLTreeWalker.m
@@ -0,0 +1,222 @@
+//
+// HTMLTreeWalker.m
+// HTMLKit
+//
+// Created by Iska on 05/06/15.
+// Copyright (c) 2015 BrainCookie. All rights reserved.
+//
+
+#import "HTMLTreeWalker.h"
+#import "HTMLNode.h"
+#import "HTMLNodeFilter.h"
+#import "HTMLNodeTraversal.h"
+
+typedef NS_ENUM(short, HTMLTreeWalkerChildrenType)
+{
+ HTMLTreeWalkerChildrenTypeFirst,
+ HTMLTreeWalkerChildrenTypeLast
+};
+
+typedef NS_ENUM(short, HTMLTreeWalkerSiblingsType)
+{
+ HTMLTreeWalkerSiblingsTypeNext,
+ HTMLTreeWalkerSiblingsTypePrevious
+};
+
+@implementation HTMLTreeWalker
+
+#pragma mark - Lifecycle
+
+- (instancetype)initWithNode:(HTMLNode *)node
+ showOptions:(HTMLNodeFilterShowOptions)showOptions
+ filter:(id)filter
+{
+ self = [super init];
+ if (self) {
+ _root = node;
+ _filter = filter;
+ _whatToShow = showOptions;
+ _currentNode = _root;
+ }
+ return self;
+}
+
+#pragma mark - Traversal
+
+- (HTMLNode *)traverseChildrenOfType:(HTMLTreeWalkerChildrenType)type
+{
+ HTMLNode *node = _currentNode;
+
+ node = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.firstChiledNode : node.lastChildNode;
+
+ while (node != nil) {
+ BOOL result = FilterNode(self.filter, self.whatToShow, node);
+ if (result) {
+ _currentNode = node;
+ return node;
+ }
+
+ HTMLNode *child = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.firstChiledNode : node.lastChildNode;
+ if (child != nil) {
+ node = child;
+ continue;
+ }
+
+ while (node != nil) {
+ HTMLNode *sibling = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.nextSibling : node.previousSibling;
+ if (sibling != nil) {
+ node = sibling;
+ continue;
+ }
+
+ HTMLNode *parent = node.parentNode;
+ if (parent == nil || parent == _root || parent == _currentNode) {
+ return nil;
+ }
+ node = parent;
+ }
+ }
+
+ return nil;
+}
+
+- (HTMLNode *)parentNode
+{
+ HTMLNode *node = _currentNode;
+
+ while (node != nil && node != _root) {
+ node = node.parentNode;
+ if (node != nil && FilterNode(self.filter, self.whatToShow, node)) {
+ _currentNode = node;
+ return node;
+ }
+ }
+ return nil;
+}
+
+- (HTMLNode *)firstChild
+{
+ return [self traverseChildrenOfType:HTMLTreeWalkerChildrenTypeFirst];
+}
+
+- (HTMLNode *)lastChild
+{
+ return [self traverseChildrenOfType:HTMLTreeWalkerChildrenTypeLast];
+}
+
+- (HTMLNode *)traverseSiblingsOfType:(HTMLTreeWalkerSiblingsType)type
+{
+ HTMLNode *node = _currentNode;
+
+ if (node == _root) {
+ return nil;
+ }
+
+ while (YES) {
+ HTMLNode *sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.nextSibling : node.previousSibling;
+ while (sibling != nil) {
+ node = sibling;
+ BOOL result = FilterNode(self.filter, self.whatToShow, node);
+ if (result) {
+ _currentNode = node;
+ return node;
+ }
+
+ HTMLNode *sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.firstChiledNode : node.lastChildNode;
+ if (sibling == nil) {
+ sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.nextSibling : node.previousSibling;
+ }
+ }
+
+ node = node.parentNode;
+ if (node == nil || node == _root) {
+ return nil;
+ }
+
+ if (FilterNode(self.filter, self.whatToShow, node)) {
+ return nil;
+ }
+ }
+ return nil;
+}
+
+- (HTMLNode *)previousSibling
+{
+ return [self traverseSiblingsOfType:HTMLTreeWalkerSiblingsTypePrevious];
+}
+
+- (HTMLNode *)nextSibling
+{
+ return [self traverseSiblingsOfType:HTMLTreeWalkerSiblingsTypeNext];
+}
+
+- (HTMLNode *)previousNode
+{
+ HTMLNode *node = _currentNode;
+
+ while (node != _root) {
+ HTMLNode *sibling = node.previousSibling;
+
+ while (sibling != nil) {
+ node = sibling;
+
+ BOOL result = FilterNode(self.filter, self.whatToShow, node);
+ while (result && node.hasChildNodes) {
+ node = node.lastChildNode;
+ result = FilterNode(self.filter, self.whatToShow, node);
+ }
+
+ if (result) {
+ _currentNode = node;
+ return node;
+ }
+
+ sibling = node.previousSibling;
+ }
+
+ if (node == _root || node.parentNode == nil) {
+ return nil;
+ }
+
+ node = node.parentNode;
+ if (FilterNode(self.filter, self.whatToShow, node)) {
+ _currentNode = node;
+ return node;
+ }
+ }
+
+ return nil;
+}
+
+- (HTMLNode *)nextNode
+{
+ HTMLNode *node = _currentNode;
+
+ BOOL result = YES;
+
+ while (YES) {
+ while (!result && node.hasChildNodes) {
+ node = node.firstChiledNode;
+ result = FilterNode(self.filter, self.whatToShow, node);
+ if (result) {
+ _currentNode = node;
+ return node;
+ }
+ }
+
+ HTMLNode *nextSibling;
+ while ((nextSibling = FollowingNodeSkippingChildren(node, _root)) != nil) {
+ node = nextSibling;
+ result = FilterNode(self.filter, self.whatToShow, node);
+ if (result) {
+ _currentNode = node;
+ return node;
+ }
+ break;
+ }
+ }
+
+ return nil;
+}
+
+@end