Add implementation for the HTML Node and its Mutation Algorithms

https://dom.spec.whatwg.org/#mutation-algorithms
This commit is contained in:
iska
2015-03-19 00:42:43 +01:00
parent 352b34b76c
commit d8d191cb5a
6 changed files with 372 additions and 4 deletions
+2
View File
@@ -27,4 +27,6 @@ typedef NS_ENUM(short, HTMLDocumentReadyState)
@property (nonatomic, assign, readonly) HTMLDocumentReadyState readyState;
- (HTMLNode *)adoptNode:(HTMLNode *)node;
@end
+11
View File
@@ -0,0 +1,11 @@
//
// HTMLKitExceptions.h
// HTMLKit
//
// Created by Iska on 17/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
FOUNDATION_EXPORT NSString * const HTMLKitHierarchyRequestError;
FOUNDATION_EXPORT NSString * const HTMLKitNotFoundError;
FOUNDATION_EXPORT NSString * const HTMLKitNotSupportedError;
+13
View File
@@ -0,0 +1,13 @@
//
// HTMLKitExceptions.m
// HTMLKit
//
// Created by Iska on 17/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLKitExceptions.h"
NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError";
NSString * const HTMLKitNotFoundError = @"NotFoundError";
NSString * const HTMLKitNotSupportedError = @"NotSupportedError";
+12 -4
View File
@@ -33,7 +33,7 @@ typedef NS_ENUM(short, HTMLNodeType)
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readonly) HTMLDocument *document;
@property (nonatomic, strong, readonly) HTMLDocument *ownerDocument;
@property (nonatomic, strong, readonly) NSString *baseURI;
@@ -57,16 +57,24 @@ typedef NS_ENUM(short, HTMLNodeType)
- (BOOL)hasChildNodes;
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type;
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block;
- (NSUInteger)childNodesCount;
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index;
- (NSUInteger)indexOfChildNode:(HTMLNode *)node;
- (HTMLNode *)insertNodeBefore:(HTMLNode *)node;
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child;
- (HTMLNode *)appendChildNode:(HTMLNode *)node;
- (HTMLNode *)appendNode:(HTMLNode *)node;
- (HTMLNode *)replaceChildNode:(HTMLNode *)node;
- (HTMLNode *)replaceChildNode:(HTMLNode *)node withNode:(HTMLNode *)replacement;
- (HTMLNode *)removeChildNode:(HTMLNode *)node;
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index;
@end
+324
View File
@@ -7,7 +7,331 @@
//
#import "HTMLNode.h"
#import "HTMLDocument.h"
#import "HTMLElement.h"
#import "HTMLKitExceptions.h"
@interface HTMLNode ()
{
NSMutableOrderedSet *_childNodes;
}
@end
@implementation HTMLNode
#pragma mark - Init
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type
{
self = [super init];
if (self) {
_name = name;
_type = type;
_childNodes = [NSMutableOrderedSet new];
}
return self;
}
#pragma mark - Properties
- (HTMLDocument *)ownerDocument
{
if (_type == HTMLNodeDocument) {
return nil;
} else {
return _parentNode.ownerDocument;
}
}
- (void)setBaseURI:(NSString *)baseURI
{
_baseURI = [baseURI copy];
[_childNodes.array makeObjectsPerformSelector:@selector(setBaseURI:) withObject:baseURI];
}
- (void)setParentNode:(HTMLNode *)parentNode
{
_parentNode = parentNode;
[_childNodes.array makeObjectsPerformSelector:@selector(setParentNode:) withObject:self];
}
- (HTMLElement *)parentElement
{
return _parentNode.type == HTMLNodeElement ? (HTMLElement *)_parentNode : nil;
}
- (HTMLNode *)firstChiledNode
{
return _childNodes.firstObject;
}
- (HTMLNode *)lastChildNode
{
return _childNodes.lastObject;
}
- (HTMLNode *)previousSibling
{
NSUInteger index = [_parentNode indexOfChildNode:self];
if (index <= 0) {
return nil;
}
return [_parentNode childNodeAtIndex:index - 1];
}
- (HTMLNode *)nextSibling
{
NSUInteger index = [_parentNode indexOfChildNode:self];
if (index >= _parentNode.childNodesCount - 1) {
return nil;
}
return [_parentNode childNodeAtIndex:index + 1];
}
- (NSString *)textContent
{
return nil;
}
#pragma mark - Child Nodes
- (BOOL)hasChildNodes
{
return _childNodes.count > 0;
}
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type
{
NSUInteger index = [_childNodes indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if ([(HTMLNode *)obj type] == type) {
*stop = YES;
return YES;
}
return NO;
}];
return index != NSNotFound;
}
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block
{
if (block == nil) {
return;
}
[_childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
block(obj, idx, stop);
}];
}
- (NSUInteger)childNodesCount
{
return _childNodes.count;
}
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index
{
return [_childNodes objectAtIndex:index];
}
- (NSUInteger)indexOfChildNode:(HTMLNode *)node
{
return [_childNodes indexOfObject:node];
}
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
{
return [_parentNode preInsertNode:node beforeChildNode:child];
}
- (HTMLNode *)appendNode:(HTMLNode *)node
{
return [self preInsertNode:node beforeChildNode:nil];
}
- (HTMLNode *)replaceChildNode:(HTMLNode *)child withNode:(HTMLNode *)node
{
[self ensureReplacementValidityOfChildNode:child withNode:node];
[self.ownerDocument adoptNode:node];
NSUInteger index = [self indexOfChildNode:child];
[_childNodes replaceObjectAtIndex:index withObject:node];
return child;
}
- (HTMLNode *)removeChildNode:(HTMLNode *)child
{
if (child.parentNode != self) {
[NSException raise:HTMLKitNotFoundError
format:@"%@: Not Fount Error, removing non-child node %@. The object can not be found here.",
NSStringFromSelector(_cmd), child];
}
[_childNodes removeObject:child];
child.parentNode = nil;
return child;
}
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index
{
HTMLNode *node = [self childNodeAtIndex:index];
return [self removeChildNode:node];
}
#pragma mark - Mutation Algorithms
- (HTMLNode *)preInsertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
{
[self ensurePreInsertionValidityOfNode:node beforeChildNode:child];
[self.ownerDocument adoptNode:node];
NSUInteger index = [self indexOfChildNode:child];
[_childNodes insertObject:node atIndex:index];
return node;
}
NS_INLINE void CheckParentValid(HTMLNode *node, NSString *cmd)
{
if (node.type != HTMLNodeDocument &&
node.type != HTMLNodeElement) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting into %@ is not allowed. The operation would yield an incorrect node tree.",
cmd, node.name];
}
}
NS_INLINE void CheckChildsParent(HTMLNode *parent, HTMLNode *child, NSString *cmd)
{
if (child != nil &&
child.parentNode != parent) {
[NSException raise:HTMLKitNotFoundError
format:@"%@: Not Fount Error, insering before non-child node %@. The object can not be found here.",
cmd, child];
}
}
NS_INLINE void CheckInsertedNodeValid(HTMLNode *node, NSString *cmd)
{
if (node.type != HTMLNodeDocumentType &&
node.type != HTMLNodeElement &&
node.type != HTMLNodeElement &&
node.type != HTMLNodeText &&
node.type != HTMLNodeComment) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting a %@ is not allowed. The operation would yield an incorrect node tree.",
cmd, node.name];
}
}
NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSString *cmd)
{
if (node.type == HTMLNodeText && parent.type == HTMLNodeDocument) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting a text node %@ into docuement is not allowed. The operation would yield an incorrect node tree.",
cmd, parent.name];
}
if (node.type == HTMLNodeDocumentType && parent.type != HTMLNodeDocument) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting a doctype %@ into a non-document node is not allowed. The operation would yield an incorrect node tree.",
cmd, parent.name];
}
}
- (void)ensurePreInsertionValidityOfNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
{
CheckParentValid(self, NSStringFromSelector(_cmd));
CheckChildsParent(self, child, NSStringFromSelector(_cmd));
CheckInsertedNodeValid(node, NSStringFromSelector(_cmd));
CheckInvalidCombination(self, node, NSStringFromSelector(_cmd));
void (^ hierarchyError)() = ^{
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error. The operation would yield an incorrect node tree.",
NSStringFromSelector(_cmd)];
};
if (self.type == HTMLNodeDocument) {
switch (node.type) {
case HTMLNodeElement:
if ([self hasChildNodeOfType:HTMLNodeElement] ||
child.type == HTMLNodeDocumentType ||
(child != nil && child.nextSibling.type == HTMLNodeDocumentType)) {
hierarchyError();
}
break;
case HTMLNodeDocumentType:
if ([self hasChildNodeOfType:HTMLNodeDocumentType] ||
child.previousSibling != nil ||
(child == nil && [self hasChildNodeOfType:HTMLNodeElement])) {
hierarchyError();
}
break;
default:
break;
}
}
}
- (void)ensureReplacementValidityOfChildNode:(HTMLNode *)node withNode:(HTMLNode *)child
{
CheckParentValid(self, NSStringFromSelector(_cmd));
CheckChildsParent(self, child, NSStringFromSelector(_cmd));
CheckInsertedNodeValid(node, NSStringFromSelector(_cmd));
CheckInvalidCombination(self, node, NSStringFromSelector(_cmd));
void (^ hierarchyError)() = ^{
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error. The operation would yield an incorrect node tree.",
NSStringFromSelector(_cmd)];
};
if (self.type == HTMLNodeDocument) {
switch (node.type) {
case HTMLNodeElement:
{
if (child.nextSibling.type == HTMLNodeDocumentType) {
hierarchyError();
}
[self enumerateChildNodesUsingBlock:^(HTMLNode *node, NSUInteger idx, BOOL *stop) {
if (node.type == HTMLNodeElement && node != child) {
*stop = YES;
hierarchyError();
}
}];
break;
}
case HTMLNodeDocumentType:
{
if (child.previousSibling.type == HTMLNodeElement) {
hierarchyError();
}
[self enumerateChildNodesUsingBlock:^(HTMLNode *node, NSUInteger idx, BOOL *stop) {
if (node.type == HTMLNodeDocument && node != child) {
*stop = YES;
hierarchyError();
}
}];
break;
}
default:
break;
}
}
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
HTMLNode *copy = [[HTMLNode alloc] initWithName:self.name type:self.type];
return copy;
}
@end