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
+10
View File
@@ -37,6 +37,9 @@
6238579B1A9E8934003A45D9 /* HTMLComment.h in Headers */ = {isa = PBXBuildFile; fileRef = 623857991A9E8934003A45D9 /* HTMLComment.h */; };
6238579C1A9E8934003A45D9 /* HTMLComment.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238579A1A9E8934003A45D9 /* HTMLComment.m */; };
6238579D1A9E8934003A45D9 /* HTMLComment.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238579A1A9E8934003A45D9 /* HTMLComment.m */; };
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 */; };
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 */; };
@@ -152,6 +155,8 @@
623857951A9E8606003A45D9 /* HTMLDocumentType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLDocumentType.m; sourceTree = "<group>"; };
623857991A9E8934003A45D9 /* HTMLComment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLComment.h; sourceTree = "<group>"; };
6238579A1A9E8934003A45D9 /* HTMLComment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLComment.m; sourceTree = "<group>"; };
6238C9831AB8D6330006512E /* HTMLKitExceptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLKitExceptions.h; sourceTree = "<group>"; };
6238C9841AB8D6330006512E /* HTMLKitExceptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitExceptions.m; sourceTree = "<group>"; };
624493A419CCC54100BCDDF4 /* HTMLTokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizer.h; sourceTree = "<group>"; };
624493A519CCC54100BCDDF4 /* HTMLTokenizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTokenizer.m; sourceTree = "<group>"; };
624493A919CCE84A00BCDDF4 /* HTMLTokenizerStates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizerStates.h; sourceTree = "<group>"; };
@@ -328,6 +333,8 @@
623424871AB467B200726190 /* HTMLOrderedDictionary.m */,
6235CE991AA509430026937B /* NSString+HTMLKit.h */,
6235CE9A1AA509430026937B /* NSString+HTMLKit.m */,
6238C9831AB8D6330006512E /* HTMLKitExceptions.h */,
6238C9841AB8D6330006512E /* HTMLKitExceptions.m */,
623719441AA1472B002E03C8 /* Nodes */,
625A14B619C7829400AD0C32 /* Supporting Files */,
);
@@ -415,6 +422,7 @@
6235CEA01AA5170A0026937B /* HTMLMarker.h in Headers */,
62F31FDD19E9DCCF007F0657 /* HTMLTokenizerEntities.h in Headers */,
6234BEEE1AABBF1400DEB15F /* HTMLStackOfOpenElements.h in Headers */,
6238C9851AB8D6330006512E /* HTMLKitExceptions.h in Headers */,
6238039F1AB63A8C008A53D0 /* HTMLEOFToken.h in Headers */,
6279F87419E1808D00F12EE5 /* HTMLElement.h in Headers */,
62AE593F19F9907C0043F069 /* HTMLTagToken.h in Headers */,
@@ -589,6 +597,7 @@
62AE594119F9907C0043F069 /* HTMLTagToken.m in Sources */,
62AE594619F992F30043F069 /* HTMLCommentToken.m in Sources */,
62AE593C19F97E1C0043F069 /* HTMLDOCTYPEToken.m in Sources */,
6238C9871AB8D6330006512E /* HTMLKitExceptions.m in Sources */,
62F873ED19E088C90062683C /* HTMLParser.m in Sources */,
623803A11AB63A8C008A53D0 /* HTMLEOFToken.m in Sources */,
6279F87619E1808D00F12EE5 /* HTMLElement.m in Sources */,
@@ -628,6 +637,7 @@
62AE594019F9907C0043F069 /* HTMLTagToken.m in Sources */,
62AE594519F992F30043F069 /* HTMLCommentToken.m in Sources */,
62AE593B19F97E1C0043F069 /* HTMLDOCTYPEToken.m in Sources */,
6238C9861AB8D6330006512E /* HTMLKitExceptions.m in Sources */,
62F873EC19E088C90062683C /* HTMLParser.m in Sources */,
623803A01AB63A8C008A53D0 /* HTMLEOFToken.m in Sources */,
6279F87519E1808D00F12EE5 /* HTMLElement.m in Sources */,
+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