280 lines
7.1 KiB
Objective-C
280 lines
7.1 KiB
Objective-C
//
|
|
// HTMLRange.m
|
|
// HTMLKit
|
|
//
|
|
// Created by Iska on 20/11/16.
|
|
// Copyright © 2016 BrainCookie. All rights reserved.
|
|
//
|
|
|
|
#import "HTMLRange.h"
|
|
#import "HTMLDocument.h"
|
|
#import "HTMLKitDOMExceptions.h"
|
|
#import "HTMLDocument+Private.h"
|
|
|
|
@interface HTMLRange ()
|
|
{
|
|
HTMLDocument *_ownerDocument;
|
|
}
|
|
@end
|
|
|
|
@implementation HTMLRange
|
|
|
|
#pragma mark - Lifecycle
|
|
|
|
- (instancetype)initWithDowcument:(HTMLDocument *)document
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_ownerDocument = document;
|
|
[_ownerDocument attachRange:self];
|
|
[self setStartNode:document startOffset:0];
|
|
[self setEndNode:document endOffset:0];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[_ownerDocument detachRange:self];
|
|
}
|
|
|
|
#pragma mark - Properties
|
|
|
|
- (BOOL)isCollapsed
|
|
{
|
|
return _startContainer == _endContainer && _startOffset == _endOffset;
|
|
}
|
|
|
|
- (HTMLNode *)commonAncestorContainer
|
|
{
|
|
HTMLNode *container = _startContainer;
|
|
while (![container containsNode:_endContainer]) {
|
|
container = container.parentNode;
|
|
}
|
|
return container;
|
|
}
|
|
|
|
- (HTMLNode *)rootNode
|
|
{
|
|
return _startContainer.rootNode;
|
|
}
|
|
|
|
#pragma mark - Boundaries
|
|
|
|
NS_INLINE void CheckValidBoundaryNode(HTMLNode *node, NSString *cmd)
|
|
{
|
|
if (node == nil || node.nodeType == HTMLNodeDocumentType) {
|
|
[NSException raise:HTMLKitNotFoundError
|
|
format:@"%@: Invalid Node Type Error, %@ is not a valid range boundary node.",
|
|
cmd, node];
|
|
}
|
|
}
|
|
|
|
NS_INLINE void CheckValidBoundaryOffset(HTMLNode *node, NSUInteger offset, NSString *cmd)
|
|
{
|
|
if (node == nil || node.nodeType == HTMLNodeDocumentType) {
|
|
[NSException raise:HTMLKitIndexSizeError
|
|
format:@"%@: Index Size Error, invalid index %lu for range boundary node %@.",
|
|
cmd, (unsigned long)offset, node];
|
|
}
|
|
}
|
|
|
|
NS_INLINE void CheckValidDocument(HTMLRange *lhs, HTMLRange *rhs, NSString *cmd)
|
|
{
|
|
if (lhs.rootNode != rhs.rootNode) {
|
|
[NSException raise:HTMLKitWrongDocumentError
|
|
format:@"%@: Wrong Document Error, ranges %@ and %@ have different roots.",
|
|
cmd, lhs, rhs];
|
|
}
|
|
}
|
|
|
|
NS_INLINE NSComparisonResult CompareBoundaries(HTMLNode *startNode, NSUInteger startOffset, HTMLNode *endNode, NSUInteger endOffset)
|
|
{
|
|
if (startNode == endNode) {
|
|
if (startOffset == endOffset) {
|
|
return NSOrderedSame;
|
|
} else if (startOffset < endOffset) {
|
|
return NSOrderedAscending;
|
|
} else {
|
|
return NSOrderedDescending;
|
|
}
|
|
}
|
|
|
|
HTMLDocumentPosition position = [startNode compareDocumentPositionWithNode:endNode];
|
|
if ((position & HTMLDocumentPositionFollowing) == HTMLDocumentPositionFollowing) {
|
|
if (CompareBoundaries(endNode, endOffset, startNode, startOffset) == NSOrderedAscending) {
|
|
return NSOrderedDescending;
|
|
} else {
|
|
return NSOrderedAscending;
|
|
}
|
|
}
|
|
|
|
if ([startNode containsNode:endNode]) {
|
|
HTMLNode *child = endNode;
|
|
while (child.parentNode != startNode) {
|
|
child = child.parentNode;
|
|
}
|
|
if ([child.parentNode indexOfChildNode:child] > startOffset) {
|
|
return NSOrderedDescending;
|
|
}
|
|
}
|
|
|
|
return NSOrderedAscending;
|
|
}
|
|
|
|
- (void)setStartNode:(HTMLNode *)node startOffset:(NSUInteger)offset
|
|
{
|
|
CheckValidBoundaryNode(node, NSStringFromSelector(_cmd));
|
|
|
|
CheckValidBoundaryOffset(node, offset, NSStringFromSelector(_cmd));
|
|
|
|
if (self.rootNode != node.rootNode ||
|
|
CompareBoundaries(node, offset, _endContainer, _endOffset) == NSOrderedDescending) {
|
|
_endContainer = node;
|
|
_endOffset = offset;
|
|
}
|
|
|
|
_startContainer = node;
|
|
_startOffset = offset;
|
|
}
|
|
|
|
- (void)setEndNode:(HTMLNode *)node endOffset:(NSUInteger)offset
|
|
{
|
|
CheckValidBoundaryNode(node, NSStringFromSelector(_cmd));
|
|
|
|
CheckValidBoundaryOffset(node, offset, NSStringFromSelector(_cmd));
|
|
|
|
if (self.rootNode != node.rootNode ||
|
|
CompareBoundaries(node, offset, _startContainer, _startOffset) == NSOrderedAscending) {
|
|
_startContainer = node;
|
|
_startOffset = offset;
|
|
}
|
|
|
|
_endContainer = node;
|
|
_endOffset = offset;
|
|
}
|
|
|
|
- (void)setStartBeforeNode:(HTMLNode *)node
|
|
{
|
|
HTMLNode *parent = node.parentNode;
|
|
CheckValidBoundaryNode(parent, NSStringFromSelector(_cmd));
|
|
|
|
[self setStartNode:parent startOffset:node.index];
|
|
}
|
|
|
|
- (void)setStartAfterNode:(HTMLNode *)node
|
|
{
|
|
HTMLNode *parent = node.parentNode;
|
|
CheckValidBoundaryNode(parent, NSStringFromSelector(_cmd));
|
|
|
|
[self setStartNode:parent startOffset:node.index + 1];
|
|
}
|
|
|
|
- (void)setEndBeforeNode:(HTMLNode *)node
|
|
{
|
|
HTMLNode *parent = node.parentNode;
|
|
CheckValidBoundaryNode(parent, NSStringFromSelector(_cmd));
|
|
|
|
[self setEndNode:parent endOffset:node.index];
|
|
}
|
|
|
|
- (void)setEndAfterNode:(HTMLNode *)node
|
|
{
|
|
HTMLNode *parent = node.parentNode;
|
|
CheckValidBoundaryNode(parent, NSStringFromSelector(_cmd));
|
|
|
|
[self setEndNode:parent endOffset:node.index + 1];
|
|
}
|
|
|
|
- (void)collapseToStart
|
|
{
|
|
[self setEndNode:_startContainer endOffset:_startOffset];
|
|
}
|
|
|
|
- (void)collapseToEnd
|
|
{
|
|
[self setStartNode:_endContainer startOffset:_endOffset];
|
|
}
|
|
|
|
- (void)selectNode:(HTMLNode *)node
|
|
{
|
|
HTMLNode *parent = node.parentNode;
|
|
CheckValidBoundaryNode(parent, NSStringFromSelector(_cmd));
|
|
|
|
[self setStartNode:parent startOffset:node.index];
|
|
[self setEndNode:parent endOffset:node.index + 1];
|
|
}
|
|
|
|
- (void)selectNodeContents:(HTMLNode *)node
|
|
{
|
|
CheckValidBoundaryNode(node, NSStringFromSelector(_cmd));
|
|
|
|
[self setStartNode:node startOffset:0];
|
|
[self setEndNode:node endOffset:node.length];
|
|
}
|
|
|
|
- (NSComparisonResult)compareBoundaryPoints:(HTMLRangeComparisonMethod)method sourceRange:(HTMLRange *)sourceRange
|
|
{
|
|
CheckValidDocument(self, sourceRange, NSStringFromSelector(_cmd));
|
|
|
|
switch (method) {
|
|
case HTMLRangeComparisonMethodStartToStart:
|
|
return CompareBoundaries(_startContainer, _startOffset, sourceRange.startContainer, sourceRange.startOffset);
|
|
case HTMLRangeComparisonMethodStartToEnd:
|
|
return CompareBoundaries(_endContainer, _endOffset, sourceRange.startContainer, sourceRange.startOffset);
|
|
case HTMLRangeComparisonMethodEndToEnd:
|
|
return CompareBoundaries(_endContainer, _endOffset, sourceRange.endContainer, sourceRange.endOffset);
|
|
case HTMLRangeComparisonMethodEndToStart:
|
|
return CompareBoundaries(_startContainer, _startOffset, sourceRange.endContainer, sourceRange.endOffset);
|
|
}
|
|
}
|
|
|
|
#pragma mark - Containment
|
|
|
|
- (BOOL)containsNode:(HTMLNode *)node
|
|
{
|
|
if (node.rootNode != self.rootNode) {
|
|
return NO;
|
|
}
|
|
|
|
if (CompareBoundaries(node, 0, _startContainer, _startOffset) == NSOrderedDescending) {
|
|
return NO;
|
|
}
|
|
|
|
if (CompareBoundaries(node, node.length, _endContainer, _endOffset) == NSOrderedAscending) {
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
#pragma mark - Update Callbacks
|
|
|
|
- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
|
|
{
|
|
if (_startContainer == node && _startOffset > offset) {
|
|
if (_startOffset <= offset + length) {
|
|
_startOffset = offset;
|
|
} else {
|
|
_startOffset = _startOffset - length;
|
|
}
|
|
} else if (_endContainer == node && _endOffset > offset) {
|
|
if (_endOffset <= offset + length) {
|
|
_endOffset = offset;
|
|
} else {
|
|
_endOffset = _endOffset - length;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
|
|
{
|
|
if (_startContainer == node && _startOffset > offset) {
|
|
_startOffset = _startOffset + length;
|
|
} else if (_endContainer == node && _endOffset > offset) {
|
|
_endOffset = _endOffset + length;
|
|
}
|
|
}
|
|
|
|
@end
|