153d48c19c
Leave the housekeeping to the NSHashTable itself, which should purge null-references automatically (not necessarily right away) Also update tests accordingly: Use `allObjects.count` instead of simply `count` on the NSHashTable, since `allObjects.count` returns non-null references. This should fix #36
244 lines
5.5 KiB
Objective-C
244 lines
5.5 KiB
Objective-C
//
|
|
// HTMLDocument.m
|
|
// HTMLKit
|
|
//
|
|
// Created by Iska on 25/02/15.
|
|
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
|
//
|
|
|
|
#import "HTMLDocument.h"
|
|
#import "HTMLParser.h"
|
|
#import "HTMLNodeIterator.h"
|
|
#import "HTMLRange.h"
|
|
#import "HTMLCharacterData.h"
|
|
#import "HTMLText.h"
|
|
#import "HTMLKitDOMExceptions.h"
|
|
#import "HTMLNode+Private.h"
|
|
#import "HTMLNodeIterator+Private.h"
|
|
#import "HTMLRange+Private.h"
|
|
|
|
@interface HTMLDocument ()
|
|
{
|
|
HTMLDocument *_inertTemplateDocument;
|
|
NSHashTable *_nodeIterators;
|
|
NSHashTable *_ranges;
|
|
}
|
|
@property (nonatomic, assign) HTMLDocumentReadyState readyState;
|
|
@end
|
|
|
|
@implementation HTMLDocument
|
|
|
|
#pragma mark - Init
|
|
|
|
+ (instancetype)documentWithString:(NSString *)string
|
|
{
|
|
HTMLParser *parser = [[HTMLParser alloc] initWithString:string];
|
|
return [parser parseDocument];
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super initWithName:@"#document" type:HTMLNodeDocument];
|
|
if (self) {
|
|
_readyState = HTMLDocumentLoading;
|
|
_nodeIterators = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:10];
|
|
_ranges = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:10];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - Accessors
|
|
|
|
- (void)setOwnerDocument:(HTMLDocument *)ownerDocument
|
|
{
|
|
[self doesNotRecognizeSelector:_cmd];
|
|
}
|
|
|
|
- (void)setDocumentType:(HTMLDocumentType *)documentType
|
|
{
|
|
if (documentType == nil) {
|
|
if (self.documentType != nil) {
|
|
[self removeChildNode:self.documentType];
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (self.documentType != nil) {
|
|
[self replaceChildNode:self.documentType withNode:documentType];
|
|
} else {
|
|
[self appendNode:documentType];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (HTMLElement *)rootElement
|
|
{
|
|
for (HTMLNode *node = self.firstChild; node; node = node.nextSibling) {
|
|
if (node.nodeType == HTMLNodeElement) {
|
|
return node.asElement;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)setRootElement:(HTMLElement *)rootElement
|
|
{
|
|
[self replaceChildNode:self.rootElement withNode:rootElement];
|
|
}
|
|
|
|
- (HTMLElement *)documentElement
|
|
{
|
|
for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
|
|
if ([node.asElement.tagName isEqualToString:@"html"]) {
|
|
return node.asElement;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)setDocumentElement:(HTMLElement *)documentElement
|
|
{
|
|
[self replaceChildNode:self.documentElement withNode:documentElement];
|
|
}
|
|
|
|
- (HTMLElement *)head
|
|
{
|
|
for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
|
|
if ([node.asElement.tagName isEqualToString:@"head"]) {
|
|
return node.asElement;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)setHead:(HTMLElement *)head
|
|
{
|
|
[self replaceChildNode:self.head withNode:head];
|
|
}
|
|
|
|
- (HTMLElement *)body
|
|
{
|
|
for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
|
|
if ([node.asElement.tagName isEqualToString:@"body"]) {
|
|
return node.asElement;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)setBody:(HTMLElement *)body
|
|
{
|
|
[self replaceChildNode:self.body withNode:body];
|
|
}
|
|
|
|
#pragma mark - Node Iterators
|
|
|
|
- (void)attachNodeIterator:(HTMLNodeIterator *)iterator
|
|
{
|
|
[_nodeIterators addObject:iterator];
|
|
}
|
|
|
|
- (void)detachNodeIterator:(HTMLNodeIterator *)iterator
|
|
{
|
|
// NOOP
|
|
}
|
|
|
|
#pragma mark - Ranges
|
|
|
|
- (void)attachRange:(HTMLRange *)range
|
|
{
|
|
[_ranges addObject:range];
|
|
}
|
|
|
|
- (void)detachRange:(HTMLRange *)range
|
|
{
|
|
// NOOP
|
|
}
|
|
|
|
- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
|
|
{
|
|
for (HTMLRange *range in _ranges) {
|
|
[range didRemoveCharacterDataInNode:node atOffset:offset withLength:length];
|
|
}
|
|
}
|
|
|
|
- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
|
|
{
|
|
for (HTMLRange *range in _ranges) {
|
|
[range didAddCharacterDataToNode:node atOffset:offset withLength:length];
|
|
}
|
|
}
|
|
|
|
- (void)didInsertNewTextNode:(HTMLText *)newNode intoParent:(HTMLNode *)parent afterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset
|
|
{
|
|
for (HTMLRange *range in _ranges) {
|
|
[range didInsertNewTextNode:newNode intoParent:parent afterSplittingTextNode:node atOffset:offset];
|
|
}
|
|
}
|
|
|
|
- (void)clampRangesAfterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset
|
|
{
|
|
for (HTMLRange *range in _ranges) {
|
|
[range clampRangesAfterSplittingTextNode:node atOffset:offset];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Mutation Callback
|
|
|
|
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
|
withOldParent:(HTMLNode *)oldParent
|
|
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling
|
|
{
|
|
for (HTMLRange *range in _ranges) {
|
|
[range runRemovingStepsForNode:oldNode
|
|
withOldParent:oldParent
|
|
andOldPreviousSibling:oldPreviousSibling];
|
|
}
|
|
|
|
for (HTMLNodeIterator *iterator in _nodeIterators) {
|
|
[iterator runRemovingStepsForNode:oldNode
|
|
withOldParent:oldParent
|
|
andOldPreviousSibling:oldPreviousSibling];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Mutation Algorithms
|
|
|
|
- (HTMLNode *)adoptNode:(HTMLNode *)node
|
|
{
|
|
if (node == nil) {
|
|
return nil;
|
|
}
|
|
|
|
if (node.nodeType == HTMLNodeDocument) {
|
|
[NSException raise:HTMLKitNotSupportedError
|
|
format:@"%@: Not Fount Error, adopting a document node. The operation is not supported.", NSStringFromSelector(_cmd)];
|
|
}
|
|
|
|
[node.parentNode removeChildNode:node];
|
|
node.ownerDocument = self;
|
|
return node;
|
|
}
|
|
|
|
#pragma mark - Template
|
|
|
|
- (HTMLDocument *)associatedInertTemplateDocument
|
|
{
|
|
if (_inertTemplateDocument == nil) {
|
|
_inertTemplateDocument = [HTMLDocument new];
|
|
_inertTemplateDocument.readyState = HTMLDocumentComplete;
|
|
}
|
|
|
|
return _inertTemplateDocument;
|
|
}
|
|
|
|
#pragma mark - Description
|
|
|
|
- (id)debugQuickLookObject
|
|
{
|
|
return [[NSAttributedString alloc] initWithString:self.innerHTML];
|
|
}
|
|
|
|
@end
|