Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d980203741 | |||
| ba02239207 | |||
| 830fa06a55 | |||
| 30c528a220 | |||
| 695ff67dd8 | |||
| f883b0d906 | |||
| be45558e86 | |||
| 46629ada01 | |||
| 700587b101 | |||
| 6c28dad930 | |||
| 572918e59b | |||
| 85edf33950 | |||
| e80188dd00 | |||
| b64cfc9c1e | |||
| 409b5502ae | |||
| 7377ec550f | |||
| 3d9e657be4 | |||
| 766c901e38 | |||
| 974e5a1615 | |||
| 518fd5eacb | |||
| f57e265cba | |||
| 4bb681eb78 | |||
| c4a5aa1bd9 | |||
| be34a57e6f | |||
| 01694a5fdf | |||
| b55ca26c7e | |||
| 846b6114fc | |||
| 1208ddf5c7 | |||
| c2bbb7d9f6 | |||
| 244dd726b4 | |||
| 032c428725 | |||
| 8d672cf0cf | |||
| 5354c7c934 | |||
| b99cb0c9d4 | |||
| 3c89df333c | |||
| ad34deb7ac | |||
| 7ee8057d38 | |||
| f2aff5f2ce | |||
| 7a1bb21b33 | |||
| fd965e014d | |||
| 9670e11fa5 | |||
| f4bd5420c0 | |||
| e773117f1f | |||
| d7476ef22d | |||
| 4fabcdf76a | |||
| bf147a6bb6 | |||
| 0a620d74b6 | |||
| 8893f28e4d | |||
| d924063b02 | |||
| f9065ab8c3 | |||
| 9815d3b39e | |||
| 1e30e4d86f | |||
| 188e5a0e56 | |||
| 991e868d14 | |||
| f3af320096 | |||
| 9d60a26622 | |||
| 936348d300 | |||
| 6410eb672a | |||
| 49e060cd3c | |||
| dac3cee3d8 | |||
| 5ee3b9b614 | |||
| e788c832c6 | |||
| a33f4e0834 | |||
| 9e4007dcba | |||
| 50b67cfa94 | |||
| 47d244289e | |||
| 6b81d6e465 | |||
| 13b8fe7808 | |||
| 94fed8afa1 | |||
| 30dc6cf343 | |||
| badf070907 | |||
| 3443321459 | |||
| 60dc6bfc43 | |||
| 60a5821a2c | |||
| 621dbe36d6 | |||
| 50ae2c5283 | |||
| 0cb374681c | |||
| c09adc2f96 | |||
| 09991a149f | |||
| f735fcdfd6 | |||
| 5a3c1063e7 | |||
| 8cb088367d | |||
| 7ec90d6470 | |||
| 002564f9ae | |||
| 4c80c520d4 | |||
| 85abf35099 | |||
| 578806ec2f | |||
| 7923efb02b | |||
| f4d42e0753 | |||
| 56f99f9168 | |||
| c8dd1d7791 | |||
| c57d3453fc | |||
| a413156514 | |||
| 203d153487 | |||
| 4ceb913b9f | |||
| 7896f1687e | |||
| 55bb8effb2 | |||
| 2e10c23f52 | |||
| 4f47a750e0 | |||
| 46beae1b2d | |||
| 2e441727bc | |||
| cc101b9f4e | |||
| 0635744a2d | |||
| b5bb0a48b6 | |||
| 95ffccf67b | |||
| 6af01a1214 | |||
| 667693939f | |||
| f05adfb56b | |||
| bed2edf22a | |||
| 2574736359 | |||
| ab7de014cf | |||
| 15e768267a | |||
| ee82cacc48 | |||
| 61d7c683fb | |||
| bd84884bf7 | |||
| f248b39d26 | |||
| 118ea137f4 | |||
| 0366f39d1a | |||
| d107c05a44 | |||
| 5ca3bf1192 | |||
| 02393ddedd | |||
| 33df9e7fb2 | |||
| 6020b2bdd9 | |||
| c44c77d63d | |||
| 9905f45e27 | |||
| c868bd1a56 | |||
| 8f38aed6c6 | |||
| 1917fb1f1c | |||
| dc93bb07f6 | |||
| a74be4dfa9 | |||
| 7cd47486ac | |||
| ad902e3138 | |||
| 6cfe3b6f83 | |||
| a2096b0e54 | |||
| 79fda9fdbe | |||
| 1a05c41f86 | |||
| bf751d94b1 | |||
| 43705dac69 | |||
| 3c7e6e1913 | |||
| af4444e9a8 | |||
| 23d0d62295 | |||
| b0668e121e | |||
| aa95bbd9f2 | |||
| 1544f71c50 | |||
| b1ddbc5f75 | |||
| 576dc4dfb2 | |||
| c4aacfae2b | |||
| a54cae8829 | |||
| 852dfad19c | |||
| a5d53b3eef | |||
| 5e5b903c52 | |||
| 40b6e41e12 | |||
| 05e224283a | |||
| 8490d4d2ca | |||
| 52b850317e | |||
| f5d970ba87 | |||
| 0c8fd754c3 | |||
| 4f03e00003 | |||
| 62b385d9b4 | |||
| a7a30a7cbf | |||
| 221b085fe5 | |||
| a2d9e65f0e | |||
| 745d0f72a7 | |||
| c401c3ca2c | |||
| 3cb5ed9a42 | |||
| acec99ffea | |||
| 7c58268dfd | |||
| c87076b470 | |||
| 81a20f0333 | |||
| 4fcaf4c810 |
+25
-6
@@ -1,8 +1,12 @@
|
||||
# OS X
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
@@ -12,13 +16,28 @@ build/
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
|
||||
## Other
|
||||
*.xccheckout
|
||||
profile
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.xcuserstate
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
|
||||
# CocoaPods
|
||||
Pods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
Pods/
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
Carthage/Checkouts
|
||||
|
||||
Carthage/Build
|
||||
@@ -0,0 +1,80 @@
|
||||
//: [Previous](@previous)
|
||||
/*:
|
||||
# CSS3 Selectors
|
||||
|
||||
HTMLKit understands CSS3 selectors making node-selection a piece of cake:
|
||||
*/
|
||||
|
||||
import HTMLKit
|
||||
|
||||
let htmlString = "<div><h1>HTMLKit</h1><p class='greeting'>Hello there!</p><p class='description'>This is a demo of HTMLKit</p></div>"
|
||||
let document = HTMLDocument(string: htmlString)
|
||||
|
||||
/*:
|
||||
All CSS3 selectors are supported and you use them the way you always have:
|
||||
*/
|
||||
var paragraphs = document.querySelectorAll("p")
|
||||
var paragraphsOrHeaders = document.querySelectorAll("p, h1")
|
||||
var hasClassAttribute = document.querySelectorAll("[class]")
|
||||
var greetings = document.querySelectorAll(".greeting")
|
||||
var classNameStartsWith_de = document.querySelectorAll("[class^='de']")
|
||||
|
||||
var hasAdjacentHeader = document.querySelectorAll("h1 + *")
|
||||
var hasSiblingHeader = document.querySelectorAll("h1 ~ *")
|
||||
var hasSiblingParagraph = document.querySelectorAll("p ~ *")
|
||||
|
||||
var nonParagraphChildOfDiv = document.querySelectorAll("div :not(p)")
|
||||
|
||||
/*:
|
||||
HTMLKit also provides API to create selector instances in a type-safe manner without the need to parse them first. The previous examples would like this:
|
||||
*/
|
||||
paragraphs = document.elementsMatchingSelector(typeSelector("p"))
|
||||
paragraphsOrHeaders = document.elementsMatchingSelector(
|
||||
anyOf([
|
||||
typeSelector("p"), typeSelector("h1")
|
||||
])
|
||||
)
|
||||
|
||||
hasClassAttribute = document.elementsMatchingSelector(hasAttributeSelector("class"))
|
||||
greetings = document.elementsMatchingSelector(classSelector("greeting"))
|
||||
classNameStartsWith_de = document.elementsMatchingSelector(attributeSelector(.Begins, "class", "de"))
|
||||
|
||||
hasAdjacentHeader = document.elementsMatchingSelector(adjacentSiblingSelector(typeSelector("h1")))
|
||||
hasAdjacentHeader = document.elementsMatchingSelector(generalSiblingSelector(typeSelector("h1")))
|
||||
hasAdjacentHeader = document.elementsMatchingSelector(generalSiblingSelector(typeSelector("p")))
|
||||
|
||||
nonParagraphChildOfDiv = document.elementsMatchingSelector(
|
||||
allOf([
|
||||
childOfElementSelector(typeSelector("div")),
|
||||
not(typeSelector("p"))
|
||||
])
|
||||
)
|
||||
|
||||
/*:
|
||||
Here are more examples
|
||||
*/
|
||||
|
||||
let firstDivElement = document.firstElementMatchingSelector(typeSelector("div"))!
|
||||
|
||||
var secondChildOfDiv = firstDivElement.querySelectorAll(":nth-child(2)")
|
||||
var secondOfType = firstDivElement.querySelectorAll(":nth-of-type(2n)")
|
||||
|
||||
secondChildOfDiv = firstDivElement.elementsMatchingSelector(nthChildSelector(CSSNthExpression(an: 0, b: 2)))
|
||||
secondOfType = firstDivElement.elementsMatchingSelector(nthOfTypeSelector(CSSNthExpression(an: 2, b: 0)))
|
||||
|
||||
|
||||
var notParagraphAndNotDiv = firstDivElement.querySelectorAll(":not(p):not(div)")
|
||||
notParagraphAndNotDiv = firstDivElement.elementsMatchingSelector(
|
||||
allOf([
|
||||
not(typeSelector("p")),
|
||||
not(typeSelector("div"))
|
||||
])
|
||||
)
|
||||
|
||||
/*:
|
||||
One more thing! You can also create your own selectors. You either subclass the CSSSelector or just use the block-based wrapper. For example the previous selector can be implemented like this:
|
||||
*/
|
||||
let myAwesomeSelector = namedBlockSelector("myAwesomeSelector", { (element) -> Bool in
|
||||
return element.tagName != "p" && element.tagName != "div"
|
||||
})
|
||||
firstDivElement.elementsMatchingSelector(myAwesomeSelector)
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -0,0 +1,30 @@
|
||||
/*:
|
||||
# HTMLKit
|
||||
|
||||

|
||||
|
||||
****
|
||||
|
||||
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&CharacterRangeLoc=572&EndingColumnNumber=5&EndingLineNumber=27&StartingColumnNumber=1&StartingLineNumber=26&Timestamp=472575682.16404"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=572&EndingColumnNumber=9&EndingLineNumber=27&StartingColumnNumber=5&StartingLineNumber=26&Timestamp=472575682.164357"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=572&EndingColumnNumber=9&EndingLineNumber=27&StartingColumnNumber=5&StartingLineNumber=26&Timestamp=472575682.16458"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=572&EndingColumnNumber=9&EndingLineNumber=1&StartingColumnNumber=5&StartingLineNumber=1&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&CharacterRangeLoc=318&EndingColumnNumber=26&EndingLineNumber=13&StartingColumnNumber=1&StartingLineNumber=13&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&CharacterRangeLoc=356&EndingColumnNumber=16&EndingLineNumber=23&StartingColumnNumber=2&StartingLineNumber=23&Timestamp=472578641.006172"
|
||||
lockedSize = "{775, 114}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=356&EndingColumnNumber=23&EndingLineNumber=23&StartingColumnNumber=2&StartingLineNumber=23&Timestamp=472578641.006502"
|
||||
lockedSize = "{775, 80}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=21&CharacterRangeLoc=262&EndingColumnNumber=23&EndingLineNumber=13&StartingColumnNumber=2&StartingLineNumber=13&Timestamp=472578641.006717"
|
||||
lockedSize = "{775, 76}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=453&EndingColumnNumber=26&EndingLineNumber=23&StartingColumnNumber=2&StartingLineNumber=23&Timestamp=472578641.006933"
|
||||
lockedSize = "{775, 83}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=668&EndingColumnNumber=26&EndingLineNumber=34&StartingColumnNumber=2&StartingLineNumber=34&Timestamp=472578641.007156"
|
||||
lockedSize = "{775, 76}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=281&EndingColumnNumber=26&EndingLineNumber=13&StartingColumnNumber=2&StartingLineNumber=13&Timestamp=472579861.71569"
|
||||
lockedSize = "{775, 68}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=448&EndingColumnNumber=26&EndingLineNumber=21&StartingColumnNumber=2&StartingLineNumber=21&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&CharacterRangeLoc=575&EndingColumnNumber=26&EndingLineNumber=24&StartingColumnNumber=1&StartingLineNumber=24&Timestamp=472578662.196737"
|
||||
lockedSize = "{763, 104}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=15&CharacterRangeLoc=843&EndingColumnNumber=22&EndingLineNumber=33&StartingColumnNumber=1&StartingLineNumber=33&Timestamp=472578662.197012"
|
||||
lockedSize = "{763, 75}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=1&CharacterRangeLoc=1757&EndingColumnNumber=44&EndingLineNumber=75&StartingColumnNumber=2&StartingLineNumber=75&Timestamp=472578674.159974"
|
||||
lockedSize = "{763, 176}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=844&EndingColumnNumber=15&EndingLineNumber=33&StartingColumnNumber=1&StartingLineNumber=33&Timestamp=472578662.197451"
|
||||
lockedSize = "{762, 70}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=18&CharacterRangeLoc=576&EndingColumnNumber=19&EndingLineNumber=24&StartingColumnNumber=1&StartingLineNumber=24&Timestamp=472578662.197662"
|
||||
lockedSize = "{752, 99}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=1&CharacterRangeLoc=1757&EndingColumnNumber=16&EndingLineNumber=75&StartingColumnNumber=2&StartingLineNumber=75&Timestamp=472578674.160606"
|
||||
lockedSize = "{763, 102}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=18&CharacterRangeLoc=1740&EndingColumnNumber=39&EndingLineNumber=75&StartingColumnNumber=2&StartingLineNumber=75&Timestamp=472578674.160818"
|
||||
lockedSize = "{759, 149}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=17&CharacterRangeLoc=1741&EndingColumnNumber=19&EndingLineNumber=75&StartingColumnNumber=2&StartingLineNumber=75&Timestamp=472578674.161034"
|
||||
lockedSize = "{763, 120}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=1230&EndingColumnNumber=15&EndingLineNumber=51&StartingColumnNumber=1&StartingLineNumber=51&Timestamp=472578674.161242"
|
||||
lockedSize = "{763, 94}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=1145&EndingColumnNumber=15&EndingLineNumber=45&StartingColumnNumber=1&StartingLineNumber=45&Timestamp=472578674.161449"
|
||||
lockedSize = "{760, 97}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=8&CharacterRangeLoc=1332&EndingColumnNumber=13&EndingLineNumber=57&StartingColumnNumber=5&StartingLineNumber=57&Timestamp=472578674.161666"
|
||||
lockedSize = "{763, 74}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=18&CharacterRangeLoc=397&EndingColumnNumber=19&EndingLineNumber=14&StartingColumnNumber=1&StartingLineNumber=14&Timestamp=472578662.199111"
|
||||
lockedSize = "{762, 90}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=701&EndingColumnNumber=15&EndingLineNumber=26&StartingColumnNumber=1&StartingLineNumber=26&Timestamp=472578662.199312"
|
||||
lockedSize = "{763, 76}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=1002&EndingColumnNumber=15&EndingLineNumber=38&StartingColumnNumber=1&StartingLineNumber=38&Timestamp=472578674.162269"
|
||||
lockedSize = "{763, 92}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=1049&EndingColumnNumber=15&EndingLineNumber=41&StartingColumnNumber=1&StartingLineNumber=41&Timestamp=472578674.162475"
|
||||
lockedSize = "{763, 83}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=8&CharacterRangeLoc=1108&EndingColumnNumber=13&EndingLineNumber=44&StartingColumnNumber=5&StartingLineNumber=44&Timestamp=472578674.162693"
|
||||
lockedSize = "{763, 73}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=17&CharacterRangeLoc=1598&EndingColumnNumber=19&EndingLineNumber=68&StartingColumnNumber=2&StartingLineNumber=68&Timestamp=472578674.162898"
|
||||
lockedSize = "{763, 92}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=8&CharacterRangeLoc=1410&EndingColumnNumber=9&EndingLineNumber=61&StartingColumnNumber=1&StartingLineNumber=59&Timestamp=472578674.163102"
|
||||
lockedSize = "{763, 60}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=590&EndingColumnNumber=15&EndingLineNumber=21&StartingColumnNumber=1&StartingLineNumber=21&Timestamp=472579886.937978"
|
||||
lockedSize = "{763, 50}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=830&EndingColumnNumber=15&EndingLineNumber=30&StartingColumnNumber=1&StartingLineNumber=30&Timestamp=472579886.937978"
|
||||
lockedSize = "{763, 50}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=877&EndingColumnNumber=15&EndingLineNumber=33&StartingColumnNumber=1&StartingLineNumber=33&Timestamp=472579886.937978"
|
||||
lockedSize = "{763, 50}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=8&CharacterRangeLoc=936&EndingColumnNumber=13&EndingLineNumber=36&StartingColumnNumber=5&StartingLineNumber=36&Timestamp=472579886.937978"
|
||||
lockedSize = "{763, 50}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=8&CharacterRangeLoc=978&EndingColumnNumber=9&EndingLineNumber=38&StartingColumnNumber=1&StartingLineNumber=38&Timestamp=472579886.937978"
|
||||
lockedSize = "{763, 50}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=17&CharacterRangeLoc=1261&EndingColumnNumber=19&EndingLineNumber=48&StartingColumnNumber=2&StartingLineNumber=48&Timestamp=472579886.937978"
|
||||
lockedSize = "{763, 92}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -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>
|
||||
@@ -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
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
+763
-350
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-OSX"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14C219C7829400AD0C32"
|
||||
BuildableName = "HTMLKitTests-OSX.xctest"
|
||||
BlueprintName = "HTMLKitTests-OSX"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance/testParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance/testTokenizerPerformance">
|
||||
</Test>
|
||||
</SkippedTests>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-OSX"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-OSX"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-OSX"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF551C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKitTests-iOS.xctest"
|
||||
BlueprintName = "HTMLKitTests-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance/testParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance/testTokenizerPerformance">
|
||||
</Test>
|
||||
</SkippedTests>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:HTMLKit.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:HTMLKit.playground">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// CSSAttributeSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 14/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CSSSelector.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
CSS Attribute Selector.
|
||||
*/
|
||||
@interface CSSAttributeSelector : CSSSelector
|
||||
|
||||
/**
|
||||
The selector type.
|
||||
*/
|
||||
@property (nonatomic, assign, readonly) CSSAttributeSelectorType type;
|
||||
|
||||
/**
|
||||
The attribute name which should be matched.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSString *name;
|
||||
|
||||
/**
|
||||
The attribute value against which should be checked.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSString *value;
|
||||
|
||||
/**
|
||||
Intializes and returns a CSS class selector.
|
||||
|
||||
@param className The class name to match.
|
||||
@returns A new instance of class selector.
|
||||
*/
|
||||
+ (instancetype)classSelector:(NSString *)className;
|
||||
|
||||
/**
|
||||
Intializes and returns a CSS id selector.
|
||||
|
||||
@param elementId The element id to match.
|
||||
@returns A new instance of id selector.
|
||||
*/
|
||||
+ (instancetype)idSelector:(NSString *)elementId;
|
||||
|
||||
/**
|
||||
Intializes and returns a CSS has-attribute selector.
|
||||
|
||||
@param attributeName The attribute name to match.
|
||||
@returns A new instance of has-attribute selector.
|
||||
*/
|
||||
+ (instancetype)hasAttributeSelector:(NSString *)attributeName;
|
||||
|
||||
/**
|
||||
Intializes and returns a CSS attribute selector.
|
||||
|
||||
@param type The selector type.
|
||||
@param name The attribute name to match.
|
||||
@param value The value to match.
|
||||
@returns A new instance of attribute selector.
|
||||
*/
|
||||
- (instancetype)initWithType:(CSSAttributeSelectorType)type
|
||||
attributeName:(NSString *)name
|
||||
attrbiuteValue:(NSString *)value;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// CSSAttributeSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 14/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSAttributeSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSCharacterSet+HTMLKit.h"
|
||||
|
||||
@interface CSSAttributeSelector ()
|
||||
{
|
||||
CSSAttributeSelectorType _type;
|
||||
NSString *_name;
|
||||
NSString *_value;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSAttributeSelector
|
||||
|
||||
+ (instancetype)classSelector:(NSString *)className
|
||||
{
|
||||
return [[self alloc] initWithType:CSSAttributeSelectorIncludes attributeName:@"class" attrbiuteValue:className];
|
||||
}
|
||||
|
||||
+ (instancetype)idSelector:(NSString *)elementId
|
||||
{
|
||||
return [[self alloc] initWithType:CSSAttributeSelectorExactMatch attributeName:@"id" attrbiuteValue:elementId];
|
||||
}
|
||||
|
||||
+ (instancetype)hasAttributeSelector:(NSString *)attributeName
|
||||
{
|
||||
return [[self alloc] initWithType:CSSAttributeSelectorExists attributeName:attributeName attrbiuteValue:@""];
|
||||
}
|
||||
|
||||
- (instancetype)initWithType:(CSSAttributeSelectorType)type
|
||||
attributeName:(NSString *)name
|
||||
attrbiuteValue:(NSString *)value
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_type = type;
|
||||
_name = [name copy];
|
||||
_value = value ? [value copy]: @"";
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
switch (_type) {
|
||||
case CSSAttributeSelectorExists:
|
||||
{
|
||||
return !!element[_name];
|
||||
}
|
||||
case CSSAttributeSelectorExactMatch:
|
||||
{
|
||||
return [element[_name] isEqualToString:_value];
|
||||
}
|
||||
case CSSAttributeSelectorIncludes:
|
||||
{
|
||||
NSArray *components = [element[_name] componentsSeparatedByCharactersInSet:[NSCharacterSet HTMLWhitespaceCharacterSet]];
|
||||
return [components containsObject:_value];
|
||||
}
|
||||
case CSSAttributeSelectorBegins:
|
||||
{
|
||||
return [element[_name] hasPrefix:_value];
|
||||
}
|
||||
case CSSAttributeSelectorEnds:
|
||||
{
|
||||
return [element[_name] hasSuffix:_value];
|
||||
}
|
||||
case CSSAttributeSelectorContains:
|
||||
{
|
||||
return [element[_name] containsString:_value];
|
||||
}
|
||||
case CSSAttributeSelectorHyphen:
|
||||
{
|
||||
return [element[_name] isEqualToString:_value] || [element[_name] hasPrefix:[_value stringByAppendingString:@"-"]];
|
||||
}
|
||||
case CSSAttributeSelectorNot:
|
||||
{
|
||||
return ![element[_name] isEqualToString:_value];
|
||||
}
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
if (self.type == CSSAttributeSelectorExists) {
|
||||
return [NSString stringWithFormat:@"[%@]", self.name];
|
||||
}
|
||||
|
||||
NSString *matcher = @{@(CSSAttributeSelectorExactMatch): @"=",
|
||||
@(CSSAttributeSelectorIncludes): @"~=",
|
||||
@(CSSAttributeSelectorBegins): @"^=",
|
||||
@(CSSAttributeSelectorEnds): @"$=",
|
||||
@(CSSAttributeSelectorContains): @"*=",
|
||||
@(CSSAttributeSelectorHyphen): @"|=",
|
||||
@(CSSAttributeSelectorNot): @"!="}[@(self.type)];
|
||||
|
||||
return [NSString stringWithFormat:@"[%@%@'%@']", self.name, matcher, self.value];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// CSSTokenizer CODEPOINTs.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 08/06/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
//------------------------------------------------------
|
||||
|
||||
#define CODEPOINTS \
|
||||
CODEPOINT( CONTROL, 0x0080 ) \
|
||||
CODEPOINT( CHARACTER_TABULATION, 0x0009 ) \
|
||||
CODEPOINT( LINE_FEED, 0x000A ) \
|
||||
CODEPOINT( SPACE, 0x0020 ) \
|
||||
CODEPOINT( QUOTATION_MARK, 0x0022 ) \
|
||||
CODEPOINT( NUMBER_SIGN, 0x0023 ) \
|
||||
CODEPOINT( DOLLAR_SIGN, 0x0024 ) \
|
||||
CODEPOINT( APOSTROPHE, 0x0027 ) \
|
||||
CODEPOINT( LEFT_PARENTHESIS, 0x0028 ) \
|
||||
CODEPOINT( RIGHT_PARENTHESIS, 0x0029 ) \
|
||||
CODEPOINT( ASTERIX, 0x002A ) \
|
||||
CODEPOINT( PLUS_SIGN, 0x002B ) \
|
||||
CODEPOINT( COMMA, 0x002C ) \
|
||||
CODEPOINT( HYPHEN_MINUS, 0x002D ) \
|
||||
CODEPOINT( FULL_STOP, 0x002E ) \
|
||||
CODEPOINT( DIGIT_ZERO, 0x0030 ) \
|
||||
CODEPOINT( DIGIT_NINE, 0x0039 ) \
|
||||
CODEPOINT( COLON, 0x003A ) \
|
||||
CODEPOINT( EQUALS_SIGN, 0x003D ) \
|
||||
CODEPOINT( GREATER_THAN_SIGN, 0x003E ) \
|
||||
CODEPOINT( LATIN_CAPITAL_LETTER_A, 0x0041 ) \
|
||||
CODEPOINT( LATIN_CAPITAL_LETTER_F, 0x0046 ) \
|
||||
CODEPOINT( LATIN_CAPITAL_LETTER_Z, 0x005A ) \
|
||||
CODEPOINT( LEFT_SQUARE_BRACKET, 0x005B ) \
|
||||
CODEPOINT( REVERSE_SOLIDUS, 0x005C ) \
|
||||
CODEPOINT( RIGHT_SQUARE_BRACKET, 0x005D ) \
|
||||
CODEPOINT( CIRCUMFLEX_ACCENT, 0x005E ) \
|
||||
CODEPOINT( LOW_LINE, 0x005F ) \
|
||||
CODEPOINT( LATIN_SMALL_LETTER_A, 0x0061 ) \
|
||||
CODEPOINT( LATIN_SMALL_LETTER_F, 0x0066 ) \
|
||||
CODEPOINT( LATIN_SMALL_LETTER_Z, 0x007A ) \
|
||||
CODEPOINT( VERTICAL_LINE, 0x007C ) \
|
||||
CODEPOINT( TILDE, 0x007E ) \
|
||||
CODEPOINT( REPLACEMENT_CHARACTER, 0xFFFD )
|
||||
|
||||
#define CODEPOINT( name, value ) static UniChar const name = value;
|
||||
CODEPOINTS
|
||||
#undef CODEPOINT
|
||||
|
||||
NS_INLINE BOOL isWhitespace(UTF32Char codePoint)
|
||||
{
|
||||
return (codePoint == CHARACTER_TABULATION ||
|
||||
codePoint == LINE_FEED ||
|
||||
codePoint == SPACE);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isCombinator(UTF32Char codePoint)
|
||||
{
|
||||
return (codePoint == SPACE ||
|
||||
codePoint == PLUS_SIGN ||
|
||||
codePoint == COMMA ||
|
||||
codePoint == GREATER_THAN_SIGN ||
|
||||
codePoint == TILDE);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isDigit(UTF32Char codePoint)
|
||||
{
|
||||
return codePoint >= DIGIT_ZERO && codePoint <= DIGIT_NINE;
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isHexDigit(UTF32Char codePoint)
|
||||
{
|
||||
return ((codePoint >= DIGIT_ZERO && codePoint <= DIGIT_NINE) ||
|
||||
(codePoint >= LATIN_CAPITAL_LETTER_A && codePoint <= LATIN_CAPITAL_LETTER_F) ||
|
||||
(codePoint >= LATIN_SMALL_LETTER_A && codePoint <= LATIN_SMALL_LETTER_F));
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isQuote(UTF32Char codePoint)
|
||||
{
|
||||
return codePoint == QUOTATION_MARK || codePoint == APOSTROPHE;
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isNewLine(UTF32Char codePoint)
|
||||
{
|
||||
return codePoint == LINE_FEED;
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isNameStart(UTF32Char codePoint)
|
||||
{
|
||||
return ((codePoint >= LATIN_CAPITAL_LETTER_A && codePoint <= LATIN_CAPITAL_LETTER_Z) ||
|
||||
(codePoint >= LATIN_SMALL_LETTER_A && codePoint <= LATIN_SMALL_LETTER_Z) ||
|
||||
codePoint >= CONTROL ||
|
||||
codePoint == LOW_LINE);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isName(UTF32Char codePoint)
|
||||
{
|
||||
return isNameStart(codePoint) || isDigit(codePoint) || codePoint == HYPHEN_MINUS;
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isValidEscape(UTF32Char first, UTF32Char second)
|
||||
{
|
||||
if (first != REVERSE_SOLIDUS || isNewLine(second)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isValidEscapedCodePoint(UTF32Char codePoint)
|
||||
{
|
||||
return (codePoint != 0 &&
|
||||
!(codePoint >= 0xD800 && codePoint <= 0x0DFFF) &&
|
||||
codePoint <= 0x10FFFF);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isValidIdentifierStart(UTF32Char first, UTF32Char second, UTF32Char third)
|
||||
{
|
||||
if (first == HYPHEN_MINUS) {
|
||||
if (isNameStart(second) ||
|
||||
second == HYPHEN_MINUS ||
|
||||
isValidEscape(second, third)) {
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
} else if (isNameStart(first)) {
|
||||
return YES;
|
||||
} else if (first == REVERSE_SOLIDUS) {
|
||||
return isValidEscape(first, second);
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
NS_INLINE void AppendCodePoint(CFMutableStringRef string, UTF32Char codePoint)
|
||||
{
|
||||
UniChar pair[2];
|
||||
Boolean isPair = CFStringGetSurrogatePairForLongCharacter(codePoint, pair);
|
||||
CFStringAppendCharacters(string, pair, isPair ? 2 : 1);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// CSSCombinatorSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 12/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelector.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
CSS Combinator Selector.
|
||||
*/
|
||||
@interface CSSCombinatorSelector : CSSSelector
|
||||
|
||||
/**
|
||||
Initializes and returns a CSS child-of-element selector, e.g. 'div > p'
|
||||
|
||||
@param selector The selector matching the parent element.
|
||||
@returns A new instance of the child of element selector.
|
||||
*/
|
||||
+ (instancetype)childOfElementCombinator:(CSSSelector *)selector;
|
||||
|
||||
/**
|
||||
Initializes and returns a CSS descendant-of-element selector, e.g. 'div p'
|
||||
|
||||
@param selector The selector matching the ancestor element.
|
||||
@returns A new instance of the descendant of element selector.
|
||||
*/
|
||||
+ (instancetype)descendantOfElementCombinator:(CSSSelector *)selector;
|
||||
|
||||
/**
|
||||
Initializes and returns a CSS adjacent sibling selector, e.g. 'p + a'
|
||||
|
||||
@param selector The selector matching the adjacent sibling element.
|
||||
@returns A new instance of the adjacent sibling selector.
|
||||
*/
|
||||
+ (instancetype)adjacentSiblingCombinator:(CSSSelector *)selector;
|
||||
|
||||
/**
|
||||
Initializes and returns a CSS general sibling selector, e.g. 'p ~ a'
|
||||
|
||||
@param selector The selector matching the general sibling element.
|
||||
@returns A new instance of the general sibling selector.
|
||||
*/
|
||||
+ (instancetype)generalSiblingCombinator:(CSSSelector *)selector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// CSSCombinatorSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 12/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSCombinatorSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
#pragma mark - Declarations
|
||||
|
||||
@interface CSSChildOfElementCombinatorSelector : CSSCombinatorSelector
|
||||
@end
|
||||
|
||||
@interface CSSDecendantOfElementCombinatorSelector : CSSCombinatorSelector
|
||||
@end
|
||||
|
||||
@interface CSSAdjacentSiblingCombinatorSelector : CSSCombinatorSelector
|
||||
@end
|
||||
|
||||
@interface CSSGeneralSiblingCombinatorSelector : CSSCombinatorSelector
|
||||
@end
|
||||
|
||||
#pragma mark - Base Combinator
|
||||
|
||||
@interface CSSCombinatorSelector ()
|
||||
{
|
||||
CSSSelector *_selector;
|
||||
}
|
||||
@property (nonatomic, strong, readonly) CSSSelector *selector;
|
||||
@end
|
||||
|
||||
@implementation CSSCombinatorSelector
|
||||
@synthesize selector = _selector;
|
||||
|
||||
+ (instancetype)childOfElementCombinator:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSChildOfElementCombinatorSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
+ (instancetype)descendantOfElementCombinator:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSDecendantOfElementCombinatorSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
+ (instancetype)adjacentSiblingCombinator:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSAdjacentSiblingCombinatorSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
+ (instancetype)generalSiblingCombinator:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSGeneralSiblingCombinatorSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelector:(CSSSelector *)selector
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Child OfElement Combinator
|
||||
|
||||
@implementation CSSChildOfElementCombinatorSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLElement *parent = element.parentElement;
|
||||
return parent != nil && [self.selector acceptElement:parent];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ > ", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Decendant Of Element Combinator
|
||||
|
||||
@implementation CSSDecendantOfElementCombinatorSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLElement *parent = element.parentElement;
|
||||
|
||||
while (parent != nil) {
|
||||
if ([self.selector acceptElement:parent]) {
|
||||
return YES;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ ", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Adjacent Sibling Combinator
|
||||
|
||||
@implementation CSSAdjacentSiblingCombinatorSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLNode *previous = element.previousSiblingElement;
|
||||
if (previous == nil || previous.nodeType != HTMLNodeElement) {
|
||||
return NO;
|
||||
}
|
||||
return [self.selector acceptElement:previous.asElement];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ + ", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - General Sibling Combinator
|
||||
|
||||
@implementation CSSGeneralSiblingCombinatorSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLNode *previous = element.previousSiblingElement;
|
||||
|
||||
while (previous != nil && previous.nodeType == HTMLNodeElement) {
|
||||
if ([self.selector acceptElement:previous.asElement]) {
|
||||
return YES;
|
||||
}
|
||||
previous = previous.previousSiblingElement;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ ~ ", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// CSSCompoundSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 18/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelector.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A Compound Selector, groups other selectors with a 'all-of' or 'any-of' relationship.
|
||||
*/
|
||||
@interface CSSCompoundSelector : CSSSelector
|
||||
|
||||
/**
|
||||
Initializes and returns a new compound selector matching only elements that match all of the specified selectors.
|
||||
|
||||
@param selectors The selectors list.
|
||||
@returns A new instance of the All-Of selector.
|
||||
*/
|
||||
+ (instancetype)andSelector:(NSArray<CSSSelector *> *)selectors;
|
||||
|
||||
/**
|
||||
Initializes and returns a new compound selector matching all elements that match at least one of the specified selectors.
|
||||
|
||||
@param selectors The selectors list.
|
||||
@returns A new instance of the Any-Of selector.
|
||||
*/
|
||||
+ (instancetype)orSelector:(NSArray<CSSSelector *> *)selectors;
|
||||
|
||||
/**
|
||||
Add the specified selector to the compound.
|
||||
|
||||
@param selector The selector to add.
|
||||
*/
|
||||
- (void)addSelector:(CSSSelector *)selector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// CSSCompoundSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 18/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSCompoundSelector.h"
|
||||
|
||||
#pragma mark - Declarations
|
||||
|
||||
@interface CSSAndCompoundSelector : CSSCompoundSelector
|
||||
@end
|
||||
|
||||
@interface CSSOrCompoundSelector : CSSCompoundSelector
|
||||
@end
|
||||
|
||||
#pragma mark - Base Combinator
|
||||
|
||||
@interface CSSCompoundSelector ()
|
||||
{
|
||||
NSMutableArray *_selectors;
|
||||
}
|
||||
@property (nonatomic, strong, readonly) NSArray *selectors;
|
||||
@end
|
||||
|
||||
@implementation CSSCompoundSelector
|
||||
@synthesize selectors = _selectors;
|
||||
|
||||
+ (instancetype)andSelector:(NSArray *)selectors
|
||||
{
|
||||
return [[CSSAndCompoundSelector alloc] initWithSelectors:selectors];
|
||||
}
|
||||
|
||||
+ (instancetype)orSelector:(NSArray *)selectors
|
||||
{
|
||||
return [[CSSOrCompoundSelector alloc] initWithSelectors:selectors];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelectors:(NSArray *)selectors
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selectors = [[NSMutableArray alloc] initWithArray:selectors];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addSelector:(CSSSelector *)selector
|
||||
{
|
||||
[_selectors addObject:selector];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - And Compound Selector
|
||||
|
||||
@implementation CSSAndCompoundSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
for (CSSSelector *selector in self.selectors) {
|
||||
if (![selector acceptElement:element]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
NSArray *descriptions = [self.selectors valueForKey:@"debugDescription"];
|
||||
return [descriptions componentsJoinedByString:@""];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Or Compound Selector
|
||||
|
||||
@implementation CSSOrCompoundSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
for (CSSSelector *selector in self.selectors) {
|
||||
if ([selector acceptElement:element]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
NSArray *descriptions = [self.selectors valueForKey:@"debugDescription"];
|
||||
return [descriptions componentsJoinedByString:@","];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// CSSInputStream.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 07/06/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLInputStreamReader.h"
|
||||
|
||||
/**
|
||||
The CSS Inpute Stream.
|
||||
|
||||
Extends the HTML Input Stream with methods relevant to CSS selectors tokenizing/parsing.
|
||||
*/
|
||||
@interface CSSInputStream : HTMLInputStreamReader
|
||||
|
||||
/**
|
||||
Consumes leading whitespace characters.
|
||||
*/
|
||||
- (void)consumeWhitespace;
|
||||
|
||||
/**
|
||||
Consumes a CSS identifier.
|
||||
http://www.w3.org/TR/css-syntax-3/#consume-an-ident-like-token
|
||||
http://www.w3.org/TR/css-syntax-3/#consume-a-string-token
|
||||
http://www.w3.org/TR/css-syntax-3/#would-start-an-identifier
|
||||
|
||||
@returns A consumed identifier, `nil` if the stream doesn't start with a valid identifier.
|
||||
*/
|
||||
- (NSString *)consumeIdentifier;
|
||||
|
||||
/**
|
||||
Consumes characters until the specified code-point is met.
|
||||
|
||||
@param endingCodePoint The code-point at which the input stream stops consuming.
|
||||
@returns The consumed string, `nil` nothing was consumed.
|
||||
*/
|
||||
- (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint;
|
||||
|
||||
/**
|
||||
Consumes an escaped code point.
|
||||
http://www.w3.org/TR/css-syntax-3/#consume-an-escaped-code-point
|
||||
http://www.w3.org/TR/css-syntax-3/#starts-with-a-valid-escape
|
||||
|
||||
@returns The value of the escaped code-point.
|
||||
*/
|
||||
- (UTF32Char)consumeEscapedCodePoint;
|
||||
|
||||
/**
|
||||
Consumes a CSS selector combinator.
|
||||
|
||||
@returns The consumed combinator, `nil` if nothing was consumed.
|
||||
*/
|
||||
- (NSString *)consumeCombinator;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// CSSInputStream.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 07/06/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSInputStream.h"
|
||||
#import "CSSCodePoints.h"
|
||||
|
||||
@interface HTMLInputStreamReader ()
|
||||
- (void)emitParseError:(NSString *)reason;
|
||||
@end
|
||||
|
||||
@implementation CSSInputStream
|
||||
|
||||
- (void)consumeWhitespace
|
||||
{
|
||||
while (isWhitespace(self.nextInputCharacter)) {
|
||||
[self consumeNextInputCharacter];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)consumeIdentifier
|
||||
{
|
||||
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
|
||||
|
||||
if (!isValidIdentifierStart([self inputCharacterPointAtOffset:0],
|
||||
[self inputCharacterPointAtOffset:1],
|
||||
[self inputCharacterPointAtOffset:2])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
while (YES) {
|
||||
UTF32Char codePoint = [self consumeNextInputCharacter];
|
||||
if (codePoint == EOF) {
|
||||
break;
|
||||
} else if (isName(codePoint)) {
|
||||
AppendCodePoint(value, codePoint);
|
||||
} else if (isValidEscape(codePoint, [self inputCharacterPointAtOffset:1])) {
|
||||
UTF32Char escapedCodePoint = [self consumeEscapedCodePoint];
|
||||
AppendCodePoint(value, escapedCodePoint);
|
||||
} else {
|
||||
[self reconsumeCurrentInputCharacter];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil);
|
||||
}
|
||||
|
||||
- (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint
|
||||
{
|
||||
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
|
||||
|
||||
while (YES) {
|
||||
UTF32Char codePoint = [self consumeNextInputCharacter];
|
||||
if (codePoint == endingCodePoint) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (codePoint) {
|
||||
case EOF:
|
||||
break;
|
||||
case LINE_FEED:
|
||||
[self emitParseError:@"New-line character (0x000A) in CSS attribute value"];
|
||||
[self reconsumeCurrentInputCharacter];
|
||||
break;
|
||||
case REVERSE_SOLIDUS:
|
||||
{
|
||||
UTF32Char next = self.nextInputCharacter;
|
||||
if (next == EOF) {
|
||||
continue;
|
||||
} else if (next == LINE_FEED) {
|
||||
[self consumeNextInputCharacter];
|
||||
} else {
|
||||
UTF32Char escapedCodePoint = [self consumeNextInputCharacter];
|
||||
AppendCodePoint(value, escapedCodePoint);
|
||||
}
|
||||
}
|
||||
default:
|
||||
AppendCodePoint(value, codePoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil);
|
||||
}
|
||||
|
||||
- (UTF32Char)consumeEscapedCodePoint
|
||||
{
|
||||
UTF32Char codePoint = [self consumeNextInputCharacter];
|
||||
|
||||
if (isHexDigit(codePoint)) {
|
||||
CFMutableStringRef hexString = CFStringCreateMutable(kCFAllocatorDefault, 6);
|
||||
AppendCodePoint(hexString, codePoint);
|
||||
|
||||
while (isHexDigit(self.nextInputCharacter) && CFStringGetLength(hexString) <= 6) {
|
||||
UniChar codePoint = [self consumeNextInputCharacter];
|
||||
CFStringAppendCharacters(hexString, &codePoint, 1);
|
||||
}
|
||||
|
||||
if (isWhitespace(self.nextInputCharacter)) {
|
||||
[self consumeNextInputCharacter];
|
||||
}
|
||||
|
||||
NSScanner *scanner = [NSScanner scannerWithString:(__bridge NSString *)(hexString)];
|
||||
UTF32Char number;
|
||||
[scanner scanHexInt:&number];
|
||||
|
||||
return isValidEscapedCodePoint(number) ? number : REPLACEMENT_CHARACTER;
|
||||
} else if (codePoint == EOF) {
|
||||
return REPLACEMENT_CHARACTER;
|
||||
}
|
||||
|
||||
return codePoint;
|
||||
}
|
||||
|
||||
- (NSString *)consumeCombinator
|
||||
{
|
||||
NSString *combinator = [self consumeCharactersInString:@" >+~"];
|
||||
combinator = [combinator stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
|
||||
return combinator;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// CSSNthExpression.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 10/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CSSSelectors.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The Nth-Expression Parser.
|
||||
|
||||
Parses CSS nth-expressions, e.g. '-2n+3', 'odd', ...etc.
|
||||
*/
|
||||
@interface CSSNthExpressionParser : NSObject
|
||||
|
||||
/**
|
||||
Parses a CSS nth-exrepssion string.
|
||||
|
||||
@param expression The expression string to parse.
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
+ (CSSNthExpression)parseExpression:(NSString *)expression;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// CSSNthExpression.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 10/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSNthExpressionParser.h"
|
||||
#import "CSSCodePoints.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "NSCharacterSet+HTMLKit.h"
|
||||
|
||||
@implementation CSSNthExpressionParser
|
||||
|
||||
+ (CSSNthExpression)parseExpression:(NSString *)expression
|
||||
{
|
||||
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
||||
|
||||
NSString *string = [expression.lowercaseString copy];
|
||||
string = [[string stringByTrimmingCharactersInSet:whitespace] copy];
|
||||
|
||||
if ([string isEqualToStringIgnoringCase:@"odd"]) {
|
||||
return CSSNthExpressionOdd;
|
||||
} else if ([string isEqualToStringIgnoringCase:@"even"]) {
|
||||
return CSSNthExpressionEven;
|
||||
}
|
||||
|
||||
NSCharacterSet *set = [[NSCharacterSet CSSNthExpressionCharacterSet] invertedSet];
|
||||
if ([string rangeOfCharacterFromSet:set].location != NSNotFound) {
|
||||
return CSSNthExpressionMake(0, 0);
|
||||
}
|
||||
NSArray *parts = [string componentsSeparatedByString:@"n"];
|
||||
|
||||
if (parts.count == 1) {
|
||||
NSInteger b = [parts[0] integerValue];
|
||||
return CSSNthExpressionMake(0, b);
|
||||
} else if (parts.count == 2) {
|
||||
NSInteger a = [parts[0] integerValue];
|
||||
if (a == 0) {
|
||||
a = [parts[0] isEqualToString:@"-"] ? -1 : 1;
|
||||
}
|
||||
NSInteger b = [parts[1] integerValue];
|
||||
return CSSNthExpressionMake(a, b);
|
||||
} else {
|
||||
return CSSNthExpressionMake(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// CSSNthExpressionSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 10/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelector.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
CSS Nth-Expression Selector.
|
||||
*/
|
||||
@interface CSSNthExpressionSelector : CSSSelector
|
||||
|
||||
/**
|
||||
The pseudo-class name.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSString *className;
|
||||
|
||||
/**
|
||||
The nth-expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
@property (nonatomic, assign, readonly) CSSNthExpression expression;
|
||||
|
||||
/**
|
||||
Initializes a new CSS nth-child selector, e.g. ':nth-child(2n+3)'
|
||||
|
||||
@param expression The nth-expression.
|
||||
@returns Nth-Child selector for the specified expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
+ (instancetype)nthChildSelector:(CSSNthExpression)expression;
|
||||
|
||||
/**
|
||||
Initializes a new CSS nth-last-child selector, e.g. ':nth-last-child(2n+3)'
|
||||
|
||||
@param expression The nth-expression.
|
||||
@returns Nth-Last-Child selector for the specified expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
+ (instancetype)nthLastChildSelector:(CSSNthExpression)expression;
|
||||
|
||||
/**
|
||||
Initializes a new CSS nth-of-type selector, e.g. ':nth-of-type(2n+3)'
|
||||
|
||||
@param expression The nth-expression.
|
||||
@returns Nth-Of-Type selector for the specified expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
+ (instancetype)nthOfTypeSelector:(CSSNthExpression)expression;
|
||||
|
||||
/**
|
||||
Initializes a new CSS nth-last-of-type selector, e.g. ':nth-last-of-type(2n+3)'
|
||||
|
||||
@param expression The nth-expression.
|
||||
@returns Nth-Last-Of-Type selector for the specified expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
+ (instancetype)nthLastOfTypeSelector:(CSSNthExpression)expression;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// CSSNthExpressionSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 10/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSNthExpressionSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
#pragma mark - Nth-Expression
|
||||
|
||||
const CSSNthExpression CSSNthExpressionOdd = (CSSNthExpression) {
|
||||
.an = 2, .b = 1
|
||||
};
|
||||
|
||||
const CSSNthExpression CSSNthExpressionEven = (CSSNthExpression) {
|
||||
.an = 2, .b = 0
|
||||
};
|
||||
|
||||
NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression)
|
||||
{
|
||||
if (expression.an == 0 && expression.b == 0) {
|
||||
return @"invalid";
|
||||
}
|
||||
|
||||
if (expression.an == 0) {
|
||||
return [NSString stringWithFormat:@"%ld", expression.b];
|
||||
}
|
||||
if (expression.b == 0) {
|
||||
return [NSString stringWithFormat:@"%ldn", expression.an];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%ldn%+ld", expression.an, expression.b];
|
||||
}
|
||||
|
||||
#pragma mark - Implementation
|
||||
|
||||
NSInteger computeIndex(NSEnumerator *enumerator, HTMLElement *element)
|
||||
{
|
||||
NSInteger index = 0;
|
||||
for (HTMLNode *node in enumerator) {
|
||||
if (node.nodeType != HTMLNodeElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([node.asElement.tagName isEqualToString:element.tagName]) {
|
||||
index++;
|
||||
}
|
||||
|
||||
if (node == element) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
@interface CSSNthExpressionSelector ()
|
||||
{
|
||||
NSString *_className;
|
||||
CSSNthExpression _expression;
|
||||
NSInteger (^ _computeIndex)(HTMLElement *);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSNthExpressionSelector
|
||||
@synthesize expression = _expression;
|
||||
@synthesize className = _className;
|
||||
|
||||
+ (instancetype)nthChildSelector:(CSSNthExpression)expression
|
||||
{
|
||||
return [[self alloc] initWithClassName:@"nth-child" expression:expression block:^NSInteger(HTMLElement *element) {
|
||||
return [element.parentElement indexOfChildElement:element] + 1;
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)nthLastChildSelector:(CSSNthExpression)expression
|
||||
{
|
||||
return [[self alloc] initWithClassName:@"nth-last-child" expression:expression block:^NSInteger(HTMLElement *element) {
|
||||
return element.parentElement.childElementsCount - [element.parentElement indexOfChildElement:element];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)nthOfTypeSelector:(CSSNthExpression)expression
|
||||
{
|
||||
return [[self alloc] initWithClassName:@"nth-of-type" expression:expression block:^NSInteger(HTMLElement *element) {
|
||||
return computeIndex(element.parentElement.childNodes.array.objectEnumerator, element);
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)nthLastOfTypeSelector:(CSSNthExpression)expression
|
||||
{
|
||||
return [[self alloc] initWithClassName:@"nth-last-of-type" expression:expression block:^NSInteger(HTMLElement *element) {
|
||||
return computeIndex(element.parentElement.childNodes.array.reverseObjectEnumerator, element);
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithClassName:(NSString *)className
|
||||
expression:(CSSNthExpression)expression
|
||||
block:(NSInteger (^)(HTMLElement *element))block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_className = [className copy];
|
||||
_expression = expression;
|
||||
_computeIndex = [block copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
NSInteger index = _computeIndex(element);
|
||||
|
||||
if (_expression.an == 0) {
|
||||
return index == _expression.b;
|
||||
} else {
|
||||
NSInteger diff = (index - _expression.b);
|
||||
return (diff * _expression.an >= 0) && (diff % _expression.an == 0);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@":%@(%@)", self.className, NSStringFromNthExpression(self.expression)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// CSSPseudoClassSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 06/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelector.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Base class for CSS Pseudo Class Selectors. This is just a simple named wrapper around another selector.
|
||||
*/
|
||||
@interface CSSPseudoClassSelector : CSSSelector
|
||||
|
||||
/**
|
||||
The pseudo-class name.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSString *className;
|
||||
|
||||
/**
|
||||
Initializes and return a new pseudo-class selector.
|
||||
|
||||
@param className The pseudo class name.
|
||||
@param selector The underlying selector.
|
||||
@returns A new instance of a pseudo-class selector.
|
||||
*/
|
||||
- (instancetype)initWithClassName:(NSString *)className selector:(CSSSelector *)selector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// CSSPseudoClassSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 06/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSPseudoClassSelector.h"
|
||||
|
||||
@interface CSSPseudoClassSelector ()
|
||||
{
|
||||
NSString *_className;
|
||||
CSSSelector *_selector;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSPseudoClassSelector
|
||||
@synthesize className = _className;
|
||||
|
||||
- (instancetype)initWithClassName:(NSString *)className selector:(CSSSelector *)selector
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_className = [className copy];
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
return [_selector acceptElement:element];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@":%@", self.className];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// CSSPseudoFunctionSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 07/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelector.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
CSS Pseudo-Function Selector
|
||||
*/
|
||||
@interface CSSPseudoFunctionSelector : CSSSelector
|
||||
|
||||
/**
|
||||
Initializes and returns a CSS nagation selector, e.g. ':not(div)'
|
||||
|
||||
@param selector The selector which should be negated.
|
||||
@returns A new instance of the negation selector.
|
||||
*/
|
||||
+ (instancetype)notSelector:(CSSSelector *)selector;
|
||||
|
||||
/**
|
||||
Initializes and returns a CSS has-descendant selector, e.g. 'div:has(p)'
|
||||
|
||||
@discussion 'div:has(p)' matches all <div> elements which have a descendant <p> element.
|
||||
|
||||
@param selector The selector matching a descendant element.
|
||||
@returns A new instance of the has-descendant selector.
|
||||
*/
|
||||
+ (instancetype)hasSelector:(CSSSelector *)selector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// CSSPseudoFunctionSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 07/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSPseudoFunctionSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
#pragma mark - Declarations
|
||||
|
||||
@interface CSSNotSelector : CSSPseudoFunctionSelector
|
||||
@end
|
||||
|
||||
@interface CSSHasSelector : CSSPseudoFunctionSelector
|
||||
@end
|
||||
|
||||
#pragma mark - Base Function Selector
|
||||
|
||||
@interface CSSPseudoFunctionSelector ()
|
||||
{
|
||||
CSSSelector *_selector;
|
||||
}
|
||||
@property (nonatomic, strong, readonly) CSSSelector *selector;
|
||||
@end
|
||||
|
||||
@implementation CSSPseudoFunctionSelector
|
||||
@synthesize selector = _selector;
|
||||
|
||||
+ (instancetype)notSelector:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSNotSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
+ (instancetype)hasSelector:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSHasSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelector:(CSSSelector *)selector
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Not Selector
|
||||
|
||||
@implementation CSSNotSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
return ![self.selector acceptElement:element];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@":not(%@)", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Has Selector
|
||||
|
||||
@implementation CSSHasSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLNodeIterator *iterator = [element nodeIteratorWithShowOptions:HTMLNodeFilterShowAll filter:nil];
|
||||
for (HTMLNode *descendant in iterator) {
|
||||
if (descendant.nodeType == HTMLNodeElement && [self.selector acceptElement:descendant.asElement]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@":has(%@)", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// HTMLSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 02/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Attribute Selector Type
|
||||
|
||||
/**
|
||||
Attribute selector type.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, CSSAttributeSelectorType)
|
||||
{
|
||||
/** Attribute exists: '[src]' */
|
||||
CSSAttributeSelectorExists,
|
||||
|
||||
/** Attribute has exact value: '[title="HTMLKit"]' */
|
||||
CSSAttributeSelectorExactMatch,
|
||||
|
||||
/** Attribute includes value: '[title~="foo"]' */
|
||||
CSSAttributeSelectorIncludes,
|
||||
|
||||
/** Attribute's value begins with: '[title^="HTML"]' */
|
||||
CSSAttributeSelectorBegins,
|
||||
|
||||
/** Attribute's value ends with: '[title$="Kit"]' */
|
||||
CSSAttributeSelectorEnds,
|
||||
|
||||
/** Attribute's value ends with: '[title*="ML"]' */
|
||||
CSSAttributeSelectorContains,
|
||||
|
||||
/** Attribute's value ends with: '[title|="en"]' */
|
||||
CSSAttributeSelectorHyphen,
|
||||
|
||||
/** Attribute's value does not equal: '[title!="foo"]' */
|
||||
CSSAttributeSelectorNot
|
||||
};
|
||||
|
||||
#pragma mark - CSS Nth-Expression
|
||||
|
||||
/**
|
||||
CSS Nth-Expression
|
||||
*/
|
||||
typedef struct CSSNthExpression
|
||||
{
|
||||
NSInteger an;
|
||||
NSInteger b;
|
||||
} CSSNthExpression;
|
||||
|
||||
NS_INLINE CSSNthExpression CSSNthExpressionMake(NSInteger an, NSInteger b) {
|
||||
return (CSSNthExpression){ .an = an, .b = b };
|
||||
}
|
||||
|
||||
extern const CSSNthExpression CSSNthExpressionOdd;
|
||||
extern const CSSNthExpression CSSNthExpressionEven;
|
||||
extern NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression);
|
||||
|
||||
#pragma mark - Base Selector Class
|
||||
|
||||
@class HTMLElement;
|
||||
|
||||
/**
|
||||
Base class for all CSS Selector implementations
|
||||
*/
|
||||
@interface CSSSelector : NSObject
|
||||
|
||||
/**
|
||||
Initializes and returns a new instance of CSS Selector.
|
||||
|
||||
@param string The selector string which will be parsed.
|
||||
@returns A new instance of a parsed CSS Selector, `nil` if the string is not a valid selector string.
|
||||
*/
|
||||
+ (nullable instancetype)selectorWithString:(NSString *)stirng;
|
||||
|
||||
/**
|
||||
Implementations should override this method to provide the selector-sprecific logic for matching elements.
|
||||
|
||||
@abstract Use one of the concrete subclasses.
|
||||
*/
|
||||
- (BOOL)acceptElement:(HTMLElement *)element;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// CSSSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/10/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CSSSelector.h"
|
||||
#import "CSSSelectorParser.h"
|
||||
|
||||
@implementation CSSSelector
|
||||
|
||||
+ (instancetype)selectorWithString:(NSString *)string
|
||||
{
|
||||
NSError *error = nil;
|
||||
CSSSelector *instance = [CSSSelectorParser parseSelector:string error:&error];
|
||||
if (error) {
|
||||
return nil;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// CSSSelectorBlock.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelector.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class HTMLElement;
|
||||
|
||||
/**
|
||||
A block-based CSS Selector implementation
|
||||
*/
|
||||
@interface CSSSelectorBlock : CSSSelector
|
||||
|
||||
/**
|
||||
Initializes and returns a new block-based selector.
|
||||
|
||||
@param name The name of the selector.
|
||||
@param block The block that should match desired elements.
|
||||
@returns A new instance of the block-based selector.
|
||||
*/
|
||||
- (instancetype)initWithName:(NSString *)name block:(BOOL (^)(HTMLElement *))block;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// CSSSelectorBlock.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelectorBlock.h"
|
||||
|
||||
@interface CSSSelectorBlock ()
|
||||
{
|
||||
NSString *_name;
|
||||
BOOL (^ _acceptBlock)(HTMLElement *);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSSelectorBlock
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name block:(BOOL (^)(HTMLElement *))block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = [name copy];
|
||||
_acceptBlock = [block copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
return _acceptBlock ? _acceptBlock(element) : NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// CSSSelectorParser.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 02/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CSSSelector;
|
||||
|
||||
/**
|
||||
The CSS Selectors Parser.
|
||||
|
||||
Parses CSS Level 3 Selectors:
|
||||
http://www.w3.org/TR/css3-selectors/
|
||||
*/
|
||||
@interface CSSSelectorParser : NSObject
|
||||
|
||||
/**
|
||||
Parses a CSS3 selector string.
|
||||
|
||||
@param string The CSS3 selector string.
|
||||
@param error If an error occurs, upon return contains an `NSError` object that describes the problem.
|
||||
@returns A parsed CSSSelector, `nil` if an error occurred.
|
||||
|
||||
@see CSSelector
|
||||
*/
|
||||
+ (CSSSelector *)parseSelector:(NSString *)string error:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,426 @@
|
||||
//
|
||||
// CSSSelectorParser.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 02/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelectorParser.h"
|
||||
#import "CSSInputStream.h"
|
||||
#import "CSSCodePoints.h"
|
||||
#import "CSSSelectors.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "NSCharacterSet+HTMLKit.h"
|
||||
#import "CSSNthExpressionParser.h"
|
||||
#import "CSSCompoundSelector.h"
|
||||
#import "HTMLKitErrorDomain.h"
|
||||
|
||||
@interface CSSSelectorParser ()
|
||||
{
|
||||
NSString *_string;
|
||||
CSSInputStream *_inputStream;
|
||||
NSUInteger _location;
|
||||
|
||||
NSMutableArray *_selectors;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSSelectorParser
|
||||
|
||||
+ (CSSSelector *)parseSelector:(NSString *)string error:(NSError * __autoreleasing *)error
|
||||
{
|
||||
CSSSelectorParser *parser = [[CSSSelectorParser alloc] initWithString:string];
|
||||
CSSSelector *selector = [parser parse:error];
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)initWithString:(NSString *)string
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_string = [self preprocessInput:string];
|
||||
_location = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)preprocessInput:(NSString *)string
|
||||
{
|
||||
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"\r" withString:@"\n"];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"\f" withString:@"\n"];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"\0" withString:@"\uFFFD"];
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
#pragma mark - Errors
|
||||
|
||||
- (void)emitError:(NSError * __autoreleasing *)error reason:(NSString *)reason
|
||||
{
|
||||
[self emitError:error reason:reason location:_location + _inputStream.currentLocation];
|
||||
}
|
||||
|
||||
- (void)emitError:(NSError * __autoreleasing *)error reason:(NSString *)reason location:(NSUInteger)location
|
||||
{
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: @"Error parsing selector",
|
||||
NSLocalizedFailureReasonErrorKey: reason,
|
||||
CSSSelectorStringKey: _string,
|
||||
CSSSelectorErrorLocationKey: @(location)
|
||||
};
|
||||
|
||||
if(error && *error == nil) {
|
||||
*error = [NSError errorWithDomain:HTMLKitSelectorErrorDomain code:HTMLKitSelectorParseError userInfo:userInfo];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Parsing
|
||||
|
||||
- (CSSSelector *)parse:(NSError * __autoreleasing *)error
|
||||
{
|
||||
if (_string.length == 0) {
|
||||
[self emitError:error reason:@"Empty selector" location:0];
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSArray *allSubSelectors = [_string componentsSeparatedByString:@","];
|
||||
NSMutableArray *parsed = [NSMutableArray array];
|
||||
|
||||
for (NSString *subSelector in allSubSelectors) {
|
||||
if ([subSelector isEqualToString:@""]) {
|
||||
[self emitError:error reason:@"Empty selector" location:_location];
|
||||
break;
|
||||
}
|
||||
|
||||
CSSSelector *selector = [self parseSelector:subSelector error:error];
|
||||
if (selector == nil) {
|
||||
break;
|
||||
}
|
||||
[parsed addObject:selector];
|
||||
|
||||
_location += subSelector.length;
|
||||
}
|
||||
|
||||
if (error && *error != nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (parsed.count > 1) {
|
||||
return anyOf(parsed);
|
||||
}
|
||||
|
||||
return parsed.firstObject;
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseSelector:(NSString *)selectorString error:(NSError * __autoreleasing *)error
|
||||
{
|
||||
_inputStream = [[CSSInputStream alloc] initWithString:selectorString];
|
||||
[_inputStream consumeWhitespace];
|
||||
|
||||
CSSSelector *result = nil;
|
||||
|
||||
while (YES) {
|
||||
CSSSelector *selector = [self parseSequenceOfSimpleSelectors:error];
|
||||
if (selector == nil) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = result ? allOf(@[result, selector]) : selector;
|
||||
|
||||
UTF32Char next = _inputStream.nextInputCharacter;
|
||||
|
||||
if (isCombinator(next)) {
|
||||
NSString *combinator = [_inputStream consumeCombinator];
|
||||
|
||||
if ([combinator isEqualToString:@""]) {
|
||||
result = descendantOfElementSelector(result);
|
||||
} else if ([combinator isEqualToString:@">"]) {
|
||||
result = childOfElementSelector(result);
|
||||
} else if ([combinator isEqualToString:@"+"]) {
|
||||
result = adjacentSiblingSelector(result);
|
||||
} else if ([combinator isEqualToString:@"~"]) {
|
||||
result = generalSiblingSelector(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseSequenceOfSimpleSelectors:(NSError * __autoreleasing *)error
|
||||
{
|
||||
NSMutableArray *selectors = [NSMutableArray array];
|
||||
|
||||
CSSSelector *typeSelector = [self parseTypeSelector:error];
|
||||
if (typeSelector != nil) {
|
||||
[selectors addObject:typeSelector];
|
||||
}
|
||||
|
||||
while (YES) {
|
||||
UTF32Char next = _inputStream.nextInputCharacter;
|
||||
if (next == EOF || isCombinator(next)) {
|
||||
break;
|
||||
}
|
||||
|
||||
CSSSelector *simpleSelector = [self parseSimpleSelector:error];
|
||||
if (simpleSelector == nil) {
|
||||
return nil;
|
||||
}
|
||||
[selectors addObject:simpleSelector];
|
||||
}
|
||||
|
||||
if (selectors.count > 1) {
|
||||
return allOf(selectors);
|
||||
}
|
||||
|
||||
return selectors.firstObject;
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseTypeSelector:(NSError * __autoreleasing *)error
|
||||
{
|
||||
NSString *identifier = [_inputStream consumeIdentifier];
|
||||
if (identifier != nil) {
|
||||
return typeSelector(identifier);
|
||||
}
|
||||
|
||||
if ([_inputStream consumeCharacter:ASTERIX]) {
|
||||
return universalSelector();
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseSimpleSelector:(NSError * __autoreleasing *)error
|
||||
{
|
||||
CSSSelector *typeSelector = [self parseTypeSelector:error];
|
||||
if (typeSelector != nil) {
|
||||
return typeSelector;
|
||||
}
|
||||
|
||||
UTF32Char codePoint = [_inputStream consumeNextInputCharacter];
|
||||
switch (codePoint) {
|
||||
case NUMBER_SIGN:
|
||||
{
|
||||
NSString *elementId = [_inputStream consumeIdentifier];
|
||||
if (elementId == nil) {
|
||||
[self emitError:error reason:@"Invalid character"];
|
||||
return nil;
|
||||
}
|
||||
return idSelector(elementId);
|
||||
}
|
||||
case FULL_STOP:
|
||||
{
|
||||
NSString *className = [_inputStream consumeIdentifier];
|
||||
if (className == nil) {
|
||||
[self emitError:error reason:@"Invalid character"];
|
||||
return nil;
|
||||
}
|
||||
return classSelector(className);
|
||||
}
|
||||
case LEFT_SQUARE_BRACKET:
|
||||
{
|
||||
return [self parseAttributeSelector:error];
|
||||
}
|
||||
case COLON:
|
||||
{
|
||||
return [self parsePseudoSelector:error];
|
||||
}
|
||||
default:
|
||||
{
|
||||
[self emitError:error reason:@"Invalid character"];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseAttributeSelector:(NSError * __autoreleasing *)error
|
||||
{
|
||||
NSString *attribute = [_inputStream consumeIdentifier];
|
||||
if (attribute == nil) {
|
||||
[self emitError:error reason:@"Invalid character" location:_location + _inputStream.currentLocation + 1];
|
||||
return nil;
|
||||
}
|
||||
[_inputStream consumeWhitespace];
|
||||
|
||||
CSSAttributeSelectorType type = CSSAttributeSelectorExists;
|
||||
|
||||
NSString *operator = [_inputStream consumeCharactersInString:@"=~|^$*!"];
|
||||
|
||||
if ([operator isEqualToString:@"="]) {
|
||||
type = CSSAttributeSelectorExactMatch;
|
||||
} else if ([operator isEqualToString:@"~="]) {
|
||||
type = CSSAttributeSelectorIncludes;
|
||||
} else if ([operator isEqualToString:@"|="]) {
|
||||
type = CSSAttributeSelectorHyphen;
|
||||
} else if ([operator isEqualToString:@"^="]) {
|
||||
type = CSSAttributeSelectorBegins;
|
||||
} else if ([operator isEqualToString:@"$="]) {
|
||||
type = CSSAttributeSelectorEnds;
|
||||
} else if ([operator isEqualToString:@"*="]) {
|
||||
type = CSSAttributeSelectorContains;
|
||||
} else if ([operator isEqualToString:@"!="]) {
|
||||
type = CSSAttributeSelectorNot;
|
||||
}
|
||||
|
||||
NSString *value = nil;
|
||||
[_inputStream consumeWhitespace];
|
||||
|
||||
UTF32Char next = _inputStream.nextInputCharacter;
|
||||
if (isQuote(next)) {
|
||||
UTF32Char quote = [_inputStream consumeNextInputCharacter];
|
||||
value = [_inputStream consumeStringWithEndingCodePoint:quote];
|
||||
} else {
|
||||
value = [_inputStream consumeIdentifier];
|
||||
}
|
||||
|
||||
[_inputStream consumeWhitespace];
|
||||
|
||||
// Consume RIGHT_SQUARE_BRACKET
|
||||
if (![_inputStream consumeCharacter:RIGHT_SQUARE_BRACKET]) {
|
||||
[self emitError:error reason:@"Expected closing right square bracket ']'"];
|
||||
}
|
||||
|
||||
if (type == CSSAttributeSelectorExists) {
|
||||
return hasAttributeSelector(attribute);
|
||||
}
|
||||
|
||||
return attributeSelector(type, attribute, value);
|
||||
}
|
||||
|
||||
- (CSSSelector *)parsePseudoSelector:(NSError * __autoreleasing *)error
|
||||
{
|
||||
NSString *pseudoClass = [_inputStream consumeIdentifier];
|
||||
|
||||
if ([pseudoClass hasPrefix:@"nth"]) {
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected opening left parenthesis '('"];
|
||||
}
|
||||
|
||||
NSString *functionExpression = [_inputStream consumeCharactersUpToString:@")"];
|
||||
CSSNthExpression expression = [CSSNthExpressionParser parseExpression:functionExpression];
|
||||
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
|
||||
}
|
||||
|
||||
if ([pseudoClass isEqualToString:@"nth-child"]) {
|
||||
return nthChildSelector(expression);
|
||||
} else if ([pseudoClass isEqualToString:@"nth-last-child"]) {
|
||||
return nthLastChildSelector(expression);
|
||||
} else if ([pseudoClass isEqualToString:@"nth-of-type"]) {
|
||||
return nthOfTypeSelector(expression);
|
||||
} else if ([pseudoClass isEqualToString:@"nth-last-of-type"]) {
|
||||
return nthLastOfTypeSelector(expression);
|
||||
}
|
||||
} else if ([pseudoClass isEqualToString:@"not"]) {
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected opening left parenthesis '('"];
|
||||
}
|
||||
|
||||
CSSSelector *subSelector = [self parseSimpleSelector:error];
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
|
||||
}
|
||||
|
||||
return not(subSelector);
|
||||
} else if ([pseudoClass isEqualToAny:@"lt", @"gt", @"eq", nil]) {
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected opening left parenthesis '('"];
|
||||
}
|
||||
|
||||
NSDecimal decimal;
|
||||
if (![_inputStream consumeDecimalNumber:&decimal]) {
|
||||
[self emitError:error reason:@"Expected a decimal number"];
|
||||
}
|
||||
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
|
||||
}
|
||||
|
||||
NSDecimalNumber *number = [[NSDecimalNumber alloc] initWithDecimal:decimal];
|
||||
if ([pseudoClass isEqualToString:@"lt"]) {
|
||||
return ltSelector(number.integerValue);
|
||||
} else if ([pseudoClass isEqualToString:@"gt"]) {
|
||||
return gtSelector(number.integerValue);
|
||||
} else if ([pseudoClass isEqualToString:@"eq"]) {
|
||||
return eqSelector(number.integerValue);
|
||||
}
|
||||
} else {
|
||||
if ([pseudoClass isEqualToString:@"even"]) {
|
||||
return evenSlector();
|
||||
} else if ([pseudoClass isEqualToString:@"odd"]) {
|
||||
return oddSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"first-child"]) {
|
||||
return firstChildSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"last-child"]) {
|
||||
return lastChildSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"first-of-type"]) {
|
||||
return firstOfTypeSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"last-of-type"]) {
|
||||
return lastOfTypeSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"only-child"]) {
|
||||
return onlyChildSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"only-of-type"]) {
|
||||
return onlyOfTypeSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"root"]) {
|
||||
return rootSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"empty"]) {
|
||||
return emptySelector();
|
||||
} else if ([pseudoClass isEqualToString:@"link"]) {
|
||||
return linkSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"enabled"]) {
|
||||
return enabledSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"disabled"]) {
|
||||
return disabledSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"checked"]) {
|
||||
return checkedSelector();
|
||||
}
|
||||
|
||||
else if ([pseudoClass isEqualToString:@"button"]) {
|
||||
return buttonSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"checkbox"]) {
|
||||
return checkboxSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"file"]) {
|
||||
return fileSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"header"]) {
|
||||
return headerSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"image"]) {
|
||||
return imageSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"optional"]) {
|
||||
return optionalSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"parent"]) {
|
||||
return parentSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"password"]) {
|
||||
return passwordSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"radio"]) {
|
||||
return radioSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"reset"]) {
|
||||
return resetSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"submit"]) {
|
||||
return submitSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"text"]) {
|
||||
return textSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"required"]) {
|
||||
return requiredSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"reset"]) {
|
||||
return resetSelector();
|
||||
}
|
||||
}
|
||||
NSString *reason = [NSString stringWithFormat:@"Unknown pseudo class: %@", pseudoClass];
|
||||
[self emitError:error reason:reason];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// CSSSelectors.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 14/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelector.h"
|
||||
#import "CSSTypeSelector.h"
|
||||
#import "CSSAttributeSelector.h"
|
||||
#import "CSSPseudoClassSelector.h"
|
||||
#import "CSSPseudoFunctionSelector.h"
|
||||
#import "CSSNthExpressionSelector.h"
|
||||
#import "CSSCombinatorSelector.h"
|
||||
#import "CSSCompoundSelector.h"
|
||||
#import "CSSSelectorBlock.h"
|
||||
#import "CSSStructuralPseudoSelectors.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Type Selectors
|
||||
|
||||
/**
|
||||
Universal CSS selector: '*'
|
||||
|
||||
@returns The universal CSS selector.
|
||||
*/
|
||||
extern CSSSelector * universalSelector();
|
||||
|
||||
/**
|
||||
CSS type selector, e.g. 'div', 'p', ...etc.
|
||||
|
||||
@param type The element type.
|
||||
@returns Type selector for the specified type.
|
||||
*/
|
||||
extern CSSSelector * typeSelector(NSString *type);
|
||||
|
||||
#pragma mark - Atribute Selectors
|
||||
|
||||
/**
|
||||
CSS id selector, e.g. '#someId'
|
||||
|
||||
@param elementId The element id.
|
||||
@returns Id selector for the specified element id.
|
||||
*/
|
||||
extern CSSSelector * idSelector(NSString *elementId);
|
||||
|
||||
/**
|
||||
CSS class selector, e.g. '.someClass'
|
||||
|
||||
@param className The class name.
|
||||
@returns Class selector for the specified class name.
|
||||
*/
|
||||
extern CSSSelector * classSelector(NSString *className);
|
||||
|
||||
/**
|
||||
CSS has-attribute selector, e.g. '[href]'
|
||||
|
||||
@param attribute The attribute.
|
||||
@returns Has-Attribute selector for the specified attribute.
|
||||
*/
|
||||
extern CSSSelector * hasAttributeSelector(NSString *attribute);
|
||||
|
||||
/**
|
||||
CSS attribute selector, e.g. '[src*="html"]', '[class^="top"]', '[title&="HTML"]', ...etc.
|
||||
|
||||
@param type The attribute selector type.
|
||||
@param attribute The attribute.
|
||||
@param value The value of the attribute.
|
||||
@returns Attribute selector.
|
||||
|
||||
@see CSSAttributeSelectorType
|
||||
*/
|
||||
extern CSSSelector * attributeSelector(CSSAttributeSelectorType type,
|
||||
NSString *attribute,
|
||||
NSString *value);
|
||||
|
||||
#pragma mark - Nth-Expression Selectors
|
||||
|
||||
/**
|
||||
CSS nth-child selector, e.g. ':nth-child(2n+3)'
|
||||
|
||||
@param expression The nth-expression.
|
||||
@returns Nth-Child selector for the specified expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
extern CSSSelector * nthChildSelector(CSSNthExpression expression);
|
||||
|
||||
/**
|
||||
CSS nth-last-child selector, e.g. ':nth-last-child(2n+3)'
|
||||
|
||||
@param expression The nth-expression.
|
||||
@returns Nth-Last-Child selector for the specified expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
extern CSSSelector * nthLastChildSelector(CSSNthExpression expression);
|
||||
|
||||
/**
|
||||
CSS nth-of-type selector, e.g. ':nth-of-type(2n+3)'
|
||||
|
||||
@param expression The nth-expression.
|
||||
@returns Nth-Of-Type selector for the specified expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
extern CSSSelector * nthOfTypeSelector(CSSNthExpression expression);
|
||||
|
||||
/**
|
||||
CSS nth-last-of-type selector, e.g. ':nth-last-of-type(2n+3)'
|
||||
|
||||
@param expression The nth-expression.
|
||||
@returns Nth-Last-Of-Type selector for the specified expression.
|
||||
|
||||
@see CSSNthExpression
|
||||
*/
|
||||
extern CSSSelector * nthLastOfTypeSelector(CSSNthExpression expression);
|
||||
|
||||
/**
|
||||
CSS odd-child selector: ':nth-child(odd)'
|
||||
|
||||
This is analogous to ':nth-child(2n+1)'
|
||||
|
||||
@returns Odd-Child selector.
|
||||
*/
|
||||
extern CSSSelector * oddSelector();
|
||||
|
||||
/**
|
||||
CSS even-child selector: ':nth-child(even)'
|
||||
|
||||
This is analogous to ':nth-child(2n)'
|
||||
|
||||
@returns Even-Child selector.
|
||||
*/
|
||||
extern CSSSelector * evenSlector();
|
||||
|
||||
/**
|
||||
CSS first-child selector: ':nth-child(1)'
|
||||
|
||||
@returns First-Child selector.
|
||||
*/
|
||||
extern CSSSelector * firstChildSelector();
|
||||
|
||||
/**
|
||||
CSS first-child selector: ':nth-last-child(1)'
|
||||
|
||||
@returns First-Child selector.
|
||||
*/
|
||||
extern CSSSelector * lastChildSelector();
|
||||
|
||||
/**
|
||||
CSS first-of-type selector: ':nth-first-of-type(1)'
|
||||
|
||||
@returns First-Of-Type selector.
|
||||
*/
|
||||
extern CSSSelector * firstOfTypeSelector();
|
||||
|
||||
/**
|
||||
CSS last-of-type selector: ':nth-last-of-type(1)'
|
||||
|
||||
@returns Last-Of-Type selector.
|
||||
*/
|
||||
extern CSSSelector * lastOfTypeSelector();
|
||||
|
||||
/**
|
||||
CSS only-child selector: ':first-child:last-child'
|
||||
|
||||
@returns Only-Child selector.
|
||||
*/
|
||||
extern CSSSelector * onlyChildSelector();
|
||||
|
||||
/**
|
||||
CSS only-of-type selector: ':first-of-type:last-of-type'
|
||||
|
||||
@returns Only-Of-Type selector.
|
||||
*/
|
||||
extern CSSSelector * onlyOfTypeSelector();
|
||||
|
||||
#pragma mark - Combinators
|
||||
|
||||
/**
|
||||
CSS child-of-element selector, e.g. 'div > p'
|
||||
|
||||
@param selector The selector matching the parent element.
|
||||
@returns A child of element selector.
|
||||
*/
|
||||
extern CSSSelector * childOfElementSelector(CSSSelector *selector);
|
||||
|
||||
/**
|
||||
CSS descendant-of-element selector, e.g. 'div p'
|
||||
|
||||
@param selector The selector matching the ancestor element.
|
||||
@returns A descendant of element selector.
|
||||
*/
|
||||
extern CSSSelector * descendantOfElementSelector(CSSSelector *selector);
|
||||
|
||||
/**
|
||||
CSS adjacent sibling selector, e.g. 'p + a'
|
||||
|
||||
@param selector The selector matching the adjacent sibling element.
|
||||
@returns A adjacent sibling selector.
|
||||
*/
|
||||
extern CSSSelector * adjacentSiblingSelector(CSSSelector *selector);
|
||||
|
||||
/**
|
||||
CSS general sibling selector, e.g. 'p ~ a'
|
||||
|
||||
@param selector The selector matching the general sibling element.
|
||||
@returns A general sibling selector.
|
||||
*/
|
||||
extern CSSSelector * generalSiblingSelector(CSSSelector *selector);
|
||||
|
||||
#pragma mark - Pseudo Functions
|
||||
|
||||
/**
|
||||
CSS nagation selector: ':not(div)'
|
||||
|
||||
@param selector The selector which should be negated.
|
||||
@returns A negation selector.
|
||||
*/
|
||||
extern CSSSelector * not(CSSSelector *selector);
|
||||
|
||||
/**
|
||||
CSS has-descendant selector, e.g. 'div:has(p)'
|
||||
|
||||
@discussion 'div:has(p)' matches all <div> elements which have a descendant <p> element.
|
||||
|
||||
@param selector The selector matching a descendant element.
|
||||
@returns A has-descendant selector.
|
||||
*/
|
||||
extern CSSSelector * has(CSSSelector *selector);
|
||||
|
||||
#pragma mark - Compound Selectors
|
||||
|
||||
/**
|
||||
A compound selector matching only elements that match all of the specified selectors.
|
||||
|
||||
@param selectors The selectors list.
|
||||
@returns All-Of selector.
|
||||
*/
|
||||
extern CSSSelector * allOf(NSArray<CSSSelector *> *selectors);
|
||||
|
||||
/**
|
||||
A compound selector matching all elements that match at least one of the specified selectors.
|
||||
|
||||
@param selectors The selectors list.
|
||||
@returns Any-Of selector.
|
||||
*/
|
||||
extern CSSSelector * anyOf(NSArray<CSSSelector *> *selectors);
|
||||
|
||||
#pragma mark - Pseudo
|
||||
|
||||
/**
|
||||
Creates a new named-pseudo selector.
|
||||
|
||||
@discussion The name specified when creating a selector is prefixed with colon.
|
||||
|
||||
@param name The name of the selector.
|
||||
@param selector The underlying selector.
|
||||
@returns A named-pseudo selector.
|
||||
*/
|
||||
extern CSSSelector * namedPseudoSelector(NSString *name, CSSSelector *selector);
|
||||
|
||||
#pragma mark - Block
|
||||
|
||||
/**
|
||||
Creates a new named selector with a specified block.
|
||||
|
||||
@param name The name of the selector.
|
||||
@param acceptBlock The block which provides the implementation for the accept-element logic.
|
||||
@returns A named-block selector.
|
||||
*/
|
||||
extern CSSSelector * namedBlockSelector(NSString *name, BOOL (^ acceptBlock)(HTMLElement *element));
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// CSSSelectors.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 19/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelectors.h"
|
||||
#import "CSSTypeSelector.h"
|
||||
#import "CSSAttributeSelector.h"
|
||||
#import "CSSPseudoClassSelector.h"
|
||||
#import "CSSPseudoFunctionSelector.h"
|
||||
#import "CSSNthExpressionSelector.h"
|
||||
#import "CSSCombinatorSelector.h"
|
||||
#import "CSSCompoundSelector.h"
|
||||
#import "CSSSelectorBlock.h"
|
||||
|
||||
#pragma mark - Type Selectors
|
||||
|
||||
CSSSelector * universalSelector()
|
||||
{
|
||||
return [CSSTypeSelector universalSelector];
|
||||
}
|
||||
|
||||
CSSSelector * typeSelector(NSString *type)
|
||||
{
|
||||
return [[CSSTypeSelector alloc] initWithType:type];
|
||||
}
|
||||
|
||||
#pragma mark - Atribute Selectors
|
||||
|
||||
CSSSelector * idSelector(NSString *elementId)
|
||||
{
|
||||
return [CSSAttributeSelector idSelector:elementId];
|
||||
}
|
||||
|
||||
CSSSelector * classSelector(NSString *className)
|
||||
{
|
||||
return [CSSAttributeSelector classSelector:className];
|
||||
}
|
||||
|
||||
CSSSelector * hasAttributeSelector(NSString *attribute)
|
||||
{
|
||||
return [CSSAttributeSelector hasAttributeSelector:attribute];
|
||||
}
|
||||
|
||||
CSSSelector * attributeSelector(CSSAttributeSelectorType type,
|
||||
NSString *attribute,
|
||||
NSString *value)
|
||||
{
|
||||
return [[CSSAttributeSelector alloc] initWithType:type attributeName:attribute attrbiuteValue:value];
|
||||
}
|
||||
|
||||
#pragma mark - Nth-Expression Selectors
|
||||
|
||||
CSSSelector * nthChildSelector(CSSNthExpression expression)
|
||||
{
|
||||
return [CSSNthExpressionSelector nthChildSelector:expression];
|
||||
}
|
||||
|
||||
CSSSelector * nthLastChildSelector(CSSNthExpression expression)
|
||||
{
|
||||
return [CSSNthExpressionSelector nthLastChildSelector:expression];
|
||||
}
|
||||
|
||||
CSSSelector * nthOfTypeSelector(CSSNthExpression expression)
|
||||
{
|
||||
return [CSSNthExpressionSelector nthOfTypeSelector:expression];
|
||||
}
|
||||
|
||||
CSSSelector * nthLastOfTypeSelector(CSSNthExpression expression)
|
||||
{
|
||||
return [CSSNthExpressionSelector nthLastOfTypeSelector:expression];
|
||||
}
|
||||
|
||||
#pragma mark - Nth-Expression Shorthand
|
||||
|
||||
CSSSelector * oddSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"odd", nthChildSelector(CSSNthExpressionOdd));
|
||||
}
|
||||
|
||||
CSSSelector * evenSlector()
|
||||
{
|
||||
return namedPseudoSelector(@"even", nthChildSelector(CSSNthExpressionEven));
|
||||
}
|
||||
|
||||
CSSSelector * firstChildSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"first-child", nthChildSelector(CSSNthExpressionMake(0, 1)));
|
||||
}
|
||||
|
||||
CSSSelector * lastChildSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"last-child", nthLastChildSelector(CSSNthExpressionMake(0, 1)));
|
||||
}
|
||||
|
||||
CSSSelector * firstOfTypeSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"first-of-type", nthOfTypeSelector(CSSNthExpressionMake(0, 1)));
|
||||
}
|
||||
|
||||
CSSSelector * lastOfTypeSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"last-of-type", nthLastOfTypeSelector(CSSNthExpressionMake(0, 1)));
|
||||
}
|
||||
|
||||
CSSSelector * onlyChildSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"only-child", allOf(@[firstChildSelector(), lastChildSelector()]));
|
||||
}
|
||||
|
||||
CSSSelector * onlyOfTypeSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"only-of-type", allOf(@[firstOfTypeSelector(), lastOfTypeSelector()]));
|
||||
}
|
||||
|
||||
#pragma mark - Combinators
|
||||
|
||||
CSSSelector * childOfElementSelector(CSSSelector *selector)
|
||||
{
|
||||
return [CSSCombinatorSelector childOfElementCombinator:selector];
|
||||
}
|
||||
|
||||
CSSSelector * descendantOfElementSelector(CSSSelector *selector)
|
||||
{
|
||||
return [CSSCombinatorSelector descendantOfElementCombinator:selector];
|
||||
}
|
||||
|
||||
CSSSelector * adjacentSiblingSelector(CSSSelector *selector)
|
||||
{
|
||||
return [CSSCombinatorSelector adjacentSiblingCombinator:selector];
|
||||
}
|
||||
|
||||
CSSSelector * generalSiblingSelector(CSSSelector *selector)
|
||||
{
|
||||
return [CSSCombinatorSelector generalSiblingCombinator:selector];
|
||||
}
|
||||
|
||||
#pragma mark - Pseudo Functions
|
||||
|
||||
CSSSelector * not(CSSSelector *selector)
|
||||
{
|
||||
return [CSSPseudoFunctionSelector notSelector:selector];
|
||||
}
|
||||
|
||||
CSSSelector * has(CSSSelector *selector)
|
||||
{
|
||||
return [CSSPseudoFunctionSelector hasSelector:selector];
|
||||
}
|
||||
|
||||
#pragma mark - Compound Selectors
|
||||
|
||||
CSSSelector * allOf( NSArray<CSSSelector *> * selectors)
|
||||
{
|
||||
return [CSSCompoundSelector andSelector:selectors];
|
||||
}
|
||||
|
||||
CSSSelector * anyOf( NSArray<CSSSelector *> * selectors)
|
||||
{
|
||||
return [CSSCompoundSelector orSelector:selectors];
|
||||
}
|
||||
|
||||
#pragma mark - Pseudo
|
||||
|
||||
CSSSelector * namedPseudoSelector(NSString *name, CSSSelector *selector)
|
||||
{
|
||||
return [[CSSPseudoClassSelector alloc] initWithClassName:name selector:selector];
|
||||
}
|
||||
|
||||
#pragma mark - Block
|
||||
|
||||
CSSSelector * namedBlockSelector(NSString *name, BOOL (^ acceptBlock)(HTMLElement *element))
|
||||
{
|
||||
return [[CSSSelectorBlock alloc] initWithName:name block:acceptBlock];
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// CSSStructuralPseudoSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 11/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
@class CSSSelector;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
@returns Root element selector: ':root'
|
||||
*/
|
||||
extern CSSSelector * rootSelector();
|
||||
|
||||
/**
|
||||
@returns Empy element selector: ':empty'
|
||||
*/
|
||||
extern CSSSelector * emptySelector();
|
||||
|
||||
/**
|
||||
@returns A parent element selector: ':parent'
|
||||
*/
|
||||
extern CSSSelector * parentSelector();
|
||||
|
||||
/**
|
||||
@returns A button element selector: ':button'
|
||||
*/
|
||||
extern CSSSelector * buttonSelector();
|
||||
|
||||
/**
|
||||
@returns A checkbox element selector: ':checkbox'
|
||||
*/
|
||||
extern CSSSelector * checkboxSelector();
|
||||
|
||||
/**
|
||||
@returns A file element selector: ':file'
|
||||
*/
|
||||
extern CSSSelector * fileSelector();
|
||||
|
||||
/**
|
||||
@returns A header element selector: ':header'
|
||||
*/
|
||||
extern CSSSelector * headerSelector();
|
||||
|
||||
/**
|
||||
@returns An image element selector: ':image'
|
||||
*/
|
||||
extern CSSSelector * imageSelector();
|
||||
|
||||
/**
|
||||
@returns A parent element selector: ':parent'
|
||||
*/
|
||||
extern CSSSelector * inputSelector();
|
||||
|
||||
/**
|
||||
@returns A link element selector: ':link'
|
||||
*/
|
||||
extern CSSSelector * linkSelector();
|
||||
|
||||
/**
|
||||
@returns A password element selector: ':password'
|
||||
*/
|
||||
extern CSSSelector * passwordSelector();
|
||||
|
||||
/**
|
||||
@returns A radio element selector: ':radio'
|
||||
*/
|
||||
extern CSSSelector * radioSelector();
|
||||
|
||||
/**
|
||||
@returns A reset element selector: ':reset'
|
||||
*/
|
||||
extern CSSSelector * resetSelector();
|
||||
|
||||
/**
|
||||
@returns A submit element selector: ':submit'
|
||||
*/
|
||||
extern CSSSelector * submitSelector();
|
||||
|
||||
/**
|
||||
@returns A text element selector: ':text'
|
||||
*/
|
||||
extern CSSSelector * textSelector();
|
||||
|
||||
/**
|
||||
@returns An enabled element selector: ':enabled'
|
||||
*/
|
||||
extern CSSSelector * enabledSelector();
|
||||
|
||||
/**
|
||||
@returns A disabled element selector: ':disabled'
|
||||
*/
|
||||
extern CSSSelector * disabledSelector();
|
||||
|
||||
/**
|
||||
@returns A checked element selector: ':checked'
|
||||
*/
|
||||
extern CSSSelector * checkedSelector();
|
||||
|
||||
/**
|
||||
@returns An optional element selector: ':optional'
|
||||
*/
|
||||
extern CSSSelector * optionalSelector();
|
||||
|
||||
/**
|
||||
@returns A required element selector: ':required'
|
||||
*/
|
||||
extern CSSSelector * requiredSelector();
|
||||
|
||||
/**
|
||||
Less-than selector, e.g. 'lt(2)'
|
||||
|
||||
Selects all elements at an index less than the specified index. A negative index counts backwards from the last element.
|
||||
|
||||
@param index The zero-based index of the element to match.
|
||||
@returns A Less-Than selector.
|
||||
*/
|
||||
extern CSSSelector * ltSelector(NSInteger index);
|
||||
|
||||
/**
|
||||
Greater-than selector, e.g. 'gt(2)'
|
||||
|
||||
Selects all elements at an index greater than the specified index. A negative index counts backwards from the
|
||||
last element.
|
||||
|
||||
@param index The zero-based index of the element to match.
|
||||
@returns A Greater-Than selector.
|
||||
*/
|
||||
extern CSSSelector * gtSelector(NSInteger index);
|
||||
|
||||
/**
|
||||
Equal selector, e.g. 'eq(3)'
|
||||
|
||||
Selects the element at the specified index. A negative index counts backwards from the last element.
|
||||
|
||||
@param index The zero-based index of the element to match.
|
||||
@returns An Equal selector.
|
||||
*/
|
||||
extern CSSSelector * eqSelector(NSInteger index);
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,313 @@
|
||||
//
|
||||
// CSSStructuralPseudoSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 11/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSStructuralPseudoSelectors.h"
|
||||
#import "CSSSelectors.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
#pragma mark - Elements
|
||||
|
||||
CSSSelector * rootSelector()
|
||||
{
|
||||
return namedBlockSelector(@":root", ^BOOL(HTMLElement * element) {
|
||||
return element.parentElement == nil;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * emptySelector()
|
||||
{
|
||||
return namedBlockSelector(@":empty", ^BOOL(HTMLElement * element) {
|
||||
for (HTMLNode *child in element.childNodes) {
|
||||
if (child.nodeType == HTMLNodeElement) {
|
||||
return NO;
|
||||
} else if (child.nodeType == HTMLNodeText && child.textContent.length > 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * parentSelector()
|
||||
{
|
||||
return namedBlockSelector(@":parent", ^BOOL(HTMLElement * element) {
|
||||
return element.childNodesCount > 0;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * buttonSelector()
|
||||
{
|
||||
return namedBlockSelector(@":button", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element.tagName isEqualToString:@"button"]) {
|
||||
return YES;
|
||||
}
|
||||
if ([element.tagName isEqualToString:@"input"] && [element[@"type"] isEqualToString:@"button"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * checkboxSelector()
|
||||
{
|
||||
return namedBlockSelector(@":checkbox", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"checkbox"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * fileSelector()
|
||||
{
|
||||
return namedBlockSelector(@":file", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"file"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * headerSelector()
|
||||
{
|
||||
return namedBlockSelector(@":header", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * imageSelector()
|
||||
{
|
||||
return namedBlockSelector(@":image", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"image"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * inputSelector()
|
||||
{
|
||||
return namedBlockSelector(@":input", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element.tagName isEqualToAny:@"button", @"input", @"select", @"textarea", nil]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * linkSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-link
|
||||
return namedBlockSelector(@":link", ^BOOL(HTMLElement * element) {
|
||||
if ([element hasAttribute:@"href"]) {
|
||||
return [element.tagName isEqualToAny:@"a", @"area", @"link", nil];
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * passwordSelector()
|
||||
{
|
||||
return namedBlockSelector(@":password", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"password"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * radioSelector()
|
||||
{
|
||||
return namedBlockSelector(@":radio", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"radio"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * resetSelector()
|
||||
{
|
||||
return namedBlockSelector(@":reset", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"reset"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * submitSelector()
|
||||
{
|
||||
return namedBlockSelector(@":submit", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element.tagName isEqualToString:@"input"] && [element[@"type"] isEqualToString:@"submit"]) {
|
||||
return YES;
|
||||
}
|
||||
if ([element.tagName isEqualToString:@"button"] && [element[@"type"] isEqualToString:@"submit"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * textSelector()
|
||||
{
|
||||
return namedBlockSelector(@":text", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"text"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - State
|
||||
|
||||
CSSSelector * enabledSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
|
||||
CSSSelector *candiate = anyOf(@[
|
||||
typeSelector(@"button"),
|
||||
typeSelector(@"input"),
|
||||
typeSelector(@"select"),
|
||||
typeSelector(@"textarea"),
|
||||
typeSelector(@"optgroup"),
|
||||
typeSelector(@"option"),
|
||||
typeSelector(@"menuitem"),
|
||||
typeSelector(@"fieldset"),
|
||||
]);
|
||||
return namedPseudoSelector(@"enabled", allOf(@[candiate, not(disabledSelector())]));
|
||||
}
|
||||
|
||||
CSSSelector * disabledSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
|
||||
CSSSelector *disabledAttribute = hasAttributeSelector(@"disabled");
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-fieldset-disabled
|
||||
CSSSelector *disabledFieldset = allOf(@[typeSelector(@"fieldset"), disabledAttribute]);
|
||||
CSSSelector *firstLegend = allOf(@[typeSelector(@"legend"), firstOfTypeSelector()]);
|
||||
CSSSelector *firstLegendDecendantDisabledFieldSet = allOf(@[firstLegend, descendantOfElementSelector(disabledFieldset)]);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
|
||||
CSSSelector *disabledForm = anyOf(@[
|
||||
anyOf(@[
|
||||
allOf(@[typeSelector(@"button"), disabledAttribute]),
|
||||
allOf(@[typeSelector(@"input"), disabledAttribute]),
|
||||
allOf(@[typeSelector(@"select"), disabledAttribute]),
|
||||
allOf(@[typeSelector(@"textarea"), disabledAttribute])
|
||||
]),
|
||||
allOf(@[
|
||||
descendantOfElementSelector(disabledFieldset),
|
||||
not(firstLegendDecendantDisabledFieldSet)
|
||||
])
|
||||
]);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
|
||||
CSSSelector *disabledMenuItem = allOf(@[typeSelector(@"menuitem"), disabledAttribute]);
|
||||
CSSSelector *disabledOptgroup = allOf(@[typeSelector(@"optgroup"), disabledAttribute]);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
|
||||
CSSSelector *disabledOption = allOf(@[
|
||||
typeSelector(@"option"),
|
||||
anyOf(@[
|
||||
disabledAttribute,
|
||||
descendantOfElementSelector(disabledOptgroup)])
|
||||
]);
|
||||
return namedPseudoSelector(@"disabled",
|
||||
anyOf(@[disabledOption, disabledOptgroup, disabledMenuItem, disabledForm, disabledFieldset]));
|
||||
}
|
||||
|
||||
CSSSelector * checkedSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-checked
|
||||
CSSSelector *candidate = anyOf(@[
|
||||
typeSelector(@"input"),
|
||||
typeSelector(@"option"),
|
||||
typeSelector(@"menutitem")
|
||||
]);
|
||||
CSSSelector *hasAttribute = anyOf(@[
|
||||
hasAttributeSelector(@"checked"),
|
||||
hasAttributeSelector(@"selected")
|
||||
]);
|
||||
|
||||
return namedPseudoSelector(@"checked", allOf(@[candidate, hasAttribute]));
|
||||
}
|
||||
|
||||
CSSSelector * optionalSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-optional
|
||||
CSSSelector *candidate = anyOf(@[
|
||||
typeSelector(@"input"),
|
||||
typeSelector(@"select"),
|
||||
typeSelector(@"textarea")
|
||||
]);
|
||||
CSSSelector *noAttribute = not(hasAttributeSelector(@"required"));
|
||||
|
||||
return namedPseudoSelector(@"optional", allOf(@[candidate, noAttribute]));
|
||||
}
|
||||
|
||||
CSSSelector * requiredSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-required
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-input-required
|
||||
CSSSelector *candidate = anyOf(@[
|
||||
typeSelector(@"input"),
|
||||
typeSelector(@"select"),
|
||||
typeSelector(@"textarea")
|
||||
]);
|
||||
CSSSelector *hasAttribute = hasAttributeSelector(@"required");
|
||||
|
||||
return namedPseudoSelector(@"required", allOf(@[candidate, hasAttribute]));
|
||||
}
|
||||
|
||||
#pragma mark - Positional
|
||||
|
||||
CSSSelector * ltSelector(NSInteger index)
|
||||
{
|
||||
NSString *name = [NSString stringWithFormat:@":lt(%ld)", (long)index];
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
return elementIndex < index;
|
||||
} else {
|
||||
return elementIndex < element.parentElement.childNodesCount - index - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * gtSelector(NSInteger index)
|
||||
{
|
||||
NSString *name = [NSString stringWithFormat:@":gt(%ld)", (long)index];
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
return elementIndex > index;
|
||||
} else {
|
||||
return elementIndex > element.parentElement.childNodesCount - index - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * eqSelector(NSInteger index)
|
||||
{
|
||||
NSString *name = [NSString stringWithFormat:@":eq(%ld)", (long)index];
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
return elementIndex == index;
|
||||
} else {
|
||||
return elementIndex == element.parentElement.childNodesCount - index - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// CSSTypeSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 13/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CSSSelector.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
CSS Type Selector.
|
||||
*/
|
||||
@interface CSSTypeSelector : CSSSelector
|
||||
|
||||
/**
|
||||
The type of elements being matched.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSString *type;
|
||||
|
||||
/**
|
||||
Returns the universal selector.
|
||||
|
||||
@returns A new instance of a universal selector that matches all elements.
|
||||
*/
|
||||
+ (instancetype)universalSelector;
|
||||
|
||||
/**
|
||||
Initializes a new selector for the specified type.
|
||||
|
||||
@param type The type of elements that should be matched.
|
||||
@returns A new instance of a type selector.
|
||||
*/
|
||||
- (instancetype)initWithType:(NSString *)type;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// CSSTypeSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 13/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSTypeSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
@interface CSSTypeSelector ()
|
||||
{
|
||||
NSString *_type;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSTypeSelector
|
||||
|
||||
+ (instancetype)universalSelector
|
||||
{
|
||||
return [[self alloc] initWithType:@"*"];
|
||||
}
|
||||
|
||||
- (instancetype)initWithType:(NSString *)type
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_type = [type copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
if ([_type isEqualToString:@"*"] || [_type isEqualToStringIgnoringCase:element.tagName]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return self.type;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -6,22 +6,76 @@
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
/**
|
||||
HTML Character Token
|
||||
*/
|
||||
@interface HTMLCharacterToken : HTMLToken
|
||||
|
||||
/** @brief The characters in this token. */
|
||||
@property (nonatomic, copy) NSString *characters;
|
||||
|
||||
/**
|
||||
Initializes a new character token.
|
||||
|
||||
@param string The string with which to initialize the token.
|
||||
@returns A new instance of a character token.
|
||||
*/
|
||||
- (instancetype)initWithString:(NSString *)string;
|
||||
|
||||
/**
|
||||
Appends the given string to this token.
|
||||
|
||||
@param string The string to append.
|
||||
*/
|
||||
- (void)appendString:(NSString *)string;
|
||||
|
||||
/**
|
||||
Checks whether this token is a whitespace character token.
|
||||
|
||||
@discussion HTML whitespace characters are: CHARACTER TABULATION U+0009, LINE FEED U+000A, FORM FEED U+000C,
|
||||
CARRIAGE RETURN U+000D, and SPACE U+0020
|
||||
|
||||
@returns `YES` if this token contains only whitespace characters, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isWhitespaceToken;
|
||||
|
||||
/**
|
||||
Checks whether this token is empty.
|
||||
|
||||
@returns `YES` if this token is empty, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
/**
|
||||
Retains all leading whitespace characters in this token.
|
||||
*/
|
||||
- (void)retainLeadingWhitespace;
|
||||
|
||||
/**
|
||||
Trims all leading whitespace characters in this token.
|
||||
*/
|
||||
- (void)trimLeadingWhitespace;
|
||||
|
||||
/**
|
||||
Trims the characters in this token from a given index
|
||||
|
||||
@param index The start index from which to trim the token.
|
||||
*/
|
||||
- (void)trimFormIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Splits this token retaining only characters after the leading whitespace. The leading whitespace characters are then
|
||||
returned a new characters token.
|
||||
|
||||
@returns A characters token with leading whitespace characters. Returns 'nil` if no leading whitespace exists.
|
||||
*/
|
||||
- (HTMLCharacterToken *)tokenBySplitingLeadingWhiteSpace;
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,10 +8,24 @@
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A HTML Comment node
|
||||
*/
|
||||
@interface HTMLComment : HTMLNode
|
||||
|
||||
/** @brief The comment string. */
|
||||
@property (nonatomic, copy) NSString *data;
|
||||
|
||||
/**
|
||||
Initializes a new HTML comment node.
|
||||
|
||||
@param data The comment string.
|
||||
@returns A new isntance of a HTML comment node.
|
||||
*/
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -6,15 +6,34 @@
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
/**
|
||||
HTML Comment Token
|
||||
*/
|
||||
@interface HTMLCommentToken : HTMLToken
|
||||
|
||||
/** @brief The comment string in this token. */
|
||||
@property (nonatomic, copy) NSString *data;
|
||||
|
||||
/**
|
||||
Initializes a new comment token.
|
||||
|
||||
@param string The string with which to initialize the token.
|
||||
@returns A new instance of a comment token.
|
||||
*/
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
/**
|
||||
Appends the given string to this token.
|
||||
|
||||
@param string The string to append.
|
||||
*/
|
||||
- (void)appendStringToData:(NSString *)string;
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,20 +6,57 @@
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
/**
|
||||
HTML DOCTYPE Token
|
||||
*/
|
||||
@interface HTMLDOCTYPEToken : HTMLToken
|
||||
|
||||
/** @brief The DOCTYPE's name. */
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
|
||||
/** @brief The DOCTYPE's public identifier. */
|
||||
@property (nonatomic, strong) NSMutableString *publicIdentifier;
|
||||
|
||||
/** @brief The DOCTYPE's system identifier. */
|
||||
@property (nonatomic, strong) NSMutableString *systemIdentifier;
|
||||
|
||||
/** @brief Flag whether this DOCTYPE forces quirks mode. */
|
||||
@property (nonatomic, assign) BOOL forceQuirks;
|
||||
|
||||
/**
|
||||
Initializes a new DOCTYPE token.
|
||||
|
||||
@param name The name with which to initialize the token.
|
||||
@returns A new instance of a DOCTYPE token.
|
||||
*/
|
||||
- (instancetype)initWithName:(NSString *)name;
|
||||
|
||||
/**
|
||||
Appends the given string to this DOCTYPE's name.
|
||||
|
||||
@param string The string to append.
|
||||
*/
|
||||
- (void)appendStringToName:(NSString *)string;
|
||||
|
||||
/**
|
||||
Appends the given string to this DOCTYPE's public identifier.
|
||||
|
||||
@param string The string to append.
|
||||
*/
|
||||
- (void)appendStringToPublicIdentifier:(NSString *)string;
|
||||
|
||||
/**
|
||||
Appends the given string to this DOCTYPE's system identifier.
|
||||
|
||||
@param string The string to append.
|
||||
*/
|
||||
- (void)appendStringToSystemIdentifier:(NSString *)string;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// HTMLDOMTokenList.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 30/11/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class HTMLElement;
|
||||
|
||||
/**
|
||||
A HTML DOM Token List.
|
||||
|
||||
The DOM Token List is used for manipulating an element's attributes that contain muliplte values separated by a space.
|
||||
|
||||
https://dom.spec.whatwg.org/#interface-domtokenlist
|
||||
*/
|
||||
@interface HTMLDOMTokenList : NSObject
|
||||
|
||||
/** @brief The associated context element. */
|
||||
@property (nonatomic, strong, readonly) HTMLElement *element;
|
||||
|
||||
/** @brief The associated attribute. */
|
||||
@property (nonatomic, strong, readonly) NSString *attribute;
|
||||
|
||||
/**
|
||||
Initializes a new DOM token list.
|
||||
|
||||
@param element The associated context element.
|
||||
@param attribute The associated attribute.
|
||||
@param value The initial attribute's value.
|
||||
@returns A new instance of the DOM token list.
|
||||
*/
|
||||
- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value;
|
||||
|
||||
/**
|
||||
@returns The length of this token list
|
||||
*/
|
||||
- (NSUInteger)length;
|
||||
|
||||
/**
|
||||
Checks whether this list contains the given token.
|
||||
|
||||
@param token The token.
|
||||
@returns `YES` if the given token is in this list, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)contains:(NSString *)token;
|
||||
|
||||
/**
|
||||
Add the given tokens to the list.
|
||||
|
||||
@param tokens The tokens to add.
|
||||
*/
|
||||
- (void)add:(NSArray<NSString *> *)tokens;
|
||||
|
||||
/**
|
||||
Removes the given tokens from the list.
|
||||
|
||||
@param tokens The tokens to remove.
|
||||
*/
|
||||
- (void)remove:(NSArray<NSString *> *)tokens;
|
||||
|
||||
/**
|
||||
Toggles the given token.
|
||||
|
||||
@param token The token to toggle.
|
||||
@returns `YES` if the token was added to the list, `NO` if it was removed from it.
|
||||
*/
|
||||
- (BOOL)toggle:(NSString *)token;
|
||||
|
||||
/**
|
||||
Replaces the given token with new token.
|
||||
|
||||
@param token The token to replace.
|
||||
@param newToken The replacement token.
|
||||
*/
|
||||
- (void)replaceToken:(NSString *)token withToken:(NSString *)newToken;
|
||||
|
||||
/**
|
||||
Returns the value of the token at the given index.
|
||||
|
||||
@param index The index at which to return the token.
|
||||
@returns The token at the given index. If index is greater than or equal to the value returned by count, an
|
||||
NSRangeException is raised.
|
||||
*/
|
||||
- (NSString *)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Set the token at the given index.
|
||||
|
||||
@param obj The token to set.
|
||||
@param index The index at which to set the token. If index is greater than or equal to the value returned by count, an
|
||||
NSRangeException is raised.
|
||||
*/
|
||||
- (void)setObject:(NSString *)obj atIndexedSubscript:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
@returns The string representation of this token list, which can be used as the attribute's value.
|
||||
*/
|
||||
- (NSString *)stringify;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -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
@@ -10,6 +10,12 @@
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLQuirksMode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The document's ready state. The document is `Loading` while being parsed, `Complete` otherwise. The `Interactive` state
|
||||
is not supported.
|
||||
*/
|
||||
typedef NS_ENUM(short, HTMLDocumentReadyState)
|
||||
{
|
||||
HTMLDocumentLoading,
|
||||
@@ -17,28 +23,76 @@ typedef NS_ENUM(short, HTMLDocumentReadyState)
|
||||
HTMLDocumentComplete
|
||||
};
|
||||
|
||||
/**
|
||||
The HTML Document. This is the root of a parsed DOM tree.
|
||||
|
||||
https://html.spec.whatwg.org/multipage/dom.html#documents
|
||||
*/
|
||||
@interface HTMLDocument : HTMLNode
|
||||
|
||||
@property (nonatomic, strong) HTMLDocumentType *documentType;
|
||||
/**
|
||||
The document's DOCTYPE.
|
||||
|
||||
@see HTMLDocumentType
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) HTMLDocumentType *documentType;
|
||||
|
||||
/**
|
||||
The document's quirks mode.
|
||||
|
||||
@see HTMLQuirksMode
|
||||
*/
|
||||
@property (nonatomic, assign) HTMLQuirksMode quirksMode;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *compatMode;
|
||||
|
||||
/**
|
||||
The document's ready state.
|
||||
|
||||
@see HTMLDocumentReadyState
|
||||
*/
|
||||
@property (nonatomic, assign, readonly) HTMLDocumentReadyState readyState;
|
||||
|
||||
@property (nonatomic, strong) HTMLElement *rootElement;
|
||||
/**
|
||||
The document's root element, which is the first element in tree order, if any. Usually it is the <html> element.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) HTMLElement *rootElement;
|
||||
|
||||
@property (nonatomic, strong) HTMLElement *documentElement;
|
||||
/**
|
||||
The document element, i.e. the <html> element, if it exists.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) HTMLElement *documentElement;
|
||||
|
||||
@property (nonatomic, strong) HTMLElement *head;
|
||||
/**
|
||||
The document's <head> element, if it exists.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) HTMLElement *head;
|
||||
|
||||
@property (nonatomic, strong) HTMLElement *body;
|
||||
/**
|
||||
The document's <body> element, if it exists.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) HTMLElement *body;
|
||||
|
||||
/**
|
||||
Retunrs a new HTML Document instance with the given HTML string.
|
||||
|
||||
@param string The HTML string to parse into a document.
|
||||
*/
|
||||
+ (instancetype)documentWithString:(NSString *)string;
|
||||
|
||||
/**
|
||||
Adopts a given node into this document, i.e. the document becomes the new owner of the node. Raises a HTMLKitNotSupportedError
|
||||
exception if node is an instance of HTMLDocument.
|
||||
|
||||
@param node The node to adopt.
|
||||
@returns The adopted node
|
||||
*/
|
||||
- (HTMLNode *)adoptNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Returns the associated HTML Document proxy instance, which owns the template contents of all its template elements.
|
||||
https://html.spec.whatwg.org/multipage/scripting.html#associated-inert-template-document
|
||||
*/
|
||||
- (HTMLDocument *)associatedInertTemplateDocument;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,8 +8,24 @@
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A HTML Document Fragment. Represents a minimal document object that has no parent. It is used as a light-weight
|
||||
version of Document
|
||||
|
||||
https://dom.spec.whatwg.org/#interface-documentfragment
|
||||
*/
|
||||
@interface HTMLDocumentFragment : HTMLNode
|
||||
|
||||
- (instancetype)initWithDocument:(HTMLDocument *)document;
|
||||
/**
|
||||
Initializes a new document fragment with the given document as owner.
|
||||
|
||||
@param document The owner document.
|
||||
@returns A new instance of a document fragment.
|
||||
*/
|
||||
- (instancetype)initWithDocument:(nullable HTMLDocument *)document;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
|
||||
#import "HTMLDocumentFragment.h"
|
||||
#import "HTMLText.h"
|
||||
|
||||
@interface HTMLNode ()
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@end
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
@implementation HTMLDocumentFragment
|
||||
|
||||
@@ -32,7 +29,7 @@
|
||||
- (NSString *)textContent
|
||||
{
|
||||
NSMutableString *content = [NSMutableString string];
|
||||
for (HTMLNode *node in self.treeEnumerator) {
|
||||
for (HTMLNode *node in self.nodeIterator) {
|
||||
if (node.nodeType == HTMLNodeText) {
|
||||
[content appendString:[(HTMLText *)node data]];
|
||||
}
|
||||
|
||||
@@ -9,17 +9,56 @@
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLQuirksMode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A HTML Document Type node. There is only one valid document type, which is `<!DOCTYPE html>`.
|
||||
|
||||
Other DOCTYPES, e.g. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
are obsolete but permitted.
|
||||
|
||||
https://dom.spec.whatwg.org/#interface-documenttype
|
||||
*/
|
||||
@interface HTMLDocumentType : HTMLNode
|
||||
|
||||
/**
|
||||
The public identifier
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSString *publicIdentifier;
|
||||
|
||||
/**
|
||||
The system identifier
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSString *systemIdentifier;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
publicIdentifier:(NSString *)publicIdentifier
|
||||
systemIdentifier:(NSString *)systemIdentifier;
|
||||
/**
|
||||
Initializes and returns a new isntance of a Document Type node.
|
||||
|
||||
@param name The name.
|
||||
@param publicIdentifier The public identifier.
|
||||
@param systemIdentifier The system identigier
|
||||
@returns A new document type instance.
|
||||
*/
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
publicIdentifier:(nullable NSString *)publicIdentifier
|
||||
systemIdentifier:(nullable NSString *)systemIdentifier;
|
||||
|
||||
/**
|
||||
Checks whether this DOCTYPE is valid.
|
||||
|
||||
@returns `YES` if this is a valid DOCTYPE, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isValid;
|
||||
|
||||
/**
|
||||
Return the quirks mode of this DOCTYPE.
|
||||
|
||||
@returns The quirks mode.
|
||||
|
||||
@see HTMLQuirksMode
|
||||
*/
|
||||
- (HTMLQuirksMode)quirksMode;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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
@@ -9,26 +9,115 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNamespaces.h"
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLDOMTokenList.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A HTML Element.
|
||||
|
||||
https://html.spec.whatwg.org/multipage/dom.html#elements
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#elements-2
|
||||
*/
|
||||
@interface HTMLElement : HTMLNode
|
||||
|
||||
/**
|
||||
The namesapce of this element.
|
||||
|
||||
@see HTMLNamespace
|
||||
*/
|
||||
@property (nonatomic, assign, readonly) HTMLNamespace htmlNamespace;
|
||||
|
||||
/**
|
||||
The elemen's tag name.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSString *tagName;
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary *attributes;
|
||||
|
||||
/**
|
||||
The elemen's id attribute value. Empty string if the element has no id attribute.
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *elementId;
|
||||
|
||||
/**
|
||||
The elemen's class attribute value. Empty string if the element has no class attribute.
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *className;
|
||||
|
||||
/**
|
||||
The element's class attribute as a DOM Token List
|
||||
|
||||
@see HTMLDOMTokenList
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) HTMLDOMTokenList *classList;
|
||||
|
||||
/**
|
||||
The element's attribites.
|
||||
*/
|
||||
@property (nonatomic, strong) NSMutableDictionary *attributes;
|
||||
|
||||
/**
|
||||
@warning Use one of the initWithTagName: methods instead.
|
||||
*/
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Initializes a new HTML element with the given tag name.
|
||||
|
||||
@param tagname The tag name.
|
||||
@returns A new HTML element.
|
||||
*/
|
||||
- (instancetype)initWithTagName:(NSString *)tagName;
|
||||
|
||||
/**
|
||||
Initializes a new HTML element with the given tag name and attributes.
|
||||
|
||||
@param tagname The tag name.
|
||||
@param attributes The attributes.
|
||||
@returns A new HTML element.
|
||||
*/
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes;
|
||||
|
||||
/**
|
||||
Initializes a new HTML element with the given tag name, namespace, and attributes.
|
||||
|
||||
@param tagname The tag name.
|
||||
@param namespace The namespace.
|
||||
@param attributes The attributes.
|
||||
@returns A new HTML element.
|
||||
*/
|
||||
- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(NSDictionary *)attributes;
|
||||
|
||||
/**
|
||||
Checks whether this element has an attribute with the given name.
|
||||
|
||||
@param name The attribute name.
|
||||
@returns `YES` if the element has such an attributes, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)hasAttribute:(NSString *)name;
|
||||
- (NSString *)objectForKeyedSubscript:(NSString *)name;
|
||||
|
||||
/**
|
||||
Returns the value of the attribute with the given name.
|
||||
|
||||
@param name The attribute's name.
|
||||
@returns The attribute's value, `nil` if the element doesn't have such attribute.
|
||||
*/
|
||||
- (nullable NSString *)objectForKeyedSubscript:(NSString *)name;
|
||||
|
||||
/**
|
||||
Set the value of the attribute with the given name.
|
||||
|
||||
@param value The value to set.
|
||||
@param name The attribute's name.
|
||||
*/
|
||||
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute;
|
||||
|
||||
/**
|
||||
Removes the attribute with the given name.
|
||||
|
||||
@param name The attribute to remove.
|
||||
*/
|
||||
- (void)removeAttribute:(NSString *)name;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
+28
-5
@@ -10,9 +10,10 @@
|
||||
#import "HTMLParser.h"
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLText.h"
|
||||
|
||||
#import "HTMLDOMTokenList.h"
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
@interface HTMLElement ()
|
||||
{
|
||||
@@ -26,12 +27,12 @@
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithTagName:nil];
|
||||
return [self initWithTagName:@""];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName
|
||||
{
|
||||
return [self initWithTagName:tagName attributes:nil];
|
||||
return [self initWithTagName:tagName attributes:@{}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes
|
||||
@@ -53,18 +54,35 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Attributes
|
||||
#pragma mark - Special Attributes
|
||||
|
||||
- (NSString *)elementId
|
||||
{
|
||||
return _attributes[@"id"] ?: @"";
|
||||
}
|
||||
|
||||
- (void)setElementId:(NSString *)elementId
|
||||
{
|
||||
_attributes[@"id"] = elementId;
|
||||
}
|
||||
|
||||
- (NSString *)className
|
||||
{
|
||||
return _attributes[@"class"];
|
||||
return _attributes[@"class"] ?: @"";
|
||||
}
|
||||
|
||||
- (void)setClassName:(NSString *)className
|
||||
{
|
||||
_attributes[@"class"] = className;
|
||||
}
|
||||
|
||||
- (HTMLDOMTokenList *)classList
|
||||
{
|
||||
return [[HTMLDOMTokenList alloc] initWithElement:self attribute:@"class" value:self.className];
|
||||
}
|
||||
|
||||
#pragma mark - Attributes
|
||||
|
||||
- (BOOL)hasAttribute:(NSString *)name
|
||||
{
|
||||
return _attributes[name] != nil;
|
||||
@@ -180,4 +198,9 @@
|
||||
return description;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return self.description;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLTokens.h"
|
||||
#import "HTMLNamespaces.h"
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLNamespaces.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
@@ -6,8 +6,17 @@
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Typedef for the error callback block.
|
||||
|
||||
@param reason The string describing the reason of the reported error.
|
||||
*/
|
||||
typedef void (^ HTMLStreamReaderErrorCallback)(NSString *reason);
|
||||
|
||||
/**
|
||||
@@ -16,29 +25,143 @@ typedef void (^ HTMLStreamReaderErrorCallback)(NSString *reason);
|
||||
*/
|
||||
@interface HTMLInputStreamReader : NSObject
|
||||
|
||||
/** @brief The underlying string with which this stream reader was initialized */
|
||||
@property (nonatomic, readonly) NSString *string;
|
||||
|
||||
/** @brief The current scan location */
|
||||
@property (nonatomic, readonly) NSUInteger currentLocation;
|
||||
|
||||
/** @brief An error callback block, which gets called when encountering errors while reading the stream */
|
||||
@property (nonatomic, copy) HTMLStreamReaderErrorCallback errorCallback;
|
||||
|
||||
/**
|
||||
Initializes a new Input Stream Reader with the given string.
|
||||
|
||||
@param string The HTML string
|
||||
@returns A new instance of the Input Stream Reader.
|
||||
*/
|
||||
- (id)initWithString:(NSString *)string;
|
||||
|
||||
/**
|
||||
Returns the current input character.
|
||||
|
||||
@returns The current code point in the input stream as a `UTF32Char`.
|
||||
*/
|
||||
- (UTF32Char)currentInputCharacter;
|
||||
|
||||
/**
|
||||
Returns the next input character without consuming it.
|
||||
|
||||
@returns The next code point in the input stream as a `UTF32Char`. Returns `EOF` if the stream is fully consumed.
|
||||
*/
|
||||
- (UTF32Char)nextInputCharacter;
|
||||
|
||||
/**
|
||||
Returns the input character at a given offset without consuming it.
|
||||
|
||||
@param offset The offset of the character.
|
||||
@returns The code point in the input stream as a `UTF32Char` at the given offset.
|
||||
*/
|
||||
- (UTF32Char)inputCharacterPointAtOffset:(NSUInteger)offset;
|
||||
|
||||
/**
|
||||
Consumes and returns the next input character. Consuming a characters advances the current scan location of the
|
||||
input stream.
|
||||
|
||||
@returns The next code point in the input stream as a `UTF32Char`. Returns `EOF` if the stream is fully consumed.
|
||||
*/
|
||||
- (UTF32Char)consumeNextInputCharacter;
|
||||
|
||||
/**
|
||||
Causes the next input character to return the current input character.
|
||||
*/
|
||||
- (void)reconsumeCurrentInputCharacter;
|
||||
|
||||
/** @brief Unconsumes the current input character. */
|
||||
- (void)unconsumeCurrentInputCharacter;
|
||||
|
||||
/**
|
||||
Consumes the given character at the current location.
|
||||
|
||||
@param character The character to consume.
|
||||
@returns YES if the given character was consumed at the current location, NO otherwise.
|
||||
*/
|
||||
- (BOOL)consumeCharacter:(UTF32Char)character;
|
||||
|
||||
/**
|
||||
Consumes characters at the current location matching an unsigned number.
|
||||
|
||||
@param result Upon return, contains the consumed unsigned number. Pass `NULL` to skip over an unsigned number at the
|
||||
current location.
|
||||
@returns YES if an unsigned number could be consumed at the current location, NO otherwise.
|
||||
*/
|
||||
- (BOOL)consumeNumber:(unsigned long long *)result;
|
||||
|
||||
/**
|
||||
Consumes characters at the current location matching a decimal number.
|
||||
|
||||
@param result Upon return, contains the consumed decimal number. Pass `NULL` to skip over a decimal number at the
|
||||
current location.
|
||||
@returns YES if a decimal number could be consumed at the current location, NO otherwise.
|
||||
*/
|
||||
- (BOOL)consumeDecimalNumber:(NSDecimal *)result;
|
||||
|
||||
/**
|
||||
Consumes characters at the current location matching a hexadecimal number.
|
||||
|
||||
@param result Upon return, contains the consumed hexadecimal number. Pass `NULL` to skip over a hexadecimal number at
|
||||
the current location.
|
||||
@returns YES if a hexadecimal number could be consumed at the current location, NO otherwise.
|
||||
*/
|
||||
- (BOOL)consumeHexNumber:(unsigned long long *)result;
|
||||
|
||||
/**
|
||||
Consumes the given string at the current location.
|
||||
|
||||
@param string The string to consume.
|
||||
@param caseSensitive YES if the string's case should be ignored, NO otherwise
|
||||
@returns YES if the given string was consumed at the current location, NO otherwise.
|
||||
*/
|
||||
- (BOOL)consumeString:(NSString *)string caseSensitive:(BOOL)caseSensitive;
|
||||
|
||||
/**
|
||||
Consumes characters starting at the current location until any character in a given string is encountered.
|
||||
|
||||
@param characters The string containing the characters to consume up to.
|
||||
@returns A string containing the consumed characters. Returns `nil` if none were consumed.
|
||||
*/
|
||||
- (NSString *)consumeCharactersUpToCharactersInString:(NSString *)characters;
|
||||
|
||||
/**
|
||||
Consumes characters starting at the current location until a given string is encountered.
|
||||
|
||||
@param string The string to consume up to.
|
||||
@returns A string containing the consumed characters. Returns `nil` if none were consumed.
|
||||
*/
|
||||
- (NSString *)consumeCharactersUpToString:(NSString *)string;
|
||||
|
||||
/**
|
||||
Consumes characters as long as the match the characters in the given string starting at the current location.
|
||||
|
||||
@param characters A string with the characters to consume.
|
||||
@returns A string containing the consumed characters. Returns `nil` if none were found.
|
||||
*/
|
||||
- (NSString *)consumeCharactersInString:(NSString *)characters;
|
||||
|
||||
/**
|
||||
Consumes alphanumeric characters starting at the current location.
|
||||
|
||||
@returns A string containing the consumed alphanumeric characters. Returns `nil` if none were found.
|
||||
*/
|
||||
- (NSString *)consumeAlphanumericCharacters;
|
||||
|
||||
/** @brief Marks the current stream scan location. */
|
||||
- (void)markCurrentLocation;
|
||||
|
||||
/** @brief Resets the stream's scan location to the previously marked location. */
|
||||
- (void)rewindToMarkedLocation;
|
||||
|
||||
/** @brief Resets the stream to its begining. */
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "HTMLInputStreamReader.h"
|
||||
#import "HTMLTokenizerCharacters.h"
|
||||
#import "NSCharacterSet+HTMLKit.h"
|
||||
|
||||
#pragma mark - HTMLInputStreamReader
|
||||
|
||||
@@ -39,6 +40,7 @@
|
||||
if (self) {
|
||||
_string = [string copy];
|
||||
_scanner = [[NSScanner alloc] initWithString:string];
|
||||
_scanner.charactersToBeSkipped = nil;
|
||||
CFStringInitInlineBuffer((CFStringRef)_string, &_buffer, CFRangeMake(0, _string.length));
|
||||
}
|
||||
return self;
|
||||
@@ -63,7 +65,6 @@
|
||||
- (UTF32Char)nextInputCharacter
|
||||
{
|
||||
if (_reconsume) {
|
||||
_reconsume = NO;
|
||||
return _currentInputCharacter;
|
||||
}
|
||||
|
||||
@@ -106,6 +107,11 @@
|
||||
return nextInputCharacter;
|
||||
}
|
||||
|
||||
- (UTF32Char)inputCharacterPointAtOffset:(NSUInteger)offset
|
||||
{
|
||||
return CFStringGetCharacterFromInlineBuffer(&_buffer, _location + offset);
|
||||
}
|
||||
|
||||
- (UTF32Char)consumeNextInputCharacter
|
||||
{
|
||||
if (_reconsume) {
|
||||
@@ -124,9 +130,12 @@
|
||||
{
|
||||
UTF32Char nextInputCharacter = [self nextInputCharacter];
|
||||
if (nextInputCharacter == character) {
|
||||
_location += _consume;
|
||||
_scanner.scanLocation = _location;
|
||||
_currentInputCharacter = nextInputCharacter;
|
||||
if (!_reconsume) {
|
||||
_location += _consume;
|
||||
_scanner.scanLocation = _location;
|
||||
_currentInputCharacter = nextInputCharacter;
|
||||
}
|
||||
_reconsume = NO;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
@@ -143,9 +152,20 @@
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)consumeDecimalNumber:(NSDecimal *)result
|
||||
{
|
||||
NSDecimal scanned;
|
||||
BOOL success = [_scanner scanDecimal:&scanned];
|
||||
if (success == NO) return NO;
|
||||
|
||||
*result = scanned;
|
||||
_location = _scanner.scanLocation;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)consumeHexNumber:(unsigned long long *)result
|
||||
{
|
||||
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEFabcdef"];
|
||||
NSCharacterSet *set = [NSCharacterSet HTMLHexNumberCharacterSet];
|
||||
|
||||
NSString *string = nil;
|
||||
BOOL success = [_scanner scanCharactersFromSet:set intoString:&string];
|
||||
@@ -193,6 +213,26 @@
|
||||
return consumed;
|
||||
}
|
||||
|
||||
- (NSString *)consumeCharactersInString:(NSString *)characters
|
||||
{
|
||||
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:characters];
|
||||
|
||||
if (_reconsume) {
|
||||
_scanner.scanLocation--;
|
||||
}
|
||||
|
||||
NSString *string = nil;
|
||||
BOOL success = [_scanner scanCharactersFromSet:set intoString:&string];
|
||||
if (success == NO) {
|
||||
_scanner.scanLocation++;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_reconsume = NO;
|
||||
_location = _scanner.scanLocation;
|
||||
return string;
|
||||
}
|
||||
|
||||
- (NSString *)consumeAlphanumericCharacters
|
||||
{
|
||||
NSCharacterSet *set = [NSCharacterSet alphanumericCharacterSet];
|
||||
|
||||
+13
-2
@@ -8,6 +8,17 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLKit : NSObject
|
||||
//! Project version number for HTMLKit.
|
||||
extern double HTMLKitVersionNumber;
|
||||
|
||||
@end
|
||||
//! Project version string for HTMLKit.
|
||||
extern const unsigned char HTMLKitVersionString[];
|
||||
|
||||
#import "HTMLDOM.h"
|
||||
#import "HTMLParser.h"
|
||||
#import "HTMLKitErrorDomain.h"
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
|
||||
#import "CSSSelectors.h"
|
||||
#import "CSSSelectorParser.h"
|
||||
#import "CSSNthExpressionParser.h"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// HTMLKit.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLKit.h"
|
||||
|
||||
@implementation HTMLKit
|
||||
|
||||
@end
|
||||
@@ -6,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;
|
||||
|
||||
@@ -11,3 +11,5 @@
|
||||
NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError";
|
||||
NSString * const HTMLKitNotFoundError = @"NotFoundError";
|
||||
NSString * const HTMLKitNotSupportedError = @"NotSupportedError";
|
||||
NSString * const HTMLKitSyntaxError = @"SyntaxError";
|
||||
NSString * const HTMLKitInvalidCharacterError = @"InvalidCharacterError";
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// HTMLKitErrorDomain.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 24/11/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef HTMLKitErrorDomain_h
|
||||
#define HTMLKitErrorDomain_h
|
||||
|
||||
static NSString *const HTMLKitErrorDomain = @"HTMLKit";
|
||||
static NSString *const HTMLKitSelectorErrorDomain = @"HTMLKitSelector";
|
||||
|
||||
static NSString *const CSSSelectorStringKey = @"CSSSelectorString";
|
||||
static NSString *const CSSSelectorErrorLocationKey = @"CSSSelectorErrorLocation";
|
||||
|
||||
NS_ENUM(NSInteger)
|
||||
{
|
||||
HTMLKitSelectorParseError = 4200
|
||||
};
|
||||
|
||||
#endif /* HTMLKitErrorDomain_h */
|
||||
@@ -6,33 +6,134 @@
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
/**
|
||||
The List of Active Formatting Elements. It is used to handle mis-nested formatting element tags.
|
||||
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#the-list-of-active-formatting-elements
|
||||
*/
|
||||
@interface HTMLListOfActiveFormattingElements : NSObject
|
||||
|
||||
/**
|
||||
Returns the object at the specified index.
|
||||
|
||||
@param index An index within the bounds of the list.
|
||||
@return The node located at index.
|
||||
*/
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Replaces the object at the index with the new object.
|
||||
|
||||
@param obj The node with which to replace the object at given index in the list.
|
||||
@param idx The index of the object to be replaced.
|
||||
*/
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||
|
||||
/**
|
||||
Returns the index of the given node in the list.
|
||||
|
||||
@param node The node.
|
||||
@returns The index of the given node in the list.
|
||||
*/
|
||||
- (NSUInteger)indexOfElement:(id)node;
|
||||
|
||||
/**
|
||||
Adds the given element to the list.
|
||||
|
||||
@param element The element to add.
|
||||
*/
|
||||
- (void)addElement:(HTMLElement *)element;
|
||||
|
||||
/**
|
||||
Removes the given element from the list.
|
||||
|
||||
@param element The element to remove.
|
||||
*/
|
||||
- (void)removeElement:(id)element;
|
||||
|
||||
/**
|
||||
Checks whether the given element is in the list.
|
||||
|
||||
@param element The element to check.
|
||||
@returns `YES` if element is in the list, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)containsElement:(id)element;
|
||||
|
||||
/**
|
||||
Inserts the given element at the index into the list.
|
||||
|
||||
@param element The element to insert.
|
||||
@param index The index at which the element should be inserted.
|
||||
*/
|
||||
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Replaces the element at the given index in the list with the new element.
|
||||
|
||||
@param index The index of the element to be replaced.
|
||||
@param element The element with which to replace the element at given index in the list.
|
||||
*/
|
||||
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
|
||||
|
||||
/**
|
||||
Returns the last element in this list.
|
||||
|
||||
@returns The last entry.
|
||||
*/
|
||||
- (id)lastEntry;
|
||||
|
||||
/**
|
||||
Adds a marker to the end of this list
|
||||
*/
|
||||
- (void)addMarker;
|
||||
|
||||
/**
|
||||
Clears all elements from the end of this list upto the last marker.
|
||||
*/
|
||||
- (void)clearUptoLastMarker;
|
||||
|
||||
/**
|
||||
Returns the last element in the list having the given tag name, that is between the end of the list and the last marker
|
||||
in the list, if any, or the start of the list otherwise.
|
||||
|
||||
@param tagName The tag name.
|
||||
@returns The formatting element.
|
||||
*/
|
||||
- (HTMLElement *)formattingElementWithTagName:(NSString *)tagName;
|
||||
|
||||
/**
|
||||
Returns the count of elements in this list.
|
||||
|
||||
@returns The elements count.
|
||||
*/
|
||||
- (NSUInteger)count;
|
||||
|
||||
/**
|
||||
Checks whether this list is empty.
|
||||
|
||||
@returns `YES` if the stack is empty, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
/**
|
||||
Return an object enumerator over this list.
|
||||
|
||||
@returns An enumerator
|
||||
*/
|
||||
- (NSEnumerator *)enumerator;
|
||||
|
||||
/**
|
||||
Return an object enumerator over this list.
|
||||
|
||||
@returns An enumerator
|
||||
*/
|
||||
- (NSEnumerator *)reverseObjectEnumerator;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// HTMLNode+Private.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/12/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <HTMLKit/HTMLKit.h>
|
||||
|
||||
/**
|
||||
Private HTML Node methods which are not intended for public API.
|
||||
*/
|
||||
@interface HTMLNode ()
|
||||
|
||||
/**
|
||||
A read-write redeclaration of the same property in the public API.
|
||||
*/
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
|
||||
/**
|
||||
A read-write redeclaration of the same property in the public API.
|
||||
*/
|
||||
@property (nonatomic, weak) HTMLNode *parentNode;
|
||||
|
||||
/**
|
||||
Designated initializer of the HTML Node, which, however, should not be used directly. It is intended to be called only
|
||||
by subclasses.
|
||||
|
||||
@abstract Use concrete subclasses of the HTML Node.
|
||||
|
||||
@param name The node's name.
|
||||
@param type The node's type.
|
||||
@returns A new instance of a HTML Node.
|
||||
*/
|
||||
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Casts this node to a HTML Element. This cast should only be performed after the appropriate check.
|
||||
*/
|
||||
- (HTMLElement *)asElement;
|
||||
|
||||
/**
|
||||
Returns the same string representation of the DOM tree rooted at this node that is used by html5lib-tests.
|
||||
|
||||
@disucssion This method is indended for testing purposes.
|
||||
*/
|
||||
- (NSString *)treeDescription;
|
||||
|
||||
@end
|
||||
+320
-20
@@ -9,22 +9,25 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNodeIterator.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The HTML node type
|
||||
*/
|
||||
typedef NS_ENUM(short, HTMLNodeType)
|
||||
{
|
||||
HTMLNodeElement = 1,
|
||||
HTMLNodeAttribute = 2, // historical
|
||||
HTMLNodeText = 3,
|
||||
HTMLNodeCDATASection = 4, // historical
|
||||
HTMLNodeEntityReference = 5, // historical
|
||||
HTMLNodeEntity = 6, // historical
|
||||
HTMLNodeProcessingInstruction = 7,
|
||||
HTMLNodeComment = 8,
|
||||
HTMLNodeDocument = 9,
|
||||
HTMLNodeDocumentType = 10,
|
||||
HTMLNodeDocumentFragment = 11,
|
||||
HTMLNodeNotation = 12 // historical
|
||||
};
|
||||
|
||||
/**
|
||||
A node's position in the HTML document when compared with other nodes.
|
||||
*/
|
||||
typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
{
|
||||
HTMLDocumentPositionEquivalent = 0x0,
|
||||
@@ -38,89 +41,386 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
|
||||
@class HTMLDocument;
|
||||
@class HTMLElement;
|
||||
@class CSSSelector;
|
||||
|
||||
/**
|
||||
A HTML Node, the base class for all HTML DOM entities.
|
||||
|
||||
HTMLKit provides a partial implementation of the WHATWG DOM specification: https://dom.spec.whatwg.org/
|
||||
*/
|
||||
@interface HTMLNode : NSObject <NSCopying>
|
||||
|
||||
/**
|
||||
The node type.
|
||||
|
||||
@see HTMLNodeType
|
||||
*/
|
||||
@property (nonatomic, assign, readonly) HTMLNodeType nodeType;
|
||||
|
||||
/**
|
||||
The node name as described in https://dom.spec.whatwg.org/#dom-node-nodename
|
||||
|
||||
@warning This is not the HTML Element tag name.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSString *name;
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLDocument *ownerDocument;
|
||||
/**
|
||||
The owner document of this node.
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLNode *parentNode;
|
||||
@see HTMLDocument
|
||||
*/
|
||||
@property (nonatomic, weak, readonly, nullable) HTMLDocument *ownerDocument;
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLElement *parentElement;
|
||||
/**
|
||||
The parent node of this node, if any.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly, nullable) HTMLNode *parentNode;
|
||||
|
||||
/**
|
||||
The parent element of this node, if any.
|
||||
|
||||
@discussion This property returns nil if the parent is a non-element node.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly, nullable) HTMLElement *parentElement;
|
||||
|
||||
/**
|
||||
A read-only ordered set of child nodes.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSOrderedSet *childNodes;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *firstChild;
|
||||
/**
|
||||
The first child node, if any.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) HTMLNode *firstChild;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *lastChild;
|
||||
/**
|
||||
The last child node, if any.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) HTMLNode *lastChild;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *previousSibling;
|
||||
/**
|
||||
The previous sibling node in the document, if any.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) HTMLNode *previousSibling;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *nextSibling;
|
||||
/**
|
||||
The next sibling node in the document, if any.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) HTMLNode *nextSibling;
|
||||
|
||||
/**
|
||||
The previous sibling element in the document, if any.
|
||||
|
||||
@discussion Previous non-element nodes will be skipped till an element is found.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) HTMLElement *previousSiblingElement;
|
||||
|
||||
/**
|
||||
The next sibling element in the document, if any.
|
||||
|
||||
@discussion Next non-element nodes will be skipped till an element is found.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) HTMLElement *nextSiblingElement;
|
||||
|
||||
/**
|
||||
The text content of this node.
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *textContent;
|
||||
|
||||
/**
|
||||
The outer HTML string.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSString *outerHTML;
|
||||
|
||||
/**
|
||||
The inner HTML string.
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *innerHTML;
|
||||
|
||||
/**
|
||||
@abstract Use concrete subclasses of the HTML Node.
|
||||
*/
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type;
|
||||
|
||||
- (HTMLElement *)asElement;
|
||||
|
||||
/**
|
||||
Checks whether this node has child nodes.
|
||||
|
||||
@returns `YES` if this node has any children, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)hasChildNodes;
|
||||
|
||||
/**
|
||||
Checks whether this node has child nodes of the given type.
|
||||
|
||||
@param type The type to check.
|
||||
@returns `YES` if this node has any children of the given type, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type;
|
||||
|
||||
/**
|
||||
Returns the cound of child nodes.
|
||||
|
||||
@returns The child nodes count.
|
||||
*/
|
||||
- (NSUInteger)childNodesCount;
|
||||
|
||||
/**
|
||||
Returns the child node at a given index.
|
||||
|
||||
@param index The index at which to return the child node.
|
||||
@returns The child node at a index. If index is greater than or equal to the value returned by count, an
|
||||
NSRangeException is raised.
|
||||
*/
|
||||
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Returns the index of the given child node in the set of child nodes.
|
||||
|
||||
@param node The node.
|
||||
@returns The index of the given node in the children set.
|
||||
*/
|
||||
- (NSUInteger)indexOfChildNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Returns the cound of child elements.
|
||||
|
||||
@discussion This method count only nodes of type HTMLNodeElement.
|
||||
|
||||
@returns The child elements count.
|
||||
*/
|
||||
- (NSUInteger)childElementsCount;
|
||||
|
||||
/**
|
||||
Returns the child element at a given index.
|
||||
|
||||
@param index The index at which to return the child element.
|
||||
@returns The child element at a index. If index is greater than or equal to the value returned by count, an
|
||||
NSRangeException is raised.
|
||||
*/
|
||||
- (HTMLElement *)childElementAtIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Returns the index of the given child element in the set of child nodes.
|
||||
|
||||
@param node The element.
|
||||
@returns The index of the given element in the children set.
|
||||
*/
|
||||
- (NSUInteger)indexOfChildElement:(HTMLElement *)element;
|
||||
|
||||
/**
|
||||
Prepends the given node to the set of child nodes.
|
||||
|
||||
@param node The node to prepend.
|
||||
@returns The node being prepended.
|
||||
*/
|
||||
- (HTMLNode *)prependNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Prepends the given array of nodes to the set of child nodes.
|
||||
|
||||
@param node The nodes to prepend.
|
||||
*/
|
||||
- (void)prependNodes:(NSArray *)nodes;
|
||||
|
||||
/**
|
||||
Appends the given node to the set of child nodes.
|
||||
|
||||
@param node The node to append.
|
||||
@returns The node being appended.
|
||||
*/
|
||||
- (HTMLNode *)appendNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Appends the given array of nodes to the set of child nodes.
|
||||
|
||||
@param nodes The nodes to append.
|
||||
*/
|
||||
- (void)appendNodes:(NSArray *)nodes;
|
||||
|
||||
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child;
|
||||
/**
|
||||
Inserts a given node before a child node.
|
||||
|
||||
@param node The node to insert.
|
||||
@param child A reference child node before which the new node should be inserted. If child is `nil` then the new node
|
||||
will be inserted as the last child node.
|
||||
@returns The node being inserted.
|
||||
*/
|
||||
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(nullable HTMLNode *)child;
|
||||
|
||||
- (HTMLNode *)replaceChildNode:(HTMLNode *)node withNode:(HTMLNode *)replacement;
|
||||
/**
|
||||
Replaces a given child node whith new node.
|
||||
|
||||
@param child The child node to replace.
|
||||
@param replacement The replacement node.
|
||||
@returns The replacement node.
|
||||
*/
|
||||
- (HTMLNode *)replaceChildNode:(HTMLNode *)child withNode:(HTMLNode *)replacement;
|
||||
|
||||
/**
|
||||
Replaces all child nodes with the given node.
|
||||
|
||||
@param node The node which will replace all child nodes.
|
||||
*/
|
||||
- (void)replaceAllChildNodesWithNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Removes this node from its parent.
|
||||
|
||||
@discussion This will detach the node from its parent and in turn from its previous document.
|
||||
*/
|
||||
- (void)removeFromParentNode;
|
||||
|
||||
/**
|
||||
Removes the given child node from children.
|
||||
|
||||
@param node The node to remove.
|
||||
*/
|
||||
- (HTMLNode *)removeChildNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Removes the child node at index from children.
|
||||
|
||||
@param index The index of the node to remove.
|
||||
*/
|
||||
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Changes children ownership from this node to the given node.
|
||||
|
||||
@discussion Running this method will append all children of this node to the given node. This node will have no children
|
||||
afterwards.
|
||||
|
||||
@param node The node which will reparent children of this node.
|
||||
*/
|
||||
- (void)reparentChildNodesIntoNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Removes all child nodes.
|
||||
*/
|
||||
- (void)removeAllChildNodes;
|
||||
|
||||
/**
|
||||
Compares the position of this node with the given node in the document.
|
||||
|
||||
@param node The node with which to comapre the position.
|
||||
@returns The HTMLDocumentPosition of this node in relation to the given node.
|
||||
|
||||
@see HTMLDocumentPosition
|
||||
*/
|
||||
- (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Checks whether this node is descendant of the given node.
|
||||
|
||||
@param node The node to check.
|
||||
@returns `YES` if this node is descendant of the gicen node, `NO` otherwsie.
|
||||
*/
|
||||
- (BOOL)isDescendantOfNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Checks whether this node contains the given node.
|
||||
|
||||
@param node The node to check.
|
||||
@returns `YES` if this node contains the given node, `NO` otherwsie.
|
||||
*/
|
||||
- (BOOL)containsNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Enumerates and applies `block` on each child node.
|
||||
|
||||
@block The block to run for each child node.
|
||||
*/
|
||||
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block;
|
||||
|
||||
/**
|
||||
Enumerates and applies `block` on each child element.
|
||||
|
||||
@discussion This method only enumerates child elements.
|
||||
|
||||
@block The block to run for each child element.
|
||||
*/
|
||||
- (void)enumerateChildElementsUsingBlock:(void (^)(HTMLElement *element, NSUInteger idx, BOOL *stop))block;
|
||||
|
||||
/**
|
||||
Returns a node iterator rooted at this node whith no filter and HTMLNodeFilterShowAll.
|
||||
|
||||
@returns A new node iterator whose root is this node.
|
||||
|
||||
@see HTMLNodeIterator
|
||||
@see HTMLNodeFilterShowOptions
|
||||
*/
|
||||
- (HTMLNodeIterator *)nodeIterator;
|
||||
|
||||
/**
|
||||
Returns a node iterator rooted at this node.
|
||||
|
||||
@param showOptions The iterator's show options.
|
||||
@param filter The iterator's filter.
|
||||
@returns A new node iterator whose root is this node.
|
||||
|
||||
@see HTMLNodeIterator
|
||||
@see HTMLNodeFilterShowOptions
|
||||
*/
|
||||
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filter:(id<HTMLNodeFilter>)filter;
|
||||
filter:(nullable id<HTMLNodeFilter>)filter;
|
||||
|
||||
/**
|
||||
Returns a node iterator rooted at this node.
|
||||
|
||||
@param showOptions The iterator's show options.
|
||||
@param filter The iterator's filter block.
|
||||
@returns A new node iterator whose root is this node.
|
||||
|
||||
@see HTMLNodeIterator
|
||||
@see HTMLNodeFilterShowOptions
|
||||
*/
|
||||
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter;
|
||||
|
||||
- (NSString *)treeDescription;
|
||||
/**
|
||||
Returns the first element in the DOM tree rooted at this node, that is matched by the given selector string.
|
||||
|
||||
@param selector The CSS seletor string.
|
||||
@returns The first element that is matched by the parsed selector. Rerturns `nil` if the selector could not be parsed
|
||||
or no element was matched.
|
||||
|
||||
@see firstElementMatchingSelector:
|
||||
@see CSSSelector
|
||||
*/
|
||||
- (nullable HTMLElement *)querySelector:(NSString *)selector;
|
||||
|
||||
/**
|
||||
Returns all elements in the DOM tree rooted at this node, that are matched by the given selector string.
|
||||
|
||||
@param selector The CSS seletor string.
|
||||
@returns The elements that are matched by the parsed selector. Rerturns an empty array if the selector could not be parsed
|
||||
or no elements were matched.
|
||||
|
||||
@see elementsMatchingSelector:
|
||||
@see CSSSelector
|
||||
*/
|
||||
- (NSArray<HTMLElement *> *)querySelectorAll:(NSString *)selector;
|
||||
|
||||
/**
|
||||
Returns the first element in the DOM tree rooted at this node, that is matched by the given selector.
|
||||
|
||||
@param selector The CSS seletor.
|
||||
@returns The first element that is matched by the parsed selector. Rerturns `nil` if no element was matched.
|
||||
|
||||
@see CSSSelector
|
||||
*/
|
||||
- (nullable HTMLElement *)firstElementMatchingSelector:(CSSSelector *)selector;
|
||||
|
||||
/**
|
||||
Returns all elements in the DOM tree rooted at this node, that are matched by the given selector.
|
||||
|
||||
@param selector The CSS seletor.
|
||||
@returns The elements that are matched by the parsed selector. Rerturns an empty array if no elements were matched.
|
||||
|
||||
@see CSSSelector
|
||||
*/
|
||||
- (NSArray<HTMLElement *> *)elementsMatchingSelector:(CSSSelector *)selector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
+103
-9
@@ -7,12 +7,15 @@
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
#import "HTMLNodeFilter.h"
|
||||
#import "CSSSelector.h"
|
||||
|
||||
@interface HTMLDocument (Private)
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
@@ -24,7 +27,6 @@
|
||||
{
|
||||
NSMutableOrderedSet *_childNodes;
|
||||
}
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@end
|
||||
|
||||
@implementation HTMLNode
|
||||
@@ -98,6 +100,24 @@
|
||||
return [_parentNode childNodeAtIndex:index + 1];
|
||||
}
|
||||
|
||||
- (HTMLElement *)previousSiblingElement
|
||||
{
|
||||
HTMLNode *node = self.previousSibling;
|
||||
while (node && node.nodeType != HTMLNodeElement) {
|
||||
node = node.previousSibling;
|
||||
}
|
||||
return node.asElement;
|
||||
}
|
||||
|
||||
- (HTMLElement *)nextSiblingElement
|
||||
{
|
||||
HTMLNode *node = self.previousSibling;
|
||||
while (node && node.nodeType != HTMLNodeElement) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return node.asElement;
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return nil;
|
||||
@@ -140,11 +160,46 @@
|
||||
return [self.childNodes objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (NSUInteger)childElementsCount
|
||||
{
|
||||
return [self.childNodes indexesOfObjectsPassingTest:^BOOL(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
return node.nodeType == HTMLNodeElement;
|
||||
}].count;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildNode:(HTMLNode *)node
|
||||
{
|
||||
return [self.childNodes indexOfObject:node];
|
||||
}
|
||||
|
||||
- (HTMLElement *)childElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
NSUInteger counter = 0;
|
||||
for (HTMLNode *node in self.childNodes) {
|
||||
if (node.nodeType == HTMLNodeElement) {
|
||||
if (counter == index) {
|
||||
return node.asElement;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildElement:(HTMLElement *)element
|
||||
{
|
||||
NSUInteger counter = 0;
|
||||
for (HTMLNode *node in self.childNodes) {
|
||||
if (node.nodeType == HTMLNodeElement) {
|
||||
if (node == element) {
|
||||
return counter;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
- (HTMLNode *)prependNode:(HTMLNode *)node
|
||||
{
|
||||
return [self insertNode:node beforeChildNode:self.firstChild];
|
||||
@@ -381,9 +436,53 @@
|
||||
}
|
||||
|
||||
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter
|
||||
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block
|
||||
{
|
||||
return [HTMLNodeIterator iteratorWithNode:self showOptions:showOptions filter:filter];
|
||||
HTMLNodeFilterBlock *filter = [HTMLNodeFilterBlock filterWithBlock:block];
|
||||
return [[HTMLNodeIterator alloc] initWithNode:self showOptions:showOptions filter:filter];
|
||||
}
|
||||
|
||||
#pragma mark - Selectors
|
||||
|
||||
- (HTMLElement *)querySelector:(NSString *)selectorString
|
||||
{
|
||||
CSSSelector *selector = [CSSSelector selectorWithString:selectorString];
|
||||
return [self firstElementMatchingSelector:selector];
|
||||
}
|
||||
|
||||
- (NSArray<HTMLElement *> *)querySelectorAll:(NSString *)selectorString
|
||||
{
|
||||
CSSSelector *selector = [CSSSelector selectorWithString:selectorString];
|
||||
return [self elementsMatchingSelector:selector];
|
||||
}
|
||||
|
||||
- (HTMLElement *)firstElementMatchingSelector:(CSSSelector *)selector
|
||||
{
|
||||
if (selector == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
for (HTMLElement *element in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
|
||||
if ([selector acceptElement:element]) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray<HTMLElement *> *)elementsMatchingSelector:(CSSSelector *)selector
|
||||
{
|
||||
if (selector == nil) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSMutableArray *result = [NSMutableArray array];
|
||||
for (HTMLElement *element in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
|
||||
if ([selector acceptElement:element]) {
|
||||
[result addObject:element];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef HTMLKIT_NO_DOM_CHECKS
|
||||
@@ -610,12 +709,7 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p %@>", self.class, self, self.name];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return self.treeDescription;
|
||||
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.name];
|
||||
}
|
||||
|
||||
- (id)debugQuickLookObject
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The node filter's value when applied to a given HTML node. The node filter can either accept a node, skip it, or
|
||||
reject it. Rejecting a node means skipping the node itself and all of it descendants.
|
||||
*/
|
||||
typedef NS_ENUM(unsigned short, HTMLNodeFilterValue)
|
||||
{
|
||||
HTMLNodeFilterAccept = 1,
|
||||
@@ -15,6 +21,12 @@ typedef NS_ENUM(unsigned short, HTMLNodeFilterValue)
|
||||
HTMLNodeFilterSkip = 3
|
||||
};
|
||||
|
||||
/**
|
||||
The show options for the HTML node iterator and tree walker.
|
||||
|
||||
@see HTMLNodeIterator
|
||||
@see HTMLTreeWalker
|
||||
*/
|
||||
typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions)
|
||||
{
|
||||
HTMLNodeFilterShowAll = 0xFFFFFFFF,
|
||||
@@ -26,16 +38,61 @@ typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions)
|
||||
HTMLNodeFilterShowDocumentFragment = 0x400
|
||||
};
|
||||
|
||||
|
||||
#pragma mark - Node Filter
|
||||
|
||||
@class HTMLNode;
|
||||
|
||||
/**
|
||||
A HTML Node Filter which can be used with a node iterator or a tree walker.
|
||||
|
||||
@see HTMLNodeIterator
|
||||
@see HTMLTreeWalker
|
||||
*/
|
||||
@protocol HTMLNodeFilter <NSObject>
|
||||
|
||||
@required
|
||||
/**
|
||||
The implementation should return a HTMLNodeFilterValue to indicate accepting, skipping or rejecting a node.
|
||||
|
||||
@param node The node to be filtered.
|
||||
@returns `HTMLNodeFilterAccept` if accepted, `HTMLNodeFilterSkip` if skipped, or `HTMLNodeFilterReject` if rejected.
|
||||
*/
|
||||
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Block Filter
|
||||
|
||||
/**
|
||||
A concrete block-based HTML Node Filter implementation.
|
||||
*/
|
||||
@interface HTMLNodeFilterBlock : NSObject <HTMLNodeFilter>
|
||||
|
||||
/**
|
||||
Initializes and returns a new instance of this filter.
|
||||
|
||||
@param block The block to apply on each node to be filtered.
|
||||
*/
|
||||
+ (instancetype)filterWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CSS Selector Filter
|
||||
|
||||
@class CSSSelector;
|
||||
|
||||
/**
|
||||
A concrete css-selector-based HTML Node Filter implementation.
|
||||
*/
|
||||
@interface HTMLSelectorNodeFilter : NSObject <HTMLNodeFilter>
|
||||
|
||||
/**
|
||||
Initializes and returns a new instance of this filter.
|
||||
|
||||
@param selector The selector to apply on each node to be filtered.
|
||||
*/
|
||||
+ (instancetype)filterWithSelector:(CSSSelector *)selector;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
//
|
||||
|
||||
#import "HTMLNodeFilter.h"
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
#import "CSSSelector.h"
|
||||
|
||||
#pragma mark - Block Filter
|
||||
|
||||
@interface HTMLNodeFilterBlock ()
|
||||
{
|
||||
@@ -40,3 +45,42 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CSS Selector Filter
|
||||
|
||||
@interface HTMLSelectorNodeFilter ()
|
||||
{
|
||||
CSSSelector *_selector;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLSelectorNodeFilter
|
||||
|
||||
+ (instancetype)filterWithSelector:(CSSSelector *)selector
|
||||
{
|
||||
return [[self alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelector:(CSSSelector *)selector
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node
|
||||
{
|
||||
if (node.nodeType != HTMLNodeElement) {
|
||||
return HTMLNodeFilterSkip;
|
||||
}
|
||||
|
||||
if ([_selector acceptElement:node.asElement]) {
|
||||
return HTMLNodeFilterAccept;
|
||||
}
|
||||
|
||||
return HTMLNodeFilterSkip;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,28 +9,87 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNodeFilter.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class HTMLNode;
|
||||
|
||||
@interface HTMLNodeIterator : NSEnumerator
|
||||
/**
|
||||
A HTML Node Iterator, which iterates the nodes in the DOM in tree order, i.e. depth-first traversal of the tree.
|
||||
|
||||
https://dom.spec.whatwg.org/#interface-nodeiterator
|
||||
*/
|
||||
@interface HTMLNodeIterator : NSEnumerator<HTMLNode *>
|
||||
|
||||
/**
|
||||
The root element of this iterator, i.e. the traversed tree is rooted at this element.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) HTMLNode *root;
|
||||
|
||||
/**
|
||||
The current reference node.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) HTMLNode *referenceNode;
|
||||
|
||||
/**
|
||||
Whether the iterator's pointer is before the reference node.
|
||||
*/
|
||||
@property (nonatomic, assign, readonly) BOOL pointerBeforeReferenceNode;
|
||||
|
||||
/**
|
||||
The iterator's show options. These options control what types of elements are shown or skipped during iteration.
|
||||
|
||||
@see HTMLNodeFilterShowOptions
|
||||
*/
|
||||
@property (nonatomic, assign, readonly) HTMLNodeFilterShowOptions whatToShow;
|
||||
@property (nonatomic, strong, readonly) id<HTMLNodeFilter> filter;
|
||||
|
||||
+ (instancetype)iteratorWithNode:(HTMLNode *)node
|
||||
showOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filter:(HTMLNodeFilterValue (^)(HTMLNode *node))filter;
|
||||
/**
|
||||
A node filter, that is applied to each node during iteration.
|
||||
|
||||
@see HTMLNodeFilter
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) id<HTMLNodeFilter> filter;
|
||||
|
||||
|
||||
/**
|
||||
Initializes a new node iterator with no filter and HTMLNodeFilterShowAll show options.
|
||||
|
||||
@param node The root node.
|
||||
@returns A new instance of a node iterator.
|
||||
*/
|
||||
- (instancetype)initWithNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Initializes a new node iterator with HTMLNodeFilterShowAll show options.
|
||||
|
||||
@param node The root node.
|
||||
@param filter The node filter to use.
|
||||
@returns A new instance of a node iterator.
|
||||
*/
|
||||
- (instancetype)initWithNode:(HTMLNode *)node
|
||||
filter:(id<HTMLNodeFilter>)filter;
|
||||
filter:(nullable id<HTMLNodeFilter>)filter;
|
||||
|
||||
/**
|
||||
Initializes a new node iterator.
|
||||
|
||||
@param node The root node.
|
||||
@param showOptions The show options for the iterator.
|
||||
@param filter The node filter to use.
|
||||
@returns A new instance of a node iterator.
|
||||
*/
|
||||
- (instancetype)initWithNode:(HTMLNode *)node
|
||||
showOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filter:(id<HTMLNodeFilter>)filter;
|
||||
filter:(nullable id<HTMLNodeFilter>)filter;
|
||||
|
||||
- (HTMLNode *)nextNode;
|
||||
- (HTMLNode *)previousNode;
|
||||
/**
|
||||
@returns The next iterated node in tree order, `nil` if there are no more nodes to iterate.
|
||||
*/
|
||||
- (nullable HTMLNode *)nextNode;
|
||||
|
||||
/**
|
||||
@returns The previous iterated node in tree order, `nil` if there are no more nodes to iterate.
|
||||
*/
|
||||
- (nullable HTMLNode *)previousNode;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNodeFilter.h"
|
||||
|
||||
|
||||
@@ -8,18 +8,83 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLOrderedDictionary : NSMutableDictionary
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (id)objectAtIndex:(NSUInteger)index;
|
||||
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey atIndex:(NSUInteger)index;
|
||||
/**
|
||||
An ordered mutable dictionary, that preserves the order of its keys.
|
||||
*/
|
||||
@interface HTMLOrderedDictionary<KeyType, ObjectType> : NSMutableDictionary<KeyType, ObjectType>
|
||||
|
||||
/**
|
||||
Returns the object at the specified index.
|
||||
|
||||
@param index An index within the bounds of the dictionary.
|
||||
@returns The object located at index.
|
||||
*/
|
||||
- (ObjectType)objectAtIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Sets the object for the given key at the specified index.
|
||||
|
||||
@param anObject The object.
|
||||
@param aKey The key.
|
||||
@param index An index within the bounds of the dictionary.
|
||||
*/
|
||||
- (void)setObject:(ObjectType)anObject forKey:(KeyType<NSCopying>)aKey atIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Removes the key-value pair located at the specified index.
|
||||
|
||||
@param index An index within the bounds of the dictionary.
|
||||
*/
|
||||
- (void)removeObjectAtIndex:(NSUInteger)index;
|
||||
- (void)replaceKeyValueAtIndex:(NSUInteger)index withObject:(id)anObject andKey:(id<NSCopying>)aKey;
|
||||
- (void)replaceKey:(id<NSCopying>)aKey withKey:(id<NSCopying>)newKey;
|
||||
- (NSUInteger)indexOfKey:(id<NSCopying>)aKey;
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index;
|
||||
/**
|
||||
Replaces the key-value pair located at the specified index.
|
||||
|
||||
- (NSEnumerator *)reverseKeyEnumerator;
|
||||
@param index An index within the bounds of the dictionary.
|
||||
@param anObject The new object.
|
||||
@param aKey The new key.
|
||||
*/
|
||||
- (void)replaceKeyValueAtIndex:(NSUInteger)index withObject:(ObjectType)anObject andKey:(KeyType<NSCopying>)aKey;
|
||||
|
||||
/**
|
||||
Replaces a key keeping the same object.
|
||||
|
||||
@param aKey The old key to replace.
|
||||
@param newKey The new key.
|
||||
*/
|
||||
- (void)replaceKey:(KeyType<NSCopying>)aKey withKey:(KeyType<NSCopying>)newKey;
|
||||
|
||||
/**
|
||||
Returns the index of the given key in the dictionary.
|
||||
|
||||
@param aKey The key.
|
||||
@returns The index of the given key in the dictionary.
|
||||
*/
|
||||
- (NSUInteger)indexOfKey:(KeyType<NSCopying>)aKey;
|
||||
|
||||
/**
|
||||
Returns the object at the specified index.
|
||||
|
||||
@param index An index within the bounds of the dictionary.
|
||||
@return The object located at index.
|
||||
*/
|
||||
- (ObjectType)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Replaces the object at the index with the new object.
|
||||
|
||||
@param obj The obj with which to replace the object at given index in the dictionary.
|
||||
@param index The index of the object to be replaced.
|
||||
*/
|
||||
- (void)setObject:(ObjectType)obj atIndexedSubscript:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
@returns A reverse key enumerator.
|
||||
*/
|
||||
- (NSEnumerator<KeyType> *)reverseKeyEnumerator;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -6,14 +6,31 @@
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
/**
|
||||
HTML Parse Error Token
|
||||
*/
|
||||
@interface HTMLParseErrorToken : HTMLToken
|
||||
|
||||
/** @brief The error's reason message. */
|
||||
@property (nonatomic, copy) NSString *reason;
|
||||
|
||||
/** @brief The error's location in the stream. */
|
||||
@property (nonatomic, assign) NSUInteger location;
|
||||
|
||||
/**
|
||||
Initializes a new Parse Error token.
|
||||
|
||||
@param reason The error's reason message.
|
||||
@param location The error's location in the stream.
|
||||
@returns A new instance of a parse error token.
|
||||
*/
|
||||
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location;
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,14 +9,66 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The HTML Parser.
|
||||
Parses HTML strings to valid HTML documents and/or fragments. This parser implements the WHATWG specification:
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#tree-construction
|
||||
|
||||
@see HTMLDocument
|
||||
@see HTMLElement
|
||||
*/
|
||||
@interface HTMLParser : NSObject
|
||||
|
||||
/**
|
||||
An array of errors that occurred during document parsing.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSArray *parseErrors;
|
||||
|
||||
/**
|
||||
The parsed HTML Document.
|
||||
|
||||
@see HTMLDocument
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) HTMLDocument *document;
|
||||
|
||||
/**
|
||||
Intializes a new parser instance with a given HTML string.
|
||||
|
||||
@discussion The parser assumes a UTF-8 encoded string and does not implement the encoding sniffing algorithm that is
|
||||
described under the following section of the specification:
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#determining-the-character-encoding
|
||||
|
||||
@param string The HTML string to parse
|
||||
@returns A new instance of the HTML parser.
|
||||
*/
|
||||
- (instancetype)initWithString:(NSString *)string;
|
||||
|
||||
/**
|
||||
Runs the parsing algorithm and generates a valid HTML document object.
|
||||
|
||||
@returns A HTML document object that is the result of parsing the HTML string, with which this parser instance was
|
||||
initialized
|
||||
|
||||
@see HTMLDocument
|
||||
*/
|
||||
- (HTMLDocument *)parseDocument;
|
||||
|
||||
/**
|
||||
Runs the HTML fragment parsing algorithm with the provided context element. The algorithm is sprecified under the
|
||||
following section: https://html.spec.whatwg.org/multipage/syntax.html#parsing-html-fragments
|
||||
|
||||
@discussion The fragment parsing algorithm can be run multiple times with different context elements on the same parser
|
||||
instance. In this case the parser will reset its internal state and re-run the parsing algorithm.
|
||||
|
||||
@param contextElement A context element used for parsing a HTML fragment
|
||||
@returns An array of HTML elements, that are the result of parsing the given HTML string with the given context element.
|
||||
|
||||
@see HTMLElement
|
||||
*/
|
||||
- (NSArray *)parseFragmentWithContextElement:(HTMLElement *)contextElement;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
+114
-102
@@ -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) {
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#define INSERTION_MODES \
|
||||
MODE_ENTRY( HTMLInsertionModeInitial, = 0 ) \
|
||||
MODE_ENTRY( HTMLInsertionModeBeforeHTML, ) \
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -6,37 +6,162 @@
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
/**
|
||||
The Stack of Open Elements. The stack grows downwards; the topmost node on the stack is the first one added to the
|
||||
stack, and the bottommost node of the stack is the most recently added node in the stack
|
||||
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements
|
||||
*/
|
||||
@interface HTMLStackOfOpenElements : NSObject <NSFastEnumeration>
|
||||
|
||||
/** @brief The current node in the stack. It is the bottommost node. */
|
||||
- (HTMLElement *)currentNode;
|
||||
|
||||
/** @brief The first node in the stack. */
|
||||
- (HTMLElement *)firstNode;
|
||||
|
||||
/** @brief The last node in the stack. */
|
||||
- (HTMLElement *)lastNode;
|
||||
|
||||
/**
|
||||
Returns the object at the specified index.
|
||||
|
||||
@param index An index within the bounds of the stack.
|
||||
@return The node located at index.
|
||||
*/
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Replaces the object at the index with the new object.
|
||||
|
||||
@param obj The node with which to replace the object at given index in the stack.
|
||||
@param idx The index of the object to be replaced.
|
||||
*/
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||
|
||||
/**
|
||||
Returns the index of the given node in the stack.
|
||||
|
||||
@param node The node.
|
||||
@returns The index of the given node in the stack.
|
||||
*/
|
||||
- (NSUInteger)indexOfElement:(id)node;
|
||||
|
||||
/**
|
||||
Pushes the given element onto the stack.
|
||||
|
||||
@param element The element.
|
||||
*/
|
||||
- (void)pushElement:(HTMLElement *)element;
|
||||
|
||||
/**
|
||||
Removes the given element from the stack.
|
||||
|
||||
@param element The element.
|
||||
*/
|
||||
- (void)removeElement:(id)element;
|
||||
|
||||
/**
|
||||
Checks whether the given element is in the stack.
|
||||
|
||||
@param element The element.
|
||||
@returns `YES` if the element is in the stack, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)containsElement:(id)element;
|
||||
|
||||
/**
|
||||
Checks whether an element with the given tag name is in the stack.
|
||||
|
||||
@param tagname The element's tag name.
|
||||
@returns `YES` if such an element is in the stack, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)containsElementWithTagName:(NSString *)tagName;
|
||||
|
||||
/**
|
||||
Inserts the given element at the index into the stack.
|
||||
|
||||
@param element The element to insert.
|
||||
@param index The index at which the element should be inserted.
|
||||
*/
|
||||
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Replaces the element at the given index in the stack with the new element.
|
||||
|
||||
@param index The index of the element to be replaced.
|
||||
@param element The element with which to replace the element at given index in the stack.
|
||||
*/
|
||||
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
|
||||
|
||||
/**
|
||||
Pops current node from the stack.
|
||||
*/
|
||||
- (void)popCurrentNode;
|
||||
|
||||
/**
|
||||
Pops elements from the stack until an element with the given tag name is poped.
|
||||
|
||||
@param tagName The tag name.
|
||||
*/
|
||||
- (void)popElementsUntilElementPoppedWithTagName:(NSString *)tagName;
|
||||
|
||||
/**
|
||||
Pops elements from the stack until an element with any of the given tag names is poped.
|
||||
|
||||
@param tagNames The tag names.
|
||||
*/
|
||||
- (void)popElementsUntilAnElementPoppedWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
|
||||
/**
|
||||
Pops elements from the stack until the given element is poped.
|
||||
|
||||
@param element The element.
|
||||
*/
|
||||
- (void)popElementsUntilElementPopped:(HTMLElement *)element;
|
||||
|
||||
/**
|
||||
Pops elements from the stack until a template element is poped.
|
||||
*/
|
||||
- (void)popElementsUntilTemplateElementPopped;
|
||||
|
||||
/**
|
||||
Clears the stack to a table context
|
||||
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#clear-the-stack-back-to-a-table-context
|
||||
*/
|
||||
- (void)clearBackToTableContext;
|
||||
|
||||
/**
|
||||
Clears the stack to a table body context
|
||||
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#clear-the-stack-back-to-a-table-body-context
|
||||
*/
|
||||
- (void)clearBackToTableBodyContext;
|
||||
|
||||
/**
|
||||
Clears the stack to a table context
|
||||
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#clear-the-stack-back-to-a-table-row-context
|
||||
*/
|
||||
- (void)clearBackToTableRowContext;
|
||||
|
||||
/**
|
||||
Pops all nodes from the stack.
|
||||
*/
|
||||
- (void)popAll;
|
||||
|
||||
/**
|
||||
Methods for checking whether the stack contains elements in speccific scopes:
|
||||
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-the-specific-scope
|
||||
*/
|
||||
- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName;
|
||||
@@ -44,12 +169,45 @@
|
||||
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName;
|
||||
|
||||
/**
|
||||
Returns the furthest block after a given index.
|
||||
|
||||
@discussion The furthest block is the topmost node in the stack of open elements that is lower in the stack than
|
||||
formatting element, and is an element in the special category. This is used in the adoption agency algorithm:
|
||||
https://html.spec.whatwg.org/multipage/syntax.html#adoption-agency-algorithm
|
||||
|
||||
@param index The index.
|
||||
@returns The furthest block after index.
|
||||
*/
|
||||
- (HTMLElement *)furthestBlockAfterIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
Returns the count of elements in this stack.
|
||||
|
||||
@returns The elements count.
|
||||
*/
|
||||
- (NSUInteger)count;
|
||||
|
||||
/**
|
||||
Checks whether this stack is empty.
|
||||
|
||||
@returns `YES` if the stack is empty, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isEmpy;
|
||||
|
||||
/**
|
||||
Return an object enumerator over this stack.
|
||||
|
||||
@returns An enumerator
|
||||
*/
|
||||
- (NSEnumerator *)enumerator;
|
||||
|
||||
/**
|
||||
Return a reverse object enumerator over this stack.
|
||||
|
||||
@returns A reverse enumerator
|
||||
*/
|
||||
- (NSEnumerator *)reverseObjectEnumerator;
|
||||
|
||||
@end
|
||||
|
||||
@@ -6,27 +6,64 @@
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
|
||||
/**
|
||||
HTML Tag Token
|
||||
*/
|
||||
@interface HTMLTagToken : HTMLToken
|
||||
|
||||
/** @brief The tag name. */
|
||||
@property (nonatomic, copy) NSString *tagName;
|
||||
|
||||
/** @brief The tag's attributes. */
|
||||
@property (nonatomic, strong) HTMLOrderedDictionary *attributes;
|
||||
|
||||
/** @brief Flag whether this tag is self-closing. */
|
||||
@property (nonatomic, assign, getter = isSelfClosing) BOOL selfClosing;
|
||||
|
||||
/**
|
||||
Initializes a new tag token.
|
||||
|
||||
@param tagName The tag's name.
|
||||
@returns A new instance of a tag token.
|
||||
*/
|
||||
- (instancetype)initWithTagName:(NSString *)tagName;
|
||||
|
||||
/**
|
||||
Initializes a new tag token.
|
||||
|
||||
@param tagName The tag's name.
|
||||
@param attributes The tag's attributes.
|
||||
@returns A new instance of a tag token.
|
||||
*/
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes;
|
||||
|
||||
/**
|
||||
Appends the given string to this token's name.
|
||||
|
||||
@param string The string to append.
|
||||
*/
|
||||
- (void)appendStringToTagName:(NSString *)string;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
HTML Start Tag Token
|
||||
*/
|
||||
@interface HTMLStartTagToken : HTMLTagToken
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
HTML End Tag Token
|
||||
*/
|
||||
@interface HTMLEndTagToken : HTMLTagToken
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,12 +8,31 @@
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A HTML Text node
|
||||
*/
|
||||
@interface HTMLText : HTMLNode
|
||||
|
||||
/** @brief The text string. */
|
||||
@property (nonatomic, copy) NSMutableString *data;
|
||||
|
||||
/**
|
||||
Initializes a new HTML text node.
|
||||
|
||||
@param data The text string.
|
||||
@returns A new isntance of a HTML text node.
|
||||
*/
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
/**
|
||||
Appends the string to this text node.
|
||||
|
||||
@param string The string to append.
|
||||
*/
|
||||
- (void)appendString:(NSString *)string;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
@implementation HTMLText
|
||||
|
||||
|
||||
@@ -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
@@ -6,23 +6,45 @@
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
#import "HTMLTokenizerStates.h"
|
||||
|
||||
@class HTMLParser;
|
||||
|
||||
/**
|
||||
* HTML Tokenizer
|
||||
* https://html.spec.whatwg.org/multipage/syntax.html#tokenization
|
||||
*/
|
||||
|
||||
@class HTMLParser;
|
||||
|
||||
@interface HTMLTokenizer : NSEnumerator
|
||||
|
||||
/** @brief The underlying string with which this tokenizer was initialized. */
|
||||
@property (nonatomic, readonly) NSString *string;
|
||||
|
||||
/**
|
||||
The current tokenizer state.
|
||||
|
||||
@see HTMLTokenizerState
|
||||
*/
|
||||
@property (nonatomic, assign) HTMLTokenizerState state;
|
||||
|
||||
/**
|
||||
The associated HTML Parser instance.
|
||||
|
||||
@see HTMLParser
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) HTMLParser *parser;
|
||||
|
||||
/**
|
||||
Initializes a new Tokenizer with the given string.
|
||||
|
||||
@param string The HTML string
|
||||
@returns A new instance of the Tokenizer.
|
||||
*/
|
||||
- (instancetype)initWithString:(NSString *)string;
|
||||
|
||||
@end
|
||||
|
||||
@@ -852,7 +852,7 @@
|
||||
case LATIN_SMALL_LETTER_A ... LATIN_SMALL_LETTER_Z:
|
||||
_currentTagToken = [[HTMLEndTagToken alloc] initWithTagName:StringFromUniChar(character)];
|
||||
[_temporaryBuffer appendString:StringFromUniChar(character)];
|
||||
[self switchToState:HTMLTokenizerStateRCDATAEndTagName];
|
||||
[self switchToState:HTMLTokenizerStateRAWTEXTEndTagName];
|
||||
break;
|
||||
default:
|
||||
[self switchToState:HTMLTokenizerStateRAWTEXT];
|
||||
@@ -1431,7 +1431,6 @@
|
||||
return;
|
||||
case LATIN_CAPITAL_LETTER_A ... LATIN_CAPITAL_LETTER_Z:
|
||||
[self appendToCurrentAttributeName:StringFromUniChar(character + 0x0020)];
|
||||
[self switchToState:HTMLTokenizerStateAttributeName];
|
||||
return;
|
||||
case NULL_CHAR:
|
||||
[self emitParseError:@"NULL character (0x0000) in Attribute Name state"];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user