From 6be1bd702f88b716ce29f38cd25e157945ae97eb Mon Sep 17 00:00:00 2001 From: iska Date: Sun, 27 Nov 2016 20:18:12 +0100 Subject: [PATCH] Add implementation for HTML Character Data methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The data property is longer exposed as a mutable string. Instead all manipulations must be performed via the provided API in order to ensure correct updates for the Document’s attached ranges. See: https://dom.spec.whatwg.org/#characterdata --- HTMLKit.xcodeproj/project.pbxproj | 10 ++++++ Sources/HTMLCharacterData.h | 10 ++++-- Sources/HTMLCharacterData.m | 55 +++++++++++++++++++++++++++++-- Sources/HTMLDocument+Private.h | 4 +++ Sources/HTMLDocument.m | 20 +++++++++-- Sources/HTMLParser.m | 4 +-- Sources/HTMLRange+Private.h | 17 ++++++++++ 7 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 Sources/HTMLRange+Private.h diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj index 51701fb..dc7ecb4 100644 --- a/HTMLKit.xcodeproj/project.pbxproj +++ b/HTMLKit.xcodeproj/project.pbxproj @@ -534,6 +534,10 @@ 62ECBFDE1C0B6E4400AF847B /* HTML Standard.html in Resources */ = {isa = PBXBuildFile; fileRef = 624AC90D19FC702E00BD3C4A /* HTML Standard.html */; }; 62ECBFDF1C0B6E4400AF847B /* html5lib-tests in Resources */ = {isa = PBXBuildFile; fileRef = 624AC90419FBFE8A00BD3C4A /* html5lib-tests */; }; 62F658711BD83C8E0045F137 /* CSSNThExpressionSelectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F6586F1BD83C8E0045F137 /* CSSNThExpressionSelectorTests.m */; }; + 62F7E5A21DEB63F900DEF09B /* HTMLRange+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 62F7E5A01DEB63F900DEF09B /* HTMLRange+Private.h */; }; + 62F7E5A31DEB63F900DEF09B /* HTMLRange+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 62F7E5A01DEB63F900DEF09B /* HTMLRange+Private.h */; }; + 62F7E5A41DEB63F900DEF09B /* HTMLRange+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 62F7E5A01DEB63F900DEF09B /* HTMLRange+Private.h */; }; + 62F7E5A51DEB63F900DEF09B /* HTMLRange+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 62F7E5A01DEB63F900DEF09B /* HTMLRange+Private.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -710,6 +714,7 @@ 62F501631BC9576B0069F47B /* CSSNthExpressionParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSSNthExpressionParser.h; path = include/CSSNthExpressionParser.h; sourceTree = ""; }; 62F501641BC9576B0069F47B /* CSSNthExpressionParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSSNthExpressionParser.m; sourceTree = ""; }; 62F6586F1BD83C8E0045F137 /* CSSNThExpressionSelectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSSNThExpressionSelectorTests.m; sourceTree = ""; }; + 62F7E5A01DEB63F900DEF09B /* HTMLRange+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "HTMLRange+Private.h"; sourceTree = ""; }; 62F873E919E088C90062683C /* HTMLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTMLParser.h; path = include/HTMLParser.h; sourceTree = ""; }; 62F873EA19E088C90062683C /* HTMLParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLParser.m; sourceTree = ""; }; 62FA04E11BCC360D009ABF98 /* CSSCombinatorSelector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSSCombinatorSelector.h; path = include/CSSCombinatorSelector.h; sourceTree = ""; }; @@ -989,6 +994,7 @@ 625D0F011C2717DE00D7BEB0 /* HTMLNode+Private.h */, 626679CD1DEA3DAF004353E7 /* HTMLCharacterData+Private.h */, 621CC4991DEA71D800D1992A /* HTMLNodeIterator+Private.h */, + 62F7E5A01DEB63F900DEF09B /* HTMLRange+Private.h */, 621CC4A31DEA721A00D1992A /* HTMLParser+Private.h */, ); name = Private; @@ -1124,6 +1130,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 62F7E5A21DEB63F900DEF09B /* HTMLRange+Private.h in Headers */, 62ECBF181C0B6A1000AF847B /* HTMLKit.h in Headers */, 62ECBEE11C0B69FD00AF847B /* HTMLKitErrorDomain.h in Headers */, 62ECBEE21C0B69FD00AF847B /* HTMLInputStreamReader.h in Headers */, @@ -1195,6 +1202,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 62F7E5A51DEB63F900DEF09B /* HTMLRange+Private.h in Headers */, 62857CBE1D398999008DC254 /* NSString+HTMLKit.h in Headers */, 62857CAC1D398973008DC254 /* HTMLKitDOMExceptions.h in Headers */, 62857CD61D3989C2008DC254 /* CSSSelectorParser.h in Headers */, @@ -1266,6 +1274,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 62F7E5A41DEB63F900DEF09B /* HTMLRange+Private.h in Headers */, 62857D831D39A46D008DC254 /* CSSStructuralPseudoSelectors.h in Headers */, 62857D7D1D39A457008DC254 /* HTMLNodeTraversal.h in Headers */, 62857D841D39A472008DC254 /* CSSSelectorParser.h in Headers */, @@ -1337,6 +1346,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 62F7E5A31DEB63F900DEF09B /* HTMLRange+Private.h in Headers */, 62ECBF901C0B6D3700AF847B /* HTMLKit.h in Headers */, 62ECBF911C0B6D3700AF847B /* HTMLKitErrorDomain.h in Headers */, 62ECBF9F1C0B6D3900AF847B /* HTMLParser.h in Headers */, diff --git a/Sources/HTMLCharacterData.h b/Sources/HTMLCharacterData.h index 7ca0e3a..1273401 100644 --- a/Sources/HTMLCharacterData.h +++ b/Sources/HTMLCharacterData.h @@ -17,8 +17,14 @@ NS_ASSUME_NONNULL_BEGIN */ @interface HTMLCharacterData : HTMLNode -/** @brief The associated mutable data string. */ -@property (nonatomic, copy, readonly) NSMutableString *data; +/** @brief The associated data string. */ +@property (nonatomic, copy, readonly) NSString *data; + +- (void)setData:(NSString *)data; +- (void)appendData:(NSString *)data; +- (void)insertData:(NSString *)data atOffset:(NSUInteger)offset; +- (void)deleteDataInRange:(NSRange)range; +- (void)replaceDataInRange:(NSRange)range withData:(NSString *)data; @end diff --git a/Sources/HTMLCharacterData.m b/Sources/HTMLCharacterData.m index 8f5eb44..86e4f90 100644 --- a/Sources/HTMLCharacterData.m +++ b/Sources/HTMLCharacterData.m @@ -8,8 +8,17 @@ #import "HTMLCharacterData.h" #import "HTMLNode+Private.h" +#import "HTMLDocument+Private.h" +#import "HTMLKitDOMExceptions.h" + +@interface HTMLCharacterData () +{ + NSMutableString *_data; +} +@end @implementation HTMLCharacterData +@synthesize data = _data; - (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type data:(NSString *)data { @@ -27,7 +36,7 @@ - (void)setTextContent:(NSString *)textContent { - [_data setString:textContent ?: @""]; + [self setData:textContent]; } - (NSUInteger)length @@ -35,12 +44,54 @@ return _data.length; } +#pragma mark - Data + +NS_INLINE void CheckValidOffset(HTMLCharacterData *node, NSUInteger offset, NSString *cmd) +{ + if (offset > node.length) { + [NSException raise:HTMLKitIndexSizeError + format:@"%@: Index Size Error, invalid index %lu for character data node %@.", + cmd, (unsigned long)offset, node]; + } +} + +- (void)setData:(NSString *)data +{ + [self replaceDataInRange:NSMakeRange(0, self.length) withData:data]; +} + +- (void)appendData:(NSString *)data +{ + [self replaceDataInRange:NSMakeRange(self.length, 0) withData:data]; +} + +- (void)insertData:(NSString *)data atOffset:(NSUInteger)offset +{ + [self replaceDataInRange:NSMakeRange(offset, 0) withData:data]; +} + +- (void)deleteDataInRange:(NSRange)range +{ + [self replaceDataInRange:range withData:@""]; +} + +- (void)replaceDataInRange:(NSRange)range withData:(NSString *)data +{ + CheckValidOffset(self, range.location, NSStringFromSelector(_cmd)); + + range.length = MIN(range.length, self.length - range.location); + + [_data replaceCharactersInRange:range withString:data]; + [self.ownerDocument didRemoveCharacterDataInNode:self atOffset:range.location withLength:range.length]; + [self.ownerDocument didAddCharacterDataToNode:self atOffset:range.location withLength:data.length]; +} + #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { HTMLCharacterData *copy = [super copyWithZone:zone]; - copy->_data = self.data; + copy->_data = _data; return copy; } diff --git a/Sources/HTMLDocument+Private.h b/Sources/HTMLDocument+Private.h index 1b30e5c..d14c723 100644 --- a/Sources/HTMLDocument+Private.h +++ b/Sources/HTMLDocument+Private.h @@ -8,6 +8,7 @@ #import "HTMLDocument.h" #import "HTMLNode.h" +#import "HTMLCharacterData.h" #import "HTMLNodeIterator.h" #import "HTMLRange.h" @@ -58,4 +59,7 @@ */ - (void)detachRange:(HTMLRange *)range; +- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length; +- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length; + @end diff --git a/Sources/HTMLDocument.m b/Sources/HTMLDocument.m index 9c8f068..8ce00da 100644 --- a/Sources/HTMLDocument.m +++ b/Sources/HTMLDocument.m @@ -10,9 +10,11 @@ #import "HTMLParser.h" #import "HTMLNodeIterator.h" #import "HTMLRange.h" +#import "HTMLCharacterData.h" #import "HTMLKitDOMExceptions.h" #import "HTMLNode+Private.h" #import "HTMLNodeIterator+Private.h" +#import "HTMLRange+Private.h" @interface HTMLDocument () { @@ -147,8 +149,8 @@ { for (HTMLNodeIterator *iterator in _nodeIterators) { [iterator runRemovingStepsForNode:oldNode - withOldParent:oldParent - andOldPreviousSibling:oldPreviousSibling]; + withOldParent:oldParent + andOldPreviousSibling:oldPreviousSibling]; } } @@ -164,6 +166,20 @@ [_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]; + } +} + #pragma mark - Mutation Algorithms - (HTMLNode *)adoptNode:(HTMLNode *)node diff --git a/Sources/HTMLParser.m b/Sources/HTMLParser.m index 15f85ab..aff3f97 100644 --- a/Sources/HTMLParser.m +++ b/Sources/HTMLParser.m @@ -451,10 +451,10 @@ if (adjustedInsertionLocation.nodeType != HTMLNodeDocument) { if (child != nil && child.previousSibling.nodeType == HTMLNodeText) { HTMLText *textNode = (HTMLText *)child.previousSibling; - [textNode.data appendString:data]; + [textNode appendData:data]; } else if (adjustedInsertionLocation.lastChild.nodeType == HTMLNodeText) { HTMLText *textNode = (HTMLText *)adjustedInsertionLocation.lastChild; - [textNode.data appendString:data]; + [textNode appendData:data]; } else { HTMLText *text = [[HTMLText alloc] initWithData:data]; [adjustedInsertionLocation insertNode:text beforeChildNode:child]; diff --git a/Sources/HTMLRange+Private.h b/Sources/HTMLRange+Private.h new file mode 100644 index 0000000..8d7e5d6 --- /dev/null +++ b/Sources/HTMLRange+Private.h @@ -0,0 +1,17 @@ +// +// HTMLRange+Private.h +// HTMLKit +// +// Created by Iska on 27/11/16. +// Copyright © 2016 BrainCookie. All rights reserved. +// + +#import "HTMLRange.h" +#import "HTMLCharacterData.h" + +@interface HTMLRange () + +- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length; +- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length; + +@end