diff --git a/Sources/HTMLKitDOMExceptions.m b/Sources/HTMLKitDOMExceptions.m index 5ea8311..d568734 100644 --- a/Sources/HTMLKitDOMExceptions.m +++ b/Sources/HTMLKitDOMExceptions.m @@ -17,3 +17,4 @@ NSString * const HTMLKitInvalidCharacterError = @"InvalidCharacterError"; NSString * const HTMLKitInvalidNodeTypeError = @"InvalidNodeTypeError"; NSString * const HTMLKitIndexSizeError = @"IndexSizeError"; NSString * const HTMLKitWrongDocumentError = @"WrongDocumentError"; +NSString * const HTMLKitInvalidStateError = @"InvalidStateError"; diff --git a/Sources/HTMLRange.h b/Sources/HTMLRange.h index eebbec8..44e90ec 100644 --- a/Sources/HTMLRange.h +++ b/Sources/HTMLRange.h @@ -221,6 +221,25 @@ typedef NS_ENUM(unsigned short, HTMLRangeComparisonMethod) */ - (HTMLDocumentFragment *)cloneContents; +/** + Inserts the given node at the start of this range. + + If the node is being added to a text node, then the text node is split at the insertion point and the given node + is inserted between the resulting text nodes. + + @param node The node to insert. + */ +- (void)insertNode:(HTMLNode *)node; + +/** + Surrounds the contents of this range with the given node. + + The range's boundaries will placed around the given node, i.e. start is before and end is after newParent. + + @param newParent The new parent node which will surround the range. + */ +- (void)surroundContents:(HTMLNode *)newParent; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/HTMLRange.m b/Sources/HTMLRange.m index 0b5a922..b1cf9b7 100644 --- a/Sources/HTMLRange.m +++ b/Sources/HTMLRange.m @@ -613,4 +613,101 @@ NS_INLINE HTMLCharacterData * CloneCharachterData(HTMLNode *node, NSUInteger sta return fragment; } +#pragma mark - Insertion & Surround + +NS_INLINE void CheckValidInsertionNode(HTMLNode *startContainer, HTMLNode *node, NSString *cmd) +{ + if (startContainer == node || startContainer.nodeType == HTMLNodeComment || + (startContainer.nodeType == HTMLNodeText && startContainer.parentNode == nil)) { + [NSException raise:HTMLKitHierarchyRequestError + format:@"%@: Hierarchy Request Error, cannot insert node into range: %@", cmd, node]; + } +} + +- (void)insertNode:(HTMLNode *)node +{ + CheckValidInsertionNode(_startContainer, node, NSStringFromSelector(_cmd)); + + HTMLNode *referenceNode = nil; + + if (_startContainer.nodeType == HTMLNodeText) { + referenceNode = _startContainer; + } else { + referenceNode = [_startContainer childNodeAtIndex:_startOffset]; + } + + HTMLNode *parent = _startContainer; + if (referenceNode != nil) { + parent = referenceNode.parentNode; + } + + if (_startContainer.nodeType == HTMLNodeText) { + referenceNode = [(HTMLText *)_startContainer splitTextAtOffset:_startOffset]; + } + + if (node == referenceNode) { + referenceNode = referenceNode.nextSibling; + } + + [node removeFromParentNode]; + + NSUInteger newOffset = referenceNode ? referenceNode.index : parent.length; + newOffset += (node.nodeType == HTMLNodeDocumentFragment) ? node.length : 1; + + [parent insertNode:node beforeChildNode:referenceNode]; + + if (self.isCollapsed) { + [self setEndNode:parent endOffset:newOffset]; + } +} + +NS_INLINE void CheckValidSurroundState(HTMLRange *range, NSString *cmd) +{ + for (HTMLNode *node in GetAncestorNodes(range.startContainer)) { + if ([node containsNode:range.endContainer]) { + return; + } + + if (node.nodeType != HTMLNodeText) { + [NSException raise:HTMLKitInvalidStateError + format:@"%@: Invalid State Error, cannot surround range with a partially-contaied non-text node.", cmd]; + } + }; + + for (HTMLNode *node in GetAncestorNodes(range.endContainer)) { + if ([node containsNode:range.startContainer]) { + return; + } + + if (node.nodeType != HTMLNodeText) { + [NSException raise:HTMLKitInvalidNodeTypeError + format:@"%@: Invalid State Error, cannot surround range with a partially-contaied non-text node.", cmd]; + } + }; +} + +NS_INLINE void CheckValidSurroundNodeType(HTMLNode *node, NSString *cmd) +{ + if (node == nil || node.nodeType == HTMLNodeDocumentType || node.nodeType == HTMLNodeDocument || + node.nodeType == HTMLNodeDocumentFragment) { + [NSException raise:HTMLKitInvalidNodeTypeError + format:@"%@: Invalid Node Type Error, %@ is not a valid new parent for a range.", + cmd, node]; + } +} + +- (void)surroundContents:(HTMLNode *)newParent +{ + CheckValidSurroundState(self, NSStringFromSelector(_cmd)); + + CheckValidSurroundNodeType(newParent, NSStringFromSelector(_cmd)); + + HTMLDocumentFragment *fragment = [self extractContents]; + [newParent removeAllChildNodes]; + + [self insertNode:newParent]; + [newParent appendNode:fragment]; + [self selectNode:newParent]; +} + @end diff --git a/Sources/include/HTMLKitDOMExceptions.h b/Sources/include/HTMLKitDOMExceptions.h index 5b13fae..4e357cc 100644 --- a/Sources/include/HTMLKitDOMExceptions.h +++ b/Sources/include/HTMLKitDOMExceptions.h @@ -18,3 +18,4 @@ extern NSString * const HTMLKitInvalidCharacterError; extern NSString * const HTMLKitInvalidNodeTypeError; extern NSString * const HTMLKitIndexSizeError; extern NSString * const HTMLKitWrongDocumentError; +extern NSString * const HTMLKitInvalidStateError;