Add implementation for the HTML Node and its Mutation Algorithms
https://dom.spec.whatwg.org/#mutation-algorithms
This commit is contained in:
@@ -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 */,
|
||||
|
||||
@@ -27,4 +27,6 @@ typedef NS_ENUM(short, HTMLDocumentReadyState)
|
||||
|
||||
@property (nonatomic, assign, readonly) HTMLDocumentReadyState readyState;
|
||||
|
||||
- (HTMLNode *)adoptNode:(HTMLNode *)node;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user