diff --git a/.jazzy.yaml b/.jazzy.yaml index 4bcae51..c95515d 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -1,5 +1,5 @@ module: HTMLKit -module_version: 2.0.5 +module_version: 2.0.6 author: Iskandar Abudiab author_url: https://twitter.com/iabudiab github_url: https://github.com/iabudiab/HTMLKit diff --git a/.travis.yml b/.travis.yml index 313ee3f..980d094 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode8.2 +osx_image: xcode8.3 branches: except: @@ -17,21 +17,19 @@ env: - MACOS_FRAMEWORK_SCHEME=HTMLKit-macOS - WATCHOS_FRAMEWORK_SCHEME="HTMLKit-watchOS" - TVOS_FRAMEWORK_SCHEME="HTMLKit-tvOS" - - IOS_SDK=iphonesimulator10.2 + - IOS_SDK=iphonesimulator10.3 - MACOS_SDK=macosx10.12 - - WATCHOS_SDK=watchsimulator3.1 - - TVOS_SDK=appletvsimulator10.1 + - WATCHOS_SDK=watchsimulator3.2 + - TVOS_SDK=appletvsimulator10.2 matrix: - DESTINATION="arch=x86_64" SIMULATOR="" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK" - - DESTINATION="OS=9.0,name=iPhone 6" SIMULATOR="iPhone 6 (9.0)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - - DESTINATION="OS=9.1,name=iPhone 6 Plus" SIMULATOR="iPhone 6 Plus (9.1)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - - DESTINATION="OS=9.2,name=iPhone 6S" SIMULATOR="iPhone 6S (9.2)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" + - DESTINATION="OS=8.4,name=iPhone 6 Plus" SIMULATOR="iPhone 6 Plus (8.4)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - DESTINATION="OS=9.3,name=iPhone 6S Plus" SIMULATOR="iPhone 6S Plus (9.3)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - - DESTINATION="OS=10.1,name=iPhone 7 Plus" SIMULATOR="iPhone 7 Plus (10.1)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" + - DESTINATION="OS=10.3,name=iPhone 7 Plus" SIMULATOR="iPhone 7 Plus (10.3)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - DESTINATION="OS=2.2,name=Apple Watch - 42mm" SIMULATOR="Apple Watch - 42mm (2.2)" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" - - DESTINATION="OS=3.1,name=Apple Watch Series 2 - 42mm" SIMULATOR="Apple Watch Series 2 - 42mm (3.1)" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" - - DESTINATION="OS=9.0,name=Apple TV 1080p" SIMULATOR="Apple TV 1080p (9.2)" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" - - DESTINATION="OS=10.0,name=Apple TV 1080p" SIMULATOR="Apple TV 1080p (10.0)" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" + - DESTINATION="OS=3.2,name=Apple Watch Series 2 - 42mm" SIMULATOR="Apple Watch Series 2 - 42mm (3.2)" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" + - DESTINATION="OS=9.2,name=Apple TV 1080p" SIMULATOR="Apple TV 1080p (9.2)" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" + - DESTINATION="OS=10.2,name=Apple TV 1080p" SIMULATOR="Apple TV 1080p (10.2)" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" script: - set -o pipefail diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f67a6c..dcf1f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,30 @@ # Change Log +## [2.0.6](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.6) + +Released on 2017.05.02 + +### Added + +- Memory consumption improvements (issue #10) + - Allocate `childNodes` collection in `HTMLNode` only when inserting child nodes + - Replace `NSStringFromSelector` calls with constants in `HTMLNode` validations + - Improve `reverseObjectEnumerator` usage while parsing HTML + - Rewrite internal logic of the `HTMLStackOfOpenElements` to prevent excessive allocations + + ## [2.0.5](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.5) Released on 2017.04.19 ### Fixed + - Xcode 8.3 issue with modulemaps - Temporary workaround (renamed modulemap file) - Memory Leaks in `CSSInputStream` ### Added + - Minor memory consumption improvements - Collections for child nodes or attributes of HTML Nodes or Elements are allocated lazily - Underyling data string of `CharacterData` is allocated on first access @@ -21,10 +36,12 @@ Released on 2017.04.19 Released on 2017.04.2 ### Fixed + - Testing with Swift 3.1 - Fixed by @tali in PR #8 ### Deprecated + - `HTMLRange` initializers with typo - `initWithDowcument:startContainer:startOffset:endContainer:endOffset:` @@ -34,6 +51,7 @@ Released on 2017.04.2 Released on 2017.03.6 ### Fixed + - Compilation for Swift 3.1 - Fixed by @tali in PR #6 @@ -43,6 +61,7 @@ Released on 2017.03.6 Released on 2017.02.26 ### Fixed + - Retain cycles in `HTMLNodeIterator` (issue #4) - Retain cycles in `HTMLRange` (issue #5) - The layout of `HTMLKit` tests module for Swift Package Manager diff --git a/HTMLKit.podspec b/HTMLKit.podspec index 4991a29..50e830e 100644 --- a/HTMLKit.podspec +++ b/HTMLKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "HTMLKit" - s.version = "2.0.5" + s.version = "2.0.6" s.summary = "HTMLKit, an Objective-C framework for your everyday HTML needs." s.license = "MIT" s.homepage = "https://github.com/iabudiab/HTMLKit" diff --git a/Sources/HTMLKit-Info.plist b/Sources/HTMLKit-Info.plist index 11b6ce9..7b6a588 100644 --- a/Sources/HTMLKit-Info.plist +++ b/Sources/HTMLKit-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0.5 + 2.0.6 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/HTMLListOfActiveFormattingElements.m b/Sources/HTMLListOfActiveFormattingElements.m index d0fb8b0..665ce8f 100644 --- a/Sources/HTMLListOfActiveFormattingElements.m +++ b/Sources/HTMLListOfActiveFormattingElements.m @@ -46,7 +46,7 @@ - (NSUInteger)indexOfElement:(id)node - (void)addElement:(HTMLElement *)element { NSUInteger existing = 0; - for (HTMLElement *node in _list.reverseObjectEnumerator.allObjects) { + for (HTMLElement *node in _list.reverseObjectEnumerator) { if ([node isEqual:[HTMLMarker marker]]) { break; } diff --git a/Sources/HTMLNode.m b/Sources/HTMLNode.m index da9d2b6..be9bb9e 100644 --- a/Sources/HTMLNode.m +++ b/Sources/HTMLNode.m @@ -19,6 +19,10 @@ #import "HTMLDocument+Private.h" #import "HTMLDOMUtils.h" +NSString * const ValidationNodePreInsertion = @"-ensurePreInsertionValidityOfNode:beforeChildNode:"; +NSString * const ValidationNodeReplacement = @"-ensureReplacementValidityOfChildNode:withNode:"; +NSString * const RemoveChildNode = @"-removeChildNode:"; + @interface HTMLNode () { NSMutableOrderedSet *_childNodes; @@ -64,7 +68,7 @@ - (HTMLDocument *)ownerDocument - (void)setOwnerDocument:(HTMLDocument *)ownerDocument { _ownerDocument = ownerDocument; - for (HTMLNode *child in self.childNodes) { + for (HTMLNode *child in _childNodes) { [child setOwnerDocument:ownerDocument]; } } @@ -86,12 +90,12 @@ - (HTMLElement *)parentElement - (HTMLNode *)firstChild { - return self.childNodes.firstObject; + return _childNodes.firstObject; } - (HTMLNode *)lastChild { - return self.childNodes.lastObject; + return _childNodes.lastObject; } - (HTMLNode *)previousSibling @@ -132,7 +136,7 @@ - (HTMLElement *)nextSiblingElement - (NSUInteger)index { - return [self.parentNode indexOfChildNode:self]; + return [_parentNode indexOfChildNode:self]; } - (NSString *)textContent @@ -156,12 +160,16 @@ - (HTMLElement *)asElement - (BOOL)hasChildNodes { - return self.childNodes.count > 0; + return _childNodes.count > 0; } - (BOOL)hasChildNodeOfType:(HTMLNodeType)type { - NSUInteger index = [self.childNodes indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { + if (_childNodes == nil) { + return NO; + } + + NSUInteger index = [_childNodes indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { if ([(HTMLNode *)obj nodeType] == type) { *stop = YES; return YES; @@ -174,7 +182,7 @@ - (BOOL)hasChildNodeOfType:(HTMLNodeType)type - (NSUInteger)childNodesCount { - return self.childNodes.count; + return _childNodes.count; } - (BOOL)isEmpty @@ -184,25 +192,25 @@ - (BOOL)isEmpty - (HTMLNode *)childNodeAtIndex:(NSUInteger)index { - return [self.childNodes objectAtIndex:index]; + return [_childNodes objectAtIndex:index]; } - (NSUInteger)childElementsCount { - return [self.childNodes indexesOfObjectsPassingTest:^BOOL(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { + return [_childNodes indexesOfObjectsPassingTest:^BOOL(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) { return node.nodeType == HTMLNodeElement; }].count; } - (NSUInteger)indexOfChildNode:(HTMLNode *)node { - return [self.childNodes indexOfObject:node]; + return [_childNodes indexOfObject:node]; } - (HTMLElement *)childElementAtIndex:(NSUInteger)index { NSUInteger counter = 0; - for (HTMLNode *node in self.childNodes) { + for (HTMLNode *node in _childNodes) { if (node.nodeType == HTMLNodeElement) { if (counter == index) { return node.asElement; @@ -216,7 +224,7 @@ - (HTMLElement *)childElementAtIndex:(NSUInteger)index - (NSUInteger)indexOfChildElement:(HTMLElement *)element { NSUInteger counter = 0; - for (HTMLNode *node in self.childNodes) { + for (HTMLNode *node in _childNodes) { if (node.nodeType == HTMLNodeElement) { if (node == element) { return counter; @@ -303,7 +311,7 @@ - (void)replaceAllChildNodesWithNode:(HTMLNode *)node - (void)removeFromParentNode { - [self.parentNode removeChildNode:self]; + [_parentNode removeChildNode:self]; } - (HTMLNode *)removeChildNode:(HTMLNode *)child @@ -311,7 +319,7 @@ - (HTMLNode *)removeChildNode:(HTMLNode *)child if (child.parentNode != self) { [NSException raise:HTMLKitNotFoundError format:@"%@: Not Fount Error, removing non-child node %@. The object can not be found here.", - NSStringFromSelector(_cmd), child]; + RemoveChildNode, child]; } HTMLNode *oldNode = child; @@ -335,18 +343,18 @@ - (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index - (void)reparentChildNodesIntoNode:(HTMLNode *)node { - for (HTMLNode *child in self.childNodes) { + for (HTMLNode *child in _childNodes) { [node appendNode:child]; } - [(NSMutableOrderedSet *)self.childNodes removeAllObjects]; + [(NSMutableOrderedSet *)_childNodes removeAllObjects]; } - (void)removeAllChildNodes { - for (HTMLNode *child in self.childNodes) { + for (HTMLNode *child in _childNodes) { [child setParentNode:nil]; } - [(NSMutableOrderedSet *)self.childNodes removeAllObjects]; + [(NSMutableOrderedSet *)_childNodes removeAllObjects]; } - (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)otherNode @@ -417,7 +425,7 @@ - (BOOL)isDescendantOfNode:(HTMLNode *)otherNode return self.nodeType != HTMLNodeDocument && self.ownerDocument == otherNode; } - for (HTMLNode *parentNode = self.parentNode; parentNode; parentNode = parentNode.parentNode) { + for (HTMLNode *parentNode = _parentNode; parentNode; parentNode = parentNode.parentNode) { if (parentNode == otherNode) { return YES; } @@ -439,7 +447,7 @@ - (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, return; } - [self.childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [_childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { block(obj, idx, stop); }]; } @@ -450,7 +458,7 @@ - (void)enumerateChildElementsUsingBlock:(void (^)(HTMLElement *element, NSUInte return; } - [self.childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + [_childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj isKindOfClass:[HTMLElement class]]) { block([obj asElement], idx, stop); } @@ -573,18 +581,18 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin - (void)ensurePreInsertionValidityOfNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child { - CheckParentValid(self, NSStringFromSelector(_cmd)); + CheckParentValid(self, ValidationNodePreInsertion); - CheckChildsParent(self, child, NSStringFromSelector(_cmd)); + CheckChildsParent(self, child, ValidationNodePreInsertion); - CheckInsertedNodeValid(node, NSStringFromSelector(_cmd)); + CheckInsertedNodeValid(node, ValidationNodePreInsertion); - CheckInvalidCombination(self, node, NSStringFromSelector(_cmd)); + CheckInvalidCombination(self, node, ValidationNodePreInsertion); void (^ hierarchyError)() = ^{ [NSException raise:HTMLKitHierarchyRequestError format:@"%@: Hierarchy Request Error, inserting (%@) into (%@). The operation would yield an incorrect node tree.", - NSStringFromSelector(_cmd), self, node]; + ValidationNodePreInsertion, self, node]; }; if (self.nodeType == HTMLNodeDocument) { @@ -623,18 +631,18 @@ - (void)ensurePreInsertionValidityOfNode:(HTMLNode *)node beforeChildNode:(HTMLN - (void)ensureReplacementValidityOfChildNode:(HTMLNode *)child withNode:(HTMLNode *)node { - CheckParentValid(self, NSStringFromSelector(_cmd)); + CheckParentValid(self, ValidationNodeReplacement); - CheckChildsParent(self, child, NSStringFromSelector(_cmd)); + CheckChildsParent(self, child, ValidationNodeReplacement); - CheckInsertedNodeValid(node, NSStringFromSelector(_cmd)); + CheckInsertedNodeValid(node, ValidationNodeReplacement); - CheckInvalidCombination(self, node, NSStringFromSelector(_cmd)); + CheckInvalidCombination(self, node, ValidationNodeReplacement); void (^ hierarchyError)() = ^{ [NSException raise:HTMLKitHierarchyRequestError format:@"%@: Hierarchy Request Error. The operation would yield an incorrect node tree.", - NSStringFromSelector(_cmd)]; + ValidationNodeReplacement]; }; void (^ checkParentHasAnotherChildOfType)(HTMLNodeType) = ^ void (HTMLNodeType type) { @@ -690,7 +698,7 @@ - (instancetype)cloneNodeDeep:(BOOL)deep HTMLNode *copy = [self copy]; if (deep) { - for (HTMLNode *child in self.childNodes) { + for (HTMLNode *child in _childNodes) { [copy appendNode:[child cloneNodeDeep:YES]]; } } diff --git a/Sources/HTMLParser.m b/Sources/HTMLParser.m index 74844bc..2485650 100644 --- a/Sources/HTMLParser.m +++ b/Sources/HTMLParser.m @@ -1272,7 +1272,7 @@ - (void)processStartTagTokenInBody:(HTMLStartTagToken *)token @"dd": @[@"dd", @"dt"], @"dt": @[@"dd", @"dt"]}; - for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator.allObjects) { + for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator) { if ([map[tagName] containsObject:node.tagName]) { [self generateImpliedEndTagsExceptForElement:node.tagName]; if (![self.currentNode.tagName isEqualToString:node.tagName]) { @@ -1529,7 +1529,7 @@ - (void)processEndTagTokenInBody:(HTMLEndTagToken *)token } [self closePElement]; } else if ([tagName isEqualToString:@"li"]) { - if (![_stackOfOpenElements hasElementInListItemScopeWithTagName:@"li"]) { + if (![_stackOfOpenElements hasElementInListItemScopeWithTagName:tagName]) { [self emitParseError:@"Unexpected
  • element in "]; return; } @@ -1549,7 +1549,7 @@ - (void)processEndTagTokenInBody:(HTMLEndTagToken *)token } [_stackOfOpenElements popElementsUntilElementPoppedWithTagName:tagName]; } else if ([tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) { - if (![_stackOfOpenElements hasAnyElementInScopeWithAnyOfTagNames:@[@"h1", @"h2", @"h3", @"h4", @"h5", @"h6"]]) { + if (![_stackOfOpenElements hasHeaderElementInScope]) { [self emitParseError:@"Unexpected <%@> element in ", tagName]; return; } @@ -1569,7 +1569,7 @@ - (void)processEndTagTokenInBody:(HTMLEndTagToken *)token return; } } else if ([tagName isEqualToAny:@"applet", @"marquee", @"object", nil]) { - if (![_stackOfOpenElements hasAnyElementInScopeWithAnyOfTagNames:@[@"applet", @"marquee", @"object"]]) { + if (![_stackOfOpenElements hasElementInScopeWithTagName:tagName]) { [self emitParseError:@"Unexpected <%@> element in ", tagName]; return; } @@ -1590,7 +1590,7 @@ - (void)processEndTagTokenInBody:(HTMLEndTagToken *)token - (void)processAnyOtherEndTagTokenInBody:(HTMLTagToken *)token { - for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator.allObjects) { + for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator) { if ([node.tagName isEqualToString:token.tagName]) { [self generateImpliedEndTagsExceptForElement:token.tagName]; if (![node.tagName isEqualToString:self.currentNode.tagName]) { diff --git a/Sources/HTMLStackOfOpenElements.m b/Sources/HTMLStackOfOpenElements.m index daf1517..7420537 100644 --- a/Sources/HTMLStackOfOpenElements.m +++ b/Sources/HTMLStackOfOpenElements.m @@ -14,7 +14,6 @@ @interface HTMLStackOfOpenElements () { NSMutableArray *_stack; - NSDictionary *_specificScopeElementTypes; } @end @@ -27,26 +26,6 @@ - (instancetype)init self = [super init]; if (self) { _stack = [NSMutableArray new]; - _specificScopeElementTypes = @{ - @"applet": @(HTMLNamespaceHTML), - @"caption": @(HTMLNamespaceHTML), - @"html": @(HTMLNamespaceHTML), - @"table": @(HTMLNamespaceHTML), - @"td": @(HTMLNamespaceHTML), - @"th": @(HTMLNamespaceHTML), - @"marquee": @(HTMLNamespaceHTML), - @"object": @(HTMLNamespaceHTML), - @"template": @(HTMLNamespaceHTML), - @"mi": @(HTMLNamespaceMathML), - @"mo": @(HTMLNamespaceMathML), - @"mn": @(HTMLNamespaceMathML), - @"ms": @(HTMLNamespaceMathML), - @"mtext": @(HTMLNamespaceMathML), - @"annotation-xml": @(HTMLNamespaceMathML), - @"foreignObject": @(HTMLNamespaceSVG), - @"desc": @(HTMLNamespaceSVG), - @"title": @(HTMLNamespaceSVG) - }; } return self; } @@ -195,82 +174,148 @@ - (void)popAll #pragma mark - Element Scope -- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName; +NS_INLINE BOOL IsSpecificScopeElement(HTMLElement *element) { - return [self hasAnyElementInSpecificScopeWithTagNames:@[tagName] andElementTypes:_specificScopeElementTypes]; + switch (element.htmlNamespace) { + case HTMLNamespaceHTML: + return [element.tagName isEqualToAny:@"applet", @"caption", @"html", @"table", @"td", @"th", @"marquee", @"object", @"template", nil]; + case HTMLNamespaceMathML: + return [element.tagName isEqualToAny:@"mi", @"mo", @"mn", @"ms", @"mtext", @"annotation-xml", nil]; + case HTMLNamespaceSVG: + return [element.tagName isEqualToAny:@"foreignObject", @"desc", @"title", nil]; + } } -- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames +NS_INLINE BOOL IsHeaderElement(HTMLElement *element) { - return [self hasAnyElementInSpecificScopeWithTagNames:tagNames andElementTypes:_specificScopeElementTypes]; + if (element.htmlNamespace != HTMLNamespaceHTML) { + return NO; + } + + return [element.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]; } -- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName +NS_INLINE BOOL IsTableScopeElement(HTMLElement *element) { - NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:_specificScopeElementTypes]; - [elementTypes addEntriesFromDictionary:@{@"ol": @(HTMLNamespaceHTML), - @"ul": @(HTMLNamespaceHTML)}]; + if (element.htmlNamespace != HTMLNamespaceHTML) { + return NO; + } - return [self hasElementInSpecificScopeWithTagName:tagName - andElementTypes:elementTypes]; + return [element.tagName isEqualToAny:@"html", @"table", @"template", nil]; } -- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName +NS_INLINE BOOL IsListItemScopeElement(HTMLElement *element) { - NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:_specificScopeElementTypes]; - [elementTypes addEntriesFromDictionary:@{@"button": @(HTMLNamespaceHTML)}]; + if (element.htmlNamespace != HTMLNamespaceHTML) { + return NO; + } + + return [element.tagName isEqualToAny:@"ol", @"ul", nil]; +} + +NS_INLINE BOOL IsSelectScopeElement(HTMLElement *element) +{ + if (element.htmlNamespace != HTMLNamespaceHTML) { + return NO; + } + + return ![element.tagName isEqualToString:@"optgroup"] && ![element.tagName isEqualToString:@"option"]; +} + +NS_INLINE BOOL IsButtonScopeElement(HTMLElement *element) +{ + if (element.htmlNamespace != HTMLNamespaceHTML) { + return NO; + } + + return [element.tagName isEqualToString:@"button"]; +} + +- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName; +{ + for (HTMLElement *node in _stack.reverseObjectEnumerator) { + if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) { + return node; + } + if (IsSpecificScopeElement(node)) { + return nil; + } + } + return nil; +} - return [self hasElementInSpecificScopeWithTagName:tagName - andElementTypes:elementTypes]; +- (HTMLElement *)hasHeaderElementInScope +{ + for (HTMLElement *node in _stack.reverseObjectEnumerator) { + if (IsHeaderElement(node)) { + return node; + } + if (IsSpecificScopeElement(node)) { + return nil; + } + } + return nil; } - (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName { - return [self hasElementInSpecificScopeWithTagName:tagName - andElementTypes:@{@"html": @(HTMLNamespaceHTML), - @"table": @(HTMLNamespaceHTML), - @"template": @(HTMLNamespaceHTML)}]; + for (HTMLElement *node in _stack.reverseObjectEnumerator) { + if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) { + return node; + } + if (IsTableScopeElement(node)) { + return nil; + } + } + return nil; } - (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames { - return [self hasAnyElementInSpecificScopeWithTagNames:tagNames - andElementTypes:@{@"html": @(HTMLNamespaceHTML), - @"table": @(HTMLNamespaceHTML), - @"template": @(HTMLNamespaceHTML)}]; + for (HTMLElement *node in _stack.reverseObjectEnumerator) { + if (node.htmlNamespace == HTMLNamespaceHTML && [tagNames containsObject:node.tagName]) { + return node; + } + if (IsTableScopeElement(node)) { + return nil; + } + } + return nil; } -- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName +- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName { for (HTMLElement *node in _stack.reverseObjectEnumerator) { - if ([node.tagName isEqualToString:tagName]) { + if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) { return node; } - if (!(node.htmlNamespace == HTMLNamespaceHTML && - [node.tagName isEqualToAny:@"optgroup", @"option", nil])) { + if (IsSpecificScopeElement(node) || IsListItemScopeElement(node)) { return nil; } } return nil; } -- (HTMLElement *)hasElementInSpecificScopeWithTagName:(NSString *)tagName - andElementTypes:(NSDictionary *)elementTypes +- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName { - return [self hasAnyElementInSpecificScopeWithTagNames:@[tagName] andElementTypes:elementTypes]; + for (HTMLElement *node in _stack.reverseObjectEnumerator) { + if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) { + return node; + } + if (IsSpecificScopeElement(node) || IsButtonScopeElement(node)) { + return nil; + } + } + return nil; } -- (HTMLElement *)hasAnyElementInSpecificScopeWithTagNames:(NSArray *)tagNames - andElementTypes:(NSDictionary *)elementTypes +- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName { for (HTMLElement *node in _stack.reverseObjectEnumerator) { - if ([tagNames containsObject:node.tagName]) { - NSNumber *namespace = elementTypes[node.tagName] ?: @(HTMLNamespaceHTML); - if ([namespace isEqual:@(node.htmlNamespace)]) { - return node; - } + if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) { + return node; } - if ([elementTypes[node.tagName] isEqual:@(node.htmlNamespace)]) { + if (IsSelectScopeElement(node)) { return nil; } } diff --git a/Sources/include/HTMLStackOfOpenElements.h b/Sources/include/HTMLStackOfOpenElements.h index eccae52..a7f0e0e 100644 --- a/Sources/include/HTMLStackOfOpenElements.h +++ b/Sources/include/HTMLStackOfOpenElements.h @@ -163,11 +163,11 @@ https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-the-specific-scope */ - (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName; -- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames; -- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName; -- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName; +- (HTMLElement *)hasHeaderElementInScope; - (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName; - (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames; +- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName; +- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName; - (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName; /**