From d4bb3c6636df003a4ee203a35fbb86b37360683d Mon Sep 17 00:00:00 2001 From: iska Date: Fri, 5 Jun 2015 16:47:37 +0200 Subject: [PATCH] Add implementation for the removing steps of the HTML Node Iterator https://dom.spec.whatwg.org/#interface-nodeiterator --- HTMLKit/HTMLDocument.m | 17 ++ HTMLKit/HTMLNode.m | 14 ++ HTMLKit/HTMLNodeIterator.m | 40 ++++ HTMLKitTests/HTMLKitNodeIteratorTests.m | 264 +++++++++++++++++++++++- 4 files changed, 333 insertions(+), 2 deletions(-) diff --git a/HTMLKit/HTMLDocument.m b/HTMLKit/HTMLDocument.m index 912f269..9b8d2b3 100644 --- a/HTMLKit/HTMLDocument.m +++ b/HTMLKit/HTMLDocument.m @@ -16,6 +16,12 @@ @property (nonatomic, weak) HTMLNode *parentNode; @end +@interface HTMLNodeIterator (Private) +- (void)runRemovingStepsForNode:(HTMLNode *)oldNode + withOldParent:(HTMLNode *)oldParent + andOldPreviousSibling:(HTMLNode *)oldPreviousSibling; +@end + @interface HTMLDocument () { HTMLDocument *_inertTemplateDocument; @@ -126,6 +132,17 @@ [_nodeIterators removeObject:iterator]; } +- (void)runRemovingStepsForNode:(HTMLNode *)oldNode + withOldParent:(HTMLNode *)oldParent + andOldPreviousSibling:(HTMLNode *)oldPreviousSibling +{ + for (HTMLNodeIterator *iterator in _nodeIterators) { + [iterator runRemovingStepsForNode:oldNode + withOldParent:oldParent + andOldPreviousSibling:oldPreviousSibling]; + } +} + #pragma mark - Mutation Algorithms - (HTMLNode *)adoptNode:(HTMLNode *)node diff --git a/HTMLKit/HTMLNode.m b/HTMLKit/HTMLNode.m index ba8cc9a..bb75fe1 100644 --- a/HTMLKit/HTMLNode.m +++ b/HTMLKit/HTMLNode.m @@ -14,6 +14,12 @@ #import "HTMLComment.h" #import "HTMLKitDOMExceptions.h" +@interface HTMLDocument (Private) +- (void)runRemovingStepsForNode:(HTMLNode *)oldNode + withOldParent:(HTMLNode *)oldParent + andOldPreviousSibling:(HTMLNode *)oldPreviousSibling; +@end + @interface HTMLNode () { NSMutableOrderedSet *_childNodes; @@ -224,8 +230,16 @@ NSStringFromSelector(_cmd), child]; } + HTMLNode *oldNode = child; + HTMLNode *oldParent = child.parentNode; + HTMLNode *oldPreviousSibling = child.previousSibling; + [(NSMutableOrderedSet *)self.childNodes removeObject:child]; child.parentNode = nil; + + [self.ownerDocument runRemovingStepsForNode:oldNode + withOldParent:oldParent + andOldPreviousSibling:oldPreviousSibling]; return child; } diff --git a/HTMLKit/HTMLNodeIterator.m b/HTMLKit/HTMLNodeIterator.m index 7b8314e..4f93445 100644 --- a/HTMLKit/HTMLNodeIterator.m +++ b/HTMLKit/HTMLNodeIterator.m @@ -63,6 +63,46 @@ typedef NS_ENUM(short, TraverseDirection) [_root.ownerDocument detachNodeIterator:self]; } +#pragma mark - Removing Steps + +- (void)runRemovingStepsForNode:(HTMLNode *)oldNode + withOldParent:(HTMLNode *)oldParent + andOldPreviousSibling:(HTMLNode *)oldPreviousSibling +{ + if ([oldNode containsNode:_root]) { + return; + } + + if (![oldNode containsNode:_referenceNode]) { + return; + } + + if (_pointerBeforeReferenceNode) { + HTMLNode *nextSibling = oldPreviousSibling != nil ? oldPreviousSibling.nextSibling : oldParent.firstChiledNode; + if (nextSibling != nil) { + _referenceNode = nextSibling; + return; + } + + HTMLNode *next = FollowingNode(oldParent, _root); + if ([_root containsNode:next]) { + _referenceNode = next; + return; + } + + _pointerBeforeReferenceNode = NO; + } + + HTMLNode * (^ lastInclusiveDescendant) (HTMLNode *) = ^ HTMLNode * (HTMLNode *node) { + while (node.lastChildNode) { + node = node.lastChildNode; + } + return node; + }; + + _referenceNode = oldPreviousSibling != nil ? lastInclusiveDescendant(oldPreviousSibling) : oldParent; +} + #pragma mark - Traversal - (HTMLNode *)traverseInDirection:(TraverseDirection)direction diff --git a/HTMLKitTests/HTMLKitNodeIteratorTests.m b/HTMLKitTests/HTMLKitNodeIteratorTests.m index 9eb9c7f..c702e6f 100644 --- a/HTMLKitTests/HTMLKitNodeIteratorTests.m +++ b/HTMLKitTests/HTMLKitNodeIteratorTests.m @@ -32,7 +32,7 @@ @implementation HTMLKitNodeIteratorTests -#pragma mark - Elements +#pragma mark - Test DOM - (HTMLElement *)div { @@ -133,7 +133,25 @@ return document; } -#pragma mark - Tests +- (HTMLDocument *)document +{ + NSString *htmlString = + @"" + @"" + @"" + @"Title" + @"" + @"" + @"HelloWorld!HTML Kit" + @"

This is an Important paragraph

" + @"" + @""; + + HTMLDocument *document = [HTMLDocument documentWithString:htmlString]; + return document; +} + +#pragma mark - Test Iterator - (void)testNodeIteratorInit { @@ -228,6 +246,8 @@ XCTAssertEqualObjects([result valueForKey:@"name"], expected); } +#pragma mark - Test Iterator ShowOptions (WhatToShow) + - (void)testShowDocument { HTMLDocument *document = self.mixedTree; @@ -301,6 +321,8 @@ XCTAssertEqualObjects([result valueForKey:@"name"], expected); } +#pragma mark - Test Iterator Filter + - (void)testNodeFilter { HTMLDocument *document = self.mixedTree; @@ -315,4 +337,242 @@ XCTAssertEqualObjects([result valueForKey:@"name"], expected); } +#pragma mark - Test Removing Steps + +/* + Test cases for the Removing Steps + https://dom.spec.whatwg.org/#interface-nodeiterator + + Following DOM is used: + +| +| +| +| "Title" +| <body> +| <span> +| "Hello" +| <strong> +| "World!" +| <strong> +| "HTML " +| <!-- This is a Comment! --> +| " Kit" +| <p> +| "This is an " +| <em> +| "Important" +| " paragraph" +*/ + +static void (^ RemoveThenInsertNode)(HTMLNode *) = ^ (HTMLNode *node) { + HTMLNode *parent = node.parentNode; + HTMLNode *nextSibling = node.nextSibling; + [parent removeChildNode:node]; + [parent insertNode:node beforeChildNode:nextSibling]; +}; + +static void (^ IterateUpToNode)(HTMLNodeIterator *, HTMLNode *) = ^ (HTMLNodeIterator *iterator, HTMLNode *target) { + for(HTMLNode *node = iterator.referenceNode; node && (node != target); node = iterator.nextNode); +}; + +static HTMLNode * (^ LastDescendant)(HTMLNode *) = ^ HTMLNode * (HTMLNode *node) { + while (node.lastChildNode) { + node = node.lastChildNode; + } + return node; +}; + +- (void)testThatRemovingRootNodeShouldNotAffectIterator +{ + HTMLDocument *document = self.document; + HTMLNode *node = document.body.firstChiledNode; // <span> + + HTMLNodeIterator *iterator = node.nodeIterator; + + [document.body removeChildNode:node]; + + XCTAssertEqualObjects(iterator.root, node); + XCTAssertEqualObjects(iterator.referenceNode, node); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); + XCTAssertEqualObjects(iterator.referenceNode.parentNode, nil); +} + +- (void)testThatRemovingANonInclusiveAnscestorOfReferenceShouldNotAffectIterator +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + [iterator nextNode]; // Reference node: <span> + + RemoveThenInsertNode(iterator.root.childNodes[1]); // Remove <p> + + XCTAssertEqualObjects(iterator.root, body); + XCTAssertEqualObjects(iterator.referenceNode, body.firstChiledNode); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + +- (void)testThatRemovingReferenceNodeShouldUpdateIterator_NilOldPreviousSibling +{ + HTMLDocument *document = self.document; + + HTMLNodeIterator *iterator = document.body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + + HTMLNode *node = iterator.nextNode; // Reference node: <span> + RemoveThenInsertNode(node); // Remove <span> with old previos sibling being nil + + XCTAssertEqualObjects(iterator.referenceNode, iterator.root); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); + + HTMLNode *next = iterator.nextNode; // "Hello" + XCTAssertEqualObjects(next, iterator.root.firstChiledNode); +} + +- (void)testThatRemovingReferenceNodeShouldUpdateIterator_NonNilOldPreviousSibling_NotBeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + + HTMLNode *node = iterator.root.childNodes[1]; // <p> + IterateUpToNode(iterator, node); // Reference node: <p>, pointer-before-reference: NO + RemoveThenInsertNode(node); // Remove <p> with old previos sibling being <span> + + XCTAssertEqualObjects(iterator.referenceNode, LastDescendant(body.firstChiledNode)); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + +- (void)testThatRemovingReferenceNodeShouldUpdateIterator_NonNilOldPreviousSibling_BeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + + HTMLNode *node = iterator.root.childNodes[1]; // <p> + IterateUpToNode(iterator, node); // Reference node: <p>, pointer-before-reference: NO + [iterator previousNode]; // pointer-before-reference: YES + RemoveThenInsertNode(node); // Remove <p> with old previos sibling being <span> + + XCTAssertEqualObjects(iterator.referenceNode, body.firstChiledNode); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); +} + +- (void)testThatRemovingThenReinsertingReferenceNodeAfterNextShouldReturnItAgain +{ + HTMLDocument *document = self.document; + HTMLNodeIterator *iterator = document.body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + + HTMLNode *node = iterator.nextNode; // <span> + RemoveThenInsertNode(node); + + XCTAssertEqualObjects(iterator.referenceNode, iterator.root); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); + + HTMLNode *next = iterator.nextNode; + XCTAssertEqualObjects(next, node); +} + +- (void)testThatRemovingThenReinsertingReferenceNodeAfterPreviousShouldReturnItAgain +{ + HTMLDocument *document = self.document; + HTMLNodeIterator *iterator = document.body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + [iterator nextNode]; // Reference node: <span> + + HTMLNode *node = iterator.previousNode; // Reference node: <span>, pointer-before-reference: YES + HTMLNode *next = node.nextSibling; // <p> + RemoveThenInsertNode(node); + + XCTAssertEqualObjects(iterator.referenceNode, next); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); + + HTMLNode *previous = iterator.previousNode; + XCTAssertEqualObjects(previous, LastDescendant(node)); +} + +- (void)testThatRemovingParentOfReferenceNodeShouldUpdateIterator_NotBeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + HTMLNode *parent = body.childNodes[1]; + + IterateUpToNode(iterator, parent); // Reference node: <p>, pointer-before-reference: NO + [iterator nextNode]; // Reference node: "This is an " + RemoveThenInsertNode(parent); + + XCTAssertEqualObjects(iterator.referenceNode, LastDescendant(body.firstChiledNode)); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + +- (void)testThatRemovingParentOfReferenceNodeShouldUpdateIterator_BeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + HTMLNode *parent = body.childNodes[1]; + + IterateUpToNode(iterator, parent); // Reference node: <p>, pointer-before-reference: NO + [iterator nextNode]; // Reference node: "This is an " + [iterator previousNode]; // pointer-before-reference: YES + RemoveThenInsertNode(parent); + + XCTAssertEqualObjects(iterator.referenceNode, body.firstChiledNode); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); +} + +- (void)testRemoveReferenceNode_NilPreviousSibling_NonNilParentFirstChild +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + [iterator nextNode]; // Reference node: <span> + + HTMLNode *node = iterator.previousNode; // Reference node: <span>, pointer-before-reference: YES + XCTAssertNil(node.previousSibling); + XCTAssertNotNil(node.nextSibling); + + HTMLNode *nextSibling = node.nextSibling; // <p> + RemoveThenInsertNode(node); + + XCTAssertEqualObjects(iterator.referenceNode, nextSibling); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); + + HTMLNode *next = iterator.nextNode; // <p> + XCTAssertNotEqualObjects(next, node); + XCTAssertEqualObjects(next, nextSibling); +} + +- (void)testRemoveReferenceNode_NodeAfterOldParentIsOutsideRoot_BeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + body.innerHTML = @"<div><p><a></a></p></div><div></div>"; + + HTMLNodeIterator *iterator = body.firstChiledNode.nodeIterator; + + IterateUpToNode(iterator, LastDescendant(body.firstChiledNode)); // Referecne node: <a> + HTMLNode *node = [iterator previousNode]; // pointer-before-reference: YES + RemoveThenInsertNode(node); + + XCTAssertEqualObjects(iterator.referenceNode, iterator.root.firstChiledNode); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + @end