diff --git a/Sources/HTMLRange.h b/Sources/HTMLRange.h
index 44e90ec..a35c468 100644
--- a/Sources/HTMLRange.h
+++ b/Sources/HTMLRange.h
@@ -240,6 +240,13 @@ typedef NS_ENUM(unsigned short, HTMLRangeComparisonMethod)
*/
- (void)surroundContents:(HTMLNode *)newParent;
+/**
+ The stringifier of the range.
+
+ @return The text contents of the range.
+ */
+- (NSString *)textContent;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Sources/HTMLRange.m b/Sources/HTMLRange.m
index b1cf9b7..57c2ade 100644
--- a/Sources/HTMLRange.m
+++ b/Sources/HTMLRange.m
@@ -710,4 +710,44 @@ NS_INLINE void CheckValidSurroundNodeType(HTMLNode *node, NSString *cmd)
[self selectNode:newParent];
}
+#pragma mark - Description & Stringifier
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"<%@: %p (%@, %lu), (%@, %lu)>", self.class, self,
+ _startContainer, (unsigned long)_startOffset,
+ _endContainer, (unsigned long)_endOffset];
+}
+
+- (NSString *)textContent
+{
+ HTMLNode *lastNode = nil;
+ if ([_endContainer isKindOfClass:[HTMLCharacterData class]]) {
+ lastNode = FollowingNodeSkippingChildren(_endContainer, _ownerDocument);
+ } else if (_endContainer.childNodesCount > _endOffset) {
+ lastNode = [_endContainer childNodeAtIndex:_endOffset];
+ } else {
+ lastNode = FollowingNodeSkippingChildren(_endContainer, _ownerDocument);
+ }
+
+ NSMutableString *content = [NSMutableString string];
+ for (HTMLNode *node = _startContainer; node != lastNode; node = FollowingNode(node, _ownerDocument)) {
+ if (node.nodeType == HTMLNodeText) {
+ HTMLText *text = (HTMLText *)node;
+
+ if (node == _startContainer) {
+ NSString *string = [text substringDataWithRange:NSMakeRange(_startOffset, _startContainer.length - _startOffset)];
+ [content appendString:string];
+ } else if (node == _endContainer) {
+ NSString *string = [text substringDataWithRange:NSMakeRange(0, _endOffset)];
+ [content appendString:string];
+ } else {
+ [content appendString:text.data];
+ }
+ }
+ }
+
+ return content;
+}
+
@end
diff --git a/Tests/HTMLRangeTests.m b/Tests/HTMLRangeTests.m
index 51bfd8a..7c657fc 100644
--- a/Tests/HTMLRangeTests.m
+++ b/Tests/HTMLRangeTests.m
@@ -1949,4 +1949,48 @@
@""));
}
+#pragma mark - Stringifier
+
+- (void)testRangeStringifier
+{
+ HTMLDocument *document = self.editingDocument;
+ HTMLRange *range = [[HTMLRange alloc] initWithDowcument:document];
+
+ HTMLNode *start = [document querySelector:@"#P1"].firstChild;
+ [range setStartNode:start startOffset:2];
+ HTMLNode *end = [document querySelector:@"#P1"].lastChild;
+ [range setEndNode:end endOffset:3];
+ XCTAssertEqualObjects([range textContent], @"is is a te");
+
+ start = [document querySelector:@"#P1"];
+ [range setStartNode:start startOffset:0];
+ end = [document querySelector:@"#P1"];
+ [range setEndNode:end endOffset:2];
+ XCTAssertEqualObjects([range textContent], @"This is a");
+
+ start = [document querySelector:@"#D1"];
+ [range setStartNode:start startOffset:0];
+ end = [document querySelector:@"#D1"];
+ [range setEndNode:end endOffset:1];
+ XCTAssertEqualObjects([range textContent], @"This is a text");
+
+ start = [document querySelector:@"#D1"];
+ [range setStartNode:start startOffset:0];
+ end = [document querySelector:@"#D1"];
+ [range setEndNode:end endOffset:2];
+ XCTAssertEqualObjects([range textContent], @"This is a textHello");
+
+ start = [document querySelector:@"#D1"];
+ [range setStartNode:start startOffset:0];
+ end = [document querySelector:@"#D2"];
+ [range setEndNode:end endOffset:0];
+ XCTAssertEqualObjects([range textContent], @"This is a textHelloWorld");
+
+ start = [document querySelector:@"#D1"];
+ [range setStartNode:start startOffset:0];
+ end = [document querySelector:@"#D2"];
+ [range setEndNode:end endOffset:1];
+ XCTAssertEqualObjects([range textContent], @"This is a textHelloWorldAnother text");
+}
+
@end