92 Commits

Author SHA1 Message Date
iska 266621edf2 Merge branch 'release/0.9.1' 2016-01-29 00:56:54 +01:00
iska cac1e4cedd Add podspec file for HTMLKit 2016-01-29 00:56:45 +01:00
iska abc847a33b Set version to 0.9.1 2016-01-29 00:55:32 +01:00
iska 4680a66fd1 Fix all warnings related to type conversion in NSLog statements 2016-01-29 00:43:01 +01:00
iska 465b78dbba Change build settings to treat warnings as errors 2016-01-29 00:39:11 +01:00
iska a64ff8782e Fix variable types in README.md examples 2016-01-28 23:20:30 +01:00
iska e03a384aa7 Update logo image 2016-01-28 23:17:06 +01:00
iska 6d2cb09082 Add travis build status to README.md 2016-01-28 22:17:18 +01:00
iska 222bfa03e1 Remove iOS 8 simulators from the build matrix in the travis.yml
Till these get fixed:
https://github.com/travis-ci/travis-ci/issues/4906
https://github.com/travis-ci/travis-ci/issues/3040
2016-01-28 22:14:48 +01:00
iska 6254e8a578 Add imports for the public categories in the umbrella header 2016-01-28 22:12:37 +01:00
iska 01be0acc0a Move the private HTMLNode header to the corresponding section 2016-01-28 22:12:13 +01:00
iska 58f0b88ff8 Add simulator-id to the build matrix in travis.yml 2016-01-20 00:33:35 +01:00
iska b90e673dc0 Add command to start the simulator before building and running tests in travis.yml 2016-01-19 21:36:50 +01:00
iska 22f293e718 Fix workspace env variable in tavis.yml 2016-01-19 01:19:39 +01:00
iska 4511335e9b Fix indentation in tavis.yml 2016-01-19 01:17:10 +01:00
iska dd2d29b8f0 Add matrix and destinations to travis.yml 2016-01-19 01:13:28 +01:00
iska f267958e83 Set deployment target to iOS 8.0 2016-01-19 00:50:57 +01:00
iska b94a80bd24 Remove code signing identity 2016-01-19 00:50:39 +01:00
iska 1df0c4ce1f Use debug configuration in travis.yml 2016-01-19 00:41:33 +01:00
iska ac49520ad9 Use correct schemes in travis.yml 2016-01-19 00:37:23 +01:00
iska ee6dbff8d5 Use ipgonesimulator SDK in travis.yml 2016-01-19 00:31:23 +01:00
iska f004e6328c Remove Team ID from project settings 2016-01-19 00:31:23 +01:00
iska f8255c861a Add .travis.yml file 2016-01-19 00:26:55 +01:00
iska 4592037aba Fix "return" attribute in source code documentation
@returns is ignored by jazzy
2016-01-18 21:48:00 +01:00
iska 948c07e4ae Merge branch 'hotfix/fix_readme' 2015-12-24 00:35:15 +01:00
iska 41d9d98201 Merge branch 'hotfix/fix_readme' into develop 2015-12-24 00:35:15 +01:00
iska 8199f647f4 Fix some typos in the README 2015-12-24 00:35:08 +01:00
iska 977737d538 Merge branch 'hotfix/fix_readme' 2015-12-23 21:53:07 +01:00
iska b8d17162d5 Merge branch 'hotfix/fix_readme' into develop 2015-12-23 21:53:07 +01:00
iska 27e1ed2bda Add new line after logo in README 2015-12-23 21:53:00 +01:00
iska d980203741 Merge branch 'release/0.9.0' 2015-12-23 21:49:36 +01:00
iska c169f0ed07 Merge branch 'release/0.9.0' into develop 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
110 changed files with 4769 additions and 752 deletions
+34
View File
@@ -0,0 +1,34 @@
language: objective-c
osx_image: xcode7.2
branches:
except:
- gh-pages
install:
- gem install xcpretty
env:
global:
- LC_CTYPE=en_US.UTF-8
- LANG=en_US.UTF-8
- WORKSPACE=HTMLKit.xcworkspace
- IOS_FRAMEWORK_SCHEME=HTMLKit-iOS
- OSX_FRAMEWORK_SCHEME=HTMLKit-OSX
- IOS_SDK=iphonesimulator9.2
- OSX_SDK=macosx10.11
matrix:
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SIMULATOR="iPhone 6 Plus (9.0)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
- DESTINATION="OS=9.1,name=iPhone 6S" SIMULATOR="iPhone 6S (9.1)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
- DESTINATION="OS=9.2,name=iPhone 6S Plus" SIMULATOR="iPhone 6S Plus (9.2)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
- DESTINATION="arch=x86_64" SIMULATOR="" SCHEME="$OSX_FRAMEWORK_SCHEME" SDK="$OSX_SDK"
script:
- set -o pipefail
- xcodebuild -version
- xcodebuild -showsdks
- SIMULATOR_ID=$(xcrun instruments -s devices | grep -io "$SIMULATOR \[.*\]" | grep -o "\[.*\]" | sed "s/^\[\(.*\)\]$/\1/")
- open -b com.apple.iphonesimulator --args -CurrentDeviceUDID $SIMULATOR_ID
- xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO clean build | xcpretty -c
- xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c
@@ -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: 116 KiB

+21
View File
@@ -0,0 +1,21 @@
Pod::Spec.new do |s|
s.name = "HTMLKit"
s.version = "0.9.1"
s.summary = "HTMLKit, an Objective-C framework for your everyday HTML needs."
s.license = "MIT"
s.homepage = "https://github.com/iabudiab/HTMLKit"
s.author = "iabudiab"
s.social_media_url = "https://twitter.com/_iabudiab"
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.9"
s.source = { :git => "https://github.com/iabudiab/HTMLKit.git", :tag => s.version }
s.source_files = "HTMLKit", "HTMLKit/**/*.{h,m}"
s.private_header_files = [
'HTMLKit/**/*{HTMLToken,HTMLTokens,HTMLTagToken,HTMLCharacterToken,HTMLCommentToken,HTMLDOCTYPEToken,HTMLEOFToken,HTMLTokenizer,HTMLTokenizerCharacters,HTMLTokenizerEntities,HTMLTokenizerStates,HTMLElementAdjustment,HTMLElementTypes,HTMLInputStreamReader,HTMLListOfActiveFormattingElements,HTMLNodeTraversal,HTMLParseErrorToken,HTMLParserInsertionModes,HTMLStackOfOpenElements,HTMLNode+Private,HTMLMarker,CSSCodePoints,CSSInputStream}.h'
]
s.requires_arc = true
end
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>
+3
View File
@@ -4,4 +4,7 @@
<FileRef
location = "group:HTMLKit.xcodeproj">
</FileRef>
<FileRef
location = "group:HTMLKit.playground">
</FileRef>
</Workspace>
+44 -2
View File
@@ -11,18 +11,60 @@
NS_ASSUME_NONNULL_BEGIN
/**
CSS Attribute Selector.
*/
@interface CSSAttributeSelector : CSSSelector
@property (nonatomic, assign) CSSAttributeSelectorType type;
/**
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.
@return A new instance of class selector.
*/
+ (instancetype)classSelector:(NSString *)className;
/**
Intializes and returns a CSS id selector.
@param elementId The element id to match.
@return 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.
@return 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.
@return A new instance of attribute selector.
*/
- (instancetype)initWithType:(CSSAttributeSelectorType)type
attributeName:(NSString *)name
attributeName:(NSString *)name
attrbiuteValue:(NSString *)value;
@end
+1 -1
View File
@@ -41,7 +41,7 @@
{
self = [super init];
if (self) {
self.type = type;
_type = type;
_name = [name copy];
_value = value ? [value copy]: @"";
}
+4
View File
@@ -6,6 +6,10 @@
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
//------------------------------------------------------
#define CODEPOINTS \
CODEPOINT( CONTROL, 0x0080 ) \
CODEPOINT( CHARACTER_TABULATION, 0x0009 ) \
+30
View File
@@ -10,11 +10,41 @@
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.
@return 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.
@return 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.
@return 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.
@return A new instance of the general sibling selector.
*/
+ (instancetype)generalSiblingCombinator:(CSSSelector *)selector;
@end
+1
View File
@@ -8,6 +8,7 @@
#import "CSSCombinatorSelector.h"
#import "HTMLElement.h"
#import "HTMLNode+Private.h"
#pragma mark - Declarations
+21
View File
@@ -10,11 +10,32 @@
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.
@return 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.
@return 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
+42
View File
@@ -6,15 +6,57 @@
// 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
@return 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.
@return 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
@return The value of the escaped code-point.
*/
- (UTF32Char)consumeEscapedCodePoint;
/**
Consumes a CSS selector combinator.
@return The consumed combinator, `nil` if nothing was consumed.
*/
- (NSString *)consumeCombinator;
@end
+1 -1
View File
@@ -106,7 +106,7 @@
}
NSScanner *scanner = [NSScanner scannerWithString:(__bridge NSString *)(hexString)];
UTF32Char number;
unsigned int number;
[scanner scanHexInt:&number];
return isValidEscapedCodePoint(number) ? number : REPLACEMENT_CHARACTER;
+15
View File
@@ -9,8 +9,23 @@
#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
+47
View File
@@ -10,14 +10,61 @@
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.
@return 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.
@return 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.
@return 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.
@return Nth-Last-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthLastOfTypeSelector:(CSSNthExpression)expression;
@end
+4 -3
View File
@@ -8,6 +8,7 @@
#import "CSSNthExpressionSelector.h"
#import "HTMLElement.h"
#import "HTMLNode+Private.h"
#pragma mark - Nth-Expression
@@ -26,13 +27,13 @@ NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression)
}
if (expression.an == 0) {
return [NSString stringWithFormat:@"%ld", expression.b];
return [NSString stringWithFormat:@"%ld", (long)expression.b];
}
if (expression.b == 0) {
return [NSString stringWithFormat:@"%ldn", expression.an];
return [NSString stringWithFormat:@"%ldn", (long)expression.an];
}
return [NSString stringWithFormat:@"%ldn%+ld", expression.an, expression.b];
return [NSString stringWithFormat:@"%ldn%+ld", (long)expression.an, (long)expression.b];
}
#pragma mark - Implementation
+13
View File
@@ -10,10 +10,23 @@
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.
@return A new instance of a pseudo-class selector.
*/
- (instancetype)initWithClassName:(NSString *)className selector:(CSSSelector *)selector;
@end
+18
View File
@@ -10,9 +10,27 @@
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.
@return 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.
@return A new instance of the has-descendant selector.
*/
+ (instancetype)hasSelector:(CSSSelector *)selector;
@end
+4 -2
View File
@@ -8,6 +8,7 @@
#import "CSSPseudoFunctionSelector.h"
#import "HTMLElement.h"
#import "HTMLNode+Private.h"
#pragma mark - Declarations
@@ -72,8 +73,9 @@
- (BOOL)acceptElement:(HTMLElement *)element
{
for (HTMLNode *child in element.childNodes) {
if (child.nodeType == HTMLNodeElement && [self.selector acceptElement:child.asElement]) {
HTMLNodeIterator *iterator = [element nodeIteratorWithShowOptions:HTMLNodeFilterShowAll filter:nil];
for (HTMLNode *descendant in iterator) {
if (descendant.nodeType == HTMLNodeElement && [self.selector acceptElement:descendant.asElement]) {
return YES;
}
}
+42 -1
View File
@@ -8,22 +8,45 @@
#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;
@@ -42,8 +65,26 @@ extern NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression
@class HTMLElement;
/**
Base class for all CSS Selector implementations
*/
@interface CSSSelector : NSObject
- (BOOL)acceptElement:(nonnull HTMLElement *)element;
/**
Initializes and returns a new instance of CSS Selector.
@param string The selector string which will be parsed.
@return 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
+11
View File
@@ -8,9 +8,20 @@
#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];
+10
View File
@@ -12,8 +12,18 @@ 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.
@return A new instance of the block-based selector.
*/
- (instancetype)initWithName:(NSString *)name block:(BOOL (^)(HTMLElement *))block;
@end
+19
View File
@@ -8,10 +8,29 @@
#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.
@return A parsed CSSSelector, `nil` if an error occurred.
@see CSSelector
*/
+ (CSSSelector *)parseSelector:(NSString *)string error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END
+71 -12
View File
@@ -61,6 +61,11 @@
#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 = @{
@@ -70,7 +75,7 @@
CSSSelectorErrorLocationKey: @(location)
};
if(error) {
if(error && *error == nil) {
*error = [NSError errorWithDomain:HTMLKitSelectorErrorDomain code:HTMLKitSelectorParseError userInfo:userInfo];
}
}
@@ -102,7 +107,7 @@
_location += subSelector.length;
}
if (*error != nil) {
if (error && *error != nil) {
return nil;
}
@@ -204,7 +209,7 @@
{
NSString *elementId = [_inputStream consumeIdentifier];
if (elementId == nil) {
[self emitError:error reason:@"Invalid character" location:_location + _inputStream.currentLocation];
[self emitError:error reason:@"Invalid character"];
return nil;
}
return idSelector(elementId);
@@ -213,7 +218,7 @@
{
NSString *className = [_inputStream consumeIdentifier];
if (className == nil) {
[self emitError:error reason:@"Invalid character" location:_location + _inputStream.currentLocation];
[self emitError:error reason:@"Invalid character"];
return nil;
}
return classSelector(className);
@@ -228,7 +233,7 @@
}
default:
{
[self emitError:error reason:@"Invalid character" location:_location + _inputStream.currentLocation];
[self emitError:error reason:@"Invalid character"];
return nil;
}
}
@@ -278,7 +283,7 @@
// Consume RIGHT_SQUARE_BRACKET
if (![_inputStream consumeCharacter:RIGHT_SQUARE_BRACKET]) {
[self emitError:error reason:@"Expected closing right square bracket ']'" location:_location + _inputStream.currentLocation];
[self emitError:error reason:@"Expected closing right square bracket ']'"];
}
if (type == CSSAttributeSelectorExists) {
@@ -295,7 +300,7 @@
if ([pseudoClass hasPrefix:@"nth"]) {
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
[self emitError:error reason:@"Expected opening left parenthesis '('" location:_location + _inputStream.currentLocation];
[self emitError:error reason:@"Expected opening left parenthesis '('"];
}
NSString *functionExpression = [_inputStream consumeCharactersUpToString:@")"];
@@ -303,7 +308,7 @@
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
[self emitError:error reason:@"Expected closing right parenthesis ')'" location:_location + _inputStream.currentLocation];
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
}
if ([pseudoClass isEqualToString:@"nth-child"]) {
@@ -318,16 +323,40 @@
} else if ([pseudoClass isEqualToString:@"not"]) {
[_inputStream consumeWhitespace];
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
[self emitError:error reason:@"Expected opening left parenthesis '('" location:_location + _inputStream.currentLocation];
[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 ')'" location:_location + _inputStream.currentLocation];
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
}
return nay(subSelector);
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();
@@ -358,9 +387,39 @@
} 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 location:_location + _inputStream.currentLocation];
[self emitError:error reason:reason];
return nil;
}
+207 -1
View File
@@ -7,65 +7,271 @@
//
#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: '*'
@return The universal CSS selector.
*/
extern CSSSelector * universalSelector();
/**
CSS type selector, e.g. 'div', 'p', ...etc.
@param type The element type.
@return 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.
@return Id selector for the specified element id.
*/
extern CSSSelector * idSelector(NSString *elementId);
/**
CSS class selector, e.g. '.someClass'
@param className The class name.
@return Class selector for the specified class name.
*/
extern CSSSelector * classSelector(NSString *className);
/**
CSS has-attribute selector, e.g. '[href]'
@param attribute The attribute.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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)'
@return Odd-Child selector.
*/
extern CSSSelector * oddSelector();
/**
CSS even-child selector: ':nth-child(even)'
This is analogous to ':nth-child(2n)'
@return Even-Child selector.
*/
extern CSSSelector * evenSlector();
/**
CSS first-child selector: ':nth-child(1)'
@return First-Child selector.
*/
extern CSSSelector * firstChildSelector();
/**
CSS first-child selector: ':nth-last-child(1)'
@return First-Child selector.
*/
extern CSSSelector * lastChildSelector();
/**
CSS first-of-type selector: ':nth-first-of-type(1)'
@return First-Of-Type selector.
*/
extern CSSSelector * firstOfTypeSelector();
/**
CSS last-of-type selector: ':nth-last-of-type(1)'
@return Last-Of-Type selector.
*/
extern CSSSelector * lastOfTypeSelector();
/**
CSS only-child selector: ':first-child:last-child'
@return Only-Child selector.
*/
extern CSSSelector * onlyChildSelector();
/**
CSS only-of-type selector: ':first-of-type:last-of-type'
@return 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.
@return 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.
@return 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.
@return 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.
@return A general sibling selector.
*/
extern CSSSelector * generalSiblingSelector(CSSSelector *selector);
#pragma mark - Pseudo Functions
extern CSSSelector * nay(CSSSelector *selector);
/**
CSS nagation selector: ':not(div)'
@param selector The selector which should be negated.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return A named-block selector.
*/
extern CSSSelector * namedBlockSelector(NSString *name, BOOL (^ acceptBlock)(HTMLElement *element));
NS_ASSUME_NONNULL_END
+1 -1
View File
@@ -140,7 +140,7 @@ CSSSelector * generalSiblingSelector(CSSSelector *selector)
#pragma mark - Pseudo Functions
CSSSelector * nay(CSSSelector *selector)
CSSSelector * not(CSSSelector *selector)
{
return [CSSPseudoFunctionSelector notSelector:selector];
}
+108 -3
View File
@@ -10,31 +10,136 @@
NS_ASSUME_NONNULL_BEGIN
/**
@return Root element selector: ':root'
*/
extern CSSSelector * rootSelector();
/**
@return Empy element selector: ':empty'
*/
extern CSSSelector * emptySelector();
/**
@return A parent element selector: ':parent'
*/
extern CSSSelector * parentSelector();
/**
@return A button element selector: ':button'
*/
extern CSSSelector * buttonSelector();
/**
@return A checkbox element selector: ':checkbox'
*/
extern CSSSelector * checkboxSelector();
/**
@return A file element selector: ':file'
*/
extern CSSSelector * fileSelector();
/**
@return A header element selector: ':header'
*/
extern CSSSelector * headerSelector();
/**
@return An image element selector: ':image'
*/
extern CSSSelector * imageSelector();
/**
@return A parent element selector: ':parent'
*/
extern CSSSelector * inputSelector();
/**
@return A link element selector: ':link'
*/
extern CSSSelector * linkSelector();
/**
@return A password element selector: ':password'
*/
extern CSSSelector * passwordSelector();
/**
@return A radio element selector: ':radio'
*/
extern CSSSelector * radioSelector();
/**
@return A reset element selector: ':reset'
*/
extern CSSSelector * resetSelector();
/**
@return A submit element selector: ':submit'
*/
extern CSSSelector * submitSelector();
/**
@return A text element selector: ':text'
*/
extern CSSSelector * textSelector();
/**
@return An enabled element selector: ':enabled'
*/
extern CSSSelector * enabledSelector();
/**
@return A disabled element selector: ':disabled'
*/
extern CSSSelector * disabledSelector();
/**
@return A checked element selector: ':checked'
*/
extern CSSSelector * checkedSelector();
/**
@return An optional element selector: ':optional'
*/
extern CSSSelector * optionalSelector();
/**
@return A required element selector: ':required'
*/
extern CSSSelector * requiredSelector();
extern CSSSelector * ltSelector();
extern CSSSelector * gtSelector();
extern CSSSelector * eqSelector();
/**
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.
@return 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.
@return 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.
@return An Equal selector.
*/
extern CSSSelector * eqSelector(NSInteger index);
NS_ASSUME_NONNULL_END
+22 -10
View File
@@ -183,7 +183,7 @@ CSSSelector * enabledSelector()
typeSelector(@"menuitem"),
typeSelector(@"fieldset"),
]);
return namedPseudoSelector(@"enabled", allOf(@[candiate, nay(disabledSelector())]));
return namedPseudoSelector(@"enabled", allOf(@[candiate, not(disabledSelector())]));
}
CSSSelector * disabledSelector()
@@ -206,7 +206,7 @@ CSSSelector * disabledSelector()
]),
allOf(@[
descendantOfElementSelector(disabledFieldset),
nay(firstLegendDecendantDisabledFieldSet)
not(firstLegendDecendantDisabledFieldSet)
])
]);
@@ -249,7 +249,7 @@ CSSSelector * optionalSelector()
typeSelector(@"select"),
typeSelector(@"textarea")
]);
CSSSelector *noAttribute = nay(hasAttributeSelector(@"required"));
CSSSelector *noAttribute = not(hasAttributeSelector(@"required"));
return namedPseudoSelector(@"optional", allOf(@[candidate, noAttribute]));
}
@@ -270,25 +270,37 @@ CSSSelector * requiredSelector()
#pragma mark - Positional
CSSSelector * ltSelector(NSUInteger index)
CSSSelector * ltSelector(NSInteger index)
{
NSString *name = [NSString stringWithFormat:@":lt(%lu)", (unsigned long)index];
NSString *name = [NSString stringWithFormat:@":lt(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
return [element.parentElement indexOfChildNode:element] < index;
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
if (index > 0) {
return elementIndex < index;
} else {
return elementIndex < element.parentElement.childNodesCount - index - 1;
}
});
}
CSSSelector * gtSelector(NSUInteger index)
CSSSelector * gtSelector(NSInteger index)
{
NSString *name = [NSString stringWithFormat:@":gt(%lu)", (unsigned long)index];
NSString *name = [NSString stringWithFormat:@":gt(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
return [element.parentElement indexOfChildNode:element] > index;
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(%lu)", (unsigned long)index];
NSString *name = [NSString stringWithFormat:@":eq(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
+17
View File
@@ -11,12 +11,29 @@
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.
@return 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.
@return A new instance of a type selector.
*/
- (instancetype)initWithType:(NSString *)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.
@return 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
@return `YES` if this token contains only whitespace characters, `NO` otherwise.
*/
- (BOOL)isWhitespaceToken;
/**
Checks whether this token is empty.
@return `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.
@return 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.
@return 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.
@return 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.
@return 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.
@return A new instance of the DOM token list.
*/
- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value;
/**
@return The length of this token list
*/
- (NSUInteger)length;
/**
Checks whether this list contains the given token.
@param token The token.
@return `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.
@return `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.
@return 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;
/**
@return 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.
@return 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.
@return A new instance of a document fragment.
*/
- (instancetype)initWithDocument:(nullable HTMLDocument *)document;
@end
NS_ASSUME_NONNULL_END
+1 -4
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
+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
@return A new document type instance.
*/
- (instancetype)initWithName:(NSString *)name
publicIdentifier:(nullable NSString *)publicIdentifier
systemIdentifier:(nullable NSString *)systemIdentifier;
/**
Checks whether this DOCTYPE is valid.
@return `YES` if this is a valid DOCTYPE, `NO` otherwise.
*/
- (BOOL)isValid;
/**
Return the quirks mode of this DOCTYPE.
@return 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.
@return 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.
@return 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.
@return 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.
@return `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.
@return 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
+23 -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;
+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"
+121
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,31 +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
@return A new instance of the Input Stream Reader.
*/
- (id)initWithString:(NSString *)string;
/**
Returns the current input character.
@return The current code point in the input stream as a `UTF32Char`.
*/
- (UTF32Char)currentInputCharacter;
/**
Returns the next input character without consuming it.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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
@return 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.
@return 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.
@return 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.
@return A string containing the consumed characters. Returns `nil` if none were found.
*/
- (NSString *)consumeCharactersInString:(NSString *)characters;
/**
Consumes alphanumeric characters starting at the current location.
@return 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
+14 -3
View File
@@ -82,7 +82,7 @@
return LINE_FEED;
}
if (CFStringIsSurrogateLowCharacter(nextInputCharacter)) {
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated low surrogate: 0x%X)", nextInputCharacter];
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated low surrogate: 0x%X)", (unsigned int)nextInputCharacter];
[self emitParseError:reason];
return nextInputCharacter;
}
@@ -90,7 +90,7 @@
if (CFStringIsSurrogateHighCharacter(nextInputCharacter)) {
UniChar surrogateLow = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) {
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated high surrogate: 0x%X)", nextInputCharacter];
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated high surrogate: 0x%X)", (unsigned int)nextInputCharacter];
[self emitParseError:reason];
return nextInputCharacter;
}
@@ -100,7 +100,7 @@
}
if (isControlOrUndefinedCharacter(nextInputCharacter)) {
NSString *reason = [NSString stringWithFormat:@"A control/undefined character found: (0x%X)", nextInputCharacter];
NSString *reason = [NSString stringWithFormat:@"A control/undefined character found: (0x%X)", (unsigned int)nextInputCharacter];
[self emitParseError:reason];
}
@@ -152,6 +152,17 @@
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 HTMLHexNumberCharacterSet];
+2 -2
View File
@@ -9,7 +9,7 @@
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.braincookie.${PRODUCT_NAME:rfc1034identifier}</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>0.9.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+17 -2
View File
@@ -6,7 +6,22 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLParser.h"
#import <Foundation/Foundation.h>
//! Project version number for HTMLKit.
extern double HTMLKitVersionNumber;
//! Project version string for HTMLKit.
extern const unsigned char HTMLKitVersionString[];
#import "HTMLDOM.h"
#import "CSSSelectors.h"
#import "HTMLParser.h"
#import "HTMLKitErrorDomain.h"
#import "HTMLOrderedDictionary.h"
#import "CSSSelectors.h"
#import "CSSSelectorParser.h"
#import "CSSNthExpressionParser.h"
#import "NSString+HTMLKit.h"
#import "NSCharacterSet+HTMLKit.h"
+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";
@@ -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.
@return 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.
@return `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.
@return 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.
@return The formatting element.
*/
- (HTMLElement *)formattingElementWithTagName:(NSString *)tagName;
/**
Returns the count of elements in this list.
@return The elements count.
*/
- (NSUInteger)count;
/**
Checks whether this list is empty.
@return `YES` if the stack is empty, `NO` otherwise.
*/
- (BOOL)isEmpty;
/**
Return an object enumerator over this list.
@return An enumerator
*/
- (NSEnumerator *)enumerator;
/**
Return an object enumerator over this list.
@return 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.
@return 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
+310 -24
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,
@@ -40,101 +43,384 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
@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;
@property (nonatomic, strong, readonly) HTMLElement *previousSiblingElement;
/**
The previous sibling element in the document, if any.
@property (nonatomic, strong, readonly) HTMLElement *nextSiblingElement;
@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.
@return `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.
@return `YES` if this node has any children of the given type, `NO` otherwise.
*/
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type;
/**
Returns the cound of child nodes.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return 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.
@return `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.
@return `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.
@return 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.
@return 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.
@return A new node iterator whose root is this node.
@see HTMLNodeIterator
@see HTMLNodeFilterShowOptions
*/
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter;
- (HTMLElement *)firstElementMatchingSelector:(CSSSelector *)selector;
/**
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.
@return 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.
@return 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.
@return 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.
@return 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;
- (NSString *)treeDescription;
@end
NS_ASSUME_NONNULL_END
+25 -3
View File
@@ -7,12 +7,14 @@
//
#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)
@@ -25,7 +27,6 @@
{
NSMutableOrderedSet *_childNodes;
}
@property (nonatomic, weak) HTMLDocument *ownerDocument;
@end
@implementation HTMLNode
@@ -435,15 +436,32 @@
}
- (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;
@@ -454,6 +472,10 @@
- (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]) {
+43 -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,
@@ -31,16 +43,36 @@ typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions)
@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.
@return `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
@@ -49,8 +81,18 @@ typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions)
@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
+1
View File
@@ -8,6 +8,7 @@
#import "HTMLNodeFilter.h"
#import "HTMLNode.h"
#import "HTMLNode+Private.h"
#import "CSSSelector.h"
#pragma mark - Block Filter
+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.
@return 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.
@return 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.
@return 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;
/**
@return The next iterated node in tree order, `nil` if there are no more nodes to iterate.
*/
- (nullable HTMLNode *)nextNode;
/**
@return 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"
+62 -1
View File
@@ -10,19 +10,80 @@
NS_ASSUME_NONNULL_BEGIN
/**
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.
@return 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;
/**
Replaces the key-value pair located at the specified index.
@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.
@return 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;
- (NSEnumerator *)reverseKeyEnumerator;
/**
@return A reverse key enumerator.
*/
- (NSEnumerator<KeyType> *)reverseKeyEnumerator;
@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.
@return A new instance of a parse error token.
*/
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location;
@end
+1 -1
View File
@@ -32,7 +32,7 @@
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p Reason='%@' Location='%lu'>", self.class, self, _reason, _location];
return [NSString stringWithFormat:@"<%@: %p Reason='%@' Location='%lu'>", self.class, self, _reason, (unsigned long)_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
@return A new instance of the HTML parser.
*/
- (instancetype)initWithString:(NSString *)string;
/**
Runs the parsing algorithm and generates a valid HTML document object.
@return 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
@return 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.
@return 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.
@return `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.
@return `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.
@return The furthest block after index.
*/
- (HTMLElement *)furthestBlockAfterIndex:(NSUInteger)index;
/**
Returns the count of elements in this stack.
@return The elements count.
*/
- (NSUInteger)count;
/**
Checks whether this stack is empty.
@return `YES` if the stack is empty, `NO` otherwise.
*/
- (BOOL)isEmpy;
/**
Return an object enumerator over this stack.
@return An enumerator
*/
- (NSEnumerator *)enumerator;
/**
Return a reverse object enumerator over this stack.
@return 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.
@return 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.
@return 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.
@return 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
@return A new instance of the Tokenizer.
*/
- (instancetype)initWithString:(NSString *)string;
@end
+3 -5
View File
@@ -452,7 +452,6 @@
NSString *entityName = nil;
NSString *entityReplacement = nil;
#warning Improve Named Entity Search
UTF32Char inputCharacter = [_inputStreamReader consumeNextInputCharacter];
NSArray *names = [HTMLTokenizerEntities entities];
NSMutableString *name = [NSMutableString stringWithString:StringFromUTF32Char(inputCharacter)];
@@ -671,7 +670,7 @@
[_inputStreamReader reconsumeCurrentInputCharacter];
break;
default:
[self emitParseError:@"Unexpected character (0x%X) in Tag Open state", character];
[self emitParseError:@"Unexpected character (0x%X) in Tag Open state", (unsigned int)character];
[self switchToState:HTMLTokenizerStateData];
[self emitCharacterToken:LESS_THAN_SIGN];
[_inputStreamReader reconsumeCurrentInputCharacter];
@@ -702,7 +701,7 @@
[_inputStreamReader reconsumeCurrentInputCharacter];
break;
default:
[self emitParseError:@"Unexpected character (0x%X) in End Tag Open state", character];
[self emitParseError:@"Unexpected character (0x%X) in End Tag Open state", (unsigned int)character];
[self switchToState:HTMLTokenizerStateBogusComment];
[_inputStreamReader reconsumeCurrentInputCharacter];
break;
@@ -852,7 +851,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 +1430,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"];
+16
View File
@@ -6,11 +6,27 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
/**
HTML character reference entitites
https://html.spec.whatwg.org/multipage/syntax.html#named-character-references
*/
@interface HTMLTokenizerEntities : NSObject
/** @brief All character reference entitites. */
+ (NSArray *)entities;
/**
Returns the replacement entity at the given index.
@param index The index of the character reference.
@return The replacement character reference entitiy.
*/
+ (NSString *)replacementAtIndex:(NSUInteger)index;
@end
+4
View File
@@ -6,6 +6,10 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#define TOKENIZER_STATES \
STATE_ENTRY( HTMLTokenizerStateData, = 0) \
STATE_ENTRY( HTMLTokenizerStateCharacterReferenceInData, ) \
+4
View File
@@ -6,6 +6,10 @@
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import "HTMLToken.h"
#import "HTMLCharacterToken.h"
#import "HTMLCommentToken.h"
+90 -10
View File
@@ -9,28 +9,108 @@
#import <Foundation/Foundation.h>
#import "HTMLNodeFilter.h"
NS_ASSUME_NONNULL_BEGIN
@class HTMLNode;
/**
A HTML Tree Walker. Used to "walk" the DOM tree in all directions, i.e. it can traverse from a given node to its parent,
child, next, or previous sibling.
https://dom.spec.whatwg.org/#interface-treewalker
*/
@interface HTMLTreeWalker : NSObject
/**
The root element of this tree walker, i.e. the traversed tree is rooted at this element.
*/
@property (nonatomic, strong, readonly) HTMLNode *root;
/**
The iterator's show options. These options control what types of elements are shown or skipped during tree walking.
@see HTMLNodeFilterShowOptions
*/
@property (nonatomic, assign, readonly) HTMLNodeFilterShowOptions whatToShow;
@property (nonatomic, strong, readonly) id<HTMLNodeFilter> filter;
/**
A node filter, that is applied to each node during tree walking.
@see HTMLNodeFilter
*/
@property (nonatomic, strong, readonly, nullable) id<HTMLNodeFilter> filter;
/**
The current node at which this walker is standing.
*/
@property (nonatomic, strong) HTMLNode *currentNode;
/**
Initializes a new tree walker with no filter and HTMLNodeFilterShowAll show options.
@param node The root node.
@param filter The node filter to use.
@return A new instance of a tree walker.
*/
- (instancetype)initWithNode:(HTMLNode *)node;
/**
Initializes a new tree walker with HTMLNodeFilterShowAll show options.
@param node The root node.
@param filter The node filter to use.
@return A new instance of a tree walker.
*/
- (instancetype)initWithNode:(HTMLNode *)node
filter:(id<HTMLNodeFilter>)filter;
filter:(nullable id<HTMLNodeFilter>)filter;
/**
Initializes a new tree walker.
@param node The root node.
@param showOptions The show options for the walker.
@param filter The node filter to use.
@return A new instance of a tree walker.
*/
- (instancetype)initWithNode:(HTMLNode *)node
showOptions:(HTMLNodeFilterShowOptions)showOptions
filter:(id<HTMLNodeFilter>)filter;
filter:(nullable id<HTMLNodeFilter>)filter;
- (HTMLNode *)parentNode;
- (HTMLNode *)firstChild;
- (HTMLNode *)lastChild;
- (HTMLNode *)previousSibling;
- (HTMLNode *)nextSibling;
- (HTMLNode *)previousNode;
- (HTMLNode *)nextNode;
/**
The parent node of the current node.
*/
- (nullable HTMLNode *)parentNode;
/**
The first child node of the current node.
*/
- (nullable HTMLNode *)firstChild;
/**
The last child node of the current node.
*/
- (nullable HTMLNode *)lastChild;
/**
The previous sibling node of the current node.
*/
- (nullable HTMLNode *)previousSibling;
/**
The next sibling node of the current node.
*/
- (nullable HTMLNode *)nextSibling;
/**
The previous node of the current node in tree order.
*/
- (nullable HTMLNode *)previousNode;
/**
The next node of the current node in tree order.
*/
- (nullable HTMLNode *)nextNode;
@end
NS_ASSUME_NONNULL_END

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