diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj index 1a94f56..1ea6aea 100644 --- a/HTMLKit.xcodeproj/project.pbxproj +++ b/HTMLKit.xcodeproj/project.pbxproj @@ -49,14 +49,11 @@ 6238579B1A9E8934003A45D9 /* HTMLComment.h in Headers */ = {isa = PBXBuildFile; fileRef = 623857991A9E8934003A45D9 /* HTMLComment.h */; }; 6238579C1A9E8934003A45D9 /* HTMLComment.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238579A1A9E8934003A45D9 /* HTMLComment.m */; }; 6238579D1A9E8934003A45D9 /* HTMLComment.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238579A1A9E8934003A45D9 /* HTMLComment.m */; }; - 6238C9851AB8D6330006512E /* HTMLKitExceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 6238C9831AB8D6330006512E /* HTMLKitExceptions.h */; }; - 6238C9861AB8D6330006512E /* HTMLKitExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238C9841AB8D6330006512E /* HTMLKitExceptions.m */; }; - 6238C9871AB8D6330006512E /* HTMLKitExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238C9841AB8D6330006512E /* HTMLKitExceptions.m */; }; - 623916C31AC707250066B4FE /* HTMLNodeTreeEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 623916C11AC707250066B4FE /* HTMLNodeTreeEnumerator.h */; }; - 623916C41AC707250066B4FE /* HTMLNodeTreeEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C21AC707250066B4FE /* HTMLNodeTreeEnumerator.m */; }; - 623916C51AC707250066B4FE /* HTMLNodeTreeEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C21AC707250066B4FE /* HTMLNodeTreeEnumerator.m */; }; - 623916C71AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C61AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m */; }; - 623916C81AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C61AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m */; }; + 6238C9851AB8D6330006512E /* HTMLKitDOMExceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 6238C9831AB8D6330006512E /* HTMLKitDOMExceptions.h */; }; + 6238C9861AB8D6330006512E /* HTMLKitDOMExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238C9841AB8D6330006512E /* HTMLKitDOMExceptions.m */; }; + 6238C9871AB8D6330006512E /* HTMLKitDOMExceptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 6238C9841AB8D6330006512E /* HTMLKitDOMExceptions.m */; }; + 623916C71AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C61AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m */; }; + 623916C81AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 623916C61AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m */; }; 6239755A1AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 623975591AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m */; }; 6239755B1AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 623975591AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m */; }; 6239755E1AC364BB007E26F1 /* HTML5LibTreeConstructionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6239755D1AC364BB007E26F1 /* HTML5LibTreeConstructionTest.m */; }; @@ -69,6 +66,20 @@ 624493AC19CD0CBE00BCDDF4 /* HTMLToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 624493AA19CD0CBE00BCDDF4 /* HTMLToken.h */; }; 624493AD19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 624493AB19CD0CBE00BCDDF4 /* HTMLToken.m */; }; 624493AE19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 624493AB19CD0CBE00BCDDF4 /* HTMLToken.m */; }; + 624717181B22333200C11912 /* HTMLNodeTraversal.h in Headers */ = {isa = PBXBuildFile; fileRef = 624717161B22333200C11912 /* HTMLNodeTraversal.h */; }; + 624717191B22333200C11912 /* HTMLNodeTraversal.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717171B22333200C11912 /* HTMLNodeTraversal.m */; }; + 6247171A1B22333200C11912 /* HTMLNodeTraversal.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717171B22333200C11912 /* HTMLNodeTraversal.m */; }; + 6247171C1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6247171B1B2240B800C11912 /* HTMLTreeWalkerTests.m */; }; + 6247171D1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6247171B1B2240B800C11912 /* HTMLTreeWalkerTests.m */; }; + 624717B81B21FE5400B38302 /* HTMLNodeFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */; }; + 624717B91B21FE5400B38302 /* HTMLNodeFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */; }; + 624717BC1B22009200B38302 /* HTMLTreeWalker.h in Headers */ = {isa = PBXBuildFile; fileRef = 624717BA1B22009200B38302 /* HTMLTreeWalker.h */; }; + 624717BD1B22009200B38302 /* HTMLTreeWalker.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717BB1B22009200B38302 /* HTMLTreeWalker.m */; }; + 624717BE1B22009200B38302 /* HTMLTreeWalker.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717BB1B22009200B38302 /* HTMLTreeWalker.m */; }; + 6247A9431B152F4F00CCF25C /* HTMLNodeIterator.h in Headers */ = {isa = PBXBuildFile; fileRef = 6247A9411B152F4F00CCF25C /* HTMLNodeIterator.h */; }; + 6247A9441B152F4F00CCF25C /* HTMLNodeIterator.m in Sources */ = {isa = PBXBuildFile; fileRef = 6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */; }; + 6247A9451B152F4F00CCF25C /* HTMLNodeIterator.m in Sources */ = {isa = PBXBuildFile; fileRef = 6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */; }; + 6247A9471B152F8C00CCF25C /* HTMLNodeFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 6247A9461B152F8C00CCF25C /* HTMLNodeFilter.h */; }; 624AC8FF19FBF59800BD3C4A /* HTMLTokens.h in Headers */ = {isa = PBXBuildFile; fileRef = 624AC8FE19FBF4F700BD3C4A /* HTMLTokens.h */; }; 624AC90119FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624AC90019FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m */; }; 624AC90219FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624AC90019FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m */; }; @@ -80,6 +91,8 @@ 624B9FB31AE0313300646C4C /* HTMLKitStringCategoryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624B9FB11AE0313300646C4C /* HTMLKitStringCategoryTests.m */; }; 624B9FB51AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624B9FB41AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m */; }; 624B9FB61AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624B9FB41AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m */; }; + 624FC37A1AE591D80015DDF9 /* HTMLKitNodesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624FC3791AE591D80015DDF9 /* HTMLKitNodesTests.m */; }; + 624FC37B1AE591D80015DDF9 /* HTMLKitNodesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 624FC3791AE591D80015DDF9 /* HTMLKitNodesTests.m */; }; 625A14B019C7829400AD0C32 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 625A14AF19C7829400AD0C32 /* Cocoa.framework */; }; 625A14BA19C7829400AD0C32 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 625A14B819C7829400AD0C32 /* InfoPlist.strings */; }; 625A14BE19C7829400AD0C32 /* HTMLKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 625A14BD19C7829400AD0C32 /* HTMLKit.m */; }; @@ -116,6 +129,16 @@ 62AE594B19F9948A0043F069 /* HTMLCharacterToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 62AE594819F9948A0043F069 /* HTMLCharacterToken.m */; }; 62D8345919FB1AC4009205A9 /* HTML5LibTokenizerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 62D8345819FB1AC4009205A9 /* HTML5LibTokenizerTest.m */; }; 62D8345A19FB1AC4009205A9 /* HTML5LibTokenizerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 62D8345819FB1AC4009205A9 /* HTML5LibTokenizerTest.m */; }; + 62D89DB01AE7080300B6243D /* HTMLDOM.h in Headers */ = {isa = PBXBuildFile; fileRef = 62362A3F1A9FDE8A00301989 /* HTMLDOM.h */; }; + 62D89DB11AE7081600B6243D /* HTMLTokenizerStates.h in Headers */ = {isa = PBXBuildFile; fileRef = 624493A919CCE84A00BCDDF4 /* HTMLTokenizerStates.h */; }; + 62D89DB21AE7081600B6243D /* HTMLTokenizerCharacters.h in Headers */ = {isa = PBXBuildFile; fileRef = 62E7CAAE19CDFFB500465A83 /* HTMLTokenizerCharacters.h */; }; + 62D89DB31AE7081600B6243D /* HTMLNamespaces.h in Headers */ = {isa = PBXBuildFile; fileRef = 628B7CE61A080E1000602C87 /* HTMLNamespaces.h */; }; + 62D89DB41AE7081600B6243D /* HTMLElementTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 6223211D1A969B9300BACED5 /* HTMLElementTypes.h */; }; + 62D89DB51AE7081600B6243D /* HTMLElementAdjustment.h in Headers */ = {isa = PBXBuildFile; fileRef = 6234C3361AB3BF710046F527 /* HTMLElementAdjustment.h */; }; + 62D89DB61AE7081600B6243D /* HTMLParserInsertionModes.h in Headers */ = {isa = PBXBuildFile; fileRef = 6279F87119E17DC700F12EE5 /* HTMLParserInsertionModes.h */; }; + 62D89DB71AE7081600B6243D /* HTMLQuirksMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 623719431AA12EE8002E03C8 /* HTMLQuirksMode.h */; }; + 62EC7AE61AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 62EC7AE51AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m */; }; + 62EC7AE71AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 62EC7AE51AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m */; }; 62F31FDD19E9DCCF007F0657 /* HTMLTokenizerEntities.h in Headers */ = {isa = PBXBuildFile; fileRef = 62F31FDB19E9DCCF007F0657 /* HTMLTokenizerEntities.h */; }; 62F31FDE19E9DCCF007F0657 /* HTMLTokenizerEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F31FDC19E9DCCF007F0657 /* HTMLTokenizerEntities.m */; }; 62F31FDF19E9DCCF007F0657 /* HTMLTokenizerEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = 62F31FDC19E9DCCF007F0657 /* HTMLTokenizerEntities.m */; }; @@ -173,7 +196,7 @@ 6235CE9F1AA5170A0026937B /* HTMLMarker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLMarker.m; sourceTree = ""; }; 62362A3A1A9FA70400301989 /* HTMLText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLText.h; sourceTree = ""; }; 62362A3B1A9FA70400301989 /* HTMLText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLText.m; sourceTree = ""; }; - 62362A3F1A9FDE8A00301989 /* HTMLNodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLNodes.h; sourceTree = ""; }; + 62362A3F1A9FDE8A00301989 /* HTMLDOM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLDOM.h; sourceTree = ""; }; 62363C3B1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLListOfActiveFormattingElements.h; sourceTree = ""; }; 62363C3C1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLListOfActiveFormattingElements.m; sourceTree = ""; }; 6236738D1AC0CE2500FF89B3 /* HTMLKitTokenizerPerformance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitTokenizerPerformance.m; sourceTree = ""; }; @@ -186,11 +209,9 @@ 623857951A9E8606003A45D9 /* HTMLDocumentType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLDocumentType.m; sourceTree = ""; }; 623857991A9E8934003A45D9 /* HTMLComment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLComment.h; sourceTree = ""; }; 6238579A1A9E8934003A45D9 /* HTMLComment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLComment.m; sourceTree = ""; }; - 6238C9831AB8D6330006512E /* HTMLKitExceptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLKitExceptions.h; sourceTree = ""; }; - 6238C9841AB8D6330006512E /* HTMLKitExceptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitExceptions.m; sourceTree = ""; }; - 623916C11AC707250066B4FE /* HTMLNodeTreeEnumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeTreeEnumerator.h; sourceTree = ""; }; - 623916C21AC707250066B4FE /* HTMLNodeTreeEnumerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeTreeEnumerator.m; sourceTree = ""; }; - 623916C61AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitNodeTreeEnumratorTests.m; sourceTree = ""; }; + 6238C9831AB8D6330006512E /* HTMLKitDOMExceptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLKitDOMExceptions.h; sourceTree = ""; }; + 6238C9841AB8D6330006512E /* HTMLKitDOMExceptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitDOMExceptions.m; sourceTree = ""; }; + 623916C61AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitNodeIteratorTests.m; sourceTree = ""; }; 623975591AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitTreeConstructionTests.m; sourceTree = ""; }; 6239755C1AC364BB007E26F1 /* HTML5LibTreeConstructionTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTML5LibTreeConstructionTest.h; sourceTree = ""; }; 6239755D1AC364BB007E26F1 /* HTML5LibTreeConstructionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTML5LibTreeConstructionTest.m; sourceTree = ""; }; @@ -200,12 +221,22 @@ 624493A919CCE84A00BCDDF4 /* HTMLTokenizerStates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizerStates.h; sourceTree = ""; }; 624493AA19CD0CBE00BCDDF4 /* HTMLToken.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLToken.h; sourceTree = ""; }; 624493AB19CD0CBE00BCDDF4 /* HTMLToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLToken.m; sourceTree = ""; }; + 624717161B22333200C11912 /* HTMLNodeTraversal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeTraversal.h; sourceTree = ""; }; + 624717171B22333200C11912 /* HTMLNodeTraversal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeTraversal.m; sourceTree = ""; }; + 6247171B1B2240B800C11912 /* HTMLTreeWalkerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTreeWalkerTests.m; sourceTree = ""; }; + 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeFilter.m; sourceTree = ""; }; + 624717BA1B22009200B38302 /* HTMLTreeWalker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLTreeWalker.h; sourceTree = ""; }; + 624717BB1B22009200B38302 /* HTMLTreeWalker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTreeWalker.m; sourceTree = ""; }; + 6247A9411B152F4F00CCF25C /* HTMLNodeIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeIterator.h; sourceTree = ""; }; + 6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeIterator.m; sourceTree = ""; }; + 6247A9461B152F8C00CCF25C /* HTMLNodeFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeFilter.h; sourceTree = ""; }; 624AC8FE19FBF4F700BD3C4A /* HTMLTokens.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLTokens.h; sourceTree = ""; }; 624AC90019FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitTokenizerTests.m; sourceTree = ""; }; 624AC90419FBFE8A00BD3C4A /* html5lib-tests */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "html5lib-tests"; sourceTree = ""; }; 624AC90D19FC702E00BD3C4A /* HTML Standard.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "HTML Standard.html"; sourceTree = ""; }; 624B9FB11AE0313300646C4C /* HTMLKitStringCategoryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitStringCategoryTests.m; sourceTree = ""; }; 624B9FB41AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitOrderedDictionaryTests.m; sourceTree = ""; }; + 624FC3791AE591D80015DDF9 /* HTMLKitNodesTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitNodesTests.m; sourceTree = ""; }; 625A14AC19C7829400AD0C32 /* HTMLKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HTMLKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 625A14AF19C7829400AD0C32 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 625A14B219C7829400AD0C32 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -242,6 +273,7 @@ 62D8345719FB1AC4009205A9 /* HTML5LibTokenizerTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTML5LibTokenizerTest.h; sourceTree = ""; }; 62D8345819FB1AC4009205A9 /* HTML5LibTokenizerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTML5LibTokenizerTest.m; sourceTree = ""; }; 62E7CAAE19CDFFB500465A83 /* HTMLTokenizerCharacters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizerCharacters.h; sourceTree = ""; }; + 62EC7AE51AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLKitMutationAlgorithmsTests.m; sourceTree = ""; }; 62F31FDB19E9DCCF007F0657 /* HTMLTokenizerEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLTokenizerEntities.h; sourceTree = ""; }; 62F31FDC19E9DCCF007F0657 /* HTMLTokenizerEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTokenizerEntities.m; sourceTree = ""; }; 62F873E919E088C90062683C /* HTMLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLParser.h; sourceTree = ""; }; @@ -299,31 +331,40 @@ name = Tokenizer; sourceTree = ""; }; - 623719441AA1472B002E03C8 /* Nodes */ = { + 623719441AA1472B002E03C8 /* DOM */ = { isa = PBXGroup; children = ( - 623719431AA12EE8002E03C8 /* HTMLQuirksMode.h */, - 62362A3F1A9FDE8A00301989 /* HTMLNodes.h */, + 62362A3F1A9FDE8A00301989 /* HTMLDOM.h */, 6234584C1A9D2FA4009BD491 /* HTMLNode.h */, 6234584D1A9D2FA4009BD491 /* HTMLNode.m */, - 6279F87219E1808D00F12EE5 /* HTMLElement.h */, - 6279F87319E1808D00F12EE5 /* HTMLElement.m */, 6238578F1A9E772B003A45D9 /* HTMLDocument.h */, 623857901A9E772B003A45D9 /* HTMLDocument.m */, 623857941A9E8606003A45D9 /* HTMLDocumentType.h */, 623857951A9E8606003A45D9 /* HTMLDocumentType.m */, + 623406E41ADB05AD004677A3 /* HTMLDocumentFragment.h */, + 623406E51ADB05AD004677A3 /* HTMLDocumentFragment.m */, + 6279F87219E1808D00F12EE5 /* HTMLElement.h */, + 6279F87319E1808D00F12EE5 /* HTMLElement.m */, 623857991A9E8934003A45D9 /* HTMLComment.h */, 6238579A1A9E8934003A45D9 /* HTMLComment.m */, 62362A3A1A9FA70400301989 /* HTMLText.h */, 62362A3B1A9FA70400301989 /* HTMLText.m */, 623406DF1ADB04F9004677A3 /* HTMLTemplate.h */, 623406E01ADB04F9004677A3 /* HTMLTemplate.m */, - 623406E41ADB05AD004677A3 /* HTMLDocumentFragment.h */, - 623406E51ADB05AD004677A3 /* HTMLDocumentFragment.m */, - 623916C11AC707250066B4FE /* HTMLNodeTreeEnumerator.h */, - 623916C21AC707250066B4FE /* HTMLNodeTreeEnumerator.m */, + 6247A9411B152F4F00CCF25C /* HTMLNodeIterator.h */, + 6247A9421B152F4F00CCF25C /* HTMLNodeIterator.m */, + 624717BA1B22009200B38302 /* HTMLTreeWalker.h */, + 624717BB1B22009200B38302 /* HTMLTreeWalker.m */, + 624717161B22333200C11912 /* HTMLNodeTraversal.h */, + 624717171B22333200C11912 /* HTMLNodeTraversal.m */, + 6247A9461B152F8C00CCF25C /* HTMLNodeFilter.h */, + 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */, + 6238C9831AB8D6330006512E /* HTMLKitDOMExceptions.h */, + 6238C9841AB8D6330006512E /* HTMLKitDOMExceptions.m */, + 628B7CE61A080E1000602C87 /* HTMLNamespaces.h */, + 623719431AA12EE8002E03C8 /* HTMLQuirksMode.h */, ); - name = Nodes; + name = DOM; sourceTree = ""; }; 623975581AC362A5007E26F1 /* Tree Construction */ = { @@ -337,13 +378,15 @@ name = "Tree Construction"; sourceTree = ""; }; - 624B9FB71AE072CB00646C4C /* Nodes */ = { + 624B9FB71AE072CB00646C4C /* DOM */ = { isa = PBXGroup; children = ( - 623916C61AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m */, - 624B9FB41AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m */, + 623916C61AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m */, + 6247171B1B2240B800C11912 /* HTMLTreeWalkerTests.m */, + 624FC3791AE591D80015DDF9 /* HTMLKitNodesTests.m */, + 62EC7AE51AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m */, ); - name = Nodes; + name = DOM; sourceTree = ""; }; 624B9FB81AE072D500646C4C /* Categories */ = { @@ -354,6 +397,14 @@ name = Categories; sourceTree = ""; }; + 624E1A2D1B1D1C8A00E66AAC /* Structures */ = { + isa = PBXGroup; + children = ( + 624B9FB41AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m */, + ); + name = Structures; + sourceTree = ""; + }; 625A14A219C7829400AD0C32 = { isa = PBXGroup; children = ( @@ -401,11 +452,9 @@ children = ( 625A14BC19C7829400AD0C32 /* HTMLKit.h */, 625A14BD19C7829400AD0C32 /* HTMLKit.m */, - 6238C9831AB8D6330006512E /* HTMLKitExceptions.h */, - 6238C9841AB8D6330006512E /* HTMLKitExceptions.m */, 62AE593219F97CCA0043F069 /* Tokenizing */, 628E16EC1ADAE71700B15A06 /* Parsing */, - 623719441AA1472B002E03C8 /* Nodes */, + 623719441AA1472B002E03C8 /* DOM */, 628E16ED1ADAE73700B15A06 /* Categories */, 628E16EE1ADAE75300B15A06 /* Structures */, 625A14B619C7829400AD0C32 /* Supporting Files */, @@ -429,8 +478,9 @@ children = ( 6236738C1AC0CD2400FF89B3 /* Tokenizer */, 623975581AC362A5007E26F1 /* Tree Construction */, - 624B9FB71AE072CB00646C4C /* Nodes */, + 624B9FB71AE072CB00646C4C /* DOM */, 624B9FB81AE072D500646C4C /* Categories */, + 624E1A2D1B1D1C8A00E66AAC /* Structures */, 625A14CB19C7829400AD0C32 /* Supporting Files */, ); name = Tests; @@ -457,7 +507,6 @@ 6234BEED1AABBF1400DEB15F /* HTMLStackOfOpenElements.m */, 62363C3B1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.h */, 62363C3C1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.m */, - 628B7CE61A080E1000602C87 /* HTMLNamespaces.h */, 6223211D1A969B9300BACED5 /* HTMLElementTypes.h */, 6234C3361AB3BF710046F527 /* HTMLElementAdjustment.h */, 6279F87119E17DC700F12EE5 /* HTMLParserInsertionModes.h */, @@ -522,24 +571,34 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 624717181B22333200C11912 /* HTMLNodeTraversal.h in Headers */, 6235CE9B1AA509430026937B /* NSString+HTMLKit.h in Headers */, + 62D89DB11AE7081600B6243D /* HTMLTokenizerStates.h in Headers */, + 62D89DB21AE7081600B6243D /* HTMLTokenizerCharacters.h in Headers */, + 62D89DB31AE7081600B6243D /* HTMLNamespaces.h in Headers */, + 62D89DB41AE7081600B6243D /* HTMLElementTypes.h in Headers */, + 62D89DB51AE7081600B6243D /* HTMLElementAdjustment.h in Headers */, + 62D89DB61AE7081600B6243D /* HTMLParserInsertionModes.h in Headers */, + 62D89DB71AE7081600B6243D /* HTMLQuirksMode.h in Headers */, + 62D89DB01AE7080300B6243D /* HTMLDOM.h in Headers */, 62F873EB19E088C90062683C /* HTMLParser.h in Headers */, 624AC8FF19FBF59800BD3C4A /* HTMLTokens.h in Headers */, - 623916C31AC707250066B4FE /* HTMLNodeTreeEnumerator.h in Headers */, 62AE594419F992F30043F069 /* HTMLCommentToken.h in Headers */, 624493AC19CD0CBE00BCDDF4 /* HTMLToken.h in Headers */, 6235CEA01AA5170A0026937B /* HTMLMarker.h in Headers */, 62F31FDD19E9DCCF007F0657 /* HTMLTokenizerEntities.h in Headers */, 623406E11ADB04F9004677A3 /* HTMLTemplate.h in Headers */, 6234BEEE1AABBF1400DEB15F /* HTMLStackOfOpenElements.h in Headers */, - 6238C9851AB8D6330006512E /* HTMLKitExceptions.h in Headers */, + 6238C9851AB8D6330006512E /* HTMLKitDOMExceptions.h in Headers */, 6238039F1AB63A8C008A53D0 /* HTMLEOFToken.h in Headers */, 6279F87419E1808D00F12EE5 /* HTMLElement.h in Headers */, 62AE593F19F9907C0043F069 /* HTMLTagToken.h in Headers */, 623857911A9E772B003A45D9 /* HTMLDocument.h in Headers */, 624493A619CCC54100BCDDF4 /* HTMLTokenizer.h in Headers */, 62AE593A19F97E1C0043F069 /* HTMLDOCTYPEToken.h in Headers */, + 6247A9471B152F8C00CCF25C /* HTMLNodeFilter.h in Headers */, 623424881AB467B200726190 /* HTMLOrderedDictionary.h in Headers */, + 6247A9431B152F4F00CCF25C /* HTMLNodeIterator.h in Headers */, 625A150419C783EB00AD0C32 /* HTMLKit.h in Headers */, 625A150819C78ABA00AD0C32 /* HTMLInputStreamReader.h in Headers */, 6238579B1A9E8934003A45D9 /* HTMLComment.h in Headers */, @@ -548,6 +607,7 @@ 623857961A9E8606003A45D9 /* HTMLDocumentType.h in Headers */, 623406E61ADB05AD004677A3 /* HTMLDocumentFragment.h in Headers */, 62363C3D1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.h in Headers */, + 624717BC1B22009200B38302 /* HTMLTreeWalker.h in Headers */, 62362A3C1A9FA70400301989 /* HTMLText.h in Headers */, 6234584E1A9D2FA4009BD491 /* HTMLNode.h in Headers */, ); @@ -701,24 +761,27 @@ files = ( 62362A3E1A9FA70400301989 /* HTMLText.m in Sources */, 6235CE9D1AA509430026937B /* NSString+HTMLKit.m in Sources */, + 6247A9451B152F4F00CCF25C /* HTMLNodeIterator.m in Sources */, 6238579D1A9E8934003A45D9 /* HTMLComment.m in Sources */, 6234BEF01AABBF1400DEB15F /* HTMLStackOfOpenElements.m in Sources */, 624493AE19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */, 6235CEA21AA5170A0026937B /* HTMLMarker.m in Sources */, 6234248A1AB467B200726190 /* HTMLOrderedDictionary.m in Sources */, + 624717BE1B22009200B38302 /* HTMLTreeWalker.m in Sources */, 62AE594119F9907C0043F069 /* HTMLTagToken.m in Sources */, 62AE594619F992F30043F069 /* HTMLCommentToken.m in Sources */, 62363C3F1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.m in Sources */, - 623916C51AC707250066B4FE /* HTMLNodeTreeEnumerator.m in Sources */, 62AE593C19F97E1C0043F069 /* HTMLDOCTYPEToken.m in Sources */, 623406E31ADB04F9004677A3 /* HTMLTemplate.m in Sources */, - 6238C9871AB8D6330006512E /* HTMLKitExceptions.m in Sources */, + 6238C9871AB8D6330006512E /* HTMLKitDOMExceptions.m in Sources */, + 6247171A1B22333200C11912 /* HTMLNodeTraversal.m in Sources */, 62F873ED19E088C90062683C /* HTMLParser.m in Sources */, 623803A11AB63A8C008A53D0 /* HTMLEOFToken.m in Sources */, 6279F87619E1808D00F12EE5 /* HTMLElement.m in Sources */, 625A150A19C78ABA00AD0C32 /* HTMLInputStreamReader.m in Sources */, 623857931A9E772B003A45D9 /* HTMLDocument.m in Sources */, 623406E81ADB05AD004677A3 /* HTMLDocumentFragment.m in Sources */, + 624717B91B21FE5400B38302 /* HTMLNodeFilter.m in Sources */, 624493A819CCC54100BCDDF4 /* HTMLTokenizer.m in Sources */, 625A14BE19C7829400AD0C32 /* HTMLKit.m in Sources */, 623857981A9E8606003A45D9 /* HTMLDocumentType.m in Sources */, @@ -735,13 +798,16 @@ files = ( 6239755B1AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m in Sources */, 623CAF9E1AD88BEA00E34C32 /* HTMLKitParserPerformance.m in Sources */, + 6247171D1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */, + 62EC7AE71AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m in Sources */, 624B9FB61AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m in Sources */, 6236738F1AC0CE2500FF89B3 /* HTMLKitTokenizerPerformance.m in Sources */, 624AC90219FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m in Sources */, 624B9FB31AE0313300646C4C /* HTMLKitStringCategoryTests.m in Sources */, - 623916C81AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m in Sources */, + 623916C81AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m in Sources */, 62D8345A19FB1AC4009205A9 /* HTML5LibTokenizerTest.m in Sources */, 6239755F1AC364BB007E26F1 /* HTML5LibTreeConstructionTest.m in Sources */, + 624FC37B1AE591D80015DDF9 /* HTMLKitNodesTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -751,24 +817,27 @@ files = ( 62362A3D1A9FA70400301989 /* HTMLText.m in Sources */, 6235CE9C1AA509430026937B /* NSString+HTMLKit.m in Sources */, + 6247A9441B152F4F00CCF25C /* HTMLNodeIterator.m in Sources */, 6238579C1A9E8934003A45D9 /* HTMLComment.m in Sources */, 6234BEEF1AABBF1400DEB15F /* HTMLStackOfOpenElements.m in Sources */, 624493AD19CD0CBE00BCDDF4 /* HTMLToken.m in Sources */, 6235CEA11AA5170A0026937B /* HTMLMarker.m in Sources */, 623424891AB467B200726190 /* HTMLOrderedDictionary.m in Sources */, + 624717BD1B22009200B38302 /* HTMLTreeWalker.m in Sources */, 62AE594019F9907C0043F069 /* HTMLTagToken.m in Sources */, 62AE594519F992F30043F069 /* HTMLCommentToken.m in Sources */, 62363C3E1ABE428200DAB4C6 /* HTMLListOfActiveFormattingElements.m in Sources */, - 623916C41AC707250066B4FE /* HTMLNodeTreeEnumerator.m in Sources */, 62AE593B19F97E1C0043F069 /* HTMLDOCTYPEToken.m in Sources */, 623406E21ADB04F9004677A3 /* HTMLTemplate.m in Sources */, - 6238C9861AB8D6330006512E /* HTMLKitExceptions.m in Sources */, + 6238C9861AB8D6330006512E /* HTMLKitDOMExceptions.m in Sources */, + 624717191B22333200C11912 /* HTMLNodeTraversal.m in Sources */, 62F873EC19E088C90062683C /* HTMLParser.m in Sources */, 623803A01AB63A8C008A53D0 /* HTMLEOFToken.m in Sources */, 6279F87519E1808D00F12EE5 /* HTMLElement.m in Sources */, 625A150919C78ABA00AD0C32 /* HTMLInputStreamReader.m in Sources */, 623857921A9E772B003A45D9 /* HTMLDocument.m in Sources */, 623406E71ADB05AD004677A3 /* HTMLDocumentFragment.m in Sources */, + 624717B81B21FE5400B38302 /* HTMLNodeFilter.m in Sources */, 624493A719CCC54100BCDDF4 /* HTMLTokenizer.m in Sources */, 625A150219C783DE00AD0C32 /* HTMLKit.m in Sources */, 623857971A9E8606003A45D9 /* HTMLDocumentType.m in Sources */, @@ -785,13 +854,16 @@ files = ( 6239755A1AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m in Sources */, 623CAF9D1AD88BEA00E34C32 /* HTMLKitParserPerformance.m in Sources */, + 6247171C1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */, + 62EC7AE61AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m in Sources */, 624B9FB51AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m in Sources */, 6236738E1AC0CE2500FF89B3 /* HTMLKitTokenizerPerformance.m in Sources */, 624AC90119FBF9ED00BD3C4A /* HTMLKitTokenizerTests.m in Sources */, 624B9FB21AE0313300646C4C /* HTMLKitStringCategoryTests.m in Sources */, - 623916C71AC7209E0066B4FE /* HTMLKitNodeTreeEnumratorTests.m in Sources */, + 623916C71AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m in Sources */, 62D8345919FB1AC4009205A9 /* HTML5LibTokenizerTest.m in Sources */, 6239755E1AC364BB007E26F1 /* HTML5LibTreeConstructionTest.m in Sources */, + 624FC37A1AE591D80015DDF9 /* HTMLKitNodesTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1008,7 +1080,6 @@ buildSettings = { BUNDLE_LOADER = ""; FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); @@ -1032,7 +1103,6 @@ buildSettings = { BUNDLE_LOADER = ""; FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); diff --git a/HTMLKit/HTMLComment.m b/HTMLKit/HTMLComment.m index 23194bc..dbe2ac4 100644 --- a/HTMLKit/HTMLComment.m +++ b/HTMLKit/HTMLComment.m @@ -10,6 +10,11 @@ @implementation HTMLComment +- (instancetype)init +{ + return [self initWithData:nil]; +} + - (instancetype)initWithData:(NSString *)data { self = [super initWithName:@"#comment" type:HTMLNodeComment]; diff --git a/HTMLKit/HTMLNodes.h b/HTMLKit/HTMLDOM.h similarity index 79% rename from HTMLKit/HTMLNodes.h rename to HTMLKit/HTMLDOM.h index f0ffa0e..c570ae0 100644 --- a/HTMLKit/HTMLNodes.h +++ b/HTMLKit/HTMLDOM.h @@ -9,8 +9,11 @@ #import "HTMLNode.h" #import "HTMLDocument.h" #import "HTMLDocumentType.h" +#import "HTMLDocumentFragment.h" #import "HTMLElement.h" #import "HTMLComment.h" #import "HTMLText.h" #import "HTMLTemplate.h" -#import "HTMLDocumentFragment.h" +#import "HTMLNodeIterator.h" +#import "HTMLTreeWalker.h" +#import "HTMLNodeFilter.h" diff --git a/HTMLKit/HTMLDocument.h b/HTMLKit/HTMLDocument.h index ddcec2b..f8a0485 100644 --- a/HTMLKit/HTMLDocument.h +++ b/HTMLKit/HTMLDocument.h @@ -27,6 +27,16 @@ typedef NS_ENUM(short, HTMLDocumentReadyState) @property (nonatomic, assign, readonly) HTMLDocumentReadyState readyState; +@property (nonatomic, strong) HTMLElement *rootElement; + +@property (nonatomic, strong) HTMLElement *documentElement; + +@property (nonatomic, strong) HTMLElement *head; + +@property (nonatomic, strong) HTMLElement *body; + ++ (instancetype)documentWithString:(NSString *)string; + - (HTMLNode *)adoptNode:(HTMLNode *)node; - (HTMLDocument *)associatedInertTemplateDocument; diff --git a/HTMLKit/HTMLDocument.m b/HTMLKit/HTMLDocument.m index 11e2a42..09baa88 100644 --- a/HTMLKit/HTMLDocument.m +++ b/HTMLKit/HTMLDocument.m @@ -7,16 +7,25 @@ // #import "HTMLDocument.h" -#import "HTMLKitExceptions.h" +#import "HTMLParser.h" +#import "HTMLNodeIterator.h" +#import "HTMLKitDOMExceptions.h" @interface HTMLNode (Private) @property (nonatomic, weak) HTMLDocument *ownerDocument; @property (nonatomic, weak) HTMLNode *parentNode; @end +@interface HTMLNodeIterator (Private) +- (void)runRemovingStepsForNode:(HTMLNode *)oldNode + withOldParent:(HTMLNode *)oldParent + andOldPreviousSibling:(HTMLNode *)oldPreviousSibling; +@end + @interface HTMLDocument () { HTMLDocument *_inertTemplateDocument; + NSMutableArray *_nodeIterators; } @property (nonatomic, assign) HTMLDocumentReadyState readyState; @end @@ -25,11 +34,18 @@ #pragma mark - Init ++ (instancetype)documentWithString:(NSString *)string +{ + HTMLParser *parser = [[HTMLParser alloc] initWithString:string]; + return [parser parseDocument]; +} + - (instancetype)init { self = [super initWithName:@"#document" type:HTMLNodeDocument]; if (self) { _readyState = HTMLDocumentLoading; + _nodeIterators = [NSMutableArray new]; } return self; } @@ -57,6 +73,91 @@ } } +#pragma mark - + +- (HTMLElement *)rootElement +{ + for (HTMLNode *node = self.firstChild; node; node = node.nextSibling) { + if (node.nodeType == HTMLNodeElement) { + return node.asElement; + } + } + return nil; +} + +- (void)setRootElement:(HTMLElement *)rootElement +{ + [self replaceChildNode:self.rootElement withNode:rootElement]; +} + +- (HTMLElement *)documentElement +{ + for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) { + if ([node.asElement.tagName isEqualToString:@"html"]) { + return node.asElement; + } + } + return nil; +} + +- (void)setDocumentElement:(HTMLElement *)documentElement +{ + [self replaceChildNode:self.documentElement withNode:documentElement]; +} + +- (HTMLElement *)head +{ + for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) { + if ([node.asElement.tagName isEqualToString:@"head"]) { + return node.asElement; + } + } + return nil; +} + +- (void)setHead:(HTMLElement *)head +{ + [self replaceChildNode:self.head withNode:head]; +} + +- (HTMLElement *)body +{ + for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) { + if ([node.asElement.tagName isEqualToString:@"body"]) { + return node.asElement; + } + } + return nil; +} + +- (void)setBody:(HTMLElement *)body +{ + [self replaceChildNode:self.body withNode:body]; +} + +#pragma mark - Node Iterators + +- (void)attachNodeIterator:(HTMLNodeIterator *)iterator +{ + [_nodeIterators addObject:iterator]; +} + +- (void)detachNodeIterator:(HTMLNodeIterator *)iterator +{ + [_nodeIterators removeObject:iterator]; +} + +- (void)runRemovingStepsForNode:(HTMLNode *)oldNode + withOldParent:(HTMLNode *)oldParent + andOldPreviousSibling:(HTMLNode *)oldPreviousSibling +{ + for (HTMLNodeIterator *iterator in _nodeIterators) { + [iterator runRemovingStepsForNode:oldNode + withOldParent:oldParent + andOldPreviousSibling:oldPreviousSibling]; + } +} + #pragma mark - Mutation Algorithms - (HTMLNode *)adoptNode:(HTMLNode *)node @@ -65,7 +166,7 @@ return nil; } - if (node.type == HTMLNodeDocument) { + if (node.nodeType == HTMLNodeDocument) { [NSException raise:HTMLKitNotSupportedError format:@"%@: Not Fount Error, adopting a document node. The operation is not supported.", NSStringFromSelector(_cmd)]; } diff --git a/HTMLKit/HTMLDocumentFragment.m b/HTMLKit/HTMLDocumentFragment.m index fc03c9a..adcde5e 100644 --- a/HTMLKit/HTMLDocumentFragment.m +++ b/HTMLKit/HTMLDocumentFragment.m @@ -15,6 +15,11 @@ @implementation HTMLDocumentFragment +- (instancetype)init +{ + return [self initWithDocument:nil]; +} + - (instancetype)initWithDocument:(HTMLDocument *)document { self = [super initWithName:@"#document-fragment" type:HTMLNodeDocumentFragment]; @@ -28,7 +33,7 @@ { NSMutableString *content = [NSMutableString string]; for (HTMLNode *node in self.treeEnumerator) { - if (node.type == HTMLNodeText) { + if (node.nodeType == HTMLNodeText) { [content appendString:[(HTMLText *)node data]]; } } diff --git a/HTMLKit/HTMLDocumentType.m b/HTMLKit/HTMLDocumentType.m index ce6687f..a7e8165 100644 --- a/HTMLKit/HTMLDocumentType.m +++ b/HTMLKit/HTMLDocumentType.m @@ -22,6 +22,11 @@ NS_INLINE BOOL nilOrEqual(id first, id second) { @implementation HTMLDocumentType +- (instancetype)init +{ + return [self initWithName:@"html" publicIdentifier:nil systemIdentifier:nil]; +} + - (instancetype)initWithName:(NSString *)name publicIdentifier:(NSString *)publicIdentifier systemIdentifier:(NSString *)systemIdentifier diff --git a/HTMLKit/HTMLElement.h b/HTMLKit/HTMLElement.h index 1085162..6343516 100644 --- a/HTMLKit/HTMLElement.h +++ b/HTMLKit/HTMLElement.h @@ -24,7 +24,7 @@ - (instancetype)initWithTagName:(NSString *)tagName; - (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes; -- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes namespace:(HTMLNamespace)htmlNamespace; +- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(NSDictionary *)attributes; - (BOOL)hasAttribute:(NSString *)name; - (NSString *)objectForKeyedSubscript:(NSString *)name; diff --git a/HTMLKit/HTMLElement.m b/HTMLKit/HTMLElement.m index e12b5f7..6083537 100644 --- a/HTMLKit/HTMLElement.m +++ b/HTMLKit/HTMLElement.m @@ -7,6 +7,7 @@ // #import "HTMLElement.h" +#import "HTMLParser.h" #import "HTMLDocument.h" #import "HTMLText.h" @@ -35,10 +36,10 @@ - (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes { - return [self initWithTagName:tagName attributes:attributes namespace:HTMLNamespaceHTML]; + return [self initWithTagName:tagName namespace:HTMLNamespaceHTML attributes:attributes]; } -- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes namespace:(HTMLNamespace)htmlNamespace +- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(NSDictionary *)attributes { self = [super initWithName:tagName type:HTMLNodeElement]; if (self) { @@ -87,8 +88,8 @@ - (NSString *)textContent { NSMutableString *content = [NSMutableString string]; - for (HTMLNode *node in self.treeEnumerator) { - if (node.type == HTMLNodeText) { + for (HTMLNode *node in self.nodeIterator) { + if (node.nodeType == HTMLNodeText) { [content appendString:[(HTMLText *)node data]]; } } @@ -101,6 +102,14 @@ [self replaceAllChildNodesWithNode:node]; } +- (void)setInnerHTML:(NSString *)innerHTML +{ + HTMLParser *parser = [[HTMLParser alloc] initWithString:innerHTML]; + NSArray *fragmentNodes = [parser parseFragmentWithContextElement:self]; + [self removeAllChildNodes]; + [self appendNodes:fragmentNodes]; +} + #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone @@ -137,8 +146,8 @@ return result; } - if ([self.tagName isEqualToAny:@"pre", @"textarea", @"listing", nil] && self.firstChiledNode.type == HTMLNodeText) { - HTMLText *textNode = (HTMLText *)self.firstChiledNode; + if ([self.tagName isEqualToAny:@"pre", @"textarea", @"listing", nil] && self.firstChild.nodeType == HTMLNodeText) { + HTMLText *textNode = (HTMLText *)self.firstChild; if ([textNode.data hasPrefix:@"\n"]) { [result appendString:@"\n"]; } diff --git a/HTMLKit/HTMLKitExceptions.h b/HTMLKit/HTMLKitDOMExceptions.h similarity index 100% rename from HTMLKit/HTMLKitExceptions.h rename to HTMLKit/HTMLKitDOMExceptions.h diff --git a/HTMLKit/HTMLKitExceptions.m b/HTMLKit/HTMLKitDOMExceptions.m similarity index 91% rename from HTMLKit/HTMLKitExceptions.m rename to HTMLKit/HTMLKitDOMExceptions.m index 8234e00..f7fcacc 100644 --- a/HTMLKit/HTMLKitExceptions.m +++ b/HTMLKit/HTMLKitDOMExceptions.m @@ -6,7 +6,7 @@ // Copyright (c) 2015 BrainCookie. All rights reserved. // -#import "HTMLKitExceptions.h" +#import "HTMLKitDOMExceptions.h" NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError"; NSString * const HTMLKitNotFoundError = @"NotFoundError"; diff --git a/HTMLKit/HTMLListOfActiveFormattingElements.m b/HTMLKit/HTMLListOfActiveFormattingElements.m index 97092c7..d0fb8b0 100644 --- a/HTMLKit/HTMLListOfActiveFormattingElements.m +++ b/HTMLKit/HTMLListOfActiveFormattingElements.m @@ -52,7 +52,7 @@ } if (node.htmlNamespace == element.htmlNamespace && [node.tagName isEqualToString:element.tagName] && - [node.attributes isEqualTo:element.attributes]) { + [node.attributes isEqual:element.attributes]) { existing++; } if (existing == 3) { diff --git a/HTMLKit/HTMLNode.h b/HTMLKit/HTMLNode.h index 00d1f08..eb07dbc 100644 --- a/HTMLKit/HTMLNode.h +++ b/HTMLKit/HTMLNode.h @@ -7,6 +7,7 @@ // #import +#import "HTMLNodeIterator.h" typedef NS_ENUM(short, HTMLNodeType) { @@ -24,28 +25,37 @@ typedef NS_ENUM(short, HTMLNodeType) HTMLNodeNotation = 12 // historical }; +typedef NS_ENUM(unsigned short, HTMLDocumentPosition) +{ + HTMLDocumentPositionEquivalent = 0x0, + HTMLDocumentPositionDisconnected = 0x01, + HTMLDocumentPositionPreceding = 0x02, + HTMLDocumentPositionFollowing = 0x04, + HTMLDocumentPositionContains = 0x08, + HTMLDocumentPositionContainedBy = 0x10, + HTMLDocumentPositionImplementationSpecific = 0x20 +}; + @class HTMLDocument; @class HTMLElement; @interface HTMLNode : NSObject -@property (nonatomic, assign, readonly) HTMLNodeType type; +@property (nonatomic, assign, readonly) HTMLNodeType nodeType; @property (nonatomic, strong, readonly) NSString *name; @property (nonatomic, weak, readonly) HTMLDocument *ownerDocument; -@property (nonatomic, strong, readonly) NSString *baseURI; - @property (nonatomic, weak, readonly) HTMLNode *parentNode; @property (nonatomic, weak, readonly) HTMLElement *parentElement; @property (nonatomic, strong, readonly) NSOrderedSet *childNodes; -@property (nonatomic, strong, readonly) HTMLNode *firstChiledNode; +@property (nonatomic, strong, readonly) HTMLNode *firstChild; -@property (nonatomic, strong, readonly) HTMLNode *lastChildNode; +@property (nonatomic, strong, readonly) HTMLNode *lastChild; @property (nonatomic, strong, readonly) HTMLNode *previousSibling; @@ -53,8 +63,16 @@ typedef NS_ENUM(short, HTMLNodeType) @property (nonatomic, copy) NSString *textContent; +@property (nonatomic, strong, readonly) NSString *outerHTML; + +@property (nonatomic, copy) NSString *innerHTML; + +- (instancetype)init NS_UNAVAILABLE; + - (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type; +- (HTMLElement *)asElement; + - (BOOL)hasChildNodes; - (BOOL)hasChildNodeOfType:(HTMLNodeType)type; @@ -65,16 +83,22 @@ typedef NS_ENUM(short, HTMLNodeType) - (NSUInteger)indexOfChildNode:(HTMLNode *)node; -- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child; +- (HTMLNode *)prependNode:(HTMLNode *)node; + +- (void)prependNodes:(NSArray *)nodes; - (HTMLNode *)appendNode:(HTMLNode *)node; - (void)appendNodes:(NSArray *)nodes; +- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child; + - (HTMLNode *)replaceChildNode:(HTMLNode *)node withNode:(HTMLNode *)replacement; - (void)replaceAllChildNodesWithNode:(HTMLNode *)node; +- (void)removeFromParentNode; + - (HTMLNode *)removeChildNode:(HTMLNode *)node; - (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index; @@ -83,15 +107,19 @@ typedef NS_ENUM(short, HTMLNodeType) - (void)removeAllChildNodes; +- (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)node; +- (BOOL)isDescendantOfNode:(HTMLNode *)node; +- (BOOL)containsNode:(HTMLNode *)node; + - (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block; -- (NSEnumerator *)treeEnumerator; +- (void)enumerateChildElementsUsingBlock:(void (^)(HTMLElement *element, NSUInteger idx, BOOL *stop))block; -- (NSEnumerator *)reverseTreeEnumerator; - -- (NSString *)outerHTML; - -- (NSString *)innerHTML; +- (HTMLNodeIterator *)nodeIterator; +- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions + filter:(id)filter; +- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions + filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter; - (NSString *)treeDescription; diff --git a/HTMLKit/HTMLNode.m b/HTMLKit/HTMLNode.m index 368a196..fdff072 100644 --- a/HTMLKit/HTMLNode.m +++ b/HTMLKit/HTMLNode.m @@ -12,8 +12,13 @@ #import "HTMLElement.h" #import "HTMLText.h" #import "HTMLComment.h" -#import "HTMLKitExceptions.h" -#import "HTMLNodeTreeEnumerator.h" +#import "HTMLKitDOMExceptions.h" + +@interface HTMLDocument (Private) +- (void)runRemovingStepsForNode:(HTMLNode *)oldNode + withOldParent:(HTMLNode *)oldParent + andOldPreviousSibling:(HTMLNode *)oldPreviousSibling; +@end @interface HTMLNode () { @@ -32,7 +37,7 @@ self = [super init]; if (self) { _name = name; - _type = type; + _nodeType = type; _childNodes = [NSMutableOrderedSet new]; } return self; @@ -42,7 +47,7 @@ - (HTMLDocument *)ownerDocument { - if (_type == HTMLNodeDocument) { + if (_nodeType == HTMLNodeDocument) { return (HTMLDocument *)self; } else { return _ownerDocument; @@ -55,12 +60,6 @@ [self.childNodes.array makeObjectsPerformSelector:@selector(setOwnerDocument:) withObject:ownerDocument]; } -- (void)setBaseURI:(NSString *)baseURI -{ - _baseURI = [baseURI copy]; - [self.childNodes.array makeObjectsPerformSelector:@selector(setBaseURI:) withObject:baseURI]; -} - - (void)setParentNode:(HTMLNode *)parentNode { _parentNode = parentNode; @@ -68,15 +67,15 @@ - (HTMLElement *)parentElement { - return _parentNode.type == HTMLNodeElement ? (HTMLElement *)_parentNode : nil; + return _parentNode.nodeType == HTMLNodeElement ? (HTMLElement *)_parentNode : nil; } -- (HTMLNode *)firstChiledNode +- (HTMLNode *)firstChild { return self.childNodes.firstObject; } -- (HTMLNode *)lastChildNode +- (HTMLNode *)lastChild { return self.childNodes.lastObject; } @@ -104,6 +103,13 @@ return nil; } +#pragma mark - Cast + +- (HTMLElement *)asElement +{ + return (HTMLElement *)self; +} + #pragma mark - Child Nodes - (BOOL)hasChildNodes @@ -114,7 +120,7 @@ - (BOOL)hasChildNodeOfType:(HTMLNodeType)type { NSUInteger index = [self.childNodes indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { - if ([(HTMLNode *)obj type] == type) { + if ([(HTMLNode *)obj nodeType] == type) { *stop = YES; return YES; } @@ -139,35 +145,65 @@ return [self.childNodes indexOfObject:node]; } -- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child +- (HTMLNode *)prependNode:(HTMLNode *)node { - node = [self preInsertNode:node beforeChildNode:child]; - node.parentNode = self; - return node; + return [self insertNode:node beforeChildNode:self.firstChild]; +} + +- (void)prependNodes:(NSArray *)nodes +{ + for (id node in nodes.reverseObjectEnumerator) { + [self insertNode:node beforeChildNode:self.firstChild]; + } } - (HTMLNode *)appendNode:(HTMLNode *)node { - node = [self preInsertNode:node beforeChildNode:nil]; - node.parentNode = self; - return node; + return [self insertNode:node beforeChildNode:nil]; } - (void)appendNodes:(NSArray *)nodes { for (id node in nodes) { - [self appendNode:node]; + [self insertNode:node beforeChildNode:nil]; } } +- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child +{ +#ifndef HTMLKIT_NO_DOM_CHECKS + [self ensurePreInsertionValidityOfNode:node beforeChildNode:child]; +#endif + + [self.ownerDocument adoptNode:node]; + + NSArray *nodes = node.nodeType == HTMLNodeDocumentFragment ? [NSArray arrayWithArray:node.childNodes.array] : @[node]; + + NSUInteger index = [self indexOfChildNode:child]; + if (index != NSNotFound) { + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, nodes.count)]; + [(NSMutableOrderedSet *)self.childNodes insertObjects:nodes atIndexes:indexes]; + } else { + [(NSMutableOrderedSet *)self.childNodes addObjectsFromArray:nodes]; + } + + if (node.nodeType == HTMLNodeDocumentFragment) { + [node removeAllChildNodes]; + } + + [nodes makeObjectsPerformSelector:@selector(setParentNode:) withObject:self]; + + return node; +} + - (HTMLNode *)replaceChildNode:(HTMLNode *)child withNode:(HTMLNode *)node { +#ifndef HTMLKIT_NO_DOM_CHECKS [self ensureReplacementValidityOfChildNode:child withNode:node]; +#endif - [self.ownerDocument adoptNode:node]; - NSUInteger index = [self indexOfChildNode:child]; - node.parentNode = self; - [(NSMutableOrderedSet *)self.childNodes replaceObjectAtIndex:index withObject:node]; + [self insertNode:node beforeChildNode:child]; + [child removeFromParentNode]; return child; } @@ -181,6 +217,11 @@ } } +- (void)removeFromParentNode +{ + [self.parentNode removeChildNode:self]; +} + - (HTMLNode *)removeChildNode:(HTMLNode *)child { if (child.parentNode != self) { @@ -189,8 +230,16 @@ NSStringFromSelector(_cmd), child]; } + HTMLNode *oldNode = child; + HTMLNode *oldParent = child.parentNode; + HTMLNode *oldPreviousSibling = child.previousSibling; + [(NSMutableOrderedSet *)self.childNodes removeObject:child]; child.parentNode = nil; + + [self.ownerDocument runRemovingStepsForNode:oldNode + withOldParent:oldParent + andOldPreviousSibling:oldPreviousSibling]; return child; } @@ -205,14 +254,95 @@ for (HTMLNode *child in self.childNodes.array) { [node appendNode:child]; } - [self removeAllChildNodes]; + [(NSMutableOrderedSet *)self.childNodes removeAllObjects]; } - (void)removeAllChildNodes { + [self.childNodes.array makeObjectsPerformSelector:@selector(setParentNode:) withObject:nil]; [(NSMutableOrderedSet *)self.childNodes removeAllObjects]; } +- (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)otherNode +{ + if (otherNode == nil) { + return HTMLDocumentPositionDisconnected; + } + + if (self == otherNode) { + return HTMLDocumentPositionEquivalent; + } + + NSArray * (^ ancestorNodes) (HTMLNode *) = ^ NSArray * (HTMLNode *node) { + NSMutableArray *ancestors = [NSMutableArray array]; + for (HTMLNode *node = self; node; node = node.parentNode) { + [ancestors addObject:node]; + } + return ancestors; + }; + + NSArray *ancestors1 = ancestorNodes(self); + NSArray *ancestors2 = ancestorNodes(otherNode); + + if (ancestors1.lastObject != ancestors2.lastObject) { + return HTMLDocumentPositionDisconnected | + HTMLDocumentPositionImplementationSpecific | + HTMLDocumentPositionFollowing; + } + + for (NSUInteger i = MIN(ancestors1.count - 1, ancestors2.count - 1); i; --i) { + HTMLNode *child1 = ancestors1[i]; + HTMLNode *child2 = ancestors2[i]; + + if (child1 != child2) { + for (HTMLNode *sibling = child1.nextSibling; sibling; sibling = sibling.nextSibling) { + if (sibling == child2) { + return HTMLDocumentPositionFollowing; + } + } + return HTMLDocumentPositionPreceding; + } + } + + if (ancestors1.count < ancestors2.count) { + return HTMLDocumentPositionContainedBy | HTMLDocumentPositionFollowing; + } else { + return HTMLDocumentPositionContains | HTMLDocumentPositionPreceding; + } +} + +- (BOOL)isDescendantOfNode:(HTMLNode *)otherNode +{ + if (otherNode == nil) { + return NO; + } + + if (self.ownerDocument != otherNode.ownerDocument) { + return NO; + } + + if (!otherNode.hasChildNodes) { + return NO; + } + + if (otherNode.nodeType == HTMLNodeDocument) { + return self.nodeType != HTMLNodeDocument && self.ownerDocument == otherNode; + } + + for (HTMLNode *parentNode = self.parentNode; parentNode; parentNode = parentNode.parentNode) { + if (parentNode == otherNode) { + return YES; + } + } + + return NO; +} + +- (BOOL)containsNode:(HTMLNode *)otherNode +{ + return self == otherNode || [otherNode isDescendantOfNode:self]; +} + #pragma mark - Enumeration - (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block @@ -226,40 +356,48 @@ }]; } -- (NSEnumerator *)treeEnumerator +- (void)enumerateChildElementsUsingBlock:(void (^)(HTMLElement *element, NSUInteger idx, BOOL *stop))block { - return [[HTMLNodeTreeEnumerator alloc] initWithNode:self reverse:NO]; -} - -- (NSEnumerator *)reverseTreeEnumerator -{ - return [[HTMLNodeTreeEnumerator alloc] initWithNode:self reverse:YES]; -} - -#pragma mark - Mutation Algorithms - -- (HTMLNode *)preInsertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child -{ - [self ensurePreInsertionValidityOfNode:node beforeChildNode:child]; - [self.ownerDocument adoptNode:node]; - NSUInteger index = [self indexOfChildNode:child]; - if (index != NSNotFound) { - [(NSMutableOrderedSet *)self.childNodes insertObject:node atIndex:index]; - } else { - [(NSMutableOrderedSet *)self.childNodes addObject:node]; + if (block == nil) { + return; } - return node; + [self.childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if ([obj isKindOfClass:[HTMLElement class]]) { + block([obj asElement], idx, stop); + } + }]; } -NS_INLINE void CheckParentValid(HTMLNode *node, NSString *cmd) +- (HTMLNodeIterator *)nodeIterator { - if (node.type != HTMLNodeDocument && - node.type != HTMLNodeDocumentFragment && - node.type != HTMLNodeElement) { + return [self nodeIteratorWithShowOptions:HTMLNodeFilterShowAll filter:nil]; +} + +- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions + filter:(id)filter +{ + return [[HTMLNodeIterator alloc] initWithNode:self showOptions:showOptions filter:filter]; +} + +- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions + filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter +{ + return [HTMLNodeIterator iteratorWithNode:self showOptions:showOptions filter:filter]; +} + +#ifndef HTMLKIT_NO_DOM_CHECKS + +#pragma mark - Validity Checks + +NS_INLINE void CheckParentValid(HTMLNode *parent, NSString *cmd) +{ + if (parent.nodeType != HTMLNodeDocument && + parent.nodeType != HTMLNodeDocumentFragment && + parent.nodeType != HTMLNodeElement) { [NSException raise:HTMLKitHierarchyRequestError format:@"%@: Hierarchy Request Error, inserting into %@ is not allowed. The operation would yield an incorrect node tree.", - cmd, node.name]; + cmd, parent.name]; } } @@ -275,11 +413,11 @@ NS_INLINE void CheckChildsParent(HTMLNode *parent, HTMLNode *child, NSString *cm NS_INLINE void CheckInsertedNodeValid(HTMLNode *node, NSString *cmd) { - if (node.type != HTMLNodeDocumentFragment && - node.type != HTMLNodeDocumentType && - node.type != HTMLNodeElement && - node.type != HTMLNodeText && - node.type != HTMLNodeComment) { + if (node.nodeType != HTMLNodeDocumentFragment && + node.nodeType != HTMLNodeDocumentType && + node.nodeType != HTMLNodeElement && + node.nodeType != HTMLNodeText && + node.nodeType != HTMLNodeComment) { [NSException raise:HTMLKitHierarchyRequestError format:@"%@: Hierarchy Request Error, inserting a %@ is not allowed. The operation would yield an incorrect node tree.", cmd, node.name]; @@ -288,13 +426,13 @@ NS_INLINE void CheckInsertedNodeValid(HTMLNode *node, NSString *cmd) NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSString *cmd) { - if (node.type == HTMLNodeText && parent.type == HTMLNodeDocument) { + if (node.nodeType == HTMLNodeText && parent.nodeType == HTMLNodeDocument) { [NSException raise:HTMLKitHierarchyRequestError format:@"%@: Hierarchy Request Error, inserting a text node %@ into docuement is not allowed. The operation would yield an incorrect node tree.", cmd, parent.name]; } - if (node.type == HTMLNodeDocumentType && parent.type != HTMLNodeDocument) { + if (node.nodeType == HTMLNodeDocumentType && parent.nodeType != HTMLNodeDocument) { [NSException raise:HTMLKitHierarchyRequestError format:@"%@: Hierarchy Request Error, inserting a doctype %@ into a non-document node is not allowed. The operation would yield an incorrect node tree.", cmd, parent.name]; @@ -313,28 +451,28 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin void (^ hierarchyError)() = ^{ [NSException raise:HTMLKitHierarchyRequestError - format:@"%@: Hierarchy Request Error. The operation would yield an incorrect node tree.", - NSStringFromSelector(_cmd)]; + format:@"%@: Hierarchy Request Error, inserting (%@) into (%@). The operation would yield an incorrect node tree.", + NSStringFromSelector(_cmd), self, node]; }; - if (self.type == HTMLNodeDocument) { - switch (node.type) { + if (self.nodeType == HTMLNodeDocument) { + switch (node.nodeType) { case HTMLNodeDocumentFragment: - if (self.childNodesCount > 1 || - [self hasChildNodeOfType:HTMLNodeText]) { + if (node.childNodesCount > 1 || + [node hasChildNodeOfType:HTMLNodeText]) { hierarchyError(); - } else if (self.childNodesCount == 1) { - if (self.hasChildNodes || - child.type == HTMLNodeDocumentType || - child.nextSibling.type == HTMLNodeDocumentType) { + } else if (node.childNodesCount == 1) { + if ([self hasChildNodeOfType:HTMLNodeElement] || + child.nodeType == HTMLNodeDocumentType || + child.nextSibling.nodeType == HTMLNodeDocumentType) { hierarchyError(); } } break; case HTMLNodeElement: if ([self hasChildNodeOfType:HTMLNodeElement] || - child.type == HTMLNodeDocumentType || - (child != nil && child.nextSibling.type == HTMLNodeDocumentType)) { + child.nodeType == HTMLNodeDocumentType || + (child != nil && child.nextSibling.nodeType == HTMLNodeDocumentType)) { hierarchyError(); } break; @@ -367,43 +505,42 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin NSStringFromSelector(_cmd)]; }; - if (self.type == HTMLNodeDocument) { - switch (node.type) { + void (^ checkParentHasAnotherChildOfType)(HTMLNodeType) = ^ void (HTMLNodeType type) { + [self enumerateChildNodesUsingBlock:^(HTMLNode *node, NSUInteger idx, BOOL *stop) { + if (node.nodeType == type && node != child) { + *stop = YES; + hierarchyError(); + } + }]; + }; + + if (self.nodeType == HTMLNodeDocument) { + switch (node.nodeType) { case HTMLNodeDocumentFragment: - if (self.childNodesCount > 1 || - [self hasChildNodeOfType:HTMLNodeText]) { + if (node.childNodesCount > 1 || + [node hasChildNodeOfType:HTMLNodeText]) { hierarchyError(); - } else if (self.childNodesCount == 1) { - if (self.firstChiledNode != node || - child.nextSibling.type == HTMLNodeDocumentType) { + } else if (node.childNodesCount == 1) { + if (child.nextSibling.nodeType == HTMLNodeDocumentType) { hierarchyError(); } + checkParentHasAnotherChildOfType(HTMLNodeElement); } break; case HTMLNodeElement: { - if (child.nextSibling.type == HTMLNodeDocumentType) { + if (child.nextSibling.nodeType == HTMLNodeDocumentType) { hierarchyError(); } - [self enumerateChildNodesUsingBlock:^(HTMLNode *node, NSUInteger idx, BOOL *stop) { - if (node.type == HTMLNodeElement && node != child) { - *stop = YES; - hierarchyError(); - } - }]; + checkParentHasAnotherChildOfType(HTMLNodeElement); break; } case HTMLNodeDocumentType: { - if (child.previousSibling.type == HTMLNodeElement) { + if (child.previousSibling.nodeType == HTMLNodeElement) { hierarchyError(); } - [self enumerateChildNodesUsingBlock:^(HTMLNode *node, NSUInteger idx, BOOL *stop) { - if (node.type == HTMLNodeDocument && node != child) { - *stop = YES; - hierarchyError(); - } - }]; + checkParentHasAnotherChildOfType(HTMLNodeDocumentType); break; } default: @@ -412,11 +549,13 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin } } +#endif + #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { - HTMLNode *copy = [[self.class alloc] initWithName:self.name type:self.type]; + HTMLNode *copy = [[self.class alloc] initWithName:self.name type:self.nodeType]; return copy; } @@ -433,6 +572,11 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin return [[self.childNodes.array valueForKey:@"outerHTML"] componentsJoinedByString:@""]; } +- (void)setInnerHTML:(NSString *)outerHTML +{ + [self doesNotRecognizeSelector:_cmd]; +} + #pragma mark - Description - (NSString *)treeDescription diff --git a/HTMLKit/HTMLNodeFilter.h b/HTMLKit/HTMLNodeFilter.h new file mode 100644 index 0000000..a32a2ea --- /dev/null +++ b/HTMLKit/HTMLNodeFilter.h @@ -0,0 +1,41 @@ +// +// HTMLNodeFilter.h +// HTMLKit +// +// Created by Iska on 27/05/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import + +typedef NS_ENUM(unsigned short, HTMLNodeFilterValue) +{ + HTMLNodeFilterAccept = 1, + HTMLNodeFilterReject = 2, + HTMLNodeFilterSkip = 3 +}; + +typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions) +{ + HTMLNodeFilterShowAll = 0xFFFFFFFF, + HTMLNodeFilterShowElement = 0x1, + HTMLNodeFilterShowText = 0x4, + HTMLNodeFilterShowComment = 0x80, + HTMLNodeFilterShowDocument = 0x100, + HTMLNodeFilterShowDocumentType = 0x200, + HTMLNodeFilterShowDocumentFragment = 0x400 +}; + +@class HTMLNode; + +@protocol HTMLNodeFilter + +- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node; + +@end + +@interface HTMLNodeFilterBlock : NSObject + ++ (instancetype)filterWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block; + +@end diff --git a/HTMLKit/HTMLNodeFilter.m b/HTMLKit/HTMLNodeFilter.m new file mode 100644 index 0000000..96e4843 --- /dev/null +++ b/HTMLKit/HTMLNodeFilter.m @@ -0,0 +1,42 @@ +// +// HTMLNodeFilter.m +// HTMLKit +// +// Created by Iska on 05/06/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import "HTMLNodeFilter.h" + +@interface HTMLNodeFilterBlock () +{ + BOOL (^ _block)(HTMLNode *); +} +@end + +@implementation HTMLNodeFilterBlock + ++ (instancetype)filterWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *))block +{ + return [[self alloc] initWithBlock:block]; +} + +- (instancetype)initWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *))block +{ + self = [super init]; + if (self) { + _block = [block copy]; + } + return self; +} + +- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node +{ + if (!_block) { + return HTMLNodeFilterSkip; + } + + return _block(node); +} + +@end diff --git a/HTMLKit/HTMLNodeIterator.h b/HTMLKit/HTMLNodeIterator.h new file mode 100644 index 0000000..6dd269e --- /dev/null +++ b/HTMLKit/HTMLNodeIterator.h @@ -0,0 +1,36 @@ +// +// HTMLNodeIterator.h +// HTMLKit +// +// Created by Iska on 27/05/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import +#import "HTMLNodeFilter.h" + +@class HTMLNode; + +@interface HTMLNodeIterator : NSEnumerator + +@property (nonatomic, strong, readonly) HTMLNode *root; +@property (nonatomic, strong, readonly) HTMLNode *referenceNode; +@property (nonatomic, assign, readonly) BOOL pointerBeforeReferenceNode; +@property (nonatomic, assign, readonly) HTMLNodeFilterShowOptions whatToShow; +@property (nonatomic, strong, readonly) id filter; + ++ (instancetype)iteratorWithNode:(HTMLNode *)node + showOptions:(HTMLNodeFilterShowOptions)showOptions + filter:(HTMLNodeFilterValue (^)(HTMLNode *node))filter; + +- (instancetype)initWithNode:(HTMLNode *)node; +- (instancetype)initWithNode:(HTMLNode *)node + filter:(id)filter; +- (instancetype)initWithNode:(HTMLNode *)node + showOptions:(HTMLNodeFilterShowOptions)showOptions + filter:(id)filter; + +- (HTMLNode *)nextNode; +- (HTMLNode *)previousNode; + +@end diff --git a/HTMLKit/HTMLNodeIterator.m b/HTMLKit/HTMLNodeIterator.m new file mode 100644 index 0000000..6bfb9f4 --- /dev/null +++ b/HTMLKit/HTMLNodeIterator.m @@ -0,0 +1,166 @@ +// +// HTMLNodeIterator.m +// HTMLKit +// +// Created by Iska on 27/05/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import "HTMLNodeIterator.h" +#import "HTMLDocument.h" +#import "HTMLNode.h" +#import "HTMLNodeFilter.h" +#import "HTMLNodeTraversal.h" + +typedef NS_ENUM(short, TraverseDirection) +{ + TraverseDirectionNext, + TraverseDirectionPrevious +}; + +@interface HTMLDocument (Private) +- (void)attachNodeIterator:(HTMLNodeIterator *)iterator; +- (void)detachNodeIterator:(HTMLNodeIterator *)iterator; +@end + +@interface HTMLNodeIterator () +{ + HTMLNode *_root; +} +@end + +@implementation HTMLNodeIterator + +#pragma mark - Lifecycle + ++ (instancetype)iteratorWithNode:(HTMLNode *)node + showOptions:(HTMLNodeFilterShowOptions)showOptions + filter:(HTMLNodeFilterValue (^)(HTMLNode *))filter +{ + return [[self alloc] initWithNode:node + showOptions:showOptions + filter:[HTMLNodeFilterBlock filterWithBlock:filter]]; +} + +- (instancetype)initWithNode:(HTMLNode *)node +{ + return [self initWithNode:node filter:nil]; +} + +- (instancetype)initWithNode:(HTMLNode *)node + filter:(id)filter +{ + return [self initWithNode:node showOptions:HTMLNodeFilterShowAll filter:filter]; +} + +- (instancetype)initWithNode:(HTMLNode *)node + showOptions:(HTMLNodeFilterShowOptions)showOptions + filter:(id)filter +{ + self = [super init]; + if (self) { + _root = node; + _filter = filter; + _whatToShow = showOptions; + _referenceNode = _root; + _pointerBeforeReferenceNode = YES; + [_root.ownerDocument attachNodeIterator:self]; + } + return self; +} + +- (void)dealloc +{ + [_root.ownerDocument detachNodeIterator:self]; +} + +#pragma mark - Removing Steps + +- (void)runRemovingStepsForNode:(HTMLNode *)oldNode + withOldParent:(HTMLNode *)oldParent + andOldPreviousSibling:(HTMLNode *)oldPreviousSibling +{ + if ([oldNode containsNode:_root]) { + return; + } + + if (![oldNode containsNode:_referenceNode]) { + return; + } + + if (_pointerBeforeReferenceNode) { + HTMLNode *nextSibling = oldPreviousSibling != nil ? oldPreviousSibling.nextSibling : oldParent.firstChild; + if (nextSibling != nil) { + _referenceNode = nextSibling; + return; + } + + HTMLNode *next = FollowingNode(oldParent, _root); + if ([_root containsNode:next]) { + _referenceNode = next; + return; + } + + _pointerBeforeReferenceNode = NO; + } + + HTMLNode * (^ lastInclusiveDescendant) (HTMLNode *) = ^ HTMLNode * (HTMLNode *node) { + while (node.lastChild) { + node = node.lastChild; + } + return node; + }; + + _referenceNode = oldPreviousSibling != nil ? lastInclusiveDescendant(oldPreviousSibling) : oldParent; +} + +#pragma mark - Traversal + +- (HTMLNode *)traverseInDirection:(TraverseDirection)direction +{ + HTMLNode *node = self.referenceNode; + BOOL beforeNode = self.pointerBeforeReferenceNode; + + do { + if (direction == TraverseDirectionNext) { + if (!beforeNode) { + node = FollowingNode(node, self.root); + if (node == nil) { + return nil; + } + } + beforeNode = NO; + } else { + if (beforeNode) { + node = PrecedingNode(node, self.root); + if (node == nil) { + return nil; + } + } + beforeNode = YES; + } + } while (FilterNode(self.filter, self.whatToShow, node) != HTMLNodeFilterAccept); + + _referenceNode = node; + _pointerBeforeReferenceNode = beforeNode; + return node; +} + +- (HTMLNode *)nextNode +{ + return [self traverseInDirection:TraverseDirectionNext]; +} + +- (HTMLNode *)previousNode +{ + return [self traverseInDirection:TraverseDirectionPrevious]; +} + +#pragma mark - NSEnumerator + +- (id)nextObject +{ + return self.nextNode; +} + +@end diff --git a/HTMLKit/HTMLNodeTraversal.h b/HTMLKit/HTMLNodeTraversal.h new file mode 100644 index 0000000..ee2bf7e --- /dev/null +++ b/HTMLKit/HTMLNodeTraversal.h @@ -0,0 +1,17 @@ +// +// HTMLNodeTraversal.h +// HTMLKit +// +// Created by Iska on 05/06/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import +#import "HTMLNodeFilter.h" + +@class HTMLNode; + +extern HTMLNode * PrecedingNode(HTMLNode *node, HTMLNode *root); +extern HTMLNode * FollowingNode(HTMLNode *node, HTMLNode *root); +extern HTMLNode * FollowingNodeSkippingChildren(HTMLNode *node, HTMLNode *root); +extern HTMLNodeFilterValue FilterNode(id filter, HTMLNodeFilterShowOptions whatToShow, HTMLNode *node); diff --git a/HTMLKit/HTMLNodeTraversal.m b/HTMLKit/HTMLNodeTraversal.m new file mode 100644 index 0000000..dd17b2a --- /dev/null +++ b/HTMLKit/HTMLNodeTraversal.m @@ -0,0 +1,76 @@ +// +// HTMLNodeTraversal.m +// HTMLKit +// +// Created by Iska on 05/06/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import "HTMLNodeTraversal.h" +#import "HTMLNode.h" +#import "HTMLNodeFilter.h" + +HTMLNode * PrecedingNode(HTMLNode *node, HTMLNode *root) +{ + HTMLNode *previous = node.previousSibling; + if (previous != nil) { + while (previous.lastChild != nil) { + previous = previous.lastChild; + } + return previous; + } + + if (node == root) { + return nil; + } + + return node.parentNode; +} + +HTMLNode * FollowingNode(HTMLNode *node, HTMLNode *root) +{ + if (node.firstChild != nil) { + return node.firstChild; + } + + do { + if (node == root) { + return nil; + } + if (node.nextSibling != nil) { + return node.nextSibling; + } + node = node.parentNode; + } while (node != nil); + + return nil; +} + +HTMLNode * FollowingNodeSkippingChildren(HTMLNode *node, HTMLNode *root) +{ + do { + if (node == root) { + return nil; + } + if (node.nextSibling != nil) { + return node.nextSibling; + } + node = node.parentNode; + } while (node != nil); + + return nil; +} + +HTMLNodeFilterValue FilterNode(id filter, HTMLNodeFilterShowOptions whatToShow, HTMLNode *node) +{ + unsigned long nthBit = (1 << (node.nodeType - 1)) & whatToShow; + if (!nthBit) { + return HTMLNodeFilterSkip; + } + + if (filter == nil) { + return HTMLNodeFilterAccept; + } + + return [filter acceptNode:node]; +} diff --git a/HTMLKit/HTMLNodeTreeEnumerator.h b/HTMLKit/HTMLNodeTreeEnumerator.h deleted file mode 100644 index baf7243..0000000 --- a/HTMLKit/HTMLNodeTreeEnumerator.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// HTMLNodeTreeEnumerator.h -// HTMLKit -// -// Created by Iska on 28/03/15. -// Copyright (c) 2015 BrainCookie. All rights reserved. -// - -#import - -@class HTMLNode; - -@interface HTMLNodeTreeEnumerator : NSEnumerator - -- (instancetype)initWithNode:(HTMLNode *)node reverse:(BOOL)reverse; - -@end diff --git a/HTMLKit/HTMLNodeTreeEnumerator.m b/HTMLKit/HTMLNodeTreeEnumerator.m deleted file mode 100644 index 8f82972..0000000 --- a/HTMLKit/HTMLNodeTreeEnumerator.m +++ /dev/null @@ -1,54 +0,0 @@ -// -// HTMLNodeTreeEnumerator.m -// HTMLKit -// -// Created by Iska on 28/03/15. -// Copyright (c) 2015 BrainCookie. All rights reserved. -// - -#import "HTMLNodeTreeEnumerator.h" -#import "HTMLNode.h" - -@interface HTMLNodeTreeEnumerator () -{ - BOOL _reverse; - NSMutableArray *_stack; -} -@end - -@implementation HTMLNodeTreeEnumerator - -- (instancetype)initWithNode:(HTMLNode *)node reverse:(BOOL)reverse -{ - self = [super init]; - if (self) { - _reverse = reverse; - _stack = [[NSMutableArray alloc] initWithObjects:node, nil]; - } - return self; -} - -- (id)nextObject -{ - if (_stack.count == 0) { - return nil; - } - - HTMLNode *node = _stack.lastObject; - [_stack removeLastObject]; - - NSArray *childNodes = node.childNodes.array; - if (childNodes != nil && childNodes.count > 0) { - if (childNodes.count > 1) { - NSRange range = NSMakeRange(_reverse ? 0 : 1, childNodes.count - 1); - NSArray *rest = [childNodes subarrayWithRange:range]; - - [_stack addObjectsFromArray:_reverse ? rest : rest.reverseObjectEnumerator.allObjects]; - } - [_stack addObject:_reverse ? childNodes.lastObject : childNodes.firstObject]; - } - - return node; -} - -@end diff --git a/HTMLKit/HTMLParser.m b/HTMLKit/HTMLParser.m index 5a6e759..6f7bb46 100644 --- a/HTMLKit/HTMLParser.m +++ b/HTMLKit/HTMLParser.m @@ -12,7 +12,7 @@ #import "HTMLStackOfOpenElements.h" #import "HTMLListOfActiveFormattingElements.h" #import "HTMLParserInsertionModes.h" -#import "HTMLNodes.h" +#import "HTMLDOM.h" #import "HTMLElementTypes.h" #import "HTMLElementAdjustment.h" #import "HTMLMarker.h" @@ -127,7 +127,7 @@ return nil; } - if ([_contextElement isEqualTo:contextElement]) { + if ([_contextElement isEqual:contextElement]) { return [_document childNodeAtIndex:0].childNodes.array; } @@ -421,8 +421,8 @@ - (HTMLElement *)createElementForToken:(HTMLTagToken *)token inNamespace:(HTMLNamespace)htmlNamespace { HTMLElement *element = [[HTMLElement alloc] initWithTagName:token.tagName - attributes:token.attributes - namespace:htmlNamespace]; + namespace:htmlNamespace + attributes:token.attributes]; return element; } @@ -452,11 +452,11 @@ HTMLElement *child = nil; HTMLNode *adjustedInsertionLocation = [self appropriatePlaceForInsertingANodeWithOverrideTarget:nil beforeChildNode:&child]; - if (adjustedInsertionLocation.type != HTMLNodeDocument) { - if (child != nil && child.previousSibling.type == HTMLNodeText) { + if (adjustedInsertionLocation.nodeType != HTMLNodeDocument) { + if (child != nil && child.previousSibling.nodeType == HTMLNodeText) { [(HTMLText *)child.previousSibling appendString:data]; - } else if (adjustedInsertionLocation.lastChildNode.type == HTMLNodeText) { - [(HTMLText *)adjustedInsertionLocation.lastChildNode appendString:data]; + } else if (adjustedInsertionLocation.lastChild.nodeType == HTMLNodeText) { + [(HTMLText *)adjustedInsertionLocation.lastChild appendString:data]; } else { HTMLText *text = [[HTMLText alloc] initWithData:data]; [adjustedInsertionLocation insertNode:text beforeChildNode:child]; diff --git a/HTMLKit/HTMLTreeWalker.h b/HTMLKit/HTMLTreeWalker.h new file mode 100644 index 0000000..fff2328 --- /dev/null +++ b/HTMLKit/HTMLTreeWalker.h @@ -0,0 +1,36 @@ +// +// HTMLTreeWalker.h +// HTMLKit +// +// Created by Iska on 05/06/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import +#import "HTMLNodeFilter.h" + +@class HTMLNode; + +@interface HTMLTreeWalker : NSObject + +@property (nonatomic, strong, readonly) HTMLNode *root; +@property (nonatomic, assign, readonly) HTMLNodeFilterShowOptions whatToShow; +@property (nonatomic, strong, readonly) id filter; +@property (nonatomic, strong) HTMLNode *currentNode; + +- (instancetype)initWithNode:(HTMLNode *)node; +- (instancetype)initWithNode:(HTMLNode *)node + filter:(id)filter; +- (instancetype)initWithNode:(HTMLNode *)node + showOptions:(HTMLNodeFilterShowOptions)showOptions + filter:(id)filter; + +- (HTMLNode *)parentNode; +- (HTMLNode *)firstChild; +- (HTMLNode *)lastChild; +- (HTMLNode *)previousSibling; +- (HTMLNode *)nextSibling; +- (HTMLNode *)previousNode; +- (HTMLNode *)nextNode; + +@end diff --git a/HTMLKit/HTMLTreeWalker.m b/HTMLKit/HTMLTreeWalker.m new file mode 100644 index 0000000..0e21ed0 --- /dev/null +++ b/HTMLKit/HTMLTreeWalker.m @@ -0,0 +1,236 @@ +// +// HTMLTreeWalker.m +// HTMLKit +// +// Created by Iska on 05/06/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import "HTMLTreeWalker.h" +#import "HTMLNode.h" +#import "HTMLNodeFilter.h" +#import "HTMLNodeTraversal.h" + +typedef NS_ENUM(short, HTMLTreeWalkerChildrenType) +{ + HTMLTreeWalkerChildrenTypeFirst, + HTMLTreeWalkerChildrenTypeLast +}; + +typedef NS_ENUM(short, HTMLTreeWalkerSiblingsType) +{ + HTMLTreeWalkerSiblingsTypeNext, + HTMLTreeWalkerSiblingsTypePrevious +}; + +@implementation HTMLTreeWalker + +#pragma mark - Lifecycle + +- (instancetype)initWithNode:(HTMLNode *)node +{ + return [self initWithNode:node filter:nil]; +} + +- (instancetype)initWithNode:(HTMLNode *)node + filter:(id)filter +{ + return [self initWithNode:node showOptions:HTMLNodeFilterShowAll filter:filter]; +} + +- (instancetype)initWithNode:(HTMLNode *)node + showOptions:(HTMLNodeFilterShowOptions)showOptions + filter:(id)filter +{ + self = [super init]; + if (self) { + _root = node; + _filter = filter; + _whatToShow = showOptions; + _currentNode = _root; + } + return self; +} + +#pragma mark - Traversal + +- (HTMLNode *)parentNode +{ + HTMLNode *node = _currentNode; + + while (node != nil && node != _root) { + node = node.parentNode; + if (node != nil && FilterNode(self.filter, self.whatToShow, node) == HTMLNodeFilterAccept) { + _currentNode = node; + return node; + } + } + return nil; +} + +- (HTMLNode *)traverseChildrenOfType:(HTMLTreeWalkerChildrenType)type +{ + HTMLNode *node = _currentNode; + + node = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.firstChild : node.lastChild; + + while (node != nil) { + HTMLNodeFilterValue result = FilterNode(self.filter, self.whatToShow, node); + if (result == HTMLNodeFilterAccept) { + _currentNode = node; + return node; + } + + if (result == HTMLNodeFilterSkip) { + HTMLNode *child = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.firstChild : node.lastChild; + if (child != nil) { + node = child; + continue; + } + } + + while (node != nil) { + HTMLNode *sibling = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.nextSibling : node.previousSibling; + if (sibling != nil) { + node = sibling; + break; + } + + HTMLNode *parent = node.parentNode; + if (parent == nil || parent == _root || parent == _currentNode) { + return nil; + } + node = parent; + } + } + + return nil; +} + +- (HTMLNode *)firstChild +{ + return [self traverseChildrenOfType:HTMLTreeWalkerChildrenTypeFirst]; +} + +- (HTMLNode *)lastChild +{ + return [self traverseChildrenOfType:HTMLTreeWalkerChildrenTypeLast]; +} + +- (HTMLNode *)traverseSiblingsOfType:(HTMLTreeWalkerSiblingsType)type +{ + HTMLNode *node = _currentNode; + + if (node == _root) { + return nil; + } + + while (YES) { + HTMLNode *sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.nextSibling : node.previousSibling; + while (sibling != nil) { + node = sibling; + HTMLNodeFilterValue result = FilterNode(self.filter, self.whatToShow, node); + if (result == HTMLNodeFilterAccept) { + _currentNode = node; + return node; + } + + sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.firstChild : node.lastChild; + if (result == HTMLNodeFilterReject || sibling == nil) { + sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.nextSibling : node.previousSibling; + } + } + + node = node.parentNode; + if (node == nil || node == _root) { + return nil; + } + + if (FilterNode(self.filter, self.whatToShow, node) == HTMLNodeFilterAccept) { + return nil; + } + } + + return nil; +} + +- (HTMLNode *)previousSibling +{ + return [self traverseSiblingsOfType:HTMLTreeWalkerSiblingsTypePrevious]; +} + +- (HTMLNode *)nextSibling +{ + return [self traverseSiblingsOfType:HTMLTreeWalkerSiblingsTypeNext]; +} + +- (HTMLNode *)previousNode +{ + HTMLNode *node = _currentNode; + + while (node != _root) { + HTMLNode *sibling = node.previousSibling; + + while (sibling != nil) { + node = sibling; + + HTMLNodeFilterValue result = FilterNode(self.filter, self.whatToShow, node); + while (result != HTMLNodeFilterReject && node.hasChildNodes) { + node = node.lastChild; + result = FilterNode(self.filter, self.whatToShow, node); + } + + if (result == HTMLNodeFilterAccept) { + _currentNode = node; + return node; + } + + sibling = node.previousSibling; + } + + if (node == _root || node.parentNode == nil) { + return nil; + } + + node = node.parentNode; + if (FilterNode(self.filter, self.whatToShow, node) == HTMLNodeFilterAccept) { + _currentNode = node; + return node; + } + } + + return nil; +} + +- (HTMLNode *)nextNode +{ + HTMLNode *node = _currentNode; + + HTMLNodeFilterValue result = YES; + + while (YES) { + while (result != HTMLNodeFilterReject && node.hasChildNodes) { + node = node.firstChild; + result = FilterNode(self.filter, self.whatToShow, node); + if (result == HTMLNodeFilterAccept) { + _currentNode = node; + return node; + } + } + + HTMLNode *nextSibling; + while ((nextSibling = FollowingNodeSkippingChildren(node, _root)) != nil) { + node = nextSibling; + result = FilterNode(self.filter, self.whatToShow, node); + if (result == HTMLNodeFilterAccept) { + _currentNode = node; + return node; + } + break; + } + } + + return nil; +} + +@end diff --git a/HTMLKitTests/HTML5LibTreeConstructionTest.m b/HTMLKitTests/HTML5LibTreeConstructionTest.m index 1e8ac4d..9d0a45c 100644 --- a/HTMLKitTests/HTML5LibTreeConstructionTest.m +++ b/HTMLKitTests/HTML5LibTreeConstructionTest.m @@ -13,7 +13,6 @@ #import "HTMLElement.h" #import "HTMLText.h" #import "HTMLComment.h" -#import "HTMLNodeTreeEnumerator.h" static NSString * const HTML5LibTests = @"html5lib-tests"; static NSString * const TreeConstruction = @"tree-construction"; @@ -111,7 +110,7 @@ static NSString * const TreeConstruction = @"tree-construction"; fragment = [fragment substringFromIndex:@"svg ".length]; namespace = HTMLNamespaceSVG; } - test.documentFragment = [[HTMLElement alloc] initWithTagName:fragment attributes:nil namespace:namespace]; + test.documentFragment = [[HTMLElement alloc] initWithTagName:fragment namespace:namespace attributes:nil]; } else if ([match hasPrefix:@"#document\n"]) { NSArray *parts = [[match substringFromIndex:@"#document\n".length] componentsSeparatedByString:@"| "]; NSArray *nodes = [HTML5LibTreeConstructionTest parseDocument:parts]; @@ -245,7 +244,7 @@ NS_INLINE HTMLElement * parseTag(NSString *str) NSString *tagName = parts.count == 2 ? parts[1] : parts[0]; HTMLNamespace namespace = parts.count == 1 ? HTMLNamespaceHTML : ([parts[0] isEqualToString:@"math"] ? HTMLNamespaceMathML : HTMLNamespaceSVG); - HTMLElement *element = [[HTMLElement alloc] initWithTagName:tagName attributes:nil namespace:namespace]; + HTMLElement *element = [[HTMLElement alloc] initWithTagName:tagName namespace:namespace attributes:nil]; return element; } diff --git a/HTMLKitTests/HTMLKitMutationAlgorithmsTests.m b/HTMLKitTests/HTMLKitMutationAlgorithmsTests.m new file mode 100644 index 0000000..421065b --- /dev/null +++ b/HTMLKitTests/HTMLKitMutationAlgorithmsTests.m @@ -0,0 +1,376 @@ +// +// HTMLKitMutationAlgorithmsTests.m +// HTMLKit +// +// Created by Iska on 27/04/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import +#import "HTMLDOM.h" +#import "NSString+HTMLKit.h" + +extern uint64_t dispatch_benchmark(size_t count, void (^block)(void)); + +@interface HTMLKitMutationAlgorithmsTests : XCTestCase + +@end + +@implementation HTMLKitMutationAlgorithmsTests + +- (void)testValidParentNodeWhenAppending +{ + HTMLElement *element = [[HTMLElement alloc] initWithTagName:@"div"]; + + id parent = [HTMLDocument new]; + XCTAssertNoThrow([parent appendNode:element]); + + parent = [HTMLDocumentFragment new]; + XCTAssertNoThrow([parent appendNode:element]); + + parent = [HTMLElement new]; + XCTAssertNoThrow([parent appendNode:element]); + + parent = [HTMLTemplate new]; + XCTAssertNoThrow([parent appendNode:element]); + + parent = [HTMLDocumentType new]; + XCTAssertThrows([parent appendNode:element]); + + parent = [HTMLComment new]; + XCTAssertThrows([parent appendNode:element]); + + parent = [HTMLText new]; + XCTAssertThrows([parent appendNode:element]); +} + +- (void)testValidParentNodeWhenInserting +{ + HTMLElement *element = [[HTMLElement alloc] initWithTagName:@"div"]; + + id parent = [HTMLDocument new]; + XCTAssertNoThrow([parent insertNode:element beforeChildNode:nil]); + + parent = [HTMLDocumentFragment new]; + XCTAssertNoThrow([parent insertNode:element beforeChildNode:nil]); + + parent = [HTMLElement new]; + XCTAssertNoThrow([parent insertNode:element beforeChildNode:nil]); + + parent = [HTMLTemplate new]; + XCTAssertNoThrow([parent insertNode:element beforeChildNode:nil]); + + parent = [HTMLDocumentType new]; + XCTAssertThrows([parent insertNode:element beforeChildNode:nil]); + + parent = [HTMLComment new]; + XCTAssertThrows([parent insertNode:element beforeChildNode:nil]); + + parent = [HTMLText new]; + XCTAssertThrows([parent insertNode:element beforeChildNode:nil]); +} + +- (void)testValidChildNodeWhenInserting +{ + HTMLElement *div = [[HTMLElement alloc] initWithTagName:@"div"]; + HTMLElement *p = [[HTMLElement alloc] initWithTagName:@"p"]; + [div appendNode:p]; + + HTMLElement *table = [[HTMLElement alloc] initWithTagName:@"table"]; + + XCTAssertNoThrow([div insertNode:table beforeChildNode:p]); + + [div removeChildNode:p]; + + HTMLElement *section = [[HTMLElement alloc] initWithTagName:@"section"]; + XCTAssertThrows([div insertNode:section beforeChildNode:p]); +} + +- (void)testValidChildNodeWhenReplacing +{ + HTMLElement *div = [[HTMLElement alloc] initWithTagName:@"div"]; + HTMLElement *p = [[HTMLElement alloc] initWithTagName:@"p"]; + [div appendNode:p]; + + HTMLElement *table = [[HTMLElement alloc] initWithTagName:@"table"]; + + XCTAssertNoThrow([div replaceChildNode:p withNode:table]); + + XCTAssertThrows([div replaceChildNode:p withNode:[HTMLComment new]]); +} + +- (void)testValidChildNodeWhenRemoving +{ + HTMLElement *div = [[HTMLElement alloc] initWithTagName:@"div"]; + HTMLElement *p = [[HTMLElement alloc] initWithTagName:@"p"]; + [div appendNode:p]; + + XCTAssertNoThrow([div removeChildNode:p]); + XCTAssertThrows([div removeChildNode:p]); +} + +- (void)testValidInsertedNode +{ + HTMLDocument *document = [HTMLDocument new]; + + XCTAssertNoThrow([document appendNode:[HTMLDocumentFragment new]]); + XCTAssertNoThrow([document appendNode:[HTMLDocumentType new]]); + XCTAssertNoThrow([document appendNode:[HTMLElement new]]); + + HTMLElement *element = [HTMLElement new]; + + XCTAssertNoThrow([element appendNode:[HTMLTemplate new]]); + XCTAssertNoThrow([element appendNode:[HTMLComment new]]); + XCTAssertNoThrow([element appendNode:[HTMLText new]]); +} + +- (void)testValidParentForDoctype +{ + HTMLDocumentType *doctype = [HTMLDocumentType new]; + + XCTAssertNoThrow([[HTMLDocument new] appendNode:doctype]); + + XCTAssertThrows([[HTMLDocumentFragment new] appendNode:doctype]); + XCTAssertThrows([[HTMLDocumentType new] appendNode:doctype]); + XCTAssertThrows([[HTMLElement new] appendNode:doctype]); + XCTAssertThrows([[HTMLTemplate new] appendNode:doctype]); + XCTAssertThrows([[HTMLComment new] appendNode:doctype]); + XCTAssertThrows([[HTMLText new] appendNode:doctype]); +} + +- (void)testValidParentForText +{ + HTMLText *text = [HTMLText new]; + + XCTAssertThrows([[HTMLDocument new] appendNode:text]); + XCTAssertThrows([[HTMLDocumentType new] appendNode:text]); + XCTAssertThrows([[HTMLComment new] appendNode:text]); + XCTAssertThrows([[HTMLText new] appendNode:text]); + + XCTAssertNoThrow([[HTMLDocumentFragment new] appendNode:text]); + XCTAssertNoThrow([[HTMLElement new] appendNode:text]); + XCTAssertNoThrow([[HTMLTemplate new] appendNode:text]); +} + +- (void)testValidDocumentFragmentInsertionIntoDocument +{ + HTMLDocument *document = [HTMLDocument new]; + HTMLDocumentFragment *fragment = [[HTMLDocumentFragment alloc] initWithDocument:document]; + + void (^ reset)() = ^ { + [fragment removeAllChildNodes]; + [document removeAllChildNodes]; + }; + + /** + * Fragment has a Text node child + */ + [fragment appendNode:[HTMLText new]]; + XCTAssertThrows([document appendNode:fragment]); + + /** + * Fragment has more than one Element child + */ + reset(); + [fragment appendNode:[HTMLElement new]]; + [fragment appendNode:[HTMLElement new]]; + XCTAssertThrows([document appendNode:fragment]); + + + /** + * Fragment has one node child + * Document has an Element child + */ + reset(); + [fragment appendNode:[HTMLElement new]]; + [document appendNode:[HTMLElement new]]; + XCTAssertThrows([document appendNode:fragment]); + + /** + * Fragment has one node child + * "before child" is a Doctype + */ + reset(); + HTMLDocumentType *doctype = [HTMLDocumentType new]; + [fragment appendNode:[HTMLElement new]]; + [document appendNode:doctype]; + XCTAssertThrows([document insertNode:fragment beforeChildNode:doctype]); + + /** + * Fragment has one node child + * "before child" is following a Doctype + */ + reset(); + HTMLComment *doctypePreviousSibling = [HTMLComment new]; + [fragment appendNode:[HTMLElement new]]; + [document appendNode:doctypePreviousSibling]; + [document appendNode:doctype]; + XCTAssertThrows([document insertNode:fragment beforeChildNode:doctypePreviousSibling]); +} + +- (void)testValidElementInsertionIntoDocument +{ + HTMLDocument *document = [HTMLDocument new]; + HTMLElement *element = [HTMLElement new]; + + void (^ reset)() = ^ { + [element removeAllChildNodes]; + [document removeAllChildNodes]; + }; + + /** + * Document has an Element child + */ + [document appendNode:[HTMLElement new]]; + XCTAssertThrows([document appendNode:element]); + + /** + * "before child" is a Doctype + */ + reset(); + HTMLDocumentType *doctype = [HTMLDocumentType new]; + [document appendNode:doctype]; + XCTAssertThrows([document insertNode:element beforeChildNode:doctype]); + + /** + * Doctype is following the "before child" + */ + reset(); + HTMLComment *doctypePreviousSibling = [HTMLComment new]; + [document appendNode:doctypePreviousSibling]; + [document appendNode:doctype]; + XCTAssertThrows([document insertNode:element beforeChildNode:doctypePreviousSibling]); +} + +- (void)testValidDoctypeInsertionIntoDocument +{ + HTMLDocument *document = [HTMLDocument new]; + HTMLDocumentType *doctype = [HTMLDocumentType new]; + + void (^ reset)() = ^ { + [document removeAllChildNodes]; + }; + + /** + * Document has a Doctype child + */ + [document appendNode:[HTMLDocumentType new]]; + XCTAssertThrows([document appendNode:doctype]); + + /** + * An Element is preceding the "before child" + */ + reset(); + HTMLComment *secondChild = [HTMLComment new]; + [document appendNode:[HTMLElement new]]; + [document appendNode:secondChild]; + XCTAssertThrows([document insertNode:doctype beforeChildNode:secondChild]); + + /** + * Document has an Element child + */ + reset(); + [document appendNode:[HTMLElement new]]; + XCTAssertThrows([document appendNode:doctype]); +} + +- (void)testValidDocumentFragmentReplacementIntoDocument +{ + HTMLDocument *document = [HTMLDocument new]; + HTMLComment *child = [HTMLComment new]; + HTMLDocumentFragment *replacement = [[HTMLDocumentFragment alloc] initWithDocument:document]; + + void (^ reset)() = ^ { + [replacement removeAllChildNodes]; + [document removeAllChildNodes]; + [document appendNode:child]; + }; + + /** + * Replacement Fragment has a Text node child + */ + [replacement appendNode:[HTMLText new]]; + XCTAssertThrows([document replaceChildNode:child withNode:replacement]); + + /** + * Replacement Fragment has more than one Element child + */ + reset(); + [replacement appendNode:[HTMLElement new]]; + [replacement appendNode:[HTMLElement new]]; + XCTAssertThrows([document replaceChildNode:child withNode:replacement]); + + /** + * Replacement Fragment has one Element child + * Document has an Element child that is not the Replacement + */ + reset(); + [replacement appendNode:[HTMLElement new]]; + [document appendNode:[HTMLElement new]]; + XCTAssertThrows([document replaceChildNode:child withNode:replacement]); + + /** + * Replacement Fragment has one Element child + * Doctype is following the child node + */ + reset(); + HTMLDocumentType *doctype = [HTMLDocumentType new]; + [replacement appendNode:[HTMLElement new]]; + [document appendNode:doctype]; + XCTAssertThrows([document replaceChildNode:child withNode:replacement]); +} + +- (void)testValidElementReplacementIntoDocument +{ + HTMLDocument *document = [HTMLDocument new]; + HTMLComment *child = [HTMLComment new]; + HTMLElement *replacement = [HTMLElement new]; + + void (^ reset)() = ^ { + [replacement removeAllChildNodes]; + [document removeAllChildNodes]; + [document appendNode:child]; + }; + + /** + * Docment has an Element child that is not replacement + */ + [document appendNode:[HTMLElement new]]; + XCTAssertThrows([document replaceChildNode:child withNode:replacement]); + + /** + * Doctype is following the child node + */ + reset(); + HTMLDocumentType *doctype = [HTMLDocumentType new]; + [document appendNode:doctype]; + XCTAssertThrows([document replaceChildNode:child withNode:replacement]); +} + +- (void)testValidDoctypeReplacementIntoDocument +{ + HTMLDocument *document = [HTMLDocument new]; + HTMLComment *child = [HTMLComment new]; + HTMLDocumentType *replacement = [HTMLDocumentType new]; + + void (^ reset)() = ^ { + [replacement removeAllChildNodes]; + [document removeAllChildNodes]; + [document appendNode:child]; + }; + + /** + * Docment has an Doctype child that is not replacement + */ + [document appendNode:[HTMLDocumentType new]]; + XCTAssertThrows([document replaceChildNode:child withNode:replacement]); + + /** + * An Element is preceding the child node + */ + reset(); + [document insertNode:[HTMLElement new] beforeChildNode:child]; + XCTAssertThrows([document replaceChildNode:child withNode:replacement]); +} + +@end diff --git a/HTMLKitTests/HTMLKitNodeIteratorTests.m b/HTMLKitTests/HTMLKitNodeIteratorTests.m new file mode 100644 index 0000000..7ff6823 --- /dev/null +++ b/HTMLKitTests/HTMLKitNodeIteratorTests.m @@ -0,0 +1,568 @@ +// +// HTMLKitNodeTreeEnumratorTests.m +// HTMLKit +// +// Created by Iska on 28/03/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import +#import "HTMLDOM.h" + +@interface HTMLKitNodeIteratorTests : XCTestCase + +@end + +@implementation HTMLKitNodeIteratorTests + +#pragma mark - Test DOM + +- (HTMLElement *)div +{ + return [[HTMLElement alloc] initWithTagName:@"div"]; +} + +- (HTMLElement *)simpleTree +{ + /* + | div + | a + | b + | c + */ + HTMLElement *div = self.div; + [div appendNode:[[HTMLElement alloc] initWithTagName:@"a"]]; + [div appendNode:[[HTMLElement alloc] initWithTagName:@"b"]]; + [div appendNode:[[HTMLElement alloc] initWithTagName:@"c"]]; + return div; +} + +- (HTMLElement *)nestedSimpleTree +{ + /* + | div + | div + | a + | b + | c + | div + | a + | b + | c + */ + HTMLElement *div = self.div; + [div appendNode:self.simpleTree]; + [div appendNode:self.simpleTree]; + return div; +} + +- (HTMLElement *)complexTree +{ + /* + | div + | div + | div + | a + | b + | c + | e + | f + | div + | a + | b + | c + */ + HTMLElement *root = self.div; + + HTMLElement *div = self.div; + [div appendNode:self.simpleTree]; + [root appendNode:div]; + + HTMLElement *e = [[HTMLElement alloc] initWithTagName:@"e"]; + [e appendNode:[[HTMLElement alloc] initWithTagName:@"f"]]; + [root appendNode:e]; + [root appendNode:self.simpleTree]; + + return root; +} + +- (HTMLDocument *)mixedTree +{ + + /* + | doctype + | #comment + | #comment + | html + | #text "This is a Text" + | div + */ + HTMLDocument *document = [HTMLDocument new]; + document.documentType = [HTMLDocumentType new];; + + HTMLComment *comment = [[HTMLComment alloc] initWithData:@"This is a Comment"]; + [document appendNode:comment]; + + HTMLComment *secondCommnet = [[HTMLComment alloc] initWithData:@"This is a second Comment"]; + [document appendNode:secondCommnet]; + + HTMLElement *root = [[HTMLElement alloc] initWithTagName:@"html"]; + [document appendNode:root]; + + [root appendNode:[[HTMLText alloc] initWithData:@"This is a Text"]]; + + [root appendNode:[[HTMLElement alloc] initWithTagName:@"div"]]; + + return document; +} + +- (HTMLDocument *)document +{ + NSString *htmlString = + @"" + @"" + @"" + @"Title" + @"" + @"" + @"HelloWorld!HTML Kit" + @"

This is an Important paragraph

" + @"" + @""; + + HTMLDocument *document = [HTMLDocument documentWithString:htmlString]; + return document; +} + +#pragma mark - Test Iterator + +- (void)testNodeIteratorInit +{ + HTMLElement *tree = self.simpleTree; + HTMLNodeIterator *iterator = tree.nodeIterator; + + XCTAssertEqualObjects(iterator.root, tree); + XCTAssertEqualObjects(iterator.referenceNode, tree); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); + XCTAssertEqualObjects(iterator.filter, nil); + XCTAssertEqual(iterator.whatToShow, HTMLNodeFilterShowAll); +} + +- (void)testNewIteratorNextNodeShouldBeRoot +{ + HTMLElement *tree = self.simpleTree; + HTMLNodeIterator *iterator = tree.nodeIterator; + + XCTAssertEqualObjects(iterator.nextNode, tree); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + +- (void)testNewIteratorPreviousNodeShouldBeNil +{ + HTMLElement *tree = self.simpleTree; + HTMLNodeIterator *iterator = tree.nodeIterator; + + XCTAssertEqualObjects(iterator.previousNode, nil); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); +} + +- (void)testNewIteratorPreviousNodeShouldBeNextNode +{ + HTMLElement *tree = self.simpleTree; + HTMLNodeIterator *iterator = tree.nodeIterator; + + HTMLNode *node = iterator.nextNode; + + XCTAssertEqualObjects(iterator.previousNode, node); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); +} + +- (void)testNextPreviousIteration +{ + HTMLElement *tree = self.simpleTree; + HTMLNodeIterator *iterator = tree.nodeIterator; + + XCTAssertEqualObjects(iterator.previousNode, nil); + XCTAssertEqualObjects(iterator.nextNode.name, @"div"); + XCTAssertEqualObjects(iterator.nextNode.name, @"a"); + XCTAssertEqualObjects(iterator.previousNode.name, @"a"); + XCTAssertEqualObjects(iterator.previousNode.name, @"div"); + XCTAssertEqualObjects(iterator.nextNode.name, @"div"); + XCTAssertEqualObjects(iterator.nextNode.name, @"a"); + XCTAssertEqualObjects(iterator.nextNode.name, @"b"); + XCTAssertEqualObjects(iterator.nextNode.name, @"c"); + XCTAssertEqualObjects(iterator.previousNode.name, @"c"); + XCTAssertEqualObjects(iterator.previousNode.name, @"b"); + XCTAssertEqualObjects(iterator.previousNode.name, @"a"); + XCTAssertEqualObjects(iterator.previousNode.name, @"div"); +} + +- (void)testSingleNodeIteration +{ + HTMLElement *div = self.div; + NSArray *result = div.nodeIterator.allObjects; + NSArray *expected = @[@"div"]; + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +- (void)testSimpleTreeIteration +{ + HTMLElement *tree = self.simpleTree; + NSArray *result = tree.nodeIterator.allObjects; + NSArray *expected = @[@"div", @"a", @"b", @"c"]; + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +- (void)testNestedSimpleTreeIteration +{ + HTMLElement *tree = self.nestedSimpleTree; + NSArray *result = tree.nodeIterator.allObjects; + NSArray *expected = @[@"div", @"div", @"a", @"b", @"c", @"div", @"a", @"b", @"c"]; + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +- (void)testComplexTreeIteration +{ + HTMLElement *tree = self.complexTree; + NSArray *result = tree.nodeIterator.allObjects; + NSArray *expected = @[@"div", @"div",@"div", @"a", @"b", @"c", @"e", @"f", @"div", @"a", @"b", @"c"]; + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +#pragma mark - Test Iterator ShowOptions (WhatToShow) + +- (void)testShowDocument +{ + HTMLDocument *document = self.mixedTree; + + HTMLNodeIterator *iterator = [document nodeIteratorWithShowOptions:HTMLNodeFilterShowDocument filter:nil]; + NSArray *result = iterator.allObjects; + NSArray *expected = @[@"#document"]; + XCTAssertEqual(result.count, 1); + XCTAssertEqual([result.firstObject class], [HTMLDocument class]); + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +- (void)testShowDocumentType +{ + HTMLDocument *document = self.mixedTree; + + HTMLNodeIterator *iterator = [document nodeIteratorWithShowOptions:HTMLNodeFilterShowDocumentType filter:nil]; + NSArray *result = iterator.allObjects; + NSArray *expected = @[@"html"]; + XCTAssertEqual(result.count, 1); + XCTAssertEqual([result.firstObject class], [HTMLDocumentType class]); + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +- (void)testShowComment +{ + HTMLDocument *document = self.mixedTree; + + HTMLNodeIterator *iterator = [document nodeIteratorWithShowOptions:HTMLNodeFilterShowComment filter:nil]; + NSArray *result = iterator.allObjects; + NSArray *expected = @[@"#comment", @"#comment"]; + XCTAssertEqual(result.count, 2); + XCTAssertEqual([result[0] class], [HTMLComment class]); + XCTAssertEqual([result[1] class], [HTMLComment class]); + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +- (void)testShowText +{ + HTMLDocument *document = self.mixedTree; + + HTMLNodeIterator *iterator = [document nodeIteratorWithShowOptions:HTMLNodeFilterShowText filter:nil]; + NSArray *result = iterator.allObjects; + NSArray *expected = @[@"#text"]; + XCTAssertEqual(result.count, 1); + XCTAssertEqual([result.firstObject class], [HTMLText class]); + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +- (void)testShowElement +{ + HTMLDocument *document = self.mixedTree; + + HTMLNodeIterator *iterator = [document nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]; + NSArray *result = iterator.allObjects; + NSArray *expected = @[@"html", @"div"]; + XCTAssertEqual(result.count, 2); + XCTAssertEqual([result.firstObject class], [HTMLElement class]); + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +- (void)testShowBitmask +{ + HTMLDocument *document = self.mixedTree; + + HTMLNodeIterator *iterator = [document nodeIteratorWithShowOptions:HTMLNodeFilterShowElement | HTMLNodeFilterShowText + filter:nil]; + NSArray *result = iterator.allObjects; + NSArray *expected = @[@"html", @"#text", @"div"]; + XCTAssertEqual(result.count, 3); + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +#pragma mark - Test Iterator Filter + +- (void)testNodeFilter +{ + HTMLDocument *document = self.mixedTree; + + HTMLNodeIterator *iterator = [document nodeIteratorWithShowOptions:HTMLNodeFilterShowAll + filterBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if (node.nodeType == HTMLNodeComment) { + if ([[(HTMLComment *)node data] rangeOfString:@"second"].location != NSNotFound) { + return HTMLNodeFilterAccept; + } + } + return HTMLNodeFilterSkip; + }]; + + NSArray *result = iterator.allObjects; + NSArray *expected = @[@"#comment"]; + XCTAssertEqual(result.count, 1); + XCTAssertEqualObjects([result[0] data], @"This is a second Comment"); + XCTAssertEqualObjects([result valueForKey:@"name"], expected); +} + +#pragma mark - Test Removing Steps + +/* + Test cases for the Removing Steps + https://dom.spec.whatwg.org/#interface-nodeiterator + + Following DOM is used: + +| +| +| +| "Title" +| <body> +| <span> +| "Hello" +| <strong> +| "World!" +| <strong> +| "HTML " +| <!-- This is a Comment! --> +| " Kit" +| <p> +| "This is an " +| <em> +| "Important" +| " paragraph" +*/ + +static void (^ RemoveThenInsertNode)(HTMLNode *) = ^ (HTMLNode *node) { + HTMLNode *parent = node.parentNode; + HTMLNode *nextSibling = node.nextSibling; + [parent removeChildNode:node]; + [parent insertNode:node beforeChildNode:nextSibling]; +}; + +static void (^ IterateUpToNode)(HTMLNodeIterator *, HTMLNode *) = ^ (HTMLNodeIterator *iterator, HTMLNode *target) { + for(HTMLNode *node = iterator.referenceNode; node && (node != target); node = iterator.nextNode); +}; + +static HTMLNode * (^ LastDescendant)(HTMLNode *) = ^ HTMLNode * (HTMLNode *node) { + while (node.lastChild) { + node = node.lastChild; + } + return node; +}; + +- (void)testThatRemovingRootNodeShouldNotAffectIterator +{ + HTMLDocument *document = self.document; + HTMLNode *node = document.body.firstChild; // <span> + + HTMLNodeIterator *iterator = node.nodeIterator; + + [document.body removeChildNode:node]; + + XCTAssertEqualObjects(iterator.root, node); + XCTAssertEqualObjects(iterator.referenceNode, node); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); + XCTAssertEqualObjects(iterator.referenceNode.parentNode, nil); +} + +- (void)testThatRemovingANonInclusiveAnscestorOfReferenceShouldNotAffectIterator +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + [iterator nextNode]; // Reference node: <span> + + RemoveThenInsertNode(iterator.root.childNodes[1]); // Remove <p> + + XCTAssertEqualObjects(iterator.root, body); + XCTAssertEqualObjects(iterator.referenceNode, body.firstChild); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + +- (void)testThatRemovingReferenceNodeShouldUpdateIterator_NilOldPreviousSibling +{ + HTMLDocument *document = self.document; + + HTMLNodeIterator *iterator = document.body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + + HTMLNode *node = iterator.nextNode; // Reference node: <span> + RemoveThenInsertNode(node); // Remove <span> with old previos sibling being nil + + XCTAssertEqualObjects(iterator.referenceNode, iterator.root); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); + + HTMLNode *next = iterator.nextNode; // "Hello" + XCTAssertEqualObjects(next, iterator.root.firstChild); +} + +- (void)testThatRemovingReferenceNodeShouldUpdateIterator_NonNilOldPreviousSibling_NotBeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + + HTMLNode *node = iterator.root.childNodes[1]; // <p> + IterateUpToNode(iterator, node); // Reference node: <p>, pointer-before-reference: NO + RemoveThenInsertNode(node); // Remove <p> with old previos sibling being <span> + + XCTAssertEqualObjects(iterator.referenceNode, LastDescendant(body.firstChild)); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + +- (void)testThatRemovingReferenceNodeShouldUpdateIterator_NonNilOldPreviousSibling_BeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + + HTMLNode *node = iterator.root.childNodes[1]; // <p> + IterateUpToNode(iterator, node); // Reference node: <p>, pointer-before-reference: NO + [iterator previousNode]; // pointer-before-reference: YES + RemoveThenInsertNode(node); // Remove <p> with old previos sibling being <span> + + XCTAssertEqualObjects(iterator.referenceNode, body.firstChild); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); +} + +- (void)testThatRemovingThenReinsertingReferenceNodeAfterNextShouldReturnItAgain +{ + HTMLDocument *document = self.document; + HTMLNodeIterator *iterator = document.body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + + HTMLNode *node = iterator.nextNode; // <span> + RemoveThenInsertNode(node); + + XCTAssertEqualObjects(iterator.referenceNode, iterator.root); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); + + HTMLNode *next = iterator.nextNode; + XCTAssertEqualObjects(next, node); +} + +- (void)testThatRemovingThenReinsertingReferenceNodeAfterPreviousShouldReturnItAgain +{ + HTMLDocument *document = self.document; + HTMLNodeIterator *iterator = document.body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + [iterator nextNode]; // Reference node: <span> + + HTMLNode *node = iterator.previousNode; // Reference node: <span>, pointer-before-reference: YES + HTMLNode *next = node.nextSibling; // <p> + RemoveThenInsertNode(node); + + XCTAssertEqualObjects(iterator.referenceNode, next); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); + + HTMLNode *previous = iterator.previousNode; + XCTAssertEqualObjects(previous, LastDescendant(node)); +} + +- (void)testThatRemovingParentOfReferenceNodeShouldUpdateIterator_NotBeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + HTMLNode *parent = body.childNodes[1]; + + IterateUpToNode(iterator, parent); // Reference node: <p>, pointer-before-reference: NO + [iterator nextNode]; // Reference node: "This is an " + RemoveThenInsertNode(parent); + + XCTAssertEqualObjects(iterator.referenceNode, LastDescendant(body.firstChild)); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + +- (void)testThatRemovingParentOfReferenceNodeShouldUpdateIterator_BeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + HTMLNode *parent = body.childNodes[1]; + + IterateUpToNode(iterator, parent); // Reference node: <p>, pointer-before-reference: NO + [iterator nextNode]; // Reference node: "This is an " + [iterator previousNode]; // pointer-before-reference: YES + RemoveThenInsertNode(parent); + + XCTAssertEqualObjects(iterator.referenceNode, body.firstChild); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); +} + +- (void)testRemoveReferenceNode_NilPreviousSibling_NonNilParentFirstChild +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + HTMLNodeIterator *iterator = body.nodeIterator; + + [iterator nextNode]; // Reference node: <body> + [iterator nextNode]; // Reference node: <span> + + HTMLNode *node = iterator.previousNode; // Reference node: <span>, pointer-before-reference: YES + XCTAssertNil(node.previousSibling); + XCTAssertNotNil(node.nextSibling); + + HTMLNode *nextSibling = node.nextSibling; // <p> + RemoveThenInsertNode(node); + + XCTAssertEqualObjects(iterator.referenceNode, nextSibling); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, YES); + + HTMLNode *next = iterator.nextNode; // <p> + XCTAssertNotEqualObjects(next, node); + XCTAssertEqualObjects(next, nextSibling); +} + +- (void)testRemoveReferenceNode_NodeAfterOldParentIsOutsideRoot_BeforeReference +{ + HTMLDocument *document = self.document; + HTMLNode *body = document.body; + + body.innerHTML = @"<div><p><a></a></p></div><div></div>"; + + HTMLNodeIterator *iterator = body.firstChild.nodeIterator; + + IterateUpToNode(iterator, LastDescendant(body.firstChild)); // Referecne node: <a> + HTMLNode *node = [iterator previousNode]; // pointer-before-reference: YES + RemoveThenInsertNode(node); + + XCTAssertEqualObjects(iterator.referenceNode, iterator.root.firstChild); + XCTAssertEqual(iterator.pointerBeforeReferenceNode, NO); +} + +@end diff --git a/HTMLKitTests/HTMLKitNodeTreeEnumratorTests.m b/HTMLKitTests/HTMLKitNodeTreeEnumratorTests.m deleted file mode 100644 index 654fbcf..0000000 --- a/HTMLKitTests/HTMLKitNodeTreeEnumratorTests.m +++ /dev/null @@ -1,148 +0,0 @@ -// -// HTMLKitNodeTreeEnumratorTests.m -// HTMLKit -// -// Created by Iska on 28/03/15. -// Copyright (c) 2015 BrainCookie. All rights reserved. -// - -#import <XCTest/XCTest.h> - -#import "HTMLNodes.h" - -@interface HTMLKitNodeTreeEnumratorTests : XCTestCase - -@end - -@implementation HTMLKitNodeTreeEnumratorTests - -#pragma mark - Elements - -- (HTMLElement *)div -{ - return [[HTMLElement alloc] initWithTagName:@"div"]; -} - -- (HTMLElement *)simpleTree -{ - /* - | div - | a - | b - | c - */ - HTMLElement *div = self.div; - [div appendNode:[[HTMLElement alloc] initWithTagName:@"a"]]; - [div appendNode:[[HTMLElement alloc] initWithTagName:@"b"]]; - [div appendNode:[[HTMLElement alloc] initWithTagName:@"c"]]; - return div; -} - -- (HTMLElement *)nestedSimpleTree -{ - /* - | div - | div - | a - | b - | c - | div - | a - | b - | c - */ - HTMLElement *div = self.div; - [div appendNode:self.simpleTree]; - [div appendNode:self.simpleTree]; - return div; -} - -- (HTMLElement *)complexTree -{ - /* - | div - | div - | div - | a - | b - | c - | e - | f - | div - | a - | b - | c - */ - HTMLElement *root = self.div; - - HTMLElement *div = self.div; - [div appendNode:self.simpleTree]; - [root appendNode:div]; - - HTMLElement *e = [[HTMLElement alloc] initWithTagName:@"e"]; - [e appendNode:[[HTMLElement alloc] initWithTagName:@"f"]]; - [root appendNode:e]; - [root appendNode:self.simpleTree]; - - return root; -} - -#pragma mark - Tests - -- (void)testSingle -{ - HTMLElement *div = self.div; - NSArray *result = div.treeEnumerator.allObjects; - NSArray *expected = @[@"div"]; - XCTAssertEqualObjects([result valueForKey:@"name"], expected); -} - -- (void)testSimpleTree -{ - HTMLElement *tree = self.simpleTree; - NSArray *result = tree.treeEnumerator.allObjects; - NSArray *expected = @[@"div", @"a", @"b", @"c"]; - XCTAssertEqualObjects([result valueForKey:@"name"], expected); -} - -- (void)testSimpleTreeReversed -{ - HTMLElement *tree = self.simpleTree; - NSArray *result = tree.reverseTreeEnumerator.allObjects; - NSArray *expected = @[@"div", @"c", @"b", @"a"]; - XCTAssertEqualObjects([result valueForKey:@"name"], expected); -} - -- (void)testNestedSimpleTree -{ - HTMLElement *tree = self.nestedSimpleTree; - NSArray *result = tree.treeEnumerator.allObjects; - NSArray *expected = @[@"div", @"div", @"a", @"b", @"c", @"div", @"a", @"b", @"c"]; - XCTAssertEqualObjects([result valueForKey:@"name"], expected); -} - -- (void)testNestedSimpleTreeReversed -{ - HTMLElement *tree = self.nestedSimpleTree; - NSArray *result = tree.reverseTreeEnumerator.allObjects; - NSArray *expected = @[@"div", @"div", @"c", @"b", @"a", @"div", @"c", @"b", @"a"]; - XCTAssertEqualObjects([result valueForKey:@"name"], expected); -} - -- (void)testComplexSimpleTree -{ - HTMLElement *tree = self.complexTree; - NSArray *result = tree.treeEnumerator.allObjects; - NSArray *expected = @[@"div", @"div",@"div", @"a", @"b", @"c", @"e", @"f", @"div", @"a", @"b", @"c"]; - XCTAssertEqualObjects([result valueForKey:@"name"], expected); -} - -- (void)testComplexSimpleTreeReversed -{ - HTMLElement *tree = self.complexTree; - NSArray *result = tree.reverseTreeEnumerator.allObjects; - NSArray *expected = @[@"div", @"div", @"c", @"b", @"a", @"e", @"f", @"div", @"div", @"c", @"b", @"a"]; - XCTAssertEqualObjects([result valueForKey:@"name"], expected); -} - -@end diff --git a/HTMLKitTests/HTMLKitNodesTests.m b/HTMLKitTests/HTMLKitNodesTests.m new file mode 100644 index 0000000..87f37f9 --- /dev/null +++ b/HTMLKitTests/HTMLKitNodesTests.m @@ -0,0 +1,485 @@ +// +// HTMLKitNodesTests.m +// HTMLKit +// +// Created by Iska on 20/04/15. +// Copyright (c) 2015 BrainCookie. All rights reserved. +// + +#import <XCTest/XCTest.h> +#import "HTMLDOM.h" + +@interface HTMLKitNodesTests : XCTestCase + +@end + +@implementation HTMLKitNodesTests + +- (void)setUp +{ + [super setUp]; +} + +- (void)tearDown +{ + [super tearDown]; +} + +- (void)testInitNode +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + XCTAssertNotNil(node); + XCTAssertEqualObjects(node.name, @"name"); + XCTAssertEqual(node.nodeType, HTMLNodeElement); + XCTAssertNotNil(node.childNodes); + XCTAssertEqual(node.childNodes.count, 0); + + XCTAssertNil(node.ownerDocument); + XCTAssertNil(node.parentNode); + XCTAssertNil(node.parentElement); + XCTAssertNil(node.firstChild); + XCTAssertNil(node.lastChild); + XCTAssertNil(node.previousSibling); + XCTAssertNil(node.lastChild); +} + +- (void)testAppendNode +{ + HTMLElement *element = [[HTMLElement alloc] initWithTagName:@"div"]; + HTMLComment *comment = [HTMLComment new]; + [element appendNode:comment]; + + XCTAssertEqual(element.childNodesCount, 1); + XCTAssertEqual(element.firstChild, comment); + + HTMLElement *firstElement = [HTMLElement new]; + HTMLElement *secondElement = [HTMLElement new]; + NSArray *nodes = @[firstElement, secondElement]; + + [element appendNodes:nodes]; + + XCTAssertEqual(element.childNodesCount, 3); + XCTAssertEqual(element.firstChild, comment); + XCTAssertEqual(element.lastChild, secondElement); +} + +- (void)testPrependNode +{ + HTMLElement *element = [[HTMLElement alloc] initWithTagName:@"div"]; + HTMLText *text = [HTMLText new]; + [element appendNode:text]; + + HTMLComment *comment = [HTMLComment new]; + [element prependNode:comment]; + + XCTAssertEqual(element.childNodesCount, 2); + XCTAssertEqual(element.firstChild, comment); + + HTMLElement *firstElement = [HTMLElement new]; + HTMLElement *secondElement = [HTMLElement new]; + NSArray *nodes = @[firstElement, secondElement]; + + [element prependNodes:nodes]; + + XCTAssertEqual(element.childNodesCount, 4); + XCTAssertEqual(element.firstChild, firstElement); + XCTAssertEqual(element.lastChild, text); +} + +- (void)testAppendDocumentFragment +{ + HTMLElement *element = [[HTMLElement alloc] initWithTagName:@"div"]; + HTMLComment *comment = [HTMLComment new]; + [element appendNode:comment]; + + HTMLDocumentFragment *fragment = [HTMLDocumentFragment new]; + HTMLElement *firstChild = [HTMLElement new]; + HTMLElement *secondChild = [HTMLElement new]; + [fragment appendNode:firstChild]; + [fragment appendNode:secondChild]; + + [element appendNode:fragment]; + XCTAssertEqual(element.childNodesCount, 3); + XCTAssertEqual(fragment.childNodesCount, 0); + + XCTAssertEqualObjects(firstChild.parentNode, element); + XCTAssertEqualObjects(secondChild.parentNode, element); + XCTAssertEqualObjects(element.firstChild, comment); + XCTAssertEqualObjects(element.firstChild.nextSibling, firstChild); + XCTAssertEqualObjects(element.lastChild.previousSibling, firstChild); + XCTAssertEqualObjects(element.lastChild, secondChild); +} + +- (void)testParentNode +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + XCTAssertNil(firstChild.parentNode); + + [node appendNode:firstChild]; + XCTAssertTrue(node.hasChildNodes); + XCTAssertEqual(node.childNodesCount, 1); + + XCTAssertNotNil(firstChild.parentNode); + XCTAssertEqualObjects(firstChild.parentNode, node); + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + + [node appendNodes:@[secondChild, thirdChild]]; + XCTAssertTrue(node.hasChildNodes); + XCTAssertEqual(node.childNodesCount, 3); + + XCTAssertNotNil(secondChild.parentNode); + XCTAssertEqualObjects(secondChild.parentNode, node); + + XCTAssertNotNil(thirdChild.parentNode); + XCTAssertEqualObjects(thirdChild.parentNode, node); +} + +- (void)testChildNodesCount +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + XCTAssertFalse(node.hasChildNodes); + XCTAssertEqual(node.childNodesCount, 0); + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + XCTAssertTrue(node.hasChildNodes); + XCTAssertEqual(node.childNodesCount, 1); + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + XCTAssertTrue(node.hasChildNodes); + XCTAssertEqual(node.childNodesCount, 2); + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + XCTAssertTrue(node.hasChildNodes); + XCTAssertEqual(node.childNodesCount, 3); +} + +- (void)testFirstAndLastChildNodes +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + XCTAssertEqualObjects(node.firstChild, firstChild); + XCTAssertEqualObjects(node.lastChild, firstChild); + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + XCTAssertEqualObjects(node.firstChild, firstChild); + XCTAssertEqualObjects(node.lastChild, secondChild); + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + XCTAssertEqualObjects(node.firstChild, firstChild); + XCTAssertEqualObjects(node.lastChild, thirdChild); +} + +- (void)testNextAndPreviousSiblingNodes +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + XCTAssertNil(firstChild.previousSibling); + XCTAssertEqualObjects(firstChild.nextSibling, secondChild); + + XCTAssertEqualObjects(secondChild.previousSibling, firstChild); + XCTAssertEqualObjects(secondChild.nextSibling, thirdChild); + + XCTAssertEqualObjects(thirdChild.previousSibling, secondChild); + XCTAssertNil(thirdChild.nextSibling); +} + +- (void)testHasChildNodeOfType +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + XCTAssertFalse([node hasChildNodeOfType:HTMLNodeElement]); + XCTAssertFalse([node hasChildNodeOfType:HTMLNodeText]); + XCTAssertFalse([node hasChildNodeOfType:HTMLNodeComment]); + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + XCTAssertTrue([node hasChildNodeOfType:HTMLNodeElement]); + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeText]; + [node appendNode:secondChild]; + + XCTAssertTrue([node hasChildNodeOfType:HTMLNodeText]); + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeComment]; + [node appendNode:thirdChild]; + + XCTAssertTrue([node hasChildNodeOfType:HTMLNodeComment]); +} + +- (void)testChildNodeAtIndex +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + XCTAssertEqualObjects([node childNodeAtIndex:0], firstChild); + XCTAssertEqualObjects([node childNodeAtIndex:1], secondChild); + XCTAssertEqualObjects([node childNodeAtIndex:2], thirdChild); +} + +- (void)testIndexOfChildNode +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + XCTAssertEqual([node indexOfChildNode:firstChild], 0); + XCTAssertEqual([node indexOfChildNode:secondChild], 1); + XCTAssertEqual([node indexOfChildNode:thirdChild], 2); +} + +- (void)testIndertNodeBeforeChild +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node insertNode:secondChild beforeChildNode:firstChild]; + + XCTAssertEqual([node indexOfChildNode:firstChild], 1); + XCTAssertEqual([node indexOfChildNode:secondChild], 0); + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node insertNode:thirdChild beforeChildNode:firstChild]; + + XCTAssertEqual([node indexOfChildNode:firstChild], 2); + XCTAssertEqual([node indexOfChildNode:secondChild], 0); + XCTAssertEqual([node indexOfChildNode:thirdChild], 1); +} + +- (void)testReplaceChildNode +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node replaceChildNode:firstChild withNode:thirdChild]; + + XCTAssertNil(firstChild.parentNode); + + XCTAssertEqual([node indexOfChildNode:firstChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:secondChild], 1); + XCTAssertEqual([node indexOfChildNode:thirdChild], 0); +} + +- (void)testReplaceAllChildNodes +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node replaceAllChildNodesWithNode:thirdChild]; + + XCTAssertNil(firstChild.parentNode); + XCTAssertNil(secondChild.parentNode); + XCTAssertEqualObjects(thirdChild.parentNode, node); + + XCTAssertEqual(node.childNodesCount, 1); + XCTAssertEqual([node indexOfChildNode:thirdChild], 0); +} + +- (void)testRemoveFromParentNode +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + [firstChild removeFromParentNode]; + + XCTAssertNil(firstChild.parentNode); + XCTAssertEqual(node.childNodesCount, 0); + XCTAssertEqual([node indexOfChildNode:firstChild], NSNotFound); +} + +- (void)testRemoveChildNode +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + [node removeChildNode:secondChild]; + XCTAssertEqual(node.childNodesCount, 2); + + XCTAssertEqualObjects(firstChild.parentNode, node); + XCTAssertNil(secondChild.parentNode); + XCTAssertEqualObjects(thirdChild.parentNode, node); + + XCTAssertEqual([node indexOfChildNode:firstChild], 0); + XCTAssertEqual([node indexOfChildNode:secondChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:thirdChild], 1); + + [node removeChildNode:firstChild]; + XCTAssertEqual(node.childNodesCount, 1); + + XCTAssertNil(firstChild.parentNode); + XCTAssertNil(secondChild.parentNode); + XCTAssertEqualObjects(thirdChild.parentNode, node); + + XCTAssertEqual([node indexOfChildNode:firstChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:secondChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:thirdChild], 0); +} + +- (void)testRemoveChildNodeAtIndex +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + [node removeChildNodeAtIndex:1]; + XCTAssertEqual(node.childNodesCount, 2); + + XCTAssertEqualObjects(firstChild.parentNode, node); + XCTAssertNil(secondChild.parentNode); + XCTAssertEqualObjects(thirdChild.parentNode, node); + + XCTAssertEqual([node indexOfChildNode:firstChild], 0); + XCTAssertEqual([node indexOfChildNode:secondChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:thirdChild], 1); + + [node removeChildNodeAtIndex:0]; + XCTAssertEqual(node.childNodesCount, 1); + + XCTAssertNil(firstChild.parentNode); + XCTAssertNil(secondChild.parentNode); + XCTAssertEqualObjects(thirdChild.parentNode, node); + + XCTAssertEqual([node indexOfChildNode:firstChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:secondChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:thirdChild], 0); +} + +- (void)testRemoveAllChildNodes +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + [node removeAllChildNodes]; + XCTAssertEqual(node.childNodesCount, 0); + + XCTAssertNil(firstChild.parentNode); + XCTAssertNil(secondChild.parentNode); + XCTAssertNil(thirdChild.parentNode); + + XCTAssertEqual([node indexOfChildNode:firstChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:secondChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:thirdChild], NSNotFound); +} + +- (void)testReparentChildNodes +{ + HTMLNode *node = [[HTMLNode alloc] initWithName:@"name" type:HTMLNodeElement]; + + HTMLNode *firstChild = [[HTMLNode alloc] initWithName:@"first" type:HTMLNodeElement]; + [node appendNode:firstChild]; + + HTMLNode *secondChild = [[HTMLNode alloc] initWithName:@"second" type:HTMLNodeElement]; + [node appendNode:secondChild]; + + HTMLNode *thirdChild = [[HTMLNode alloc] initWithName:@"third" type:HTMLNodeElement]; + [node appendNode:thirdChild]; + + HTMLNode *newParent = [[HTMLNode alloc] initWithName:@"new-parent" type:HTMLNodeElement]; + + [node reparentChildNodesIntoNode:newParent]; + XCTAssertEqual(node.childNodesCount, 0); + + XCTAssertEqual([node indexOfChildNode:firstChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:secondChild], NSNotFound); + XCTAssertEqual([node indexOfChildNode:thirdChild], NSNotFound); + + XCTAssertEqualObjects(firstChild.parentNode, newParent); + XCTAssertEqualObjects(secondChild.parentNode, newParent); + XCTAssertEqualObjects(thirdChild.parentNode, newParent); +} + +- (void)testElementSetInnerHTML +{ + HTMLElement *element = [[HTMLElement alloc] initWithTagName:@"div"]; + [element appendNode:[[HTMLElement alloc] initWithTagName:@"img"]]; + + [element setInnerHTML:@"<p></p><p></p>"]; + + XCTAssertEqual(element.childNodesCount, 2); + + XCTAssertEqualObjects(element.firstChild.asElement.tagName, @"p"); + XCTAssertEqualObjects([element childNodeAtIndex:1].asElement.tagName, @"p"); +} + +@end diff --git a/HTMLKitTests/HTMLKitParserPerformance.m b/HTMLKitTests/HTMLKitParserPerformance.m index f4839d0..005282f 100644 --- a/HTMLKitTests/HTMLKitParserPerformance.m +++ b/HTMLKitTests/HTMLKitParserPerformance.m @@ -15,6 +15,8 @@ @implementation HTMLKitParserPerformance +#define HTMLKIT_NO_DOM_CHECKS + - (void)testParserPerformance { NSString *path = [[NSBundle bundleForClass:self.class] resourcePath]; @@ -28,4 +30,6 @@ }]; } +#undef HTMLKIT_NO_DOM_CHECKS + @end diff --git a/HTMLKitTests/HTMLKitTreeConstructionTests.m b/HTMLKitTests/HTMLKitTreeConstructionTests.m index cfb7430..ca1f223 100644 --- a/HTMLKitTests/HTMLKitTreeConstructionTests.m +++ b/HTMLKitTests/HTMLKitTreeConstructionTests.m @@ -9,7 +9,7 @@ #import <XCTest/XCTest.h> #import "HTML5LibTreeConstructionTest.h" -#import "HTMLNodes.h" +#import "HTMLDOM.h" #import "HTMLParser.h" @@ -121,14 +121,14 @@ { XCTAssertEqualObjects(actual.name, expected.name, @"Node name mismatch [%@ should be %@]:\n%@", actual.name, expected.name, message); - XCTAssert(actual.type == expected.type, @"Node type mismatch [%hd should be %hd]:\n%@", - actual.type, expected.type, message); + XCTAssert(actual.nodeType == expected.nodeType, @"Node type mismatch [%hd should be %hd]:\n%@", + actual.nodeType, expected.nodeType, message); - if (actual.type != expected.type) { + if (actual.nodeType != expected.nodeType) { return; } - switch (actual.type) { + switch (actual.nodeType) { case HTMLNodeDocumentType: XCTAssertEqualObjects([(HTMLDocumentType *)actual publicIdentifier], [(HTMLDocumentType *)expected publicIdentifier], @"%@", message); XCTAssertEqualObjects([(HTMLDocumentType *)actual systemIdentifier], [(HTMLDocumentType *)expected systemIdentifier], @"%@", message); diff --git a/HTMLKitTests/HTMLTreeWalkerTests.m b/HTMLKitTests/HTMLTreeWalkerTests.m new file mode 100644 index 0000000..6aeeaf2 --- /dev/null +++ b/HTMLKitTests/HTMLTreeWalkerTests.m @@ -0,0 +1,503 @@ + // + // HTMLTreeWalkerTests.m + // HTMLKit + // + // Created by Iska on 05/06/15. + // Copyright (c) 2015 BrainCookie. All rights reserved. + // + +#import <XCTest/XCTest.h> +#import "HTMLTreeWalker.h" +#import "HTMLDOM.h" + +@interface HTMLTreeWalkerTests : XCTestCase + +@end + +@implementation HTMLTreeWalkerTests + +#pragma mark - Asserts + +#define AssertElementWithId(input, id) \ + do { \ + HTMLNode *node = input;\ + XCTAssertEqual(node.nodeType, HTMLNodeElement);\ + XCTAssertEqualObjects(node.asElement[@"id"], id);\ + } while(0) + +#define AssertTextWithValue(input, value) \ + do { \ + HTMLNode *node = input;\ + XCTAssertEqual(node.nodeType, HTMLNodeText);\ + XCTAssertEqualObjects(node.textContent, value);\ + } while(0) + +#define AssertCommentWithValue(input, value) \ + do { \ + HTMLNode *node = input;\ + XCTAssertEqual(node.nodeType, HTMLNodeComment);\ + XCTAssertEqualObjects(node.textContent, value);\ + } while(0) + +#pragma mark - Tests + +- (void)testTreeWalkerInit +{ + HTMLNode *root = self.basicWalkingDOM; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root]; + + XCTAssertNotNil(walker); + XCTAssertNotNil(walker.root); + XCTAssertNotNil(walker.currentNode); + XCTAssertNil(walker.filter); + XCTAssertEqual(walker.whatToShow, HTMLNodeFilterShowAll); + + XCTAssertEqualObjects(walker.root, root); + XCTAssertEqualObjects(walker.root, walker.currentNode); +} + +#pragma mark - Basic Walking + +- (HTMLNode *)basicWalkingDOM +{ + // Tree structure: + // #a + // | + // +----+----+ + // | | + // "b" #c + // | + // +----+----+ + // | | + // #d <!--j--> + // | + // +----+----+ + // | | | + // "e" #f "i" + // | + // +--+--+ + // | | + // "g" <!--h--> + + HTMLElement *div = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"a"}]; + + [div appendNode:[[HTMLText alloc] initWithData:@"b"]]; + + HTMLElement *c = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"c"}]; + [div appendNode:c]; + + HTMLElement *d = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"d"}]; + [c appendNode:d]; + [c appendNode:[[HTMLComment alloc] initWithData:@"j"]]; + + [d appendNode:[[HTMLText alloc] initWithData:@"e"]]; + + HTMLElement *f = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"f"}]; + [d appendNode:f]; + [d appendNode:[[HTMLText alloc] initWithData:@"i"]]; + + [f appendNode:[[HTMLText alloc] initWithData:@"g"]]; + [f appendNode:[[HTMLComment alloc] initWithData:@"h"]]; + + return div; +} + +- (void)testBasicWalking +{ + HTMLNode *root = self.basicWalkingDOM; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root]; + + HTMLNode *f = root.lastChild.firstChild.childNodes[1]; + + AssertElementWithId(walker.currentNode, @"a"); + XCTAssertNil(walker.parentNode); + AssertElementWithId(walker.currentNode, @"a"); + + AssertTextWithValue(walker.firstChild, @"b"); + AssertTextWithValue(walker.currentNode, @"b"); + + AssertElementWithId(walker.nextSibling, @"c"); + AssertElementWithId(walker.currentNode, @"c"); + + AssertCommentWithValue(walker.lastChild, @"j"); + AssertCommentWithValue(walker.currentNode, @"j"); + + AssertElementWithId(walker.previousSibling, @"d"); + AssertElementWithId(walker.currentNode, @"d"); + + AssertTextWithValue(walker.nextNode, @"e"); + AssertTextWithValue(walker.currentNode, @"e"); + + AssertElementWithId(walker.parentNode, @"d"); + AssertElementWithId(walker.currentNode, @"d"); + + AssertElementWithId(walker.previousNode, @"c"); + AssertElementWithId(walker.currentNode, @"c"); + + XCTAssertNil(walker.nextSibling); + AssertElementWithId(walker.currentNode, @"c"); + + walker.currentNode = f; + XCTAssertEqualObjects(walker.currentNode, f); +} + +#pragma mark - Current Node + +- (HTMLDocument *)currentNodeDOM +{ + HTMLDocument *document = [HTMLDocument documentWithString: + @"<div id='first'><p><a></a></p></div>" + @"<div id='second'><p><b></b></p</div>"]; + + return document; +} + +- (void)testThatTreeWalkerParentHasNoEffectCurrentNodeWhenParentIsNotUnderRoot +{ + HTMLDocument *document = self.currentNodeDOM; + HTMLNode *first = document.body.firstChild; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:first + showOptions:HTMLNodeFilterShowElement + filter:nil]; + + AssertElementWithId(walker.currentNode, @"first"); + XCTAssertNil(walker.parentNode); + AssertElementWithId(walker.currentNode, @"first"); +} + +- (void)testThatSettingCurrentNodeToNodesNotUnderRootIsHandledCorrectly +{ + HTMLDocument *document = self.currentNodeDOM; + HTMLNode *first = document.body.firstChild; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:first + showOptions:HTMLNodeFilterShowElement|HTMLNodeFilterShowComment + filter:nil]; + walker.currentNode = document.documentElement; + XCTAssertNil(walker.parentNode); + XCTAssertEqualObjects(walker.currentNode, document.documentElement); + + walker.currentNode = document.documentElement; + XCTAssertEqualObjects(walker.nextNode, document.documentElement.firstChild); + XCTAssertEqualObjects(walker.currentNode, document.documentElement.firstChild); + + walker.currentNode = document.documentElement; + XCTAssertNil(walker.previousNode); + XCTAssertEqualObjects(walker.currentNode, document.documentElement); + + walker.currentNode = document.documentElement; + XCTAssertEqualObjects(walker.firstChild, document.documentElement.firstChild); + XCTAssertEqualObjects(walker.currentNode, document.documentElement.firstChild); + + walker.currentNode = document.documentElement; + XCTAssertEqualObjects(walker.lastChild, document.documentElement.lastChild); + XCTAssertEqualObjects(walker.currentNode, document.documentElement.lastChild); + + walker.currentNode = document.documentElement; + XCTAssertNil(walker.nextSibling); + XCTAssertEqualObjects(walker.currentNode, document.documentElement); + + walker.currentNode = document.documentElement; + XCTAssertNil(walker.previousSibling); + XCTAssertEqualObjects(walker.currentNode, document.documentElement); +} + +#pragma mark - Filter + +- (HTMLElement *)filterBasicDOM +{ + HTMLElement *root = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"root"}]; + + HTMLElement *a1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"A1"}]; + HTMLElement *b1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B1"}]; + HTMLElement *b2 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B2"}]; + + [root appendNode:a1]; + [a1 appendNode:b1]; + [a1 appendNode:b2]; + + return root; +} + +- (void)testTreeWalkerNilFilter +{ + HTMLElement *root = self.filterBasicDOM; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:nil]; + + AssertElementWithId(walker.currentNode, @"root"); + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.currentNode, @"A1"); + AssertElementWithId(walker.nextNode, @"B1"); + AssertElementWithId(walker.currentNode, @"B1"); +} + +- (void)testTreeWalkerWithFilter +{ + HTMLElement *root = self.filterBasicDOM; + + id<HTMLNodeFilter> filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B1"]) { + return HTMLNodeFilterSkip; + } + return HTMLNodeFilterAccept; + }]; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.currentNode, @"root"); + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.currentNode, @"A1"); + AssertElementWithId(walker.nextNode, @"B2"); + AssertElementWithId(walker.currentNode, @"B2"); +} + +#pragma mark - Filter Skip + +- (HTMLElement *)filterDOM +{ + HTMLElement *root = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"root"}]; + + HTMLElement *a1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"A1"}]; + HTMLElement *b1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B1"}]; + HTMLElement *b2 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B2"}]; + HTMLElement *b3 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B3"}]; + HTMLElement *c1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"C1"}]; + + [root appendNode:a1]; + [a1 appendNode:b1]; + [a1 appendNode:b2]; + [a1 appendNode:b3]; + [b1 appendNode:c1]; + + return root; +} + +- (id<HTMLNodeFilter>)skipB1Filter +{ + id<HTMLNodeFilter> filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B1"]) { + return HTMLNodeFilterSkip; + } + return HTMLNodeFilterAccept; + }]; + return filter; +} + +- (id<HTMLNodeFilter>)skipB2Filter +{ + id<HTMLNodeFilter> filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B2"]) { + return HTMLNodeFilterSkip; + } + return HTMLNodeFilterAccept; + }]; + return filter; +} + +- (id<HTMLNodeFilter>)rejectB1Filter +{ + id<HTMLNodeFilter> filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B1"]) { + return HTMLNodeFilterReject; + } + return HTMLNodeFilterAccept; + }]; + return filter; +} + +- (id<HTMLNodeFilter>)rejectB2Filter +{ + id<HTMLNodeFilter> filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B2"]) { + return HTMLNodeFilterReject; + } + return HTMLNodeFilterAccept; + }]; + return filter; +} + +static HTMLElement * (^ FindElementById)(HTMLNode *, NSString *) = ^ HTMLElement * (HTMLNode *root, NSString *id) { + for (HTMLNode *node in [root nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) { + if ([node.asElement[@"id"] isEqualToString:id]) { + return node.asElement; + } + } + return nil; +}; + +- (void)testThatFilterSkipsNextNode +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.skipB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.nextNode, @"A1"); + AssertElementWithId(walker.nextNode, @"C1"); + AssertElementWithId(walker.nextNode, @"B2"); + AssertElementWithId(walker.nextNode, @"B3"); +} + +- (void)testThatFilterSkipsFirstChild +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.skipB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.firstChild, @"C1"); +} + +- (void)testThatFilterSkipsNextSibling +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.skipB2Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.firstChild, @"B1"); + AssertElementWithId(walker.nextSibling, @"B3"); +} + +- (void)testThatFilterSkipsParentNode +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.skipB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"C1"); + AssertElementWithId(walker.parentNode, @"A1"); +} + +- (void)testThatFilterSkipsPreviousSibling +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.skipB2Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"B3"); + AssertElementWithId(walker.previousSibling, @"B1"); +} + +- (void)testThatFilterSkipsPreviousNode +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.skipB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"B3"); + AssertElementWithId(walker.previousNode, @"B2"); + AssertElementWithId(walker.previousNode, @"C1"); + AssertElementWithId(walker.previousNode, @"A1"); +} + +#pragma mark - Filter Reject + +- (void)testThatFilterRejectsNextNode +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.rejectB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.nextNode, @"A1"); + AssertElementWithId(walker.nextNode, @"B2"); + AssertElementWithId(walker.nextNode, @"B3"); +} + +- (void)testThatFilterRejectsFirstChild +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.rejectB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.firstChild, @"B2"); +} + +- (void)testThatFilterRejectsNextSibling +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.rejectB2Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.firstChild, @"B1"); + AssertElementWithId(walker.nextSibling, @"B3"); +} + +- (void)testThatFilterRejectsParentNode +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.rejectB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"C1"); + AssertElementWithId(walker.parentNode, @"A1"); +} + +- (void)testThatFilterRejectsPreviousSibling +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.rejectB2Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"B3"); + AssertElementWithId(walker.previousSibling, @"B1"); +} + +- (void)testThatFilterRejectsPreviousNode +{ + HTMLElement *root = self.filterDOM; + id<HTMLNodeFilter> filter = self.rejectB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"B3"); + AssertElementWithId(walker.previousNode, @"B2"); + AssertElementWithId(walker.previousNode, @"A1"); +} + + +@end