From 644c180f81655c7ba8fbda14b65993c227677b81 Mon Sep 17 00:00:00 2001 From: iska Date: Wed, 26 Apr 2017 21:31:55 +0200 Subject: [PATCH] Improve memory allocation/consumption in the Stack of Open Elements Instead of allocating new dictionaries for the scope elements, the scope checks are just unrolled in-place. Now we have 6 almost identical methods that differ only in the inline-check-method. Not optimal but minimal memory and performance penalty. This should reduce memory consumption and increase the performance while parsing, see issue #10 --- Sources/HTMLParser.m | 6 +- Sources/HTMLStackOfOpenElements.m | 211 +++++++++++++--------- Sources/include/HTMLStackOfOpenElements.h | 6 +- 3 files changed, 134 insertions(+), 89 deletions(-) diff --git a/Sources/HTMLParser.m b/Sources/HTMLParser.m index 3137081..2485650 100644 --- a/Sources/HTMLParser.m +++ b/Sources/HTMLParser.m @@ -1529,7 +1529,7 @@ } [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 @@ } [_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 @@ 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; } 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 @@ 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 @@ #pragma mark - Element Scope +NS_INLINE BOOL IsSpecificScopeElement(HTMLElement *element) +{ + 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]; + } +} + +NS_INLINE BOOL IsHeaderElement(HTMLElement *element) +{ + if (element.htmlNamespace != HTMLNamespaceHTML) { + return NO; + } + + return [element.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]; +} + +NS_INLINE BOOL IsTableScopeElement(HTMLElement *element) +{ + if (element.htmlNamespace != HTMLNamespaceHTML) { + return NO; + } + + return [element.tagName isEqualToAny:@"html", @"table", @"template", nil]; +} + +NS_INLINE BOOL IsListItemScopeElement(HTMLElement *element) +{ + 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; -{ - return [self hasAnyElementInSpecificScopeWithTagNames:@[tagName] andElementTypes:_specificScopeElementTypes]; -} - -- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames -{ - return [self hasAnyElementInSpecificScopeWithTagNames:tagNames andElementTypes:_specificScopeElementTypes]; -} - -- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName -{ - NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:_specificScopeElementTypes]; - [elementTypes addEntriesFromDictionary:@{@"ol": @(HTMLNamespaceHTML), - @"ul": @(HTMLNamespaceHTML)}]; - - return [self hasElementInSpecificScopeWithTagName:tagName - andElementTypes:elementTypes]; -} - -- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName -{ - NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:_specificScopeElementTypes]; - [elementTypes addEntriesFromDictionary:@{@"button": @(HTMLNamespaceHTML)}]; - - return [self hasElementInSpecificScopeWithTagName:tagName - andElementTypes:elementTypes]; -} - -- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName -{ - return [self hasElementInSpecificScopeWithTagName:tagName - andElementTypes:@{@"html": @(HTMLNamespaceHTML), - @"table": @(HTMLNamespaceHTML), - @"template": @(HTMLNamespaceHTML)}]; -} - -- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames -{ - return [self hasAnyElementInSpecificScopeWithTagNames:tagNames - andElementTypes:@{@"html": @(HTMLNamespaceHTML), - @"table": @(HTMLNamespaceHTML), - @"template": @(HTMLNamespaceHTML)}]; -} - -- (HTMLElement *)hasElementInSelectScopeWithTagName:(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)) { return nil; } } return nil; } -- (HTMLElement *)hasElementInSpecificScopeWithTagName:(NSString *)tagName - andElementTypes:(NSDictionary *)elementTypes -{ - return [self hasAnyElementInSpecificScopeWithTagNames:@[tagName] andElementTypes:elementTypes]; -} - -- (HTMLElement *)hasAnyElementInSpecificScopeWithTagNames:(NSArray *)tagNames - andElementTypes:(NSDictionary *)elementTypes +- (HTMLElement *)hasHeaderElementInScope { 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 (IsHeaderElement(node)) { + return node; } - if ([elementTypes[node.tagName] isEqual:@(node.htmlNamespace)]) { + if (IsSpecificScopeElement(node)) { + return nil; + } + } + return nil; +} + +- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName +{ + 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 +{ + for (HTMLElement *node in _stack.reverseObjectEnumerator) { + if (node.htmlNamespace == HTMLNamespaceHTML && [tagNames containsObject:node.tagName]) { + return node; + } + if (IsTableScopeElement(node)) { + return nil; + } + } + return nil; +} + +- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName +{ + for (HTMLElement *node in _stack.reverseObjectEnumerator) { + if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) { + return node; + } + if (IsSpecificScopeElement(node) || IsListItemScopeElement(node)) { + return nil; + } + } + return nil; +} + +- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName +{ + 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 *)hasElementInSelectScopeWithTagName:(NSString *)tagName +{ + for (HTMLElement *node in _stack.reverseObjectEnumerator) { + if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) { + return node; + } + 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; /**