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;