Compare commits
241 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d756fc4298 | |||
| 7c2335184e | |||
| 0c6084ba73 | |||
| 5201860073 | |||
| cf21b88eb1 | |||
| 7617fceff1 | |||
| 9dd3c70fc3 | |||
| fe2a441171 | |||
| 7c32b5f219 | |||
| bf0d8560b0 | |||
| ebc6f7a097 | |||
| d29a70dfec | |||
| b8f56ca97d | |||
| 0f954d5d56 | |||
| ac5089d69b | |||
| c9c36397e0 | |||
| 923e271429 | |||
| 8546168324 | |||
| da765169fa | |||
| ec6d03ee57 | |||
| 7944549bd7 | |||
| bb6a97984b | |||
| 0aff9cf787 | |||
| b7bf9d2b04 | |||
| 12af838fa2 | |||
| 14b8b5390f | |||
| a5599c4163 | |||
| c0ac25645d | |||
| fa431550d7 | |||
| 70c6aee814 | |||
| 7309b97b92 | |||
| 7ffb8e5ca0 | |||
| 682b19c242 | |||
| 16ed846e65 | |||
| 820b3ef091 | |||
| fb7ea25b1f | |||
| 5302c16600 | |||
| 2dfdeb22ca | |||
| 135715b606 | |||
| f8e6a39171 | |||
| 20cf2dc4d0 | |||
| 0921ebad8a | |||
| c63e2cb8fd | |||
| 3deeb0138f | |||
| b358a08f30 | |||
| 84960e71ee | |||
| 7b028a2819 | |||
| 5aa77eac9b | |||
| c1cd1c0831 | |||
| 1101556cfe | |||
| 839a724432 | |||
| 300705cb63 | |||
| 1963c010f9 | |||
| 89ef6bc101 | |||
| 9069923e82 | |||
| c16c647394 | |||
| 27b8eaac67 | |||
| 956c46ec45 | |||
| cb7e5d519d | |||
| 4e1959bd81 | |||
| f0d9817c68 | |||
| fee2d14ffc | |||
| 4291840a53 | |||
| 4b7c260738 | |||
| 4a41b89ecf | |||
| d4ff2e3869 | |||
| 6dee6c1e74 | |||
| 20ebd8c603 | |||
| c002299247 | |||
| bb6d7ccc67 | |||
| bf655c7ea2 | |||
| 6cd03b4eed | |||
| cc0734e470 | |||
| eca6098361 | |||
| 2cd5098d8a | |||
| 514d23f7a0 | |||
| 2446da3a6d | |||
| 644c180f81 | |||
| 47ec0867a8 | |||
| 1cd1a915d3 | |||
| 8379cee44f | |||
| 4094f51458 | |||
| 57c931aece | |||
| 76b379448b | |||
| 49bdfa018e | |||
| 746ef2ea3b | |||
| 653f6cdf7e | |||
| d9670cddf4 | |||
| 30389c5010 | |||
| 14dfc0b854 | |||
| b693a60358 | |||
| d35e6c4d91 | |||
| c353512a65 | |||
| aaee85909b | |||
| 8d3f24ef8b | |||
| b652935739 | |||
| 601c83a51f | |||
| 774ac35bbb | |||
| 9651d12fd0 | |||
| 0899a4013d | |||
| 22de41d912 | |||
| 2f47e733bd | |||
| 36f5df015a | |||
| bbc5467b33 | |||
| 7ef19f5e20 | |||
| a102f5cd7d | |||
| 7a40e8bafd | |||
| 40b057b569 | |||
| a7ace9929f | |||
| 32b674f786 | |||
| e9e3f04136 | |||
| 44f1bc6946 | |||
| ef36e674a4 | |||
| b8f1123b86 | |||
| 17a2e375e0 | |||
| b5470a61c3 | |||
| fb60759492 | |||
| f21cbf2a5e | |||
| 50c48e589a | |||
| bb9987636b | |||
| 60b086f72e | |||
| f1fd0f3ef6 | |||
| 5bb7122d7e | |||
| 356709a096 | |||
| 963a2b9c09 | |||
| 9926179a62 | |||
| cb9886cbf7 | |||
| 717f62a5f1 | |||
| f5476e0c4c | |||
| b9099a26ed | |||
| b57e27137a | |||
| dbe9c575a1 | |||
| 17961b94a4 | |||
| d6d1e77471 | |||
| 987d37e042 | |||
| 6d7e9bca1b | |||
| 0b8b18b675 | |||
| 325ca47587 | |||
| 05c6b9d294 | |||
| 76753b1d95 | |||
| daf71a01f3 | |||
| da2344e5cf | |||
| 049d7b7148 | |||
| c727ce6643 | |||
| 1a5238863a | |||
| 46e36e9c24 | |||
| 1919992b0e | |||
| 28ecfb0278 | |||
| 65666a13c0 | |||
| e5237a68c4 | |||
| ea63b38379 | |||
| 542bc26ff9 | |||
| 718e43ff9c | |||
| 472d59546a | |||
| bdaddc0938 | |||
| a38d1e6484 | |||
| f53ce3464d | |||
| bc94f91c6e | |||
| 1e5517f403 | |||
| 925848404b | |||
| fe46b106b2 | |||
| c7edbd3e14 | |||
| f1b1f523b8 | |||
| 51c94d49bd | |||
| 395ce5e981 | |||
| e48ccf42ca | |||
| 0ac49f2a0f | |||
| 07c72f65d9 | |||
| 8602ad5a7b | |||
| 12fc972602 | |||
| b66f5264ba | |||
| 1042571c57 | |||
| c294802199 | |||
| ea17eeef9d | |||
| 9795172c9c | |||
| 0319536a6c | |||
| 3f74fcaa7b | |||
| 78b3069a25 | |||
| dfd01b1908 | |||
| bdacc629eb | |||
| 734c185137 | |||
| de6c215550 | |||
| b7b29d2af5 | |||
| 15acfa06f6 | |||
| 7506ed8cae | |||
| 3898ae243c | |||
| 6da3f7bd49 | |||
| fb04d49693 | |||
| 4d132af99f | |||
| e4a0669cf2 | |||
| e60fe36e25 | |||
| 1f6f9d843e | |||
| 5c2f6527a7 | |||
| e4b57f3333 | |||
| 6be1bd702f | |||
| ef672e1dbb | |||
| 0d95ba33d1 | |||
| 9df89043d3 | |||
| 6fdc7ff85b | |||
| c3c7c34310 | |||
| fb2952062d | |||
| 49799f8bcd | |||
| 9bd9dfbf6b | |||
| 8f0a6d133e | |||
| cfb1707fc4 | |||
| 733780091d | |||
| 8391a01147 | |||
| 6ed0238a89 | |||
| 80299ad9e0 | |||
| ffc9e629f2 | |||
| 394a1a53c4 | |||
| 14289f96e5 | |||
| c57117670f | |||
| a08fb84be9 | |||
| 9fd5d34244 | |||
| 2fb871a7ff | |||
| 46072a09cc | |||
| de2d35fbe6 | |||
| 6ae00f40e1 | |||
| 76a7930e7d | |||
| 8dc5d0ea50 | |||
| 0e9956efc0 | |||
| d5888e68a7 | |||
| 02177e7fac | |||
| af76666f3c | |||
| 73dc32aae9 | |||
| 64cba55677 | |||
| 214722c0bf | |||
| b8592630d0 | |||
| a641c64f74 | |||
| e20531925a | |||
| 024ab12242 | |||
| c2a11e6f97 | |||
| 800336d317 | |||
| a751cec215 | |||
| 87a476e523 | |||
| 7e42fd1e08 | |||
| 7d1b94219d | |||
| 29bd89b808 | |||
| 45a8e25028 | |||
| 33bb1e39a4 |
@@ -0,0 +1,5 @@
|
||||
codecov:
|
||||
branch: develop
|
||||
|
||||
ignore:
|
||||
- "Tests/"
|
||||
+3
-1
@@ -40,5 +40,7 @@ Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
Carthage/Checkouts
|
||||
Carthage/Build
|
||||
|
||||
Carthage/Build
|
||||
# Jazzy
|
||||
docs/
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "HTMLKitTests/html5lib-tests"]
|
||||
[submodule "html5lib-tests"]
|
||||
path = Tests/html5lib-tests
|
||||
url = https://github.com/html5lib/html5lib-tests.git
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
module: HTMLKit
|
||||
module_version: 2.1.5
|
||||
author: Iskandar Abudiab
|
||||
author_url: https://twitter.com/iabudiab
|
||||
github_url: https://github.com/iabudiab/HTMLKit
|
||||
github_file_prefix: https://github.com/iabudiab/HTMLKit/tree/master
|
||||
output: docs
|
||||
umbrella_header: Sources/include/HTMLKit.h
|
||||
clean: true
|
||||
objc: true
|
||||
skip_undocumented: true
|
||||
|
||||
custom_categories:
|
||||
- name: Parsing
|
||||
children:
|
||||
- HTMLParser
|
||||
|
||||
- name: DOM
|
||||
children:
|
||||
- HTMLNamespace
|
||||
- HTMLNode
|
||||
- HTMLNodeType
|
||||
- HTMLElement
|
||||
- HTMLDocument
|
||||
- HTMLDocumentReadyState
|
||||
- HTMLQuirksMode
|
||||
- HTMLDocumentType
|
||||
- HTMLDocumentFragment
|
||||
- HTMLDocumentPosition
|
||||
- HTMLCharacterData
|
||||
- HTMLText
|
||||
- HTMLComment
|
||||
- HTMLTemplate
|
||||
- HTMLDOMTokenList
|
||||
- HTMLRange
|
||||
|
||||
- name: Iteration & Filtering
|
||||
children:
|
||||
- HTMLNodeIterator
|
||||
- HTMLNodeFilter
|
||||
- HTMLNodeFilterShowOptions
|
||||
- HTMLNodeFilterValue
|
||||
- HTMLNodeFilterBlock
|
||||
- HTMLSelectorNodeFilter
|
||||
- HTMLTreeWalker
|
||||
|
||||
- name: Structures
|
||||
children:
|
||||
- HTMLOrderedDictionary
|
||||
- CSSNthExpression
|
||||
|
||||
- name: CSS Selectors Implementation
|
||||
children:
|
||||
- CSSSelector
|
||||
- CSSSelectorParser
|
||||
- CSSTypeSelector
|
||||
- CSSAttributeSelector
|
||||
- CSSNthExpressionParser
|
||||
- CSSNthExpressionSelector
|
||||
- CSSPseudoClassSelector
|
||||
- CSSPseudoFunctionSelector
|
||||
- CSSSelectorBlock
|
||||
- CSSCombinatorSelector
|
||||
- CSSCompoundSelector
|
||||
|
||||
- name: Typed Selectors and Extensions
|
||||
children:
|
||||
- adjacentSiblingSelector
|
||||
- allOf
|
||||
- anyOf
|
||||
- attributeSelector
|
||||
- buttonSelector
|
||||
- checkboxSelector
|
||||
- checkedSelector
|
||||
- childOfElementSelector
|
||||
- classSelector
|
||||
- descendantOfElementSelector
|
||||
- disabledSelector
|
||||
- emptySelector
|
||||
- enabledSelector
|
||||
- eqSelector
|
||||
- evenSlector
|
||||
- fileSelector
|
||||
- firstChildSelector
|
||||
- firstOfTypeSelector
|
||||
- generalSiblingSelector
|
||||
- gtSelector
|
||||
- has
|
||||
- hasAttributeSelector
|
||||
- headerSelector
|
||||
- idSelector
|
||||
- imageSelector
|
||||
- inputSelector
|
||||
- lastChildSelector
|
||||
- lastOfTypeSelector
|
||||
- linkSelector
|
||||
- ltSelector
|
||||
- namedBlockSelector
|
||||
- namedPseudoSelector
|
||||
- not
|
||||
- nthChildSelector
|
||||
- nthLastChildSelector
|
||||
- nthLastOfTypeSelector
|
||||
- nthOfTypeSelector
|
||||
- oddSelector
|
||||
- onlyChildSelector
|
||||
- onlyOfTypeSelector
|
||||
- optionalSelector
|
||||
- parentSelector
|
||||
- passwordSelector
|
||||
- radioSelector
|
||||
- requiredSelector
|
||||
- resetSelector
|
||||
- rootSelector
|
||||
- submitSelector
|
||||
- textSelector
|
||||
- typeSelector
|
||||
- universalSelector
|
||||
|
||||
- name: Categories
|
||||
children:
|
||||
- NSCharacterSet(HTMLKit)
|
||||
- NSString(HTMLKit)
|
||||
+20
-17
@@ -1,5 +1,5 @@
|
||||
language: objective-c
|
||||
osx_image: xcode7.3
|
||||
osx_image: xcode9
|
||||
|
||||
branches:
|
||||
except:
|
||||
@@ -14,29 +14,32 @@ env:
|
||||
- LANG=en_US.UTF-8
|
||||
- WORKSPACE=HTMLKit.xcworkspace
|
||||
- IOS_FRAMEWORK_SCHEME=HTMLKit-iOS
|
||||
- OSX_FRAMEWORK_SCHEME=HTMLKit-OSX
|
||||
- MACOS_FRAMEWORK_SCHEME=HTMLKit-macOS
|
||||
- WATCHOS_FRAMEWORK_SCHEME="HTMLKit-watchOS"
|
||||
- TVOS_FRAMEWORK_SCHEME="HTMLKit-tvOS"
|
||||
- IOS_SDK=iphonesimulator9.3
|
||||
- OSX_SDK=macosx10.11
|
||||
- WATCHOS_SDK=watchsimulator2.2
|
||||
- TVOS_SDK=appletvsimulator9.2
|
||||
- IOS_SDK=iphonesimulator11.0
|
||||
- MACOS_SDK=macosx10.13
|
||||
- WATCHOS_SDK=watchsimulator4.0
|
||||
- TVOS_SDK=appletvsimulator11.0
|
||||
matrix:
|
||||
- DESTINATION="OS=9.0,name=iPhone 6" SIMULATOR="iPhone 6 (9.0)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=9.1,name=iPhone 6 Plus" SIMULATOR="iPhone 6 Plus (9.1)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=9.2,name=iPhone 6S" SIMULATOR="iPhone 6S (9.2)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=9.3,name=iPhone 6S Plus" SIMULATOR="iPhone 6S Plus (9.3)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="arch=x86_64" SIMULATOR="" SCHEME="$OSX_FRAMEWORK_SCHEME" SDK="$OSX_SDK"
|
||||
- DESTINATION="OS=2.2,name=Apple Watch - 42mm" SIMULATOR="Apple Watch - 42mm (2.2)" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK"
|
||||
- DESTINATION="OS=9.2,name=Apple TV 1080p" SIMULATOR="Apple TV 1080p (9.2)" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
|
||||
- DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK"
|
||||
- DESTINATION="OS=9.3,name=iPhone 6s Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=10.3.1,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=11.0,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=3.2,name=Apple Watch Series 2 - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK"
|
||||
- DESTINATION="OS=4.0,name=Apple Watch Series 3 - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK"
|
||||
- DESTINATION="OS=10.2,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
|
||||
- DESTINATION="OS=11.0,name=Apple TV 4K" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
|
||||
|
||||
script:
|
||||
- set -o pipefail
|
||||
- xcodebuild -version
|
||||
- xcodebuild -showsdks
|
||||
- SIMULATOR_ID=$(xcrun instruments -s devices | grep -io "$SIMULATOR \[.*\]" | grep -o "\[.*\]" | sed "s/^\[\(.*\)\]$/\1/")
|
||||
- open -b com.apple.iphonesimulator --args -CurrentDeviceUDID $SIMULATOR_ID
|
||||
- xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO clean build | xcpretty -c
|
||||
- travis_retry xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO clean build | xcpretty -c
|
||||
- if [ "$SDK" != "$WATCHOS_SDK" ]; then
|
||||
xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c;
|
||||
travis_retry xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES test | xcpretty -c;
|
||||
fi
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
|
||||
+198
-7
@@ -1,5 +1,202 @@
|
||||
# Change Log
|
||||
|
||||
## [2.1.5](https://github.com/iabudiab/HTMLKit/releases/tag/2.1.5)
|
||||
|
||||
Released on 2018.07.16
|
||||
|
||||
### Fixes
|
||||
|
||||
- Parser would handle foreign attributes incorrectly (issue #30)
|
||||
|
||||
|
||||
## [2.1.4](https://github.com/iabudiab/HTMLKit/releases/tag/2.1.4)
|
||||
|
||||
Released on 2018.05.01
|
||||
|
||||
### Fixes
|
||||
|
||||
- `gt(n)`, `lt(n)` and `eq(n)` selectors would select wrong elements for the zero-index (issue #25)
|
||||
|
||||
|
||||
## [2.1.3](https://github.com/iabudiab/HTMLKit/releases/tag/2.1.3)
|
||||
|
||||
Released on 2018.03.21
|
||||
|
||||
### Fixes
|
||||
|
||||
- `HTMLElement` clone would return an immutable dictionary for attributes (issue #20)
|
||||
- Fixed by @CRivlaldo in PR #24
|
||||
- `HTMLNodeFilterBlock` would behave differently on simulator and device (issue #22)
|
||||
- Fixed by @CRivlaldo in PR #23
|
||||
|
||||
|
||||
## [2.1.2](https://github.com/iabudiab/HTMLKit/releases/tag/2.1.2)
|
||||
|
||||
Released on 2017.11.6
|
||||
|
||||
### Fixes
|
||||
|
||||
- `HTMLText` serialization (issue #16)
|
||||
- `HTMLElement` attribute value serialization (issue #17)
|
||||
|
||||
|
||||
## [2.1.1](https://github.com/iabudiab/HTMLKit/releases/tag/2.1.1)
|
||||
|
||||
Released on 2017.10.13
|
||||
|
||||
### Hotfix
|
||||
|
||||
- Fixed documentation comments
|
||||
- Should fix CocoaDocs generation and percentage
|
||||
|
||||
|
||||
## [2.1.0](https://github.com/iabudiab/HTMLKit/releases/tag/2.1.0)
|
||||
|
||||
Released on 2017.10.12
|
||||
|
||||
### Added
|
||||
|
||||
- Standarized tokenizer error codes:
|
||||
- [whatwg/html#2701](https://github.com/whatwg/html/pull/2701)
|
||||
- [html5lib/html5lib-tests#92](https://github.com/html5lib/html5lib-tests/pull/92)
|
||||
|
||||
### Updated
|
||||
|
||||
- Project for Xcode 9
|
||||
- Travis config for iOS 11.0, macOS 10.13, tvOS 11.0 and watchOS 4.0
|
||||
- Updated HTML5Lib-Tests submodule (cbafeba)
|
||||
|
||||
## [2.0.6](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.6)
|
||||
|
||||
Released on 2017.05.02
|
||||
|
||||
### Added
|
||||
|
||||
- Memory consumption improvements (issue #10)
|
||||
- Allocate `childNodes` collection in `HTMLNode` only when inserting child nodes
|
||||
- Replace `NSStringFromSelector` calls with constants in `HTMLNode` validations
|
||||
- Improve `reverseObjectEnumerator` usage while parsing HTML
|
||||
- Rewrite internal logic of the `HTMLStackOfOpenElements` to prevent excessive allocations
|
||||
|
||||
|
||||
## [2.0.5](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.5)
|
||||
|
||||
Released on 2017.04.19
|
||||
|
||||
### Fixed
|
||||
|
||||
- Xcode 8.3 issue with modulemaps
|
||||
- Temporary workaround (renamed modulemap file)
|
||||
- Memory Leaks in `CSSInputStream`
|
||||
|
||||
### Added
|
||||
|
||||
- Minor memory consumption improvements
|
||||
- Collections for child nodes or attributes of HTML Nodes or Elements are allocated lazily
|
||||
- Underyling data string of `CharacterData` is allocated on first access
|
||||
- Autorelease pool for the main `HTMLTokenizer` loop
|
||||
|
||||
|
||||
## [2.0.4](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.4)
|
||||
|
||||
Released on 2017.04.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- Testing with Swift 3.1
|
||||
- Fixed by @tali in PR #8
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `HTMLRange` initializers with typo
|
||||
- `initWithDowcument:startContainer:startOffset:endContainer:endOffset:`
|
||||
|
||||
|
||||
## [2.0.3](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.3)
|
||||
|
||||
Released on 2017.03.6
|
||||
|
||||
### Fixed
|
||||
|
||||
- Compilation for Swift 3.1
|
||||
- Fixed by @tali in PR #6
|
||||
|
||||
|
||||
## [2.0.2](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.2)
|
||||
|
||||
Released on 2017.02.26
|
||||
|
||||
### Fixed
|
||||
|
||||
- Retain cycles in `HTMLNodeIterator` (issue #4)
|
||||
- Retain cycles in `HTMLRange` (issue #5)
|
||||
- The layout of `HTMLKit` tests module for Swift Package Manager
|
||||
|
||||
|
||||
## [2.0.1](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.1)
|
||||
|
||||
Released on 2017.02.20
|
||||
|
||||
### Hotifx
|
||||
|
||||
- Set `INSTALL_PATH` and `DYLIB_INSTALL_NAME_BASE` to `@rpath` for macOS target
|
||||
- This fixes embedding `HTMLKit` in a Cocoa application
|
||||
|
||||
|
||||
## [2.0.0](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.0)
|
||||
|
||||
Released on 2017.02.11
|
||||
|
||||
### Spec Change
|
||||
|
||||
- Make `<menuitem>` parse like an unkonwn element. See:
|
||||
- [whatwg/html#2319](https://github.com/whatwg/html/pull/2319)
|
||||
- [html5lib/html5lib-tests#88](https://github.com/html5lib/html5lib-tests/pull/88)
|
||||
|
||||
### Updated
|
||||
|
||||
- Updated HTML5Lib-Tests submodule (13f1805)
|
||||
|
||||
|
||||
## [1.1.0](https://github.com/iabudiab/HTMLKit/releases/tag/1.1.0)
|
||||
|
||||
Released on 2017.01.14
|
||||
|
||||
### Added
|
||||
|
||||
- `DOM Ranges` implementation ([spec](https://dom.spec.whatwg.org/#ranges))
|
||||
- `HTMLChatacterData` as base class for `HTMLText` & `HTMLComment`
|
||||
- `HTMLText` and `HTMLComment` no longer extend `HTMLNode` directly
|
||||
- `splitText` implementation for `HTMLText` nodes
|
||||
- `index` property for `HTMLNode`
|
||||
- `cloneNodeDeep` method for `HTMLNode`
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `appendString` method in `HTMLText` in favor of `appendData` from the supperclass `HTMLCharacterData`
|
||||
|
||||
|
||||
## [1.0.0](https://github.com/iabudiab/HTMLKit/releases/tag/1.0.0)
|
||||
|
||||
Released on 2016.09.28
|
||||
|
||||
### Added
|
||||
|
||||
- Jazzy configuration file
|
||||
- Example HTMLKit project
|
||||
|
||||
### Updated
|
||||
|
||||
- Project for Xcode 8
|
||||
- Playground syntax for Swift 3
|
||||
- Travis config for iOS 10.0, macOS 10.12, tvOS 10.0 and watchOS 3.0
|
||||
- Deployment targets to macOS 10.9, iOS 9.0, tvOS 9.0 and watchOS 2.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nullability annotation in `CSSSelectorParser` class
|
||||
- Missing lightweight generics in `HTMLParser`, `HTMLNode` & `HTMLElement`
|
||||
|
||||
## [0.9.4](https://github.com/iabudiab/HTMLKit/releases/tag/0.9.4)
|
||||
|
||||
Released on 2016.09.03
|
||||
@@ -40,16 +237,12 @@ This release passes all tokenizer and tree construction html5lib-tests as of 201
|
||||
- `<isindex>` is completely removed from the spec now, therefore it is dropped from the implementation
|
||||
- `Tokenizer` and `Tree-Construction` tests are now generated dynamically
|
||||
- Test failures are collected by a `XCTestObservation` for better reporting
|
||||
|
||||
- `<isindex>` is completely removed from the spec now, therefore it is dropped from the implementation
|
||||
- `Tokenizer` and `Tree-Construction` tests are now generated dynamically
|
||||
- Test failures are collected by a `XCTestObservation` for better reporting
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Parser now checks the qualified name instead of the local name when handling elements in the `MathML` and `SVG` namespaces
|
||||
|
||||
|
||||
## [0.9.1](https://github.com/iabudiab/HTMLKit/releases/tag/0.9.1)
|
||||
|
||||
Released on 2016.01.29
|
||||
@@ -59,7 +252,6 @@ Released on 2016.01.29
|
||||
- Travis-CI integration.
|
||||
- CocoaPods spec.
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Warnings are treated as errors.
|
||||
@@ -91,7 +283,6 @@ This is the first public release of `HTMLKit`.
|
||||
- Unused namespaces.
|
||||
- Historical node types.
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- `lt`, `gt` & `eq` CSS Selectors method declarations.
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
629A63CD1D9AFE0E0089679F /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A63CC1D9AFE0E0089679F /* main.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
629A63C71D9AFE0E0089679F /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
629A63C91D9AFE0E0089679F /* HTMLKitExample */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = HTMLKitExample; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
629A63CC1D9AFE0E0089679F /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
629A63C61D9AFE0E0089679F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
629A63C01D9AFE0E0089679F = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
629A63CB1D9AFE0E0089679F /* HTMLKitExample */,
|
||||
629A63CA1D9AFE0E0089679F /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
629A63CA1D9AFE0E0089679F /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
629A63C91D9AFE0E0089679F /* HTMLKitExample */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
629A63CB1D9AFE0E0089679F /* HTMLKitExample */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
629A63CC1D9AFE0E0089679F /* main.swift */,
|
||||
);
|
||||
path = HTMLKitExample;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
629A63C81D9AFE0E0089679F /* HTMLKitExample */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 629A63D01D9AFE0E0089679F /* Build configuration list for PBXNativeTarget "HTMLKitExample" */;
|
||||
buildPhases = (
|
||||
629A63C51D9AFE0E0089679F /* Sources */,
|
||||
629A63C61D9AFE0E0089679F /* Frameworks */,
|
||||
629A63C71D9AFE0E0089679F /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = HTMLKitExample;
|
||||
productName = HTMLKitExample;
|
||||
productReference = 629A63C91D9AFE0E0089679F /* HTMLKitExample */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
629A63C11D9AFE0E0089679F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0800;
|
||||
LastUpgradeCheck = 0900;
|
||||
ORGANIZATIONNAME = iabudiab;
|
||||
TargetAttributes = {
|
||||
629A63C81D9AFE0E0089679F = {
|
||||
CreatedOnToolsVersion = 8.0;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 629A63C41D9AFE0E0089679F /* Build configuration list for PBXProject "HTMLKitExample" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 629A63C01D9AFE0E0089679F;
|
||||
productRefGroup = 629A63CA1D9AFE0E0089679F /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
629A63C81D9AFE0E0089679F /* HTMLKitExample */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
629A63C51D9AFE0E0089679F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
629A63CD1D9AFE0E0089679F /* main.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
629A63CE1D9AFE0E0089679F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
629A63CF1D9AFE0E0089679F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
629A63D11D9AFE0E0089679F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
629A63D21D9AFE0E0089679F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
629A63C41D9AFE0E0089679F /* Build configuration list for PBXProject "HTMLKitExample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
629A63CE1D9AFE0E0089679F /* Debug */,
|
||||
629A63CF1D9AFE0E0089679F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
629A63D01D9AFE0E0089679F /* Build configuration list for PBXNativeTarget "HTMLKitExample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
629A63D11D9AFE0E0089679F /* Debug */,
|
||||
629A63D21D9AFE0E0089679F /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 629A63C11D9AFE0E0089679F /* Project object */;
|
||||
}
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:HTMLKitExample.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "629A63C81D9AFE0E0089679F"
|
||||
BuildableName = "HTMLKitExample"
|
||||
BlueprintName = "HTMLKitExample"
|
||||
ReferencedContainer = "container:HTMLKitExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "629A63C81D9AFE0E0089679F"
|
||||
BuildableName = "HTMLKitExample"
|
||||
BlueprintName = "HTMLKitExample"
|
||||
ReferencedContainer = "container:HTMLKitExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "629A63C81D9AFE0E0089679F"
|
||||
BuildableName = "HTMLKitExample"
|
||||
BlueprintName = "HTMLKitExample"
|
||||
ReferencedContainer = "container:HTMLKitExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "629A63C81D9AFE0E0089679F"
|
||||
BuildableName = "HTMLKitExample"
|
||||
BlueprintName = "HTMLKitExample"
|
||||
ReferencedContainer = "container:HTMLKitExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,167 @@
|
||||
//
|
||||
// main.swift
|
||||
// HTMLKitExample
|
||||
//
|
||||
// Created by Iska on 27/09/16.
|
||||
// Copyright © 2016 iabudiab. All rights reserved.
|
||||
//
|
||||
|
||||
import HTMLKit
|
||||
|
||||
// Simple scraper that is able to load a page, query via CSS Selectors, and following links
|
||||
class Scraper {
|
||||
|
||||
enum ScrapingError: Error {
|
||||
case DocumentNotLoaded
|
||||
case ElementNotFound(String)
|
||||
case InvalidAnchorUrl(String)
|
||||
case CouldNotLoadPage(URL)
|
||||
}
|
||||
|
||||
private var url: URL
|
||||
private(set) var document: HTMLDocument?
|
||||
|
||||
init(url: URL) {
|
||||
self.url = url
|
||||
self.document = nil
|
||||
}
|
||||
|
||||
func load() throws {
|
||||
try loadDocument(at: url)
|
||||
}
|
||||
|
||||
func listElements(matching selector: CSSSelector) throws -> [HTMLElement] {
|
||||
guard let document = document else {
|
||||
throw ScrapingError.DocumentNotLoaded
|
||||
}
|
||||
|
||||
return document.elements(matching: selector)
|
||||
}
|
||||
|
||||
func followLink(matchingSelector selector: CSSSelector) throws {
|
||||
guard let document = document else {
|
||||
throw ScrapingError.DocumentNotLoaded
|
||||
}
|
||||
|
||||
guard let link = document.firstElement(matching: selector) else {
|
||||
throw ScrapingError.ElementNotFound(selector.debugDescription)
|
||||
}
|
||||
|
||||
guard let targetUrl = URL(string: link["href"], relativeTo: url) else {
|
||||
throw ScrapingError.InvalidAnchorUrl(link["href"])
|
||||
}
|
||||
|
||||
try loadDocument(at: targetUrl)
|
||||
}
|
||||
|
||||
private func loadDocument(at url: URL) throws {
|
||||
guard let content = try? String(contentsOf: url) else {
|
||||
throw ScrapingError.CouldNotLoadPage(url)
|
||||
}
|
||||
document = HTMLDocument(string: content)
|
||||
}
|
||||
}
|
||||
|
||||
// A custom block-based selector, that matches only elements having the given text content:
|
||||
// i.e. textContentSelector("Hello") will match <p>Hello</p> and <a href='example.com'>Hello</a>
|
||||
// but wont match <div>World</div> or <p>Hello there</p>
|
||||
func textContentSelector(text: String) -> CSSSelector {
|
||||
return namedBlockSelector("[@textContent='\(text)']") { (element) -> Bool in
|
||||
return element.textContent == text
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a typed-selector matching an anchor element that has the given
|
||||
// text content.
|
||||
func anchorElement(havingContent: String) -> CSSSelector {
|
||||
return allOf(
|
||||
[
|
||||
typeSelector("a"),
|
||||
textContentSelector(text: havingContent)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// Helper function to print the content of a github repository file content
|
||||
func printRepositoryFile(element: HTMLElement) {
|
||||
|
||||
// A node iterator filter that iterates only <td> elements of class "content" i.e. <td class='content'>
|
||||
let contentIterator = element.nodeIterator(showOptions: .element) { (node) -> HTMLNodeFilterValue in
|
||||
guard let element = node as? HTMLElement else { return .reject }
|
||||
|
||||
if element.tagName == "td" && element["class"] == "content" {
|
||||
return .accept
|
||||
}
|
||||
|
||||
return .reject
|
||||
}
|
||||
|
||||
for td in contentIterator {
|
||||
// The cast is necessary because Swift3 wont import the generics info of the NSEnumerator class
|
||||
// i.e. the nextObject() function alwasy has the following signature 'func nextObject() -> Any?'
|
||||
let title = (td as AnyObject).textContent.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
print("- \(title)")
|
||||
}
|
||||
}
|
||||
|
||||
let htmlKitUrl = URL(string: "https://github.com/iabudiab/HTMLKit")!
|
||||
let scraper = Scraper(url: htmlKitUrl)
|
||||
|
||||
do {
|
||||
// Load the page
|
||||
try scraper.load()
|
||||
|
||||
// Parse the selector
|
||||
let repositoryContent = try CSSSelectorParser.parseSelector("[role='main'] .repository-content > .file-wrap > .files tr.js-navigation-item")
|
||||
|
||||
// Query matching elements
|
||||
let files = try scraper.listElements(matching: repositoryContent)
|
||||
|
||||
print("HTMLKit repositroy root:")
|
||||
files.forEach(printRepositoryFile)
|
||||
} catch let error {
|
||||
print(error)
|
||||
}
|
||||
|
||||
do {
|
||||
// Follow some links
|
||||
try scraper.followLink(matchingSelector: anchorElement(havingContent: "Sources"))
|
||||
try scraper.followLink(matchingSelector: anchorElement(havingContent: "HTMLEOFToken.m"))
|
||||
|
||||
// The following selector: "[role='main'] div.file table.js-file-line-container td:nth-child(2)"
|
||||
// can be defined in type-safe manner:
|
||||
let selector = allOf([
|
||||
descendantOfElementSelector(
|
||||
attributeSelector(.exactMatch, "role", "main")
|
||||
),
|
||||
descendantOfElementSelector(
|
||||
allOf([
|
||||
typeSelector("div"),
|
||||
classSelector("file")
|
||||
])
|
||||
),
|
||||
descendantOfElementSelector(
|
||||
allOf([
|
||||
typeSelector("table"),
|
||||
classSelector("js-file-line-container")
|
||||
])
|
||||
),
|
||||
typeSelector("td"),
|
||||
nthChildSelector(
|
||||
CSSNthExpressionMake(0, 2)
|
||||
)
|
||||
])
|
||||
|
||||
// Query matching elements
|
||||
let elements = try scraper.listElements(matching: selector)
|
||||
|
||||
// This will print the source code for the "HTMLEOFToken.m" file under this url:
|
||||
// https://github.com/iabudiab/HTMLKit/blob/master/Sources/HTMLEOFToken.m
|
||||
|
||||
print("\nHTMLEOFToken:")
|
||||
elements.forEach {
|
||||
print($0.textContent)
|
||||
}
|
||||
} catch let error {
|
||||
print(error)
|
||||
}
|
||||
@@ -28,22 +28,22 @@ 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(
|
||||
paragraphs = document.elements(matching: typeSelector("p"))
|
||||
paragraphsOrHeaders = document.elements(matching:
|
||||
anyOf([
|
||||
typeSelector("p"), typeSelector("h1")
|
||||
])
|
||||
)
|
||||
|
||||
hasClassAttribute = document.elementsMatchingSelector(hasAttributeSelector("class"))
|
||||
greetings = document.elementsMatchingSelector(classSelector("greeting"))
|
||||
classNameStartsWith_de = document.elementsMatchingSelector(attributeSelector(.Begins, "class", "de"))
|
||||
hasClassAttribute = document.elements(matching: hasAttributeSelector("class"))
|
||||
greetings = document.elements(matching: classSelector("greeting"))
|
||||
classNameStartsWith_de = document.elements(matching: attributeSelector(.begins, "class", "de"))
|
||||
|
||||
hasAdjacentHeader = document.elementsMatchingSelector(adjacentSiblingSelector(typeSelector("h1")))
|
||||
hasAdjacentHeader = document.elementsMatchingSelector(generalSiblingSelector(typeSelector("h1")))
|
||||
hasAdjacentHeader = document.elementsMatchingSelector(generalSiblingSelector(typeSelector("p")))
|
||||
hasAdjacentHeader = document.elements(matching: adjacentSiblingSelector(typeSelector("h1")))
|
||||
hasAdjacentHeader = document.elements(matching: generalSiblingSelector(typeSelector("h1")))
|
||||
hasAdjacentHeader = document.elements(matching: generalSiblingSelector(typeSelector("p")))
|
||||
|
||||
nonParagraphChildOfDiv = document.elementsMatchingSelector(
|
||||
nonParagraphChildOfDiv = document.elements(matching:
|
||||
allOf([
|
||||
childOfElementSelector(typeSelector("div")),
|
||||
not(typeSelector("p"))
|
||||
@@ -54,17 +54,17 @@ nonParagraphChildOfDiv = document.elementsMatchingSelector(
|
||||
Here are more examples
|
||||
*/
|
||||
|
||||
let firstDivElement = document.firstElementMatchingSelector(typeSelector("div"))!
|
||||
let firstDivElement = document.firstElement(matching: 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)))
|
||||
secondChildOfDiv = firstDivElement.elements(matching: nthChildSelector(CSSNthExpression(an: 0, b: 2)))
|
||||
secondOfType = firstDivElement.elements(matching: nthOfTypeSelector(CSSNthExpression(an: 2, b: 0)))
|
||||
|
||||
|
||||
var notParagraphAndNotDiv = firstDivElement.querySelectorAll(":not(p):not(div)")
|
||||
notParagraphAndNotDiv = firstDivElement.elementsMatchingSelector(
|
||||
notParagraphAndNotDiv = firstDivElement.elements(matching:
|
||||
allOf([
|
||||
not(typeSelector("p")),
|
||||
not(typeSelector("div"))
|
||||
@@ -77,4 +77,4 @@ One more thing! You can also create your own selectors. You either subclass the
|
||||
let myAwesomeSelector = namedBlockSelector("myAwesomeSelector", { (element) -> Bool in
|
||||
return element.tagName != "p" && element.tagName != "div"
|
||||
})
|
||||
firstDivElement.elementsMatchingSelector(myAwesomeSelector)
|
||||
firstDivElement.elements(matching: myAwesomeSelector)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -9,7 +9,8 @@ import HTMLKit
|
||||
Given some HTML content
|
||||
*/
|
||||
|
||||
let htmlString = try! String(contentsOfURL: [#FileReference(fileReferenceLiteral: "HTMLKit.html")#])
|
||||
let htmlString = "<div><h1>HTMLKit</h1><p class='greeting'>Hello there!</p><p class='description'>This is a demo of HTMLKit</p></div>"
|
||||
htmlString
|
||||
|
||||
/*:
|
||||
You can parse it using the HTMLParser:
|
||||
|
||||
@@ -3,10 +3,20 @@
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=1&CharacterRangeLoc=318&EndingColumnNumber=26&EndingLineNumber=13&StartingColumnNumber=1&StartingLineNumber=13&Timestamp=472578634.909266"
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=581&EndingColumnNumber=26&EndingLineNumber=11&StartingColumnNumber=1&StartingLineNumber=11&Timestamp=496084829.845787"
|
||||
lockedSize = "{800, 186}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=27&CharacterRangeLoc=393&EndingColumnNumber=28&EndingLineNumber=20&StartingColumnNumber=1&StartingLineNumber=20&Timestamp=496084834.772773"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=18&CharacterRangeLoc=544&EndingColumnNumber=19&EndingLineNumber=27&StartingColumnNumber=1&StartingLineNumber=27&Timestamp=496084834.772773"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
|
||||
@@ -18,7 +18,7 @@ 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)
|
||||
var elements = parser.parseFragment(withContextElement: tableContext)
|
||||
|
||||
for element in elements {
|
||||
print(element.outerHTML)
|
||||
@@ -29,7 +29,7 @@ The same parser instance can be reusued:
|
||||
*/
|
||||
|
||||
let bodyContext = HTMLElement(tagName: "body")
|
||||
elements = parser.parseFragmentWithContextElement(bodyContext)
|
||||
elements = parser.parseFragment(withContextElement: bodyContext)
|
||||
|
||||
for element in elements {
|
||||
print(element.outerHTML)
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=453&EndingColumnNumber=26&EndingLineNumber=23&StartingColumnNumber=2&StartingLineNumber=23&Timestamp=472578641.006933"
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=455&EndingColumnNumber=26&EndingLineNumber=23&StartingColumnNumber=2&StartingLineNumber=23&Timestamp=495492881.371878"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=672&EndingColumnNumber=26&EndingLineNumber=34&StartingColumnNumber=2&StartingLineNumber=34&Timestamp=495493308.359844"
|
||||
lockedSize = "{775, 76}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
@@ -39,7 +39,7 @@
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=448&EndingColumnNumber=26&EndingLineNumber=21&StartingColumnNumber=2&StartingLineNumber=21&Timestamp=472579861.71569"
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=450&EndingColumnNumber=26&EndingLineNumber=21&StartingColumnNumber=2&StartingLineNumber=21&Timestamp=495492881.372527"
|
||||
lockedSize = "{775, 68}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
|
||||
@@ -21,7 +21,7 @@ description["content"] = "HTMLKit for iOS & OSX"
|
||||
Append nodes to the document
|
||||
*/
|
||||
let head = document.head!
|
||||
head.appendNode(description)
|
||||
head.append(description)
|
||||
document.innerHTML
|
||||
|
||||
let body = document.body!
|
||||
@@ -30,17 +30,17 @@ let nodes = [
|
||||
HTMLElement(tagName: "div", attributes: ["class": "green"]),
|
||||
HTMLElement(tagName: "div", attributes: ["class": "blue"])
|
||||
]
|
||||
body.appendNodes(nodes)
|
||||
body.append(nodes)
|
||||
body.innerHTML
|
||||
|
||||
/*:
|
||||
Enumerate child elements and perform DOM manipulation
|
||||
*/
|
||||
body.enumerateChildElementsUsingBlock { (element, index, stop) -> Void in
|
||||
body.enumerateChildElements { (element, index, stop) -> Void in
|
||||
if element.tagName == "div" {
|
||||
let lorem = HTMLElement(tagName: "p")
|
||||
lorem.textContent = "Lorem ipsum: \(index)"
|
||||
element.appendNode(lorem)
|
||||
element.append(lorem)
|
||||
}
|
||||
}
|
||||
body.innerHTML
|
||||
@@ -48,7 +48,7 @@ body.innerHTML
|
||||
/*:
|
||||
Remove nodes from the document
|
||||
*/
|
||||
body.removeChildNodeAtIndex(1)
|
||||
body.removeChildNode(at: 1)
|
||||
body.innerHTML
|
||||
|
||||
/*:
|
||||
@@ -56,24 +56,26 @@ Navigate to child and sibling nodes
|
||||
*/
|
||||
body.lastChild!.removeFromParentNode()
|
||||
let greenDiv = body.firstChild!.nextSibling!
|
||||
greenDiv.outerHTML
|
||||
|
||||
/*:
|
||||
Manipulate the HTML directly
|
||||
*/
|
||||
greenDiv.innerHTML = "<ul><li>item 1<li>item 2"
|
||||
greenDiv.outerHTML
|
||||
|
||||
/*:
|
||||
Iterate the DOM tree with custom filters
|
||||
*/
|
||||
let filter = HTMLNodeFilterBlock.filterWithBlock { (node) -> HTMLNodeFilterValue in
|
||||
let filter = HTMLNodeFilterBlock.filter { (node) -> HTMLNodeFilterValue in
|
||||
if node.childNodesCount() != 1 {
|
||||
return .Reject
|
||||
return .reject
|
||||
}
|
||||
return .Accept
|
||||
return .accept
|
||||
}
|
||||
|
||||
for element in body.nodeIteratorWithShowOptions(.Element, filter: filter) {
|
||||
element.outerHTML
|
||||
for element in body.nodeIterator(showOptions: .element, filter: filter) {
|
||||
(element as! AnyObject).outerHTML
|
||||
}
|
||||
|
||||
//: [Next](@next)
|
||||
|
||||
@@ -3,71 +3,65 @@
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=19&CharacterRangeLoc=575&EndingColumnNumber=26&EndingLineNumber=24&StartingColumnNumber=1&StartingLineNumber=24&Timestamp=472578662.196737"
|
||||
documentLocation = "#CharacterRangeLen=19&CharacterRangeLoc=571&EndingColumnNumber=26&EndingLineNumber=24&StartingColumnNumber=1&StartingLineNumber=24&Timestamp=495493395.434491"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=15&CharacterRangeLoc=834&EndingColumnNumber=22&EndingLineNumber=33&StartingColumnNumber=1&StartingLineNumber=33&Timestamp=495493408.111041"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=1&CharacterRangeLoc=1774&EndingColumnNumber=44&EndingLineNumber=77&StartingColumnNumber=2&StartingLineNumber=77&Timestamp=496085091.150842"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=835&EndingColumnNumber=15&EndingLineNumber=33&StartingColumnNumber=1&StartingLineNumber=33&Timestamp=495493408.111552"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=18&CharacterRangeLoc=572&EndingColumnNumber=19&EndingLineNumber=24&StartingColumnNumber=1&StartingLineNumber=24&Timestamp=495493395.435449"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=1&CharacterRangeLoc=1774&EndingColumnNumber=16&EndingLineNumber=77&StartingColumnNumber=2&StartingLineNumber=77&Timestamp=496085091.151491"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=33&CharacterRangeLoc=1742&EndingColumnNumber=39&EndingLineNumber=77&StartingColumnNumber=2&StartingLineNumber=77&Timestamp=496085091.151711"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=32&CharacterRangeLoc=1743&EndingColumnNumber=19&EndingLineNumber=77&StartingColumnNumber=2&StartingLineNumber=77&Timestamp=496085091.151931"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=1204&EndingColumnNumber=15&EndingLineNumber=51&StartingColumnNumber=1&StartingLineNumber=51&Timestamp=495493441.437517"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=1122&EndingColumnNumber=15&EndingLineNumber=45&StartingColumnNumber=1&StartingLineNumber=45&Timestamp=495493427.982381"
|
||||
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}"
|
||||
@@ -75,76 +69,82 @@
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=701&EndingColumnNumber=15&EndingLineNumber=26&StartingColumnNumber=1&StartingLineNumber=26&Timestamp=472578662.199312"
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=697&EndingColumnNumber=15&EndingLineNumber=26&StartingColumnNumber=1&StartingLineNumber=26&Timestamp=495493395.437213"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=983&EndingColumnNumber=15&EndingLineNumber=38&StartingColumnNumber=1&StartingLineNumber=38&Timestamp=495493427.983236"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=1030&EndingColumnNumber=15&EndingLineNumber=41&StartingColumnNumber=1&StartingLineNumber=41&Timestamp=495493427.983458"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=8&CharacterRangeLoc=1089&EndingColumnNumber=13&EndingLineNumber=44&StartingColumnNumber=5&StartingLineNumber=44&Timestamp=495493427.983688"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=17&CharacterRangeLoc=1601&EndingColumnNumber=19&EndingLineNumber=70&StartingColumnNumber=2&StartingLineNumber=70&Timestamp=496085091.153628"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=586&EndingColumnNumber=15&EndingLineNumber=21&StartingColumnNumber=1&StartingLineNumber=21&Timestamp=495493395.438545"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=9&CharacterRangeLoc=826&EndingColumnNumber=15&EndingLineNumber=30&StartingColumnNumber=1&StartingLineNumber=30&Timestamp=495493408.115325"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=868&EndingColumnNumber=15&EndingLineNumber=33&StartingColumnNumber=1&StartingLineNumber=33&Timestamp=495493408.115547"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=8&CharacterRangeLoc=927&EndingColumnNumber=13&EndingLineNumber=36&StartingColumnNumber=5&StartingLineNumber=36&Timestamp=495493408.115762"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=8&CharacterRangeLoc=959&EndingColumnNumber=9&EndingLineNumber=38&StartingColumnNumber=1&StartingLineNumber=38&Timestamp=495493427.985192"
|
||||
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"
|
||||
documentLocation = "#CharacterRangeLen=17&CharacterRangeLoc=1235&EndingColumnNumber=19&EndingLineNumber=48&StartingColumnNumber=2&StartingLineNumber=48&Timestamp=495493441.440785"
|
||||
lockedSize = "{763, 92}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=18&CharacterRangeLoc=1451&EndingColumnNumber=19&EndingLineNumber=64&StartingColumnNumber=1&StartingLineNumber=64&Timestamp=496085091.155099"
|
||||
lockedSize = "{740, 65}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=18&CharacterRangeLoc=1347&EndingColumnNumber=19&EndingLineNumber=58&StartingColumnNumber=1&StartingLineNumber=58&Timestamp=496085092.425909"
|
||||
lockedSize = "{740, 69}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<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>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='6.0' target-platform='ios' display-mode='rendered'>
|
||||
<playground version='6.0' target-platform='macos' display-mode='rendered' last-migration='0800'>
|
||||
<pages>
|
||||
<page name='Intro'/>
|
||||
<page name='Parsing Documents'/>
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "HTMLKit"
|
||||
s.version = "0.9.4"
|
||||
s.version = "2.1.5"
|
||||
s.summary = "HTMLKit, an Objective-C framework for your everyday HTML needs."
|
||||
s.license = "MIT"
|
||||
s.homepage = "https://github.com/iabudiab/HTMLKit"
|
||||
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
||||
|
||||
s.source_files = "Sources", "Sources/**/*.{h,m}"
|
||||
s.private_header_files = [
|
||||
'Sources/**/*{HTMLToken,HTMLTokens,HTMLTagToken,HTMLCharacterToken,HTMLCommentToken,HTMLDOCTYPEToken,HTMLEOFToken,HTMLTokenizer,HTMLTokenizerCharacters,HTMLTokenizerEntities,HTMLTokenizerStates,HTMLElementAdjustment,HTMLElementTypes,HTMLInputStreamReader,HTMLListOfActiveFormattingElements,HTMLNodeTraversal,HTMLParseErrorToken,HTMLParserInsertionModes,HTMLStackOfOpenElements,HTMLNode+Private,HTMLMarker,CSSCodePoints,CSSInputStream}.h'
|
||||
'Sources/**/*{HTMLToken,HTMLTokens,HTMLTagToken,HTMLCharacterToken,HTMLCommentToken,HTMLDOCTYPEToken,HTMLEOFToken,HTMLTokenizer,HTMLTokenizerCharacters,HTMLTokenizerEntities,HTMLTokenizerStates,HTMLElementAdjustment,HTMLElementTypes,HTMLInputStreamReader,HTMLListOfActiveFormattingElements,HTMLParseErrorToken,HTMLParserInsertionModes,HTMLStackOfOpenElements,HTMLMarker,HTMLNode+Private,HTMLDocument+Private,HTMLCharacterData+Private,HTMLRange+Private,HTMLParser+Private,HTMLNodeIterator+Private,HTMLNodeTraversal,HTMLDOMUtils,CSSCodePoints,CSSInputStream}.h'
|
||||
]
|
||||
|
||||
s.requires_arc = true
|
||||
|
||||
+347
-113
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
@@ -70,6 +71,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
+11
-8
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -16,7 +16,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-OSX"
|
||||
BlueprintName = "HTMLKit-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@@ -26,15 +26,17 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14C219C7829400AD0C32"
|
||||
BuildableName = "HTMLKitTests-OSX.xctest"
|
||||
BlueprintName = "HTMLKitTests-OSX"
|
||||
BuildableName = "HTMLKitTests-macOS.xctest"
|
||||
BlueprintName = "HTMLKitTests-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
@@ -58,7 +60,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-OSX"
|
||||
BlueprintName = "HTMLKit-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -69,6 +71,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
@@ -80,7 +83,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-OSX"
|
||||
BlueprintName = "HTMLKit-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -98,7 +101,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-OSX"
|
||||
BlueprintName = "HTMLKit-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,7 +26,9 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -63,6 +65,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,7 +26,9 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
@@ -36,6 +38,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
+3
@@ -7,4 +7,7 @@
|
||||
<FileRef
|
||||
location = "group:HTMLKit.playground">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Example/HTMLKitExample/HTMLKitExample.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "HTMLKit"
|
||||
name: "HTMLKit",
|
||||
exclude: ["Tests/Fixtures", "Tests/css-tests", "Tests/html5lib-tests"]
|
||||
)
|
||||
|
||||
@@ -4,13 +4,20 @@
|
||||
|
||||
An Objective-C framework for your everyday HTML needs.
|
||||
|
||||
[](https://travis-ci.org/iabudiab/HTMLKit)
|
||||
[](https://travis-ci.org/iabudiab/HTMLKit)
|
||||
[](https://codecov.io/gh/iabudiab/HTMLKit)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://cocoapods.org/pods/HTMLKit)
|
||||
[](http://cocoadocs.org/docsets/HTMLKit)
|
||||
[](http://cocoadocs.org/docsets/HTMLKit)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
- [Quick Overview](#overview)
|
||||
- [Installation](#installation)
|
||||
- [Parsing](#parsing)
|
||||
- [The DOM](#the-dom)
|
||||
- [CSS3 Selectors](#css3-selectors)
|
||||
|
||||
# Quick Overview
|
||||
|
||||
HTMLKit is a [WHATWG specification](https://html.spec.whatwg.org/multipage/)-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.
|
||||
@@ -33,7 +40,7 @@ Check out the playground!
|
||||
|
||||
# Installation
|
||||
|
||||
### Carthage
|
||||
## Carthage
|
||||
|
||||
[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
|
||||
|
||||
@@ -56,7 +63,7 @@ Then run the following command to build the framework and drag the built `HTMLKi
|
||||
$ carthage update
|
||||
```
|
||||
|
||||
### CocoaPods
|
||||
## CocoaPods
|
||||
|
||||
[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects.
|
||||
|
||||
@@ -69,10 +76,8 @@ $ gem install cocoapods
|
||||
To add `HTMLKit` as a dependency into your project using CocoaPods just add the following in your `Podfile`:
|
||||
|
||||
```ruby
|
||||
use_frameworks!
|
||||
|
||||
target 'MyTarget' do
|
||||
pod 'HTMLKit', '~> 0.9'
|
||||
pod 'HTMLKit', '~> 2.1'
|
||||
end
|
||||
```
|
||||
|
||||
@@ -82,14 +87,14 @@ Then, run the following command:
|
||||
$ pod install
|
||||
```
|
||||
|
||||
### Swift Package Manager
|
||||
## Swift Package Manager
|
||||
|
||||
[Swift Package Manager](https://github.com/apple/swift-package-manager) is the package manager for the Swift programming language.
|
||||
|
||||
Add `HTMLKit` to your `Package.swift` dependecies:
|
||||
|
||||
```swift
|
||||
.Package(url: "https://github.com/iabudiab/HTMLKit", majorVersion: 0, minor: 9)
|
||||
.Package(url: "https://github.com/iabudiab/HTMLKit", majorVersion: 2)
|
||||
```
|
||||
|
||||
Then run:
|
||||
@@ -98,7 +103,7 @@ Then run:
|
||||
$ swift build
|
||||
```
|
||||
|
||||
### Manually
|
||||
## Manually
|
||||
|
||||
1- Add `HTMLKit` as git submodule
|
||||
|
||||
@@ -110,9 +115,9 @@ $ git submodule add https://github.com/iabudiab/HTMLKit.git
|
||||
|
||||
3- In the General panel of your target add `HTMLKit.framework` under the `Embedded Binaries`
|
||||
|
||||
# Features
|
||||
# Parsing
|
||||
|
||||
# Parsing Documents
|
||||
## Parsing Documents
|
||||
|
||||
Given some HTML content, you can parse it either via the `HTMLParser` or instatiate a `HTMLDocument` directly:
|
||||
|
||||
@@ -127,7 +132,7 @@ HTMLDocument *document = [parser parseDocument];
|
||||
HTMLDocument *document = [HTMLDocument documentWithString:htmlString];
|
||||
```
|
||||
|
||||
# Parsing Fragments
|
||||
## Parsing Fragments
|
||||
|
||||
You can also prase HTML content as a document fragment with a specified context element:
|
||||
|
||||
@@ -150,7 +155,7 @@ nodes = [parser parseFragmentWithContextElement:bodyContext];
|
||||
|
||||
# The DOM
|
||||
|
||||
Here are some of the things you can do:
|
||||
The DOM tree can be manipulated in several ways, here are just a few:
|
||||
|
||||
* Create new elements and assign attributes
|
||||
|
||||
@@ -174,7 +179,7 @@ NSArray *nodes = @[
|
||||
[body appendNodes:nodes];
|
||||
```
|
||||
|
||||
* Enumerate child elements and perform DOM manipulation
|
||||
* Enumerate child elements and perform DOM editing
|
||||
|
||||
```objective-c
|
||||
[body enumerateChildElementsUsingBlock:^(HTMLElement *element, NSUInteger idx, BOOL *stop) {
|
||||
@@ -194,6 +199,12 @@ NSArray *nodes = @[
|
||||
[body.lastChild removeFromParentNode];
|
||||
```
|
||||
|
||||
* Manipulate the HTML directly
|
||||
|
||||
```objective-c
|
||||
greenDiv.innerHTML = @"<ul><li>item 1<li>item 2";
|
||||
```
|
||||
|
||||
* Navigate to child and sibling nodes
|
||||
|
||||
```objective-c
|
||||
@@ -201,12 +212,6 @@ HTMLNode *firstChild = body.firstChild;
|
||||
HTMLNode *greenDiv = firstChild.nextSibling;
|
||||
```
|
||||
|
||||
* Manipulate the HTML directly
|
||||
|
||||
```objective-c
|
||||
greenDiv.innerHTML = @"<ul><li>item 1<li>item 2";
|
||||
```
|
||||
|
||||
* Iterate the DOM tree with custom filters
|
||||
|
||||
```objective-c
|
||||
@@ -222,6 +227,17 @@ for (HTMLElement *element in [body nodeIteratorWithShowOptions:HTMLNodeFilterSho
|
||||
}
|
||||
```
|
||||
|
||||
* Create and manipulate DOM Ranges
|
||||
|
||||
```objective-c
|
||||
HTMLDocument *document = [HTMLDocument documentWithString:@"<div><h1>HTMLKit</h1><p id='foo'>Hello there!</p></div>"];
|
||||
HTMLRange *range = [[HTMLRange alloc] initWithDocument:document];
|
||||
|
||||
HTMLNode *paragraph = [document querySelector:@"#foo"];
|
||||
[range selectNode:paragraph];
|
||||
[range extractContents];
|
||||
```
|
||||
|
||||
# CSS3 Selectors
|
||||
|
||||
All CSS3 Selectors are supported except for the pseudo-elements (`::first-line`, `::first-letter`, ...etc.). You can use them the way you always have:
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
|
||||
- (NSString *)consumeIdentifier
|
||||
{
|
||||
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
|
||||
|
||||
if (!isValidIdentifierStart([self inputCharacterPointAtOffset:0],
|
||||
[self inputCharacterPointAtOffset:1],
|
||||
[self inputCharacterPointAtOffset:2])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
|
||||
|
||||
while (YES) {
|
||||
UTF32Char codePoint = [self consumeNextInputCharacter];
|
||||
if (codePoint == EOF) {
|
||||
@@ -47,7 +47,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil);
|
||||
if (CFStringGetLength(value) > 0) {
|
||||
return (__bridge_transfer NSString *)value;
|
||||
}
|
||||
|
||||
CFRelease(value);
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint
|
||||
@@ -85,7 +90,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil);
|
||||
if (CFStringGetLength(value) > 0) {
|
||||
return (__bridge_transfer NSString *)value;
|
||||
}
|
||||
|
||||
CFRelease(value);
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UTF32Char)consumeEscapedCodePoint
|
||||
@@ -105,7 +115,7 @@
|
||||
[self consumeNextInputCharacter];
|
||||
}
|
||||
|
||||
NSScanner *scanner = [NSScanner scannerWithString:(__bridge NSString *)(hexString)];
|
||||
NSScanner *scanner = [NSScanner scannerWithString:(__bridge_transfer NSString *)(hexString)];
|
||||
unsigned int number;
|
||||
[scanner scanHexInt:&number];
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression)
|
||||
|
||||
#pragma mark - Implementation
|
||||
|
||||
NSInteger computeIndex(NSEnumerator *enumerator, HTMLElement *element)
|
||||
NS_INLINE NSInteger computeIndex(NSEnumerator *enumerator, HTMLElement *element)
|
||||
{
|
||||
NSInteger index = 0;
|
||||
for (HTMLNode *node in enumerator) {
|
||||
|
||||
@@ -276,7 +276,7 @@ CSSSelector * ltSelector(NSInteger index)
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
if (index >= 0) {
|
||||
return elementIndex < index;
|
||||
} else {
|
||||
return elementIndex < element.parentElement.childNodesCount - index - 1;
|
||||
@@ -290,7 +290,7 @@ CSSSelector * gtSelector(NSInteger index)
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
if (index >= 0) {
|
||||
return elementIndex > index;
|
||||
} else {
|
||||
return elementIndex > element.parentElement.childNodesCount - index - 1;
|
||||
@@ -304,7 +304,7 @@ CSSSelector * eqSelector(NSInteger index)
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
if (index >= 0) {
|
||||
return elementIndex == index;
|
||||
} else {
|
||||
return elementIndex == element.parentElement.childNodesCount - index - 1;
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// HTMLCharacterData.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLCharacterData.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
#import "HTMLDocument+Private.h"
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
|
||||
@interface HTMLCharacterData ()
|
||||
{
|
||||
NSMutableString *_data;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLCharacterData
|
||||
@synthesize data = _data;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type data:(NSString *)data
|
||||
{
|
||||
self = [super initWithName:name type:type];
|
||||
if (self) {
|
||||
if (data) {
|
||||
_data = [[NSMutableString alloc] initWithString:data];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)data
|
||||
{
|
||||
if (_data == nil) {
|
||||
_data = [[NSMutableString alloc] initWithString:@""];
|
||||
}
|
||||
|
||||
return _data;
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return [self.data copy];
|
||||
}
|
||||
|
||||
- (void)setTextContent:(NSString *)textContent
|
||||
{
|
||||
[self setData:textContent];
|
||||
}
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
return self.data.length;
|
||||
}
|
||||
|
||||
#pragma mark - Data
|
||||
|
||||
NS_INLINE void CheckValidOffset(HTMLCharacterData *node, NSUInteger offset, NSString *cmd)
|
||||
{
|
||||
if (offset > node.length) {
|
||||
[NSException raise:HTMLKitIndexSizeError
|
||||
format:@"%@: Index Size Error, invalid index %lu for character data node %@.",
|
||||
cmd, (unsigned long)offset, node];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setData:(NSString *)data
|
||||
{
|
||||
[self replaceDataInRange:NSMakeRange(0, self.length) withData:data];
|
||||
}
|
||||
|
||||
- (void)appendData:(NSString *)data
|
||||
{
|
||||
[self replaceDataInRange:NSMakeRange(self.length, 0) withData:data];
|
||||
}
|
||||
|
||||
- (void)insertData:(NSString *)data atOffset:(NSUInteger)offset
|
||||
{
|
||||
[self replaceDataInRange:NSMakeRange(offset, 0) withData:data];
|
||||
}
|
||||
|
||||
- (void)deleteDataInRange:(NSRange)range
|
||||
{
|
||||
[self replaceDataInRange:range withData:@""];
|
||||
}
|
||||
|
||||
- (void)replaceDataInRange:(NSRange)range withData:(NSString *)data
|
||||
{
|
||||
CheckValidOffset(self, range.location, NSStringFromSelector(_cmd));
|
||||
|
||||
range.length = MIN(range.length, self.length - range.location);
|
||||
|
||||
[(NSMutableString *)self.data replaceCharactersInRange:range withString:data];
|
||||
[self.ownerDocument didRemoveCharacterDataInNode:self atOffset:range.location withLength:range.length];
|
||||
[self.ownerDocument didAddCharacterDataToNode:self atOffset:range.location withLength:data.length];
|
||||
}
|
||||
|
||||
- (NSString *)substringDataWithRange:(NSRange)range
|
||||
{
|
||||
return [_data substringWithRange:range];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLCharacterData *copy = [super copyWithZone:zone];
|
||||
copy->_data = [_data mutableCopy];
|
||||
return copy;
|
||||
}
|
||||
|
||||
@end
|
||||
+2
-25
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
#import "HTMLCharacterData+Private.h"
|
||||
|
||||
@implementation HTMLComment
|
||||
|
||||
@@ -18,30 +18,7 @@
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data
|
||||
{
|
||||
self = [super initWithName:@"#comment" type:HTMLNodeComment];
|
||||
if (self) {
|
||||
self.data = data ?: @"";
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return self.data;
|
||||
}
|
||||
|
||||
- (void)setTextContent:(NSString *)textContent
|
||||
{
|
||||
self.data = textContent ?: @"";
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLComment *copy = [super copyWithZone:zone];
|
||||
copy.data = self.data;
|
||||
return copy;
|
||||
return [super initWithName:@"#comment" type:HTMLNodeComment data:data];
|
||||
}
|
||||
|
||||
#pragma mark - Serialization
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// HTMLDOMUtils.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 03/12/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLDOMUtils.h"
|
||||
#import "HTMLNode.h"
|
||||
|
||||
extern HTMLNode * GetCommonAncestorContainer(HTMLNode *nodeA, HTMLNode *nodeB)
|
||||
{
|
||||
for (HTMLNode *parentA = nodeA; parentA != nil; parentA = parentA.parentNode) {
|
||||
for (HTMLNode *parentB = nodeB; parentB != nil; parentB = parentB.parentNode) {
|
||||
if (parentA == parentB) {
|
||||
return parentA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
extern NSArray<HTMLNode *> * GetAncestorNodes(HTMLNode *node)
|
||||
{
|
||||
NSMutableArray *ancestors = [NSMutableArray array];
|
||||
for (HTMLNode *it = node; it; it = it.parentNode) {
|
||||
[ancestors addObject:it];
|
||||
}
|
||||
return ancestors;
|
||||
}
|
||||
+59
-10
@@ -9,19 +9,19 @@
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLParser.h"
|
||||
#import "HTMLNodeIterator.h"
|
||||
#import "HTMLRange.h"
|
||||
#import "HTMLCharacterData.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
@interface HTMLNodeIterator (Private)
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
withOldParent:(HTMLNode *)oldParent
|
||||
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling;
|
||||
@end
|
||||
#import "HTMLNodeIterator+Private.h"
|
||||
#import "HTMLRange+Private.h"
|
||||
|
||||
@interface HTMLDocument ()
|
||||
{
|
||||
HTMLDocument *_inertTemplateDocument;
|
||||
NSMutableArray *_nodeIterators;
|
||||
NSHashTable *_nodeIterators;
|
||||
NSHashTable *_ranges;
|
||||
}
|
||||
@property (nonatomic, assign) HTMLDocumentReadyState readyState;
|
||||
@end
|
||||
@@ -41,7 +41,8 @@
|
||||
self = [super initWithName:@"#document" type:HTMLNodeDocument];
|
||||
if (self) {
|
||||
_readyState = HTMLDocumentLoading;
|
||||
_nodeIterators = [NSMutableArray new];
|
||||
_nodeIterators = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:10];
|
||||
_ranges = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:10];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -143,14 +144,62 @@
|
||||
[_nodeIterators removeObject:iterator];
|
||||
}
|
||||
|
||||
#pragma mark - Ranges
|
||||
|
||||
- (void)attachRange:(HTMLRange *)range
|
||||
{
|
||||
[_ranges addObject:range];
|
||||
}
|
||||
|
||||
- (void)detachRange:(HTMLRange *)range
|
||||
{
|
||||
[_ranges removeObject:range];
|
||||
}
|
||||
|
||||
- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
|
||||
{
|
||||
for (HTMLRange *range in _ranges) {
|
||||
[range didRemoveCharacterDataInNode:node atOffset:offset withLength:length];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
|
||||
{
|
||||
for (HTMLRange *range in _ranges) {
|
||||
[range didAddCharacterDataToNode:node atOffset:offset withLength:length];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didInsertNewTextNode:(HTMLText *)newNode intoParent:(HTMLNode *)parent afterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset
|
||||
{
|
||||
for (HTMLRange *range in _ranges) {
|
||||
[range didInsertNewTextNode:newNode intoParent:parent afterSplittingTextNode:node atOffset:offset];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clampRangesAfterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset
|
||||
{
|
||||
for (HTMLRange *range in _ranges) {
|
||||
[range clampRangesAfterSplittingTextNode:node atOffset:offset];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Mutation Callback
|
||||
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
withOldParent:(HTMLNode *)oldParent
|
||||
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling
|
||||
{
|
||||
for (HTMLRange *range in _ranges) {
|
||||
[range runRemovingStepsForNode:oldNode
|
||||
withOldParent:oldParent
|
||||
andOldPreviousSibling:oldPreviousSibling];
|
||||
}
|
||||
|
||||
for (HTMLNodeIterator *iterator in _nodeIterators) {
|
||||
[iterator runRemovingStepsForNode:oldNode
|
||||
withOldParent:oldParent
|
||||
andOldPreviousSibling:oldPreviousSibling];
|
||||
withOldParent:oldParent
|
||||
andOldPreviousSibling:oldPreviousSibling];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,13 +129,18 @@ NS_INLINE BOOL nilOrEqual(id first, id second) {
|
||||
return HTMLQuirksModeNoQuirks;
|
||||
}
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLDocumentType *copy = [super copyWithZone:zone];
|
||||
copy->_publicIdentifier = self.publicIdentifier;
|
||||
copy->_systemIdentifier = self.systemIdentifier;
|
||||
copy->_publicIdentifier = [_publicIdentifier copy];
|
||||
copy->_systemIdentifier = [_systemIdentifier copy];
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
+24
-15
@@ -32,7 +32,7 @@
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName
|
||||
{
|
||||
return [self initWithTagName:tagName attributes:@{}];
|
||||
return [self initWithTagName:tagName attributes:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes
|
||||
@@ -45,8 +45,9 @@
|
||||
self = [super initWithName:tagName type:HTMLNodeElement];
|
||||
if (self) {
|
||||
_tagName = [tagName copy];
|
||||
_attributes = [HTMLOrderedDictionary new];
|
||||
_attributes = nil;
|
||||
if (attributes != nil) {
|
||||
_attributes = [HTMLOrderedDictionary new];
|
||||
[_attributes addEntriesFromDictionary:attributes];
|
||||
}
|
||||
_htmlNamespace = htmlNamespace;
|
||||
@@ -56,24 +57,33 @@
|
||||
|
||||
#pragma mark - Special Attributes
|
||||
|
||||
- (NSMutableDictionary<NSString *,NSString *> *)attributes
|
||||
{
|
||||
if (_attributes == nil) {
|
||||
_attributes = [HTMLOrderedDictionary new];
|
||||
}
|
||||
|
||||
return _attributes;
|
||||
}
|
||||
|
||||
- (NSString *)elementId
|
||||
{
|
||||
return _attributes[@"id"] ?: @"";
|
||||
return self.attributes[@"id"] ?: @"";
|
||||
}
|
||||
|
||||
- (void)setElementId:(NSString *)elementId
|
||||
{
|
||||
_attributes[@"id"] = elementId;
|
||||
self.attributes[@"id"] = elementId;
|
||||
}
|
||||
|
||||
- (NSString *)className
|
||||
{
|
||||
return _attributes[@"class"] ?: @"";
|
||||
return self.attributes[@"class"] ?: @"";
|
||||
}
|
||||
|
||||
- (void)setClassName:(NSString *)className
|
||||
{
|
||||
_attributes[@"class"] = className;
|
||||
self.attributes[@"class"] = className;
|
||||
}
|
||||
|
||||
- (HTMLDOMTokenList *)classList
|
||||
@@ -85,22 +95,22 @@
|
||||
|
||||
- (BOOL)hasAttribute:(NSString *)name
|
||||
{
|
||||
return _attributes[name] != nil;
|
||||
return self.attributes[name] != nil;
|
||||
}
|
||||
|
||||
- (NSString *)objectForKeyedSubscript:(NSString *)name;
|
||||
{
|
||||
return _attributes[name];
|
||||
return self.attributes[name];
|
||||
}
|
||||
|
||||
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute
|
||||
{
|
||||
_attributes[attribute] = value;
|
||||
self.attributes[attribute] = value;
|
||||
}
|
||||
|
||||
- (void)removeAttribute:(NSString *)name
|
||||
{
|
||||
[_attributes removeObjectForKey:name];
|
||||
[self.attributes removeObjectForKey:name];
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
@@ -134,7 +144,7 @@
|
||||
{
|
||||
HTMLElement *copy = [super copyWithZone:zone];
|
||||
copy->_tagName = [_tagName copy];
|
||||
copy->_attributes = [_attributes copy];
|
||||
copy->_attributes = [_attributes mutableCopy];
|
||||
copy->_htmlNamespace = _htmlNamespace;
|
||||
return copy;
|
||||
}
|
||||
@@ -147,11 +157,10 @@
|
||||
|
||||
[result appendFormat:@"<%@", self.tagName];
|
||||
[self.attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
|
||||
NSRange range = NSMakeRange(0, value.length);
|
||||
NSMutableString *escaped = [value mutableCopy];
|
||||
[escaped replaceOccurrencesOfString:@"&" withString:@"&" options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"\00A0" withString:@" " options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"\"" withString:@""" options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"&" withString:@"&" options:0 range:NSMakeRange(0, escaped.length)];
|
||||
[escaped replaceOccurrencesOfString:@"0x00A0" withString:@" " options:0 range:NSMakeRange(0, escaped.length)];
|
||||
[escaped replaceOccurrencesOfString:@"\"" withString:@""" options:0 range:NSMakeRange(0, escaped.length)];
|
||||
|
||||
[result appendFormat:@" %@=\"%@\"", key, escaped];
|
||||
}];
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
|
||||
#pragma mark - Errors
|
||||
|
||||
- (void)emitParseError:(NSString *)reason
|
||||
- (void)emitParseError:(NSString *)code details:(NSString *)details
|
||||
{
|
||||
if (self.errorCallback) {
|
||||
self.errorCallback(reason);
|
||||
self.errorCallback(code, details);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,16 +82,16 @@
|
||||
return LINE_FEED;
|
||||
}
|
||||
if (CFStringIsSurrogateLowCharacter(nextInputCharacter)) {
|
||||
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated low surrogate: 0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
NSString *details = [NSString stringWithFormat:@"Non-Unicode character found (an isolated low surrogate: 0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:@"surrogate-in-input-stream" details:details];
|
||||
return nextInputCharacter;
|
||||
}
|
||||
|
||||
if (CFStringIsSurrogateHighCharacter(nextInputCharacter)) {
|
||||
UniChar surrogateLow = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
|
||||
if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) {
|
||||
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated high surrogate: 0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
NSString *details = [NSString stringWithFormat:@"Non-Unicode character found (an isolated high surrogate: 0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:@"surrogate-in-input-stream" details:details];
|
||||
return nextInputCharacter;
|
||||
}
|
||||
|
||||
@@ -99,9 +99,14 @@
|
||||
nextInputCharacter = CFStringGetLongCharacterForSurrogatePair(nextInputCharacter, surrogateLow);
|
||||
}
|
||||
|
||||
if (isControlOrUndefinedCharacter(nextInputCharacter)) {
|
||||
NSString *reason = [NSString stringWithFormat:@"A control/undefined character found: (0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
if (isControlCharacter(nextInputCharacter)) {
|
||||
NSString *details = [NSString stringWithFormat:@"A control character found: (0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:@"control-character-in-input-stream" details:details];
|
||||
}
|
||||
|
||||
if (isNoncharacter(nextInputCharacter)) {
|
||||
NSString *details = [NSString stringWithFormat:@"A noncharacter found: (0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:@"noncharacter-in-input-stream" details:details];
|
||||
}
|
||||
|
||||
return nextInputCharacter;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.9.4</string>
|
||||
<string>2.1.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -14,3 +14,7 @@ NSString * const HTMLKitNotFoundError = @"NotFoundError";
|
||||
NSString * const HTMLKitNotSupportedError = @"NotSupportedError";
|
||||
NSString * const HTMLKitSyntaxError = @"SyntaxError";
|
||||
NSString * const HTMLKitInvalidCharacterError = @"InvalidCharacterError";
|
||||
NSString * const HTMLKitInvalidNodeTypeError = @"InvalidNodeTypeError";
|
||||
NSString * const HTMLKitIndexSizeError = @"IndexSizeError";
|
||||
NSString * const HTMLKitWrongDocumentError = @"WrongDocumentError";
|
||||
NSString * const HTMLKitInvalidStateError = @"InvalidStateError";
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
- (void)addElement:(HTMLElement *)element
|
||||
{
|
||||
NSUInteger existing = 0;
|
||||
for (HTMLElement *node in _list.reverseObjectEnumerator.allObjects) {
|
||||
for (HTMLElement *node in _list.reverseObjectEnumerator) {
|
||||
if ([node isEqual:[HTMLMarker marker]]) {
|
||||
break;
|
||||
}
|
||||
|
||||
+118
-62
@@ -16,12 +16,12 @@
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
#import "HTMLNodeFilter.h"
|
||||
#import "CSSSelector.h"
|
||||
#import "HTMLDocument+Private.h"
|
||||
#import "HTMLDOMUtils.h"
|
||||
|
||||
@interface HTMLDocument (Private)
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
withOldParent:(HTMLNode *)oldParent
|
||||
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling;
|
||||
@end
|
||||
NSString * const ValidationNodePreInsertion = @"-ensurePreInsertionValidityOfNode:beforeChildNode:";
|
||||
NSString * const ValidationNodeReplacement = @"-ensureReplacementValidityOfChildNode:withNode:";
|
||||
NSString * const RemoveChildNode = @"-removeChildNode:";
|
||||
|
||||
@interface HTMLNode ()
|
||||
{
|
||||
@@ -40,13 +40,22 @@
|
||||
if (self) {
|
||||
_name = name;
|
||||
_nodeType = type;
|
||||
_childNodes = [NSMutableOrderedSet new];
|
||||
_childNodes = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (NSOrderedSet<HTMLNode *> *)childNodes
|
||||
{
|
||||
if (_childNodes == nil) {
|
||||
_childNodes = [NSMutableOrderedSet new];
|
||||
}
|
||||
|
||||
return _childNodes;
|
||||
}
|
||||
|
||||
- (HTMLDocument *)ownerDocument
|
||||
{
|
||||
if (_nodeType == HTMLNodeDocument) {
|
||||
@@ -59,7 +68,14 @@
|
||||
- (void)setOwnerDocument:(HTMLDocument *)ownerDocument
|
||||
{
|
||||
_ownerDocument = ownerDocument;
|
||||
[self.childNodes.array makeObjectsPerformSelector:@selector(setOwnerDocument:) withObject:ownerDocument];
|
||||
for (HTMLNode *child in _childNodes) {
|
||||
[child setOwnerDocument:ownerDocument];
|
||||
}
|
||||
}
|
||||
|
||||
- (HTMLNode *)rootNode
|
||||
{
|
||||
return _parentNode == nil ? self : _parentNode.rootNode;
|
||||
}
|
||||
|
||||
- (void)setParentNode:(HTMLNode *)parentNode
|
||||
@@ -74,12 +90,12 @@
|
||||
|
||||
- (HTMLNode *)firstChild
|
||||
{
|
||||
return self.childNodes.firstObject;
|
||||
return _childNodes.firstObject;
|
||||
}
|
||||
|
||||
- (HTMLNode *)lastChild
|
||||
{
|
||||
return self.childNodes.lastObject;
|
||||
return _childNodes.lastObject;
|
||||
}
|
||||
|
||||
- (HTMLNode *)previousSibling
|
||||
@@ -111,18 +127,28 @@
|
||||
|
||||
- (HTMLElement *)nextSiblingElement
|
||||
{
|
||||
HTMLNode *node = self.previousSibling;
|
||||
HTMLNode *node = self.nextSibling;
|
||||
while (node && node.nodeType != HTMLNodeElement) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return node.asElement;
|
||||
}
|
||||
|
||||
- (NSUInteger)index
|
||||
{
|
||||
return [_parentNode indexOfChildNode:self];
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
return self.childNodesCount;
|
||||
}
|
||||
|
||||
#pragma mark - Cast
|
||||
|
||||
- (HTMLElement *)asElement
|
||||
@@ -134,12 +160,16 @@
|
||||
|
||||
- (BOOL)hasChildNodes
|
||||
{
|
||||
return self.childNodes.count > 0;
|
||||
return _childNodes.count > 0;
|
||||
}
|
||||
|
||||
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type
|
||||
{
|
||||
NSUInteger index = [self.childNodes indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if (_childNodes == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSUInteger index = [_childNodes indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([(HTMLNode *)obj nodeType] == type) {
|
||||
*stop = YES;
|
||||
return YES;
|
||||
@@ -152,30 +182,35 @@
|
||||
|
||||
- (NSUInteger)childNodesCount
|
||||
{
|
||||
return self.childNodes.count;
|
||||
return _childNodes.count;
|
||||
}
|
||||
|
||||
- (BOOL)isEmpty
|
||||
{
|
||||
return self.length == 0;
|
||||
}
|
||||
|
||||
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [self.childNodes objectAtIndex:index];
|
||||
return [_childNodes objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (NSUInteger)childElementsCount
|
||||
{
|
||||
return [self.childNodes indexesOfObjectsPassingTest:^BOOL(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
return [_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];
|
||||
return [_childNodes indexOfObject:node];
|
||||
}
|
||||
|
||||
- (HTMLElement *)childElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
NSUInteger counter = 0;
|
||||
for (HTMLNode *node in self.childNodes) {
|
||||
for (HTMLNode *node in _childNodes) {
|
||||
if (node.nodeType == HTMLNodeElement) {
|
||||
if (counter == index) {
|
||||
return node.asElement;
|
||||
@@ -189,7 +224,7 @@
|
||||
- (NSUInteger)indexOfChildElement:(HTMLElement *)element
|
||||
{
|
||||
NSUInteger counter = 0;
|
||||
for (HTMLNode *node in self.childNodes) {
|
||||
for (HTMLNode *node in _childNodes) {
|
||||
if (node.nodeType == HTMLNodeElement) {
|
||||
if (node == element) {
|
||||
return counter;
|
||||
@@ -246,7 +281,9 @@
|
||||
[node removeAllChildNodes];
|
||||
}
|
||||
|
||||
[nodes makeObjectsPerformSelector:@selector(setParentNode:) withObject:self];
|
||||
for (HTMLNode *node in nodes) {
|
||||
[node setParentNode:self];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
@@ -274,7 +311,7 @@
|
||||
|
||||
- (void)removeFromParentNode
|
||||
{
|
||||
[self.parentNode removeChildNode:self];
|
||||
[_parentNode removeChildNode:self];
|
||||
}
|
||||
|
||||
- (HTMLNode *)removeChildNode:(HTMLNode *)child
|
||||
@@ -282,7 +319,7 @@
|
||||
if (child.parentNode != self) {
|
||||
[NSException raise:HTMLKitNotFoundError
|
||||
format:@"%@: Not Fount Error, removing non-child node %@. The object can not be found here.",
|
||||
NSStringFromSelector(_cmd), child];
|
||||
RemoveChildNode, child];
|
||||
}
|
||||
|
||||
HTMLNode *oldNode = child;
|
||||
@@ -306,16 +343,18 @@
|
||||
|
||||
- (void)reparentChildNodesIntoNode:(HTMLNode *)node
|
||||
{
|
||||
for (HTMLNode *child in self.childNodes.array) {
|
||||
for (HTMLNode *child in _childNodes) {
|
||||
[node appendNode:child];
|
||||
}
|
||||
[(NSMutableOrderedSet *)self.childNodes removeAllObjects];
|
||||
[(NSMutableOrderedSet *)_childNodes removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)removeAllChildNodes
|
||||
{
|
||||
[self.childNodes.array makeObjectsPerformSelector:@selector(setParentNode:) withObject:nil];
|
||||
[(NSMutableOrderedSet *)self.childNodes removeAllObjects];
|
||||
for (HTMLNode *child in _childNodes) {
|
||||
[child setParentNode:nil];
|
||||
}
|
||||
[(NSMutableOrderedSet *)_childNodes removeAllObjects];
|
||||
}
|
||||
|
||||
- (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)otherNode
|
||||
@@ -328,41 +367,43 @@
|
||||
return HTMLDocumentPositionEquivalent;
|
||||
}
|
||||
|
||||
NSArray * (^ ancestorNodes) (HTMLNode *) = ^ NSArray * (HTMLNode *node) {
|
||||
NSMutableArray *ancestors = [NSMutableArray array];
|
||||
for (HTMLNode *node = self; node; node = node.parentNode) {
|
||||
[ancestors addObject:node];
|
||||
}
|
||||
return ancestors;
|
||||
};
|
||||
|
||||
NSArray *ancestors1 = ancestorNodes(self);
|
||||
NSArray *ancestors2 = ancestorNodes(otherNode);
|
||||
|
||||
if (ancestors1.lastObject != ancestors2.lastObject) {
|
||||
return HTMLDocumentPositionDisconnected |
|
||||
HTMLDocumentPositionImplementationSpecific |
|
||||
HTMLDocumentPositionFollowing;
|
||||
if (self.ownerDocument != otherNode.ownerDocument) {
|
||||
return (HTMLDocumentPositionDisconnected | HTMLDocumentPositionImplementationSpecific |
|
||||
self.hash < otherNode.hash ? HTMLDocumentPositionPreceding : HTMLDocumentPositionFollowing);
|
||||
}
|
||||
|
||||
for (NSUInteger i = MIN(ancestors1.count - 1, ancestors2.count - 1); i; --i) {
|
||||
HTMLNode *child1 = ancestors1[i];
|
||||
HTMLNode *child2 = ancestors2[i];
|
||||
NSArray *ancestors1 = GetAncestorNodes(self);
|
||||
NSArray *ancestors2 = GetAncestorNodes(otherNode);
|
||||
|
||||
if (ancestors1.lastObject != ancestors2.lastObject) {
|
||||
return (HTMLDocumentPositionDisconnected | HTMLDocumentPositionImplementationSpecific |
|
||||
self.hash < otherNode.hash ? HTMLDocumentPositionPreceding : HTMLDocumentPositionFollowing);
|
||||
}
|
||||
|
||||
NSUInteger index1 = ancestors1.count;
|
||||
NSUInteger index2 = ancestors2.count;
|
||||
|
||||
for (NSUInteger i = MIN(index1, index2); i; --i) {
|
||||
index1 -= 1;
|
||||
index2 -= 1;
|
||||
|
||||
HTMLNode *child1 = ancestors1[index1];
|
||||
HTMLNode *child2 = ancestors2[index2];
|
||||
|
||||
if (child1 != child2) {
|
||||
for (HTMLNode *sibling = child1.nextSibling; sibling; sibling = sibling.nextSibling) {
|
||||
if (sibling == child2) {
|
||||
return HTMLDocumentPositionFollowing;
|
||||
return HTMLDocumentPositionPreceding;
|
||||
}
|
||||
}
|
||||
return HTMLDocumentPositionPreceding;
|
||||
return HTMLDocumentPositionFollowing;
|
||||
}
|
||||
}
|
||||
|
||||
if (ancestors1.count < ancestors2.count) {
|
||||
return HTMLDocumentPositionContainedBy | HTMLDocumentPositionFollowing;
|
||||
} else {
|
||||
return HTMLDocumentPositionContains | HTMLDocumentPositionPreceding;
|
||||
} else {
|
||||
return HTMLDocumentPositionContainedBy | HTMLDocumentPositionFollowing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +425,7 @@
|
||||
return self.nodeType != HTMLNodeDocument && self.ownerDocument == otherNode;
|
||||
}
|
||||
|
||||
for (HTMLNode *parentNode = self.parentNode; parentNode; parentNode = parentNode.parentNode) {
|
||||
for (HTMLNode *parentNode = _parentNode; parentNode; parentNode = parentNode.parentNode) {
|
||||
if (parentNode == otherNode) {
|
||||
return YES;
|
||||
}
|
||||
@@ -406,7 +447,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
[self.childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
[_childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
block(obj, idx, stop);
|
||||
}];
|
||||
}
|
||||
@@ -417,7 +458,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
[self.childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
[_childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([obj isKindOfClass:[HTMLElement class]]) {
|
||||
block([obj asElement], idx, stop);
|
||||
}
|
||||
@@ -540,18 +581,18 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin
|
||||
|
||||
- (void)ensurePreInsertionValidityOfNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
|
||||
{
|
||||
CheckParentValid(self, NSStringFromSelector(_cmd));
|
||||
CheckParentValid(self, ValidationNodePreInsertion);
|
||||
|
||||
CheckChildsParent(self, child, NSStringFromSelector(_cmd));
|
||||
CheckChildsParent(self, child, ValidationNodePreInsertion);
|
||||
|
||||
CheckInsertedNodeValid(node, NSStringFromSelector(_cmd));
|
||||
CheckInsertedNodeValid(node, ValidationNodePreInsertion);
|
||||
|
||||
CheckInvalidCombination(self, node, NSStringFromSelector(_cmd));
|
||||
CheckInvalidCombination(self, node, ValidationNodePreInsertion);
|
||||
|
||||
void (^ hierarchyError)() = ^{
|
||||
void (^ hierarchyError)(void) = ^{
|
||||
[NSException raise:HTMLKitHierarchyRequestError
|
||||
format:@"%@: Hierarchy Request Error, inserting (%@) into (%@). The operation would yield an incorrect node tree.",
|
||||
NSStringFromSelector(_cmd), self, node];
|
||||
ValidationNodePreInsertion, self, node];
|
||||
};
|
||||
|
||||
if (self.nodeType == HTMLNodeDocument) {
|
||||
@@ -590,18 +631,18 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin
|
||||
|
||||
- (void)ensureReplacementValidityOfChildNode:(HTMLNode *)child withNode:(HTMLNode *)node
|
||||
{
|
||||
CheckParentValid(self, NSStringFromSelector(_cmd));
|
||||
CheckParentValid(self, ValidationNodeReplacement);
|
||||
|
||||
CheckChildsParent(self, child, NSStringFromSelector(_cmd));
|
||||
CheckChildsParent(self, child, ValidationNodeReplacement);
|
||||
|
||||
CheckInsertedNodeValid(node, NSStringFromSelector(_cmd));
|
||||
CheckInsertedNodeValid(node, ValidationNodeReplacement);
|
||||
|
||||
CheckInvalidCombination(self, node, NSStringFromSelector(_cmd));
|
||||
CheckInvalidCombination(self, node, ValidationNodeReplacement);
|
||||
|
||||
void (^ hierarchyError)() = ^{
|
||||
void (^ hierarchyError)(void) = ^{
|
||||
[NSException raise:HTMLKitHierarchyRequestError
|
||||
format:@"%@: Hierarchy Request Error. The operation would yield an incorrect node tree.",
|
||||
NSStringFromSelector(_cmd)];
|
||||
ValidationNodeReplacement];
|
||||
};
|
||||
|
||||
void (^ checkParentHasAnotherChildOfType)(HTMLNodeType) = ^ void (HTMLNodeType type) {
|
||||
@@ -650,6 +691,21 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin
|
||||
|
||||
#endif
|
||||
|
||||
#pragma mark - Clone
|
||||
|
||||
- (instancetype)cloneNodeDeep:(BOOL)deep
|
||||
{
|
||||
HTMLNode *copy = [self copy];
|
||||
|
||||
if (deep) {
|
||||
for (HTMLNode *child in _childNodes) {
|
||||
[copy appendNode:[child cloneNodeDeep:YES]];
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
@interface HTMLNodeFilterBlock ()
|
||||
{
|
||||
BOOL (^ _block)(HTMLNode *);
|
||||
HTMLNodeFilterValue (^ _block)(HTMLNode *);
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLNodeFilter.h"
|
||||
#import "HTMLNodeTraversal.h"
|
||||
#import "HTMLDocument+Private.h"
|
||||
|
||||
typedef NS_ENUM(short, TraverseDirection)
|
||||
{
|
||||
@@ -18,11 +19,6 @@ typedef NS_ENUM(short, TraverseDirection)
|
||||
TraverseDirectionPrevious
|
||||
};
|
||||
|
||||
@interface HTMLDocument (Private)
|
||||
- (void)attachNodeIterator:(HTMLNodeIterator *)iterator;
|
||||
- (void)detachNodeIterator:(HTMLNodeIterator *)iterator;
|
||||
@end
|
||||
|
||||
@interface HTMLNodeIterator ()
|
||||
{
|
||||
HTMLNode *_root;
|
||||
|
||||
@@ -154,4 +154,11 @@
|
||||
return [_keys countByEnumeratingWithState:state objects:buffer count:len];
|
||||
}
|
||||
|
||||
#pragma mark - Copying
|
||||
|
||||
- (id)mutableCopy
|
||||
{
|
||||
return [[HTMLOrderedDictionary alloc] initWithDictionary:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -10,29 +10,46 @@
|
||||
|
||||
@interface HTMLParseErrorToken ()
|
||||
{
|
||||
NSString *_reason;
|
||||
NSString *_code;
|
||||
NSString *_details;
|
||||
NSUInteger _location;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLParseErrorToken
|
||||
@synthesize reason = _reason;
|
||||
@synthesize code = _code;
|
||||
@synthesize details = _details;
|
||||
@synthesize location = _location;
|
||||
|
||||
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location
|
||||
- (instancetype)initWithCode:(NSString *)code details:(NSString *)details location:(NSUInteger)location
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.type = HTMLTokenTypeParseError;
|
||||
_reason = [reason copy];
|
||||
_code = [code copy];
|
||||
_details = [details copy];
|
||||
_location = location;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)other
|
||||
{
|
||||
if ([other isKindOfClass:[self class]]) {
|
||||
HTMLParseErrorToken *token = (HTMLParseErrorToken *)other;
|
||||
return bothNilOrEqual(self.code, token.code);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return self.code.hash + self.code.hash;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p Reason='%@' Location='%lu'>", self.class, self, _reason, (unsigned long)_location];
|
||||
return [NSString stringWithFormat:@"<%@: %p Code='%@' Details='%@' Location='%lu'>", self.class, self, _code, _details, (unsigned long)_location];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+26
-48
@@ -18,14 +18,7 @@
|
||||
#import "HTMLMarker.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "CSSSelectors.h"
|
||||
|
||||
@interface HTMLTokenizer (Private)
|
||||
@property (nonatomic, weak) HTMLParser *parser;
|
||||
@end
|
||||
|
||||
@interface HTMLDocument (Private)
|
||||
@property (nonatomic, assign) HTMLDocumentReadyState readyState;
|
||||
@end
|
||||
#import "HTMLDocument+Private.h"
|
||||
|
||||
@interface HTMLParser ()
|
||||
{
|
||||
@@ -80,6 +73,10 @@
|
||||
|
||||
_tokenizer = [[HTMLTokenizer alloc] initWithString:string ?: @""];
|
||||
_tokenizer.parser = self;
|
||||
__weak HTMLParser *weakSelf = self;
|
||||
_tokenizer.parseErrorCallback = ^(HTMLParseErrorToken *token) {
|
||||
[weakSelf emitParseError:@"Tokenization error: %@", token.asParseError];
|
||||
};
|
||||
|
||||
_pendingTableCharacterTokens = [[HTMLCharacterToken alloc] initWithString:@""];
|
||||
|
||||
@@ -137,6 +134,10 @@
|
||||
[self initializeDocument];
|
||||
_tokenizer = [[HTMLTokenizer alloc] initWithString:_tokenizer.string];
|
||||
_tokenizer.parser = self;
|
||||
__weak HTMLParser *weakSelf = self;
|
||||
_tokenizer.parseErrorCallback = ^(HTMLParseErrorToken *token) {
|
||||
[weakSelf emitParseError:@"Tokenization error: %@", token.asParseError];
|
||||
};
|
||||
|
||||
_contextElement = contextElement;
|
||||
_fragmentParsingAlgorithm = YES;
|
||||
@@ -233,11 +234,6 @@
|
||||
return NO;
|
||||
};
|
||||
|
||||
if (token.isParseError) {
|
||||
[self emitParseError:@"Tokenizer Parser Error: %@", token.asParseError];
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ignoreNextLineFeedCharacterToken) {
|
||||
_ignoreNextLineFeedCharacterToken = NO;
|
||||
if (token.isCharacterToken) {
|
||||
@@ -457,9 +453,11 @@
|
||||
beforeChildNode:&child];
|
||||
if (adjustedInsertionLocation.nodeType != HTMLNodeDocument) {
|
||||
if (child != nil && child.previousSibling.nodeType == HTMLNodeText) {
|
||||
[(HTMLText *)child.previousSibling appendString:data];
|
||||
HTMLText *textNode = (HTMLText *)child.previousSibling;
|
||||
[textNode appendData:data];
|
||||
} else if (adjustedInsertionLocation.lastChild.nodeType == HTMLNodeText) {
|
||||
[(HTMLText *)adjustedInsertionLocation.lastChild appendString:data];
|
||||
HTMLText *textNode = (HTMLText *)adjustedInsertionLocation.lastChild;
|
||||
[textNode appendData:data];
|
||||
} else {
|
||||
HTMLText *text = [[HTMLText alloc] initWithData:data];
|
||||
[adjustedInsertionLocation insertNode:text beforeChildNode:child];
|
||||
@@ -519,7 +517,7 @@
|
||||
|
||||
- (void)generateImpliedEndTagsExceptForElement:(NSString *)tagName
|
||||
{
|
||||
while ([self.currentNode.tagName isEqualToAny:@"dd", @"dt", @"li", @"menuitem", @"option", @"optgroup", @"p", @"rb", @"rp", @"rt", @"rtc", nil] &&
|
||||
while ([self.currentNode.tagName isEqualToAny:@"dd", @"dt", @"li", @"option", @"optgroup", @"p", @"rb", @"rp", @"rt", @"rtc", nil] &&
|
||||
![self.currentNode.tagName isEqualToString:tagName]) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
}
|
||||
@@ -1167,8 +1165,8 @@
|
||||
[self HTMLInsertionModeInTemplate:token];
|
||||
} else {
|
||||
for (HTMLElement *node in _stackOfOpenElements) {
|
||||
if ([node.tagName isEqualToAny:@"dd", @"dt", @"li", @"menuitem", @"optgroup", @"option", @"p", @"rb",
|
||||
@"rp", @"rt", @"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) {
|
||||
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;
|
||||
}
|
||||
@@ -1231,20 +1229,12 @@
|
||||
[self switchInsertionMode:HTMLInsertionModeInFrameset];
|
||||
} else if ([tagName isEqualToAny:@"address", @"article", @"aside", @"blockquote", @"center",
|
||||
@"details", @"dialog", @"dir", @"div", @"dl", @"fieldset", @"figcaption", @"figure",
|
||||
@"footer", @"header", @"hgroup", @"main", @"nav", @"ol", @"p", @"section",
|
||||
@"footer", @"header", @"hgroup", @"main", @"menu", @"nav", @"ol", @"p", @"section",
|
||||
@"summary", @"ul", nil]) {
|
||||
if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) {
|
||||
[self closePElement];
|
||||
}
|
||||
[self insertElementForToken:token];
|
||||
} else if ([tagName isEqualToString:@"menu"]) {
|
||||
if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) {
|
||||
[self closePElement];
|
||||
}
|
||||
if ([self.currentNode.tagName isEqualToString:@"menuitem"]) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
}
|
||||
[self insertElementForToken:token];
|
||||
} else if ([tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
|
||||
if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) {
|
||||
[self closePElement];
|
||||
@@ -1285,7 +1275,7 @@
|
||||
@"dd": @[@"dd", @"dt"],
|
||||
@"dt": @[@"dd", @"dt"]};
|
||||
|
||||
for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator.allObjects) {
|
||||
for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator) {
|
||||
if ([map[tagName] containsObject:node.tagName]) {
|
||||
[self generateImpliedEndTagsExceptForElement:node.tagName];
|
||||
if (![self.currentNode.tagName isEqualToString:node.tagName]) {
|
||||
@@ -1388,9 +1378,6 @@
|
||||
if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) {
|
||||
[self closePElement];
|
||||
}
|
||||
if ([self.currentNode.tagName isEqualToString:@"menuitem"]) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
}
|
||||
[self insertElementForToken:token];
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
_framesetOkFlag = NO;
|
||||
@@ -1436,12 +1423,6 @@
|
||||
}
|
||||
[self reconstructActiveFormattingElements];
|
||||
[self insertElementForToken:token];
|
||||
} else if ([tagName isEqualToString:@"menuitem"]) {
|
||||
if ([self.currentNode.tagName isEqualToString:@"menuitem"]) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
}
|
||||
[self reconstructActiveFormattingElements];
|
||||
[self insertElementForToken:token];
|
||||
} else if ([tagName isEqualToAny:@"rb", @"rtc", nil]) {
|
||||
if ([_stackOfOpenElements hasElementInScopeWithTagName:@"ruby"]) {
|
||||
[self generateImpliedEndTagsExceptForElement:nil];
|
||||
@@ -1462,7 +1443,6 @@
|
||||
} else if ([tagName isEqualToString:@"math"]) {
|
||||
[self reconstructActiveFormattingElements];
|
||||
AdjustMathMLAttributes(token);
|
||||
AdjustForeignAttributes(token);
|
||||
[self insertForeignElementForToken:token inNamespace:HTMLNamespaceMathML];
|
||||
if (token.isSelfClosing) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
@@ -1470,7 +1450,6 @@
|
||||
} else if ([tagName isEqualToString:@"svg"]) {
|
||||
[self reconstructActiveFormattingElements];
|
||||
AdjustSVGAttributes(token);
|
||||
AdjustForeignAttributes(token);
|
||||
[self insertForeignElementForToken:token inNamespace:HTMLNamespaceSVG];
|
||||
if (token.isSelfClosing) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
@@ -1496,8 +1475,8 @@
|
||||
[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", @"menuitem", @"optgroup", @"option", @"p", @"rb", @"rp",
|
||||
@"rt", @"rtc", @"tbody", @"td", @"tfoot", @"th", @"thead", @"tr", @"body", @"html", nil]) {
|
||||
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;
|
||||
}
|
||||
@@ -1551,7 +1530,7 @@
|
||||
}
|
||||
[self closePElement];
|
||||
} else if ([tagName isEqualToString:@"li"]) {
|
||||
if (![_stackOfOpenElements hasElementInListItemScopeWithTagName:@"li"]) {
|
||||
if (![_stackOfOpenElements hasElementInListItemScopeWithTagName:tagName]) {
|
||||
[self emitParseError:@"Unexpected <li> element in <body>"];
|
||||
return;
|
||||
}
|
||||
@@ -1571,7 +1550,7 @@
|
||||
}
|
||||
[_stackOfOpenElements popElementsUntilElementPoppedWithTagName:tagName];
|
||||
} else if ([tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
|
||||
if (![_stackOfOpenElements hasAnyElementInScopeWithAnyOfTagNames:@[@"h1", @"h2", @"h3", @"h4", @"h5", @"h6"]]) {
|
||||
if (![_stackOfOpenElements hasHeaderElementInScope]) {
|
||||
[self emitParseError:@"Unexpected <%@> element in <body>", tagName];
|
||||
return;
|
||||
}
|
||||
@@ -1591,7 +1570,7 @@
|
||||
return;
|
||||
}
|
||||
} else if ([tagName isEqualToAny:@"applet", @"marquee", @"object", nil]) {
|
||||
if (![_stackOfOpenElements hasAnyElementInScopeWithAnyOfTagNames:@[@"applet", @"marquee", @"object"]]) {
|
||||
if (![_stackOfOpenElements hasElementInScopeWithTagName:tagName]) {
|
||||
[self emitParseError:@"Unexpected <%@> element in <body>", tagName];
|
||||
return;
|
||||
}
|
||||
@@ -1612,7 +1591,7 @@
|
||||
|
||||
- (void)processAnyOtherEndTagTokenInBody:(HTMLTagToken *)token
|
||||
{
|
||||
for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator.allObjects) {
|
||||
for (HTMLElement *node in _stackOfOpenElements.reverseObjectEnumerator) {
|
||||
if ([node.tagName isEqualToString:token.tagName]) {
|
||||
[self generateImpliedEndTagsExceptForElement:token.tagName];
|
||||
if (![node.tagName isEqualToString:self.currentNode.tagName]) {
|
||||
@@ -2552,7 +2531,7 @@
|
||||
return;
|
||||
case HTMLTokenTypeStartTag:
|
||||
{
|
||||
void (^ anythingElse)() = ^ {
|
||||
void (^ anythingElse)(void) = ^ {
|
||||
if (self.adjustedCurrentNode.htmlNamespace == HTMLNamespaceMathML) {
|
||||
AdjustMathMLAttributes(token.asTagToken);
|
||||
}
|
||||
@@ -2560,14 +2539,13 @@
|
||||
AdjustSVGNameCase(token.asTagToken);
|
||||
AdjustSVGAttributes(token.asTagToken);
|
||||
}
|
||||
AdjustForeignAttributes(token.asTagToken);
|
||||
[self insertForeignElementForToken:token.asTagToken inNamespace:self.adjustedCurrentNode.htmlNamespace];
|
||||
if (token.asTagToken.selfClosing) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
}
|
||||
};
|
||||
|
||||
void (^ matchedCase)() = ^ {
|
||||
void (^ matchedCase)(void) = ^ {
|
||||
[self emitParseError:@"Unexpected start tag <%@> in foreign content", token.asTagToken.tagName];
|
||||
if (_fragmentParsingAlgorithm) {
|
||||
anythingElse();
|
||||
|
||||
@@ -0,0 +1,767 @@
|
||||
//
|
||||
// HTMLRange.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLRange.h"
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
#import "HTMLDocument+Private.h"
|
||||
#import "HTMLDOMUtils.h"
|
||||
#import "HTMLNodeTraversal.h"
|
||||
|
||||
@interface HTMLRange ()
|
||||
{
|
||||
HTMLDocument *_ownerDocument;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLRange
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithDocument:(HTMLDocument *)document
|
||||
{
|
||||
return [self initWithDocument:document startContainer:document startOffset:0 endContainer:document endOffset:0];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDowcument:(HTMLDocument *)document
|
||||
{
|
||||
return [self initWithDocument:document startContainer:document startOffset:0 endContainer:document endOffset:0];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDowcument:(HTMLDocument *)document
|
||||
startContainer:(HTMLNode *)startContainer startOffset:(NSUInteger)startOffset
|
||||
endContainer:(HTMLNode *)endContainer endOffset:(NSUInteger)endOffset
|
||||
{
|
||||
return [self initWithDocument:document
|
||||
startContainer:startContainer startOffset:startOffset
|
||||
endContainer:endContainer endOffset:endOffset];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDocument:(HTMLDocument *)document
|
||||
startContainer:(HTMLNode *)startContainer startOffset:(NSUInteger)startOffset
|
||||
endContainer:(HTMLNode *)endContainer endOffset:(NSUInteger)endOffset
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_ownerDocument = document;
|
||||
[_ownerDocument attachRange:self];
|
||||
[self setStartNode:startContainer startOffset:startOffset];
|
||||
[self setEndNode:endContainer endOffset:endOffset];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_ownerDocument detachRange:self];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (BOOL)isCollapsed
|
||||
{
|
||||
return _startContainer == _endContainer && _startOffset == _endOffset;
|
||||
}
|
||||
|
||||
- (HTMLNode *)commonAncestorContainer
|
||||
{
|
||||
return GetCommonAncestorContainer(_startContainer, _endContainer);
|
||||
}
|
||||
|
||||
- (HTMLNode *)rootNode
|
||||
{
|
||||
return _startContainer.rootNode;
|
||||
}
|
||||
|
||||
#pragma mark - Boundaries
|
||||
|
||||
NS_INLINE void CheckValidBoundaryNode(HTMLDocument *document, HTMLNode *node, NSString *cmd)
|
||||
{
|
||||
if (node.ownerDocument != document) {
|
||||
[NSException raise:HTMLKitWrongDocumentError
|
||||
format:@"%@: Invalid Node Error, %@ is not in the same document.",
|
||||
cmd, node];
|
||||
}
|
||||
}
|
||||
|
||||
NS_INLINE void CheckValidBoundaryNodeType(HTMLNode *node, NSString *cmd)
|
||||
{
|
||||
if (node == nil || node.nodeType == HTMLNodeDocumentType) {
|
||||
[NSException raise:HTMLKitInvalidNodeTypeError
|
||||
format:@"%@: Invalid Node Type Error, %@ is not a valid range boundary node.",
|
||||
cmd, node];
|
||||
}
|
||||
}
|
||||
|
||||
NS_INLINE void CheckValidBoundaryOffset(HTMLNode *node, NSUInteger offset, NSString *cmd)
|
||||
{
|
||||
if (node.length < offset) {
|
||||
[NSException raise:HTMLKitIndexSizeError
|
||||
format:@"%@: Index Size Error, invalid index %lu for range boundary node %@.",
|
||||
cmd, (unsigned long)offset, node];
|
||||
}
|
||||
}
|
||||
|
||||
NS_INLINE void CheckValidDocument(HTMLRange *lhs, HTMLRange *rhs, NSString *cmd)
|
||||
{
|
||||
if (lhs.rootNode != rhs.rootNode) {
|
||||
[NSException raise:HTMLKitWrongDocumentError
|
||||
format:@"%@: Wrong Document Error, ranges %@ and %@ are not in the same document.",
|
||||
cmd, lhs, rhs];
|
||||
}
|
||||
}
|
||||
|
||||
NS_INLINE NSComparisonResult CompareBoundaries(HTMLNode *startNode, NSUInteger startOffset, HTMLNode *endNode, NSUInteger endOffset)
|
||||
{
|
||||
if (startNode == endNode) {
|
||||
if (startOffset == endOffset) {
|
||||
return NSOrderedSame;
|
||||
} else if (startOffset < endOffset) {
|
||||
return NSOrderedAscending;
|
||||
} else {
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
}
|
||||
|
||||
HTMLDocumentPosition position = [startNode compareDocumentPositionWithNode:endNode];
|
||||
if ((position & HTMLDocumentPositionFollowing) == HTMLDocumentPositionFollowing) {
|
||||
if (CompareBoundaries(endNode, endOffset, startNode, startOffset) == NSOrderedAscending) {
|
||||
return NSOrderedDescending;
|
||||
} else {
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
}
|
||||
|
||||
if ((position & HTMLDocumentPositionContains) == HTMLDocumentPositionContains) {
|
||||
HTMLNode *child = endNode;
|
||||
while (child.parentNode != startNode) {
|
||||
child = child.parentNode;
|
||||
}
|
||||
if (child.index < startOffset) {
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
}
|
||||
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
|
||||
- (void)setStartNode:(HTMLNode *)node startOffset:(NSUInteger)offset
|
||||
{
|
||||
CheckValidBoundaryNode(_ownerDocument, node, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckValidBoundaryNodeType(node, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckValidBoundaryOffset(node, offset, NSStringFromSelector(_cmd));
|
||||
|
||||
if (self.rootNode != node.rootNode ||
|
||||
CompareBoundaries(node, offset, _endContainer, _endOffset) == NSOrderedDescending) {
|
||||
_endContainer = node;
|
||||
_endOffset = offset;
|
||||
}
|
||||
|
||||
_startContainer = node;
|
||||
_startOffset = offset;
|
||||
}
|
||||
|
||||
- (void)setEndNode:(HTMLNode *)node endOffset:(NSUInteger)offset
|
||||
{
|
||||
CheckValidBoundaryNode(_ownerDocument, node, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckValidBoundaryNodeType(node, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckValidBoundaryOffset(node, offset, NSStringFromSelector(_cmd));
|
||||
|
||||
if (self.rootNode != node.rootNode ||
|
||||
CompareBoundaries(node, offset, _startContainer, _startOffset) == NSOrderedAscending) {
|
||||
_startContainer = node;
|
||||
_startOffset = offset;
|
||||
}
|
||||
|
||||
_endContainer = node;
|
||||
_endOffset = offset;
|
||||
}
|
||||
|
||||
- (void)setStartBeforeNode:(HTMLNode *)node
|
||||
{
|
||||
HTMLNode *parent = node.parentNode;
|
||||
[self setStartNode:parent startOffset:node.index];
|
||||
}
|
||||
|
||||
- (void)setStartAfterNode:(HTMLNode *)node
|
||||
{
|
||||
HTMLNode *parent = node.parentNode;
|
||||
[self setStartNode:parent startOffset:node.index + 1];
|
||||
}
|
||||
|
||||
- (void)setEndBeforeNode:(HTMLNode *)node
|
||||
{
|
||||
HTMLNode *parent = node.parentNode;
|
||||
[self setEndNode:parent endOffset:node.index];
|
||||
}
|
||||
|
||||
- (void)setEndAfterNode:(HTMLNode *)node
|
||||
{
|
||||
HTMLNode *parent = node.parentNode;
|
||||
[self setEndNode:parent endOffset:node.index + 1];
|
||||
}
|
||||
|
||||
- (void)collapseToStart
|
||||
{
|
||||
[self setEndNode:_startContainer endOffset:_startOffset];
|
||||
}
|
||||
|
||||
- (void)collapseToEnd
|
||||
{
|
||||
[self setStartNode:_endContainer startOffset:_endOffset];
|
||||
}
|
||||
|
||||
- (void)selectNode:(HTMLNode *)node
|
||||
{
|
||||
HTMLNode *parent = node.parentNode;
|
||||
[self setStartNode:parent startOffset:node.index];
|
||||
[self setEndNode:parent endOffset:node.index + 1];
|
||||
}
|
||||
|
||||
- (void)selectNodeContents:(HTMLNode *)node
|
||||
{
|
||||
[self setStartNode:node startOffset:0];
|
||||
[self setEndNode:node endOffset:node.length];
|
||||
}
|
||||
|
||||
- (NSComparisonResult)compareBoundaryPoints:(HTMLRangeComparisonMethod)method sourceRange:(HTMLRange *)sourceRange
|
||||
{
|
||||
CheckValidDocument(self, sourceRange, NSStringFromSelector(_cmd));
|
||||
|
||||
switch (method) {
|
||||
case HTMLRangeComparisonMethodStartToStart:
|
||||
return CompareBoundaries(_startContainer, _startOffset, sourceRange.startContainer, sourceRange.startOffset);
|
||||
case HTMLRangeComparisonMethodStartToEnd:
|
||||
return CompareBoundaries(_endContainer, _endOffset, sourceRange.startContainer, sourceRange.startOffset);
|
||||
case HTMLRangeComparisonMethodEndToEnd:
|
||||
return CompareBoundaries(_endContainer, _endOffset, sourceRange.endContainer, sourceRange.endOffset);
|
||||
case HTMLRangeComparisonMethodEndToStart:
|
||||
return CompareBoundaries(_startContainer, _startOffset, sourceRange.endContainer, sourceRange.endOffset);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Containment
|
||||
|
||||
- (NSComparisonResult)comparePoint:(HTMLNode *)node offset:(NSUInteger)offset
|
||||
{
|
||||
CheckValidBoundaryNode(_ownerDocument, node, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckValidBoundaryNodeType(node, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckValidBoundaryOffset(node, offset, NSStringFromSelector(_cmd));
|
||||
|
||||
if (CompareBoundaries(node, offset, _startContainer, _startOffset) == NSOrderedAscending) {
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
|
||||
if (CompareBoundaries(node, offset, _endContainer, _endOffset) == NSOrderedDescending) {
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
return NSOrderedSame;
|
||||
}
|
||||
|
||||
- (BOOL)containsPoint:(HTMLNode *)node offset:(NSUInteger)offset
|
||||
{
|
||||
return [self comparePoint:node offset:offset] == NSOrderedSame;
|
||||
}
|
||||
|
||||
- (BOOL)intersectsNode:(HTMLNode *)node
|
||||
{
|
||||
if (self.rootNode != node.rootNode) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
HTMLNode *parent = node.parentNode;
|
||||
if (parent == nil) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSUInteger offset = node.index;
|
||||
if (CompareBoundaries(parent, offset, _endContainer, _endOffset) == NSOrderedAscending &&
|
||||
CompareBoundaries(parent, offset + 1, _startContainer, _startOffset) == NSOrderedDescending) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)containsNode:(HTMLNode *)node
|
||||
{
|
||||
return CompareBoundaries(_startContainer, _startOffset, node, 0) == NSOrderedAscending &&
|
||||
CompareBoundaries(_endContainer, _endOffset, node, node.length) == NSOrderedDescending;
|
||||
}
|
||||
|
||||
- (BOOL)partiallyContainsNode:(HTMLNode *)node
|
||||
{
|
||||
return [GetAncestorNodes(_startContainer) containsObject:node] || [GetAncestorNodes(_endContainer) containsObject:node];
|
||||
}
|
||||
|
||||
- (NSArray *)containedNodes:(HTMLNode *)commonAncestor
|
||||
{
|
||||
NSMutableArray *containedNodes = [NSMutableArray array];
|
||||
[commonAncestor.childNodes enumerateObjectsUsingBlock:^(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (node.nodeType == HTMLNodeDocumentType) {
|
||||
[NSException raise:HTMLKitHierarchyRequestError format:@"Hierarchy Request Error, encountered a DOCTYPE contained in range: %@", self];
|
||||
}
|
||||
if ([self containsNode:node]) {
|
||||
[containedNodes addObject:node];
|
||||
}
|
||||
}];
|
||||
|
||||
return containedNodes;
|
||||
}
|
||||
|
||||
#pragma mark - Update Callbacks
|
||||
|
||||
- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
|
||||
{
|
||||
if (_startContainer == node && _startOffset > offset) {
|
||||
if (_startOffset <= offset + length) {
|
||||
_startOffset = offset;
|
||||
} else {
|
||||
_startOffset = _startOffset - length;
|
||||
}
|
||||
}
|
||||
if (_endContainer == node && _endOffset > offset) {
|
||||
if (_endOffset <= offset + length) {
|
||||
_endOffset = offset;
|
||||
} else {
|
||||
_endOffset = _endOffset - length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
|
||||
{
|
||||
if (_startContainer == node && _startOffset > offset) {
|
||||
_startOffset = _startOffset + length;
|
||||
}
|
||||
if (_endContainer == node && _endOffset > offset) {
|
||||
_endOffset = _endOffset + length;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didInsertNewTextNode:(HTMLText *)newNode intoParent:(HTMLNode *)parent afterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset
|
||||
{
|
||||
if (_startContainer == node && _startOffset > offset) {
|
||||
_startContainer = newNode;
|
||||
_startOffset -= offset;
|
||||
}
|
||||
|
||||
if (_endContainer == node && _endOffset > offset) {
|
||||
_endContainer = newNode;
|
||||
_endOffset -= offset;
|
||||
}
|
||||
|
||||
if (_startContainer == parent && _startOffset == node.index + 1) {
|
||||
_startOffset += 1;
|
||||
}
|
||||
|
||||
if (_endContainer == parent && _endOffset == node.index + 1) {
|
||||
_endOffset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clampRangesAfterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset
|
||||
{
|
||||
if (_startContainer == node && _startOffset > offset) {
|
||||
_startOffset = offset;
|
||||
}
|
||||
|
||||
if (_endContainer == node && _endOffset > offset) {
|
||||
_endOffset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode withOldParent:(HTMLNode *)oldParent andOldPreviousSibling:(HTMLNode *)oldPreviousSibling
|
||||
{
|
||||
NSUInteger oldIndex = oldPreviousSibling.index + 1;
|
||||
|
||||
if ([_startContainer containsNode:oldNode]) {
|
||||
[self setStartNode:oldNode startOffset:oldIndex];
|
||||
}
|
||||
|
||||
if ([_endContainer containsNode:oldNode]) {
|
||||
[self setEndNode:oldNode endOffset:oldIndex];
|
||||
}
|
||||
|
||||
if (_startContainer == oldParent && _startOffset > oldIndex) {
|
||||
_startOffset -= 1;
|
||||
}
|
||||
|
||||
if (_endContainer == oldParent && _endOffset > oldIndex) {
|
||||
_endOffset -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Mutations
|
||||
|
||||
NS_INLINE HTMLNode * GetHighestPartiallyContainedChild(HTMLNode *node, HTMLNode *root)
|
||||
{
|
||||
if (node == root) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
while (node.parentNode != root) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
NS_INLINE HTMLCharacterData * CloneCharachterData(HTMLNode *node, NSUInteger start, NSUInteger length, BOOL delete)
|
||||
{
|
||||
HTMLCharacterData *clone = (HTMLCharacterData *)[node copy];
|
||||
NSRange range = NSMakeRange(start, length);
|
||||
[clone setData:[clone.data substringWithRange:range]];
|
||||
|
||||
if (delete) {
|
||||
[(HTMLCharacterData *)node deleteDataInRange:range];
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
- (void)deleteContents
|
||||
{
|
||||
if (self.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_startContainer == _endContainer && [_startContainer isKindOfClass:[HTMLCharacterData class]]) {
|
||||
[(HTMLCharacterData *)_startContainer deleteDataInRange:NSMakeRange(_startOffset, _endOffset - _startOffset)];
|
||||
return;
|
||||
}
|
||||
|
||||
HTMLNode *commonAncestor = self.commonAncestorContainer;
|
||||
|
||||
NSMutableArray *containedNodes = [NSMutableArray array];
|
||||
|
||||
HTMLNode *node = FollowingNode(_startContainer, commonAncestor);
|
||||
while (node) {
|
||||
if ([self containsNode:node]) {
|
||||
[containedNodes addObject:node];
|
||||
node = FollowingNodeSkippingChildren(node, commonAncestor);
|
||||
} else {
|
||||
node = FollowingNode(node, commonAncestor);
|
||||
}
|
||||
}
|
||||
|
||||
HTMLNode *newNode = _startContainer;
|
||||
NSUInteger newOffset = _startOffset;
|
||||
|
||||
if (![_startContainer containsNode:_endContainer]) {
|
||||
HTMLNode *referenceNode = _startContainer;
|
||||
while (referenceNode.parentNode) {
|
||||
if ([referenceNode.parentNode containsNode:_endContainer]) {
|
||||
newNode = referenceNode.parentNode;
|
||||
newOffset = referenceNode.index + 1;
|
||||
break;
|
||||
}
|
||||
referenceNode = referenceNode.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
if ([_startContainer isKindOfClass:[HTMLCharacterData class]]) {
|
||||
[(HTMLCharacterData *)_startContainer deleteDataInRange:NSMakeRange(_startOffset, _startContainer.length - _startOffset)];
|
||||
}
|
||||
|
||||
for (HTMLNode *node in containedNodes) {
|
||||
[node removeFromParentNode];
|
||||
}
|
||||
|
||||
if ([_endContainer isKindOfClass:[HTMLCharacterData class]]) {
|
||||
[(HTMLCharacterData *)_endContainer deleteDataInRange:NSMakeRange(0, _endOffset)];
|
||||
}
|
||||
|
||||
[self setStartNode:newNode startOffset:newOffset];
|
||||
[self setEndNode:newNode endOffset:newOffset];
|
||||
}
|
||||
|
||||
- (HTMLDocumentFragment *)extractContents
|
||||
{
|
||||
HTMLDocumentFragment *fragment = [[HTMLDocumentFragment alloc] initWithDocument:_ownerDocument];
|
||||
|
||||
// Nothing todo
|
||||
if (self.isCollapsed) {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
// Same character data container, handle that and return
|
||||
if (_startContainer == _endContainer && [_startContainer isKindOfClass:[HTMLCharacterData class]]) {
|
||||
HTMLCharacterData *clone = CloneCharachterData(_startContainer, _startOffset, _endOffset - _startOffset, YES);
|
||||
[fragment appendNode:clone];
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
HTMLNode *commonAncestor = self.commonAncestorContainer;
|
||||
HTMLNode *firstPartiallyContainedChild = GetHighestPartiallyContainedChild(_startContainer, commonAncestor);
|
||||
HTMLNode *lastPartiallyContainedChild = GetHighestPartiallyContainedChild(_endContainer, commonAncestor);
|
||||
NSArray *containedNodes = [self containedNodes:commonAncestor];
|
||||
|
||||
HTMLNode *newNode = _startContainer;
|
||||
NSUInteger newOffset = _startOffset;
|
||||
|
||||
if (![_startContainer containsNode:_endContainer]) {
|
||||
HTMLNode *referenceNode = _startContainer;
|
||||
while (referenceNode.parentNode) {
|
||||
if ([referenceNode.parentNode containsNode:_endContainer]) {
|
||||
newNode = referenceNode.parentNode;
|
||||
newOffset = referenceNode.index + 1;
|
||||
break;
|
||||
}
|
||||
referenceNode = referenceNode.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
if ([firstPartiallyContainedChild isKindOfClass:[HTMLCharacterData class]]) {
|
||||
HTMLCharacterData *clone = CloneCharachterData(_startContainer, _startOffset, _startContainer.length - _startOffset, YES);
|
||||
[fragment appendNode:clone];
|
||||
} else if (firstPartiallyContainedChild != nil) {
|
||||
HTMLNode *clone = [firstPartiallyContainedChild copy];
|
||||
[fragment appendNode:clone];
|
||||
|
||||
HTMLRange *subRange = [[HTMLRange alloc] initWithDocument:_ownerDocument
|
||||
startContainer:_startContainer
|
||||
startOffset:_startOffset
|
||||
endContainer:firstPartiallyContainedChild
|
||||
endOffset:firstPartiallyContainedChild.length];
|
||||
HTMLDocumentFragment *subFragment = [subRange extractContents];
|
||||
[clone appendNode:subFragment];
|
||||
}
|
||||
|
||||
for (HTMLNode *node in containedNodes) {
|
||||
[fragment appendNode:node];
|
||||
}
|
||||
|
||||
if ([lastPartiallyContainedChild isKindOfClass:[HTMLCharacterData class]]) {
|
||||
HTMLCharacterData *clone = CloneCharachterData(_endContainer, 0, _endOffset, YES);
|
||||
[fragment appendNode:clone];
|
||||
} else if (lastPartiallyContainedChild != nil) {
|
||||
HTMLNode *clone = [lastPartiallyContainedChild copy];
|
||||
[fragment appendNode:clone];
|
||||
|
||||
HTMLRange *subRange = [[HTMLRange alloc] initWithDocument:_ownerDocument
|
||||
startContainer:lastPartiallyContainedChild
|
||||
startOffset:0
|
||||
endContainer:_endContainer
|
||||
endOffset:_endOffset];
|
||||
HTMLDocumentFragment *subFragment = [subRange extractContents];
|
||||
[clone appendNode:subFragment];
|
||||
}
|
||||
|
||||
[self setStartNode:newNode startOffset:newOffset];
|
||||
[self setEndNode:newNode endOffset:newOffset];
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
- (HTMLDocumentFragment *)cloneContents
|
||||
{
|
||||
HTMLDocumentFragment *fragment = [[HTMLDocumentFragment alloc] initWithDocument:_ownerDocument];
|
||||
|
||||
// Nothing todo
|
||||
if (self.isCollapsed) {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
// Same character data container, handle that and return
|
||||
if (_startContainer == _endContainer && [_startContainer isKindOfClass:[HTMLCharacterData class]]) {
|
||||
HTMLCharacterData *clone = CloneCharachterData(_startContainer, _startOffset, _endOffset - _startOffset, NO);
|
||||
[fragment appendNode:clone];
|
||||
return fragment;
|
||||
}
|
||||
|
||||
HTMLNode *commonAncestor = self.commonAncestorContainer;
|
||||
HTMLNode *firstPartiallyContainedChild = GetHighestPartiallyContainedChild(_startContainer, commonAncestor);
|
||||
HTMLNode *lastPartiallyContainedChild = GetHighestPartiallyContainedChild(_endContainer, commonAncestor);
|
||||
NSArray *containedNodes = [self containedNodes:commonAncestor];
|
||||
|
||||
if ([firstPartiallyContainedChild isKindOfClass:[HTMLCharacterData class]]) {
|
||||
HTMLCharacterData *clone = CloneCharachterData(_startContainer, _startOffset, _startContainer.length - _startOffset, NO);
|
||||
[fragment appendNode:clone];
|
||||
} else if (firstPartiallyContainedChild != nil) {
|
||||
HTMLNode *clone = [firstPartiallyContainedChild copy];
|
||||
[fragment appendNode:clone];
|
||||
|
||||
HTMLRange *subRange = [[HTMLRange alloc] initWithDocument:_ownerDocument
|
||||
startContainer:_startContainer
|
||||
startOffset:_startOffset
|
||||
endContainer:firstPartiallyContainedChild
|
||||
endOffset:firstPartiallyContainedChild.length];
|
||||
HTMLDocumentFragment *subFragment = [subRange cloneContents];
|
||||
[clone appendNode:subFragment];
|
||||
}
|
||||
|
||||
for (HTMLNode *node in containedNodes) {
|
||||
HTMLNode *clone = [node cloneNodeDeep:YES];
|
||||
[fragment appendNode:clone];
|
||||
}
|
||||
|
||||
if ([lastPartiallyContainedChild isKindOfClass:[HTMLCharacterData class]]) {
|
||||
HTMLCharacterData *clone = CloneCharachterData(_endContainer, 0, _endOffset, NO);
|
||||
[fragment appendNode:clone];
|
||||
} else if (lastPartiallyContainedChild != nil) {
|
||||
HTMLNode *clone = [lastPartiallyContainedChild copy];
|
||||
[fragment appendNode:clone];
|
||||
|
||||
HTMLRange *subRange = [[HTMLRange alloc] initWithDocument:_ownerDocument
|
||||
startContainer:lastPartiallyContainedChild
|
||||
startOffset:0
|
||||
endContainer:_endContainer
|
||||
endOffset:_endOffset];
|
||||
HTMLDocumentFragment *subFragment = [subRange cloneContents];
|
||||
[clone appendNode:subFragment];
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
#pragma mark - Insertion & Surround
|
||||
|
||||
NS_INLINE void CheckValidInsertionNode(HTMLNode *startContainer, HTMLNode *node, NSString *cmd)
|
||||
{
|
||||
if (startContainer == node || startContainer.nodeType == HTMLNodeComment ||
|
||||
(startContainer.nodeType == HTMLNodeText && startContainer.parentNode == nil)) {
|
||||
[NSException raise:HTMLKitHierarchyRequestError
|
||||
format:@"%@: Hierarchy Request Error, cannot insert node into range: %@", cmd, node];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)insertNode:(HTMLNode *)node
|
||||
{
|
||||
CheckValidInsertionNode(_startContainer, node, NSStringFromSelector(_cmd));
|
||||
|
||||
HTMLNode *referenceNode = nil;
|
||||
|
||||
if (_startContainer.nodeType == HTMLNodeText) {
|
||||
referenceNode = _startContainer;
|
||||
} else {
|
||||
referenceNode = [_startContainer childNodeAtIndex:_startOffset];
|
||||
}
|
||||
|
||||
HTMLNode *parent = _startContainer;
|
||||
if (referenceNode != nil) {
|
||||
parent = referenceNode.parentNode;
|
||||
}
|
||||
|
||||
if (_startContainer.nodeType == HTMLNodeText) {
|
||||
referenceNode = [(HTMLText *)_startContainer splitTextAtOffset:_startOffset];
|
||||
}
|
||||
|
||||
if (node == referenceNode) {
|
||||
referenceNode = referenceNode.nextSibling;
|
||||
}
|
||||
|
||||
[node removeFromParentNode];
|
||||
|
||||
NSUInteger newOffset = referenceNode ? referenceNode.index : parent.length;
|
||||
newOffset += (node.nodeType == HTMLNodeDocumentFragment) ? node.length : 1;
|
||||
|
||||
[parent insertNode:node beforeChildNode:referenceNode];
|
||||
|
||||
if (self.isCollapsed) {
|
||||
[self setEndNode:parent endOffset:newOffset];
|
||||
}
|
||||
}
|
||||
|
||||
NS_INLINE void CheckValidSurroundState(HTMLRange *range, NSString *cmd)
|
||||
{
|
||||
for (HTMLNode *node in GetAncestorNodes(range.startContainer)) {
|
||||
if ([node containsNode:range.endContainer]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.nodeType != HTMLNodeText) {
|
||||
[NSException raise:HTMLKitInvalidStateError
|
||||
format:@"%@: Invalid State Error, cannot surround range with a partially-contaied non-text node.", cmd];
|
||||
}
|
||||
};
|
||||
|
||||
for (HTMLNode *node in GetAncestorNodes(range.endContainer)) {
|
||||
if ([node containsNode:range.startContainer]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.nodeType != HTMLNodeText) {
|
||||
[NSException raise:HTMLKitInvalidNodeTypeError
|
||||
format:@"%@: Invalid State Error, cannot surround range with a partially-contaied non-text node.", cmd];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
NS_INLINE void CheckValidSurroundNodeType(HTMLNode *node, NSString *cmd)
|
||||
{
|
||||
if (node == nil || node.nodeType == HTMLNodeDocumentType || node.nodeType == HTMLNodeDocument ||
|
||||
node.nodeType == HTMLNodeDocumentFragment) {
|
||||
[NSException raise:HTMLKitInvalidNodeTypeError
|
||||
format:@"%@: Invalid Node Type Error, %@ is not a valid new parent for a range.",
|
||||
cmd, node];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)surroundContents:(HTMLNode *)newParent
|
||||
{
|
||||
CheckValidSurroundState(self, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckValidSurroundNodeType(newParent, NSStringFromSelector(_cmd));
|
||||
|
||||
HTMLDocumentFragment *fragment = [self extractContents];
|
||||
[newParent removeAllChildNodes];
|
||||
|
||||
[self insertNode:newParent];
|
||||
[newParent appendNode:fragment];
|
||||
[self selectNode:newParent];
|
||||
}
|
||||
|
||||
#pragma mark - Description & Stringifier
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p (%@, %lu), (%@, %lu)>", self.class, self,
|
||||
_startContainer, (unsigned long)_startOffset,
|
||||
_endContainer, (unsigned long)_endOffset];
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
HTMLNode *lastNode = nil;
|
||||
if ([_endContainer isKindOfClass:[HTMLCharacterData class]]) {
|
||||
lastNode = FollowingNodeSkippingChildren(_endContainer, _ownerDocument);
|
||||
} else if (_endContainer.childNodesCount > _endOffset) {
|
||||
lastNode = [_endContainer childNodeAtIndex:_endOffset];
|
||||
} else {
|
||||
lastNode = FollowingNodeSkippingChildren(_endContainer, _ownerDocument);
|
||||
}
|
||||
|
||||
NSMutableString *content = [NSMutableString string];
|
||||
for (HTMLNode *node = _startContainer; node != lastNode; node = FollowingNode(node, _ownerDocument)) {
|
||||
if (node.nodeType == HTMLNodeText) {
|
||||
HTMLText *text = (HTMLText *)node;
|
||||
|
||||
if (node == _startContainer) {
|
||||
NSString *string = [text substringDataWithRange:NSMakeRange(_startOffset, _startContainer.length - _startOffset)];
|
||||
[content appendString:string];
|
||||
} else if (node == _endContainer) {
|
||||
NSString *string = [text substringDataWithRange:NSMakeRange(0, _endOffset)];
|
||||
[content appendString:string];
|
||||
} else {
|
||||
[content appendString:text.data];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,7 +14,6 @@
|
||||
@interface HTMLStackOfOpenElements ()
|
||||
{
|
||||
NSMutableArray *_stack;
|
||||
NSDictionary *_specificScopeElementTypes;
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -27,26 +26,6 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_stack = [NSMutableArray new];
|
||||
_specificScopeElementTypes = @{
|
||||
@"applet": @(HTMLNamespaceHTML),
|
||||
@"caption": @(HTMLNamespaceHTML),
|
||||
@"html": @(HTMLNamespaceHTML),
|
||||
@"table": @(HTMLNamespaceHTML),
|
||||
@"td": @(HTMLNamespaceHTML),
|
||||
@"th": @(HTMLNamespaceHTML),
|
||||
@"marquee": @(HTMLNamespaceHTML),
|
||||
@"object": @(HTMLNamespaceHTML),
|
||||
@"template": @(HTMLNamespaceHTML),
|
||||
@"mi": @(HTMLNamespaceMathML),
|
||||
@"mo": @(HTMLNamespaceMathML),
|
||||
@"mn": @(HTMLNamespaceMathML),
|
||||
@"ms": @(HTMLNamespaceMathML),
|
||||
@"mtext": @(HTMLNamespaceMathML),
|
||||
@"annotation-xml": @(HTMLNamespaceMathML),
|
||||
@"foreignObject": @(HTMLNamespaceSVG),
|
||||
@"desc": @(HTMLNamespaceSVG),
|
||||
@"title": @(HTMLNamespaceSVG)
|
||||
};
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -195,82 +174,148 @@
|
||||
|
||||
#pragma mark - Element Scope
|
||||
|
||||
NS_INLINE BOOL IsSpecificScopeElement(HTMLElement *element)
|
||||
{
|
||||
switch (element.htmlNamespace) {
|
||||
case HTMLNamespaceHTML:
|
||||
return [element.tagName isEqualToAny:@"applet", @"caption", @"html", @"table", @"td", @"th", @"marquee", @"object", @"template", nil];
|
||||
case HTMLNamespaceMathML:
|
||||
return [element.tagName isEqualToAny:@"mi", @"mo", @"mn", @"ms", @"mtext", @"annotation-xml", nil];
|
||||
case HTMLNamespaceSVG:
|
||||
return [element.tagName isEqualToAny:@"foreignObject", @"desc", @"title", nil];
|
||||
}
|
||||
}
|
||||
|
||||
NS_INLINE BOOL IsHeaderElement(HTMLElement *element)
|
||||
{
|
||||
if (element.htmlNamespace != HTMLNamespaceHTML) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [element.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil];
|
||||
}
|
||||
|
||||
NS_INLINE BOOL IsTableScopeElement(HTMLElement *element)
|
||||
{
|
||||
if (element.htmlNamespace != HTMLNamespaceHTML) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [element.tagName isEqualToAny:@"html", @"table", @"template", nil];
|
||||
}
|
||||
|
||||
NS_INLINE BOOL IsListItemScopeElement(HTMLElement *element)
|
||||
{
|
||||
if (element.htmlNamespace != HTMLNamespaceHTML) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [element.tagName isEqualToAny:@"ol", @"ul", nil];
|
||||
}
|
||||
|
||||
NS_INLINE BOOL IsSelectScopeElement(HTMLElement *element)
|
||||
{
|
||||
if (element.htmlNamespace != HTMLNamespaceHTML) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return ![element.tagName isEqualToString:@"optgroup"] && ![element.tagName isEqualToString:@"option"];
|
||||
}
|
||||
|
||||
NS_INLINE BOOL IsButtonScopeElement(HTMLElement *element)
|
||||
{
|
||||
if (element.htmlNamespace != HTMLNamespaceHTML) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [element.tagName isEqualToString:@"button"];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName;
|
||||
{
|
||||
return [self hasAnyElementInSpecificScopeWithTagNames:@[tagName] andElementTypes:_specificScopeElementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames
|
||||
{
|
||||
return [self hasAnyElementInSpecificScopeWithTagNames:tagNames andElementTypes:_specificScopeElementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:_specificScopeElementTypes];
|
||||
[elementTypes addEntriesFromDictionary:@{@"ol": @(HTMLNamespaceHTML),
|
||||
@"ul": @(HTMLNamespaceHTML)}];
|
||||
|
||||
return [self hasElementInSpecificScopeWithTagName:tagName
|
||||
andElementTypes:elementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:_specificScopeElementTypes];
|
||||
[elementTypes addEntriesFromDictionary:@{@"button": @(HTMLNamespaceHTML)}];
|
||||
|
||||
return [self hasElementInSpecificScopeWithTagName:tagName
|
||||
andElementTypes:elementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
return [self hasElementInSpecificScopeWithTagName:tagName
|
||||
andElementTypes:@{@"html": @(HTMLNamespaceHTML),
|
||||
@"table": @(HTMLNamespaceHTML),
|
||||
@"template": @(HTMLNamespaceHTML)}];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames
|
||||
{
|
||||
return [self hasAnyElementInSpecificScopeWithTagNames:tagNames
|
||||
andElementTypes:@{@"html": @(HTMLNamespaceHTML),
|
||||
@"table": @(HTMLNamespaceHTML),
|
||||
@"template": @(HTMLNamespaceHTML)}];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if ([node.tagName isEqualToString:tagName]) {
|
||||
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
|
||||
return node;
|
||||
}
|
||||
if (!(node.htmlNamespace == HTMLNamespaceHTML &&
|
||||
[node.tagName isEqualToAny:@"optgroup", @"option", nil])) {
|
||||
if (IsSpecificScopeElement(node)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInSpecificScopeWithTagName:(NSString *)tagName
|
||||
andElementTypes:(NSDictionary *)elementTypes
|
||||
{
|
||||
return [self hasAnyElementInSpecificScopeWithTagNames:@[tagName] andElementTypes:elementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasAnyElementInSpecificScopeWithTagNames:(NSArray *)tagNames
|
||||
andElementTypes:(NSDictionary *)elementTypes
|
||||
- (HTMLElement *)hasHeaderElementInScope
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if ([tagNames containsObject:node.tagName]) {
|
||||
NSNumber *namespace = elementTypes[node.tagName] ?: @(HTMLNamespaceHTML);
|
||||
if ([namespace isEqual:@(node.htmlNamespace)]) {
|
||||
return node;
|
||||
}
|
||||
if (IsHeaderElement(node)) {
|
||||
return node;
|
||||
}
|
||||
if ([elementTypes[node.tagName] isEqual:@(node.htmlNamespace)]) {
|
||||
if (IsSpecificScopeElement(node)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
|
||||
return node;
|
||||
}
|
||||
if (IsTableScopeElement(node)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if (node.htmlNamespace == HTMLNamespaceHTML && [tagNames containsObject:node.tagName]) {
|
||||
return node;
|
||||
}
|
||||
if (IsTableScopeElement(node)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
|
||||
return node;
|
||||
}
|
||||
if (IsSpecificScopeElement(node) || IsListItemScopeElement(node)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
|
||||
return node;
|
||||
}
|
||||
if (IsSpecificScopeElement(node) || IsButtonScopeElement(node)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
|
||||
return node;
|
||||
}
|
||||
if (IsSelectScopeElement(node)) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
|
||||
#import "HTMLTemplate.h"
|
||||
#import "HTMLDocument.h"
|
||||
|
||||
@interface HTMLNode (Private)
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@end
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
@implementation HTMLTemplate
|
||||
|
||||
|
||||
+42
-28
@@ -9,7 +9,9 @@
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
#import "HTMLCharacterData+Private.h"
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
#import "HTMLDocument+Private.h"
|
||||
|
||||
@implementation HTMLText
|
||||
|
||||
@@ -20,35 +22,48 @@
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data
|
||||
{
|
||||
self = [super initWithName:@"#text" type:HTMLNodeText];
|
||||
if (self) {
|
||||
_data = [[NSMutableString alloc] initWithString:data ?: @""];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return [self.data copy];
|
||||
}
|
||||
|
||||
- (void)setTextContent:(NSString *)textContent
|
||||
{
|
||||
[self.data setString:textContent ?: @""];
|
||||
return [super initWithName:@"#text" type:HTMLNodeText data:data];
|
||||
}
|
||||
|
||||
- (void)appendString:(NSString *)string
|
||||
{
|
||||
[self.data appendString:string];
|
||||
[self appendData:string];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
NS_INLINE void CheckValidOffset(HTMLNode *node, NSUInteger offset, NSString *cmd)
|
||||
{
|
||||
HTMLText *copy = [super copyWithZone:zone];
|
||||
copy.data = self.data;
|
||||
return copy;
|
||||
if (offset > node.length) {
|
||||
[NSException raise:HTMLKitIndexSizeError
|
||||
format:@"%@: Index Size Error, invalid offset %lu for splitting text node %@.",
|
||||
cmd, (unsigned long)offset, node];
|
||||
}
|
||||
}
|
||||
|
||||
- (HTMLText *)splitTextAtOffset:(NSUInteger)offset
|
||||
{
|
||||
CheckValidOffset(self, offset, NSStringFromSelector(_cmd));
|
||||
|
||||
NSUInteger length = self.length;
|
||||
NSUInteger count = length - offset;
|
||||
NSRange range = NSMakeRange(offset, count);
|
||||
|
||||
NSString *newData = [self.data substringWithRange:range];
|
||||
HTMLText *newNode = [[HTMLText alloc] initWithData:newData];
|
||||
[self.ownerDocument adoptNode:newNode];
|
||||
|
||||
HTMLNode *parent = self.parentNode;
|
||||
if (parent != nil) {
|
||||
[parent insertNode:newNode beforeChildNode:self.nextSibling];
|
||||
[self.ownerDocument didInsertNewTextNode:newNode intoParent:parent afterSplittingTextNode:self atOffset:offset];
|
||||
}
|
||||
|
||||
[self deleteDataInRange:range];
|
||||
|
||||
if (parent != nil) {
|
||||
[self.ownerDocument clampRangesAfterSplittingTextNode:self atOffset:offset];
|
||||
}
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
#pragma mark - Serialization
|
||||
@@ -59,12 +74,11 @@
|
||||
@"plaintext", @"noscript", nil]) {
|
||||
return self.data;
|
||||
} else {
|
||||
NSRange range = NSMakeRange(0, self.data.length);
|
||||
NSMutableString *escaped = [self.data mutableCopy];
|
||||
[escaped replaceOccurrencesOfString:@"&" withString:@"&" options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"\00A0" withString:@" " options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"<" withString:@"<" options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@">" withString:@">" options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"&" withString:@"&" options:0 range:NSMakeRange(0, escaped.length)];
|
||||
[escaped replaceOccurrencesOfString:@"\00A0" withString:@" " options:0 range:NSMakeRange(0, escaped.length)];
|
||||
[escaped replaceOccurrencesOfString:@"<" withString:@"<" options:0 range:NSMakeRange(0, escaped.length)];
|
||||
[escaped replaceOccurrencesOfString:@">" withString:@">" options:0 range:NSMakeRange(0, escaped.length)];
|
||||
return escaped;
|
||||
}
|
||||
}
|
||||
|
||||
+822
-655
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
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.
|
||||
@discussion 'div:has(p)' matches all <div> elements which have a descendant <p> element.
|
||||
|
||||
@param selector The selector matching a descendant element.
|
||||
@return A new instance of the has-descendant selector.
|
||||
|
||||
@@ -76,7 +76,7 @@ extern NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression
|
||||
@param string The selector string which will be parsed.
|
||||
@return A new instance of a parsed CSS Selector, `nil` if the string is not a valid selector string.
|
||||
*/
|
||||
+ (nullable instancetype)selectorWithString:(NSString *)stirng;
|
||||
+ (nullable instancetype)selectorWithString:(NSString *)string;
|
||||
|
||||
/**
|
||||
Implementations should override this method to provide the selector-sprecific logic for matching elements.
|
||||
|
||||
@@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@see CSSelector
|
||||
*/
|
||||
+ (CSSSelector *)parseSelector:(NSString *)string error:(NSError **)error;
|
||||
+ (nullable CSSSelector *)parseSelector:(NSString *)string error:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@return The universal CSS selector.
|
||||
*/
|
||||
extern CSSSelector * universalSelector();
|
||||
extern CSSSelector * universalSelector(void);
|
||||
|
||||
/**
|
||||
CSS type selector, e.g. 'div', 'p', ...etc.
|
||||
@@ -125,7 +125,7 @@ extern CSSSelector * nthLastOfTypeSelector(CSSNthExpression expression);
|
||||
|
||||
@return Odd-Child selector.
|
||||
*/
|
||||
extern CSSSelector * oddSelector();
|
||||
extern CSSSelector * oddSelector(void);
|
||||
|
||||
/**
|
||||
CSS even-child selector: ':nth-child(even)'
|
||||
@@ -134,49 +134,49 @@ extern CSSSelector * oddSelector();
|
||||
|
||||
@return Even-Child selector.
|
||||
*/
|
||||
extern CSSSelector * evenSlector();
|
||||
extern CSSSelector * evenSlector(void);
|
||||
|
||||
/**
|
||||
CSS first-child selector: ':nth-child(1)'
|
||||
|
||||
@return First-Child selector.
|
||||
*/
|
||||
extern CSSSelector * firstChildSelector();
|
||||
extern CSSSelector * firstChildSelector(void);
|
||||
|
||||
/**
|
||||
CSS first-child selector: ':nth-last-child(1)'
|
||||
|
||||
@return First-Child selector.
|
||||
*/
|
||||
extern CSSSelector * lastChildSelector();
|
||||
extern CSSSelector * lastChildSelector(void);
|
||||
|
||||
/**
|
||||
CSS first-of-type selector: ':nth-first-of-type(1)'
|
||||
|
||||
@return First-Of-Type selector.
|
||||
*/
|
||||
extern CSSSelector * firstOfTypeSelector();
|
||||
extern CSSSelector * firstOfTypeSelector(void);
|
||||
|
||||
/**
|
||||
CSS last-of-type selector: ':nth-last-of-type(1)'
|
||||
|
||||
@return Last-Of-Type selector.
|
||||
*/
|
||||
extern CSSSelector * lastOfTypeSelector();
|
||||
extern CSSSelector * lastOfTypeSelector(void);
|
||||
|
||||
/**
|
||||
CSS only-child selector: ':first-child:last-child'
|
||||
|
||||
@return Only-Child selector.
|
||||
*/
|
||||
extern CSSSelector * onlyChildSelector();
|
||||
extern CSSSelector * onlyChildSelector(void);
|
||||
|
||||
/**
|
||||
CSS only-of-type selector: ':first-of-type:last-of-type'
|
||||
|
||||
@return Only-Of-Type selector.
|
||||
*/
|
||||
extern CSSSelector * onlyOfTypeSelector();
|
||||
extern CSSSelector * onlyOfTypeSelector(void);
|
||||
|
||||
#pragma mark - Combinators
|
||||
|
||||
@@ -225,7 +225,7 @@ 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.
|
||||
@discussion 'div:has(p)' matches all <div> elements which have a descendant <p> element.
|
||||
|
||||
@param selector The selector matching a descendant element.
|
||||
@return A has-descendant selector.
|
||||
|
||||
@@ -15,102 +15,102 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
@return Root element selector: ':root'
|
||||
*/
|
||||
extern CSSSelector * rootSelector();
|
||||
extern CSSSelector * rootSelector(void);
|
||||
|
||||
/**
|
||||
@return Empy element selector: ':empty'
|
||||
*/
|
||||
extern CSSSelector * emptySelector();
|
||||
extern CSSSelector * emptySelector(void);
|
||||
|
||||
/**
|
||||
@return A parent element selector: ':parent'
|
||||
*/
|
||||
extern CSSSelector * parentSelector();
|
||||
extern CSSSelector * parentSelector(void);
|
||||
|
||||
/**
|
||||
@return A button element selector: ':button'
|
||||
*/
|
||||
extern CSSSelector * buttonSelector();
|
||||
extern CSSSelector * buttonSelector(void);
|
||||
|
||||
/**
|
||||
@return A checkbox element selector: ':checkbox'
|
||||
*/
|
||||
extern CSSSelector * checkboxSelector();
|
||||
extern CSSSelector * checkboxSelector(void);
|
||||
|
||||
/**
|
||||
@return A file element selector: ':file'
|
||||
*/
|
||||
extern CSSSelector * fileSelector();
|
||||
extern CSSSelector * fileSelector(void);
|
||||
|
||||
/**
|
||||
@return A header element selector: ':header'
|
||||
*/
|
||||
extern CSSSelector * headerSelector();
|
||||
extern CSSSelector * headerSelector(void);
|
||||
|
||||
/**
|
||||
@return An image element selector: ':image'
|
||||
*/
|
||||
extern CSSSelector * imageSelector();
|
||||
extern CSSSelector * imageSelector(void);
|
||||
|
||||
/**
|
||||
@return A parent element selector: ':parent'
|
||||
*/
|
||||
extern CSSSelector * inputSelector();
|
||||
extern CSSSelector * inputSelector(void);
|
||||
|
||||
/**
|
||||
@return A link element selector: ':link'
|
||||
*/
|
||||
extern CSSSelector * linkSelector();
|
||||
extern CSSSelector * linkSelector(void);
|
||||
|
||||
/**
|
||||
@return A password element selector: ':password'
|
||||
*/
|
||||
extern CSSSelector * passwordSelector();
|
||||
extern CSSSelector * passwordSelector(void);
|
||||
|
||||
/**
|
||||
@return A radio element selector: ':radio'
|
||||
*/
|
||||
extern CSSSelector * radioSelector();
|
||||
extern CSSSelector * radioSelector(void);
|
||||
|
||||
/**
|
||||
@return A reset element selector: ':reset'
|
||||
*/
|
||||
extern CSSSelector * resetSelector();
|
||||
extern CSSSelector * resetSelector(void);
|
||||
|
||||
/**
|
||||
@return A submit element selector: ':submit'
|
||||
*/
|
||||
extern CSSSelector * submitSelector();
|
||||
extern CSSSelector * submitSelector(void);
|
||||
|
||||
/**
|
||||
@return A text element selector: ':text'
|
||||
*/
|
||||
extern CSSSelector * textSelector();
|
||||
extern CSSSelector * textSelector(void);
|
||||
|
||||
/**
|
||||
@return An enabled element selector: ':enabled'
|
||||
*/
|
||||
extern CSSSelector * enabledSelector();
|
||||
extern CSSSelector * enabledSelector(void);
|
||||
|
||||
/**
|
||||
@return A disabled element selector: ':disabled'
|
||||
*/
|
||||
extern CSSSelector * disabledSelector();
|
||||
extern CSSSelector * disabledSelector(void);
|
||||
|
||||
/**
|
||||
@return A checked element selector: ':checked'
|
||||
*/
|
||||
extern CSSSelector * checkedSelector();
|
||||
extern CSSSelector * checkedSelector(void);
|
||||
|
||||
/**
|
||||
@return An optional element selector: ':optional'
|
||||
*/
|
||||
extern CSSSelector * optionalSelector();
|
||||
extern CSSSelector * optionalSelector(void);
|
||||
|
||||
/**
|
||||
@return A required element selector: ':required'
|
||||
*/
|
||||
extern CSSSelector * requiredSelector();
|
||||
extern CSSSelector * requiredSelector(void);
|
||||
|
||||
/**
|
||||
Less-than selector, e.g. 'lt(2)'
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// HTMLCharacterData+Private.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLCharacterData.h"
|
||||
#import "HTMLNode.h"
|
||||
|
||||
/**
|
||||
Private HTML Character Data methods which are not intended for public API.
|
||||
*/
|
||||
@interface HTMLCharacterData ()
|
||||
|
||||
/**
|
||||
Designated initializer of the HTML CharacterData, which, however, should not be used directly. It is intended to be
|
||||
called only by subclasses, i.e. HTMLText and HTMLComment.
|
||||
|
||||
@param name The node's name.
|
||||
@param type The node's type.
|
||||
@param data The node's data string.
|
||||
@return A new instance of a HTML CharacterData.
|
||||
*/
|
||||
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type data:(NSString *)data NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// HTMLCharacterData.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A HTML CharacterData
|
||||
|
||||
https://dom.spec.whatwg.org/#characterdata
|
||||
*/
|
||||
@interface HTMLCharacterData : HTMLNode
|
||||
|
||||
/** @brief The associated data string. */
|
||||
@property (nonatomic, copy, readonly) NSString *data;
|
||||
|
||||
- (void)setData:(NSString *)data;
|
||||
- (void)appendData:(NSString *)data;
|
||||
- (void)insertData:(NSString *)data atOffset:(NSUInteger)offset;
|
||||
- (void)deleteDataInRange:(NSRange)range;
|
||||
- (void)replaceDataInRange:(NSRange)range withData:(NSString *)data;
|
||||
- (NSString *)substringDataWithRange:(NSRange)range;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -6,17 +6,14 @@
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLCharacterData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A HTML Comment node
|
||||
*/
|
||||
@interface HTMLComment : HTMLNode
|
||||
|
||||
/** @brief The comment string. */
|
||||
@property (nonatomic, copy) NSString *data;
|
||||
@interface HTMLComment : HTMLCharacterData
|
||||
|
||||
/**
|
||||
Initializes a new HTML comment node.
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/**
|
||||
Initializes a new comment token.
|
||||
|
||||
@param string The string with which to initialize the token.
|
||||
@param data The string with which to initialize the token.
|
||||
@return A new instance of a comment token.
|
||||
*/
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLDocumentFragment.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLCharacterData.h"
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLTemplate.h"
|
||||
#import "HTMLRange.h"
|
||||
#import "HTMLDOMTokenList.h"
|
||||
#import "HTMLNodeIterator.h"
|
||||
#import "HTMLTreeWalker.h"
|
||||
@@ -22,3 +24,5 @@
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
#import "HTMLNamespaces.h"
|
||||
#import "HTMLQuirksMode.h"
|
||||
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// HTMLDOMUtils.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 03/12/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLDOM.h"
|
||||
|
||||
@class HTMLNode;
|
||||
|
||||
extern HTMLNode * GetCommonAncestorContainer(HTMLNode *nodeA, HTMLNode *nodeB);
|
||||
extern NSArray<HTMLNode *> * GetAncestorNodes(HTMLNode *node);
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// HTMLDocument+Private.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 27/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLCharacterData.h"
|
||||
#import "HTMLNodeIterator.h"
|
||||
#import "HTMLRange.h"
|
||||
#import "HTMLText.h"
|
||||
|
||||
/**
|
||||
Private HTML Document methods which are not intended for public API.
|
||||
*/
|
||||
@interface HTMLDocument ()
|
||||
|
||||
@property (nonatomic, assign) HTMLDocumentReadyState readyState;
|
||||
|
||||
/**
|
||||
Runs the necessary steps after removing a node from the DOM.
|
||||
|
||||
@param oldNode The old node that was removed.
|
||||
@param oldParent The old parent of the node that was removed.
|
||||
@param oldPreviousSibling The old previous sibling node of the node that was removed.
|
||||
*/
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
withOldParent:(HTMLNode *)oldParent
|
||||
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling;
|
||||
|
||||
/**
|
||||
Attaches a node iterator to this document.
|
||||
|
||||
@param iterator The iterator to attach.
|
||||
*/
|
||||
- (void)attachNodeIterator:(HTMLNodeIterator *)iterator;
|
||||
|
||||
/**
|
||||
Detaches a node interator from this document.
|
||||
|
||||
@param iterator The iterator to detach.
|
||||
*/
|
||||
- (void)detachNodeIterator:(HTMLNodeIterator *)iterator;
|
||||
|
||||
|
||||
/**
|
||||
Attaches a range to this document.
|
||||
|
||||
@param range The range to attach.
|
||||
*/
|
||||
- (void)attachRange:(HTMLRange *)range;
|
||||
|
||||
/**
|
||||
Detaches a range from this document.
|
||||
|
||||
@param range The range to detach.
|
||||
*/
|
||||
- (void)detachRange:(HTMLRange *)range;
|
||||
|
||||
/**
|
||||
Callback on removing text from a CharacterData node.
|
||||
|
||||
@param node The CharacterData node.
|
||||
@param offset The offset at which the data was removed.
|
||||
@param length The length of the data that was removed.
|
||||
*/
|
||||
- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length;
|
||||
|
||||
/**
|
||||
Callback on adding text from a CharacterData node.
|
||||
|
||||
@param node The CharacterData node.
|
||||
@param offset The offset at which the data was added.
|
||||
@param length The length of the data that was added.
|
||||
*/
|
||||
- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length;
|
||||
|
||||
/**
|
||||
Callback on inserting a new text node when an old text node is split.
|
||||
|
||||
@param newNode The new text node after splitting.
|
||||
@param parent The parent where newNode was inserted.
|
||||
@param node The old text node that was split.
|
||||
@param offset The offset of splitting.
|
||||
*/
|
||||
- (void)didInsertNewTextNode:(HTMLText *)newNode intoParent:(HTMLNode *)parent afterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset;
|
||||
|
||||
/**
|
||||
Callback for clamping current ranges whose end boundary is after the text node upon splitting it.
|
||||
|
||||
@param node The text node that was split.
|
||||
@param offset The offset of splitting
|
||||
*/
|
||||
- (void)clampRangesAfterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset;
|
||||
|
||||
@end
|
||||
@@ -53,7 +53,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
The element's attribites.
|
||||
*/
|
||||
@property (nonatomic, strong) NSMutableDictionary *attributes;
|
||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *attributes;
|
||||
|
||||
/**
|
||||
@warning Use one of the initWithTagName: methods instead.
|
||||
@@ -63,7 +63,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
Initializes a new HTML element with the given tag name.
|
||||
|
||||
@param tagname The tag name.
|
||||
@param tagName The tag name.
|
||||
@return A new HTML element.
|
||||
*/
|
||||
- (instancetype)initWithTagName:(NSString *)tagName;
|
||||
@@ -71,21 +71,21 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
Initializes a new HTML element with the given tag name and attributes.
|
||||
|
||||
@param tagname The tag name.
|
||||
@param tagName The tag name.
|
||||
@param attributes The attributes.
|
||||
@return A new HTML element.
|
||||
*/
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes;
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(nullable NSDictionary<NSString *, NSString *> *)attributes;
|
||||
|
||||
/**
|
||||
Initializes a new HTML element with the given tag name, namespace, and attributes.
|
||||
|
||||
@param tagname The tag name.
|
||||
@param namespace The namespace.
|
||||
@param tagName The tag name.
|
||||
@param htmlNamespace The HTML namespace.
|
||||
@param attributes The attributes.
|
||||
@return A new HTML element.
|
||||
*/
|
||||
- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(NSDictionary *)attributes;
|
||||
- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(nullable NSDictionary<NSString *, NSString *> *)attributes;
|
||||
|
||||
/**
|
||||
Checks whether this element has an attribute with the given name.
|
||||
@@ -107,7 +107,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
Set the value of the attribute with the given name.
|
||||
|
||||
@param value The value to set.
|
||||
@param name The attribute's name.
|
||||
@param attribute The attribute's name.
|
||||
*/
|
||||
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute;
|
||||
|
||||
|
||||
@@ -140,31 +140,3 @@ NS_INLINE void AdjustSVGNameCase(HTMLTagToken *token)
|
||||
NSString *replacement = replacements[token.tagName] ?: token.tagName;
|
||||
token.tagName = replacement;
|
||||
}
|
||||
|
||||
NS_INLINE void AdjustForeignAttributes(HTMLTagToken *token)
|
||||
{
|
||||
if (token.attributes == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *replacements = @{ @"xlink:actuate": @"xlink actuate",
|
||||
@"xlink:arcrole": @"xlink arcrole",
|
||||
@"xlink:href": @"xlink href",
|
||||
@"xlink:role": @"xlink role",
|
||||
@"xlink:show": @"xlink show",
|
||||
@"xlink:title": @"xlink title",
|
||||
@"xlink:type": @"xlink type",
|
||||
@"xml:base": @"xml base",
|
||||
@"xml:lang": @"xml lang",
|
||||
@"xml:space": @"xml space",
|
||||
@"xmlns": @"xmlns",
|
||||
@"xmlns:xlink": @"xmlns xlink"};
|
||||
|
||||
HTMLOrderedDictionary *adjusted = [HTMLOrderedDictionary new];
|
||||
for (id key in token.attributes) {
|
||||
NSString *replacement = replacements[key] ?: key;
|
||||
adjusted[replacement] = token.attributes[key];
|
||||
}
|
||||
token.attributes = adjusted;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
/**
|
||||
Typedef for the error callback block.
|
||||
|
||||
@param reason The string describing the reason of the reported error.
|
||||
@param code The standarized error-code
|
||||
@param details The string describing the reason of the reported error.
|
||||
*/
|
||||
typedef void (^ HTMLStreamReaderErrorCallback)(NSString *reason);
|
||||
typedef void (^ HTMLStreamReaderErrorCallback)(NSString *code, NSString *details);
|
||||
|
||||
/**
|
||||
* HTML Input Stream Reader processor conforming to the HTML standard
|
||||
|
||||
@@ -14,3 +14,8 @@ extern NSString * const HTMLKitNotSupportedError;
|
||||
|
||||
extern NSString * const HTMLKitSyntaxError;
|
||||
extern NSString * const HTMLKitInvalidCharacterError;
|
||||
|
||||
extern NSString * const HTMLKitInvalidNodeTypeError;
|
||||
extern NSString * const HTMLKitIndexSizeError;
|
||||
extern NSString * const HTMLKitWrongDocumentError;
|
||||
extern NSString * const HTMLKitInvalidStateError;
|
||||
|
||||
@@ -28,7 +28,7 @@ typedef NS_ENUM(short, HTMLNodeType)
|
||||
/**
|
||||
A node's position in the HTML document when compared with other nodes.
|
||||
*/
|
||||
typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
typedef NS_OPTIONS(unsigned short, HTMLDocumentPosition)
|
||||
{
|
||||
HTMLDocumentPositionEquivalent = 0x0,
|
||||
HTMLDocumentPositionDisconnected = 0x01,
|
||||
@@ -71,6 +71,11 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
*/
|
||||
@property (nonatomic, weak, readonly, nullable) HTMLDocument *ownerDocument;
|
||||
|
||||
/**
|
||||
The root node of this node, if any.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly, nullable) HTMLNode *rootNode;
|
||||
|
||||
/**
|
||||
The parent node of this node, if any.
|
||||
*/
|
||||
@@ -86,7 +91,7 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
/**
|
||||
A read-only ordered set of child nodes.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSOrderedSet *childNodes;
|
||||
@property (nonatomic, strong, readonly) NSOrderedSet<HTMLNode *> *childNodes;
|
||||
|
||||
/**
|
||||
The first child node, if any.
|
||||
@@ -122,6 +127,11 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) HTMLElement *nextSiblingElement;
|
||||
|
||||
/**
|
||||
The index of this node.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) NSUInteger index;
|
||||
|
||||
/**
|
||||
The text content of this node.
|
||||
*/
|
||||
@@ -137,6 +147,11 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
*/
|
||||
@property (nonatomic, copy) NSString *innerHTML;
|
||||
|
||||
/**
|
||||
The length of the node as described in https://dom.spec.whatwg.org/#concept-node-length
|
||||
*/
|
||||
@property (nonatomic, assign) NSUInteger length;
|
||||
|
||||
/**
|
||||
@abstract Use concrete subclasses of the HTML Node.
|
||||
*/
|
||||
@@ -164,6 +179,21 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
*/
|
||||
- (NSUInteger)childNodesCount;
|
||||
|
||||
/**
|
||||
Checks whether the node is empty as described in https://dom.spec.whatwg.org/#concept-node-length
|
||||
|
||||
@return `YES` if the node is empty, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
/**
|
||||
Clones this node.
|
||||
|
||||
@param deep If `YES` then also clones child nodes. Otherwise a shallow clone is returned, which behaves the same as `copy`.
|
||||
@return A clone of this node.
|
||||
*/
|
||||
- (instancetype)cloneNodeDeep:(BOOL)deep;
|
||||
|
||||
/**
|
||||
Returns the child node at a given index.
|
||||
|
||||
@@ -202,7 +232,7 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
/**
|
||||
Returns the index of the given child element in the set of child nodes.
|
||||
|
||||
@param node The element.
|
||||
@param element The element.
|
||||
@return The index of the given element in the children set.
|
||||
*/
|
||||
- (NSUInteger)indexOfChildElement:(HTMLElement *)element;
|
||||
@@ -218,9 +248,9 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
/**
|
||||
Prepends the given array of nodes to the set of child nodes.
|
||||
|
||||
@param node The nodes to prepend.
|
||||
@param nodes The nodes to prepend.
|
||||
*/
|
||||
- (void)prependNodes:(NSArray *)nodes;
|
||||
- (void)prependNodes:(NSArray<HTMLNode *> *)nodes;
|
||||
|
||||
/**
|
||||
Appends the given node to the set of child nodes.
|
||||
@@ -235,7 +265,7 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
|
||||
@param nodes The nodes to append.
|
||||
*/
|
||||
- (void)appendNodes:(NSArray *)nodes;
|
||||
- (void)appendNodes:(NSArray<HTMLNode *> *)nodes;
|
||||
|
||||
/**
|
||||
Inserts a given node before a child node.
|
||||
@@ -318,7 +348,8 @@ typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
- (BOOL)isDescendantOfNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Checks whether this node contains the given node.
|
||||
Checks whether this node contains the given node. This performs an `invlusive ancestor` check, i.e. it returns `YES`
|
||||
if the given node is the same object as this node.
|
||||
|
||||
@param node The node to check.
|
||||
@return `YES` if this node contains the given node, `NO` otherwsie.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// HTMLNodeIterator+Private.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 27/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNodeIterator.h"
|
||||
#import "HTMLNode.h"
|
||||
|
||||
/**
|
||||
Private HTML Node Iterator methods which are not intended for public API.
|
||||
*/
|
||||
@interface HTMLNodeIterator (Private)
|
||||
|
||||
/**
|
||||
Runs the necessary steps after removing a node from the DOM.
|
||||
|
||||
@param oldNode The old node that was removed.
|
||||
@param oldParent The old parent of the node that was removed.
|
||||
@param oldPreviousSibling The old previous sibling node of the node that was removed.
|
||||
*/
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
withOldParent:(HTMLNode *)oldParent
|
||||
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling;
|
||||
|
||||
@end
|
||||
@@ -18,19 +18,22 @@
|
||||
*/
|
||||
@interface HTMLParseErrorToken : HTMLToken
|
||||
|
||||
/** @brief The error's reason message. */
|
||||
@property (nonatomic, copy) NSString *reason;
|
||||
/** @brief The parse error's code as specified at https://html.spec.whatwg.org/multipage/parsing.html#parse-errors. */
|
||||
@property (nonatomic, strong, readonly) NSString *code;
|
||||
|
||||
/** @brief Additional detailed error information. */
|
||||
@property (nonatomic, strong, readonly) NSString *details;
|
||||
|
||||
/** @brief The error's location in the stream. */
|
||||
@property (nonatomic, assign) NSUInteger location;
|
||||
@property (nonatomic, assign, readonly) NSUInteger location;
|
||||
|
||||
/**
|
||||
Initializes a new Parse Error token.
|
||||
|
||||
@param reason The error's reason message.
|
||||
@param code The parse error's as specified at https://html.spec.whatwg.org/multipage/parsing.html#parse-errors.
|
||||
@param location The error's location in the stream.
|
||||
@return A new instance of a parse error token.
|
||||
*/
|
||||
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location;
|
||||
- (instancetype)initWithCode:(NSString *)code details:(NSString *)details location:(NSUInteger)location;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// HTLMLParser+Private.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 27/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLParser.h"
|
||||
#import "HTMLElement.h"
|
||||
|
||||
/**
|
||||
Private HTML Parser properties & methods which are not intended for public API.
|
||||
*/
|
||||
@interface HTMLParser (Private)
|
||||
|
||||
/**
|
||||
The adjusted current node in the context of HTML parsing as described in:
|
||||
https://html.spec.whatwg.org/#adjusted-current-node
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) HTMLElement *adjustedCurrentNode;
|
||||
|
||||
@end
|
||||
@@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
An array of errors that occurred during document parsing.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly) NSArray *parseErrors;
|
||||
@property (nonatomic, strong, readonly) NSArray<NSString *> *parseErrors;
|
||||
|
||||
/**
|
||||
The parsed HTML Document.
|
||||
@@ -67,7 +67,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@see HTMLElement
|
||||
*/
|
||||
- (NSArray *)parseFragmentWithContextElement:(HTMLElement *)contextElement;
|
||||
- (NSArray<HTMLNode *> *)parseFragmentWithContextElement:(HTMLElement *)contextElement;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// HTMLRange+Private.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 27/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLRange.h"
|
||||
#import "HTMLCharacterData.h"
|
||||
|
||||
@interface HTMLRange ()
|
||||
|
||||
/**
|
||||
Runs the necessary steps after removing data from a character data node that may be a range's boundary.
|
||||
|
||||
@param node The character data node.
|
||||
@param offset The offset at which the data was removed.
|
||||
@param length The length of the data that was removed.
|
||||
*/
|
||||
- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length;
|
||||
|
||||
/**
|
||||
Runs the necessary steps after adding data to a character data node that may be a range's boundary.
|
||||
|
||||
@param node The character data node.
|
||||
@param offset The offset at which the data was added.
|
||||
@param length The length of the data that was added.
|
||||
*/
|
||||
- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length;
|
||||
|
||||
/**
|
||||
Runs the necessary steps after inserting a new text node when an old text node is split.
|
||||
|
||||
@param newNode The new text node after splitting.
|
||||
@param parent The parent where newNode was inserted.
|
||||
@param node The old text node that was split.
|
||||
@param offset The offset of splitting.
|
||||
*/
|
||||
- (void)didInsertNewTextNode:(HTMLText *)newNode intoParent:(HTMLNode *)parent afterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset;
|
||||
|
||||
/**
|
||||
Runs the necessary steps to clamp the range whose end boundary is after the text node upon splitting it.
|
||||
|
||||
@param node The text node that was split.
|
||||
@param offset The offset of splitting
|
||||
*/
|
||||
- (void)clampRangesAfterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset;
|
||||
|
||||
/**
|
||||
Runs the necessary steps after removing a node from the DOM.
|
||||
|
||||
@param oldNode The old node that was removed.
|
||||
@param oldParent The old parent of the node that was removed.
|
||||
@param oldPreviousSibling The old previous sibling node of the node that was removed.
|
||||
*/
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
withOldParent:(HTMLNode *)oldParent
|
||||
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,265 @@
|
||||
//
|
||||
// HTMLRange.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/11/16.
|
||||
// Copyright © 2016 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLDocumentFragment.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The comparison method of range boundaries
|
||||
|
||||
- HTMLRangeComparisonMethodStartToStart: Compares the start boundary-point of sourceRange to the start boundary-point of this range.
|
||||
- HTMLRangeComparisonMethodStartToEnd: Compares the start boundary-point of sourceRange to the end boundary-point of this range.
|
||||
- HTMLRangeComparisonMethodEndToEnd: Compares the end boundary-point of sourceRange to the end boundary-point of this range.
|
||||
- HTMLRangeComparisonMethodEndToStart: Compares the end boundary-point of sourceRange to the start boundary-point of this range.
|
||||
*/
|
||||
typedef NS_ENUM(unsigned short, HTMLRangeComparisonMethod)
|
||||
{
|
||||
HTMLRangeComparisonMethodStartToStart = 0,
|
||||
HTMLRangeComparisonMethodStartToEnd = 1,
|
||||
HTMLRangeComparisonMethodEndToEnd = 2,
|
||||
HTMLRangeComparisonMethodEndToStart = 3
|
||||
};
|
||||
|
||||
#pragma mark - DOM Range
|
||||
|
||||
/**
|
||||
A HTML Range, represents a sequence of content within a node tree.
|
||||
Each range has a start and an end which are boundary points.
|
||||
A boundary point is a tuple consisting of a node and a non-negative numeric offset.
|
||||
|
||||
https://dom.spec.whatwg.org/#ranges
|
||||
*/
|
||||
@interface HTMLRange : NSObject
|
||||
|
||||
/**
|
||||
The node of the start boundary point.
|
||||
*/
|
||||
@property (nonatomic, readonly, strong) HTMLNode *startContainer;
|
||||
|
||||
/**
|
||||
The offset of the start boundary point.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) NSUInteger startOffset;
|
||||
|
||||
/**
|
||||
The node of the end boundary point.
|
||||
*/
|
||||
@property (nonatomic, readonly, strong) HTMLNode *endContainer;
|
||||
|
||||
/**
|
||||
The offset of the end boundary point.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign) NSUInteger endOffset;
|
||||
|
||||
/**
|
||||
Checks whether the range is collapsed, i.e. if start is the same as end.
|
||||
|
||||
@return `YES` if the range is collapsed, `NO` otherwise.
|
||||
*/
|
||||
@property (nonatomic, readonly, assign, getter=isCollapsed) BOOL collapsed;
|
||||
|
||||
/**
|
||||
The common container node that contains both start and end nodes.
|
||||
*/
|
||||
@property (nonatomic, readonly, weak) HTMLNode *commonAncestorContainer;
|
||||
|
||||
/**
|
||||
@abstract A range is always associated with a HTML Document. Use `initWithDocument:` initializer instead.
|
||||
*/
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Initializes a new range instance for the given document.
|
||||
|
||||
@param document The HTML doucment for which the range will be constructed.
|
||||
@return A new HTML Range instance.
|
||||
*/
|
||||
- (instancetype)initWithDocument:(HTMLDocument *)document;
|
||||
|
||||
/**
|
||||
Deprecated due to typo.
|
||||
*/
|
||||
- (instancetype)initWithDowcument:(HTMLDocument *)document __attribute__((deprecated("Replaced by -initWithDocument:")));
|
||||
|
||||
/**
|
||||
Initializes a new range instance for the given document and boundaries.
|
||||
|
||||
@param document The HTML doucment for which the range will be constructed.
|
||||
@param startContainer The node for the start boundary
|
||||
@param startOffset The offset of the start boundary
|
||||
@param endContainer The node for the end boundary
|
||||
@param endOffset The offset of the end boundary
|
||||
@return A new HTML Range instance.
|
||||
*/
|
||||
- (instancetype)initWithDocument:(HTMLDocument *)document
|
||||
startContainer:(HTMLNode *)startContainer startOffset:(NSUInteger)startOffset
|
||||
endContainer:(HTMLNode *)endContainer endOffset:(NSUInteger)endOffset;
|
||||
|
||||
/**
|
||||
Deprecated due to typo.
|
||||
*/
|
||||
- (instancetype)initWithDowcument:(HTMLDocument *)document
|
||||
startContainer:(HTMLNode *)startContainer startOffset:(NSUInteger)startOffset
|
||||
endContainer:(HTMLNode *)endContainer endOffset:(NSUInteger)endOffset
|
||||
__attribute__((deprecated("Replaced by -initWithDocument:startContainer:startOffset:endContainer:endOffset:")));
|
||||
|
||||
/**
|
||||
Sets the start boundary.
|
||||
|
||||
@param node The new node of the start boundary.
|
||||
@param offset The new offset of the start boundary.
|
||||
*/
|
||||
- (void)setStartNode:(HTMLNode *)node startOffset:(NSUInteger)offset;
|
||||
|
||||
/**
|
||||
Sets the end boundary.
|
||||
|
||||
@param node The new node of the end boundary.
|
||||
@param offset The new offset of the end boundary.
|
||||
*/
|
||||
- (void)setEndNode:(HTMLNode *)node endOffset:(NSUInteger)offset;
|
||||
|
||||
/**
|
||||
Sets the start boundary before the given node.
|
||||
|
||||
@param node The node before which the boundary will be set.
|
||||
*/
|
||||
- (void)setStartBeforeNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Sets the start boundary after the given node.
|
||||
|
||||
@param node The node after which the boundary will be set.
|
||||
*/
|
||||
- (void)setStartAfterNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Sets the end boundary before the given node.
|
||||
|
||||
@param node The node before which the boundary will be set.
|
||||
*/
|
||||
- (void)setEndBeforeNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Sets the end boundary after the given node.
|
||||
|
||||
@param node The node after which the boundary will be set.
|
||||
*/
|
||||
- (void)setEndAfterNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Collapses this range to its start.
|
||||
*/
|
||||
- (void)collapseToStart;
|
||||
|
||||
/**
|
||||
Collapses this range to its end.
|
||||
*/
|
||||
- (void)collapseToEnd;
|
||||
|
||||
/**
|
||||
Selects the given node in the range.
|
||||
|
||||
@param node The node to select
|
||||
*/
|
||||
- (void)selectNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Selects the node's contents in the range.
|
||||
|
||||
@param node The node to select the contents.
|
||||
*/
|
||||
- (void)selectNodeContents:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Compares the boundary points of the given range with this range.
|
||||
|
||||
@param method The comparison method.
|
||||
@param sourceRange The source range for comparison.
|
||||
@return `NSOrderedAscending` if ordered before, `NSOrderedSame` if ordered same, `NSOrderedDescending` otherwise.
|
||||
|
||||
@see HTMLRangeComparisonMethod
|
||||
*/
|
||||
- (NSComparisonResult)compareBoundaryPoints:(HTMLRangeComparisonMethod)method sourceRange:(HTMLRange *)sourceRange;
|
||||
|
||||
/**
|
||||
Compares the given point (reference node, offset) with this range.
|
||||
|
||||
@param node The node to compare with this range.
|
||||
@param offset The offset inside the reference node.
|
||||
@return `NSOrderedAscending`, `NSOrderedSame`, or `NSOrderedDescending` depending on whether the node is before, the same as, or after this range.
|
||||
*/
|
||||
- (NSComparisonResult)comparePoint:(HTMLNode *)node offset:(NSUInteger)offset;
|
||||
|
||||
/**
|
||||
Checks if the given point (reference node, offset) is in this range.
|
||||
|
||||
@param node The node to compare with this range.
|
||||
@param offset The offset inside the reference node.
|
||||
@return `YES` if the given point is in this range, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)containsPoint:(HTMLNode *)node offset:(NSUInteger)offset;
|
||||
|
||||
/**
|
||||
Checks if the given node intersects this range.
|
||||
|
||||
@param node The node to compare with this range.
|
||||
@return `YES` if the given node intersects the range, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)intersectsNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Deletes the contents represented by this range from the associated document.
|
||||
*/
|
||||
- (void)deleteContents;
|
||||
|
||||
/**
|
||||
Extracts the contents represented by this range from the associated document.
|
||||
|
||||
@return A document fragment with the extracted contents.
|
||||
*/
|
||||
- (HTMLDocumentFragment *)extractContents;
|
||||
|
||||
/**
|
||||
Clones the contents represented by this range in the associated document.
|
||||
|
||||
@return A document fragment with the cloned contents.
|
||||
*/
|
||||
- (HTMLDocumentFragment *)cloneContents;
|
||||
|
||||
/**
|
||||
Inserts the given node at the start of this range.
|
||||
|
||||
If the node is being added to a text node, then the text node is split at the insertion point and the given node
|
||||
is inserted between the resulting text nodes.
|
||||
|
||||
@param node The node to insert.
|
||||
*/
|
||||
- (void)insertNode:(HTMLNode *)node;
|
||||
|
||||
/**
|
||||
Surrounds the contents of this range with the given node.
|
||||
|
||||
The range's boundaries will placed around the given node, i.e. start is before and end is after newParent.
|
||||
|
||||
@param newParent The new parent node which will surround the range.
|
||||
*/
|
||||
- (void)surroundContents:(HTMLNode *)newParent;
|
||||
|
||||
/**
|
||||
The stringifier of the range.
|
||||
|
||||
@return The text contents of the range.
|
||||
*/
|
||||
- (NSString *)textContent;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -79,7 +79,7 @@
|
||||
/**
|
||||
Checks whether an element with the given tag name is in the stack.
|
||||
|
||||
@param tagname The element's tag name.
|
||||
@param tagName The element's tag name.
|
||||
@return `YES` if such an element is in the stack, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)containsElementWithTagName:(NSString *)tagName;
|
||||
@@ -163,11 +163,11 @@
|
||||
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;
|
||||
- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasHeaderElementInScope;
|
||||
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName;
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,17 +6,14 @@
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLCharacterData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A HTML Text node
|
||||
*/
|
||||
@interface HTMLText : HTMLNode
|
||||
|
||||
/** @brief The text string. */
|
||||
@property (nonatomic, copy) NSMutableString *data;
|
||||
@interface HTMLText : HTMLCharacterData
|
||||
|
||||
/**
|
||||
Initializes a new HTML text node.
|
||||
@@ -31,7 +28,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@param string The string to append.
|
||||
*/
|
||||
- (void)appendString:(NSString *)string;
|
||||
- (void)appendString:(NSString *)string __attribute__((deprecated("Use `appendData:` instead.")));
|
||||
|
||||
- (HTMLText *)splitTextAtOffset:(NSUInteger)offset;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
@class HTMLParser;
|
||||
|
||||
/**
|
||||
Typedef for the parse error callback block.
|
||||
|
||||
@param token The parse error token.
|
||||
*/
|
||||
typedef void (^ HTMLTokenizerParseErrorCallback)(HTMLParseErrorToken *token);
|
||||
|
||||
/**
|
||||
* HTML Tokenizer
|
||||
* https://html.spec.whatwg.org/multipage/syntax.html#tokenization
|
||||
@@ -37,7 +44,14 @@
|
||||
|
||||
@see HTMLParser
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) HTMLParser *parser;
|
||||
@property (nonatomic, weak) HTMLParser *parser;
|
||||
|
||||
/**
|
||||
An error callback block, which gets called when encountering parse errors while tokenizing the stream
|
||||
|
||||
Parse error tokens are dropped if the callback is `nil`.
|
||||
*/
|
||||
@property (nonatomic, copy) HTMLTokenizerParseErrorCallback parseErrorCallback;
|
||||
|
||||
/**
|
||||
Initializes a new Tokenizer with the given string.
|
||||
|
||||
@@ -23,11 +23,16 @@
|
||||
CHAR( DIGIT_NINE, 0x0039 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_A, 0x0041 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_F, 0x0046 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_P, 0x0050 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_S, 0x0053 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_X, 0x0058 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_Z, 0x005A ) \
|
||||
CHAR( RIGHT_SQUARE_BRACKET, 0x005D ) \
|
||||
CHAR( GRAVE_ACCENT, 0x0060 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_A, 0x0061 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_F, 0x0066 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_P, 0x0070 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_S, 0x0073 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_X, 0x0078 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_Z, 0x007A ) \
|
||||
CHAR( HYPHEN_MINUS, 0x002D ) \
|
||||
@@ -82,13 +87,17 @@ NUMERIC_REPLACEMENT_CHARACTERS
|
||||
#undef CHAR
|
||||
};
|
||||
|
||||
NS_INLINE BOOL isControlOrUndefinedCharacter(UTF32Char character)
|
||||
NS_INLINE BOOL isControlCharacter(unsigned long long character)
|
||||
{
|
||||
return ((character >= 0x0001 && character <= 0x0008) ||
|
||||
(character >= 0x000D && character <= 0x001F) ||
|
||||
(character >= 0x007F && character <= 0x009F) ||
|
||||
(character >= 0xFDD0 && character <= 0xFDEF) ||
|
||||
character == 0x000B ||
|
||||
(character >= 0x000E && character <= 0x001F) ||
|
||||
(character >= 0x007F && character <= 0x009F));
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isNoncharacter(unsigned long long character)
|
||||
{
|
||||
return ((character >= 0xFDD0 && character <= 0xFDEF) ||
|
||||
character == 0xFFFE ||
|
||||
character == 0xFFFF ||
|
||||
character == 0x1FFFE ||
|
||||
@@ -137,6 +146,18 @@ NS_INLINE BOOL isHexDigit(UTF32Char character)
|
||||
(character >= LATIN_SMALL_LETTER_A && character <= LATIN_SMALL_LETTER_F));
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isUpperHexDigit(UTF32Char character)
|
||||
{
|
||||
return ((character >= LATIN_CAPITAL_LETTER_A && character <= LATIN_CAPITAL_LETTER_F) ||
|
||||
(character >= DIGIT_ZERO && character <= DIGIT_NINE));
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isLowerHexDigit(UTF32Char character)
|
||||
{
|
||||
return ((character >= LATIN_SMALL_LETTER_A && character <= LATIN_SMALL_LETTER_F) ||
|
||||
(character >= DIGIT_ZERO && character <= DIGIT_NINE));
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isAlphanumeric(UTF32Char character)
|
||||
{
|
||||
return ((character >= DIGIT_ZERO && character <= DIGIT_NINE) ||
|
||||
@@ -151,17 +172,14 @@ NS_INLINE BOOL isStringAlphanumeric(NSString *string)
|
||||
return ([string rangeOfCharacterFromSet:set].location == NSNotFound);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isInvalidNumericRange(unsigned long long numeric)
|
||||
NS_INLINE BOOL isSurrogate(unsigned long long character)
|
||||
{
|
||||
return ((numeric >= 0xD800 && numeric <= 0xDFFF) ||
|
||||
numeric > 0x10FFFF);
|
||||
return (character >= 0xD800 && character <= 0xDFFF);
|
||||
}
|
||||
|
||||
NS_INLINE unichar NumericReplacementCharacter(UTF32Char character)
|
||||
{
|
||||
if (character == NULL_CHAR) {
|
||||
return REPLACEMENT_CHAR;
|
||||
} else if (character >= 0x0080 && character <= 0x009F) {
|
||||
if (character >= 0x0080 && character <= 0x009F) {
|
||||
return NumericReplacementTable[character - 0x0080];
|
||||
} else {
|
||||
return NULL_CHAR;
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
|
||||
#define TOKENIZER_STATES \
|
||||
STATE_ENTRY( HTMLTokenizerStateData, = 0) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCharacterReferenceInData, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRCDATA, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCharacterReferenceInRCDATA, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRAWTEXT, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptData, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStatePLAINTEXT, ) \
|
||||
@@ -59,6 +57,10 @@
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentStart, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentStartDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateComment, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentLessThanSign, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentLessThanSignBang, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentLessThanSignBangDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentLessThanSignBangDashDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentEndDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentEnd, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentEndBang, ) \
|
||||
@@ -78,7 +80,18 @@
|
||||
STATE_ENTRY( HTMLTokenizerStateDOCTYPESystemIdentifierSingleQuoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAfterDOCTYPESystemIdentifier, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBogusDOCTYPE, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCDATASection, )
|
||||
STATE_ENTRY( HTMLTokenizerStateCDATASection, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCDATASectionBracket, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCDATASectionEnd, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCharacterReference, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateNamedCharacterReference, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAmbiguousAmpersand, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateNumericCharacterReference, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateHexadecimalCharacterReferenceStart, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateDecimalCharacterReferenceStart, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateHexadecimalCharacterReference, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateDecimalCharacterReference, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateNumericCharacterReferenceEnd, )
|
||||
|
||||
typedef NS_ENUM(NSUInteger, HTMLTokenizerState)
|
||||
{
|
||||
|
||||
@@ -49,7 +49,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
Initializes a new tree walker with no filter and HTMLNodeFilterShowAll show options.
|
||||
|
||||
@param node The root node.
|
||||
@param filter The node filter to use.
|
||||
@return A new instance of a tree walker.
|
||||
*/
|
||||
- (instancetype)initWithNode:(HTMLNode *)node;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
module HTMLKit {
|
||||
umbrella header "HTMLKit.h"
|
||||
|
||||
module * { export * }
|
||||
export *
|
||||
|
||||
explicit module Private {
|
||||
textual header "CSSCodePoints.h"
|
||||
header "CSSInputStream.h"
|
||||
header "HTMLCharacterToken.h"
|
||||
header "HTMLCommentToken.h"
|
||||
header "HTMLDOCTYPEToken.h"
|
||||
header "HTMLElementAdjustment.h"
|
||||
header "HTMLElementTypes.h"
|
||||
header "HTMLEOFToken.h"
|
||||
header "HTMLInputStreamReader.h"
|
||||
header "HTMLListOfActiveFormattingElements.h"
|
||||
header "HTMLMarker.h"
|
||||
header "HTMLParseErrorToken.h"
|
||||
header "HTMLParserInsertionModes.h"
|
||||
header "HTMLStackOfOpenElements.h"
|
||||
header "HTMLTagToken.h"
|
||||
header "HTMLToken.h"
|
||||
header "HTMLTokenizer.h"
|
||||
textual header "HTMLTokenizerCharacters.h"
|
||||
header "HTMLTokenizerEntities.h"
|
||||
header "HTMLTokenizerStates.h"
|
||||
header "HTMLTokens.h"
|
||||
header "HTMLNode+Private.h"
|
||||
header "HTMLDocument+Private.h"
|
||||
header "HTMLCharacterData+Private.h"
|
||||
header "HTMLRange+Private.h"
|
||||
header "HTMLNodeIterator+Private.h"
|
||||
header "HTMLParser+Private.h"
|
||||
header "HTMLNodeTraversal.h"
|
||||
header "HTMLDOMUtils.h"
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
module HTMLKit {
|
||||
umbrella header "HTMLKit.h"
|
||||
|
||||
private header "CSSCodePoints.h"
|
||||
private header "CSSInputStream.h"
|
||||
private header "HTMLCharacterToken.h"
|
||||
private header "HTMLCommentToken.h"
|
||||
private header "HTMLDOCTYPEToken.h"
|
||||
private header "HTMLElementAdjustment.h"
|
||||
private header "HTMLElementTypes.h"
|
||||
private header "HTMLEOFToken.h"
|
||||
private header "HTMLInputStreamReader.h"
|
||||
private header "HTMLListOfActiveFormattingElements.h"
|
||||
private header "HTMLMarker.h"
|
||||
private header "HTMLNode+Private.h"
|
||||
private header "HTMLNodeTraversal.h"
|
||||
private header "HTMLParseErrorToken.h"
|
||||
private header "HTMLParserInsertionModes.h"
|
||||
private header "HTMLStackOfOpenElements.h"
|
||||
private header "HTMLTagToken.h"
|
||||
private header "HTMLToken.h"
|
||||
private header "HTMLTokenizer.h"
|
||||
private header "HTMLTokenizerCharacters.h"
|
||||
private header "HTMLTokenizerEntities.h"
|
||||
private header "HTMLTokenizerStates.h"
|
||||
private header "HTMLTokens.h"
|
||||
|
||||
module * { export * }
|
||||
export *
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <HTMLKit/HTMLKit.h>
|
||||
#import "HTMLKit.h"
|
||||
#import "CSSSelectorTest.h"
|
||||
#import "CSSSelectorParser.h"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "CSSSelectors.h"
|
||||
#import "HTMLKitTestUtil.h"
|
||||
|
||||
static NSString * const CSSTests = @"css-tests";
|
||||
|
||||
@@ -18,8 +19,7 @@ static NSString * const CSSTests = @"css-tests";
|
||||
|
||||
+ (NSArray *)loadCSSSelectorTests
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
path = [path stringByAppendingPathComponent:CSSTests];
|
||||
NSString *path = [HTMLKitTestUtil pathForFixture:CSSTests ofType:nil inDirectory:nil];
|
||||
|
||||
NSMutableArray *tests = [NSMutableArray array];
|
||||
NSArray *testFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// CSSStructuralPseudoSelectors.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 18.04.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "CSSSelectors.h"
|
||||
#import "HTMLParser.h"
|
||||
#import "HTMLDOM.h"
|
||||
|
||||
@interface CSSStructuralPseudoSelectors : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSSStructuralPseudoSelectors
|
||||
|
||||
#pragma mark - Bug Fixes
|
||||
|
||||
- (void)testBugFix_Issue_25
|
||||
{
|
||||
NSString *html = @"<table><tr><td>TD #0</td><td>TD #1</td><td>TD #2</td><td>TD #3</td></tr></table>";
|
||||
HTMLDocument *doc = [HTMLDocument documentWithString:html];
|
||||
NSArray<HTMLElement *> *elements = [doc querySelectorAll:@"td:gt(0)"];
|
||||
|
||||
XCTAssertEqual(elements.count, 3);
|
||||
XCTAssertEqualObjects(elements[0].textContent, @"TD #1");
|
||||
XCTAssertEqualObjects(elements[1].textContent, @"TD #2");
|
||||
XCTAssertEqualObjects(elements[2].textContent, @"TD #3");
|
||||
|
||||
elements = [doc querySelectorAll:@"td:lt(0)"];
|
||||
XCTAssertEqual(elements.count, 0);
|
||||
|
||||
elements = [doc querySelectorAll:@"td:eq(0)"];
|
||||
XCTAssertEqual(elements.count, 1);
|
||||
XCTAssertEqualObjects(elements[0].textContent, @"TD #0");
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,9 +14,9 @@
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) NSString *input;
|
||||
@property (nonatomic, strong) NSArray *output;
|
||||
@property (nonatomic, strong) NSArray *errors;
|
||||
@property (nonatomic, strong) NSArray *initialStates;
|
||||
@property (nonatomic, copy) NSString *lastStartTag;
|
||||
@property (nonatomic, assign) BOOL ignoreErrorOrder;
|
||||
|
||||
+ (NSDictionary *)loadHTML5LibTokenizerTests;
|
||||
|
||||
@@ -7,23 +7,22 @@
|
||||
//
|
||||
|
||||
#import "HTML5LibTokenizerTest.h"
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
#import "HTMLTokenizerStates.h"
|
||||
#import "HTMLTokens.h"
|
||||
#import "HTMLKitTestUtil.h"
|
||||
|
||||
static NSString * const HTML5LibTests = @"html5lib-tests";
|
||||
static NSString * const TOKENIZER = @"tokenizer";
|
||||
static NSString * const Tokenizer = @"tokenizer";
|
||||
|
||||
@implementation HTML5LibTokenizerTest
|
||||
|
||||
+ (NSDictionary *)loadHTML5LibTokenizerTests
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
path = [path stringByAppendingPathComponent:HTML5LibTests];
|
||||
path = [path stringByAppendingPathComponent:TOKENIZER];
|
||||
|
||||
NSMutableDictionary *testsMap = [NSMutableDictionary dictionary];
|
||||
NSString *path = [HTMLKitTestUtil pathForFixture:Tokenizer ofType:nil inDirectory:HTML5LibTests];
|
||||
NSArray *testFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
||||
|
||||
NSMutableDictionary *testsMap = [NSMutableDictionary dictionary];
|
||||
for (NSString *testFile in testFiles) {
|
||||
if (![testFile.pathExtension isEqualToString:@"test"]) {
|
||||
continue;
|
||||
@@ -71,16 +70,16 @@ static NSString * const TOKENIZER = @"tokenizer";
|
||||
{
|
||||
BOOL doubleEscaped = [test[@"doubleEscaped"] boolValue];
|
||||
|
||||
// Test Title
|
||||
// Test Title
|
||||
self.title = test[@"description"];
|
||||
|
||||
// Test Input
|
||||
// Test Input
|
||||
self.input = test[@"input"];
|
||||
if (doubleEscaped) {
|
||||
self.input = [self processDoubleEscaped:self.input];
|
||||
}
|
||||
|
||||
// Test Output
|
||||
// Test Output
|
||||
NSMutableArray *tokens = [NSMutableArray array];
|
||||
NSArray *outputs = test[@"output"];
|
||||
for (NSArray *output in outputs) {
|
||||
@@ -90,7 +89,7 @@ static NSString * const TOKENIZER = @"tokenizer";
|
||||
[tokens addObject:[HTMLEOFToken token]];
|
||||
self.output = tokens;
|
||||
|
||||
// Test Initial States
|
||||
// Test Initial States
|
||||
NSMutableArray *initialStates = [NSMutableArray array];
|
||||
|
||||
NSArray *states = test[@"initialStates"];
|
||||
@@ -102,6 +101,10 @@ static NSString * const TOKENIZER = @"tokenizer";
|
||||
state = HTMLTokenizerStateRCDATA;
|
||||
} else if ([name isEqualToString:@"RAWTEXT state"]) {
|
||||
state = HTMLTokenizerStateRAWTEXT;
|
||||
} else if ([name isEqualToString:@"Script data state"]) {
|
||||
state = HTMLTokenizerStateScriptData;
|
||||
} else if ([name isEqualToString:@"CDATA section state"]) {
|
||||
state = HTMLTokenizerStateCDATASection;
|
||||
}
|
||||
[initialStates addObject:@(state)];
|
||||
}
|
||||
@@ -111,19 +114,21 @@ static NSString * const TOKENIZER = @"tokenizer";
|
||||
|
||||
self.initialStates = initialStates;
|
||||
|
||||
// Test Last Start Tag
|
||||
// Test Last Start Tag
|
||||
self.lastStartTag = test[@"lastStartTag"];
|
||||
|
||||
// Ignore Error Order
|
||||
self.ignoreErrorOrder = [test[@"ignoreErrorOrder"] boolValue];
|
||||
// Test errors
|
||||
NSArray *errors = test[@"errors"];
|
||||
NSMutableArray *errorTokens = [NSMutableArray new];
|
||||
for (NSDictionary *error in errors) {
|
||||
HTMLParseErrorToken *token = [[HTMLParseErrorToken alloc] initWithCode:error[@"code"] details:nil location:0];
|
||||
[errorTokens addObject:token];
|
||||
}
|
||||
self.errors = errorTokens;
|
||||
}
|
||||
|
||||
- (HTMLToken *)processOutputToken:(id)output doubleEscaped:(BOOL)doubleEscaped
|
||||
{
|
||||
if ([output isKindOfClass:[NSString class]] && [output isEqualToString:@"ParseError"]) {
|
||||
return [HTMLParseErrorToken new];
|
||||
}
|
||||
|
||||
NSString *type = [output firstObject];
|
||||
|
||||
NSString *data = nil;
|
||||
@@ -147,8 +152,6 @@ static NSString * const TOKENIZER = @"tokenizer";
|
||||
return token;
|
||||
} else if ([type isEqualToString:@"EndTag"]) {
|
||||
return [[HTMLEndTagToken alloc] initWithTagName:data];
|
||||
} else if ([type isEqualToString:@"ParseError"]) {
|
||||
return [HTMLParseErrorToken new];
|
||||
} else if ([type isEqualToString:@"StartTag"]) {
|
||||
HTMLStartTagToken *token = [[HTMLStartTagToken alloc] initWithTagName:data];
|
||||
NSDictionary *attributes = output[2];
|
||||
+7
-6
@@ -9,10 +9,12 @@
|
||||
#import "HTML5LibTreeConstructionTest.h"
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "HTMLDOM.h"
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLKitTestUtil.h"
|
||||
|
||||
static NSString * const HTML5LibTests = @"html5lib-tests";
|
||||
static NSString * const TreeConstruction = @"tree-construction";
|
||||
@@ -21,13 +23,10 @@ static NSString * const TreeConstruction = @"tree-construction";
|
||||
|
||||
+ (NSDictionary *)loadHTML5LibTreeConstructionTests
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
path = [path stringByAppendingPathComponent:HTML5LibTests];
|
||||
path = [path stringByAppendingPathComponent:TreeConstruction];
|
||||
|
||||
NSMutableDictionary *testsMap = [NSMutableDictionary dictionary];
|
||||
NSString *path = [HTMLKitTestUtil pathForFixture:TreeConstruction ofType:nil inDirectory:HTML5LibTests];
|
||||
NSArray *testFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
||||
|
||||
NSMutableDictionary *testsMap = [NSMutableDictionary dictionary];
|
||||
for (NSString *testFile in testFiles) {
|
||||
if (![testFile.pathExtension isEqualToString:@"dat"]) {
|
||||
continue;
|
||||
@@ -48,7 +47,7 @@ static NSString * const TreeConstruction = @"tree-construction";
|
||||
NSMutableArray *tests = [NSMutableArray array];
|
||||
|
||||
NSScanner *scanner = [NSScanner scannerWithString:contents];
|
||||
NSString * (^ nextTest)() = ^ NSString * () {
|
||||
NSString * (^ nextTest)(void) = ^ NSString * () {
|
||||
NSString *str;
|
||||
[scanner scanUpToString:@"\n\n#data" intoString:&str];
|
||||
return str;
|
||||
@@ -291,6 +290,8 @@ NS_INLINE NSArray * parseAttribute(NSString *str)
|
||||
NSRange range = [str rangeOfString:@"=" options:0];
|
||||
|
||||
NSString *key = [str substringToIndex:range.location];
|
||||
key = [key stringByReplacingOccurrencesOfString:@" " withString:@":"];
|
||||
|
||||
NSString *value = [str substringFromIndex:range.location + 2];
|
||||
value = [value substringToIndex:value.length - 1];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user