From 0495404f018ed45d2ac4d6add01d8097f3c0c434 Mon Sep 17 00:00:00 2001 From: iska Date: Mon, 9 Mar 2015 00:34:06 +0100 Subject: [PATCH] Add a wrapper class for the Stack of Open Elements - Better understandability - Prevent polluting the parser --- HTMLKit.xcodeproj/project.pbxproj | 10 ++ HTMLKit/HTMLParser.m | 125 +++++-------------- HTMLKit/HTMLStackOfOpenElements.h | 40 ++++++ HTMLKit/HTMLStackOfOpenElements.m | 201 ++++++++++++++++++++++++++++++ 4 files changed, 283 insertions(+), 93 deletions(-) create mode 100644 HTMLKit/HTMLStackOfOpenElements.h create mode 100644 HTMLKit/HTMLStackOfOpenElements.m diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj index 12727ea..909fc1f 100644 --- a/HTMLKit.xcodeproj/project.pbxproj +++ b/HTMLKit.xcodeproj/project.pbxproj @@ -10,6 +10,9 @@ 6234584E1A9D2FA4009BD491 /* HTMLNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 6234584C1A9D2FA4009BD491 /* HTMLNode.h */; }; 6234584F1A9D2FA4009BD491 /* HTMLNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 6234584D1A9D2FA4009BD491 /* HTMLNode.m */; }; 623458501A9D2FA4009BD491 /* HTMLNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 6234584D1A9D2FA4009BD491 /* HTMLNode.m */; }; + 6234BEEE1AABBF1400DEB15F /* HTMLStackOfOpenElements.h in Headers */ = {isa = PBXBuildFile; fileRef = 6234BEEC1AABBF1400DEB15F /* HTMLStackOfOpenElements.h */; }; + 6234BEEF1AABBF1400DEB15F /* HTMLStackOfOpenElements.m in Sources */ = {isa = PBXBuildFile; fileRef = 6234BEED1AABBF1400DEB15F /* HTMLStackOfOpenElements.m */; }; + 6234BEF01AABBF1400DEB15F /* HTMLStackOfOpenElements.m in Sources */ = {isa = PBXBuildFile; fileRef = 6234BEED1AABBF1400DEB15F /* HTMLStackOfOpenElements.m */; }; 6235CE9B1AA509430026937B /* NSString+HTMLKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6235CE991AA509430026937B /* NSString+HTMLKit.h */; }; 6235CE9C1AA509430026937B /* NSString+HTMLKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 6235CE9A1AA509430026937B /* NSString+HTMLKit.m */; }; 6235CE9D1AA509430026937B /* NSString+HTMLKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 6235CE9A1AA509430026937B /* NSString+HTMLKit.m */; }; @@ -122,6 +125,8 @@ 6223211D1A969B9300BACED5 /* HTMLElementTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLElementTypes.h; sourceTree = ""; }; 6234584C1A9D2FA4009BD491 /* HTMLNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNode.h; sourceTree = ""; }; 6234584D1A9D2FA4009BD491 /* HTMLNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNode.m; sourceTree = ""; }; + 6234BEEC1AABBF1400DEB15F /* HTMLStackOfOpenElements.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLStackOfOpenElements.h; sourceTree = ""; }; + 6234BEED1AABBF1400DEB15F /* HTMLStackOfOpenElements.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLStackOfOpenElements.m; sourceTree = ""; }; 6235CE991AA509430026937B /* NSString+HTMLKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+HTMLKit.h"; sourceTree = ""; }; 6235CE9A1AA509430026937B /* NSString+HTMLKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+HTMLKit.m"; sourceTree = ""; }; 6235CE9E1AA5170A0026937B /* HTMLMarker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLMarker.h; sourceTree = ""; }; @@ -300,6 +305,8 @@ 62AE593219F97CCA0043F069 /* Tokenizing */, 62F873E919E088C90062683C /* HTMLParser.h */, 62F873EA19E088C90062683C /* HTMLParser.m */, + 6234BEEC1AABBF1400DEB15F /* HTMLStackOfOpenElements.h */, + 6234BEED1AABBF1400DEB15F /* HTMLStackOfOpenElements.m */, 628B7CE61A080E1000602C87 /* HTMLNamespaces.h */, 6223211D1A969B9300BACED5 /* HTMLElementTypes.h */, 6279F87119E17DC700F12EE5 /* HTMLParserInsertionModes.h */, @@ -391,6 +398,7 @@ 624493AC19CD0CBE00BCDDF4 /* HTMLToken.h in Headers */, 6235CEA01AA5170A0026937B /* HTMLMarker.h in Headers */, 62F31FDD19E9DCCF007F0657 /* HTMLTokenizerEntities.h in Headers */, + 6234BEEE1AABBF1400DEB15F /* HTMLStackOfOpenElements.h in Headers */, 6279F87419E1808D00F12EE5 /* HTMLElement.h in Headers */, 62AE593F19F9907C0043F069 /* HTMLTagToken.h in Headers */, 623857911A9E772B003A45D9 /* HTMLDocument.h in Headers */, @@ -556,6 +564,7 @@ 62362A3E1A9FA70400301989 /* HTMLText.m in Sources */, 6235CE9D1AA509430026937B /* NSString+HTMLKit.m in Sources */, 6238579D1A9E8934003A45D9 /* HTMLComment.m in Sources */, + 6234BEF01AABBF1400DEB15F /* HTMLStackOfOpenElements.m in Sources */, 624493AE19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */, 6235CEA21AA5170A0026937B /* HTMLMarker.m in Sources */, 62AE594119F9907C0043F069 /* HTMLTagToken.m in Sources */, @@ -592,6 +601,7 @@ 62362A3D1A9FA70400301989 /* HTMLText.m in Sources */, 6235CE9C1AA509430026937B /* NSString+HTMLKit.m in Sources */, 6238579C1A9E8934003A45D9 /* HTMLComment.m in Sources */, + 6234BEEF1AABBF1400DEB15F /* HTMLStackOfOpenElements.m in Sources */, 624493AD19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */, 6235CEA11AA5170A0026937B /* HTMLMarker.m in Sources */, 62AE594019F9907C0043F069 /* HTMLTagToken.m in Sources */, diff --git a/HTMLKit/HTMLParser.m b/HTMLKit/HTMLParser.m index b201392..eb6d6d1 100644 --- a/HTMLKit/HTMLParser.m +++ b/HTMLKit/HTMLParser.m @@ -9,6 +9,7 @@ #import "HTMLParser.h" #import "HTMLTokenizer.h" #import "HTMLTokens.h" +#import "HTMLStackOfOpenElements.h" #import "HTMLParserInsertionModes.h" #import "HTMLNodes.h" #import "HTMLElementTypes.h" @@ -25,7 +26,7 @@ HTMLInsertionMode _insertionMode; HTMLInsertionMode _originalInsertionMode; - NSMutableArray *_stackOfOpenElements; + HTMLStackOfOpenElements *_stackOfOpenElements; NSMutableArray *_listOfActiveFormattingElements; HTMLDocument *_document; @@ -57,7 +58,7 @@ _insertionMode = HTMLInsertionModeInitial; [self setupStateMachine]; - _stackOfOpenElements = [NSMutableArray new]; + _stackOfOpenElements = [HTMLStackOfOpenElements new]; _listOfActiveFormattingElements = [NSMutableArray new]; _tokenizer = [[HTMLTokenizer alloc] initWithString:string]; @@ -82,7 +83,7 @@ - (HTMLElement *)currentNode { - return _stackOfOpenElements.lastObject; + return _stackOfOpenElements.currentNode; } - (HTMLElement *)adjustedCurrentNode @@ -117,12 +118,12 @@ - (void)resetInsertionModeAppropriately { BOOL last = NO; - HTMLElement *node = _stackOfOpenElements.lastObject; + HTMLElement *node = _stackOfOpenElements.lastNode; NSUInteger nodeIndex = _stackOfOpenElements.count - 1; while (YES) { - if ([_stackOfOpenElements.firstObject isEqual:node]) { + if ([_stackOfOpenElements.firstNode isEqual:node]) { last = YES; if (_fragmentParsingAlgorithm) { node = _contextElement; @@ -135,12 +136,12 @@ NSUInteger ancestorIndex = nodeIndex; while (YES) { - if ([ancestor isEqual:_stackOfOpenElements.firstObject]) { + if ([ancestor isEqual:_stackOfOpenElements.firstNode]) { break; } ancestorIndex--; - ancestor = [_stackOfOpenElements objectAtIndex:ancestorIndex]; + ancestor = _stackOfOpenElements[ancestorIndex]; if ([ancestor.tagName isEqualToString:@"template"]) { break; @@ -225,7 +226,7 @@ } nodeIndex--; - node = [_stackOfOpenElements objectAtIndex:nodeIndex]; + node = _stackOfOpenElements[nodeIndex]; } } @@ -367,13 +368,13 @@ return nil; } if (lastTable == nil) { - HTMLElement *htmlElement = _stackOfOpenElements.firstObject; + HTMLElement *htmlElement = _stackOfOpenElements.firstNode; return htmlElement; } if (lastTable.parentNode != nil) { return lastTable.parentNode; } - NSUInteger lastTableIndex = [_stackOfOpenElements indexOfObject:lastTable]; + NSUInteger lastTableIndex = [_stackOfOpenElements indexOfElement:lastTable]; HTMLElement *previousNode = _stackOfOpenElements[lastTableIndex]; return previousNode; } else { @@ -407,7 +408,7 @@ HTMLElement *element = [self createElementForToken:token inNamespace:HTMLNamespaceHTML]; HTMLNode *adjustedInsertionLocation = [self appropriatePlaceForInsertingANodeWithOverrideTarget:nil]; [adjustedInsertionLocation appendChildNode:element]; - [_stackOfOpenElements addObject:element]; + [_stackOfOpenElements pushElement:element]; return element; } @@ -434,7 +435,7 @@ } id last = _listOfActiveFormattingElements.lastObject; - if (last == [HTMLMarker marker] || [_stackOfOpenElements containsObject:last]) { + if (last == [HTMLMarker marker] || [_stackOfOpenElements constainsElement:last]) { return; } @@ -465,7 +466,7 @@ create(); } entry = _listOfActiveFormattingElements[index--]; - if (entry != [HTMLMarker marker] && ![_stackOfOpenElements containsObject:entry]) { + if (entry != [HTMLMarker marker] && ![_stackOfOpenElements constainsElement:entry]) { rewind(); } }; @@ -480,75 +481,11 @@ } } -- (HTMLElement *)hasElementWithTagNameInSpecificScope:(NSString *)tagName - withAdditionalTypes:(NSDictionary *)additional -{ - NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:additional]; - [elementTypes addEntriesFromDictionary:@{ - @"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) - }]; - - for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator) { - if ([node.tagName isEqualToString:tagName]) { - return node; - } - if ([elementTypes[node.tagName] isEqual:@(node.namespace)]) { - return nil; - } - } - return nil; -} - -- (HTMLElement *)hasElementWithTagNameInButtonScope:(NSString *)tagName -{ - return [self hasElementWithTagNameInSpecificScope:tagName - withAdditionalTypes:@{@"ol": @(HTMLNamespaceHTML), - @"ul": @(HTMLNamespaceHTML)}]; -} - -- (HTMLElement *)hasElementWithTagNameInListScope:(NSString *)tagName -{ - return [self hasElementWithTagNameInSpecificScope:tagName - withAdditionalTypes:@{@"button": @(HTMLNamespaceHTML)}]; -} - -- (HTMLElement *)hasElementWithTagNameInTableScope:(NSString *)tagName -{ - return [self hasElementWithTagNameInSpecificScope:tagName - withAdditionalTypes:@{@"html": @(HTMLNamespaceHTML), - @"table": @(HTMLNamespaceHTML), - @"template": @(HTMLNamespaceHTML)}]; -} - -- (HTMLElement *)hasElementWithTagNameInSelectScope:(NSString *)tagName -{ - return [self hasElementWithTagNameInSpecificScope:tagName - withAdditionalTypes:@{@"optgroup": @(HTMLNamespaceHTML), - @"option": @(HTMLNamespaceHTML)}]; -} - - (void)generateImpliedEndTagsExceptForElement:(NSString *)tagName { while ([self.currentNode.tagName isEqualToAny:@"dd", @"dt", @"li", @"option", @"optgroup", @"p", @"rp", @"rt", nil] && ![self.currentNode.tagName isEqualToString:tagName]) { - [_stackOfOpenElements removeLastObject]; + [_stackOfOpenElements popCurrentNode]; } } @@ -558,8 +495,8 @@ if (![self.currentNode.tagName isEqualToString:@"p"]) { [self emitParseError:@"Current node being closed is not a

element"]; } - while (![self.currentNode.tagName isEqualToString:@"p"]) { - [_stackOfOpenElements removeLastObject]; + [_stackOfOpenElements popElementsUntilElementPoppedWithTagName:@"p"]; +} } [_stackOfOpenElements removeLastObject]; } @@ -625,7 +562,7 @@ if ([token.asStartTagToken.tagName isEqualToString:@"html"]) { HTMLElement *html = [self createElementForToken:token.asTagToken inNamespace:HTMLNamespaceHTML]; [_document appendChildNode:html]; - [_stackOfOpenElements addObject:html]; + [_stackOfOpenElements pushElement:html]; [self switchInsertionMode:HTMLInsertionModeBeforeHead]; return; } @@ -642,7 +579,7 @@ HTMLElement *html = [[HTMLElement alloc] initWithTagName:@"html"]; [_document appendChildNode:html]; - [_stackOfOpenElements addObject:html]; + [_stackOfOpenElements pushElement:html]; [self switchInsertionMode:HTMLInsertionModeBeforeHead]; [self reprocessToken:token]; } @@ -713,11 +650,11 @@ return; } else if ([token.asStartTagToken.tagName isEqualToAny:@"base", @"basefont", @"bgsound", @"link", nil]) { [self insertElementForToken:token.asStartTagToken]; - [_stackOfOpenElements removeLastObject]; + [_stackOfOpenElements popCurrentNode]; return; } else if ([token.asStartTagToken.tagName isEqualToString:@"meta"]) { [self insertElementForToken:token.asStartTagToken]; - [_stackOfOpenElements removeLastObject]; + [_stackOfOpenElements popCurrentNode]; return; } else if ([token.asStartTagToken.tagName isEqualToString:@"title"]) { [self applyGenericParsingAlgorithmForToken:token.asStartTagToken withTokenizerState:HTMLTokenizerStateRCDATA]; @@ -730,7 +667,7 @@ HTMLElement *script = [self createElementForToken:token.asStartTagToken inNamespace:HTMLNamespaceHTML]; #warning Script Element Flags (https://html.spec.whatwg.org/multipage/scripting.html#parser-inserted) [adjustedInsertionLocation appendChildNode:script]; - [_stackOfOpenElements addObject:script]; + [_stackOfOpenElements pushElement:script]; _tokenizer.state = HTMLTokenizerStateScriptData; _originalInsertionMode = _insertionMode; [self switchInsertionMode:HTMLInsertionModeText]; @@ -743,7 +680,7 @@ #warning Implement HTML Template case HTMLTokenTypeEndTag: if ([token.asEndTagToken.tagName isEqualToString:@"head"]) { - [_stackOfOpenElements removeLastObject]; + [_stackOfOpenElements popCurrentNode]; [self switchInsertionMode:HTMLInsertionModeAfterHead]; return; } else if ([token.asEndTagToken.tagName isEqualToAny:@"body", @"html", @"br", nil]) { @@ -757,7 +694,7 @@ break; } - [_stackOfOpenElements removeLastObject]; + [_stackOfOpenElements popCurrentNode]; [self switchInsertionMode:HTMLInsertionModeAfterHead]; [self reprocessToken:token]; } @@ -772,7 +709,8 @@ if ([token.asStartTagToken.tagName isEqualToString:@"html"]) { [self HTMLInsertionModeInBody:token]; return; - } else if ([token.asStartTagToken.tagName isEqualToAny:@"basefont", @"bgsound", @"link", @"meta", @"noframes", @"style", nil]) { + } else if ([token.asStartTagToken.tagName isEqualToAny:@"basefont", @"bgsound", @"link", @"meta", @"noframes", + @"style", nil]) { [self HTMLInsertionModeInHead:token]; return; } else if ([token.asStartTagToken.tagName isEqualToAny:@"head", @"noscript", nil]) { @@ -782,7 +720,7 @@ break; case HTMLTokenTypeEndTag: if ([token.asEndTagToken.tagName isEqualToString:@"noscript"]) { - [_stackOfOpenElements removeLastObject]; + [_stackOfOpenElements popCurrentNode]; [self switchInsertionMode:HTMLInsertionModeInHead]; return; } else if ([token.asEndTagToken.tagName isEqualToString:@"br"]) { @@ -801,7 +739,7 @@ } [self emitParseError:@"Unexpected Tag Token (%@) in