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