170 Commits

Author SHA1 Message Date
iska d980203741 Merge branch 'release/0.9.0' 2015-12-23 21:49:36 +01:00
iska ba02239207 Remove build status from README for now 2015-12-23 21:49:10 +01:00
iska 830fa06a55 Remove travis.yml for now since it is being queued on a linux vm 2015-12-23 21:47:57 +01:00
iska 30c528a220 Add build status to README 2015-12-23 21:44:05 +01:00
iska 695ff67dd8 Use xcodebuild instead of xctool in travis.yml 2015-12-23 21:35:18 +01:00
iska f883b0d906 Update README.md 2015-12-23 21:32:18 +01:00
iska be45558e86 Add travis-ci yaml file 2015-12-23 21:24:35 +01:00
iska 46629ada01 Update html5lib-tests to latest commit before the Blink changes on 16.09.2015 2015-12-23 21:11:06 +01:00
iska 700587b101 Rename the negation selector to "not" 2015-12-23 21:10:40 +01:00
iska 6c28dad930 Finalize parsing for <ruby> elements in the HTML Parser
https://github.com/whatwg/html/pull/101
2015-12-23 20:44:16 +01:00
iska 572918e59b Add HTMLKit Demo playground 2015-12-23 17:17:00 +01:00
iska 85edf33950 Reset the document's ready state on document initialization in the HTML Parser 2015-12-23 16:20:42 +01:00
iska e80188dd00 Specify the generic type of the Node Iterator 2015-12-23 03:02:30 +01:00
iska b64cfc9c1e Remove historic HTML Node types 2015-12-23 03:02:18 +01:00
iska 409b5502ae Add "Intro" page for HTMLKit in the playground 2015-12-22 17:57:21 +01:00
iska 7377ec550f Improve HTML Parser errors 2015-12-22 17:56:25 +01:00
iska 3d9e657be4 Remove performance tests from the HTMLKit OSX scheme 2015-12-22 02:52:46 +01:00
iska 766c901e38 Fix format specifier in the index-based CSS selector names 2015-12-22 02:52:30 +01:00
iska 974e5a1615 Refactor emit-error method in the CSS parser with implicit location reporting 2015-12-22 02:43:04 +01:00
iska 518fd5eacb Add tests for parsing extension selectors 2015-12-22 02:37:28 +01:00
iska f57e265cba Change "lt", "gt" & "eq"-selectors to accept an unsigned integer index 2015-12-22 02:35:00 +01:00
iska 4bb681eb78 Add missing CSS structural selectors to the parser 2015-12-22 02:32:03 +01:00
iska c4a5aa1bd9 Fix assignment in the initializer of attribute selector 2015-12-22 02:31:45 +01:00
iska be34a57e6f Add CSS parsing for "lt", "gt" & "eq"-selectors 2015-12-22 02:19:22 +01:00
iska 01694a5fdf Add CSS parsing for extension selectors 2015-12-22 02:18:48 +01:00
iska b55ca26c7e Add HTML stream method to consume a decimal number 2015-12-22 02:13:50 +01:00
iska 846b6114fc Add source code documentation for the CSS Selector classes 2015-12-21 22:21:55 +01:00
iska 1208ddf5c7 Reorder source code a little bit 2015-12-21 17:19:07 +01:00
iska c2bbb7d9f6 Add source code documentation for the private CSS Selectors related classes 2015-12-21 17:18:45 +01:00
iska 244dd726b4 Add source code documentation for the public CSS Selectors 2015-12-21 17:18:21 +01:00
iska 032c428725 Add source code documentation for the CSS Selectors parsers 2015-12-21 17:17:51 +01:00
iska 8d672cf0cf Fix "lt", "gt" & "eq"-selectors declarations
Added missing index argument
2015-12-21 17:01:38 +01:00
iska 5354c7c934 Fix node filter initialization in HTML Node class 2015-12-21 16:52:11 +01:00
iska b99cb0c9d4 Change ":has" selector's behaviour to include all descendants instead of direct child elements. 2015-12-21 16:25:26 +01:00
iska 3c89df333c Add source code documentation for the ordered dictionary class 2015-12-21 15:42:32 +01:00
iska ad34deb7ac Add source code documentation for HTMLKit categories 2015-12-21 00:18:04 +01:00
iska 7ee8057d38 Add source code documentation for node filter, tree walker, namespaces and quirks modes 2015-12-21 00:07:50 +01:00
iska f2aff5f2ce Remove unused namespaces 2015-12-21 00:06:26 +01:00
iska 7a1bb21b33 Add source code documentation for all HTML Node subclasses 2015-12-20 23:35:34 +01:00
iska fd965e014d Add source documentation for the HTML Node 2015-12-20 19:11:52 +01:00
iska 9670e11fa5 Refactor private HTML Node methods into separate extension to hide them from public API 2015-12-20 19:08:25 +01:00
iska f4bd5420c0 Add source documentation for the Parser classes 2015-12-20 17:27:13 +01:00
iska e773117f1f Fix the returned object of the fragment parsing algorithm
- Return a non-nil empty array instead of nil when context element is nil
- Return a copy of the document root's child nodes instead of the "view"-array
- Use the root selector instead of the index-based access when rerunning the algorithm with the same context element
2015-12-20 16:42:08 +01:00
iska d7476ef22d Add another sample code to the plaground 2015-12-20 00:02:05 +01:00
iska 4fabcdf76a Fix switch-state call in the Tokenizer for RAWTEXT End Tag state 2015-12-20 00:02:05 +01:00
iska bf147a6bb6 Remove redundant switch-state call in the Tokenizer 2015-12-20 00:02:05 +01:00
iska 0a620d74b6 Add source documentation for the Toknizer classes 2015-12-19 23:59:39 +01:00
iska 8893f28e4d Add HTML Element class-list property 2015-11-30 20:22:35 +01:00
iska d924063b02 Add implementation for a DOM Token List
https://dom.spec.whatwg.org/#interface-domtokenlist
2015-11-30 20:22:11 +01:00
iska f9065ab8c3 Add playground with sample code 2015-11-30 02:50:12 +01:00
iska 9815d3b39e Add nullability annotations throughout the code base 2015-11-30 02:49:58 +01:00
iska 1e30e4d86f Add HTML Node's querySelector & querySelectorAll methods 2015-11-29 19:54:23 +01:00
iska 188e5a0e56 Add missing check for nil-reference in the selector parser 2015-11-29 19:53:52 +01:00
iska 991e868d14 Add selector initializer for creating instances with a given selector string 2015-11-29 19:53:39 +01:00
iska f3af320096 Share target schemes 2015-11-29 19:52:15 +01:00
iska 9d60a26622 Organize public headers and build phases for iOS 2015-11-29 18:44:43 +01:00
iska 936348d300 Add iOS Framework target 2015-11-29 18:33:04 +01:00
iska 6410eb672a Organize framework target's build phases 2015-11-29 18:22:02 +01:00
iska 49e060cd3c Organize public header imports 2015-11-29 18:09:58 +01:00
iska dac3cee3d8 Add missing imports to the umbrella CSS Selectors header 2015-11-29 18:04:24 +01:00
iska 5ee3b9b614 Organize CSS Selectors' virtual groups 2015-11-29 18:03:56 +01:00
iska e788c832c6 Merge branch 'release/0.3.0' 2015-11-29 03:35:31 +01:00
iska a33f4e0834 Use nodes tree description in HTML Parser tests 2015-11-29 03:29:13 +01:00
iska 9e4007dcba Use nullability annotations for HTML Ordered Dictionary 2015-11-29 03:29:12 +01:00
iska 50b67cfa94 Add CSS Selector tests into own virtual group 2015-11-29 03:29:12 +01:00
iska 47d244289e Merge branch 'feature/css_selectors' into develop 2015-11-28 21:14:24 +01:00
iska 6b81d6e465 Add tests for the CSS Selector Parser 2015-11-28 21:11:48 +01:00
iska 13b8fe7808 Add CSS Selectors Parser implementation 2015-11-28 21:11:16 +01:00
iska 94fed8afa1 Add CSS test fixtures
Tests are adapted from the official suite:
http://www.w3.org/Style/CSS/Test/CSS3/Selectors/current/html/static/flat/

Test file format:
Each test file is a HTML fragment with:

<script> element with id="selectors": contains a JSON array of several selector
definitions. Each selector definition has a selector string as input, a "match" array
of the expected matched IDs. Some selectors must fail with a parse error, such
selectors have an "error" key with the error location in the selector string.

<div> element with id="testDOM": contains the DOM fragment which should be
used to select the elements for each of the defined selectors.
2015-11-28 21:10:31 +01:00
iska 30dc6cf343 Add HTML Node method to count child elements 2015-11-28 21:09:44 +01:00
iska badf070907 Add HTMLKit error domain header 2015-11-28 21:09:25 +01:00
iska 3443321459 Add missing check in cosnume-character HTML stream method
Should only advance location if not reconsuming previous character
2015-11-28 21:08:39 +01:00
iska 60dc6bfc43 Fix nth-last-child selector 2015-11-28 21:07:10 +01:00
iska 60a5821a2c Fix general sibling combinator selector 2015-11-28 21:06:52 +01:00
iska 621dbe36d6 Fix pseudo selectors' names 2015-11-27 19:44:57 +01:00
iska 50ae2c5283 Fix :root selector 2015-11-27 19:41:09 +01:00
iska 0cb374681c Fix sibling combinator selectors 2015-11-27 01:10:27 +01:00
iska c09adc2f96 Add Node properties for previous and next sibling elements 2015-11-27 01:10:07 +01:00
iska 09991a149f Fix :disabled selector's check for disabled form elements
Should've  used or- instead of all-combinator
2015-11-27 01:01:02 +01:00
iska f735fcdfd6 Treat nil attribute value as an empty string in the Attribute selector 2015-11-27 00:47:45 +01:00
iska 5a3c1063e7 Fix Nth-Expression selector's compute-index methods 2015-11-27 00:47:24 +01:00
iska 8cb088367d Add Node methods to handle child elements' indices 2015-11-27 00:46:57 +01:00
iska 7ec90d6470 Fix escaped-code-point consuming in CSS input stream 2015-11-25 21:34:43 +01:00
iska 002564f9ae Use debug description in combinator selectors 2015-11-25 21:33:06 +01:00
iska 4c80c520d4 Fix reconsume checks in input stream reader for CSS logic 2015-11-24 23:04:21 +01:00
iska 85abf35099 Fix implementation of the shorthand "anyOf" selector 2015-11-24 23:03:13 +01:00
iska 578806ec2f Add description methods for compound selectors 2015-11-24 23:02:32 +01:00
iska 7923efb02b Add compound selector method to append further selectors 2015-11-24 23:02:14 +01:00
iska f4d42e0753 Fix description methods for combinator selectors 2015-11-24 23:01:49 +01:00
iska 56f99f9168 Add input stream method to consume a CSS combinator 2015-11-24 23:01:27 +01:00
iska c8dd1d7791 Fix consume CSS identifier input stream method
http://www.w3.org/TR/CSS21/syndata.html#characters
https://drafts.csswg.org/css-syntax/#consume-name
2015-11-24 23:01:11 +01:00
iska c57d3453fc Add id & class selector tests 2015-10-26 22:27:00 +01:00
iska a413156514 Fix method name for checked element selector 2015-10-26 22:26:43 +01:00
iska 203d153487 Fix hyphen-attribute test 2015-10-26 22:26:19 +01:00
iska 4ceb913b9f Add HTMLKit workspace and setup project headers 2015-10-26 22:26:00 +01:00
iska 7896f1687e Remove temp test case from type selector tests class 2015-10-23 23:51:30 +02:00
iska 55bb8effb2 Add tests for the CSS Combinator selectors 2015-10-23 23:48:47 +02:00
iska 2e10c23f52 Add debug description method to HTML Element 2015-10-23 22:54:34 +02:00
iska 4f47a750e0 Use lowercase string in the Nth-Expression selector parser 2015-10-23 22:54:06 +02:00
iska 46beae1b2d Change attribute selector's debug description string 2015-10-23 22:53:06 +02:00
iska 2e441727bc Fix equals method in the attribute selector for iOS target 2015-10-23 22:52:40 +02:00
iska cc101b9f4e Fix type selectors in the main header's implementation 2015-10-23 22:52:14 +02:00
iska 0635744a2d Add a CSS selector-base Node Filter implementation 2015-10-23 22:50:51 +02:00
iska b5bb0a48b6 Add tests for the Nth-Expression selectors 2015-10-23 22:50:20 +02:00
iska 95ffccf67b Add HTML Node methods for selecting elements with given CSS Selectors 2015-10-23 22:49:25 +02:00
iska 6af01a1214 Add -ObjC to iOS testing target to load category methods 2015-10-22 19:20:22 +02:00
iska 667693939f Update project settings for Xcode7 2015-10-22 19:12:42 +02:00
iska f05adfb56b Add implementation for the Nth-Expressions selector
http://www.w3.org/TR/css3-selectors/#structural-pseudos
2015-10-21 23:34:24 +02:00
iska bed2edf22a Remove obsolete CSS Tokenizer and Token classes 2015-10-21 22:32:09 +02:00
iska 2574736359 Use pseudo class and block selectors for parameterless pseudo class selectors 2015-10-21 22:30:58 +02:00
iska ab7de014cf Add implementation for "required" and "optional" selectors
https://html.spec.whatwg.org/multipage/scripting.html#selector-reqiored
https://html.spec.whatwg.org/multipage/scripting.html#selector-optional
2015-10-21 22:30:22 +02:00
iska 15e768267a Rename CSS tokenizer code points header file 2015-10-21 22:21:24 +02:00
iska ee82cacc48 Add CSS input stream method to consume characters till given codepoint is met 2015-10-21 22:16:18 +02:00
iska 61d7c683fb Add implementations for structural pseudo class selectors
https://html.spec.whatwg.org/multipage/scripting.html#selectors
http://api.jquery.com/category/selectors/jquery-selector-extensions/
http://sizzlejs.com
2015-10-21 22:15:36 +02:00
iska bd84884bf7 Add a block-based selector class for inline selector implementations 2015-10-21 22:13:03 +02:00
iska f248b39d26 Move Nth-Expression typedef to the public header 2015-10-21 00:35:07 +02:00
iska 118ea137f4 Update existing type and attribute tests for new structure 2015-10-20 21:31:08 +02:00
iska 0366f39d1a Add public header for all available selectors and hide the actual implementations 2015-10-20 21:30:39 +02:00
iska d107c05a44 Add stubs for the Nth-Expression Selectors
http://www.w3.org/TR/css3-selectors/#structural-pseudos
2015-10-20 21:17:42 +02:00
iska 5ca3bf1192 Replace nullability specifiers from methods and properties with nullability-regions 2015-10-20 21:15:30 +02:00
iska 02393ddedd Add implementation for the CSS Combinators
http://www.w3.org/TR/css3-selectors/#combinators
2015-10-19 21:51:46 +02:00
iska 33df9e7fb2 Add implementation for combining multiple selectors with "All" and "Exists" quantifiers 2015-10-19 21:49:51 +02:00
iska 6020b2bdd9 Add implementation for the negation and "has" selectors
The negation selector is specified here:
http://www.w3.org/TR/css3-selectors/#negation

The "has" selector is an extra convenience selector
2015-10-19 21:41:33 +02:00
iska c44c77d63d Refactor selectors code
- Remove simple sequence
- Remove simple selector protocol
- Let selectors subclass CSSSelector directly
2015-10-18 22:36:03 +02:00
iska 9905f45e27 Change the CSS selector base class to a protocol unrelated to the HTML Node Filter 2015-10-12 20:12:50 +02:00
iska c868bd1a56 Add HTML stream method to consume characters in given string 2015-10-12 20:00:44 +02:00
iska 8f38aed6c6 Add "not" attribute selector to match attribute not-having given value 2015-10-12 20:00:08 +02:00
iska 1917fb1f1c Add base class for pseudo class selectors
http://www.w3.org/TR/css3-selectors/#pseudo-classes
2015-10-11 23:43:29 +02:00
iska dc93bb07f6 Remove unneeded methods from the base selector class 2015-10-11 23:41:15 +02:00
iska a74be4dfa9 Add nullability specifiers to the simple sequence of selectors 2015-10-11 23:40:51 +02:00
iska 7cd47486ac Add nullability specifiers to type selector 2015-10-11 23:40:15 +02:00
iska ad902e3138 Add nullability specifiers to attribute selector 2015-10-11 23:40:03 +02:00
iska 6cfe3b6f83 Add implementation for CSS nth-expressions
https://drafts.csswg.org/css-syntax/#anb-microsyntax
2015-10-10 21:34:50 +02:00
iska a2096b0e54 Add debug descriptions for the attribute, type & sequence selectors 2015-10-10 00:06:09 +02:00
iska 79fda9fdbe Remove extra initializer methods from type and attribute selectors
keep it simple
2015-10-06 23:05:25 +02:00
iska 1a05c41f86 Refactor CSS input stream to subclass the HTML input stream class to prevent duplication 2015-10-06 23:01:59 +02:00
iska bf751d94b1 Add category method for the hex number character set
and use a dispatch once to initialize the set only once
2015-10-04 23:50:39 +02:00
iska 43705dac69 Update gitignore file 2015-10-02 19:29:10 +02:00
iska 3c7e6e1913 Change returned code points to UniChar instead of UTF32Chars in CSS input stream 2015-10-02 19:28:32 +02:00
iska af4444e9a8 Add log message on CSS Tokenizer error
Proper handling will be implemented later
2015-06-23 00:07:47 +02:00
iska 23d0d62295 Reorganize CSS related classes 2015-06-22 23:48:31 +02:00
iska b0668e121e Remove obsolete CSS lexer 2015-06-22 23:40:16 +02:00
iska aa95bbd9f2 Add Unicode Range CSS Token parsing 2015-06-22 20:55:33 +02:00
iska 1544f71c50 Add initial implementation for a CSS Tokenizer
http://www.w3.org/TR/2013/WD-css-syntax-3-20131105/#tokenization
2015-06-22 00:44:09 +02:00
iska b1ddbc5f75 Add CSS Tokenizer related helper function for dealing with code points 2015-06-22 00:43:32 +02:00
iska 576dc4dfb2 Add CSS Token classes 2015-06-22 00:42:34 +02:00
iska c4aacfae2b Add CSS Input Stream class
http://www.w3.org/TR/2013/WD-css-syntax-3-20131105/#tokenizing-and-parsing
2015-06-15 22:06:26 +02:00
iska a54cae8829 Fix type & attribute selectors and their tests after merge with new DOM 2015-06-07 22:34:50 +02:00
iska 852dfad19c Add enum values for the other different tokens in a CSS Selector 2015-06-07 20:52:42 +02:00
iska a5d53b3eef Fix acceptNode method in the selector's implementation after merge with new DOM 2015-06-07 00:08:40 +02:00
iska 5e5b903c52 Fix import in CSS Selector test classes 2015-06-06 23:29:04 +02:00
iska 40b6e41e12 Merge branch 'develop' into feature/css_selectors
Conflicts:
	HTMLKit.xcodeproj/project.pbxproj
	HTMLKit/HTMLNodeFilter.h
2015-06-06 20:20:27 +02:00
iska 05e224283a Expose tokenizer's position variables for better error reporting 2015-05-16 14:23:06 +02:00
iska 8490d4d2ca Add tokenizer method to get next-non-space token 2015-05-16 14:22:44 +02:00
iska 52b850317e Remove selector method to match a HTML Element used in the simple selectors
Selectors have only one method to implement, the Node Filter's acceptNode
2015-05-15 22:37:56 +02:00
iska f5d970ba87 Add implementation for a CSS Simple Sequence of Selectors
http://www.w3.org/TR/css3-selectors/#sequence
2015-05-15 02:37:51 +02:00
iska 0c8fd754c3 Add CSS Simple Selector protocol to the Type & Attribute selectors 2015-05-15 02:29:18 +02:00
iska 4f03e00003 Add a protocol for CSS Simple Selectors
Simple Selectors should conform to this protocol in order to be addable to the Simple Selector Sequence.
2015-05-15 02:28:03 +02:00
iska 62b385d9b4 Add test cases for changing selectors' properties for the Type & Attribute selectors 2015-05-14 21:42:53 +02:00
iska a7a30a7cbf Add convenience initializer for the Type Selector 2015-05-14 21:27:08 +02:00
iska 221b085fe5 Add tests for the Type & Attribute selectors 2015-05-14 21:18:55 +02:00
iska a2d9e65f0e Fix Attribute Selector for the Hyphen-case
Check for prefix instead of suffix with "-"
2015-05-14 21:16:29 +02:00
iska 745d0f72a7 Add convenience methods to create Class & ID Attribute Selectors
http://www.w3.org/TR/css3-selectors/#class-html
http://www.w3.org/TR/css3-selectors/#id-selectors
2015-05-14 21:15:39 +02:00
iska c401c3ca2c Add umbrella header for all CSS Selectors 2015-05-14 17:09:17 +02:00
iska 3cb5ed9a42 Add implementation for a CSS Attribute Selector
http://www.w3.org/TR/css3-selectors/#attribute-selectors
2015-05-14 16:32:47 +02:00
iska acec99ffea Add implementation for a CSS Type Selector
Matches an Element with the given tag name or all Elements (universal selector)
http://www.w3.org/TR/css3-selectors/#type-selectors
2015-05-14 16:23:23 +02:00
iska 7c58268dfd Add initial implementation for the CSS Selector base class 2015-05-14 16:21:26 +02:00
iska c87076b470 Add a NSCharacterSet category for HTML Whitespace characters
http://www.w3.org/TR/css3-selectors/#whitespace
2015-05-14 16:19:31 +02:00
iska 81a20f0333 Add HTML Node Filter protocol
https://dom.spec.whatwg.org/#interface-nodefilter
2015-05-14 16:15:41 +02:00
iska 4fcaf4c810 Add implementation for a CSS Selector Tokenizer
The Tokenizer is based on a flex-generated lexer for the grammar defined in the CSS Selectors Level 3 Spec
http://www.w3.org/TR/css3-selectors/#lex
2015-05-09 18:58:50 +02:00
227 changed files with 10461 additions and 616 deletions
+25 -6
View File
@@ -1,8 +1,12 @@
# OS X
.DS_Store
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
@@ -12,13 +16,28 @@ build/
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
profile
*.moved-aside
DerivedData
*.xcuserstate
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
# CocoaPods
Pods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
Carthage/Checkouts
Carthage/Build
@@ -0,0 +1,80 @@
//: [Previous](@previous)
/*:
# CSS3 Selectors
HTMLKit understands CSS3 selectors making node-selection a piece of cake:
*/
import HTMLKit
let htmlString = "<div><h1>HTMLKit</h1><p class='greeting'>Hello there!</p><p class='description'>This is a demo of HTMLKit</p></div>"
let document = HTMLDocument(string: htmlString)
/*:
All CSS3 selectors are supported and you use them the way you always have:
*/
var paragraphs = document.querySelectorAll("p")
var paragraphsOrHeaders = document.querySelectorAll("p, h1")
var hasClassAttribute = document.querySelectorAll("[class]")
var greetings = document.querySelectorAll(".greeting")
var classNameStartsWith_de = document.querySelectorAll("[class^='de']")
var hasAdjacentHeader = document.querySelectorAll("h1 + *")
var hasSiblingHeader = document.querySelectorAll("h1 ~ *")
var hasSiblingParagraph = document.querySelectorAll("p ~ *")
var nonParagraphChildOfDiv = document.querySelectorAll("div :not(p)")
/*:
HTMLKit also provides API to create selector instances in a type-safe manner without the need to parse them first. The previous examples would like this:
*/
paragraphs = document.elementsMatchingSelector(typeSelector("p"))
paragraphsOrHeaders = document.elementsMatchingSelector(
anyOf([
typeSelector("p"), typeSelector("h1")
])
)
hasClassAttribute = document.elementsMatchingSelector(hasAttributeSelector("class"))
greetings = document.elementsMatchingSelector(classSelector("greeting"))
classNameStartsWith_de = document.elementsMatchingSelector(attributeSelector(.Begins, "class", "de"))
hasAdjacentHeader = document.elementsMatchingSelector(adjacentSiblingSelector(typeSelector("h1")))
hasAdjacentHeader = document.elementsMatchingSelector(generalSiblingSelector(typeSelector("h1")))
hasAdjacentHeader = document.elementsMatchingSelector(generalSiblingSelector(typeSelector("p")))
nonParagraphChildOfDiv = document.elementsMatchingSelector(
allOf([
childOfElementSelector(typeSelector("div")),
not(typeSelector("p"))
])
)
/*:
Here are more examples
*/
let firstDivElement = document.firstElementMatchingSelector(typeSelector("div"))!
var secondChildOfDiv = firstDivElement.querySelectorAll(":nth-child(2)")
var secondOfType = firstDivElement.querySelectorAll(":nth-of-type(2n)")
secondChildOfDiv = firstDivElement.elementsMatchingSelector(nthChildSelector(CSSNthExpression(an: 0, b: 2)))
secondOfType = firstDivElement.elementsMatchingSelector(nthOfTypeSelector(CSSNthExpression(an: 2, b: 0)))
var notParagraphAndNotDiv = firstDivElement.querySelectorAll(":not(p):not(div)")
notParagraphAndNotDiv = firstDivElement.elementsMatchingSelector(
allOf([
not(typeSelector("p")),
not(typeSelector("div"))
])
)
/*:
One more thing! You can also create your own selectors. You either subclass the CSSSelector or just use the block-based wrapper. For example the previous selector can be implemented like this:
*/
let myAwesomeSelector = namedBlockSelector("myAwesomeSelector", { (element) -> Bool in
return element.tagName != "p" && element.tagName != "div"
})
firstDivElement.elementsMatchingSelector(myAwesomeSelector)
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
@@ -0,0 +1,30 @@
/*:
# HTMLKit
![Logo](HTMLKit.png)
****
An Objective-C kit for your everyday HTML needs.
****
HTMLKit is a [WHATWG](https://html.spec.whatwg.org/multipage/) specification-compliant framework for parsing and serializing HTML documents and document fragments for iOS and OSX. HTMLKit parses real-world HTML the same way modern web browsers would.
****
HTMLKit is available under the MIT License
****
# Table of Contents
- [Parsing Document](Parsing%20Documents)
- [Parsing Fragments](Parsing%20Fragments)
- [The DOM](The%20DOM)
- [CSS Selectors](CSS%20Selectors)
****
[Next](@next)
*/
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=572&amp;EndingColumnNumber=5&amp;EndingLineNumber=27&amp;StartingColumnNumber=1&amp;StartingLineNumber=26&amp;Timestamp=472575682.16404"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=572&amp;EndingColumnNumber=9&amp;EndingLineNumber=27&amp;StartingColumnNumber=5&amp;StartingLineNumber=26&amp;Timestamp=472575682.164357"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=572&amp;EndingColumnNumber=9&amp;EndingLineNumber=27&amp;StartingColumnNumber=5&amp;StartingLineNumber=26&amp;Timestamp=472575682.16458"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=572&amp;EndingColumnNumber=9&amp;EndingLineNumber=1&amp;StartingColumnNumber=5&amp;StartingLineNumber=1&amp;Timestamp=472575682.164803"
lockedSize = "{309, 236}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
</TimelineItems>
</Timeline>
@@ -0,0 +1,29 @@
//: [Previous](@previous)
/*:
# Parsing HTML Documents
*/
import HTMLKit
/*:
Given some HTML content
*/
let htmlString = try! String(contentsOfURL: [#FileReference(fileReferenceLiteral: "HTMLKit.html")#])
/*:
You can parse it using the HTMLParser:
*/
let parser = HTMLParser(string: htmlString)
let documentViaParser = parser.parseDocument()
documentViaParser.innerHTML
/*:
You can also create a document from a given HTML string directly:
*/
let document = HTMLDocument(string: htmlString)
document.innerHTML
//: [Next](@next)
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=1&amp;CharacterRangeLoc=318&amp;EndingColumnNumber=26&amp;EndingLineNumber=13&amp;StartingColumnNumber=1&amp;StartingLineNumber=13&amp;Timestamp=472578634.909266"
lockedSize = "{800, 186}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
</TimelineItems>
</Timeline>
@@ -0,0 +1,38 @@
//: [Previous](@previous)
/*:
# Parsing Document Fragments
*/
import HTMLKit
/*:
Given some HTML content
*/
let htmlString = "<div><p>Hello HTMLKit</p></div><td>some table data"
/*:
You can prase it as a document fragment in a specified context element:
*/
let parser = HTMLParser(string: htmlString)
let tableContext = HTMLElement(tagName: "table")
var elements = parser.parseFragmentWithContextElement(tableContext)
for element in elements {
print(element.outerHTML)
}
/*:
The same parser instance can be reusued:
*/
let bodyContext = HTMLElement(tagName: "body")
elements = parser.parseFragmentWithContextElement(bodyContext)
for element in elements {
print(element.outerHTML)
}
//: [Next](@next)
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=356&amp;EndingColumnNumber=16&amp;EndingLineNumber=23&amp;StartingColumnNumber=2&amp;StartingLineNumber=23&amp;Timestamp=472578641.006172"
lockedSize = "{775, 114}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=0&amp;CharacterRangeLoc=356&amp;EndingColumnNumber=23&amp;EndingLineNumber=23&amp;StartingColumnNumber=2&amp;StartingLineNumber=23&amp;Timestamp=472578641.006502"
lockedSize = "{775, 80}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=21&amp;CharacterRangeLoc=262&amp;EndingColumnNumber=23&amp;EndingLineNumber=13&amp;StartingColumnNumber=2&amp;StartingLineNumber=13&amp;Timestamp=472578641.006717"
lockedSize = "{775, 76}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "YES">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=24&amp;CharacterRangeLoc=453&amp;EndingColumnNumber=26&amp;EndingLineNumber=23&amp;StartingColumnNumber=2&amp;StartingLineNumber=23&amp;Timestamp=472578641.006933"
lockedSize = "{775, 83}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=24&amp;CharacterRangeLoc=668&amp;EndingColumnNumber=26&amp;EndingLineNumber=34&amp;StartingColumnNumber=2&amp;StartingLineNumber=34&amp;Timestamp=472578641.007156"
lockedSize = "{775, 76}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "YES">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=24&amp;CharacterRangeLoc=281&amp;EndingColumnNumber=26&amp;EndingLineNumber=13&amp;StartingColumnNumber=2&amp;StartingLineNumber=13&amp;Timestamp=472579861.71569"
lockedSize = "{775, 68}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=24&amp;CharacterRangeLoc=448&amp;EndingColumnNumber=26&amp;EndingLineNumber=21&amp;StartingColumnNumber=2&amp;StartingLineNumber=21&amp;Timestamp=472579861.71569"
lockedSize = "{775, 68}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "YES">
</LoggerValueHistoryTimelineItem>
</TimelineItems>
</Timeline>
@@ -0,0 +1,79 @@
//: [Previous](@previous)
/*:
# The DOM
HTMLKit provides a rich DOM implementation for manipulating and navigating the document tree. Here are some of the features:
*/
import HTMLKit
let htmlString = "<div><h1>HTMLKit</h1><p>Hello there!</p></div>"
let document = HTMLDocument(string: htmlString)
/*:
Create new elements and assign attributes
*/
let description = HTMLElement(tagName:"meta", attributes: ["name": "description"])
description["content"] = "HTMLKit for iOS & OSX"
/*:
Append nodes to the document
*/
let head = document.head!
head.appendNode(description)
document.innerHTML
let body = document.body!
let nodes = [
HTMLElement(tagName: "div", attributes: ["class": "red"]),
HTMLElement(tagName: "div", attributes: ["class": "green"]),
HTMLElement(tagName: "div", attributes: ["class": "blue"])
]
body.appendNodes(nodes)
body.innerHTML
/*:
Enumerate child elements and perform DOM manipulation
*/
body.enumerateChildElementsUsingBlock { (element, index, stop) -> Void in
if element.tagName == "div" {
let lorem = HTMLElement(tagName: "p")
lorem.textContent = "Lorem ipsum: \(index)"
element.appendNode(lorem)
}
}
body.innerHTML
/*:
Remove nodes from the document
*/
body.removeChildNodeAtIndex(1)
body.innerHTML
/*:
Navigate to child and sibling nodes
*/
body.lastChild!.removeFromParentNode()
let greenDiv = body.firstChild!.nextSibling!
/*:
Manipulate the HTML directly
*/
greenDiv.innerHTML = "<ul><li>item 1<li>item 2"
/*:
Iterate the DOM tree with custom filters
*/
let filter = HTMLNodeFilterBlock.filterWithBlock { (node) -> HTMLNodeFilterValue in
if node.childNodesCount() != 1 {
return .Reject
}
return .Accept
}
for element in body.nodeIteratorWithShowOptions(.Element, filter: filter) {
element.outerHTML
}
//: [Next](@next)
@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=19&amp;CharacterRangeLoc=575&amp;EndingColumnNumber=26&amp;EndingLineNumber=24&amp;StartingColumnNumber=1&amp;StartingLineNumber=24&amp;Timestamp=472578662.196737"
lockedSize = "{763, 104}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "YES">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=15&amp;CharacterRangeLoc=843&amp;EndingColumnNumber=22&amp;EndingLineNumber=33&amp;StartingColumnNumber=1&amp;StartingLineNumber=33&amp;Timestamp=472578662.197012"
lockedSize = "{763, 75}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=1&amp;CharacterRangeLoc=1757&amp;EndingColumnNumber=44&amp;EndingLineNumber=75&amp;StartingColumnNumber=2&amp;StartingLineNumber=75&amp;Timestamp=472578674.159974"
lockedSize = "{763, 176}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=844&amp;EndingColumnNumber=15&amp;EndingLineNumber=33&amp;StartingColumnNumber=1&amp;StartingLineNumber=33&amp;Timestamp=472578662.197451"
lockedSize = "{762, 70}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=18&amp;CharacterRangeLoc=576&amp;EndingColumnNumber=19&amp;EndingLineNumber=24&amp;StartingColumnNumber=1&amp;StartingLineNumber=24&amp;Timestamp=472578662.197662"
lockedSize = "{752, 99}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=1&amp;CharacterRangeLoc=1757&amp;EndingColumnNumber=16&amp;EndingLineNumber=75&amp;StartingColumnNumber=2&amp;StartingLineNumber=75&amp;Timestamp=472578674.160606"
lockedSize = "{763, 102}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=18&amp;CharacterRangeLoc=1740&amp;EndingColumnNumber=39&amp;EndingLineNumber=75&amp;StartingColumnNumber=2&amp;StartingLineNumber=75&amp;Timestamp=472578674.160818"
lockedSize = "{759, 149}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=17&amp;CharacterRangeLoc=1741&amp;EndingColumnNumber=19&amp;EndingLineNumber=75&amp;StartingColumnNumber=2&amp;StartingLineNumber=75&amp;Timestamp=472578674.161034"
lockedSize = "{763, 120}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=1230&amp;EndingColumnNumber=15&amp;EndingLineNumber=51&amp;StartingColumnNumber=1&amp;StartingLineNumber=51&amp;Timestamp=472578674.161242"
lockedSize = "{763, 94}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=1145&amp;EndingColumnNumber=15&amp;EndingLineNumber=45&amp;StartingColumnNumber=1&amp;StartingLineNumber=45&amp;Timestamp=472578674.161449"
lockedSize = "{760, 97}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=8&amp;CharacterRangeLoc=1332&amp;EndingColumnNumber=13&amp;EndingLineNumber=57&amp;StartingColumnNumber=5&amp;StartingLineNumber=57&amp;Timestamp=472578674.161666"
lockedSize = "{763, 74}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=18&amp;CharacterRangeLoc=397&amp;EndingColumnNumber=19&amp;EndingLineNumber=14&amp;StartingColumnNumber=1&amp;StartingLineNumber=14&amp;Timestamp=472578662.199111"
lockedSize = "{762, 90}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=701&amp;EndingColumnNumber=15&amp;EndingLineNumber=26&amp;StartingColumnNumber=1&amp;StartingLineNumber=26&amp;Timestamp=472578662.199312"
lockedSize = "{763, 76}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=1002&amp;EndingColumnNumber=15&amp;EndingLineNumber=38&amp;StartingColumnNumber=1&amp;StartingLineNumber=38&amp;Timestamp=472578674.162269"
lockedSize = "{763, 92}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=1049&amp;EndingColumnNumber=15&amp;EndingLineNumber=41&amp;StartingColumnNumber=1&amp;StartingLineNumber=41&amp;Timestamp=472578674.162475"
lockedSize = "{763, 83}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=8&amp;CharacterRangeLoc=1108&amp;EndingColumnNumber=13&amp;EndingLineNumber=44&amp;StartingColumnNumber=5&amp;StartingLineNumber=44&amp;Timestamp=472578674.162693"
lockedSize = "{763, 73}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=17&amp;CharacterRangeLoc=1598&amp;EndingColumnNumber=19&amp;EndingLineNumber=68&amp;StartingColumnNumber=2&amp;StartingLineNumber=68&amp;Timestamp=472578674.162898"
lockedSize = "{763, 92}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=8&amp;CharacterRangeLoc=1410&amp;EndingColumnNumber=9&amp;EndingLineNumber=61&amp;StartingColumnNumber=1&amp;StartingLineNumber=59&amp;Timestamp=472578674.163102"
lockedSize = "{763, 60}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=590&amp;EndingColumnNumber=15&amp;EndingLineNumber=21&amp;StartingColumnNumber=1&amp;StartingLineNumber=21&amp;Timestamp=472579886.937978"
lockedSize = "{763, 50}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=830&amp;EndingColumnNumber=15&amp;EndingLineNumber=30&amp;StartingColumnNumber=1&amp;StartingLineNumber=30&amp;Timestamp=472579886.937978"
lockedSize = "{763, 50}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "YES">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=14&amp;CharacterRangeLoc=877&amp;EndingColumnNumber=15&amp;EndingLineNumber=33&amp;StartingColumnNumber=1&amp;StartingLineNumber=33&amp;Timestamp=472579886.937978"
lockedSize = "{763, 50}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "YES">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=8&amp;CharacterRangeLoc=936&amp;EndingColumnNumber=13&amp;EndingLineNumber=36&amp;StartingColumnNumber=5&amp;StartingLineNumber=36&amp;Timestamp=472579886.937978"
lockedSize = "{763, 50}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=8&amp;CharacterRangeLoc=978&amp;EndingColumnNumber=9&amp;EndingLineNumber=38&amp;StartingColumnNumber=1&amp;StartingLineNumber=38&amp;Timestamp=472579886.937978"
lockedSize = "{763, 50}"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "YES">
</LoggerValueHistoryTimelineItem>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=17&amp;CharacterRangeLoc=1261&amp;EndingColumnNumber=19&amp;EndingLineNumber=48&amp;StartingColumnNumber=2&amp;StartingLineNumber=48&amp;Timestamp=472579886.937978"
lockedSize = "{763, 92}"
selectedRepresentationIndex = "1"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
</TimelineItems>
</Timeline>
+11
View File
@@ -0,0 +1,11 @@
<html>
<head>
<title>HTMLKit</title>
</head>
<body>
<h1>HTMLKit</h1>
<p>HTMLKit is a <a href="https://html.spec.whatwg.org/multipage">WHATWG specification-compliant</a> Objective-C framework for parsing and serializing HTML documents and document fragments for iOS and OSX.</p>
<p>HTMLKit parses real-world HTML the same way modern web browsers would.</p>
<p>HTMLKit comes armed with a <a href="http://www.w3.org/TR/css3-selectors">CSS3 Selectors</a> engine for querying the DOM.</p>
</body>
</html>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='rendered'>
<pages>
<page name='Intro'/>
<page name='Parsing Documents'/>
<page name='Parsing Fragments'/>
<page name='The DOM'/>
<page name='CSS Selectors'/>
</pages>
</playground>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because it is too large Load Diff
@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0710"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
BuildableName = "HTMLKit.framework"
BlueprintName = "HTMLKit-OSX"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "625A14C219C7829400AD0C32"
BuildableName = "HTMLKitTests-OSX.xctest"
BlueprintName = "HTMLKitTests-OSX"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "HTMLKitParserPerformance">
</Test>
<Test
Identifier = "HTMLKitParserPerformance/testParserPerformance">
</Test>
<Test
Identifier = "HTMLKitTokenizerPerformance">
</Test>
<Test
Identifier = "HTMLKitTokenizerPerformance/testTokenizerPerformance">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
BuildableName = "HTMLKit.framework"
BlueprintName = "HTMLKit-OSX"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
BuildableName = "HTMLKit.framework"
BlueprintName = "HTMLKit-OSX"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
BuildableName = "HTMLKit.framework"
BlueprintName = "HTMLKit-OSX"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0710"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
BuildableName = "HTMLKit.framework"
BlueprintName = "HTMLKit-iOS"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "62ECBF551C0B6C7600AF847B"
BuildableName = "HTMLKitTests-iOS.xctest"
BlueprintName = "HTMLKitTests-iOS"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "HTMLKitParserPerformance">
</Test>
<Test
Identifier = "HTMLKitParserPerformance/testParserPerformance">
</Test>
<Test
Identifier = "HTMLKitTokenizerPerformance">
</Test>
<Test
Identifier = "HTMLKitTokenizerPerformance/testTokenizerPerformance">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
BuildableName = "HTMLKit.framework"
BlueprintName = "HTMLKit-iOS"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
BuildableName = "HTMLKit.framework"
BlueprintName = "HTMLKit-iOS"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
BuildableName = "HTMLKit.framework"
BlueprintName = "HTMLKit-iOS"
ReferencedContainer = "container:HTMLKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:HTMLKit.xcodeproj">
</FileRef>
<FileRef
location = "group:HTMLKit.playground">
</FileRef>
</Workspace>
+72
View File
@@ -0,0 +1,72 @@
//
// CSSAttributeSelector.h
// HTMLKit
//
// Created by Iska on 14/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Attribute Selector.
*/
@interface CSSAttributeSelector : CSSSelector
/**
The selector type.
*/
@property (nonatomic, assign, readonly) CSSAttributeSelectorType type;
/**
The attribute name which should be matched.
*/
@property (nonatomic, strong, readonly) NSString *name;
/**
The attribute value against which should be checked.
*/
@property (nonatomic, strong, readonly) NSString *value;
/**
Intializes and returns a CSS class selector.
@param className The class name to match.
@returns A new instance of class selector.
*/
+ (instancetype)classSelector:(NSString *)className;
/**
Intializes and returns a CSS id selector.
@param elementId The element id to match.
@returns A new instance of id selector.
*/
+ (instancetype)idSelector:(NSString *)elementId;
/**
Intializes and returns a CSS has-attribute selector.
@param attributeName The attribute name to match.
@returns A new instance of has-attribute selector.
*/
+ (instancetype)hasAttributeSelector:(NSString *)attributeName;
/**
Intializes and returns a CSS attribute selector.
@param type The selector type.
@param name The attribute name to match.
@param value The value to match.
@returns A new instance of attribute selector.
*/
- (instancetype)initWithType:(CSSAttributeSelectorType)type
attributeName:(NSString *)name
attrbiuteValue:(NSString *)value;
@end
NS_ASSUME_NONNULL_END
+111
View File
@@ -0,0 +1,111 @@
//
// CSSAttributeSelector.m
// HTMLKit
//
// Created by Iska on 14/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "CSSAttributeSelector.h"
#import "HTMLElement.h"
#import "NSCharacterSet+HTMLKit.h"
@interface CSSAttributeSelector ()
{
CSSAttributeSelectorType _type;
NSString *_name;
NSString *_value;
}
@end
@implementation CSSAttributeSelector
+ (instancetype)classSelector:(NSString *)className
{
return [[self alloc] initWithType:CSSAttributeSelectorIncludes attributeName:@"class" attrbiuteValue:className];
}
+ (instancetype)idSelector:(NSString *)elementId
{
return [[self alloc] initWithType:CSSAttributeSelectorExactMatch attributeName:@"id" attrbiuteValue:elementId];
}
+ (instancetype)hasAttributeSelector:(NSString *)attributeName
{
return [[self alloc] initWithType:CSSAttributeSelectorExists attributeName:attributeName attrbiuteValue:@""];
}
- (instancetype)initWithType:(CSSAttributeSelectorType)type
attributeName:(NSString *)name
attrbiuteValue:(NSString *)value
{
self = [super init];
if (self) {
_type = type;
_name = [name copy];
_value = value ? [value copy]: @"";
}
return self;
}
- (BOOL)acceptElement:(HTMLElement *)element
{
switch (_type) {
case CSSAttributeSelectorExists:
{
return !!element[_name];
}
case CSSAttributeSelectorExactMatch:
{
return [element[_name] isEqualToString:_value];
}
case CSSAttributeSelectorIncludes:
{
NSArray *components = [element[_name] componentsSeparatedByCharactersInSet:[NSCharacterSet HTMLWhitespaceCharacterSet]];
return [components containsObject:_value];
}
case CSSAttributeSelectorBegins:
{
return [element[_name] hasPrefix:_value];
}
case CSSAttributeSelectorEnds:
{
return [element[_name] hasSuffix:_value];
}
case CSSAttributeSelectorContains:
{
return [element[_name] containsString:_value];
}
case CSSAttributeSelectorHyphen:
{
return [element[_name] isEqualToString:_value] || [element[_name] hasPrefix:[_value stringByAppendingString:@"-"]];
}
case CSSAttributeSelectorNot:
{
return ![element[_name] isEqualToString:_value];
}
default:
return NO;
}
}
#pragma mark - Description
- (NSString *)debugDescription
{
if (self.type == CSSAttributeSelectorExists) {
return [NSString stringWithFormat:@"[%@]", self.name];
}
NSString *matcher = @{@(CSSAttributeSelectorExactMatch): @"=",
@(CSSAttributeSelectorIncludes): @"~=",
@(CSSAttributeSelectorBegins): @"^=",
@(CSSAttributeSelectorEnds): @"$=",
@(CSSAttributeSelectorContains): @"*=",
@(CSSAttributeSelectorHyphen): @"|=",
@(CSSAttributeSelectorNot): @"!="}[@(self.type)];
return [NSString stringWithFormat:@"[%@%@'%@']", self.name, matcher, self.value];
}
@end
+144
View File
@@ -0,0 +1,144 @@
//
// CSSTokenizer CODEPOINTs.h
// HTMLKit
//
// Created by Iska on 08/06/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
//------------------------------------------------------
#define CODEPOINTS \
CODEPOINT( CONTROL, 0x0080 ) \
CODEPOINT( CHARACTER_TABULATION, 0x0009 ) \
CODEPOINT( LINE_FEED, 0x000A ) \
CODEPOINT( SPACE, 0x0020 ) \
CODEPOINT( QUOTATION_MARK, 0x0022 ) \
CODEPOINT( NUMBER_SIGN, 0x0023 ) \
CODEPOINT( DOLLAR_SIGN, 0x0024 ) \
CODEPOINT( APOSTROPHE, 0x0027 ) \
CODEPOINT( LEFT_PARENTHESIS, 0x0028 ) \
CODEPOINT( RIGHT_PARENTHESIS, 0x0029 ) \
CODEPOINT( ASTERIX, 0x002A ) \
CODEPOINT( PLUS_SIGN, 0x002B ) \
CODEPOINT( COMMA, 0x002C ) \
CODEPOINT( HYPHEN_MINUS, 0x002D ) \
CODEPOINT( FULL_STOP, 0x002E ) \
CODEPOINT( DIGIT_ZERO, 0x0030 ) \
CODEPOINT( DIGIT_NINE, 0x0039 ) \
CODEPOINT( COLON, 0x003A ) \
CODEPOINT( EQUALS_SIGN, 0x003D ) \
CODEPOINT( GREATER_THAN_SIGN, 0x003E ) \
CODEPOINT( LATIN_CAPITAL_LETTER_A, 0x0041 ) \
CODEPOINT( LATIN_CAPITAL_LETTER_F, 0x0046 ) \
CODEPOINT( LATIN_CAPITAL_LETTER_Z, 0x005A ) \
CODEPOINT( LEFT_SQUARE_BRACKET, 0x005B ) \
CODEPOINT( REVERSE_SOLIDUS, 0x005C ) \
CODEPOINT( RIGHT_SQUARE_BRACKET, 0x005D ) \
CODEPOINT( CIRCUMFLEX_ACCENT, 0x005E ) \
CODEPOINT( LOW_LINE, 0x005F ) \
CODEPOINT( LATIN_SMALL_LETTER_A, 0x0061 ) \
CODEPOINT( LATIN_SMALL_LETTER_F, 0x0066 ) \
CODEPOINT( LATIN_SMALL_LETTER_Z, 0x007A ) \
CODEPOINT( VERTICAL_LINE, 0x007C ) \
CODEPOINT( TILDE, 0x007E ) \
CODEPOINT( REPLACEMENT_CHARACTER, 0xFFFD )
#define CODEPOINT( name, value ) static UniChar const name = value;
CODEPOINTS
#undef CODEPOINT
NS_INLINE BOOL isWhitespace(UTF32Char codePoint)
{
return (codePoint == CHARACTER_TABULATION ||
codePoint == LINE_FEED ||
codePoint == SPACE);
}
NS_INLINE BOOL isCombinator(UTF32Char codePoint)
{
return (codePoint == SPACE ||
codePoint == PLUS_SIGN ||
codePoint == COMMA ||
codePoint == GREATER_THAN_SIGN ||
codePoint == TILDE);
}
NS_INLINE BOOL isDigit(UTF32Char codePoint)
{
return codePoint >= DIGIT_ZERO && codePoint <= DIGIT_NINE;
}
NS_INLINE BOOL isHexDigit(UTF32Char codePoint)
{
return ((codePoint >= DIGIT_ZERO && codePoint <= DIGIT_NINE) ||
(codePoint >= LATIN_CAPITAL_LETTER_A && codePoint <= LATIN_CAPITAL_LETTER_F) ||
(codePoint >= LATIN_SMALL_LETTER_A && codePoint <= LATIN_SMALL_LETTER_F));
}
NS_INLINE BOOL isQuote(UTF32Char codePoint)
{
return codePoint == QUOTATION_MARK || codePoint == APOSTROPHE;
}
NS_INLINE BOOL isNewLine(UTF32Char codePoint)
{
return codePoint == LINE_FEED;
}
NS_INLINE BOOL isNameStart(UTF32Char codePoint)
{
return ((codePoint >= LATIN_CAPITAL_LETTER_A && codePoint <= LATIN_CAPITAL_LETTER_Z) ||
(codePoint >= LATIN_SMALL_LETTER_A && codePoint <= LATIN_SMALL_LETTER_Z) ||
codePoint >= CONTROL ||
codePoint == LOW_LINE);
}
NS_INLINE BOOL isName(UTF32Char codePoint)
{
return isNameStart(codePoint) || isDigit(codePoint) || codePoint == HYPHEN_MINUS;
}
NS_INLINE BOOL isValidEscape(UTF32Char first, UTF32Char second)
{
if (first != REVERSE_SOLIDUS || isNewLine(second)) {
return false;
}
return YES;
}
NS_INLINE BOOL isValidEscapedCodePoint(UTF32Char codePoint)
{
return (codePoint != 0 &&
!(codePoint >= 0xD800 && codePoint <= 0x0DFFF) &&
codePoint <= 0x10FFFF);
}
NS_INLINE BOOL isValidIdentifierStart(UTF32Char first, UTF32Char second, UTF32Char third)
{
if (first == HYPHEN_MINUS) {
if (isNameStart(second) ||
second == HYPHEN_MINUS ||
isValidEscape(second, third)) {
return YES;
} else {
return NO;
}
} else if (isNameStart(first)) {
return YES;
} else if (first == REVERSE_SOLIDUS) {
return isValidEscape(first, second);
} else {
return NO;
}
}
NS_INLINE void AppendCodePoint(CFMutableStringRef string, UTF32Char codePoint)
{
UniChar pair[2];
Boolean isPair = CFStringGetSurrogatePairForLongCharacter(codePoint, pair);
CFStringAppendCharacters(string, pair, isPair ? 2 : 1);
}
+52
View File
@@ -0,0 +1,52 @@
//
// CSSCombinatorSelector.h
// HTMLKit
//
// Created by Iska on 12/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Combinator Selector.
*/
@interface CSSCombinatorSelector : CSSSelector
/**
Initializes and returns a CSS child-of-element selector, e.g. 'div > p'
@param selector The selector matching the parent element.
@returns A new instance of the child of element selector.
*/
+ (instancetype)childOfElementCombinator:(CSSSelector *)selector;
/**
Initializes and returns a CSS descendant-of-element selector, e.g. 'div p'
@param selector The selector matching the ancestor element.
@returns A new instance of the descendant of element selector.
*/
+ (instancetype)descendantOfElementCombinator:(CSSSelector *)selector;
/**
Initializes and returns a CSS adjacent sibling selector, e.g. 'p + a'
@param selector The selector matching the adjacent sibling element.
@returns A new instance of the adjacent sibling selector.
*/
+ (instancetype)adjacentSiblingCombinator:(CSSSelector *)selector;
/**
Initializes and returns a CSS general sibling selector, e.g. 'p ~ a'
@param selector The selector matching the general sibling element.
@returns A new instance of the general sibling selector.
*/
+ (instancetype)generalSiblingCombinator:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END
+155
View File
@@ -0,0 +1,155 @@
//
// CSSCombinatorSelector.m
// HTMLKit
//
// Created by Iska on 12/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSCombinatorSelector.h"
#import "HTMLElement.h"
#import "HTMLNode+Private.h"
#pragma mark - Declarations
@interface CSSChildOfElementCombinatorSelector : CSSCombinatorSelector
@end
@interface CSSDecendantOfElementCombinatorSelector : CSSCombinatorSelector
@end
@interface CSSAdjacentSiblingCombinatorSelector : CSSCombinatorSelector
@end
@interface CSSGeneralSiblingCombinatorSelector : CSSCombinatorSelector
@end
#pragma mark - Base Combinator
@interface CSSCombinatorSelector ()
{
CSSSelector *_selector;
}
@property (nonatomic, strong, readonly) CSSSelector *selector;
@end
@implementation CSSCombinatorSelector
@synthesize selector = _selector;
+ (instancetype)childOfElementCombinator:(CSSSelector *)selector
{
return [[CSSChildOfElementCombinatorSelector alloc] initWithSelector:selector];
}
+ (instancetype)descendantOfElementCombinator:(CSSSelector *)selector
{
return [[CSSDecendantOfElementCombinatorSelector alloc] initWithSelector:selector];
}
+ (instancetype)adjacentSiblingCombinator:(CSSSelector *)selector
{
return [[CSSAdjacentSiblingCombinatorSelector alloc] initWithSelector:selector];
}
+ (instancetype)generalSiblingCombinator:(CSSSelector *)selector
{
return [[CSSGeneralSiblingCombinatorSelector alloc] initWithSelector:selector];
}
- (instancetype)initWithSelector:(CSSSelector *)selector
{
self = [super init];
if (self) {
_selector = selector;
}
return self;
}
@end
#pragma mark - Child OfElement Combinator
@implementation CSSChildOfElementCombinatorSelector
- (BOOL)acceptElement:(HTMLElement *)element
{
HTMLElement *parent = element.parentElement;
return parent != nil && [self.selector acceptElement:parent];
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@"%@ > ", self.selector.debugDescription];
}
@end
#pragma mark - Decendant Of Element Combinator
@implementation CSSDecendantOfElementCombinatorSelector
- (BOOL)acceptElement:(HTMLElement *)element
{
HTMLElement *parent = element.parentElement;
while (parent != nil) {
if ([self.selector acceptElement:parent]) {
return YES;
}
parent = parent.parentElement;
}
return NO;
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@"%@ ", self.selector.debugDescription];
}
@end
#pragma mark - Adjacent Sibling Combinator
@implementation CSSAdjacentSiblingCombinatorSelector
- (BOOL)acceptElement:(HTMLElement *)element
{
HTMLNode *previous = element.previousSiblingElement;
if (previous == nil || previous.nodeType != HTMLNodeElement) {
return NO;
}
return [self.selector acceptElement:previous.asElement];
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@"%@ + ", self.selector.debugDescription];
}
@end
#pragma mark - General Sibling Combinator
@implementation CSSGeneralSiblingCombinatorSelector
- (BOOL)acceptElement:(HTMLElement *)element
{
HTMLNode *previous = element.previousSiblingElement;
while (previous != nil && previous.nodeType == HTMLNodeElement) {
if ([self.selector acceptElement:previous.asElement]) {
return YES;
}
previous = previous.previousSiblingElement;
}
return NO;
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@"%@ ~ ", self.selector.debugDescription];
}
@end
+43
View File
@@ -0,0 +1,43 @@
//
// CSSCompoundSelector.h
// HTMLKit
//
// Created by Iska on 18/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
A Compound Selector, groups other selectors with a 'all-of' or 'any-of' relationship.
*/
@interface CSSCompoundSelector : CSSSelector
/**
Initializes and returns a new compound selector matching only elements that match all of the specified selectors.
@param selectors The selectors list.
@returns A new instance of the All-Of selector.
*/
+ (instancetype)andSelector:(NSArray<CSSSelector *> *)selectors;
/**
Initializes and returns a new compound selector matching all elements that match at least one of the specified selectors.
@param selectors The selectors list.
@returns A new instance of the Any-Of selector.
*/
+ (instancetype)orSelector:(NSArray<CSSSelector *> *)selectors;
/**
Add the specified selector to the compound.
@param selector The selector to add.
*/
- (void)addSelector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END
+99
View File
@@ -0,0 +1,99 @@
//
// CSSCompoundSelector.m
// HTMLKit
//
// Created by Iska on 18/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSCompoundSelector.h"
#pragma mark - Declarations
@interface CSSAndCompoundSelector : CSSCompoundSelector
@end
@interface CSSOrCompoundSelector : CSSCompoundSelector
@end
#pragma mark - Base Combinator
@interface CSSCompoundSelector ()
{
NSMutableArray *_selectors;
}
@property (nonatomic, strong, readonly) NSArray *selectors;
@end
@implementation CSSCompoundSelector
@synthesize selectors = _selectors;
+ (instancetype)andSelector:(NSArray *)selectors
{
return [[CSSAndCompoundSelector alloc] initWithSelectors:selectors];
}
+ (instancetype)orSelector:(NSArray *)selectors
{
return [[CSSOrCompoundSelector alloc] initWithSelectors:selectors];
}
- (instancetype)initWithSelectors:(NSArray *)selectors
{
self = [super init];
if (self) {
_selectors = [[NSMutableArray alloc] initWithArray:selectors];
}
return self;
}
- (void)addSelector:(CSSSelector *)selector
{
[_selectors addObject:selector];
}
@end
#pragma mark - And Compound Selector
@implementation CSSAndCompoundSelector
- (BOOL)acceptElement:(HTMLElement *)element
{
for (CSSSelector *selector in self.selectors) {
if (![selector acceptElement:element]) {
return NO;
}
}
return YES;
}
- (NSString *)debugDescription
{
NSArray *descriptions = [self.selectors valueForKey:@"debugDescription"];
return [descriptions componentsJoinedByString:@""];
}
@end
#pragma mark - Or Compound Selector
@implementation CSSOrCompoundSelector
- (BOOL)acceptElement:(HTMLElement *)element
{
for (CSSSelector *selector in self.selectors) {
if ([selector acceptElement:element]) {
return YES;
}
}
return NO;
}
- (NSString *)debugDescription
{
NSArray *descriptions = [self.selectors valueForKey:@"debugDescription"];
return [descriptions componentsJoinedByString:@","];
}
@end
+62
View File
@@ -0,0 +1,62 @@
//
// CSSInputStream.h
// HTMLKit
//
// Created by Iska on 07/06/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLInputStreamReader.h"
/**
The CSS Inpute Stream.
Extends the HTML Input Stream with methods relevant to CSS selectors tokenizing/parsing.
*/
@interface CSSInputStream : HTMLInputStreamReader
/**
Consumes leading whitespace characters.
*/
- (void)consumeWhitespace;
/**
Consumes a CSS identifier.
http://www.w3.org/TR/css-syntax-3/#consume-an-ident-like-token
http://www.w3.org/TR/css-syntax-3/#consume-a-string-token
http://www.w3.org/TR/css-syntax-3/#would-start-an-identifier
@returns A consumed identifier, `nil` if the stream doesn't start with a valid identifier.
*/
- (NSString *)consumeIdentifier;
/**
Consumes characters until the specified code-point is met.
@param endingCodePoint The code-point at which the input stream stops consuming.
@returns The consumed string, `nil` nothing was consumed.
*/
- (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint;
/**
Consumes an escaped code point.
http://www.w3.org/TR/css-syntax-3/#consume-an-escaped-code-point
http://www.w3.org/TR/css-syntax-3/#starts-with-a-valid-escape
@returns The value of the escaped code-point.
*/
- (UTF32Char)consumeEscapedCodePoint;
/**
Consumes a CSS selector combinator.
@returns The consumed combinator, `nil` if nothing was consumed.
*/
- (NSString *)consumeCombinator;
@end
+128
View File
@@ -0,0 +1,128 @@
//
// CSSInputStream.m
// HTMLKit
//
// Created by Iska on 07/06/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "CSSInputStream.h"
#import "CSSCodePoints.h"
@interface HTMLInputStreamReader ()
- (void)emitParseError:(NSString *)reason;
@end
@implementation CSSInputStream
- (void)consumeWhitespace
{
while (isWhitespace(self.nextInputCharacter)) {
[self consumeNextInputCharacter];
}
}
- (NSString *)consumeIdentifier
{
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
if (!isValidIdentifierStart([self inputCharacterPointAtOffset:0],
[self inputCharacterPointAtOffset:1],
[self inputCharacterPointAtOffset:2])) {
return nil;
}
while (YES) {
UTF32Char codePoint = [self consumeNextInputCharacter];
if (codePoint == EOF) {
break;
} else if (isName(codePoint)) {
AppendCodePoint(value, codePoint);
} else if (isValidEscape(codePoint, [self inputCharacterPointAtOffset:1])) {
UTF32Char escapedCodePoint = [self consumeEscapedCodePoint];
AppendCodePoint(value, escapedCodePoint);
} else {
[self reconsumeCurrentInputCharacter];
break;
}
}
return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil);
}
- (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint
{
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
while (YES) {
UTF32Char codePoint = [self consumeNextInputCharacter];
if (codePoint == endingCodePoint) {
break;
}
switch (codePoint) {
case EOF:
break;
case LINE_FEED:
[self emitParseError:@"New-line character (0x000A) in CSS attribute value"];
[self reconsumeCurrentInputCharacter];
break;
case REVERSE_SOLIDUS:
{
UTF32Char next = self.nextInputCharacter;
if (next == EOF) {
continue;
} else if (next == LINE_FEED) {
[self consumeNextInputCharacter];
} else {
UTF32Char escapedCodePoint = [self consumeNextInputCharacter];
AppendCodePoint(value, escapedCodePoint);
}
}
default:
AppendCodePoint(value, codePoint);
break;
}
}
return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil);
}
- (UTF32Char)consumeEscapedCodePoint
{
UTF32Char codePoint = [self consumeNextInputCharacter];
if (isHexDigit(codePoint)) {
CFMutableStringRef hexString = CFStringCreateMutable(kCFAllocatorDefault, 6);
AppendCodePoint(hexString, codePoint);
while (isHexDigit(self.nextInputCharacter) && CFStringGetLength(hexString) <= 6) {
UniChar codePoint = [self consumeNextInputCharacter];
CFStringAppendCharacters(hexString, &codePoint, 1);
}
if (isWhitespace(self.nextInputCharacter)) {
[self consumeNextInputCharacter];
}
NSScanner *scanner = [NSScanner scannerWithString:(__bridge NSString *)(hexString)];
UTF32Char number;
[scanner scanHexInt:&number];
return isValidEscapedCodePoint(number) ? number : REPLACEMENT_CHARACTER;
} else if (codePoint == EOF) {
return REPLACEMENT_CHARACTER;
}
return codePoint;
}
- (NSString *)consumeCombinator
{
NSString *combinator = [self consumeCharactersInString:@" >+~"];
combinator = [combinator stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
return combinator;
}
@end
+31
View File
@@ -0,0 +1,31 @@
//
// CSSNthExpression.h
// HTMLKit
//
// Created by Iska on 10/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CSSSelectors.h"
NS_ASSUME_NONNULL_BEGIN
/**
The Nth-Expression Parser.
Parses CSS nth-expressions, e.g. '-2n+3', 'odd', ...etc.
*/
@interface CSSNthExpressionParser : NSObject
/**
Parses a CSS nth-exrepssion string.
@param expression The expression string to parse.
@see CSSNthExpression
*/
+ (CSSNthExpression)parseExpression:(NSString *)expression;
@end
NS_ASSUME_NONNULL_END
+50
View File
@@ -0,0 +1,50 @@
//
// CSSNthExpression.m
// HTMLKit
//
// Created by Iska on 10/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSNthExpressionParser.h"
#import "CSSCodePoints.h"
#import "NSString+HTMLKit.h"
#import "NSCharacterSet+HTMLKit.h"
@implementation CSSNthExpressionParser
+ (CSSNthExpression)parseExpression:(NSString *)expression
{
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString *string = [expression.lowercaseString copy];
string = [[string stringByTrimmingCharactersInSet:whitespace] copy];
if ([string isEqualToStringIgnoringCase:@"odd"]) {
return CSSNthExpressionOdd;
} else if ([string isEqualToStringIgnoringCase:@"even"]) {
return CSSNthExpressionEven;
}
NSCharacterSet *set = [[NSCharacterSet CSSNthExpressionCharacterSet] invertedSet];
if ([string rangeOfCharacterFromSet:set].location != NSNotFound) {
return CSSNthExpressionMake(0, 0);
}
NSArray *parts = [string componentsSeparatedByString:@"n"];
if (parts.count == 1) {
NSInteger b = [parts[0] integerValue];
return CSSNthExpressionMake(0, b);
} else if (parts.count == 2) {
NSInteger a = [parts[0] integerValue];
if (a == 0) {
a = [parts[0] isEqualToString:@"-"] ? -1 : 1;
}
NSInteger b = [parts[1] integerValue];
return CSSNthExpressionMake(a, b);
} else {
return CSSNthExpressionMake(0, 0);
}
}
@end
+72
View File
@@ -0,0 +1,72 @@
//
// CSSNthExpressionSelector.h
// HTMLKit
//
// Created by Iska on 10/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Nth-Expression Selector.
*/
@interface CSSNthExpressionSelector : CSSSelector
/**
The pseudo-class name.
*/
@property (nonatomic, strong, readonly) NSString *className;
/**
The nth-expression.
@see CSSNthExpression
*/
@property (nonatomic, assign, readonly) CSSNthExpression expression;
/**
Initializes a new CSS nth-child selector, e.g. ':nth-child(2n+3)'
@param expression The nth-expression.
@returns Nth-Child selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthChildSelector:(CSSNthExpression)expression;
/**
Initializes a new CSS nth-last-child selector, e.g. ':nth-last-child(2n+3)'
@param expression The nth-expression.
@returns Nth-Last-Child selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthLastChildSelector:(CSSNthExpression)expression;
/**
Initializes a new CSS nth-of-type selector, e.g. ':nth-of-type(2n+3)'
@param expression The nth-expression.
@returns Nth-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthOfTypeSelector:(CSSNthExpression)expression;
/**
Initializes a new CSS nth-last-of-type selector, e.g. ':nth-last-of-type(2n+3)'
@param expression The nth-expression.
@returns Nth-Last-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthLastOfTypeSelector:(CSSNthExpression)expression;
@end
NS_ASSUME_NONNULL_END
+131
View File
@@ -0,0 +1,131 @@
//
// CSSNthExpressionSelector.m
// HTMLKit
//
// Created by Iska on 10/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSNthExpressionSelector.h"
#import "HTMLElement.h"
#import "HTMLNode+Private.h"
#pragma mark - Nth-Expression
const CSSNthExpression CSSNthExpressionOdd = (CSSNthExpression) {
.an = 2, .b = 1
};
const CSSNthExpression CSSNthExpressionEven = (CSSNthExpression) {
.an = 2, .b = 0
};
NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression)
{
if (expression.an == 0 && expression.b == 0) {
return @"invalid";
}
if (expression.an == 0) {
return [NSString stringWithFormat:@"%ld", expression.b];
}
if (expression.b == 0) {
return [NSString stringWithFormat:@"%ldn", expression.an];
}
return [NSString stringWithFormat:@"%ldn%+ld", expression.an, expression.b];
}
#pragma mark - Implementation
NSInteger computeIndex(NSEnumerator *enumerator, HTMLElement *element)
{
NSInteger index = 0;
for (HTMLNode *node in enumerator) {
if (node.nodeType != HTMLNodeElement) {
continue;
}
if ([node.asElement.tagName isEqualToString:element.tagName]) {
index++;
}
if (node == element) {
break;
}
}
return index;
}
@interface CSSNthExpressionSelector ()
{
NSString *_className;
CSSNthExpression _expression;
NSInteger (^ _computeIndex)(HTMLElement *);
}
@end
@implementation CSSNthExpressionSelector
@synthesize expression = _expression;
@synthesize className = _className;
+ (instancetype)nthChildSelector:(CSSNthExpression)expression
{
return [[self alloc] initWithClassName:@"nth-child" expression:expression block:^NSInteger(HTMLElement *element) {
return [element.parentElement indexOfChildElement:element] + 1;
}];
}
+ (instancetype)nthLastChildSelector:(CSSNthExpression)expression
{
return [[self alloc] initWithClassName:@"nth-last-child" expression:expression block:^NSInteger(HTMLElement *element) {
return element.parentElement.childElementsCount - [element.parentElement indexOfChildElement:element];
}];
}
+ (instancetype)nthOfTypeSelector:(CSSNthExpression)expression
{
return [[self alloc] initWithClassName:@"nth-of-type" expression:expression block:^NSInteger(HTMLElement *element) {
return computeIndex(element.parentElement.childNodes.array.objectEnumerator, element);
}];
}
+ (instancetype)nthLastOfTypeSelector:(CSSNthExpression)expression
{
return [[self alloc] initWithClassName:@"nth-last-of-type" expression:expression block:^NSInteger(HTMLElement *element) {
return computeIndex(element.parentElement.childNodes.array.reverseObjectEnumerator, element);
}];
}
- (instancetype)initWithClassName:(NSString *)className
expression:(CSSNthExpression)expression
block:(NSInteger (^)(HTMLElement *element))block
{
self = [super init];
if (self) {
_className = [className copy];
_expression = expression;
_computeIndex = [block copy];
}
return self;
}
- (BOOL)acceptElement:(HTMLElement *)element
{
NSInteger index = _computeIndex(element);
if (_expression.an == 0) {
return index == _expression.b;
} else {
NSInteger diff = (index - _expression.b);
return (diff * _expression.an >= 0) && (diff % _expression.an == 0);
}
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@":%@(%@)", self.className, NSStringFromNthExpression(self.expression)];
}
@end
+34
View File
@@ -0,0 +1,34 @@
//
// CSSPseudoClassSelector.h
// HTMLKit
//
// Created by Iska on 06/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
Base class for CSS Pseudo Class Selectors. This is just a simple named wrapper around another selector.
*/
@interface CSSPseudoClassSelector : CSSSelector
/**
The pseudo-class name.
*/
@property (nonatomic, strong, readonly) NSString *className;
/**
Initializes and return a new pseudo-class selector.
@param className The pseudo class name.
@param selector The underlying selector.
@returns A new instance of a pseudo-class selector.
*/
- (instancetype)initWithClassName:(NSString *)className selector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END
+46
View File
@@ -0,0 +1,46 @@
//
// CSSPseudoClassSelector.m
// HTMLKit
//
// Created by Iska on 06/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSPseudoClassSelector.h"
@interface CSSPseudoClassSelector ()
{
NSString *_className;
CSSSelector *_selector;
}
@end
@implementation CSSPseudoClassSelector
@synthesize className = _className;
- (instancetype)initWithClassName:(NSString *)className selector:(CSSSelector *)selector
{
self = [super init];
if (self) {
_className = [className copy];
_selector = selector;
}
return self;
}
-(BOOL)acceptElement:(HTMLElement *)element
{
return [_selector acceptElement:element];
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@":%@", self.className];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.debugDescription];
}
@end
+38
View File
@@ -0,0 +1,38 @@
//
// CSSPseudoFunctionSelector.h
// HTMLKit
//
// Created by Iska on 07/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Pseudo-Function Selector
*/
@interface CSSPseudoFunctionSelector : CSSSelector
/**
Initializes and returns a CSS nagation selector, e.g. ':not(div)'
@param selector The selector which should be negated.
@returns A new instance of the negation selector.
*/
+ (instancetype)notSelector:(CSSSelector *)selector;
/**
Initializes and returns a CSS has-descendant selector, e.g. 'div:has(p)'
@discussion 'div:has(p)' matches all <div> elements which have a descendant <p> element.
@param selector The selector matching a descendant element.
@returns A new instance of the has-descendant selector.
*/
+ (instancetype)hasSelector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END
+91
View File
@@ -0,0 +1,91 @@
//
// CSSPseudoFunctionSelector.m
// HTMLKit
//
// Created by Iska on 07/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSPseudoFunctionSelector.h"
#import "HTMLElement.h"
#import "HTMLNode+Private.h"
#pragma mark - Declarations
@interface CSSNotSelector : CSSPseudoFunctionSelector
@end
@interface CSSHasSelector : CSSPseudoFunctionSelector
@end
#pragma mark - Base Function Selector
@interface CSSPseudoFunctionSelector ()
{
CSSSelector *_selector;
}
@property (nonatomic, strong, readonly) CSSSelector *selector;
@end
@implementation CSSPseudoFunctionSelector
@synthesize selector = _selector;
+ (instancetype)notSelector:(CSSSelector *)selector
{
return [[CSSNotSelector alloc] initWithSelector:selector];
}
+ (instancetype)hasSelector:(CSSSelector *)selector
{
return [[CSSHasSelector alloc] initWithSelector:selector];
}
- (instancetype)initWithSelector:(CSSSelector *)selector
{
self = [super init];
if (self) {
_selector = selector;
}
return self;
}
@end
#pragma mark - Not Selector
@implementation CSSNotSelector
- (BOOL)acceptElement:(HTMLElement *)element
{
return ![self.selector acceptElement:element];
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@":not(%@)", self.selector.debugDescription];
}
@end
#pragma mark - Has Selector
@implementation CSSHasSelector
- (BOOL)acceptElement:(HTMLElement *)element
{
HTMLNodeIterator *iterator = [element nodeIteratorWithShowOptions:HTMLNodeFilterShowAll filter:nil];
for (HTMLNode *descendant in iterator) {
if (descendant.nodeType == HTMLNodeElement && [self.selector acceptElement:descendant.asElement]) {
return YES;
}
}
return NO;
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@":has(%@)", self.selector.debugDescription];
}
@end
+90
View File
@@ -0,0 +1,90 @@
//
// HTMLSelector.h
// HTMLKit
//
// Created by Iska on 02/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Attribute Selector Type
/**
Attribute selector type.
*/
typedef NS_ENUM(NSUInteger, CSSAttributeSelectorType)
{
/** Attribute exists: '[src]' */
CSSAttributeSelectorExists,
/** Attribute has exact value: '[title="HTMLKit"]' */
CSSAttributeSelectorExactMatch,
/** Attribute includes value: '[title~="foo"]' */
CSSAttributeSelectorIncludes,
/** Attribute's value begins with: '[title^="HTML"]' */
CSSAttributeSelectorBegins,
/** Attribute's value ends with: '[title$="Kit"]' */
CSSAttributeSelectorEnds,
/** Attribute's value ends with: '[title*="ML"]' */
CSSAttributeSelectorContains,
/** Attribute's value ends with: '[title|="en"]' */
CSSAttributeSelectorHyphen,
/** Attribute's value does not equal: '[title!="foo"]' */
CSSAttributeSelectorNot
};
#pragma mark - CSS Nth-Expression
/**
CSS Nth-Expression
*/
typedef struct CSSNthExpression
{
NSInteger an;
NSInteger b;
} CSSNthExpression;
NS_INLINE CSSNthExpression CSSNthExpressionMake(NSInteger an, NSInteger b) {
return (CSSNthExpression){ .an = an, .b = b };
}
extern const CSSNthExpression CSSNthExpressionOdd;
extern const CSSNthExpression CSSNthExpressionEven;
extern NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression);
#pragma mark - Base Selector Class
@class HTMLElement;
/**
Base class for all CSS Selector implementations
*/
@interface CSSSelector : NSObject
/**
Initializes and returns a new instance of CSS Selector.
@param string The selector string which will be parsed.
@returns A new instance of a parsed CSS Selector, `nil` if the string is not a valid selector string.
*/
+ (nullable instancetype)selectorWithString:(NSString *)stirng;
/**
Implementations should override this method to provide the selector-sprecific logic for matching elements.
@abstract Use one of the concrete subclasses.
*/
- (BOOL)acceptElement:(HTMLElement *)element;
@end
NS_ASSUME_NONNULL_END
+44
View File
@@ -0,0 +1,44 @@
//
// CSSSelector.m
// HTMLKit
//
// Created by Iska on 15/10/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CSSSelector.h"
#import "CSSSelectorParser.h"
@implementation CSSSelector
+ (instancetype)selectorWithString:(NSString *)string
{
NSError *error = nil;
CSSSelector *instance = [CSSSelectorParser parseSelector:string error:&error];
if (error) {
return nil;
}
return instance;
}
- (BOOL)acceptElement:(HTMLElement *)element
{
[self doesNotRecognizeSelector:_cmd];
return NO;
}
#pragma mark - Description
- (NSString *)debugDescription
{
[self doesNotRecognizeSelector:_cmd];
return @"";
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.debugDescription];
}
@end
+31
View File
@@ -0,0 +1,31 @@
//
// CSSSelectorBlock.h
// HTMLKit
//
// Created by Iska on 20/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
@class HTMLElement;
/**
A block-based CSS Selector implementation
*/
@interface CSSSelectorBlock : CSSSelector
/**
Initializes and returns a new block-based selector.
@param name The name of the selector.
@param block The block that should match desired elements.
@returns A new instance of the block-based selector.
*/
- (instancetype)initWithName:(NSString *)name block:(BOOL (^)(HTMLElement *))block;
@end
NS_ASSUME_NONNULL_END
+40
View File
@@ -0,0 +1,40 @@
//
// CSSSelectorBlock.m
// HTMLKit
//
// Created by Iska on 20/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelectorBlock.h"
@interface CSSSelectorBlock ()
{
NSString *_name;
BOOL (^ _acceptBlock)(HTMLElement *);
}
@end
@implementation CSSSelectorBlock
- (instancetype)initWithName:(NSString *)name block:(BOOL (^)(HTMLElement *))block
{
self = [super init];
if (self) {
_name = [name copy];
_acceptBlock = [block copy];
}
return self;
}
- (BOOL)acceptElement:(HTMLElement *)element
{
return _acceptBlock ? _acceptBlock(element) : NO;
}
- (NSString *)debugDescription
{
return _name;
}
@end
+36
View File
@@ -0,0 +1,36 @@
//
// CSSSelectorParser.h
// HTMLKit
//
// Created by Iska on 02/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class CSSSelector;
/**
The CSS Selectors Parser.
Parses CSS Level 3 Selectors:
http://www.w3.org/TR/css3-selectors/
*/
@interface CSSSelectorParser : NSObject
/**
Parses a CSS3 selector string.
@param string The CSS3 selector string.
@param error If an error occurs, upon return contains an `NSError` object that describes the problem.
@returns A parsed CSSSelector, `nil` if an error occurred.
@see CSSelector
*/
+ (CSSSelector *)parseSelector:(NSString *)string error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END
+426
View File
@@ -0,0 +1,426 @@
//
// CSSSelectorParser.m
// HTMLKit
//
// Created by Iska on 02/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelectorParser.h"
#import "CSSInputStream.h"
#import "CSSCodePoints.h"
#import "CSSSelectors.h"
#import "NSString+HTMLKit.h"
#import "NSCharacterSet+HTMLKit.h"
#import "CSSNthExpressionParser.h"
#import "CSSCompoundSelector.h"
#import "HTMLKitErrorDomain.h"
@interface CSSSelectorParser ()
{
NSString *_string;
CSSInputStream *_inputStream;
NSUInteger _location;
NSMutableArray *_selectors;
}
@end
@implementation CSSSelectorParser
+ (CSSSelector *)parseSelector:(NSString *)string error:(NSError * __autoreleasing *)error
{
CSSSelectorParser *parser = [[CSSSelectorParser alloc] initWithString:string];
CSSSelector *selector = [parser parse:error];
return selector;
}
#pragma mark - Init
- (instancetype)initWithString:(NSString *)string
{
self = [super init];
if (self) {
_string = [self preprocessInput:string];
_location = 0;
}
return self;
}
- (NSString *)preprocessInput:(NSString *)string
{
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
string = [string stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"];
string = [string stringByReplacingOccurrencesOfString:@"\r" withString:@"\n"];
string = [string stringByReplacingOccurrencesOfString:@"\f" withString:@"\n"];
string = [string stringByReplacingOccurrencesOfString:@"\0" withString:@"\uFFFD"];
return string;
}
#pragma mark - Errors
- (void)emitError:(NSError * __autoreleasing *)error reason:(NSString *)reason
{
[self emitError:error reason:reason location:_location + _inputStream.currentLocation];
}
- (void)emitError:(NSError * __autoreleasing *)error reason:(NSString *)reason location:(NSUInteger)location
{
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @"Error parsing selector",
NSLocalizedFailureReasonErrorKey: reason,
CSSSelectorStringKey: _string,
CSSSelectorErrorLocationKey: @(location)
};
if(error && *error == nil) {
*error = [NSError errorWithDomain:HTMLKitSelectorErrorDomain code:HTMLKitSelectorParseError userInfo:userInfo];
}
}
#pragma mark - Parsing
- (CSSSelector *)parse:(NSError * __autoreleasing *)error
{
if (_string.length == 0) {
[self emitError:error reason:@"Empty selector" location:0];
return nil;
}
NSArray *allSubSelectors = [_string componentsSeparatedByString:@","];
NSMutableArray *parsed = [NSMutableArray array];
for (NSString *subSelector in allSubSelectors) {
if ([subSelector isEqualToString:@""]) {
[self emitError:error reason:@"Empty selector" location:_location];
break;
}
CSSSelector *selector = [self parseSelector:subSelector error:error];
if (selector == nil) {
break;
}
[parsed addObject:selector];
_location += subSelector.length;
}
if (error && *error != nil) {
return nil;
}
if (parsed.count > 1) {
return anyOf(parsed);
}
return parsed.firstObject;
}
- (CSSSelector *)parseSelector:(NSString *)selectorString error:(NSError * __autoreleasing *)error
{
_inputStream = [[CSSInputStream alloc] initWithString:selectorString];
[_inputStream consumeWhitespace];
CSSSelector *result = nil;
while (YES) {
CSSSelector *selector = [self parseSequenceOfSimpleSelectors:error];
if (selector == nil) {
break;
}
result = result ? allOf(@[result, selector]) : selector;
UTF32Char next = _inputStream.nextInputCharacter;
if (isCombinator(next)) {
NSString *combinator = [_inputStream consumeCombinator];
if ([combinator isEqualToString:@""]) {
result = descendantOfElementSelector(result);
} else if ([combinator isEqualToString:@">"]) {
result = childOfElementSelector(result);
} else if ([combinator isEqualToString:@"+"]) {
result = adjacentSiblingSelector(result);
} else if ([combinator isEqualToString:@"~"]) {
result = generalSiblingSelector(result);
}
}
}
return result;
}
- (CSSSelector *)parseSequenceOfSimpleSelectors:(NSError * __autoreleasing *)error
{
NSMutableArray *selectors = [NSMutableArray array];
CSSSelector *typeSelector = [self parseTypeSelector:error];
if (typeSelector != nil) {
[selectors addObject:typeSelector];
}
while (YES) {
UTF32Char next = _inputStream.nextInputCharacter;
if (next == EOF || isCombinator(next)) {
break;
}
CSSSelector *simpleSelector = [self parseSimpleSelector:error];
if (simpleSelector == nil) {
return nil;
}
[selectors addObject:simpleSelector];
}
if (selectors.count > 1) {
return allOf(selectors);
}
return selectors.firstObject;
}
- (CSSSelector *)parseTypeSelector:(NSError * __autoreleasing *)error
{
NSString *identifier = [_inputStream consumeIdentifier];
if (identifier != nil) {
return typeSelector(identifier);
}
if ([_inputStream consumeCharacter:ASTERIX]) {
return universalSelector();
}
return nil;
}
- (CSSSelector *)parseSimpleSelector:(NSError * __autoreleasing *)error
{
CSSSelector *typeSelector = [self parseTypeSelector:error];
if (typeSelector != nil) {
return typeSelector;
}
UTF32Char codePoint = [_inputStream consumeNextInputCharacter];
switch (codePoint) {
case NUMBER_SIGN:
{
NSString *elementId = [_inputStream consumeIdentifier];
if (elementId == nil) {
[self emitError:error reason:@"Invalid character"];
return nil;
}
return idSelector(elementId);
}
case FULL_STOP:
{
NSString *className = [_inputStream consumeIdentifier];
if (className == nil) {
[self emitError:error reason:@"Invalid character"];
return nil;
}
return classSelector(className);
}
case LEFT_SQUARE_BRACKET:
{
return [self parseAttributeSelector:error];
}
case COLON:
{
return [self parsePseudoSelector:error];
}
default:
{
[self emitError:error reason:@"Invalid character"];
return nil;
}
}
}
- (CSSSelector *)parseAttributeSelector:(NSError * __autoreleasing *)error
{
NSString *attribute = [_inputStream consumeIdentifier];
if (attribute == nil) {
[self emitError:error reason:@"Invalid character" location:_location + _inputStream.currentLocation + 1];
return nil;
}
[_inputStream consumeWhitespace];
CSSAttributeSelectorType type = CSSAttributeSelectorExists;
NSString *operator = [_inputStream consumeCharactersInString:@"=~|^$*!"];
if ([operator isEqualToString:@"="]) {
type = CSSAttributeSelectorExactMatch;
} else if ([operator isEqualToString:@"~="]) {
type = CSSAttributeSelectorIncludes;
} else if ([operator isEqualToString:@"|="]) {
type = CSSAttributeSelectorHyphen;
} else if ([operator isEqualToString:@"^="]) {
type = CSSAttributeSelectorBegins;
} else if ([operator isEqualToString:@"$="]) {
type = CSSAttributeSelectorEnds;
} else if ([operator isEqualToString:@"*="]) {
type = CSSAttributeSelectorContains;
} else if ([operator isEqualToString:@"!="]) {
type = CSSAttributeSelectorNot;
}
NSString *value = nil;
[_inputStream consumeWhitespace];
UTF32Char next = _inputStream.nextInputCharacter;
if (isQuote(next)) {
UTF32Char quote = [_inputStream consumeNextInputCharacter];
value = [_inputStream consumeStringWithEndingCodePoint:quote];
} else {
value = [_inputStream consumeIdentifier];
}
[_inputStream consumeWhitespace];
// Consume RIGHT_SQUARE_BRACKET
if (![_inputStream consumeCharacter:RIGHT_SQUARE_BRACKET]) {
[self emitError:error reason:@"Expected closing right square bracket ']'"];
}
if (type == CSSAttributeSelectorExists) {
return hasAttributeSelector(attribute);
}
return attributeSelector(type, attribute, value);
}
- (CSSSelector *)parsePseudoSelector:(NSError * __autoreleasing *)error
{
NSString *pseudoClass = [_inputStream consumeIdentifier];
if ([pseudoClass hasPrefix:@"nth"]) {
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
[self emitError:error reason:@"Expected opening left parenthesis '('"];
}
NSString *functionExpression = [_inputStream consumeCharactersUpToString:@")"];
CSSNthExpression expression = [CSSNthExpressionParser parseExpression:functionExpression];
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
}
if ([pseudoClass isEqualToString:@"nth-child"]) {
return nthChildSelector(expression);
} else if ([pseudoClass isEqualToString:@"nth-last-child"]) {
return nthLastChildSelector(expression);
} else if ([pseudoClass isEqualToString:@"nth-of-type"]) {
return nthOfTypeSelector(expression);
} else if ([pseudoClass isEqualToString:@"nth-last-of-type"]) {
return nthLastOfTypeSelector(expression);
}
} else if ([pseudoClass isEqualToString:@"not"]) {
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
[self emitError:error reason:@"Expected opening left parenthesis '('"];
}
CSSSelector *subSelector = [self parseSimpleSelector:error];
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
}
return not(subSelector);
} else if ([pseudoClass isEqualToAny:@"lt", @"gt", @"eq", nil]) {
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
[self emitError:error reason:@"Expected opening left parenthesis '('"];
}
NSDecimal decimal;
if (![_inputStream consumeDecimalNumber:&decimal]) {
[self emitError:error reason:@"Expected a decimal number"];
}
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
}
NSDecimalNumber *number = [[NSDecimalNumber alloc] initWithDecimal:decimal];
if ([pseudoClass isEqualToString:@"lt"]) {
return ltSelector(number.integerValue);
} else if ([pseudoClass isEqualToString:@"gt"]) {
return gtSelector(number.integerValue);
} else if ([pseudoClass isEqualToString:@"eq"]) {
return eqSelector(number.integerValue);
}
} else {
if ([pseudoClass isEqualToString:@"even"]) {
return evenSlector();
} else if ([pseudoClass isEqualToString:@"odd"]) {
return oddSelector();
} else if ([pseudoClass isEqualToString:@"first-child"]) {
return firstChildSelector();
} else if ([pseudoClass isEqualToString:@"last-child"]) {
return lastChildSelector();
} else if ([pseudoClass isEqualToString:@"first-of-type"]) {
return firstOfTypeSelector();
} else if ([pseudoClass isEqualToString:@"last-of-type"]) {
return lastOfTypeSelector();
} else if ([pseudoClass isEqualToString:@"only-child"]) {
return onlyChildSelector();
} else if ([pseudoClass isEqualToString:@"only-of-type"]) {
return onlyOfTypeSelector();
} else if ([pseudoClass isEqualToString:@"root"]) {
return rootSelector();
} else if ([pseudoClass isEqualToString:@"empty"]) {
return emptySelector();
} else if ([pseudoClass isEqualToString:@"link"]) {
return linkSelector();
} else if ([pseudoClass isEqualToString:@"enabled"]) {
return enabledSelector();
} else if ([pseudoClass isEqualToString:@"disabled"]) {
return disabledSelector();
} else if ([pseudoClass isEqualToString:@"checked"]) {
return checkedSelector();
}
else if ([pseudoClass isEqualToString:@"button"]) {
return buttonSelector();
} else if ([pseudoClass isEqualToString:@"checkbox"]) {
return checkboxSelector();
} else if ([pseudoClass isEqualToString:@"file"]) {
return fileSelector();
} else if ([pseudoClass isEqualToString:@"header"]) {
return headerSelector();
} else if ([pseudoClass isEqualToString:@"image"]) {
return imageSelector();
} else if ([pseudoClass isEqualToString:@"optional"]) {
return optionalSelector();
} else if ([pseudoClass isEqualToString:@"parent"]) {
return parentSelector();
} else if ([pseudoClass isEqualToString:@"password"]) {
return passwordSelector();
} else if ([pseudoClass isEqualToString:@"radio"]) {
return radioSelector();
} else if ([pseudoClass isEqualToString:@"reset"]) {
return resetSelector();
} else if ([pseudoClass isEqualToString:@"submit"]) {
return submitSelector();
} else if ([pseudoClass isEqualToString:@"text"]) {
return textSelector();
} else if ([pseudoClass isEqualToString:@"required"]) {
return requiredSelector();
} else if ([pseudoClass isEqualToString:@"reset"]) {
return resetSelector();
}
}
NSString *reason = [NSString stringWithFormat:@"Unknown pseudo class: %@", pseudoClass];
[self emitError:error reason:reason];
return nil;
}
@end
+277
View File
@@ -0,0 +1,277 @@
//
// CSSSelectors.h
// HTMLKit
//
// Created by Iska on 14/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
#import "CSSTypeSelector.h"
#import "CSSAttributeSelector.h"
#import "CSSPseudoClassSelector.h"
#import "CSSPseudoFunctionSelector.h"
#import "CSSNthExpressionSelector.h"
#import "CSSCombinatorSelector.h"
#import "CSSCompoundSelector.h"
#import "CSSSelectorBlock.h"
#import "CSSStructuralPseudoSelectors.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Type Selectors
/**
Universal CSS selector: '*'
@returns The universal CSS selector.
*/
extern CSSSelector * universalSelector();
/**
CSS type selector, e.g. 'div', 'p', ...etc.
@param type The element type.
@returns Type selector for the specified type.
*/
extern CSSSelector * typeSelector(NSString *type);
#pragma mark - Atribute Selectors
/**
CSS id selector, e.g. '#someId'
@param elementId The element id.
@returns Id selector for the specified element id.
*/
extern CSSSelector * idSelector(NSString *elementId);
/**
CSS class selector, e.g. '.someClass'
@param className The class name.
@returns Class selector for the specified class name.
*/
extern CSSSelector * classSelector(NSString *className);
/**
CSS has-attribute selector, e.g. '[href]'
@param attribute The attribute.
@returns Has-Attribute selector for the specified attribute.
*/
extern CSSSelector * hasAttributeSelector(NSString *attribute);
/**
CSS attribute selector, e.g. '[src*="html"]', '[class^="top"]', '[title&="HTML"]', ...etc.
@param type The attribute selector type.
@param attribute The attribute.
@param value The value of the attribute.
@returns Attribute selector.
@see CSSAttributeSelectorType
*/
extern CSSSelector * attributeSelector(CSSAttributeSelectorType type,
NSString *attribute,
NSString *value);
#pragma mark - Nth-Expression Selectors
/**
CSS nth-child selector, e.g. ':nth-child(2n+3)'
@param expression The nth-expression.
@returns Nth-Child selector for the specified expression.
@see CSSNthExpression
*/
extern CSSSelector * nthChildSelector(CSSNthExpression expression);
/**
CSS nth-last-child selector, e.g. ':nth-last-child(2n+3)'
@param expression The nth-expression.
@returns Nth-Last-Child selector for the specified expression.
@see CSSNthExpression
*/
extern CSSSelector * nthLastChildSelector(CSSNthExpression expression);
/**
CSS nth-of-type selector, e.g. ':nth-of-type(2n+3)'
@param expression The nth-expression.
@returns Nth-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
extern CSSSelector * nthOfTypeSelector(CSSNthExpression expression);
/**
CSS nth-last-of-type selector, e.g. ':nth-last-of-type(2n+3)'
@param expression The nth-expression.
@returns Nth-Last-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
extern CSSSelector * nthLastOfTypeSelector(CSSNthExpression expression);
/**
CSS odd-child selector: ':nth-child(odd)'
This is analogous to ':nth-child(2n+1)'
@returns Odd-Child selector.
*/
extern CSSSelector * oddSelector();
/**
CSS even-child selector: ':nth-child(even)'
This is analogous to ':nth-child(2n)'
@returns Even-Child selector.
*/
extern CSSSelector * evenSlector();
/**
CSS first-child selector: ':nth-child(1)'
@returns First-Child selector.
*/
extern CSSSelector * firstChildSelector();
/**
CSS first-child selector: ':nth-last-child(1)'
@returns First-Child selector.
*/
extern CSSSelector * lastChildSelector();
/**
CSS first-of-type selector: ':nth-first-of-type(1)'
@returns First-Of-Type selector.
*/
extern CSSSelector * firstOfTypeSelector();
/**
CSS last-of-type selector: ':nth-last-of-type(1)'
@returns Last-Of-Type selector.
*/
extern CSSSelector * lastOfTypeSelector();
/**
CSS only-child selector: ':first-child:last-child'
@returns Only-Child selector.
*/
extern CSSSelector * onlyChildSelector();
/**
CSS only-of-type selector: ':first-of-type:last-of-type'
@returns Only-Of-Type selector.
*/
extern CSSSelector * onlyOfTypeSelector();
#pragma mark - Combinators
/**
CSS child-of-element selector, e.g. 'div > p'
@param selector The selector matching the parent element.
@returns A child of element selector.
*/
extern CSSSelector * childOfElementSelector(CSSSelector *selector);
/**
CSS descendant-of-element selector, e.g. 'div p'
@param selector The selector matching the ancestor element.
@returns A descendant of element selector.
*/
extern CSSSelector * descendantOfElementSelector(CSSSelector *selector);
/**
CSS adjacent sibling selector, e.g. 'p + a'
@param selector The selector matching the adjacent sibling element.
@returns A adjacent sibling selector.
*/
extern CSSSelector * adjacentSiblingSelector(CSSSelector *selector);
/**
CSS general sibling selector, e.g. 'p ~ a'
@param selector The selector matching the general sibling element.
@returns A general sibling selector.
*/
extern CSSSelector * generalSiblingSelector(CSSSelector *selector);
#pragma mark - Pseudo Functions
/**
CSS nagation selector: ':not(div)'
@param selector The selector which should be negated.
@returns A negation selector.
*/
extern CSSSelector * not(CSSSelector *selector);
/**
CSS has-descendant selector, e.g. 'div:has(p)'
@discussion 'div:has(p)' matches all <div> elements which have a descendant <p> element.
@param selector The selector matching a descendant element.
@returns A has-descendant selector.
*/
extern CSSSelector * has(CSSSelector *selector);
#pragma mark - Compound Selectors
/**
A compound selector matching only elements that match all of the specified selectors.
@param selectors The selectors list.
@returns All-Of selector.
*/
extern CSSSelector * allOf(NSArray<CSSSelector *> *selectors);
/**
A compound selector matching all elements that match at least one of the specified selectors.
@param selectors The selectors list.
@returns Any-Of selector.
*/
extern CSSSelector * anyOf(NSArray<CSSSelector *> *selectors);
#pragma mark - Pseudo
/**
Creates a new named-pseudo selector.
@discussion The name specified when creating a selector is prefixed with colon.
@param name The name of the selector.
@param selector The underlying selector.
@returns A named-pseudo selector.
*/
extern CSSSelector * namedPseudoSelector(NSString *name, CSSSelector *selector);
#pragma mark - Block
/**
Creates a new named selector with a specified block.
@param name The name of the selector.
@param acceptBlock The block which provides the implementation for the accept-element logic.
@returns A named-block selector.
*/
extern CSSSelector * namedBlockSelector(NSString *name, BOOL (^ acceptBlock)(HTMLElement *element));
NS_ASSUME_NONNULL_END
+177
View File
@@ -0,0 +1,177 @@
//
// CSSSelectors.m
// HTMLKit
//
// Created by Iska on 19/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelectors.h"
#import "CSSTypeSelector.h"
#import "CSSAttributeSelector.h"
#import "CSSPseudoClassSelector.h"
#import "CSSPseudoFunctionSelector.h"
#import "CSSNthExpressionSelector.h"
#import "CSSCombinatorSelector.h"
#import "CSSCompoundSelector.h"
#import "CSSSelectorBlock.h"
#pragma mark - Type Selectors
CSSSelector * universalSelector()
{
return [CSSTypeSelector universalSelector];
}
CSSSelector * typeSelector(NSString *type)
{
return [[CSSTypeSelector alloc] initWithType:type];
}
#pragma mark - Atribute Selectors
CSSSelector * idSelector(NSString *elementId)
{
return [CSSAttributeSelector idSelector:elementId];
}
CSSSelector * classSelector(NSString *className)
{
return [CSSAttributeSelector classSelector:className];
}
CSSSelector * hasAttributeSelector(NSString *attribute)
{
return [CSSAttributeSelector hasAttributeSelector:attribute];
}
CSSSelector * attributeSelector(CSSAttributeSelectorType type,
NSString *attribute,
NSString *value)
{
return [[CSSAttributeSelector alloc] initWithType:type attributeName:attribute attrbiuteValue:value];
}
#pragma mark - Nth-Expression Selectors
CSSSelector * nthChildSelector(CSSNthExpression expression)
{
return [CSSNthExpressionSelector nthChildSelector:expression];
}
CSSSelector * nthLastChildSelector(CSSNthExpression expression)
{
return [CSSNthExpressionSelector nthLastChildSelector:expression];
}
CSSSelector * nthOfTypeSelector(CSSNthExpression expression)
{
return [CSSNthExpressionSelector nthOfTypeSelector:expression];
}
CSSSelector * nthLastOfTypeSelector(CSSNthExpression expression)
{
return [CSSNthExpressionSelector nthLastOfTypeSelector:expression];
}
#pragma mark - Nth-Expression Shorthand
CSSSelector * oddSelector()
{
return namedPseudoSelector(@"odd", nthChildSelector(CSSNthExpressionOdd));
}
CSSSelector * evenSlector()
{
return namedPseudoSelector(@"even", nthChildSelector(CSSNthExpressionEven));
}
CSSSelector * firstChildSelector()
{
return namedPseudoSelector(@"first-child", nthChildSelector(CSSNthExpressionMake(0, 1)));
}
CSSSelector * lastChildSelector()
{
return namedPseudoSelector(@"last-child", nthLastChildSelector(CSSNthExpressionMake(0, 1)));
}
CSSSelector * firstOfTypeSelector()
{
return namedPseudoSelector(@"first-of-type", nthOfTypeSelector(CSSNthExpressionMake(0, 1)));
}
CSSSelector * lastOfTypeSelector()
{
return namedPseudoSelector(@"last-of-type", nthLastOfTypeSelector(CSSNthExpressionMake(0, 1)));
}
CSSSelector * onlyChildSelector()
{
return namedPseudoSelector(@"only-child", allOf(@[firstChildSelector(), lastChildSelector()]));
}
CSSSelector * onlyOfTypeSelector()
{
return namedPseudoSelector(@"only-of-type", allOf(@[firstOfTypeSelector(), lastOfTypeSelector()]));
}
#pragma mark - Combinators
CSSSelector * childOfElementSelector(CSSSelector *selector)
{
return [CSSCombinatorSelector childOfElementCombinator:selector];
}
CSSSelector * descendantOfElementSelector(CSSSelector *selector)
{
return [CSSCombinatorSelector descendantOfElementCombinator:selector];
}
CSSSelector * adjacentSiblingSelector(CSSSelector *selector)
{
return [CSSCombinatorSelector adjacentSiblingCombinator:selector];
}
CSSSelector * generalSiblingSelector(CSSSelector *selector)
{
return [CSSCombinatorSelector generalSiblingCombinator:selector];
}
#pragma mark - Pseudo Functions
CSSSelector * not(CSSSelector *selector)
{
return [CSSPseudoFunctionSelector notSelector:selector];
}
CSSSelector * has(CSSSelector *selector)
{
return [CSSPseudoFunctionSelector hasSelector:selector];
}
#pragma mark - Compound Selectors
CSSSelector * allOf( NSArray<CSSSelector *> * selectors)
{
return [CSSCompoundSelector andSelector:selectors];
}
CSSSelector * anyOf( NSArray<CSSSelector *> * selectors)
{
return [CSSCompoundSelector orSelector:selectors];
}
#pragma mark - Pseudo
CSSSelector * namedPseudoSelector(NSString *name, CSSSelector *selector)
{
return [[CSSPseudoClassSelector alloc] initWithClassName:name selector:selector];
}
#pragma mark - Block
CSSSelector * namedBlockSelector(NSString *name, BOOL (^ acceptBlock)(HTMLElement *element))
{
return [[CSSSelectorBlock alloc] initWithName:name block:acceptBlock];
}
+145
View File
@@ -0,0 +1,145 @@
//
// CSSStructuralPseudoSelector.h
// HTMLKit
//
// Created by Iska on 11/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
@class CSSSelector;
NS_ASSUME_NONNULL_BEGIN
/**
@returns Root element selector: ':root'
*/
extern CSSSelector * rootSelector();
/**
@returns Empy element selector: ':empty'
*/
extern CSSSelector * emptySelector();
/**
@returns A parent element selector: ':parent'
*/
extern CSSSelector * parentSelector();
/**
@returns A button element selector: ':button'
*/
extern CSSSelector * buttonSelector();
/**
@returns A checkbox element selector: ':checkbox'
*/
extern CSSSelector * checkboxSelector();
/**
@returns A file element selector: ':file'
*/
extern CSSSelector * fileSelector();
/**
@returns A header element selector: ':header'
*/
extern CSSSelector * headerSelector();
/**
@returns An image element selector: ':image'
*/
extern CSSSelector * imageSelector();
/**
@returns A parent element selector: ':parent'
*/
extern CSSSelector * inputSelector();
/**
@returns A link element selector: ':link'
*/
extern CSSSelector * linkSelector();
/**
@returns A password element selector: ':password'
*/
extern CSSSelector * passwordSelector();
/**
@returns A radio element selector: ':radio'
*/
extern CSSSelector * radioSelector();
/**
@returns A reset element selector: ':reset'
*/
extern CSSSelector * resetSelector();
/**
@returns A submit element selector: ':submit'
*/
extern CSSSelector * submitSelector();
/**
@returns A text element selector: ':text'
*/
extern CSSSelector * textSelector();
/**
@returns An enabled element selector: ':enabled'
*/
extern CSSSelector * enabledSelector();
/**
@returns A disabled element selector: ':disabled'
*/
extern CSSSelector * disabledSelector();
/**
@returns A checked element selector: ':checked'
*/
extern CSSSelector * checkedSelector();
/**
@returns An optional element selector: ':optional'
*/
extern CSSSelector * optionalSelector();
/**
@returns A required element selector: ':required'
*/
extern CSSSelector * requiredSelector();
/**
Less-than selector, e.g. 'lt(2)'
Selects all elements at an index less than the specified index. A negative index counts backwards from the last element.
@param index The zero-based index of the element to match.
@returns A Less-Than selector.
*/
extern CSSSelector * ltSelector(NSInteger index);
/**
Greater-than selector, e.g. 'gt(2)'
Selects all elements at an index greater than the specified index. A negative index counts backwards from the
last element.
@param index The zero-based index of the element to match.
@returns A Greater-Than selector.
*/
extern CSSSelector * gtSelector(NSInteger index);
/**
Equal selector, e.g. 'eq(3)'
Selects the element at the specified index. A negative index counts backwards from the last element.
@param index The zero-based index of the element to match.
@returns An Equal selector.
*/
extern CSSSelector * eqSelector(NSInteger index);
NS_ASSUME_NONNULL_END
+313
View File
@@ -0,0 +1,313 @@
//
// CSSStructuralPseudoSelector.m
// HTMLKit
//
// Created by Iska on 11/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSStructuralPseudoSelectors.h"
#import "CSSSelectors.h"
#import "HTMLElement.h"
#import "NSString+HTMLKit.h"
#pragma mark - Elements
CSSSelector * rootSelector()
{
return namedBlockSelector(@":root", ^BOOL(HTMLElement * element) {
return element.parentElement == nil;
});
}
CSSSelector * emptySelector()
{
return namedBlockSelector(@":empty", ^BOOL(HTMLElement * element) {
for (HTMLNode *child in element.childNodes) {
if (child.nodeType == HTMLNodeElement) {
return NO;
} else if (child.nodeType == HTMLNodeText && child.textContent.length > 0) {
return NO;
}
}
return YES;
});
}
CSSSelector * parentSelector()
{
return namedBlockSelector(@":parent", ^BOOL(HTMLElement * element) {
return element.childNodesCount > 0;
});
}
CSSSelector * buttonSelector()
{
return namedBlockSelector(@":button", ^BOOL(HTMLElement * _Nonnull element) {
if ([element.tagName isEqualToString:@"button"]) {
return YES;
}
if ([element.tagName isEqualToString:@"input"] && [element[@"type"] isEqualToString:@"button"]) {
return YES;
}
return NO;
});
}
CSSSelector * checkboxSelector()
{
return namedBlockSelector(@":checkbox", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"checkbox"]) {
return YES;
}
return NO;
});
}
CSSSelector * fileSelector()
{
return namedBlockSelector(@":file", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"file"]) {
return YES;
}
return NO;
});
}
CSSSelector * headerSelector()
{
return namedBlockSelector(@":header", ^BOOL(HTMLElement * _Nonnull element) {
if ([element.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
return YES;
}
return NO;
});
}
CSSSelector * imageSelector()
{
return namedBlockSelector(@":image", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"image"]) {
return YES;
}
return NO;
});
}
CSSSelector * inputSelector()
{
return namedBlockSelector(@":input", ^BOOL(HTMLElement * _Nonnull element) {
if ([element.tagName isEqualToAny:@"button", @"input", @"select", @"textarea", nil]) {
return YES;
}
return NO;
});
}
CSSSelector * linkSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-link
return namedBlockSelector(@":link", ^BOOL(HTMLElement * element) {
if ([element hasAttribute:@"href"]) {
return [element.tagName isEqualToAny:@"a", @"area", @"link", nil];
}
return NO;
});
}
CSSSelector * passwordSelector()
{
return namedBlockSelector(@":password", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"password"]) {
return YES;
}
return NO;
});
}
CSSSelector * radioSelector()
{
return namedBlockSelector(@":radio", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"radio"]) {
return YES;
}
return NO;
});
}
CSSSelector * resetSelector()
{
return namedBlockSelector(@":reset", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"reset"]) {
return YES;
}
return NO;
});
}
CSSSelector * submitSelector()
{
return namedBlockSelector(@":submit", ^BOOL(HTMLElement * _Nonnull element) {
if ([element.tagName isEqualToString:@"input"] && [element[@"type"] isEqualToString:@"submit"]) {
return YES;
}
if ([element.tagName isEqualToString:@"button"] && [element[@"type"] isEqualToString:@"submit"]) {
return YES;
}
return NO;
});
}
CSSSelector * textSelector()
{
return namedBlockSelector(@":text", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"text"]) {
return YES;
}
return NO;
});
}
#pragma mark - State
CSSSelector * enabledSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
CSSSelector *candiate = anyOf(@[
typeSelector(@"button"),
typeSelector(@"input"),
typeSelector(@"select"),
typeSelector(@"textarea"),
typeSelector(@"optgroup"),
typeSelector(@"option"),
typeSelector(@"menuitem"),
typeSelector(@"fieldset"),
]);
return namedPseudoSelector(@"enabled", allOf(@[candiate, not(disabledSelector())]));
}
CSSSelector * disabledSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
CSSSelector *disabledAttribute = hasAttributeSelector(@"disabled");
// https://html.spec.whatwg.org/multipage/forms.html#concept-fieldset-disabled
CSSSelector *disabledFieldset = allOf(@[typeSelector(@"fieldset"), disabledAttribute]);
CSSSelector *firstLegend = allOf(@[typeSelector(@"legend"), firstOfTypeSelector()]);
CSSSelector *firstLegendDecendantDisabledFieldSet = allOf(@[firstLegend, descendantOfElementSelector(disabledFieldset)]);
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
CSSSelector *disabledForm = anyOf(@[
anyOf(@[
allOf(@[typeSelector(@"button"), disabledAttribute]),
allOf(@[typeSelector(@"input"), disabledAttribute]),
allOf(@[typeSelector(@"select"), disabledAttribute]),
allOf(@[typeSelector(@"textarea"), disabledAttribute])
]),
allOf(@[
descendantOfElementSelector(disabledFieldset),
not(firstLegendDecendantDisabledFieldSet)
])
]);
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
CSSSelector *disabledMenuItem = allOf(@[typeSelector(@"menuitem"), disabledAttribute]);
CSSSelector *disabledOptgroup = allOf(@[typeSelector(@"optgroup"), disabledAttribute]);
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
CSSSelector *disabledOption = allOf(@[
typeSelector(@"option"),
anyOf(@[
disabledAttribute,
descendantOfElementSelector(disabledOptgroup)])
]);
return namedPseudoSelector(@"disabled",
anyOf(@[disabledOption, disabledOptgroup, disabledMenuItem, disabledForm, disabledFieldset]));
}
CSSSelector * checkedSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-checked
CSSSelector *candidate = anyOf(@[
typeSelector(@"input"),
typeSelector(@"option"),
typeSelector(@"menutitem")
]);
CSSSelector *hasAttribute = anyOf(@[
hasAttributeSelector(@"checked"),
hasAttributeSelector(@"selected")
]);
return namedPseudoSelector(@"checked", allOf(@[candidate, hasAttribute]));
}
CSSSelector * optionalSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-optional
CSSSelector *candidate = anyOf(@[
typeSelector(@"input"),
typeSelector(@"select"),
typeSelector(@"textarea")
]);
CSSSelector *noAttribute = not(hasAttributeSelector(@"required"));
return namedPseudoSelector(@"optional", allOf(@[candidate, noAttribute]));
}
CSSSelector * requiredSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-required
// https://html.spec.whatwg.org/multipage/forms.html#concept-input-required
CSSSelector *candidate = anyOf(@[
typeSelector(@"input"),
typeSelector(@"select"),
typeSelector(@"textarea")
]);
CSSSelector *hasAttribute = hasAttributeSelector(@"required");
return namedPseudoSelector(@"required", allOf(@[candidate, hasAttribute]));
}
#pragma mark - Positional
CSSSelector * ltSelector(NSInteger index)
{
NSString *name = [NSString stringWithFormat:@":lt(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
if (index > 0) {
return elementIndex < index;
} else {
return elementIndex < element.parentElement.childNodesCount - index - 1;
}
});
}
CSSSelector * gtSelector(NSInteger index)
{
NSString *name = [NSString stringWithFormat:@":gt(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
if (index > 0) {
return elementIndex > index;
} else {
return elementIndex > element.parentElement.childNodesCount - index - 1;
}
});
}
CSSSelector * eqSelector(NSInteger index)
{
NSString *name = [NSString stringWithFormat:@":eq(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
if (index > 0) {
return elementIndex == index;
} else {
return elementIndex == element.parentElement.childNodesCount - index - 1;
}
});
}
+41
View File
@@ -0,0 +1,41 @@
//
// CSSTypeSelector.h
// HTMLKit
//
// Created by Iska on 13/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Type Selector.
*/
@interface CSSTypeSelector : CSSSelector
/**
The type of elements being matched.
*/
@property (nonatomic, strong, readonly) NSString *type;
/**
Returns the universal selector.
@returns A new instance of a universal selector that matches all elements.
*/
+ (instancetype)universalSelector;
/**
Initializes a new selector for the specified type.
@param type The type of elements that should be matched.
@returns A new instance of a type selector.
*/
- (instancetype)initWithType:(NSString *)type;
@end
NS_ASSUME_NONNULL_END
+50
View File
@@ -0,0 +1,50 @@
//
// CSSTypeSelector.m
// HTMLKit
//
// Created by Iska on 13/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "CSSTypeSelector.h"
#import "HTMLElement.h"
#import "NSString+HTMLKit.h"
@interface CSSTypeSelector ()
{
NSString *_type;
}
@end
@implementation CSSTypeSelector
+ (instancetype)universalSelector
{
return [[self alloc] initWithType:@"*"];
}
- (instancetype)initWithType:(NSString *)type
{
self = [super init];
if (self) {
_type = [type copy];
}
return self;
}
- (BOOL)acceptElement:(HTMLElement *)element
{
if ([_type isEqualToString:@"*"] || [_type isEqualToStringIgnoringCase:element.tagName]) {
return YES;
}
return NO;
}
#pragma mark - Description
- (NSString *)debugDescription
{
return self.type;
}
@end
+54
View File
@@ -6,22 +6,76 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
/**
HTML Character Token
*/
@interface HTMLCharacterToken : HTMLToken
/** @brief The characters in this token. */
@property (nonatomic, copy) NSString *characters;
/**
Initializes a new character token.
@param string The string with which to initialize the token.
@returns A new instance of a character token.
*/
- (instancetype)initWithString:(NSString *)string;
/**
Appends the given string to this token.
@param string The string to append.
*/
- (void)appendString:(NSString *)string;
/**
Checks whether this token is a whitespace character token.
@discussion HTML whitespace characters are: CHARACTER TABULATION U+0009, LINE FEED U+000A, FORM FEED U+000C,
CARRIAGE RETURN U+000D, and SPACE U+0020
@returns `YES` if this token contains only whitespace characters, `NO` otherwise.
*/
- (BOOL)isWhitespaceToken;
/**
Checks whether this token is empty.
@returns `YES` if this token is empty, `NO` otherwise.
*/
- (BOOL)isEmpty;
/**
Retains all leading whitespace characters in this token.
*/
- (void)retainLeadingWhitespace;
/**
Trims all leading whitespace characters in this token.
*/
- (void)trimLeadingWhitespace;
/**
Trims the characters in this token from a given index
@param index The start index from which to trim the token.
*/
- (void)trimFormIndex:(NSUInteger)index;
/**
Splits this token retaining only characters after the leading whitespace. The leading whitespace characters are then
returned a new characters token.
@returns A characters token with leading whitespace characters. Returns 'nil` if no leading whitespace exists.
*/
- (HTMLCharacterToken *)tokenBySplitingLeadingWhiteSpace;
@end
+14
View File
@@ -8,10 +8,24 @@
#import "HTMLNode.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Comment node
*/
@interface HTMLComment : HTMLNode
/** @brief The comment string. */
@property (nonatomic, copy) NSString *data;
/**
Initializes a new HTML comment node.
@param data The comment string.
@returns A new isntance of a HTML comment node.
*/
- (instancetype)initWithData:(NSString *)data;
@end
NS_ASSUME_NONNULL_END
+2 -1
View File
@@ -7,12 +7,13 @@
//
#import "HTMLComment.h"
#import "HTMLNode+Private.h"
@implementation HTMLComment
- (instancetype)init
{
return [self initWithData:nil];
return [self initWithData:@""];
}
- (instancetype)initWithData:(NSString *)data
+19
View File
@@ -6,15 +6,34 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
/**
HTML Comment Token
*/
@interface HTMLCommentToken : HTMLToken
/** @brief The comment string in this token. */
@property (nonatomic, copy) NSString *data;
/**
Initializes a new comment token.
@param string The string with which to initialize the token.
@returns A new instance of a comment token.
*/
- (instancetype)initWithData:(NSString *)data;
/**
Appends the given string to this token.
@param string The string to append.
*/
- (void)appendStringToData:(NSString *)string;
@end
+37
View File
@@ -6,20 +6,57 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
/**
HTML DOCTYPE Token
*/
@interface HTMLDOCTYPEToken : HTMLToken
/** @brief The DOCTYPE's name. */
@property (nonatomic, copy) NSString *name;
/** @brief The DOCTYPE's public identifier. */
@property (nonatomic, strong) NSMutableString *publicIdentifier;
/** @brief The DOCTYPE's system identifier. */
@property (nonatomic, strong) NSMutableString *systemIdentifier;
/** @brief Flag whether this DOCTYPE forces quirks mode. */
@property (nonatomic, assign) BOOL forceQuirks;
/**
Initializes a new DOCTYPE token.
@param name The name with which to initialize the token.
@returns A new instance of a DOCTYPE token.
*/
- (instancetype)initWithName:(NSString *)name;
/**
Appends the given string to this DOCTYPE's name.
@param string The string to append.
*/
- (void)appendStringToName:(NSString *)string;
/**
Appends the given string to this DOCTYPE's public identifier.
@param string The string to append.
*/
- (void)appendStringToPublicIdentifier:(NSString *)string;
/**
Appends the given string to this DOCTYPE's system identifier.
@param string The string to append.
*/
- (void)appendStringToSystemIdentifier:(NSString *)string;
@end
+5
View File
@@ -14,6 +14,11 @@
#import "HTMLComment.h"
#import "HTMLText.h"
#import "HTMLTemplate.h"
#import "HTMLDOMTokenList.h"
#import "HTMLNodeIterator.h"
#import "HTMLTreeWalker.h"
#import "HTMLNodeFilter.h"
#import "HTMLKitDOMExceptions.h"
#import "HTMLNamespaces.h"
#import "HTMLQuirksMode.h"
+108
View File
@@ -0,0 +1,108 @@
//
// HTMLDOMTokenList.h
// HTMLKit
//
// Created by Iska on 30/11/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class HTMLElement;
/**
A HTML DOM Token List.
The DOM Token List is used for manipulating an element's attributes that contain muliplte values separated by a space.
https://dom.spec.whatwg.org/#interface-domtokenlist
*/
@interface HTMLDOMTokenList : NSObject
/** @brief The associated context element. */
@property (nonatomic, strong, readonly) HTMLElement *element;
/** @brief The associated attribute. */
@property (nonatomic, strong, readonly) NSString *attribute;
/**
Initializes a new DOM token list.
@param element The associated context element.
@param attribute The associated attribute.
@param value The initial attribute's value.
@returns A new instance of the DOM token list.
*/
- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value;
/**
@returns The length of this token list
*/
- (NSUInteger)length;
/**
Checks whether this list contains the given token.
@param token The token.
@returns `YES` if the given token is in this list, `NO` otherwise.
*/
- (BOOL)contains:(NSString *)token;
/**
Add the given tokens to the list.
@param tokens The tokens to add.
*/
- (void)add:(NSArray<NSString *> *)tokens;
/**
Removes the given tokens from the list.
@param tokens The tokens to remove.
*/
- (void)remove:(NSArray<NSString *> *)tokens;
/**
Toggles the given token.
@param token The token to toggle.
@returns `YES` if the token was added to the list, `NO` if it was removed from it.
*/
- (BOOL)toggle:(NSString *)token;
/**
Replaces the given token with new token.
@param token The token to replace.
@param newToken The replacement token.
*/
- (void)replaceToken:(NSString *)token withToken:(NSString *)newToken;
/**
Returns the value of the token at the given index.
@param index The index at which to return the token.
@returns The token at the given index. If index is greater than or equal to the value returned by count, an
NSRangeException is raised.
*/
- (NSString *)objectAtIndexedSubscript:(NSUInteger)index;
/**
Set the token at the given index.
@param obj The token to set.
@param index The index at which to set the token. If index is greater than or equal to the value returned by count, an
NSRangeException is raised.
*/
- (void)setObject:(NSString *)obj atIndexedSubscript:(NSUInteger)index;
/**
@returns The string representation of this token list, which can be used as the attribute's value.
*/
- (NSString *)stringify;
@end
NS_ASSUME_NONNULL_END
+109
View File
@@ -0,0 +1,109 @@
//
// HTMLDOMTokenList.m
// HTMLKit
//
// Created by Iska on 30/11/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "HTMLDOMTokenList.h"
#import "HTMLElement.h"
@interface HTMLDOMTokenList ()
{
HTMLElement *_element;
NSString *_attribute;
NSMutableOrderedSet *_tokens;
}
@end
@implementation HTMLDOMTokenList
@synthesize element = _element;
@synthesize attribute = _attribute;
#pragma mark - Init
- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value
{
self = [super init];
if (self) {
_element = element;
_attribute = [attribute copy];
_tokens = [NSMutableOrderedSet new];
[self add:[value componentsSeparatedByString:@" "]];
}
return self;
}
#pragma mark - Access
- (void)updateValue
{
_element[_attribute] = self.stringify;
}
- (NSUInteger)length
{
return _tokens.count;
}
- (BOOL)contains:(NSString *)token
{
return [_tokens containsObject:token];
}
- (void)add:(NSArray<NSString *> *)tokens
{
for (NSString *token in tokens) {
if (![token isEqualToString:@""]) {
[_tokens addObject:token];
}
}
[self updateValue];
}
- (void)remove:(NSArray<NSString *> *)tokens
{
for (NSString *token in tokens) {
[_tokens removeObject:token];
}
[self updateValue];
}
- (BOOL)toggle:(NSString *)token
{
if ([_tokens containsObject:token]) {
[_tokens removeObject:token];
[self updateValue];
return NO;
} else {
[_tokens addObject:token];
[self updateValue];
return YES;
}
}
- (void)replaceToken:(NSString *)token withToken:(NSString *)newToken
{
NSUInteger index = [_tokens indexOfObject:token];
_tokens[index] = newToken;
[self updateValue];
}
- (NSString *)objectAtIndexedSubscript:(NSUInteger)index
{
return _tokens[index];
}
- (void)setObject:(NSString *)obj atIndexedSubscript:(NSUInteger)index
{
_tokens[index] = obj;
[self updateValue];
}
- (NSString *)stringify
{
return [_tokens.array componentsJoinedByString:@" "];
}
@end
+61 -7
View File
@@ -10,6 +10,12 @@
#import "HTMLDocumentType.h"
#import "HTMLQuirksMode.h"
NS_ASSUME_NONNULL_BEGIN
/**
The document's ready state. The document is `Loading` while being parsed, `Complete` otherwise. The `Interactive` state
is not supported.
*/
typedef NS_ENUM(short, HTMLDocumentReadyState)
{
HTMLDocumentLoading,
@@ -17,28 +23,76 @@ typedef NS_ENUM(short, HTMLDocumentReadyState)
HTMLDocumentComplete
};
/**
The HTML Document. This is the root of a parsed DOM tree.
https://html.spec.whatwg.org/multipage/dom.html#documents
*/
@interface HTMLDocument : HTMLNode
@property (nonatomic, strong) HTMLDocumentType *documentType;
/**
The document's DOCTYPE.
@see HTMLDocumentType
*/
@property (nonatomic, strong, nullable) HTMLDocumentType *documentType;
/**
The document's quirks mode.
@see HTMLQuirksMode
*/
@property (nonatomic, assign) HTMLQuirksMode quirksMode;
@property (nonatomic, copy, readonly) NSString *compatMode;
/**
The document's ready state.
@see HTMLDocumentReadyState
*/
@property (nonatomic, assign, readonly) HTMLDocumentReadyState readyState;
@property (nonatomic, strong) HTMLElement *rootElement;
/**
The document's root element, which is the first element in tree order, if any. Usually it is the <html> element.
*/
@property (nonatomic, strong, nullable) HTMLElement *rootElement;
@property (nonatomic, strong) HTMLElement *documentElement;
/**
The document element, i.e. the <html> element, if it exists.
*/
@property (nonatomic, strong, nullable) HTMLElement *documentElement;
@property (nonatomic, strong) HTMLElement *head;
/**
The document's <head> element, if it exists.
*/
@property (nonatomic, strong, nullable) HTMLElement *head;
@property (nonatomic, strong) HTMLElement *body;
/**
The document's <body> element, if it exists.
*/
@property (nonatomic, strong, nullable) HTMLElement *body;
/**
Retunrs a new HTML Document instance with the given HTML string.
@param string The HTML string to parse into a document.
*/
+ (instancetype)documentWithString:(NSString *)string;
/**
Adopts a given node into this document, i.e. the document becomes the new owner of the node. Raises a HTMLKitNotSupportedError
exception if node is an instance of HTMLDocument.
@param node The node to adopt.
@returns The adopted node
*/
- (HTMLNode *)adoptNode:(HTMLNode *)node;
/**
Returns the associated HTML Document proxy instance, which owns the template contents of all its template elements.
https://html.spec.whatwg.org/multipage/scripting.html#associated-inert-template-document
*/
- (HTMLDocument *)associatedInertTemplateDocument;
@end
NS_ASSUME_NONNULL_END
+1 -5
View File
@@ -10,11 +10,7 @@
#import "HTMLParser.h"
#import "HTMLNodeIterator.h"
#import "HTMLKitDOMExceptions.h"
@interface HTMLNode (Private)
@property (nonatomic, weak) HTMLDocument *ownerDocument;
@property (nonatomic, weak) HTMLNode *parentNode;
@end
#import "HTMLNode+Private.h"
@interface HTMLNodeIterator (Private)
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
+17 -1
View File
@@ -8,8 +8,24 @@
#import "HTMLNode.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Document Fragment. Represents a minimal document object that has no parent. It is used as a light-weight
version of Document
https://dom.spec.whatwg.org/#interface-documentfragment
*/
@interface HTMLDocumentFragment : HTMLNode
- (instancetype)initWithDocument:(HTMLDocument *)document;
/**
Initializes a new document fragment with the given document as owner.
@param document The owner document.
@returns A new instance of a document fragment.
*/
- (instancetype)initWithDocument:(nullable HTMLDocument *)document;
@end
NS_ASSUME_NONNULL_END
+2 -5
View File
@@ -8,10 +8,7 @@
#import "HTMLDocumentFragment.h"
#import "HTMLText.h"
@interface HTMLNode ()
@property (nonatomic, weak) HTMLDocument *ownerDocument;
@end
#import "HTMLNode+Private.h"
@implementation HTMLDocumentFragment
@@ -32,7 +29,7 @@
- (NSString *)textContent
{
NSMutableString *content = [NSMutableString string];
for (HTMLNode *node in self.treeEnumerator) {
for (HTMLNode *node in self.nodeIterator) {
if (node.nodeType == HTMLNodeText) {
[content appendString:[(HTMLText *)node data]];
}
+42 -3
View File
@@ -9,17 +9,56 @@
#import "HTMLNode.h"
#import "HTMLQuirksMode.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Document Type node. There is only one valid document type, which is `<!DOCTYPE html>`.
Other DOCTYPES, e.g. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
are obsolete but permitted.
https://dom.spec.whatwg.org/#interface-documenttype
*/
@interface HTMLDocumentType : HTMLNode
/**
The public identifier
*/
@property (nonatomic, copy, readonly) NSString *publicIdentifier;
/**
The system identifier
*/
@property (nonatomic, copy, readonly) NSString *systemIdentifier;
- (instancetype)initWithName:(NSString *)name
publicIdentifier:(NSString *)publicIdentifier
systemIdentifier:(NSString *)systemIdentifier;
/**
Initializes and returns a new isntance of a Document Type node.
@param name The name.
@param publicIdentifier The public identifier.
@param systemIdentifier The system identigier
@returns A new document type instance.
*/
- (instancetype)initWithName:(NSString *)name
publicIdentifier:(nullable NSString *)publicIdentifier
systemIdentifier:(nullable NSString *)systemIdentifier;
/**
Checks whether this DOCTYPE is valid.
@returns `YES` if this is a valid DOCTYPE, `NO` otherwise.
*/
- (BOOL)isValid;
/**
Return the quirks mode of this DOCTYPE.
@returns The quirks mode.
@see HTMLQuirksMode
*/
- (HTMLQuirksMode)quirksMode;
@end
NS_ASSUME_NONNULL_END
+1
View File
@@ -8,6 +8,7 @@
#import "HTMLDocumentType.h"
#import "NSString+HTMLKit.h"
#import "HTMLNode+Private.h"
NS_INLINE BOOL nilOrEqual(id first, id second) {
return (first == nil) || ([first isEqual:second]);
+8
View File
@@ -6,10 +6,18 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import "HTMLToken.h"
/**
A HTML EOF Token.
*/
@interface HTMLEOFToken : HTMLToken
/** Returns the singleton instance of the EOF Token. */
+ (instancetype)token;
@end
+92 -3
View File
@@ -9,26 +9,115 @@
#import <Foundation/Foundation.h>
#import "HTMLNamespaces.h"
#import "HTMLNode.h"
#import "HTMLDOMTokenList.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Element.
https://html.spec.whatwg.org/multipage/dom.html#elements
https://html.spec.whatwg.org/multipage/syntax.html#elements-2
*/
@interface HTMLElement : HTMLNode
/**
The namesapce of this element.
@see HTMLNamespace
*/
@property (nonatomic, assign, readonly) HTMLNamespace htmlNamespace;
/**
The elemen's tag name.
*/
@property (nonatomic, copy, readonly) NSString *tagName;
@property (nonatomic, strong) NSMutableDictionary *attributes;
/**
The elemen's id attribute value. Empty string if the element has no id attribute.
*/
@property (nonatomic, copy) NSString *elementId;
/**
The elemen's class attribute value. Empty string if the element has no class attribute.
*/
@property (nonatomic, copy) NSString *className;
/**
The element's class attribute as a DOM Token List
@see HTMLDOMTokenList
*/
@property (nonatomic, strong, readonly) HTMLDOMTokenList *classList;
/**
The element's attribites.
*/
@property (nonatomic, strong) NSMutableDictionary *attributes;
/**
@warning Use one of the initWithTagName: methods instead.
*/
- (instancetype)init NS_UNAVAILABLE;
/**
Initializes a new HTML element with the given tag name.
@param tagname The tag name.
@returns A new HTML element.
*/
- (instancetype)initWithTagName:(NSString *)tagName;
/**
Initializes a new HTML element with the given tag name and attributes.
@param tagname The tag name.
@param attributes The attributes.
@returns A new HTML element.
*/
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes;
/**
Initializes a new HTML element with the given tag name, namespace, and attributes.
@param tagname The tag name.
@param namespace The namespace.
@param attributes The attributes.
@returns A new HTML element.
*/
- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(NSDictionary *)attributes;
/**
Checks whether this element has an attribute with the given name.
@param name The attribute name.
@returns `YES` if the element has such an attributes, `NO` otherwise.
*/
- (BOOL)hasAttribute:(NSString *)name;
- (NSString *)objectForKeyedSubscript:(NSString *)name;
/**
Returns the value of the attribute with the given name.
@param name The attribute's name.
@returns The attribute's value, `nil` if the element doesn't have such attribute.
*/
- (nullable NSString *)objectForKeyedSubscript:(NSString *)name;
/**
Set the value of the attribute with the given name.
@param value The value to set.
@param name The attribute's name.
*/
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute;
/**
Removes the attribute with the given name.
@param name The attribute to remove.
*/
- (void)removeAttribute:(NSString *)name;
@end
NS_ASSUME_NONNULL_END
+28 -5
View File
@@ -10,9 +10,10 @@
#import "HTMLParser.h"
#import "HTMLDocument.h"
#import "HTMLText.h"
#import "HTMLDOMTokenList.h"
#import "HTMLOrderedDictionary.h"
#import "NSString+HTMLKit.h"
#import "HTMLNode+Private.h"
@interface HTMLElement ()
{
@@ -26,12 +27,12 @@
- (instancetype)init
{
return [self initWithTagName:nil];
return [self initWithTagName:@""];
}
- (instancetype)initWithTagName:(NSString *)tagName
{
return [self initWithTagName:tagName attributes:nil];
return [self initWithTagName:tagName attributes:@{}];
}
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes
@@ -53,18 +54,35 @@
return self;
}
#pragma mark - Attributes
#pragma mark - Special Attributes
- (NSString *)elementId
{
return _attributes[@"id"] ?: @"";
}
- (void)setElementId:(NSString *)elementId
{
_attributes[@"id"] = elementId;
}
- (NSString *)className
{
return _attributes[@"class"];
return _attributes[@"class"] ?: @"";
}
- (void)setClassName:(NSString *)className
{
_attributes[@"class"] = className;
}
- (HTMLDOMTokenList *)classList
{
return [[HTMLDOMTokenList alloc] initWithElement:self attribute:@"class" value:self.className];
}
#pragma mark - Attributes
- (BOOL)hasAttribute:(NSString *)name
{
return _attributes[name] != nil;
@@ -180,4 +198,9 @@
return description;
}
- (NSString *)debugDescription
{
return self.description;
}
@end
+4
View File
@@ -6,6 +6,10 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import "HTMLElement.h"
#import "HTMLTokens.h"
#import "HTMLNamespaces.h"
+4
View File
@@ -6,6 +6,10 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import "HTMLElement.h"
#import "HTMLNamespaces.h"
#import "NSString+HTMLKit.h"
+123
View File
@@ -6,8 +6,17 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
/**
Typedef for the error callback block.
@param reason The string describing the reason of the reported error.
*/
typedef void (^ HTMLStreamReaderErrorCallback)(NSString *reason);
/**
@@ -16,29 +25,143 @@ typedef void (^ HTMLStreamReaderErrorCallback)(NSString *reason);
*/
@interface HTMLInputStreamReader : NSObject
/** @brief The underlying string with which this stream reader was initialized */
@property (nonatomic, readonly) NSString *string;
/** @brief The current scan location */
@property (nonatomic, readonly) NSUInteger currentLocation;
/** @brief An error callback block, which gets called when encountering errors while reading the stream */
@property (nonatomic, copy) HTMLStreamReaderErrorCallback errorCallback;
/**
Initializes a new Input Stream Reader with the given string.
@param string The HTML string
@returns A new instance of the Input Stream Reader.
*/
- (id)initWithString:(NSString *)string;
/**
Returns the current input character.
@returns The current code point in the input stream as a `UTF32Char`.
*/
- (UTF32Char)currentInputCharacter;
/**
Returns the next input character without consuming it.
@returns The next code point in the input stream as a `UTF32Char`. Returns `EOF` if the stream is fully consumed.
*/
- (UTF32Char)nextInputCharacter;
/**
Returns the input character at a given offset without consuming it.
@param offset The offset of the character.
@returns The code point in the input stream as a `UTF32Char` at the given offset.
*/
- (UTF32Char)inputCharacterPointAtOffset:(NSUInteger)offset;
/**
Consumes and returns the next input character. Consuming a characters advances the current scan location of the
input stream.
@returns The next code point in the input stream as a `UTF32Char`. Returns `EOF` if the stream is fully consumed.
*/
- (UTF32Char)consumeNextInputCharacter;
/**
Causes the next input character to return the current input character.
*/
- (void)reconsumeCurrentInputCharacter;
/** @brief Unconsumes the current input character. */
- (void)unconsumeCurrentInputCharacter;
/**
Consumes the given character at the current location.
@param character The character to consume.
@returns YES if the given character was consumed at the current location, NO otherwise.
*/
- (BOOL)consumeCharacter:(UTF32Char)character;
/**
Consumes characters at the current location matching an unsigned number.
@param result Upon return, contains the consumed unsigned number. Pass `NULL` to skip over an unsigned number at the
current location.
@returns YES if an unsigned number could be consumed at the current location, NO otherwise.
*/
- (BOOL)consumeNumber:(unsigned long long *)result;
/**
Consumes characters at the current location matching a decimal number.
@param result Upon return, contains the consumed decimal number. Pass `NULL` to skip over a decimal number at the
current location.
@returns YES if a decimal number could be consumed at the current location, NO otherwise.
*/
- (BOOL)consumeDecimalNumber:(NSDecimal *)result;
/**
Consumes characters at the current location matching a hexadecimal number.
@param result Upon return, contains the consumed hexadecimal number. Pass `NULL` to skip over a hexadecimal number at
the current location.
@returns YES if a hexadecimal number could be consumed at the current location, NO otherwise.
*/
- (BOOL)consumeHexNumber:(unsigned long long *)result;
/**
Consumes the given string at the current location.
@param string The string to consume.
@param caseSensitive YES if the string's case should be ignored, NO otherwise
@returns YES if the given string was consumed at the current location, NO otherwise.
*/
- (BOOL)consumeString:(NSString *)string caseSensitive:(BOOL)caseSensitive;
/**
Consumes characters starting at the current location until any character in a given string is encountered.
@param characters The string containing the characters to consume up to.
@returns A string containing the consumed characters. Returns `nil` if none were consumed.
*/
- (NSString *)consumeCharactersUpToCharactersInString:(NSString *)characters;
/**
Consumes characters starting at the current location until a given string is encountered.
@param string The string to consume up to.
@returns A string containing the consumed characters. Returns `nil` if none were consumed.
*/
- (NSString *)consumeCharactersUpToString:(NSString *)string;
/**
Consumes characters as long as the match the characters in the given string starting at the current location.
@param characters A string with the characters to consume.
@returns A string containing the consumed characters. Returns `nil` if none were found.
*/
- (NSString *)consumeCharactersInString:(NSString *)characters;
/**
Consumes alphanumeric characters starting at the current location.
@returns A string containing the consumed alphanumeric characters. Returns `nil` if none were found.
*/
- (NSString *)consumeAlphanumericCharacters;
/** @brief Marks the current stream scan location. */
- (void)markCurrentLocation;
/** @brief Resets the stream's scan location to the previously marked location. */
- (void)rewindToMarkedLocation;
/** @brief Resets the stream to its begining. */
- (void)reset;
@end
+45 -5
View File
@@ -8,6 +8,7 @@
#import "HTMLInputStreamReader.h"
#import "HTMLTokenizerCharacters.h"
#import "NSCharacterSet+HTMLKit.h"
#pragma mark - HTMLInputStreamReader
@@ -39,6 +40,7 @@
if (self) {
_string = [string copy];
_scanner = [[NSScanner alloc] initWithString:string];
_scanner.charactersToBeSkipped = nil;
CFStringInitInlineBuffer((CFStringRef)_string, &_buffer, CFRangeMake(0, _string.length));
}
return self;
@@ -63,7 +65,6 @@
- (UTF32Char)nextInputCharacter
{
if (_reconsume) {
_reconsume = NO;
return _currentInputCharacter;
}
@@ -106,6 +107,11 @@
return nextInputCharacter;
}
- (UTF32Char)inputCharacterPointAtOffset:(NSUInteger)offset
{
return CFStringGetCharacterFromInlineBuffer(&_buffer, _location + offset);
}
- (UTF32Char)consumeNextInputCharacter
{
if (_reconsume) {
@@ -124,9 +130,12 @@
{
UTF32Char nextInputCharacter = [self nextInputCharacter];
if (nextInputCharacter == character) {
_location += _consume;
_scanner.scanLocation = _location;
_currentInputCharacter = nextInputCharacter;
if (!_reconsume) {
_location += _consume;
_scanner.scanLocation = _location;
_currentInputCharacter = nextInputCharacter;
}
_reconsume = NO;
return YES;
}
return NO;
@@ -143,9 +152,20 @@
return success;
}
- (BOOL)consumeDecimalNumber:(NSDecimal *)result
{
NSDecimal scanned;
BOOL success = [_scanner scanDecimal:&scanned];
if (success == NO) return NO;
*result = scanned;
_location = _scanner.scanLocation;
return success;
}
- (BOOL)consumeHexNumber:(unsigned long long *)result
{
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEFabcdef"];
NSCharacterSet *set = [NSCharacterSet HTMLHexNumberCharacterSet];
NSString *string = nil;
BOOL success = [_scanner scanCharactersFromSet:set intoString:&string];
@@ -193,6 +213,26 @@
return consumed;
}
- (NSString *)consumeCharactersInString:(NSString *)characters
{
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:characters];
if (_reconsume) {
_scanner.scanLocation--;
}
NSString *string = nil;
BOOL success = [_scanner scanCharactersFromSet:set intoString:&string];
if (success == NO) {
_scanner.scanLocation++;
return nil;
}
_reconsume = NO;
_location = _scanner.scanLocation;
return string;
}
- (NSString *)consumeAlphanumericCharacters
{
NSCharacterSet *set = [NSCharacterSet alphanumericCharacterSet];
+13 -2
View File
@@ -8,6 +8,17 @@
#import <Foundation/Foundation.h>
@interface HTMLKit : NSObject
//! Project version number for HTMLKit.
extern double HTMLKitVersionNumber;
@end
//! Project version string for HTMLKit.
extern const unsigned char HTMLKitVersionString[];
#import "HTMLDOM.h"
#import "HTMLParser.h"
#import "HTMLKitErrorDomain.h"
#import "HTMLOrderedDictionary.h"
#import "CSSSelectors.h"
#import "CSSSelectorParser.h"
#import "CSSNthExpressionParser.h"
-13
View File
@@ -1,13 +0,0 @@
//
// HTMLKit.m
// HTMLKit
//
// Created by Iska on 15/09/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLKit.h"
@implementation HTMLKit
@end
+6 -3
View File
@@ -6,6 +6,9 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
FOUNDATION_EXPORT NSString * const HTMLKitHierarchyRequestError;
FOUNDATION_EXPORT NSString * const HTMLKitNotFoundError;
FOUNDATION_EXPORT NSString * const HTMLKitNotSupportedError;
extern NSString * const HTMLKitHierarchyRequestError;
extern NSString * const HTMLKitNotFoundError;
extern NSString * const HTMLKitNotSupportedError;
extern NSString * const HTMLKitSyntaxError;
extern NSString * const HTMLKitInvalidCharacterError;
+2
View File
@@ -11,3 +11,5 @@
NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError";
NSString * const HTMLKitNotFoundError = @"NotFoundError";
NSString * const HTMLKitNotSupportedError = @"NotSupportedError";
NSString * const HTMLKitSyntaxError = @"SyntaxError";
NSString * const HTMLKitInvalidCharacterError = @"InvalidCharacterError";
+23
View File
@@ -0,0 +1,23 @@
//
// HTMLKitErrorDomain.h
// HTMLKit
//
// Created by Iska on 24/11/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#ifndef HTMLKitErrorDomain_h
#define HTMLKitErrorDomain_h
static NSString *const HTMLKitErrorDomain = @"HTMLKit";
static NSString *const HTMLKitSelectorErrorDomain = @"HTMLKitSelector";
static NSString *const CSSSelectorStringKey = @"CSSSelectorString";
static NSString *const CSSSelectorErrorLocationKey = @"CSSSelectorErrorLocation";
NS_ENUM(NSInteger)
{
HTMLKitSelectorParseError = 4200
};
#endif /* HTMLKitErrorDomain_h */
@@ -6,33 +6,134 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLElement.h"
/**
The List of Active Formatting Elements. It is used to handle mis-nested formatting element tags.
https://html.spec.whatwg.org/multipage/syntax.html#the-list-of-active-formatting-elements
*/
@interface HTMLListOfActiveFormattingElements : NSObject
/**
Returns the object at the specified index.
@param index An index within the bounds of the list.
@return The node located at index.
*/
- (id)objectAtIndexedSubscript:(NSUInteger)index;
/**
Replaces the object at the index with the new object.
@param obj The node with which to replace the object at given index in the list.
@param idx The index of the object to be replaced.
*/
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
/**
Returns the index of the given node in the list.
@param node The node.
@returns The index of the given node in the list.
*/
- (NSUInteger)indexOfElement:(id)node;
/**
Adds the given element to the list.
@param element The element to add.
*/
- (void)addElement:(HTMLElement *)element;
/**
Removes the given element from the list.
@param element The element to remove.
*/
- (void)removeElement:(id)element;
/**
Checks whether the given element is in the list.
@param element The element to check.
@returns `YES` if element is in the list, `NO` otherwise.
*/
- (BOOL)containsElement:(id)element;
/**
Inserts the given element at the index into the list.
@param element The element to insert.
@param index The index at which the element should be inserted.
*/
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
/**
Replaces the element at the given index in the list with the new element.
@param index The index of the element to be replaced.
@param element The element with which to replace the element at given index in the list.
*/
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
/**
Returns the last element in this list.
@returns The last entry.
*/
- (id)lastEntry;
/**
Adds a marker to the end of this list
*/
- (void)addMarker;
/**
Clears all elements from the end of this list upto the last marker.
*/
- (void)clearUptoLastMarker;
/**
Returns the last element in the list having the given tag name, that is between the end of the list and the last marker
in the list, if any, or the start of the list otherwise.
@param tagName The tag name.
@returns The formatting element.
*/
- (HTMLElement *)formattingElementWithTagName:(NSString *)tagName;
/**
Returns the count of elements in this list.
@returns The elements count.
*/
- (NSUInteger)count;
/**
Checks whether this list is empty.
@returns `YES` if the stack is empty, `NO` otherwise.
*/
- (BOOL)isEmpty;
/**
Return an object enumerator over this list.
@returns An enumerator
*/
- (NSEnumerator *)enumerator;
/**
Return an object enumerator over this list.
@returns An enumerator
*/
- (NSEnumerator *)reverseObjectEnumerator;
@end
+12
View File
@@ -6,10 +6,22 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
/**
A Maker that is used in the List of Active Formatting Elements.
@see HTMLListOfActiveFormattingElements
*/
@interface HTMLMarker : NSObject
/**
Returns the singleton instance of the Marker.
*/
+ (instancetype)marker;
@end
+10 -4
View File
@@ -6,12 +6,18 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
/**
HTML Namespaces
https://html.spec.whatwg.org/multipage/infrastructure.html#namespaces
*/
typedef NS_ENUM(NSInteger, HTMLNamespace)
{
/** The default HTML namespace. */
HTMLNamespaceHTML,
/** The namespace for most of the <math> elements. */
HTMLNamespaceMathML,
HTMLNamespaceSVG,
HTMLNamespaceXLink,
HTMLNamespaceXML,
HTMLNamespaceXMLNS,
/** The namespace for most of the <svg> elements. */
HTMLNamespaceSVG
};
+54
View File
@@ -0,0 +1,54 @@
//
// HTMLNode+Private.h
// HTMLKit
//
// Created by Iska on 20/12/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <HTMLKit/HTMLKit.h>
/**
Private HTML Node methods which are not intended for public API.
*/
@interface HTMLNode ()
/**
A read-write redeclaration of the same property in the public API.
*/
@property (nonatomic, weak) HTMLDocument *ownerDocument;
/**
A read-write redeclaration of the same property in the public API.
*/
@property (nonatomic, weak) HTMLNode *parentNode;
/**
Designated initializer of the HTML Node, which, however, should not be used directly. It is intended to be called only
by subclasses.
@abstract Use concrete subclasses of the HTML Node.
@param name The node's name.
@param type The node's type.
@returns A new instance of a HTML Node.
*/
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type NS_DESIGNATED_INITIALIZER;
/**
Casts this node to a HTML Element. This cast should only be performed after the appropriate check.
*/
- (HTMLElement *)asElement;
/**
Returns the same string representation of the DOM tree rooted at this node that is used by html5lib-tests.
@disucssion This method is indended for testing purposes.
*/
- (NSString *)treeDescription;
@end
+320 -20
View File
@@ -9,22 +9,25 @@
#import <Foundation/Foundation.h>
#import "HTMLNodeIterator.h"
NS_ASSUME_NONNULL_BEGIN
/**
The HTML node type
*/
typedef NS_ENUM(short, HTMLNodeType)
{
HTMLNodeElement = 1,
HTMLNodeAttribute = 2, // historical
HTMLNodeText = 3,
HTMLNodeCDATASection = 4, // historical
HTMLNodeEntityReference = 5, // historical
HTMLNodeEntity = 6, // historical
HTMLNodeProcessingInstruction = 7,
HTMLNodeComment = 8,
HTMLNodeDocument = 9,
HTMLNodeDocumentType = 10,
HTMLNodeDocumentFragment = 11,
HTMLNodeNotation = 12 // historical
};
/**
A node's position in the HTML document when compared with other nodes.
*/
typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
{
HTMLDocumentPositionEquivalent = 0x0,
@@ -38,89 +41,386 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
@class HTMLDocument;
@class HTMLElement;
@class CSSSelector;
/**
A HTML Node, the base class for all HTML DOM entities.
HTMLKit provides a partial implementation of the WHATWG DOM specification: https://dom.spec.whatwg.org/
*/
@interface HTMLNode : NSObject <NSCopying>
/**
The node type.
@see HTMLNodeType
*/
@property (nonatomic, assign, readonly) HTMLNodeType nodeType;
/**
The node name as described in https://dom.spec.whatwg.org/#dom-node-nodename
@warning This is not the HTML Element tag name.
*/
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, weak, readonly) HTMLDocument *ownerDocument;
/**
The owner document of this node.
@property (nonatomic, weak, readonly) HTMLNode *parentNode;
@see HTMLDocument
*/
@property (nonatomic, weak, readonly, nullable) HTMLDocument *ownerDocument;
@property (nonatomic, weak, readonly) HTMLElement *parentElement;
/**
The parent node of this node, if any.
*/
@property (nonatomic, weak, readonly, nullable) HTMLNode *parentNode;
/**
The parent element of this node, if any.
@discussion This property returns nil if the parent is a non-element node.
*/
@property (nonatomic, weak, readonly, nullable) HTMLElement *parentElement;
/**
A read-only ordered set of child nodes.
*/
@property (nonatomic, strong, readonly) NSOrderedSet *childNodes;
@property (nonatomic, strong, readonly) HTMLNode *firstChild;
/**
The first child node, if any.
*/
@property (nonatomic, strong, readonly, nullable) HTMLNode *firstChild;
@property (nonatomic, strong, readonly) HTMLNode *lastChild;
/**
The last child node, if any.
*/
@property (nonatomic, strong, readonly, nullable) HTMLNode *lastChild;
@property (nonatomic, strong, readonly) HTMLNode *previousSibling;
/**
The previous sibling node in the document, if any.
*/
@property (nonatomic, strong, readonly, nullable) HTMLNode *previousSibling;
@property (nonatomic, strong, readonly) HTMLNode *nextSibling;
/**
The next sibling node in the document, if any.
*/
@property (nonatomic, strong, readonly, nullable) HTMLNode *nextSibling;
/**
The previous sibling element in the document, if any.
@discussion Previous non-element nodes will be skipped till an element is found.
*/
@property (nonatomic, strong, readonly, nullable) HTMLElement *previousSiblingElement;
/**
The next sibling element in the document, if any.
@discussion Next non-element nodes will be skipped till an element is found.
*/
@property (nonatomic, strong, readonly, nullable) HTMLElement *nextSiblingElement;
/**
The text content of this node.
*/
@property (nonatomic, copy) NSString *textContent;
/**
The outer HTML string.
*/
@property (nonatomic, strong, readonly) NSString *outerHTML;
/**
The inner HTML string.
*/
@property (nonatomic, copy) NSString *innerHTML;
/**
@abstract Use concrete subclasses of the HTML Node.
*/
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type;
- (HTMLElement *)asElement;
/**
Checks whether this node has child nodes.
@returns `YES` if this node has any children, `NO` otherwise.
*/
- (BOOL)hasChildNodes;
/**
Checks whether this node has child nodes of the given type.
@param type The type to check.
@returns `YES` if this node has any children of the given type, `NO` otherwise.
*/
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type;
/**
Returns the cound of child nodes.
@returns The child nodes count.
*/
- (NSUInteger)childNodesCount;
/**
Returns the child node at a given index.
@param index The index at which to return the child node.
@returns The child node at a index. If index is greater than or equal to the value returned by count, an
NSRangeException is raised.
*/
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index;
/**
Returns the index of the given child node in the set of child nodes.
@param node The node.
@returns The index of the given node in the children set.
*/
- (NSUInteger)indexOfChildNode:(HTMLNode *)node;
/**
Returns the cound of child elements.
@discussion This method count only nodes of type HTMLNodeElement.
@returns The child elements count.
*/
- (NSUInteger)childElementsCount;
/**
Returns the child element at a given index.
@param index The index at which to return the child element.
@returns The child element at a index. If index is greater than or equal to the value returned by count, an
NSRangeException is raised.
*/
- (HTMLElement *)childElementAtIndex:(NSUInteger)index;
/**
Returns the index of the given child element in the set of child nodes.
@param node The element.
@returns The index of the given element in the children set.
*/
- (NSUInteger)indexOfChildElement:(HTMLElement *)element;
/**
Prepends the given node to the set of child nodes.
@param node The node to prepend.
@returns The node being prepended.
*/
- (HTMLNode *)prependNode:(HTMLNode *)node;
/**
Prepends the given array of nodes to the set of child nodes.
@param node The nodes to prepend.
*/
- (void)prependNodes:(NSArray *)nodes;
/**
Appends the given node to the set of child nodes.
@param node The node to append.
@returns The node being appended.
*/
- (HTMLNode *)appendNode:(HTMLNode *)node;
/**
Appends the given array of nodes to the set of child nodes.
@param nodes The nodes to append.
*/
- (void)appendNodes:(NSArray *)nodes;
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child;
/**
Inserts a given node before a child node.
@param node The node to insert.
@param child A reference child node before which the new node should be inserted. If child is `nil` then the new node
will be inserted as the last child node.
@returns The node being inserted.
*/
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(nullable HTMLNode *)child;
- (HTMLNode *)replaceChildNode:(HTMLNode *)node withNode:(HTMLNode *)replacement;
/**
Replaces a given child node whith new node.
@param child The child node to replace.
@param replacement The replacement node.
@returns The replacement node.
*/
- (HTMLNode *)replaceChildNode:(HTMLNode *)child withNode:(HTMLNode *)replacement;
/**
Replaces all child nodes with the given node.
@param node The node which will replace all child nodes.
*/
- (void)replaceAllChildNodesWithNode:(HTMLNode *)node;
/**
Removes this node from its parent.
@discussion This will detach the node from its parent and in turn from its previous document.
*/
- (void)removeFromParentNode;
/**
Removes the given child node from children.
@param node The node to remove.
*/
- (HTMLNode *)removeChildNode:(HTMLNode *)node;
/**
Removes the child node at index from children.
@param index The index of the node to remove.
*/
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index;
/**
Changes children ownership from this node to the given node.
@discussion Running this method will append all children of this node to the given node. This node will have no children
afterwards.
@param node The node which will reparent children of this node.
*/
- (void)reparentChildNodesIntoNode:(HTMLNode *)node;
/**
Removes all child nodes.
*/
- (void)removeAllChildNodes;
/**
Compares the position of this node with the given node in the document.
@param node The node with which to comapre the position.
@returns The HTMLDocumentPosition of this node in relation to the given node.
@see HTMLDocumentPosition
*/
- (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)node;
/**
Checks whether this node is descendant of the given node.
@param node The node to check.
@returns `YES` if this node is descendant of the gicen node, `NO` otherwsie.
*/
- (BOOL)isDescendantOfNode:(HTMLNode *)node;
/**
Checks whether this node contains the given node.
@param node The node to check.
@returns `YES` if this node contains the given node, `NO` otherwsie.
*/
- (BOOL)containsNode:(HTMLNode *)node;
/**
Enumerates and applies `block` on each child node.
@block The block to run for each child node.
*/
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block;
/**
Enumerates and applies `block` on each child element.
@discussion This method only enumerates child elements.
@block The block to run for each child element.
*/
- (void)enumerateChildElementsUsingBlock:(void (^)(HTMLElement *element, NSUInteger idx, BOOL *stop))block;
/**
Returns a node iterator rooted at this node whith no filter and HTMLNodeFilterShowAll.
@returns A new node iterator whose root is this node.
@see HTMLNodeIterator
@see HTMLNodeFilterShowOptions
*/
- (HTMLNodeIterator *)nodeIterator;
/**
Returns a node iterator rooted at this node.
@param showOptions The iterator's show options.
@param filter The iterator's filter.
@returns A new node iterator whose root is this node.
@see HTMLNodeIterator
@see HTMLNodeFilterShowOptions
*/
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
filter:(id<HTMLNodeFilter>)filter;
filter:(nullable id<HTMLNodeFilter>)filter;
/**
Returns a node iterator rooted at this node.
@param showOptions The iterator's show options.
@param filter The iterator's filter block.
@returns A new node iterator whose root is this node.
@see HTMLNodeIterator
@see HTMLNodeFilterShowOptions
*/
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter;
- (NSString *)treeDescription;
/**
Returns the first element in the DOM tree rooted at this node, that is matched by the given selector string.
@param selector The CSS seletor string.
@returns The first element that is matched by the parsed selector. Rerturns `nil` if the selector could not be parsed
or no element was matched.
@see firstElementMatchingSelector:
@see CSSSelector
*/
- (nullable HTMLElement *)querySelector:(NSString *)selector;
/**
Returns all elements in the DOM tree rooted at this node, that are matched by the given selector string.
@param selector The CSS seletor string.
@returns The elements that are matched by the parsed selector. Rerturns an empty array if the selector could not be parsed
or no elements were matched.
@see elementsMatchingSelector:
@see CSSSelector
*/
- (NSArray<HTMLElement *> *)querySelectorAll:(NSString *)selector;
/**
Returns the first element in the DOM tree rooted at this node, that is matched by the given selector.
@param selector The CSS seletor.
@returns The first element that is matched by the parsed selector. Rerturns `nil` if no element was matched.
@see CSSSelector
*/
- (nullable HTMLElement *)firstElementMatchingSelector:(CSSSelector *)selector;
/**
Returns all elements in the DOM tree rooted at this node, that are matched by the given selector.
@param selector The CSS seletor.
@returns The elements that are matched by the parsed selector. Rerturns an empty array if no elements were matched.
@see CSSSelector
*/
- (NSArray<HTMLElement *> *)elementsMatchingSelector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END
+103 -9
View File
@@ -7,12 +7,15 @@
//
#import "HTMLNode.h"
#import "HTMLNode+Private.h"
#import "HTMLDocument.h"
#import "HTMLDocumentType.h"
#import "HTMLElement.h"
#import "HTMLText.h"
#import "HTMLComment.h"
#import "HTMLKitDOMExceptions.h"
#import "HTMLNodeFilter.h"
#import "CSSSelector.h"
@interface HTMLDocument (Private)
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
@@ -24,7 +27,6 @@
{
NSMutableOrderedSet *_childNodes;
}
@property (nonatomic, weak) HTMLDocument *ownerDocument;
@end
@implementation HTMLNode
@@ -98,6 +100,24 @@
return [_parentNode childNodeAtIndex:index + 1];
}
- (HTMLElement *)previousSiblingElement
{
HTMLNode *node = self.previousSibling;
while (node && node.nodeType != HTMLNodeElement) {
node = node.previousSibling;
}
return node.asElement;
}
- (HTMLElement *)nextSiblingElement
{
HTMLNode *node = self.previousSibling;
while (node && node.nodeType != HTMLNodeElement) {
node = node.nextSibling;
}
return node.asElement;
}
- (NSString *)textContent
{
return nil;
@@ -140,11 +160,46 @@
return [self.childNodes objectAtIndex:index];
}
- (NSUInteger)childElementsCount
{
return [self.childNodes indexesOfObjectsPassingTest:^BOOL(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
return node.nodeType == HTMLNodeElement;
}].count;
}
- (NSUInteger)indexOfChildNode:(HTMLNode *)node
{
return [self.childNodes indexOfObject:node];
}
- (HTMLElement *)childElementAtIndex:(NSUInteger)index
{
NSUInteger counter = 0;
for (HTMLNode *node in self.childNodes) {
if (node.nodeType == HTMLNodeElement) {
if (counter == index) {
return node.asElement;
}
counter++;
}
}
return nil;
}
- (NSUInteger)indexOfChildElement:(HTMLElement *)element
{
NSUInteger counter = 0;
for (HTMLNode *node in self.childNodes) {
if (node.nodeType == HTMLNodeElement) {
if (node == element) {
return counter;
}
counter++;
}
}
return NSNotFound;
}
- (HTMLNode *)prependNode:(HTMLNode *)node
{
return [self insertNode:node beforeChildNode:self.firstChild];
@@ -381,9 +436,53 @@
}
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block
{
return [HTMLNodeIterator iteratorWithNode:self showOptions:showOptions filter:filter];
HTMLNodeFilterBlock *filter = [HTMLNodeFilterBlock filterWithBlock:block];
return [[HTMLNodeIterator alloc] initWithNode:self showOptions:showOptions filter:filter];
}
#pragma mark - Selectors
- (HTMLElement *)querySelector:(NSString *)selectorString
{
CSSSelector *selector = [CSSSelector selectorWithString:selectorString];
return [self firstElementMatchingSelector:selector];
}
- (NSArray<HTMLElement *> *)querySelectorAll:(NSString *)selectorString
{
CSSSelector *selector = [CSSSelector selectorWithString:selectorString];
return [self elementsMatchingSelector:selector];
}
- (HTMLElement *)firstElementMatchingSelector:(CSSSelector *)selector
{
if (selector == nil) {
return nil;
}
for (HTMLElement *element in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
if ([selector acceptElement:element]) {
return element;
}
}
return nil;
}
- (NSArray<HTMLElement *> *)elementsMatchingSelector:(CSSSelector *)selector
{
if (selector == nil) {
return @[];
}
NSMutableArray *result = [NSMutableArray array];
for (HTMLElement *element in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
if ([selector acceptElement:element]) {
[result addObject:element];
}
}
return result;
}
#ifndef HTMLKIT_NO_DOM_CHECKS
@@ -610,12 +709,7 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p %@>", self.class, self, self.name];
}
- (NSString *)debugDescription
{
return self.treeDescription;
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.name];
}
- (id)debugQuickLookObject
+58 -1
View File
@@ -8,6 +8,12 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
The node filter's value when applied to a given HTML node. The node filter can either accept a node, skip it, or
reject it. Rejecting a node means skipping the node itself and all of it descendants.
*/
typedef NS_ENUM(unsigned short, HTMLNodeFilterValue)
{
HTMLNodeFilterAccept = 1,
@@ -15,6 +21,12 @@ typedef NS_ENUM(unsigned short, HTMLNodeFilterValue)
HTMLNodeFilterSkip = 3
};
/**
The show options for the HTML node iterator and tree walker.
@see HTMLNodeIterator
@see HTMLTreeWalker
*/
typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions)
{
HTMLNodeFilterShowAll = 0xFFFFFFFF,
@@ -26,16 +38,61 @@ typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions)
HTMLNodeFilterShowDocumentFragment = 0x400
};
#pragma mark - Node Filter
@class HTMLNode;
/**
A HTML Node Filter which can be used with a node iterator or a tree walker.
@see HTMLNodeIterator
@see HTMLTreeWalker
*/
@protocol HTMLNodeFilter <NSObject>
@required
/**
The implementation should return a HTMLNodeFilterValue to indicate accepting, skipping or rejecting a node.
@param node The node to be filtered.
@returns `HTMLNodeFilterAccept` if accepted, `HTMLNodeFilterSkip` if skipped, or `HTMLNodeFilterReject` if rejected.
*/
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node;
@end
#pragma mark - Block Filter
/**
A concrete block-based HTML Node Filter implementation.
*/
@interface HTMLNodeFilterBlock : NSObject <HTMLNodeFilter>
/**
Initializes and returns a new instance of this filter.
@param block The block to apply on each node to be filtered.
*/
+ (instancetype)filterWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block;
@end
#pragma mark - CSS Selector Filter
@class CSSSelector;
/**
A concrete css-selector-based HTML Node Filter implementation.
*/
@interface HTMLSelectorNodeFilter : NSObject <HTMLNodeFilter>
/**
Initializes and returns a new instance of this filter.
@param selector The selector to apply on each node to be filtered.
*/
+ (instancetype)filterWithSelector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END
+44
View File
@@ -7,6 +7,11 @@
//
#import "HTMLNodeFilter.h"
#import "HTMLNode.h"
#import "HTMLNode+Private.h"
#import "CSSSelector.h"
#pragma mark - Block Filter
@interface HTMLNodeFilterBlock ()
{
@@ -40,3 +45,42 @@
}
@end
#pragma mark - CSS Selector Filter
@interface HTMLSelectorNodeFilter ()
{
CSSSelector *_selector;
}
@end
@implementation HTMLSelectorNodeFilter
+ (instancetype)filterWithSelector:(CSSSelector *)selector
{
return [[self alloc] initWithSelector:selector];
}
- (instancetype)initWithSelector:(CSSSelector *)selector
{
self = [super init];
if (self) {
_selector = selector;
}
return self;
}
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node
{
if (node.nodeType != HTMLNodeElement) {
return HTMLNodeFilterSkip;
}
if ([_selector acceptElement:node.asElement]) {
return HTMLNodeFilterAccept;
}
return HTMLNodeFilterSkip;
}
@end
+68 -9
View File
@@ -9,28 +9,87 @@
#import <Foundation/Foundation.h>
#import "HTMLNodeFilter.h"
NS_ASSUME_NONNULL_BEGIN
@class HTMLNode;
@interface HTMLNodeIterator : NSEnumerator
/**
A HTML Node Iterator, which iterates the nodes in the DOM in tree order, i.e. depth-first traversal of the tree.
https://dom.spec.whatwg.org/#interface-nodeiterator
*/
@interface HTMLNodeIterator : NSEnumerator<HTMLNode *>
/**
The root element of this iterator, i.e. the traversed tree is rooted at this element.
*/
@property (nonatomic, strong, readonly) HTMLNode *root;
/**
The current reference node.
*/
@property (nonatomic, strong, readonly) HTMLNode *referenceNode;
/**
Whether the iterator's pointer is before the reference node.
*/
@property (nonatomic, assign, readonly) BOOL pointerBeforeReferenceNode;
/**
The iterator's show options. These options control what types of elements are shown or skipped during iteration.
@see HTMLNodeFilterShowOptions
*/
@property (nonatomic, assign, readonly) HTMLNodeFilterShowOptions whatToShow;
@property (nonatomic, strong, readonly) id<HTMLNodeFilter> filter;
+ (instancetype)iteratorWithNode:(HTMLNode *)node
showOptions:(HTMLNodeFilterShowOptions)showOptions
filter:(HTMLNodeFilterValue (^)(HTMLNode *node))filter;
/**
A node filter, that is applied to each node during iteration.
@see HTMLNodeFilter
*/
@property (nonatomic, strong, readonly, nullable) id<HTMLNodeFilter> filter;
/**
Initializes a new node iterator with no filter and HTMLNodeFilterShowAll show options.
@param node The root node.
@returns A new instance of a node iterator.
*/
- (instancetype)initWithNode:(HTMLNode *)node;
/**
Initializes a new node iterator with HTMLNodeFilterShowAll show options.
@param node The root node.
@param filter The node filter to use.
@returns A new instance of a node iterator.
*/
- (instancetype)initWithNode:(HTMLNode *)node
filter:(id<HTMLNodeFilter>)filter;
filter:(nullable id<HTMLNodeFilter>)filter;
/**
Initializes a new node iterator.
@param node The root node.
@param showOptions The show options for the iterator.
@param filter The node filter to use.
@returns A new instance of a node iterator.
*/
- (instancetype)initWithNode:(HTMLNode *)node
showOptions:(HTMLNodeFilterShowOptions)showOptions
filter:(id<HTMLNodeFilter>)filter;
filter:(nullable id<HTMLNodeFilter>)filter;
- (HTMLNode *)nextNode;
- (HTMLNode *)previousNode;
/**
@returns The next iterated node in tree order, `nil` if there are no more nodes to iterate.
*/
- (nullable HTMLNode *)nextNode;
/**
@returns The previous iterated node in tree order, `nil` if there are no more nodes to iterate.
*/
- (nullable HTMLNode *)previousNode;
@end
NS_ASSUME_NONNULL_END
-9
View File
@@ -33,15 +33,6 @@ typedef NS_ENUM(short, TraverseDirection)
#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];
+4
View File
@@ -6,6 +6,10 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLNodeFilter.h"
+74 -9
View File
@@ -8,18 +8,83 @@
#import <Foundation/Foundation.h>
@interface HTMLOrderedDictionary : NSMutableDictionary
NS_ASSUME_NONNULL_BEGIN
- (id)objectAtIndex:(NSUInteger)index;
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey atIndex:(NSUInteger)index;
/**
An ordered mutable dictionary, that preserves the order of its keys.
*/
@interface HTMLOrderedDictionary<KeyType, ObjectType> : NSMutableDictionary<KeyType, ObjectType>
/**
Returns the object at the specified index.
@param index An index within the bounds of the dictionary.
@returns The object located at index.
*/
- (ObjectType)objectAtIndex:(NSUInteger)index;
/**
Sets the object for the given key at the specified index.
@param anObject The object.
@param aKey The key.
@param index An index within the bounds of the dictionary.
*/
- (void)setObject:(ObjectType)anObject forKey:(KeyType<NSCopying>)aKey atIndex:(NSUInteger)index;
/**
Removes the key-value pair located at the specified index.
@param index An index within the bounds of the dictionary.
*/
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceKeyValueAtIndex:(NSUInteger)index withObject:(id)anObject andKey:(id<NSCopying>)aKey;
- (void)replaceKey:(id<NSCopying>)aKey withKey:(id<NSCopying>)newKey;
- (NSUInteger)indexOfKey:(id<NSCopying>)aKey;
- (id)objectAtIndexedSubscript:(NSUInteger)index;
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index;
/**
Replaces the key-value pair located at the specified index.
- (NSEnumerator *)reverseKeyEnumerator;
@param index An index within the bounds of the dictionary.
@param anObject The new object.
@param aKey The new key.
*/
- (void)replaceKeyValueAtIndex:(NSUInteger)index withObject:(ObjectType)anObject andKey:(KeyType<NSCopying>)aKey;
/**
Replaces a key keeping the same object.
@param aKey The old key to replace.
@param newKey The new key.
*/
- (void)replaceKey:(KeyType<NSCopying>)aKey withKey:(KeyType<NSCopying>)newKey;
/**
Returns the index of the given key in the dictionary.
@param aKey The key.
@returns The index of the given key in the dictionary.
*/
- (NSUInteger)indexOfKey:(KeyType<NSCopying>)aKey;
/**
Returns the object at the specified index.
@param index An index within the bounds of the dictionary.
@return The object located at index.
*/
- (ObjectType)objectAtIndexedSubscript:(NSUInteger)index;
/**
Replaces the object at the index with the new object.
@param obj The obj with which to replace the object at given index in the dictionary.
@param index The index of the object to be replaced.
*/
- (void)setObject:(ObjectType)obj atIndexedSubscript:(NSUInteger)index;
/**
@returns A reverse key enumerator.
*/
- (NSEnumerator<KeyType> *)reverseKeyEnumerator;
@end
NS_ASSUME_NONNULL_END
+17
View File
@@ -6,14 +6,31 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
/**
HTML Parse Error Token
*/
@interface HTMLParseErrorToken : HTMLToken
/** @brief The error's reason message. */
@property (nonatomic, copy) NSString *reason;
/** @brief The error's location in the stream. */
@property (nonatomic, assign) NSUInteger location;
/**
Initializes a new Parse Error token.
@param reason The error's reason message.
@param location The error's location in the stream.
@returns A new instance of a parse error token.
*/
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location;
@end
+52
View File
@@ -9,14 +9,66 @@
#import <Foundation/Foundation.h>
#import "HTMLElement.h"
NS_ASSUME_NONNULL_BEGIN
/**
The HTML Parser.
Parses HTML strings to valid HTML documents and/or fragments. This parser implements the WHATWG specification:
https://html.spec.whatwg.org/multipage/syntax.html#tree-construction
@see HTMLDocument
@see HTMLElement
*/
@interface HTMLParser : NSObject
/**
An array of errors that occurred during document parsing.
*/
@property (nonatomic, strong, readonly) NSArray *parseErrors;
/**
The parsed HTML Document.
@see HTMLDocument
*/
@property (nonatomic, strong, readonly) HTMLDocument *document;
/**
Intializes a new parser instance with a given HTML string.
@discussion The parser assumes a UTF-8 encoded string and does not implement the encoding sniffing algorithm that is
described under the following section of the specification:
https://html.spec.whatwg.org/multipage/syntax.html#determining-the-character-encoding
@param string The HTML string to parse
@returns A new instance of the HTML parser.
*/
- (instancetype)initWithString:(NSString *)string;
/**
Runs the parsing algorithm and generates a valid HTML document object.
@returns A HTML document object that is the result of parsing the HTML string, with which this parser instance was
initialized
@see HTMLDocument
*/
- (HTMLDocument *)parseDocument;
/**
Runs the HTML fragment parsing algorithm with the provided context element. The algorithm is sprecified under the
following section: https://html.spec.whatwg.org/multipage/syntax.html#parsing-html-fragments
@discussion The fragment parsing algorithm can be run multiple times with different context elements on the same parser
instance. In this case the parser will reset its internal state and re-run the parsing algorithm.
@param contextElement A context element used for parsing a HTML fragment
@returns An array of HTML elements, that are the result of parsing the given HTML string with the given context element.
@see HTMLElement
*/
- (NSArray *)parseFragmentWithContextElement:(HTMLElement *)contextElement;
@end
NS_ASSUME_NONNULL_END
+114 -102
View File
@@ -17,6 +17,7 @@
#import "HTMLElementAdjustment.h"
#import "HTMLMarker.h"
#import "NSString+HTMLKit.h"
#import "CSSSelectors.h"
@interface HTMLTokenizer (Private)
@property (nonatomic, weak) HTMLParser *parser;
@@ -107,6 +108,7 @@
if (_document == nil) {
_document = [HTMLDocument new];
}
_document.readyState = HTMLDocumentLoading;
_document.quirksMode = HTMLQuirksModeNoQuirks;
_document.documentType = nil;
[_document removeAllChildNodes];
@@ -124,11 +126,12 @@
- (NSArray *)parseFragmentWithContextElement:(HTMLElement *)contextElement
{
if (contextElement == nil) {
return nil;
return @[];
}
if ([_contextElement isEqual:contextElement]) {
return [_document childNodeAtIndex:0].childNodes.array;
HTMLElement *root = [_document firstElementMatchingSelector:rootSelector()];
return root? root.childNodes.objectEnumerator.allObjects: @[];
}
[self initializeDocument];
@@ -173,7 +176,7 @@
[self runParser];
return root.childNodes.array;
return root.childNodes.objectEnumerator.allObjects;
}
- (void)runParser
@@ -516,7 +519,7 @@
- (void)generateImpliedEndTagsExceptForElement:(NSString *)tagName
{
while ([self.currentNode.tagName isEqualToAny:@"dd", @"dt", @"li", @"option", @"optgroup", @"p", @"rp", @"rt", nil] &&
while ([self.currentNode.tagName isEqualToAny:@"dd", @"dt", @"li", @"option", @"optgroup", @"p", @"rb", @"rp", @"rt", @"rtc", nil] &&
![self.currentNode.tagName isEqualToString:tagName]) {
[_stackOfOpenElements popCurrentNode];
}
@@ -525,7 +528,7 @@
- (void)generateAllImpliedEndTagsThoroughly
{
while ([self.currentNode.tagName isEqualToAny:@"caption", @"colgroup", @"dd", @"dt", @"li", @"option", @"optgroup", @"p",
@"rp", @"rt", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", nil]) {
@"rb", @"rp", @"rt", @"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", nil]) {
[_stackOfOpenElements popCurrentNode];
}
}
@@ -555,18 +558,18 @@
}
if (![_stackOfOpenElements containsElement:formattingElement]) {
[self emitParseError:@"Formatting element is not in the Stack of Open Elements"];
[self emitParseError:@"Formatting element <%@> is not in the Stack of Open Elements (Adoption Agency)", tagName];
[_listOfActiveFormattingElements removeElement:formattingElement];
return NO;
}
if (![_stackOfOpenElements hasElementInScopeWithTagName:formattingElement.tagName]) {
[self emitParseError:@"Formatting element is not in scope"];
[self emitParseError:@"Formatting element <%@> is not in scope (Adoption Agency)", tagName];
return NO;
}
if (![formattingElement isEqual:self.currentNode]) {
[self emitParseError:@"Formatting element is not the current node"];
[self emitParseError:@"Formatting element <%@> is not the current node (Adoption Agency)", tagName];
}
NSUInteger formattingElementIndex = [_stackOfOpenElements indexOfElement:formattingElement];
@@ -638,7 +641,7 @@
{
[self generateImpliedEndTagsExceptForElement:nil];
if (![self.currentNode.tagName isEqualToAny:@"td", @"th", nil]) {
[self emitParseError:@"Misnested Cell"];
[self emitParseError:@"Closing misnested Cell <%@>", self.currentNode.tagName];
}
[_stackOfOpenElements popElementsUntilAnElementPoppedWithAnyOfTagNames:@[@"td", @"th"]];
[_listOfActiveFormattingElements clearUptoLastMarker];
@@ -812,7 +815,7 @@
break;
}
[self emitParseError:@"Expected a DOCTYPE but got %@", token];
[self emitParseError:@"Expected a DOCTYPE"];
_document.quirksMode = HTMLQuirksModeQuirks;
[self switchInsertionMode:HTMLInsertionModeBeforeHTML];
[self reprocessToken:token];
@@ -847,7 +850,7 @@
break;
case HTMLTokenTypeEndTag:
if (![token.asEndTagToken.tagName isEqualToAny:@"head", @"body", @"html", @"br", nil]) {
[self emitParseError:@"Unexpected End Tag Token (%@) before <html>", token.asEndTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> before <html>", token.asEndTagToken.tagName];
return;
}
break;
@@ -893,7 +896,7 @@
return;
case HTMLTokenTypeEndTag:
if (![token.asEndTagToken.tagName isEqualToAny:@"head", @"body", @"html", @"br", nil]) {
[self emitParseError:@"Unexpected End Tag Token (%@) before <head>", token.asEndTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> before <head>", token.asEndTagToken.tagName];
return;
}
break;
@@ -953,7 +956,7 @@
_originalInsertionMode = _insertionMode;
[self switchInsertionMode:HTMLInsertionModeText];
} else if ([token.asStartTagToken.tagName isEqualToString:@"head"]) {
[self emitParseError:@"Unexpected Start Tag Token (head) in <head>"];
[self emitParseError:@"Unexpected start tag <head> in <head>"];
} else if ([token.asStartTagToken.tagName isEqualToString:@"template"]) {
HTMLTemplate *template = [HTMLTemplate new];
[self insertElement:template];
@@ -973,19 +976,19 @@
break;
} else if ([token.asEndTagToken.tagName isEqualToString:@"template"]) {
if (![_stackOfOpenElements containsElementWithTagName:@"template"]) {
[self emitParseError:@"Unexpected End Tag Token (template) in <head>"];
[self emitParseError:@"Unexpected end tag </template> in <head>"];
return;
}
[self generateAllImpliedEndTagsThoroughly];
if (![self.currentNode.tagName isEqualToString:@"template"]) {
[self emitParseError:@"Unexpected End Tag Token in <head>"];
[self emitParseError:@"Unexpected end tag </%@> in <head>", self.currentNode.tagName];
}
[_stackOfOpenElements popElementsUntilTemplateElementPopped];
[_listOfActiveFormattingElements clearUptoLastMarker];
[_stackOfTemplateInsertionModes removeLastObject];
[self resetInsertionModeAppropriately];
} else {
[self emitParseError:@"Unexpected End Tag Token (%@) in <head>", token.asEndTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <head>", token.asEndTagToken.tagName];
return;
}
return;
@@ -1013,7 +1016,7 @@
[self HTMLInsertionModeInHead:token];
return;
} else if ([token.asStartTagToken.tagName isEqualToAny:@"head", @"noscript", nil]) {
[self emitParseError:@"Unexpected Start Tag Token (%@) in <head><noscript>", token.asStartTagToken.tagName];
[self emitParseError:@"Unexpected start tag <%@> in <head><noscript>", token.asStartTagToken.tagName];
return;
} else {
break;
@@ -1027,7 +1030,7 @@
} else if ([token.asEndTagToken.tagName isEqualToString:@"br"]) {
break;
} else {
[self emitParseError:@"Unexpected End Tag Token (%@) in <head><noscript>", token.asEndTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <head><noscript>", token.asEndTagToken.tagName];
return;
}
return;
@@ -1039,7 +1042,7 @@
break;
}
[self emitParseError:@"Unexpected Tag Token (%@) in <head><noscript>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected Tag Token <%@> in <head><noscript>", token.asTagToken.tagName];
[_stackOfOpenElements popCurrentNode];
[self switchInsertionMode:HTMLInsertionModeInHead];
[self reprocessToken:token];
@@ -1081,13 +1084,13 @@
return;
} else if ([token.asStartTagToken.tagName isEqualToAny:@"base", @"basefont", @"bgsound", @"link", @"meta",
@"noframes", @"script", @"style", @"template", @"title", nil]) {
[self emitParseError:@"Unexpected Start Tag Token (%@) after <head>", token.asStartTagToken.tagName];
[self emitParseError:@"Unexpected start tag <%@> after <head>", token.asStartTagToken.tagName];
[_stackOfOpenElements pushElement:_headElementPointer];
[self HTMLInsertionModeInHead:token];
[_stackOfOpenElements removeElement:_headElementPointer];
return;
} else if ([token.asStartTagToken.tagName isEqualToString:@"html"]) {
[self emitParseError:@"Unexpected Start Tag Token (head) after <head>"];
[self emitParseError:@"Unexpected start tag <head> after <head>"];
return;
} else {
break;
@@ -1100,7 +1103,7 @@
} else if ([token.asEndTagToken.tagName isEqualToAny:@"body", @"html", @"br", nil]) {
break;
} else {
[self emitParseError:@"Unexpected End Tag Token (%@) after <head>", token.asEndTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> after <head>", token.asEndTagToken.tagName];
return;
}
return;
@@ -1155,9 +1158,9 @@
[self HTMLInsertionModeInTemplate:token];
} else {
for (HTMLElement *node in _stackOfOpenElements) {
if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"optgroup", @"option", @"p", @"rp"
@"rt", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) {
[self emitParseError:@"EOF reached with unclosed element (%@)", node.tagName];
if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"optgroup", @"option", @"p", @"rb", @"rp",
@"rt", @"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) {
[self emitParseError:@"EOF reached with unclosed element <%@> in <body>", node.tagName];
break;
}
}
@@ -1174,7 +1177,7 @@
NSString *tagName = token.tagName;
if ([tagName isEqualToString:@"html"]) {
[self emitParseError:@"Unexpected Start Tag Token (html) in <body>"];
[self emitParseError:@"Unexpected start tag <html> in <body>"];
if ([_stackOfOpenElements containsElementWithTagName:@"template"]) {
return;
}
@@ -1188,7 +1191,7 @@
@"noframes", @"script", @"style", @"template", @"title", nil]) {
[self HTMLInsertionModeInHead:token];
} else if ([tagName isEqualToString:@"body"]) {
[self emitParseError:@"Unexpected Start Tag Token (body) in <body>"];
[self emitParseError:@"Unexpected start tag <body> in <body>"];
if (_stackOfOpenElements.count < 2 ||
![[_stackOfOpenElements[1] tagName] isEqualToString:@"body"] ||
[_stackOfOpenElements containsElementWithTagName:@"template"]) {
@@ -1202,7 +1205,7 @@
}
}
} else if ([tagName isEqualToString:@"frameset"]) {
[self emitParseError:@"Unexpected Start Tag Token (frameset) in <body>"];
[self emitParseError:@"Unexpected start tag <frameset> in <body>"];
if (_stackOfOpenElements.count == 1 ||
![[_stackOfOpenElements[1] tagName] isEqualToString:@"body"]) {
return;
@@ -1230,7 +1233,7 @@
[self closePElement];
}
if ([self.currentNode.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
[self emitParseError:@"Unexpected nested Start Tag Token (%@) in <body>", self.currentNode.tagName];
[self emitParseError:@"Unexpected nested Start Tag Token <%@> in <body>", self.currentNode.tagName];
[_stackOfOpenElements popCurrentNode];
}
[self insertElementForToken:token];
@@ -1244,7 +1247,7 @@
} else if ([tagName isEqualToString:@"form"]) {
if (_formElementPointer != nil &&
![_stackOfOpenElements containsElementWithTagName:@"template"]) {
[self emitParseError:@"Unexpected nested Start Tag Token (form) in <body>"];
[self emitParseError:@"Unexpected nested Start Tag Token <form> in <body>"];
} else {
if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) {
[self closePElement];
@@ -1269,7 +1272,7 @@
if ([map[tagName] containsObject:node.tagName]) {
[self generateImpliedEndTagsExceptForElement:node.tagName];
if (![self.currentNode.tagName isEqualToString:node.tagName]) {
[self emitParseError:@"Unexpected Start Tag (%@) in <body>", node.tagName];
[self emitParseError:@"Unexpected Start Tag <%@> in <body>", node.tagName];
}
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:node.tagName];
break;
@@ -1290,7 +1293,7 @@
_tokenizer.state = HTMLTokenizerStatePLAINTEXT;
} else if ([tagName isEqualToString:@"button"]) {
if ([_stackOfOpenElements hasElementInScopeWithTagName:@"button"]) {
[self emitParseError:@"Unexpected nested Start Tag (button) tag in <body>"];
[self emitParseError:@"Unexpected nested Start Tag <button> in <body>"];
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:@"button"];
}
[self reconstructActiveFormattingElements];
@@ -1307,7 +1310,7 @@
return nil;
}();
if (element != nil) {
[self emitParseError:@"Unexpected nested Start Tag (a) in <body>"];
[self emitParseError:@"Unexpected nested Start Tag <a> in <body>"];
if ([self runAdoptionAgencyAlgorithmForTagName:@"a"]) {
[self processAnyOtherEndTagTokenInBody:token.asTagToken];
return;
@@ -1326,7 +1329,7 @@
} else if ([tagName isEqualToString:@"nobr"]) {
[self reconstructActiveFormattingElements];
if ([_stackOfOpenElements hasElementInScopeWithTagName:@"nobr"]) {
[self emitParseError:@"Unexpected nested Start Tag (nobr) in <body>"];
[self emitParseError:@"Unexpected nested Start Tag <nobr> in <body>"];
if ([self runAdoptionAgencyAlgorithmForTagName:@"nobr"]) {
[self processAnyOtherEndTagTokenInBody:token.asTagToken];
return;
@@ -1372,11 +1375,11 @@
[_stackOfOpenElements popCurrentNode];
_framesetOkFlag = NO;
} else if ([tagName isEqualToString:@"image"]) {
[self emitParseError:@"Image Start Tag Token with tagname (image) should be (img). Don't ask."];
[self emitParseError:@"Image Start Tag Token with tagname <image> should be <img>. Don't ask."];
token.tagName = @"img";
[self reprocessToken:token];
} else if ([tagName isEqualToString:@"isindex"]) {
[self emitParseError:@"Unexpected Start Tag Token (isindex) in <body>"];
[self emitParseError:@"Unexpected start tag <isindex> in <body>"];
if (_formElementPointer != nil && ![_stackOfOpenElements containsElementWithTagName:@"template"]) {
return;
}
@@ -1459,11 +1462,20 @@
}
[self reconstructActiveFormattingElements];
[self insertElementForToken:token];
} else if ([tagName isEqualToAny:@"rp", @"rt", nil]) {
} else if ([tagName isEqualToAny:@"rb", @"rtc", nil]) {
if ([_stackOfOpenElements hasElementInScopeWithTagName:@"ruby"]) {
[self generateImpliedEndTagsExceptForElement:nil];
if (![self.currentNode.tagName isEqualToString:@"ruby"]) {
[self emitParseError:@"Unexpected Start Tag Token (%@) not in <ruby> in <body>", tagName];
[self emitParseError:@"Unexpected start tag <%@> outside of <ruby> in <body>", tagName];
}
}
[self insertElementForToken:token];
} else if ([tagName isEqualToAny:@"rp", @"rt", nil]) {
if ([_stackOfOpenElements hasElementInScopeWithTagName:@"ruby"]) {
[self generateImpliedEndTagsExceptForElement:@"rtc"];
if (![self.currentNode.tagName isEqualToString:@"rtc"] &&
![self.currentNode.tagName isEqualToString:@"ruby"]) {
[self emitParseError:@"Unexpected start tag <%@> outside of <ruby> or <rtc> in <body>", tagName];
}
}
[self insertElementForToken:token];
@@ -1485,7 +1497,7 @@
}
} else if ([tagName isEqualToAny:@"caption", @"col", @"colgroup", @"frame", @"head", @"tbody", @"td",
@"tfoot", @"th", @"thead", @"tr", nil]) {
[self emitParseError:@"Unexpected Start Tag Token (%@) in <body>", tagName];
[self emitParseError:@"Unexpected start tag <%@> in <body>", tagName];
} else {
[self reconstructActiveFormattingElements];
[self insertElementForToken:token];
@@ -1501,12 +1513,12 @@
} else if ([tagName isEqualToAny:@"body", @"html", nil]) {
// End tags "body" & "html" are identical, expect for the reprocessing step
if (![_stackOfOpenElements hasElementInScopeWithTagName:@"body"]) {
[self emitParseError:@"End Tag (body) without body element in scope in <body>"];
[self emitParseError:@"Unexpected end tag </body> without body element in scope in <body>"];
}
for (HTMLElement *node in _stackOfOpenElements) {
if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"optgroup", @"option", @"p", @"rp"
@"rt", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) {
[self emitParseError:@"End Tag (%@) with open element (%@) in <body>", tagName, node.tagName];
if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"optgroup", @"option", @"p", @"rb", @"rp", @"rt",
@"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) {
[self emitParseError:@"Misnested end tag </%@> with open element <%@> in <body>", tagName, node.tagName];
break;
}
}
@@ -1519,12 +1531,12 @@
@"figure", @"footer", @"header", @"hgroup", @"listing", @"main", @"menu", @"nav",
@"ol", @"pre", @"section", @"summary", @"ul", nil]) {
if (![_stackOfOpenElements hasElementInScopeWithTagName:tagName]) {
[self emitParseError:@"End Tag (%@) with open element in <body>", tagName];
[self emitParseError:@"Misnested end tag </%@> with open element in <body>", tagName];
return;
}
[self generateImpliedEndTagsExceptForElement:nil];
if (![self.currentNode.tagName isEqualToString:tagName]) {
[self emitParseError:@"Unexpected End Tag Token (%@) in <body>", tagName];
[self emitParseError:@"Unexpected end tag </%@> in <body>", tagName];
}
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:tagName];
} else if ([tagName isEqualToString:@"form"]) {
@@ -1532,60 +1544,60 @@
HTMLElement *node = _formElementPointer;
_formElementPointer = nil;
if (node == nil || ![_stackOfOpenElements hasElementInScopeWithTagName:node.tagName]) {
[self emitParseError:@"Unexpected closed (form) element in <body>"];
[self emitParseError:@"Misnested <form> element in <body>"];
return;
}
[self generateImpliedEndTagsExceptForElement:nil];
if ([self.currentNode isEqual:node]) {
[self emitParseError:@"Unexpected nested (form) element in <body>"];
[self emitParseError:@"Unexpected nested <form> element in <body>"];
}
[_stackOfOpenElements removeElement:node];
} else {
if ([_stackOfOpenElements hasElementInScopeWithTagName:@"form"]) {
[self emitParseError:@"Unexpected closed (form) element in <body>"];
[self emitParseError:@"Misnested <form> element in <body>"];
return;
}
[self generateImpliedEndTagsExceptForElement:nil];
if (![self.currentNode.tagName isEqualToString:@"form"]) {
[self emitParseError:@"Unexpected open element in <body>"];
[self emitParseError:@"Misnested <form> element with open <%@> in <body>", self.currentNode.tagName];
}
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:@"form"];
}
} else if ([tagName isEqualToString:@"p"]) {
if (![_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) {
[self emitParseError:@"Unexpected End Tag Token (p) in <body>"];
[self emitParseError:@"Unexpected <p> element in <body>"];
HTMLEndTagToken *pToken = [[HTMLEndTagToken alloc] initWithTagName:@"p"];
[self insertElementForToken:pToken];
}
[self closePElement];
} else if ([tagName isEqualToString:@"li"]) {
if (![_stackOfOpenElements hasElementInListItemScopeWithTagName:@"li"]) {
[self emitParseError:@"Unexpected closed (li) element in <body>"];
[self emitParseError:@"Unexpected <li> element in <body>"];
return;
}
[self generateImpliedEndTagsExceptForElement:@"li"];
if (![self.currentNode.tagName isEqualToString:@"li"]) {
[self emitParseError:@"Unexpected nested (li) element in <body>"];
[self emitParseError:@"Unexpected end tag </li> in <body>"];
}
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:@"li"];
} else if ([tagName isEqualToAny:@"dd", @"dt", nil]) {
if (![_stackOfOpenElements hasElementInScopeWithTagName:tagName]) {
[self emitParseError:@"Unexpected closed (%@) element in <body>", tagName];
[self emitParseError:@"Unexpected <%@> element in <body>", tagName];
return;
}
[self generateImpliedEndTagsExceptForElement:tagName];
if ([self.currentNode.tagName isEqualToString:@"li"]) {
[self emitParseError:@"Unexpected nested (%@) element in <body>", tagName];
[self emitParseError:@"Unexpected end tag </%@> in <body>", tagName];
}
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:tagName];
} else if ([tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
if (![_stackOfOpenElements hasAnyElementInScopeWithAnyOfTagNames:@[@"h1", @"h2", @"h3", @"h4", @"h5", @"h6"]]) {
[self emitParseError:@"Unexpected closed (%@) element in <body>", tagName];
[self emitParseError:@"Unexpected <%@> element in <body>", tagName];
return;
}
[self generateImpliedEndTagsExceptForElement:nil];
if (![self.currentNode.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
[self emitParseError:@"Unexpected nested (%@) element in <body>", tagName];
[self emitParseError:@"Unexpected end tag </%@> in <body>", tagName];
}
[_stackOfOpenElements popElementsUntilAnElementPoppedWithAnyOfTagNames:@[@"h1", @"h2", @"h3", @"h4", @"h5", @"h6"]];
} else if ([tagName isEqualToString:@"sarcasm"]) {
@@ -1600,17 +1612,17 @@
}
} else if ([tagName isEqualToAny:@"applet", @"marquee", @"object", nil]) {
if (![_stackOfOpenElements hasAnyElementInScopeWithAnyOfTagNames:@[@"applet", @"marquee", @"object"]]) {
[self emitParseError:@"Unexpected closed (%@) element in <body>", tagName];
[self emitParseError:@"Unexpected <%@> element in <body>", tagName];
return;
}
[self generateImpliedEndTagsExceptForElement:nil];
if (![self.currentNode.tagName isEqualToAny:@"applet", @"marquee", @"object", nil]) {
[self emitParseError:@"Unexpected nested (%@) element in <body>", tagName];
[self emitParseError:@"Unexpected end tag </%@> in <body>", tagName];
}
[_stackOfOpenElements popElementsUntilAnElementPoppedWithAnyOfTagNames:@[@"applet", @"marquee", @"object"]];
[_listOfActiveFormattingElements clearUptoLastMarker];
} else if ([tagName isEqualToString:@"br"]) {
[self emitParseError:@"Unexpected End Tag Token (br) in <body>"];
[self emitParseError:@"Unexpected end tag </br> in <body>"];
HTMLStartTagToken *brToken = [[HTMLStartTagToken alloc] initWithTagName:@"br"];
[self processStartTagTokenInBody:brToken];
} else {
@@ -1624,12 +1636,12 @@
if ([node.tagName isEqualToString:token.tagName]) {
[self generateImpliedEndTagsExceptForElement:token.tagName];
if (![node.tagName isEqualToString:self.currentNode.tagName]) {
[self emitParseError:@"Unexpected nested End Tag Token (%@) in <body>", node.tagName];
[self emitParseError:@"Unexpected <%@> element in <body>", node.tagName];
}
[_stackOfOpenElements popElementsUntilElementPopped:node];
break;
} else if (IsSpecialElement(node)) {
[self emitParseError:@"Unexpected End Tag Token (%@) in <body>", node.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <body>", node.tagName];
return;
}
}
@@ -1642,7 +1654,7 @@
[self insertCharacters:token.asCharacterToken.characters];
return;
case HTMLTokenTypeEOF:
[self emitParseError:@"Unexpected EOF Token reached in 'text' insertion mode"];
[self emitParseError:@"EOF reached in 'text' insertion mode"];
[_stackOfOpenElements popCurrentNode];
[self switchInsertionMode:_originalInsertionMode];
[self reprocessToken:token];
@@ -1703,7 +1715,7 @@
[self switchInsertionMode:HTMLInsertionModeInTableBody];
[self reprocessToken:token];
} else if ([token.asTagToken.tagName isEqualToString:@"table"]) {
[self emitParseError:@"Unexpected nested Start Tag Token (table) in <table>"];
[self emitParseError:@"Unexpected start tag <table> in <table>"];
if (![_stackOfOpenElements hasElementInTableScopeWithTagName:@"table"]) {
return;
}
@@ -1717,12 +1729,12 @@
if (type == nil || ![type isEqualToStringIgnoringCase:@"hidden"]) {
break;
} else {
[self emitParseError:@"Unexpected non-hidden Start Tag Token (input) in <table>"];
[self emitParseError:@"Unexpected non-hidden start tag <input> in <table>"];
[self insertElementForToken:token.asTagToken];
[_stackOfOpenElements popCurrentNode];
}
} else if ([token.asTagToken.tagName isEqualToString:@"form"]) {
[self emitParseError:@"Unexpected Start Tag Token (form) in <table>"];
[self emitParseError:@"Unexpected start tag <form> in <table>"];
if (_formElementPointer != nil || [_stackOfOpenElements containsElementWithTagName:@"template"]) {
return;
}
@@ -1736,7 +1748,7 @@
case HTMLTokenTypeEndTag:
if ([token.asTagToken.tagName isEqualToString:@"table"]) {
if (![_stackOfOpenElements hasElementInTableScopeWithTagName:@"table"]) {
[self emitParseError:@"Unexpected End Tag Token (table) for element in <table>"];
[self emitParseError:@"Unexpected end tag </table> for misnested element in <table>"];
return;
}
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:@"table"];
@@ -1744,7 +1756,7 @@
return;
} else if ([token.asTagToken.tagName isEqualToAny:@"body", @"caption", @"col", @"colgroup", @"html",
@"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", nil]) {
[self emitParseError:@"Unexpected End Tag Token (%@) in <table>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <table>", token.asTagToken.tagName];
return;
} else if ([token.asTagToken.tagName isEqualToString:@"template"]) {
[self HTMLInsertionModeInHead:token];
@@ -1764,7 +1776,7 @@
- (void)processAnythingElseInTable:(HTMLToken *)token
{
[self emitParseError:@"Unexpected Token foster parenting in <table>"];
[self emitParseError:@"Unexpected token foster parenting in <table>"];
_fosterParenting = YES;
[self HTMLInsertionModeInBody:token];
_fosterParenting = NO;
@@ -1806,12 +1818,12 @@
{
void (^ common) (BOOL) = ^ (BOOL reprocess) {
if (![_stackOfOpenElements hasElementInTableScopeWithTagName:@"caption"]) {
[self emitParseError:@"Unexpected Tag Token (caption) for element in <caption>"];
[self emitParseError:@"Unexpected end tag </caption> for misnested element in <caption>"];
return;
}
[self generateImpliedEndTagsExceptForElement:nil];
if (![self.currentNode.tagName isEqualToString:@"caption"]) {
[self emitParseError:@"Unexpected nested (caption) element in <caption>"];
[self emitParseError:@"Misnested <caption> element in <caption>"];
}
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:@"caption"];
[_listOfActiveFormattingElements clearUptoLastMarker];
@@ -1830,7 +1842,7 @@
common(YES);
} else if ([token.asTagToken.tagName isEqualToAny:@"body", @"col", @"colgroup", @"html",
@"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", nil]) {
[self emitParseError:@"Unexpected End Tag Token (%@) in <caption>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <caption>", token.asTagToken.tagName];
} else {
break;
}
@@ -1886,14 +1898,14 @@
case HTMLTokenTypeEndTag:
if ([token.asTagToken.tagName isEqualToString:@"colgroup"]) {
if (![self.currentNode.tagName isEqualToString:@"colgroup"]) {
[self emitParseError:@"Unexpected nested (colgroup) element in <colgroup>"];
[self emitParseError:@"Unexpected end tag </colgroup> for misnested element in <colgroup>"];
return;
} else {
[_stackOfOpenElements popCurrentNode];
[self switchInsertionMode:HTMLInsertionModeInTable];
}
} else if ([token.asTagToken.tagName isEqualToString:@"col"]) {
[self emitParseError:@"Unexpected End Tag Token (col) in <colgroup>"];
[self emitParseError:@"Unexpected end tag </col> in <colgroup>"];
} else if ([token.asTagToken.tagName isEqualToString:@"template"]) {
[self HTMLInsertionModeInHead:token];
} else {
@@ -1908,7 +1920,7 @@
}
if (![self.currentNode.tagName isEqualToString:@"colgroup"]) {
[self emitParseError:@"Unexpected Token in <colgroup>"];
[self emitParseError:@"Unexpected tag '%@' in <colgroup>", self.currentNode.tagName];
return;
}
[_stackOfOpenElements popCurrentNode];
@@ -1920,7 +1932,7 @@
{
void (^ common) (BOOL) = ^ (BOOL reprocess) {
if (![_stackOfOpenElements hasElementInTableScopeWithAnyOfTagNames:@[@"tbody", @"tfoot", @"thead"]]) {
[self emitParseError:@"Unexpected Tag Token (%@) for element in <tbody>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected tag '%@' for misnested element in <tbody>", token.asTagToken.tagName];
return;
} else {
[_stackOfOpenElements clearBackToTableBodyContext];
@@ -1940,7 +1952,7 @@
[self insertElementForToken:token.asTagToken];
[self switchInsertionMode:HTMLInsertionModeInRow];
} else if ([token.asTagToken.tagName isEqualToAny:@"th", @"td", nil]) {
[self emitParseError:@"Unexpected Start Tag Token (%@) in <tbody>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected start tag <%@> in <tbody>", token.asTagToken.tagName];
[_stackOfOpenElements clearBackToTableBodyContext];
HTMLStartTagToken *trToken = [[HTMLStartTagToken alloc] initWithTagName:@"tr"];
[self insertElementForToken:trToken];
@@ -1960,7 +1972,7 @@
common(YES);
} else if ([token.asTagToken.tagName isEqualToAny:@"body", @"caption", @"col", @"colgroup",
@"html", @"td", @"th", @"tr", nil]) {
[self emitParseError:@"Unexpected End Tag Token (%@) in <tbody>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <tbody>", token.asTagToken.tagName];
} else {
break;
}
@@ -1976,7 +1988,7 @@
{
void (^ common) (NSString *, BOOL) = ^ (NSString *elementTagName, BOOL reprocess) {
if (![_stackOfOpenElements hasElementInTableScopeWithTagName:elementTagName]) {
[self emitParseError:@"Unexpected Tag Token (%@) for element (%@) in <tr>", token.asTagToken.tagName, elementTagName];
[self emitParseError:@"Unexpected tag '%@' for misnested element <%@> in <tr>", token.asTagToken.tagName, elementTagName];
return;
} else {
[_stackOfOpenElements clearBackToTableRowContext];
@@ -2012,7 +2024,7 @@
common(token.asTagToken.tagName, NO);
} else if ([token.asTagToken.tagName isEqualToAny:@"body", @"caption", @"col", @"colgroup",
@"html", @"td", @"th", nil]) {
[self emitParseError:@"Unexpected End Tag Token (%@) in <tr>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <tr>", token.asTagToken.tagName];
} else {
break;
}
@@ -2030,12 +2042,12 @@
case HTMLTokenTypeEndTag:
if ([token.asTagToken.tagName isEqualToAny:@"td", @"th", nil]) {
if (![_stackOfOpenElements hasElementInTableScopeWithTagName:token.asTagToken.tagName]) {
[self emitParseError:@"Unexpected Tag Token (%@) for element in <td>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected tag '%@' for misnested element in <td>", token.asTagToken.tagName];
return;
} else {
[self generateImpliedEndTagsExceptForElement:nil];
if (![self.currentNode.tagName isEqualToString:token.asTagToken.tagName]) {
[self emitParseError:@"Unexpected nested (%@) element in <td>", token.asTagToken.tagName];
[self emitParseError:@"Misnested element <%@> in <td>", token.asTagToken.tagName];
}
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:token.asTagToken.tagName];
[_listOfActiveFormattingElements clearUptoLastMarker];
@@ -2043,10 +2055,10 @@
}
} else if ([token.asTagToken.tagName isEqualToAny:@"body", @"caption", @"col", @"colgroup",
@"html", nil]) {
[self emitParseError:@"Unexpected End Tag Token (%@) in <td>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <td>", token.asTagToken.tagName];
} else if ([token.asTagToken.tagName isEqualToAny:@"table", @"tbody", @"tfoot", @"thhead", @"tr", nil]) {
if (![_stackOfOpenElements hasElementInTableScopeWithTagName:token.asTagToken.tagName]) {
[self emitParseError:@"Unexpected End Tag Token (%@) for element in <td>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> for misnested element in <td>", token.asTagToken.tagName];
return;
} else {
[self closeTheCell];
@@ -2060,7 +2072,7 @@
if ([token.asTagToken.tagName isEqualToAny:@"caption", @"col", @"colgroup", @"tbody",
@"td", @"tfoot", @"th", @"thead", @"tr", nil]) {
if (![_stackOfOpenElements hasElementInTableScopeWithAnyOfTagNames:@[@"td", @"th"]]) {
[self emitParseError:@"Unexpected Start Tag Token (%@) for element in <td>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected start tag <%@> for misnested element in <td>", token.asTagToken.tagName];
return;
} else {
[self closeTheCell];
@@ -2119,7 +2131,7 @@
}
[self insertElementForToken:token.asTagToken];
} else if ([token.asTagToken.tagName isEqualToString:@"select"]) {
[self emitParseError:@"Unexpect Start Tag Token (select) in <select>"];
[self emitParseError:@"Unexpect start tag <select> in <select>"];
if (![_stackOfOpenElements hasElementInSelectScopeWithTagName:@"select"]) {
return;
} else {
@@ -2127,7 +2139,7 @@
[self resetInsertionModeAppropriately];
}
} else if ([token.asTagToken.tagName isEqualToAny:@"input", @"keygen", @"textarea", nil]) {
[self emitParseError:@"Unexpect Start Tag Token (%@) in <select>", token.asTagToken.tagName];
[self emitParseError:@"Unexpect start tag <%@> in <select>", token.asTagToken.tagName];
if (![_stackOfOpenElements hasElementInSelectScopeWithTagName:@"select"]) {
return;
} else {
@@ -2151,19 +2163,19 @@
if ([self.currentNode.tagName isEqualToString:@"optgroup"]) {
[_stackOfOpenElements popCurrentNode];
} else {
[self emitParseError:@"Unexpected nested End Tag Token (optgroup) for element in <select>"];
[self emitParseError:@"Unexpected end tag </optgroup> for misnested element in <select>"];
return;
}
} else if ([token.asTagToken.tagName isEqualToString:@"option"]) {
if ([self.currentNode.tagName isEqualToString:@"option"]) {
[_stackOfOpenElements popCurrentNode];
} else {
[self emitParseError:@"Unexpected nested End Tag Token (option) for element in <select>"];
[self emitParseError:@"Unexpected end tag </option> for misnested element in <select>"];
return;
}
} else if ([token.asTagToken.tagName isEqualToString:@"select"]) {
if (![_stackOfOpenElements hasElementInSelectScopeWithTagName:@"select"]) {
[self emitParseError:@"Unexpected End Tag Token (select) for element in <select>"];
[self emitParseError:@"Unexpected end tag </select> for misnested lement in <select>"];
return;
} else {
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:@"select"];
@@ -2182,7 +2194,7 @@
break;
}
[self emitParseError:@"Unexpected Token in <select>"];
[self emitParseError:@"Unexpected token in <select>"];
}
- (void)HTMLInsertionModeInSelectInTable:(HTMLToken *)token
@@ -2191,7 +2203,7 @@
case HTMLTokenTypeStartTag:
if ([token.asTagToken.tagName isEqualToAny:@"caption", @"table", @"tbody", @"tfoot", @"thead",
@"tr", @"td", @"th", nil]) {
[self emitParseError:@"Unexpected Start Tag Token (%@) in <select> in <table>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected start tag <%@> in <select> in <table>", token.asTagToken.tagName];
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:@"select"];
[self resetInsertionModeAppropriately];
[self reprocessToken:token];
@@ -2200,7 +2212,7 @@
case HTMLTokenTypeEndTag:
if ([token.asTagToken.tagName isEqualToAny:@"caption", @"table", @"tbody", @"tfoot", @"thead",
@"tr", @"td", @"th", nil]) {
[self emitParseError:@"Unexpected End Tag Token (%@) in <select> in <table>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <select> in <table>", token.asTagToken.tagName];
if (![_stackOfOpenElements hasElementInTableScopeWithTagName:token.asTagToken.tagName]) {
return;
}
@@ -2264,7 +2276,7 @@
if ([token.asTagToken.tagName isEqualToString:@"template"]) {
[self HTMLInsertionModeInHead:token];
} else {
[self emitParseError:@"Unexpected End Tag Token (%@) in <template>", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> in <template>", token.asTagToken.tagName];
}
return;
case HTMLTokenTypeEOF:
@@ -2313,7 +2325,7 @@
case HTMLTokenTypeEndTag:
if ([token.asTagToken.tagName isEqualToString:@"html"]) {
if (_fragmentParsingAlgorithm) {
[self emitParseError:@"Unexpected End Tag Token (html) fragment parsing afeter <body>"];
[self emitParseError:@"Unexpected end tag </html> in fragment parsing after <body>"];
return;
}
[self switchInsertionMode:HTMLInsertionModeAfterAfterBody];
@@ -2327,7 +2339,7 @@
break;
}
[self emitParseError:@"Unexpected Token after <body>"];
[self emitParseError:@"Unexpected token after <body>"];
[self switchInsertionMode:HTMLInsertionModeInBody];
[self reprocessToken:token];
}
@@ -2375,7 +2387,7 @@
if ([token.asTagToken.tagName isEqualToString:@"frameset"]) {
if (self.currentNode == _stackOfOpenElements.firstNode &&
[self.currentNode.tagName isEqualToString:@"html"]) {
[self emitParseError:@"Unexpected nested End Tag (frameset) in <frameset>"];
[self emitParseError:@"Unexpected end tag </frameset> for misnested element in <frameset>"];
return;
} else {
[_stackOfOpenElements popCurrentNode];
@@ -2397,7 +2409,7 @@
break;
}
[self emitParseError:@"Unexpected Token in <frameset>"];
[self emitParseError:@"Unexpected token in <frameset>"];
}
- (void)HTMLInsertionModeAfterFrameset:(HTMLToken *)token
@@ -2447,7 +2459,7 @@
break;
}
[self emitParseError:@"Unexpected Token after <frameset>"];
[self emitParseError:@"Unexpected token after <frameset>"];
}
- (void)HTMLInsertionModeAfterAfterBody:(HTMLToken *)token
@@ -2483,7 +2495,7 @@
break;
}
[self emitParseError:@"Unexpected Token after after <body>"];
[self emitParseError:@"Unexpected token after after <body>"];
[self switchInsertionMode:HTMLInsertionModeInBody];
[self reprocessToken:token];
}
@@ -2525,7 +2537,7 @@
break;
}
[self emitParseError:@"Unexpected Token after after <frameset>"];
[self emitParseError:@"Unexpected token after after <frameset>"];
}
- (void)processTokenByApplyingRulesForParsingTokensInForeignContent:(HTMLToken *)token
@@ -2576,7 +2588,7 @@
};
void (^ matchedCase)() = ^ {
[self emitParseError:@"Unexpected Start Tag Token (%@) in foreign content", token.asTagToken.tagName];
[self emitParseError:@"Unexpected start tag <%@> in foreign content", token.asTagToken.tagName];
if (_fragmentParsingAlgorithm) {
anythingElse();
} else {
@@ -2611,7 +2623,7 @@
NSUInteger index = _stackOfOpenElements.count - 1;
if (![node.tagName isEqualToStringIgnoringCase:token.asTagToken.tagName]) {
[self emitParseError:@"Unexpected nested End Tag Token (%@) in foreign content", token.asTagToken.tagName];
[self emitParseError:@"Unexpected end tag </%@> for misnested element in foreign content", token.asTagToken.tagName];
}
while (YES) {
+4
View File
@@ -6,6 +6,10 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#define INSERTION_MODES \
MODE_ENTRY( HTMLInsertionModeInitial, = 0 ) \
MODE_ENTRY( HTMLInsertionModeBeforeHTML, ) \
+4
View File
@@ -8,6 +8,10 @@
#import "NSString+HTMLKit.h"
/**
HTML quirks modes
https://html.spec.whatwg.org/multipage/infrastructure.html#quirks-mode
*/
typedef NS_ENUM(short, HTMLQuirksMode)
{
HTMLQuirksModeNoQuirks,
+158
View File
@@ -6,37 +6,162 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLElement.h"
/**
The Stack of Open Elements. The stack grows downwards; the topmost node on the stack is the first one added to the
stack, and the bottommost node of the stack is the most recently added node in the stack
https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements
*/
@interface HTMLStackOfOpenElements : NSObject <NSFastEnumeration>
/** @brief The current node in the stack. It is the bottommost node. */
- (HTMLElement *)currentNode;
/** @brief The first node in the stack. */
- (HTMLElement *)firstNode;
/** @brief The last node in the stack. */
- (HTMLElement *)lastNode;
/**
Returns the object at the specified index.
@param index An index within the bounds of the stack.
@return The node located at index.
*/
- (id)objectAtIndexedSubscript:(NSUInteger)index;
/**
Replaces the object at the index with the new object.
@param obj The node with which to replace the object at given index in the stack.
@param idx The index of the object to be replaced.
*/
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
/**
Returns the index of the given node in the stack.
@param node The node.
@returns The index of the given node in the stack.
*/
- (NSUInteger)indexOfElement:(id)node;
/**
Pushes the given element onto the stack.
@param element The element.
*/
- (void)pushElement:(HTMLElement *)element;
/**
Removes the given element from the stack.
@param element The element.
*/
- (void)removeElement:(id)element;
/**
Checks whether the given element is in the stack.
@param element The element.
@returns `YES` if the element is in the stack, `NO` otherwise.
*/
- (BOOL)containsElement:(id)element;
/**
Checks whether an element with the given tag name is in the stack.
@param tagname The element's tag name.
@returns `YES` if such an element is in the stack, `NO` otherwise.
*/
- (BOOL)containsElementWithTagName:(NSString *)tagName;
/**
Inserts the given element at the index into the stack.
@param element The element to insert.
@param index The index at which the element should be inserted.
*/
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
/**
Replaces the element at the given index in the stack with the new element.
@param index The index of the element to be replaced.
@param element The element with which to replace the element at given index in the stack.
*/
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
/**
Pops current node from the stack.
*/
- (void)popCurrentNode;
/**
Pops elements from the stack until an element with the given tag name is poped.
@param tagName The tag name.
*/
- (void)popElementsUntilElementPoppedWithTagName:(NSString *)tagName;
/**
Pops elements from the stack until an element with any of the given tag names is poped.
@param tagNames The tag names.
*/
- (void)popElementsUntilAnElementPoppedWithAnyOfTagNames:(NSArray *)tagNames;
/**
Pops elements from the stack until the given element is poped.
@param element The element.
*/
- (void)popElementsUntilElementPopped:(HTMLElement *)element;
/**
Pops elements from the stack until a template element is poped.
*/
- (void)popElementsUntilTemplateElementPopped;
/**
Clears the stack to a table context
https://html.spec.whatwg.org/multipage/syntax.html#clear-the-stack-back-to-a-table-context
*/
- (void)clearBackToTableContext;
/**
Clears the stack to a table body context
https://html.spec.whatwg.org/multipage/syntax.html#clear-the-stack-back-to-a-table-body-context
*/
- (void)clearBackToTableBodyContext;
/**
Clears the stack to a table context
https://html.spec.whatwg.org/multipage/syntax.html#clear-the-stack-back-to-a-table-row-context
*/
- (void)clearBackToTableRowContext;
/**
Pops all nodes from the stack.
*/
- (void)popAll;
/**
Methods for checking whether the stack contains elements in speccific scopes:
https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-the-specific-scope
*/
- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName;
- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames;
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName;
@@ -44,12 +169,45 @@
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName;
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames;
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName;
/**
Returns the furthest block after a given index.
@discussion The furthest block is the topmost node in the stack of open elements that is lower in the stack than
formatting element, and is an element in the special category. This is used in the adoption agency algorithm:
https://html.spec.whatwg.org/multipage/syntax.html#adoption-agency-algorithm
@param index The index.
@returns The furthest block after index.
*/
- (HTMLElement *)furthestBlockAfterIndex:(NSUInteger)index;
/**
Returns the count of elements in this stack.
@returns The elements count.
*/
- (NSUInteger)count;
/**
Checks whether this stack is empty.
@returns `YES` if the stack is empty, `NO` otherwise.
*/
- (BOOL)isEmpy;
/**
Return an object enumerator over this stack.
@returns An enumerator
*/
- (NSEnumerator *)enumerator;
/**
Return a reverse object enumerator over this stack.
@returns A reverse enumerator
*/
- (NSEnumerator *)reverseObjectEnumerator;
@end
+37
View File
@@ -6,27 +6,64 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
#import "HTMLOrderedDictionary.h"
/**
HTML Tag Token
*/
@interface HTMLTagToken : HTMLToken
/** @brief The tag name. */
@property (nonatomic, copy) NSString *tagName;
/** @brief The tag's attributes. */
@property (nonatomic, strong) HTMLOrderedDictionary *attributes;
/** @brief Flag whether this tag is self-closing. */
@property (nonatomic, assign, getter = isSelfClosing) BOOL selfClosing;
/**
Initializes a new tag token.
@param tagName The tag's name.
@returns A new instance of a tag token.
*/
- (instancetype)initWithTagName:(NSString *)tagName;
/**
Initializes a new tag token.
@param tagName The tag's name.
@param attributes The tag's attributes.
@returns A new instance of a tag token.
*/
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes;
/**
Appends the given string to this token's name.
@param string The string to append.
*/
- (void)appendStringToTagName:(NSString *)string;
@end
/**
HTML Start Tag Token
*/
@interface HTMLStartTagToken : HTMLTagToken
@end
/**
HTML End Tag Token
*/
@interface HTMLEndTagToken : HTMLTagToken
@end
+14
View File
@@ -9,8 +9,22 @@
#import "HTMLElement.h"
#import "HTMLDocumentFragment.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Template node.
https://html.spec.whatwg.org/multipage/scripting.html#the-template-element
*/
@interface HTMLTemplate : HTMLElement
/**
The content of the template.
@see HTMLDocumentFragment
*/
@property (nonatomic, strong) HTMLDocumentFragment *content;
@end
NS_ASSUME_NONNULL_END
+19
View File
@@ -8,12 +8,31 @@
#import "HTMLNode.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Text node
*/
@interface HTMLText : HTMLNode
/** @brief The text string. */
@property (nonatomic, copy) NSMutableString *data;
/**
Initializes a new HTML text node.
@param data The text string.
@returns A new isntance of a HTML text node.
*/
- (instancetype)initWithData:(NSString *)data;
/**
Appends the string to this text node.
@param string The string to append.
*/
- (void)appendString:(NSString *)string;
@end
NS_ASSUME_NONNULL_END
+1
View File
@@ -9,6 +9,7 @@
#import "HTMLText.h"
#import "HTMLElement.h"
#import "NSString+HTMLKit.h"
#import "HTMLNode+Private.h"
@implementation HTMLText
+58
View File
@@ -6,6 +6,10 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
@class HTMLDOCTYPEToken;
@@ -16,10 +20,12 @@
@class HTMLCharacterToken;
@class HTMLParseErrorToken;
/** @brief Returns YES if both arguments are `nil` or equal, NO otherwise. */
NS_INLINE BOOL bothNilOrEqual(id first, id second) {
return (first == nil && second == nil) || ([first isEqual:second]);
}
/** @brief The token type. */
typedef NS_ENUM(NSUInteger, HTMLTokenType)
{
HTMLTokenTypeCharacter,
@@ -31,24 +37,76 @@ typedef NS_ENUM(NSUInteger, HTMLTokenType)
HTMLTokenTypeStartTag
};
/**
Base class for HTML Tokens emitted by the Tokenizer.
@see HTMLTokenizer
*/
@interface HTMLToken : NSObject
@property (nonatomic, assign) HTMLTokenType type;
/** @brief YES if this token is DOCTYPE token. NO otherwise */
- (BOOL)isDoctypeToken;
/** @brief YES if this token is Start Tag token. NO otherwise */
- (BOOL)isStartTagToken;
/** @brief YES if this token is End Tag token. NO otherwise */
- (BOOL)isEndTagToken;
/** @brief YES if this token is Comment token. NO otherwise */
- (BOOL)isCommentToken;
/** @brief YES if this token is Character token. NO otherwise */
- (BOOL)isCharacterToken;
/** @brief YES if this token is EOF token. NO otherwise */
- (BOOL)isEOFToken;
/** @brief YES if this token is Parse Error token. NO otherwise */
- (BOOL)isParseError;
/**
@brief Casts this token to DOCTYPE token.
@warning This is a convenience method and should be paired with the appropriate check.
*/
- (HTMLDOCTYPEToken *)asDoctypeToken;
/**
@brief Casts this token to Tag token.
@warning This is a convenience method and should be paired with the appropriate check.
*/
- (HTMLTagToken *)asTagToken;
/**
@brief Casts this token to Start Tag token.
@warning This is a convenience method and should be paired with the appropriate check.
*/
- (HTMLStartTagToken *)asStartTagToken;
/**
@brief Casts this token to End Tag token.
@warning This is a convenience method and should be paired with the appropriate check.
*/
- (HTMLEndTagToken *)asEndTagToken;
/**
@brief Casts this token to Comment token.
@warning This is a convenience method and should be paired with the appropriate check.
*/
- (HTMLCommentToken *)asCommentToken;
/**
@brief Casts this token to Character token.
@warning This is a convenience method and should be paired with the appropriate check.
*/
- (HTMLCharacterToken *)asCharacterToken;
/**
@brief Casts this token to Parse Error token.
@warning This is a convenience method and should be paired with the appropriate check.
*/
- (HTMLParseErrorToken *)asParseError;
@end
+25 -3
View File
@@ -6,23 +6,45 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
#import "HTMLTokenizerStates.h"
@class HTMLParser;
/**
* HTML Tokenizer
* https://html.spec.whatwg.org/multipage/syntax.html#tokenization
*/
@class HTMLParser;
@interface HTMLTokenizer : NSEnumerator
/** @brief The underlying string with which this tokenizer was initialized. */
@property (nonatomic, readonly) NSString *string;
/**
The current tokenizer state.
@see HTMLTokenizerState
*/
@property (nonatomic, assign) HTMLTokenizerState state;
/**
The associated HTML Parser instance.
@see HTMLParser
*/
@property (nonatomic, weak, readonly) HTMLParser *parser;
/**
Initializes a new Tokenizer with the given string.
@param string The HTML string
@returns A new instance of the Tokenizer.
*/
- (instancetype)initWithString:(NSString *)string;
@end
+1 -2
View File
@@ -852,7 +852,7 @@
case LATIN_SMALL_LETTER_A ... LATIN_SMALL_LETTER_Z:
_currentTagToken = [[HTMLEndTagToken alloc] initWithTagName:StringFromUniChar(character)];
[_temporaryBuffer appendString:StringFromUniChar(character)];
[self switchToState:HTMLTokenizerStateRCDATAEndTagName];
[self switchToState:HTMLTokenizerStateRAWTEXTEndTagName];
break;
default:
[self switchToState:HTMLTokenizerStateRAWTEXT];
@@ -1431,7 +1431,6 @@
return;
case LATIN_CAPITAL_LETTER_A ... LATIN_CAPITAL_LETTER_Z:
[self appendToCurrentAttributeName:StringFromUniChar(character + 0x0020)];
[self switchToState:HTMLTokenizerStateAttributeName];
return;
case NULL_CHAR:
[self emitParseError:@"NULL character (0x0000) in Attribute Name state"];

Some files were not shown because too many files have changed in this diff Show More