//
// 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
{
[_nodeIterators removeObject:iterator];
}
#pragma mark - Ranges
- (void)attachRange:(HTMLRange *)range
{
[_ranges addObject:range];
}
- (void)detachRange:(HTMLRange *)range
{
[_ranges removeObject:range];
}
- (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