diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0efa3a1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,129 @@ +# Change Log + +## [0.9.2](https://github.com/iabudiab/HTMLKit/releases/tag/0.9.2) + +Released on 2016.05.18 + +This release passes all html5lib-tests as of 2016.05.18 + +### Added + +- Handling for `` and `` +- Changelog + +### Changed + +- Updated adoption agency algorithm according to the latest specification, see: + - [whatwg/html@22ce3c3](https://github.com/whatwg/html/commit/22ce3c3) + - [Mozilla Bug 901319](https://bugzilla.mozilla.org/show_bug.cgi?id=901319) + - [Chrome Issue 268121](https://bugs.chromium.org/p/chromium/issues/detail?id=268121) + - [WebKit Bug 119478](https://bugs.webkit.org/show_bug.cgi?id=119478) +- `` is completely removed from the spec now, therefore it is dropped from the implementation +- `Tokenizer` and `Tree-Construction` tests are now generated dynamically +- Test failures are collected by a `XCTestObservation` for better reporting + +- `` is completely removed from the spec now, therefore it is dropped from the implementation +- `Tokenizer` and `Tree-Construction` tests are now generated dynamically +- Test failures are collected by a `XCTestObservation` for better reporting + +### Fixed + +- Parser now checks the qualified name instead of the local name when handling elements in the `MathML` and `SVG` namespaces + + +## [0.9.1](https://github.com/iabudiab/HTMLKit/releases/tag/0.9.1) + +Released on 2016.01.29 + +### Added + +- Travis-CI integration. +- CocoaPods spec. + + +### Changed + +- Warnings are treated as errors. + +### Fixed + +- Warnings related to format specifier and loss of precision due to NS(U)-integer usage. +- Replaced `@returns` with `@return` throughout the documentation to play nicely with Jazzy. +- Some README examples used Swift syntax. + +## [0.9.0](https://github.com/iabudiab/HTMLKit/releases/tag/0.9.0) + +Released on 2015.12.23 + +This is the first public release of `HTMLKit`. + +### Added + +- `iOS` & `OSX` Frameworks. +- Source code documentation. +- CSS Selectors extension (analogous to jQuery selectors). +- `DOMTokenList` for malipulating `HTMLElements` attributes as a list, e.g. `class`. +- Handling for `` elements in the Parser implementation. + - Updated HTML5Lib-Tests submodule (56c435f) +- Xcode Playground with Swift documentation. + +### Removed + +- Unused namespaces. +- Historical node types. + + +### Fixed + +- `lt`, `gt` & `eq` CSS Selectors method declarations. + +## [0.3.0](https://github.com/iabudiab/HTMLKit/releases/tag/0.3.0) + +Released on 2015.11.29 + +### Added + +- CSS3 Selectors support. +- Nullability annotations. +- `HTMLNode` properties for previous and next sibling elements. +- `HTMLNode` methods for accessing child elements (analogous to child nodes). +- `NSCharacterSet` category for HTML-related character sets. + +### Fixed + +- `InputStreaReader`'s reconsume-logic that is required by the CSS Parser. + +## [0.2.0](https://github.com/iabudiab/HTMLKit/releases/tag/0.1.0) + +Released on 2015.06.06 + +### Added + +- `HTMLDocument` methods to access `root`, `head` & `body` elements. +- `innerHTML` implementation for the `HTMLElement`. +- `HTMLNode` methods to append, prepend, check containment and descendancy of nodes. +- `HTMLNode` methods to enumerate child nodes. +- Implementations for `NodeIterator` and `NodeFilter` +- Implementation for `TreeWalker` +- Validation for DOM manipulations. +- Tests for the DOM implementation. + +### Changed + +- `type` property renamed to `nodeType` in `HTMLNode`. +- `firstChildNode` and `lastChildNode` renamed to `firtChild` and `lastChild` in `HTMLNode`. + +### Removed + +- `baseURI` proeprty from `HTMLNode` +- `HTMLNodeTreeEnumerator` is superseded by the `HTMLNodeIterator`. + +## [0.1.0](https://github.com/iabudiab/HTMLKit/releases/tag/0.1.0) + +Released on 2015.04.20 + +### Added + +- Initial release. +- Initial DOM implementation. +- Tokenizer and Parser pass all [HTML5Lib](https://github.com/html5lib/html5lib-tests) tokenizer and tree construction tests except for `` elements. diff --git a/HTMLKit.podspec b/HTMLKit.podspec index 5b3cd3b..62ec389 100644 --- a/HTMLKit.podspec +++ b/HTMLKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "HTMLKit" - s.version = "0.9.1" + s.version = "0.9.2" 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/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj index ca55b51..5667236 100644 --- a/HTMLKit.xcodeproj/project.pbxproj +++ b/HTMLKit.xcodeproj/project.pbxproj @@ -40,6 +40,10 @@ 625A14CF19C7829400AD0C32 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 625A14CD19C7829400AD0C32 /* InfoPlist.strings */; }; 625D0F031C2717DE00D7BEB0 /* HTMLNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 625D0F011C2717DE00D7BEB0 /* HTMLNode+Private.h */; }; 625D0F041C2717DE00D7BEB0 /* HTMLNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 625D0F011C2717DE00D7BEB0 /* HTMLNode+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 625EE4571CBAA41D00F2CC8E /* HTMLKitTestObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 625EE4561CBAA41D00F2CC8E /* HTMLKitTestObserver.m */; }; + 625EE4581CBAA41D00F2CC8E /* HTMLKitTestObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 625EE4561CBAA41D00F2CC8E /* HTMLKitTestObserver.m */; }; + 625EE45B1CBB171300F2CC8E /* HTMLKitTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 625EE45A1CBB171300F2CC8E /* HTMLKitTestUtil.m */; }; + 625EE45C1CBB171300F2CC8E /* HTMLKitTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 625EE45A1CBB171300F2CC8E /* HTMLKitTestUtil.m */; }; 628AF6301BC99A6C00496128 /* CSSNthExpressionsParserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 628AF62E1BC99A6C00496128 /* CSSNthExpressionsParserTests.m */; }; 62D8345A19FB1AC4009205A9 /* HTML5LibTokenizerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 62D8345819FB1AC4009205A9 /* HTML5LibTokenizerTest.m */; }; 62EC7AE71AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 62EC7AE51AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m */; }; @@ -393,6 +397,10 @@ 625A150619C78ABA00AD0C32 /* HTMLInputStreamReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLInputStreamReader.h; sourceTree = ""; }; 625A150719C78ABA00AD0C32 /* HTMLInputStreamReader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLInputStreamReader.m; sourceTree = ""; }; 625D0F011C2717DE00D7BEB0 /* HTMLNode+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "HTMLNode+Private.h"; sourceTree = ""; }; + 625EE4551CBAA41D00F2CC8E /* HTMLKitTestObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLKitTestObserver.h; sourceTree = ""; }; + 625EE4561CBAA41D00F2CC8E /* HTMLKitTestObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitTestObserver.m; sourceTree = ""; }; + 625EE4591CBB171300F2CC8E /* HTMLKitTestUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLKitTestUtil.h; sourceTree = ""; }; + 625EE45A1CBB171300F2CC8E /* HTMLKitTestUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitTestUtil.m; sourceTree = ""; }; 626652F81C03D30F00C3F121 /* HTMLKitErrorDomain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLKitErrorDomain.h; sourceTree = ""; }; 6279F87119E17DC700F12EE5 /* HTMLParserInsertionModes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLParserInsertionModes.h; sourceTree = ""; }; 6279F87219E1808D00F12EE5 /* HTMLElement.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.objc; fileEncoding = 4; path = HTMLElement.h; sourceTree = ""; }; @@ -653,6 +661,10 @@ 624E1A2D1B1D1C8A00E66AAC /* Structures */, 62FF516C1C0A430A009BFDFE /* Selectors */, 625A14CB19C7829400AD0C32 /* Supporting Files */, + 625EE4551CBAA41D00F2CC8E /* HTMLKitTestObserver.h */, + 625EE4561CBAA41D00F2CC8E /* HTMLKitTestObserver.m */, + 625EE4591CBB171300F2CC8E /* HTMLKitTestUtil.h */, + 625EE45A1CBB171300F2CC8E /* HTMLKitTestUtil.m */, ); name = Tests; path = HTMLKitTests; @@ -1153,6 +1165,7 @@ 6239755B1AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m in Sources */, 62F658711BD83C8E0045F137 /* CSSNThExpressionSelectorTests.m in Sources */, 623CAF9E1AD88BEA00E34C32 /* HTMLKitParserPerformance.m in Sources */, + 625EE4571CBAA41D00F2CC8E /* HTMLKitTestObserver.m in Sources */, 6247171D1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */, 62EC7AE71AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m in Sources */, 624AB31B1B050A4D00F3830D /* CSSAttributeSelectorTests.m in Sources */, @@ -1168,6 +1181,7 @@ 624FC37B1AE591D80015DDF9 /* HTMLKitNodesTests.m in Sources */, 621FBE5B1BDAD68700BC9555 /* CSSSelectorParserTests.m in Sources */, 621FBE5E1BDAD90200BC9555 /* CSSCombinatorSelectorTests.m in Sources */, + 625EE45B1CBB171300F2CC8E /* HTMLKitTestUtil.m in Sources */, 628AF6301BC99A6C00496128 /* CSSNthExpressionsParserTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1233,6 +1247,7 @@ 62ECBFCB1C0B6E2E00AF847B /* HTMLKitTokenizerTests.m in Sources */, 62ECBFCC1C0B6E2E00AF847B /* HTMLKitTokenizerPerformance.m in Sources */, 62ECBFCD1C0B6E2E00AF847B /* HTML5LibTreeConstructionTest.m in Sources */, + 625EE4581CBAA41D00F2CC8E /* HTMLKitTestObserver.m in Sources */, 62ECBFCE1C0B6E2E00AF847B /* HTMLKitTreeConstructionTests.m in Sources */, 62ECBFCF1C0B6E2E00AF847B /* HTMLKitParserPerformance.m in Sources */, 62ECBFD01C0B6E2E00AF847B /* HTMLKitNodeIteratorTests.m in Sources */, @@ -1248,6 +1263,7 @@ 62ECBFD91C0B6E2E00AF847B /* CSSTypeSelectorTests.m in Sources */, 62ECBFDA1C0B6E2E00AF847B /* CSSAttributeSelectorTests.m in Sources */, 62ECBFDB1C0B6E2E00AF847B /* CSSNThExpressionSelectorTests.m in Sources */, + 625EE45C1CBB171300F2CC8E /* HTMLKitTestUtil.m in Sources */, 62ECBFDC1C0B6E2E00AF847B /* CSSCombinatorSelectorTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/HTMLKit/HTMLElementTypes.h b/HTMLKit/HTMLElementTypes.h index bf7ce2d..62be23d 100644 --- a/HTMLKit/HTMLElementTypes.h +++ b/HTMLKit/HTMLElementTypes.h @@ -39,8 +39,8 @@ NS_INLINE BOOL IsSpecialElement(HTMLElement *element) @"dir", @"div", @"dl", @"dt", @"embed", @"fieldset", @"figcaption", @"figure", @"footer", @"form", @"frame", @"frameset", @"h1", @"h2", @"h3", @"h4", @"h5", @"h6", @"head", @"header", @"hgroup", @"hr", @"html", @"iframe", - @"img", @"input", @"isindex", @"li", @"link", @"listing", @"main", @"marquee", - @"menu", @"menuitem", @"meta", @"nav", @"noembed", @"noframes", @"noscript", + @"img", @"input", @"li", @"link", @"listing", @"main", @"marquee", + @"menu", @"meta", @"nav", @"noembed", @"noframes", @"noscript", @"object", @"ol", @"p", @"param", @"plaintext", @"pre", @"script", @"section", @"select", @"source", @"style", @"summary", @"table", @"tbody", @"td", @"template", @"textarea", @"tfoot", @"th", @"thead", @"title", @"tr", diff --git a/HTMLKit/HTMLKit-Info.plist b/HTMLKit/HTMLKit-Info.plist index 8efb56e..b59e26b 100644 --- a/HTMLKit/HTMLKit-Info.plist +++ b/HTMLKit/HTMLKit-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.1 + 0.9.2 CFBundleSignature ???? CFBundleVersion diff --git a/HTMLKit/HTMLParser.m b/HTMLKit/HTMLParser.m index 8558282..230ad53 100644 --- a/HTMLKit/HTMLParser.m +++ b/HTMLKit/HTMLParser.m @@ -519,7 +519,7 @@ - (void)generateImpliedEndTagsExceptForElement:(NSString *)tagName { - while ([self.currentNode.tagName isEqualToAny:@"dd", @"dt", @"li", @"option", @"optgroup", @"p", @"rb", @"rp", @"rt", @"rtc", nil] && + while ([self.currentNode.tagName isEqualToAny:@"dd", @"dt", @"li", @"menuitem", @"option", @"optgroup", @"p", @"rb", @"rp", @"rt", @"rtc", nil] && ![self.currentNode.tagName isEqualToString:tagName]) { [_stackOfOpenElements popCurrentNode]; } @@ -588,20 +588,29 @@ HTMLElement *lastNode = furthestBlock; NSUInteger index = [_stackOfOpenElements indexOfElement:node]; - for (int innerLoopCounter = 0; innerLoopCounter < 3; innerLoopCounter ++) { - index--; + int innerLoopCounter = 0; + while (YES) { + + innerLoopCounter += 1; + index -= 1; + node = _stackOfOpenElements[index]; + if ([node isEqual:formattingElement]) { + break; + } + + if (innerLoopCounter > 3 && [_listOfActiveFormattingElements containsElement:node]) { + [_listOfActiveFormattingElements removeElement:node]; + continue; + } + if (![_listOfActiveFormattingElements containsElement:node]) { [_stackOfOpenElements removeElement:node]; continue; } - if ([node isEqual:formattingElement]) { - break; - } - HTMLElement *newElement = [node copy]; [_listOfActiveFormattingElements replaceElementAtIndex:[_listOfActiveFormattingElements indexOfElement:node] withElement:newElement]; @@ -1158,8 +1167,8 @@ [self HTMLInsertionModeInTemplate:token]; } else { for (HTMLElement *node in _stackOfOpenElements) { - if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"optgroup", @"option", @"p", @"rb", @"rp", - @"rt", @"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) { + if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"menuitem", @"optgroup", @"option", @"p", @"rb", + @"rp", @"rt", @"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) { [self emitParseError:@"EOF reached with unclosed element <%@> in ", node.tagName]; break; } @@ -1222,12 +1231,20 @@ [self switchInsertionMode:HTMLInsertionModeInFrameset]; } else if ([tagName isEqualToAny:@"address", @"article", @"aside", @"blockquote", @"center", @"details", @"dialog", @"dir", @"div", @"dl", @"fieldset", @"figcaption", @"figure", - @"footer", @"header", @"hgroup", @"main", @"menu", @"nav", @"ol", @"p", @"section", + @"footer", @"header", @"hgroup", @"main", @"nav", @"ol", @"p", @"section", @"summary", @"ul", nil]) { if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) { [self closePElement]; } [self insertElementForToken:token]; + } else if ([tagName isEqualToString:@"menu"]) { + if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) { + [self closePElement]; + } + if ([self.currentNode.tagName isEqualToString:@"menuitem"]) { + [_stackOfOpenElements popCurrentNode]; + } + [self insertElementForToken:token]; } else if ([tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) { if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) { [self closePElement]; @@ -1364,13 +1381,16 @@ if (type == nil || ![type isEqualToStringIgnoringCase:@"hidden"]) { _framesetOkFlag = NO; } - } else if ([tagName isEqualToAny:@"menuitem", @"param", @"source", @"track", nil]) { + } else if ([tagName isEqualToAny:@"param", @"source", @"track", nil]) { [self insertElementForToken:token]; [_stackOfOpenElements popCurrentNode]; } else if ([tagName isEqualToString:@"hr"]) { if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) { [self closePElement]; } + if ([self.currentNode.tagName isEqualToString:@"menuitem"]) { + [_stackOfOpenElements popCurrentNode]; + } [self insertElementForToken:token]; [_stackOfOpenElements popCurrentNode]; _framesetOkFlag = NO; @@ -1378,52 +1398,6 @@ [self emitParseError:@"Image Start Tag Token with tagname should be . Don't ask."]; token.tagName = @"img"; [self reprocessToken:token]; - } else if ([tagName isEqualToString:@"isindex"]) { - [self emitParseError:@"Unexpected start tag in "]; - if (_formElementPointer != nil && ![_stackOfOpenElements containsElementWithTagName:@"template"]) { - return; - } - _framesetOkFlag = NO; - if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) { - [self closePElement]; - } - - HTMLStartTagToken *formToken = [[HTMLStartTagToken alloc] initWithTagName:@"form"]; - HTMLElement *form = [self insertElementForToken:formToken]; - if (![_stackOfOpenElements containsElementWithTagName:@"template"]) { - _formElementPointer = form; - } - NSString *action = token.attributes[@"action"]; - if (action != nil) { - form.attributes[@"action"] = action; - } - - HTMLStartTagToken *hrToken = [[HTMLStartTagToken alloc] initWithTagName:@"hr"]; - [self insertElementForToken:hrToken]; - - [_stackOfOpenElements popCurrentNode]; - [self reconstructActiveFormattingElements]; - - HTMLStartTagToken *labelToken = [[HTMLStartTagToken alloc] initWithTagName:@"label"]; - [self insertElementForToken:labelToken]; - - NSString *prompt = token.attributes[@"prompt"] ?: @"This is a searchable index. Enter search keywords: "; - [self insertCharacters:prompt]; - - NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:token.attributes]; - attributes[@"name"] = @"isindex"; - [attributes removeObjectForKey:@"action"]; - [attributes removeObjectForKey:@"prompt"]; - - HTMLStartTagToken *inputToken = [[HTMLStartTagToken alloc] initWithTagName:@"input" attributes:attributes]; - [self insertElementForToken:inputToken]; - [_stackOfOpenElements popCurrentNode]; - - [_stackOfOpenElements popCurrentNode]; - [self insertElementForToken:hrToken]; - [_stackOfOpenElements popCurrentNode]; - [_stackOfOpenElements popCurrentNode]; - _formElementPointer = nil; } else if ([tagName isEqualToString:@"textarea"]) { [self insertElementForToken:token]; _ignoreNextLineFeedCharacterToken = YES; @@ -1462,6 +1436,12 @@ } [self reconstructActiveFormattingElements]; [self insertElementForToken:token]; + } else if ([tagName isEqualToString:@"menuitem"]) { + if ([self.currentNode.tagName isEqualToString:@"menuitem"]) { + [_stackOfOpenElements popCurrentNode]; + } + [self reconstructActiveFormattingElements]; + [self insertElementForToken:token]; } else if ([tagName isEqualToAny:@"rb", @"rtc", nil]) { if ([_stackOfOpenElements hasElementInScopeWithTagName:@"ruby"]) { [self generateImpliedEndTagsExceptForElement:nil]; @@ -1516,8 +1496,8 @@ [self emitParseError:@"Unexpected end tag without body element in scope in "]; } for (HTMLElement *node in _stackOfOpenElements) { - if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"optgroup", @"option", @"p", @"rb", @"rp", @"rt", - @"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) { + if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"menuitem", @"optgroup", @"option", @"p", @"rb", @"rp", + @"rt", @"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) { [self emitParseError:@"Misnested end tag with open element <%@> in ", tagName, node.tagName]; break; } diff --git a/HTMLKit/HTMLStackOfOpenElements.m b/HTMLKit/HTMLStackOfOpenElements.m index 7747366..daf1517 100644 --- a/HTMLKit/HTMLStackOfOpenElements.m +++ b/HTMLKit/HTMLStackOfOpenElements.m @@ -129,7 +129,11 @@ - (void)popElementsUntilElementPoppedWithTagName:(NSString *)tagName { - while (self.currentNode && ![self.currentNode.tagName isEqualToString:tagName]) { + while (self.currentNode) { + if (self.currentNode.htmlNamespace == HTMLNamespaceHTML && + [self.currentNode.tagName isEqualToString:tagName]) { + break; + } [_stack removeLastObject]; } [_stack removeLastObject]; @@ -137,7 +141,11 @@ - (void)popElementsUntilAnElementPoppedWithAnyOfTagNames:(NSArray *)tagNames { - while (self.currentNode && ![tagNames containsObject:self.currentNode.tagName]) { + while (self.currentNode) { + if (self.currentNode.htmlNamespace == HTMLNamespaceHTML && + [tagNames containsObject:self.currentNode.tagName]) { + break; + } [_stack removeLastObject]; } [_stack removeLastObject]; @@ -257,7 +265,10 @@ { for (HTMLElement *node in _stack.reverseObjectEnumerator) { if ([tagNames containsObject:node.tagName]) { - return node; + NSNumber *namespace = elementTypes[node.tagName] ?: @(HTMLNamespaceHTML); + if ([namespace isEqual:@(node.htmlNamespace)]) { + return node; + } } if ([elementTypes[node.tagName] isEqual:@(node.htmlNamespace)]) { return nil; diff --git a/HTMLKitTests/HTML5LibTokenizerTest.h b/HTMLKitTests/HTML5LibTokenizerTest.h index 893973d..7fefe44 100644 --- a/HTMLKitTests/HTML5LibTokenizerTest.h +++ b/HTMLKitTests/HTML5LibTokenizerTest.h @@ -10,7 +10,7 @@ @interface HTML5LibTokenizerTest : NSObject -@property (nonatomic, copy) NSString *testName; +@property (nonatomic, copy) NSString *testFile; @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *input; @property (nonatomic, strong) NSArray *output; diff --git a/HTMLKitTests/HTML5LibTokenizerTest.m b/HTMLKitTests/HTML5LibTokenizerTest.m index e5a09e9..f864634 100644 --- a/HTMLKitTests/HTML5LibTokenizerTest.m +++ b/HTMLKitTests/HTML5LibTokenizerTest.m @@ -39,7 +39,7 @@ static NSString * const TOKENIZER = @"tokenizer"; + (NSArray *)loadTestsWithFileAtPath:(NSString *)filePath { - NSString *testName = filePath.lastPathComponent.stringByDeletingLastPathComponent; + NSString *testFile = filePath.lastPathComponent; NSString *json = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding]; @@ -52,7 +52,7 @@ static NSString * const TOKENIZER = @"tokenizer"; for (NSDictionary *test in jsonTests) { HTML5LibTokenizerTest *html5libTest = [[HTML5LibTokenizerTest alloc] initWithTestDictionary:test]; - html5libTest.testName = testName; + html5libTest.testFile = testFile; [tests addObject:html5libTest]; } return tests; diff --git a/HTMLKitTests/HTMLKitTestObserver.h b/HTMLKitTests/HTMLKitTestObserver.h new file mode 100644 index 0000000..c9cd28f --- /dev/null +++ b/HTMLKitTests/HTMLKitTestObserver.h @@ -0,0 +1,24 @@ +// +// HTMLKitTestObserver.h +// HTMLKit +// +// Created by Iska on 10/04/16. +// Copyright © 2016 BrainCookie. All rights reserved. +// + +#import + +@interface HTMLKitTestReport : NSObject +@property (assign, readonly) NSUInteger totalCount; +@property (assign, readonly) NSUInteger failureCount; +@property (copy, readonly) NSString *failureReport; +@end + +@interface HTMLKitTestObserver : NSObject + +- (instancetype)initWithName:(NSString *)name; + +- (void)addCaseForHTML5LibTestWithInput:(NSString *)input; +- (HTMLKitTestReport *)generateReport; + +@end diff --git a/HTMLKitTests/HTMLKitTestObserver.m b/HTMLKitTests/HTMLKitTestObserver.m new file mode 100644 index 0000000..fc81acf --- /dev/null +++ b/HTMLKitTests/HTMLKitTestObserver.m @@ -0,0 +1,86 @@ +// +// HTMLKitTestObserver.m +// HTMLKit +// +// Created by Iska on 10/04/16. +// Copyright © 2016 BrainCookie. All rights reserved. +// + +#import "HTMLKitTestObserver.h" + +#pragma mark - HTMLKitTestReport + +@interface HTMLKitTestReport () +@property (assign) NSUInteger totalCount; +@property (assign) NSUInteger failureCount; +@property (copy) NSString *failureReport; +@end + +@implementation HTMLKitTestReport +@synthesize totalCount, failureCount, failureReport; +@end + +#pragma mark - HTMLKitTestObserver + +@interface HTMLKitTestObserver () +{ + NSString *_name; + NSMutableArray *_cases; + NSMutableDictionary *_currentCase; +} +@end + +@implementation HTMLKitTestObserver + +- (instancetype)initWithName:(NSString *)name +{ + self = [super init]; + if (self) { + _name = [name copy]; + _cases = [NSMutableArray new]; + } + return self; +} + + +- (void)addCaseForHTML5LibTestWithInput:(NSString *)input +{ + _currentCase = [NSMutableDictionary new]; + _currentCase[@"input"] = input; + _currentCase[@"status"] = @"Passed"; + [_cases addObject:_currentCase]; +} + +- (void)testCase:(XCTestCase *)testCase didFailWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber +{ + _currentCase[@"status"] = @"Failed"; +} + +- (HTMLKitTestReport *)generateReport +{ + NSMutableString *reportDescription = [NSMutableString string]; + + NSIndexSet *failedIndexes = [_cases indexesOfObjectsPassingTest:^BOOL(NSDictionary *testCase, NSUInteger idx, BOOL * _Nonnull stop) { + return [testCase[@"status"] isEqualToString:@"Failed"]; + }]; + + NSArray *failedTests = [_cases objectsAtIndexes:failedIndexes]; + + NSUInteger totalCount = _cases.count; + NSUInteger failureCount = failedTests.count; + + [reportDescription appendFormat:@"HTML5Lib test %@ failed [%lu] out of [%lu] total tests\n", _name, failureCount, _cases.count]; + + for (NSDictionary *testCase in failedTests) { + [reportDescription appendFormat:@"Failed test for input: %@\n", testCase[@"input"]]; + } + + HTMLKitTestReport *report = [HTMLKitTestReport new]; + report.totalCount = totalCount; + report.failureCount = failureCount; + report.failureReport = reportDescription; + + return report; +} + +@end diff --git a/HTMLKitTests/HTMLKitTestUtil.h b/HTMLKitTests/HTMLKitTestUtil.h new file mode 100644 index 0000000..47abfae --- /dev/null +++ b/HTMLKitTests/HTMLKitTestUtil.h @@ -0,0 +1,15 @@ +// +// HTMLKitTestUtil.h +// HTMLKit +// +// Created by Iska on 11/04/16. +// Copyright © 2016 BrainCookie. All rights reserved. +// + +#import + +@interface HTMLKitTestUtil : NSObject + ++ (NSInvocation *)addTestToClass:(Class)cls withName:(NSString *)name block:(id)block; + +@end diff --git a/HTMLKitTests/HTMLKitTestUtil.m b/HTMLKitTests/HTMLKitTestUtil.m new file mode 100644 index 0000000..ac43bee --- /dev/null +++ b/HTMLKitTests/HTMLKitTestUtil.m @@ -0,0 +1,29 @@ +// +// HTMLKitTestUtil.m +// HTMLKit +// +// Created by Iska on 11/04/16. +// Copyright © 2016 BrainCookie. All rights reserved. +// + +#import "HTMLKitTestUtil.h" +#import + +@implementation HTMLKitTestUtil + ++ (NSInvocation *)addTestToClass:(Class)cls withName:(NSString *)name block:(id)block +{ + IMP implementation = imp_implementationWithBlock(block); + const char *types = [[NSString stringWithFormat:@"%s%s%s", @encode(id), @encode(id), @encode(SEL)] UTF8String]; + + SEL selector = NSSelectorFromString(name); + class_addMethod(cls, selector, implementation, types); + + NSMethodSignature *signature = [cls instanceMethodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + invocation.selector = selector; + + return invocation; +} + +@end diff --git a/HTMLKitTests/HTMLKitTokenizerTests.m b/HTMLKitTests/HTMLKitTokenizerTests.m index 76e7545..b11d918 100644 --- a/HTMLKitTests/HTMLKitTokenizerTests.m +++ b/HTMLKitTests/HTMLKitTokenizerTests.m @@ -7,8 +7,10 @@ // #import -#import "HTML5LibTokenizerTest.h" +#import "HTMLKitTestUtil.h" + +#import "HTML5LibTokenizerTest.h" #import "HTMLTokenizer.h" #import "HTMLTokenizerStates.h" #import "HTMLTokens.h" @@ -30,7 +32,6 @@ #pragma mark - HTML5Lib Test Suite @interface HTMLKitTokenizerTests : XCTestCase -@property (nonatomic, strong) NSString *testName; @property (nonatomic, strong) NSArray *testsList; @end @@ -50,44 +51,31 @@ + (void)addTestCaseForTestFile:(NSString *)testFile withTests:(NSArray *)tests toTestSuite:(XCTestSuite *)suite { - NSArray *allInvocations = [self testInvocations]; - for (NSInvocation *invocation in allInvocations) { - XCTestCase *testCase = [[self alloc] initWithInvocation:invocation - testName:testFile - tests:tests]; - [suite addTest:testCase]; - } + NSString *testName = [testFile.stringByDeletingPathExtension stringByReplacingOccurrencesOfString:@"-" withString:@"_"]; + testName = [NSString stringWithFormat:@"testTokenizer__%@", testName]; + + NSInvocation *invocation = [HTMLKitTestUtil addTestToClass:self withName:testName block:^ (HTMLKitTokenizerTests *instance){ + [instance runTests]; + }]; + + XCTestCase *testCase = [[self alloc] initWithInvocation:invocation tests:tests]; + [suite addTest:testCase]; } #pragma mark - Instance -- (instancetype)initWithInvocation:(NSInvocation *)invocation - testName:(NSString *)testName - tests:(NSArray *)tests +- (instancetype)initWithInvocation:(NSInvocation *)invocation tests:(NSArray *)tests { self = [super initWithInvocation:invocation]; if (self) { - _testName = testName; _testsList = tests; } return self; } -- (NSString *)name -{ - NSInvocation *invocation = [self invocation]; - NSString *title = self.testName.stringByDeletingPathExtension; - return [NSString stringWithFormat:@"-[%@ %@_%@]", self.class, NSStringFromSelector(invocation.selector), title]; -} - -- (NSString *)description -{ - return self.name; -} - #pragma mark - Tests -- (void)testTokenizer +- (void)runTests { for (HTML5LibTokenizerTest *test in self.testsList) { @@ -101,7 +89,7 @@ NSArray *tokens = tokenizer.allObjects; NSString *message = [NSString stringWithFormat:@"HTML5Lib test in file: \'%@\' Title: '%@'\nInput: '%@'\nExpected:\n%@\nActual:\n%@\n", - self.testName, + test.testFile, test.title, test.input, expectedTokens, diff --git a/HTMLKitTests/HTMLKitTreeConstructionTests.m b/HTMLKitTests/HTMLKitTreeConstructionTests.m index d8de4be..f001278 100644 --- a/HTMLKitTests/HTMLKitTreeConstructionTests.m +++ b/HTMLKitTests/HTMLKitTreeConstructionTests.m @@ -8,6 +8,9 @@ #import +#import "HTMLKitTestUtil.h" + +#import "HTMLKitTestObserver.h" #import "HTML5LibTreeConstructionTest.h" #import "HTMLDOM.h" #import "HTMLParser.h" @@ -26,7 +29,9 @@ #pragma mark - HTML5Lib Test Suite @interface HTMLKitTreeConstructionTests : XCTestCase -@property (nonatomic, strong) NSString *testName; +{ + HTMLKitTestObserver *_observer; +} @property (nonatomic, strong) NSArray *testsList; @end @@ -45,50 +50,58 @@ + (void)addTestCaseForTestFile:(NSString *)testFile withTests:(NSArray *)tests toTestSuite:(XCTestSuite *)suite { - NSArray *allInvocations = [self testInvocations]; - for (NSInvocation *invocation in allInvocations) { - XCTestCase *testCase = [[self alloc] initWithInvocation:invocation - testName:testFile - tests:tests]; - [suite addTest:testCase]; - } + NSString *testName = [testFile.stringByDeletingPathExtension stringByReplacingOccurrencesOfString:@"-" withString:@"_"]; + testName = [NSString stringWithFormat:@"testPareser__%@", testName]; + + NSInvocation *invocation = [HTMLKitTestUtil addTestToClass:self withName:testName block:^ (HTMLKitTreeConstructionTests *instance){ + [instance runTests]; + }]; + + XCTestCase *testCase = [[self alloc] initWithInvocation:invocation tests:tests]; + [suite addTest:testCase]; } #pragma mark - Instance -- (instancetype)initWithInvocation:(NSInvocation *)invocation - testName:(NSString *)testName - tests:(NSArray *)tests +- (instancetype)initWithInvocation:(NSInvocation *)invocation tests:(NSArray *)tests { self = [super initWithInvocation:invocation]; if (self) { - _testName = testName; _testsList = tests; } return self; } -- (NSString *)name +#pragma mark - Setup + +- (void)setUp { - NSInvocation *invocation = [self invocation]; - NSString *title = self.testName.stringByDeletingPathExtension; - return [NSString stringWithFormat:@"-[%@ %@_%@]", self.class, NSStringFromSelector(invocation.selector), title]; + _observer = [[HTMLKitTestObserver alloc] initWithName:self.name]; + [[XCTestObservationCenter sharedTestObservationCenter] addTestObserver:_observer]; + + [super setUp]; } -- (NSString *)description +- (void)tearDown { - return self.name; + HTMLKitTestReport *testReport = [_observer generateReport]; + XCTAssertTrue(testReport.failureCount == 0, @"%@", testReport.failureReport); + + [[XCTestObservationCenter sharedTestObservationCenter] removeTestObserver:_observer]; + [super tearDown]; } #pragma mark - Tests -- (void)testParser +- (void)runTests { for (HTML5LibTreeConstructionTest *test in self.testsList) { + NSString *testInput = test.data; + + [_observer addCaseForHTML5LibTestWithInput:testInput]; + + HTMLParser *parser = [[HTMLParser alloc] initWithString:testInput]; HTMLElement *contextElement = test.documentFragment; - - HTMLParser *parser = [[HTMLParser alloc] initWithString:test.data]; - NSArray *actual = nil; if (contextElement == nil) { actual = [parser parseDocument].childNodes.array; diff --git a/HTMLKitTests/html5lib-tests b/HTMLKitTests/html5lib-tests index 56c435f..b2f4f58 160000 --- a/HTMLKitTests/html5lib-tests +++ b/HTMLKitTests/html5lib-tests @@ -1 +1 @@ -Subproject commit 56c435f033d3cb052e216747beb4251c7eaeb5c9 +Subproject commit b2f4f5844192ed097bd7993dfb0bcbd4c7a4aeb0 diff --git a/README.md b/README.md index 306588e..581f65c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # HTMLKit -![HTMLKit Logo](HTMLKit.png) +![HTMLKit Logo](https://raw.githubusercontent.com/iabudiab/HTMLKit/master/HTMLKit.png) An Objective-C framework for your everyday HTML needs. -[![Build Status](https://travis-ci.org/iabudiab/HTMLKit.svg?branch=develop)](https://travis-ci.org/iabudiab/HTMLKit) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![License MIT](https://img.shields.io/badge/license-MIT-4481C7.svg)](https://opensource.org/licenses/MIT) +[![Build Status](https://img.shields.io/travis/iabudiab/HTMLKit/develop.svg?style=flat)](https://travis-ci.org/iabudiab/HTMLKit) +[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/HTMLKit.svg?style=flat)](https://cocoapods.org/pods/HTMLKit) +[![CocoaDocs](https://img.shields.io/cocoapods/metrics/doc-percent/HTMLKit.svg?style=flat)](http://cocoadocs.org/docsets/HTMLKit) +[![Platform](https://img.shields.io/cocoapods/p/HTMLKit.svg?style=flat)](http://cocoadocs.org/docsets/HTMLKit) +[![License MIT](https://img.shields.io/badge/license-MIT-4481C7.svg?style=flat)](https://opensource.org/licenses/MIT) # Quick Overview @@ -18,7 +23,7 @@ DOM mutations are validated as described in the [WHATWG DOM Standard](https://do ## Tests -HTMLKit passes all of the [HTML5Lib](https://github.com/html5lib/html5lib-tests) Tokenizer and Tree Construction tests except for the Blink changes introduced on the 16.09.2015. The `html5lib-tests` is configured as a git-submodule. If you plan to run the tests, do not forget to pull it too. +HTMLKit passes all of the [HTML5Lib](https://github.com/html5lib/html5lib-tests) Tokenizer and Tree Construction tests. The `html5lib-tests` is configured as a git-submodule. If you plan to run the tests, do not forget to pull it too. The CSS3 Selector implementation is tested with an adapted version of the [CSS3 Selectors Test Suite](http://www.w3.org/Style/CSS/Test/CSS3/Selectors/current/html/full/flat/index.html), ignoring the tests that require user interaction, session history, and scripting. @@ -214,6 +219,10 @@ CSSSelector *myAwesomeSelector = namedBlockSelector(@"myAwesomeSelector", ^BOOL notParagraphAndNotDiv = [firstDivElement elementsMatchingSelector:myAwesomeSelector]; ``` +# Change Log + +See the [CHANGELOG.md](CHANGELOG.md) for more info. + # License -HTMLKit is available under the MIT license. See the LICENSE file for more info. +HTMLKit is available under the MIT license. See the [LICENSE](LICENSE) file for more info.