Compare commits
325 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8038ec9654 | |||
| c13abcb833 | |||
| 785ca48f52 | |||
| 1c8be332a4 | |||
| ef29e27f39 | |||
| 482d936196 | |||
| 9dd3c70fc3 | |||
| fe2a441171 | |||
| 7c32b5f219 | |||
| ebc6f7a097 | |||
| d29a70dfec | |||
| b8f56ca97d | |||
| 0f954d5d56 | |||
| ac5089d69b | |||
| c9c36397e0 | |||
| 923e271429 | |||
| da765169fa | |||
| ec6d03ee57 | |||
| 7944549bd7 | |||
| bb6a97984b | |||
| 0aff9cf787 | |||
| b7bf9d2b04 | |||
| 12af838fa2 | |||
| 14b8b5390f | |||
| a5599c4163 | |||
| c0ac25645d | |||
| fa431550d7 | |||
| 70c6aee814 | |||
| 7309b97b92 | |||
| 7ffb8e5ca0 | |||
| 16ed846e65 | |||
| 820b3ef091 | |||
| fb7ea25b1f | |||
| 5302c16600 | |||
| 2dfdeb22ca | |||
| 135715b606 | |||
| f8e6a39171 | |||
| 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 | |||
| 44c987ec13 | |||
| 3bd5b2d4a2 | |||
| 8c312e1508 | |||
| 20d8449a94 | |||
| 16b85af5af | |||
| 3c56a1d694 | |||
| ca20e6d03f | |||
| f58c2e3f94 | |||
| 9698919d35 | |||
| 9a1a12c788 | |||
| e56a980e80 | |||
| a440fd56b2 | |||
| 1a3c1dd3d2 | |||
| 170c298571 | |||
| f51c76d257 | |||
| 1df63b8e00 | |||
| 37444d5899 | |||
| 85e4941057 | |||
| c7a339fbed | |||
| 0f80b0bbe2 | |||
| b1a9b7ecee | |||
| f794e04b9f | |||
| 0ffecea0f3 | |||
| 62fef829d3 | |||
| 21dd607ec6 | |||
| 7ae337471c | |||
| 730b8a3239 | |||
| 33a8238513 | |||
| 769113ec0e | |||
| 4970976485 | |||
| 38fef77be5 | |||
| 754b7191b9 | |||
| 56cc5b1a3e | |||
| ccecc4106d | |||
| 39dda3aaf5 | |||
| a136976462 | |||
| 8aabc94fdb | |||
| 322b42b9d1 | |||
| c2acbb6344 | |||
| a6e4aac937 | |||
| 899438fa24 | |||
| 137fa8617e | |||
| 70abd998f7 | |||
| bb948e0ef8 | |||
| b0bad5068f | |||
| a01f1c1c5b | |||
| 6967798823 | |||
| 58b60dd390 | |||
| 4441b7decd | |||
| 0b2681f8a8 | |||
| 1e07acd032 | |||
| 432df997b3 | |||
| c9d72646bc | |||
| 1af25abadb | |||
| 75138cc35f | |||
| 93401e4054 | |||
| 266621edf2 | |||
| 25ad5a3f82 | |||
| cac1e4cedd | |||
| abc847a33b | |||
| 4680a66fd1 | |||
| 465b78dbba | |||
| a64ff8782e | |||
| e03a384aa7 | |||
| 6d2cb09082 | |||
| 222bfa03e1 | |||
| 6254e8a578 | |||
| 01be0acc0a | |||
| 58f0b88ff8 | |||
| b90e673dc0 | |||
| 22f293e718 | |||
| 4511335e9b | |||
| dd2d29b8f0 | |||
| f267958e83 | |||
| b94a80bd24 | |||
| 1df0c4ce1f | |||
| ac49520ad9 | |||
| ee6dbff8d5 | |||
| f004e6328c | |||
| f8255c861a | |||
| 4592037aba | |||
| 948c07e4ae | |||
| 41d9d98201 | |||
| 8199f647f4 | |||
| 977737d538 | |||
| b8d17162d5 | |||
| 27e1ed2bda | |||
| c169f0ed07 |
@@ -0,0 +1,5 @@
|
||||
codecov:
|
||||
branch: develop
|
||||
|
||||
ignore:
|
||||
- "Tests/"
|
||||
+4
-1
@@ -26,6 +26,7 @@ xcuserdata
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
@@ -39,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/
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
[submodule "HTMLKitTests/html5lib-tests"]
|
||||
path = 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.4
|
||||
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)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
language: objective-c
|
||||
osx_image: xcode9
|
||||
|
||||
branches:
|
||||
except:
|
||||
- gh-pages
|
||||
|
||||
install:
|
||||
- gem install xcpretty
|
||||
|
||||
env:
|
||||
global:
|
||||
- LC_CTYPE=en_US.UTF-8
|
||||
- LANG=en_US.UTF-8
|
||||
- WORKSPACE=HTMLKit.xcworkspace
|
||||
- IOS_FRAMEWORK_SCHEME=HTMLKit-iOS
|
||||
- MACOS_FRAMEWORK_SCHEME=HTMLKit-macOS
|
||||
- WATCHOS_FRAMEWORK_SCHEME="HTMLKit-watchOS"
|
||||
- TVOS_FRAMEWORK_SCHEME="HTMLKit-tvOS"
|
||||
- IOS_SDK=iphonesimulator11.0
|
||||
- MACOS_SDK=macosx10.13
|
||||
- WATCHOS_SDK=watchsimulator4.0
|
||||
- TVOS_SDK=appletvsimulator11.0
|
||||
matrix:
|
||||
- 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
|
||||
- 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
|
||||
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)
|
||||
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
# Change Log
|
||||
|
||||
## [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
|
||||
|
||||
### Added
|
||||
|
||||
- `Swift Package Manager` support
|
||||
|
||||
## [0.9.3](https://github.com/iabudiab/HTMLKit/releases/tag/0.9.3)
|
||||
|
||||
Released on 2016.07.16
|
||||
|
||||
This release passes all tokenizer and tree construction html5lib-tests as of 2016.07.16
|
||||
|
||||
### Added
|
||||
|
||||
- `watchOS` and `tvOS` targets
|
||||
- Updated HTML5Lib-Tests submodule (c305da7)
|
||||
|
||||
## [0.9.2](https://github.com/iabudiab/HTMLKit/releases/tag/0.9.2)
|
||||
|
||||
Released on 2016.05.18
|
||||
|
||||
This release passes all tokenizer and tree construction html5lib-tests as of 2016.05.18
|
||||
|
||||
### Added
|
||||
|
||||
- Handling for `<menu>` and `<menuitem>`
|
||||
- Changelog
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated adoption agency algorithm according to the latest specification, see:
|
||||
- [whatwg/html@22ce3c3](https://github.com/whatwg/html/commit/22ce3c3)
|
||||
- [Mozilla Bug 901319](https://bugzilla.mozilla.org/show_bug.cgi?id=901319)
|
||||
- [Chrome Issue 268121](https://bugs.chromium.org/p/chromium/issues/detail?id=268121)
|
||||
- [WebKit Bug 119478](https://bugs.webkit.org/show_bug.cgi?id=119478)
|
||||
- `<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
|
||||
|
||||
### Added
|
||||
|
||||
- Travis-CI integration.
|
||||
- CocoaPods spec.
|
||||
|
||||
### Changed
|
||||
|
||||
- Warnings are treated as errors.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Warnings related to format specifier and loss of precision due to NS(U)-integer usage.
|
||||
- Replaced `@returns` with `@return` throughout the documentation to play nicely with Jazzy.
|
||||
- Some README examples used Swift syntax.
|
||||
|
||||
## [0.9.0](https://github.com/iabudiab/HTMLKit/releases/tag/0.9.0)
|
||||
|
||||
Released on 2015.12.23
|
||||
|
||||
This is the first public release of `HTMLKit`.
|
||||
|
||||
### Added
|
||||
|
||||
- `iOS` & `OSX` Frameworks.
|
||||
- Source code documentation.
|
||||
- CSS Selectors extension (analogous to jQuery selectors).
|
||||
- `DOMTokenList` for malipulating `HTMLElements` attributes as a list, e.g. `class`.
|
||||
- Handling for `<ruby>` elements in the Parser implementation.
|
||||
- Updated HTML5Lib-Tests submodule (56c435f)
|
||||
- Xcode Playground with Swift documentation.
|
||||
|
||||
### Removed
|
||||
|
||||
- Unused namespaces.
|
||||
- Historical node types.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `lt`, `gt` & `eq` CSS Selectors method declarations.
|
||||
|
||||
## [0.3.0](https://github.com/iabudiab/HTMLKit/releases/tag/0.3.0)
|
||||
|
||||
Released on 2015.11.29
|
||||
|
||||
### Added
|
||||
|
||||
- CSS3 Selectors support.
|
||||
- Nullability annotations.
|
||||
- `HTMLNode` properties for previous and next sibling elements.
|
||||
- `HTMLNode` methods for accessing child elements (analogous to child nodes).
|
||||
- `NSCharacterSet` category for HTML-related character sets.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `InputStreaReader`'s reconsume-logic that is required by the CSS Parser.
|
||||
|
||||
## [0.2.0](https://github.com/iabudiab/HTMLKit/releases/tag/0.1.0)
|
||||
|
||||
Released on 2015.06.06
|
||||
|
||||
### Added
|
||||
|
||||
- `HTMLDocument` methods to access `root`, `head` & `body` elements.
|
||||
- `innerHTML` implementation for the `HTMLElement`.
|
||||
- `HTMLNode` methods to append, prepend, check containment and descendancy of nodes.
|
||||
- `HTMLNode` methods to enumerate child nodes.
|
||||
- Implementations for `NodeIterator` and `NodeFilter`
|
||||
- Implementation for `TreeWalker`
|
||||
- Validation for DOM manipulations.
|
||||
- Tests for the DOM implementation.
|
||||
|
||||
### Changed
|
||||
|
||||
- `type` property renamed to `nodeType` in `HTMLNode`.
|
||||
- `firstChildNode` and `lastChildNode` renamed to `firtChild` and `lastChild` in `HTMLNode`.
|
||||
|
||||
### Removed
|
||||
|
||||
- `baseURI` proeprty from `HTMLNode`
|
||||
- `HTMLNodeTreeEnumerator` is superseded by the `HTMLNodeIterator`.
|
||||
|
||||
## [0.1.0](https://github.com/iabudiab/HTMLKit/releases/tag/0.1.0)
|
||||
|
||||
Released on 2015.04.20
|
||||
|
||||
### Added
|
||||
|
||||
- Initial release.
|
||||
- Initial DOM implementation.
|
||||
- Tokenizer and Parser pass all [HTML5Lib](https://github.com/html5lib/html5lib-tests) tokenizer and tree construction tests except for `<ruby>` elements.
|
||||
@@ -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">
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
import HTMLKit
|
||||
|
||||
HTMLSanitizingPolicy { (builder) in
|
||||
builder
|
||||
.allowCommonBlockElements()
|
||||
.allowCommonInlineFormattingElements()
|
||||
.allowElements(["p", "div"])
|
||||
.allow(HTMLElementPolicy.identity(), onElements: ["b", "p"])
|
||||
.allow(HTMLAttributePolicy.init(), onElements: [])
|
||||
.disallowText(inElements: ["a"])
|
||||
}
|
||||
|
||||
HTMLElementPolicy { (str) -> String in
|
||||
return str
|
||||
}
|
||||
|
||||
|
||||
HTMLSanitizer { (builder) in
|
||||
builder
|
||||
.allowCommonBlockElements()
|
||||
.allowCommonInlineFormattingElements()
|
||||
.allowElements(["p", "div"])
|
||||
.allow(HTMLElementPolicy.identity() , onElements: ["b", "p"])
|
||||
.allow(HTMLAttributePolicy.init(), onElements: [])
|
||||
.disallowText(inElements: ["a"])
|
||||
}
|
||||
|
||||
|
||||
@@ -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,10 +1,11 @@
|
||||
<?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='raw' last-migration='0800'>
|
||||
<pages>
|
||||
<page name='Intro'/>
|
||||
<page name='Parsing Documents'/>
|
||||
<page name='Parsing Fragments'/>
|
||||
<page name='The DOM'/>
|
||||
<page name='CSS Selectors'/>
|
||||
<page name='Sanitizing HTML'/>
|
||||
</pages>
|
||||
</playground>
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 116 KiB |
@@ -0,0 +1,23 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "HTMLKit"
|
||||
s.version = "2.1.4"
|
||||
s.summary = "HTMLKit, an Objective-C framework for your everyday HTML needs."
|
||||
s.license = "MIT"
|
||||
s.homepage = "https://github.com/iabudiab/HTMLKit"
|
||||
s.author = "iabudiab"
|
||||
s.social_media_url = "https://twitter.com/_iabudiab"
|
||||
|
||||
s.ios.deployment_target = "8.0"
|
||||
s.osx.deployment_target = "10.9"
|
||||
s.watchos.deployment_target = "2.0"
|
||||
s.tvos.deployment_target = "9.0"
|
||||
|
||||
s.source = { :git => "https://github.com/iabudiab/HTMLKit.git", :tag => s.version }
|
||||
|
||||
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,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
|
||||
end
|
||||
+1398
-208
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>
|
||||
@@ -0,0 +1,110 @@
|
||||
<?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 = "62857CE91D39A262008DC254"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-tvOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62857CF21D39A262008DC254"
|
||||
BuildableName = "HTMLKit-tvOSTests.xctest"
|
||||
BlueprintName = "HTMLKit-tvOSTests"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance">
|
||||
</Test>
|
||||
</SkippedTests>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62857CE91D39A262008DC254"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-tvOS"
|
||||
ReferencedContainer = "container:HTMLKit.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">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62857CE91D39A262008DC254"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-tvOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62857CE91D39A262008DC254"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-tvOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,83 @@
|
||||
<?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 = "62857C4D1D398642008DC254"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-watchOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<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">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62857C4D1D398642008DC254"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-watchOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62857C4D1D398642008DC254"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-watchOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
+3
@@ -7,4 +7,7 @@
|
||||
<FileRef
|
||||
location = "group:HTMLKit.playground">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Example/HTMLKitExample/HTMLKitExample.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
//
|
||||
// CSSStructuralPseudoSelector.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 11/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
@class CSSSelector;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
@returns Root element selector: ':root'
|
||||
*/
|
||||
extern CSSSelector * rootSelector();
|
||||
|
||||
/**
|
||||
@returns Empy element selector: ':empty'
|
||||
*/
|
||||
extern CSSSelector * emptySelector();
|
||||
|
||||
/**
|
||||
@returns A parent element selector: ':parent'
|
||||
*/
|
||||
extern CSSSelector * parentSelector();
|
||||
|
||||
/**
|
||||
@returns A button element selector: ':button'
|
||||
*/
|
||||
extern CSSSelector * buttonSelector();
|
||||
|
||||
/**
|
||||
@returns A checkbox element selector: ':checkbox'
|
||||
*/
|
||||
extern CSSSelector * checkboxSelector();
|
||||
|
||||
/**
|
||||
@returns A file element selector: ':file'
|
||||
*/
|
||||
extern CSSSelector * fileSelector();
|
||||
|
||||
/**
|
||||
@returns A header element selector: ':header'
|
||||
*/
|
||||
extern CSSSelector * headerSelector();
|
||||
|
||||
/**
|
||||
@returns An image element selector: ':image'
|
||||
*/
|
||||
extern CSSSelector * imageSelector();
|
||||
|
||||
/**
|
||||
@returns A parent element selector: ':parent'
|
||||
*/
|
||||
extern CSSSelector * inputSelector();
|
||||
|
||||
/**
|
||||
@returns A link element selector: ':link'
|
||||
*/
|
||||
extern CSSSelector * linkSelector();
|
||||
|
||||
/**
|
||||
@returns A password element selector: ':password'
|
||||
*/
|
||||
extern CSSSelector * passwordSelector();
|
||||
|
||||
/**
|
||||
@returns A radio element selector: ':radio'
|
||||
*/
|
||||
extern CSSSelector * radioSelector();
|
||||
|
||||
/**
|
||||
@returns A reset element selector: ':reset'
|
||||
*/
|
||||
extern CSSSelector * resetSelector();
|
||||
|
||||
/**
|
||||
@returns A submit element selector: ':submit'
|
||||
*/
|
||||
extern CSSSelector * submitSelector();
|
||||
|
||||
/**
|
||||
@returns A text element selector: ':text'
|
||||
*/
|
||||
extern CSSSelector * textSelector();
|
||||
|
||||
/**
|
||||
@returns An enabled element selector: ':enabled'
|
||||
*/
|
||||
extern CSSSelector * enabledSelector();
|
||||
|
||||
/**
|
||||
@returns A disabled element selector: ':disabled'
|
||||
*/
|
||||
extern CSSSelector * disabledSelector();
|
||||
|
||||
/**
|
||||
@returns A checked element selector: ':checked'
|
||||
*/
|
||||
extern CSSSelector * checkedSelector();
|
||||
|
||||
/**
|
||||
@returns An optional element selector: ':optional'
|
||||
*/
|
||||
extern CSSSelector * optionalSelector();
|
||||
|
||||
/**
|
||||
@returns A required element selector: ':required'
|
||||
*/
|
||||
extern CSSSelector * requiredSelector();
|
||||
|
||||
/**
|
||||
Less-than selector, e.g. 'lt(2)'
|
||||
|
||||
Selects all elements at an index less than the specified index. A negative index counts backwards from the last element.
|
||||
|
||||
@param index The zero-based index of the element to match.
|
||||
@returns A Less-Than selector.
|
||||
*/
|
||||
extern CSSSelector * ltSelector(NSInteger index);
|
||||
|
||||
/**
|
||||
Greater-than selector, e.g. 'gt(2)'
|
||||
|
||||
Selects all elements at an index greater than the specified index. A negative index counts backwards from the
|
||||
last element.
|
||||
|
||||
@param index The zero-based index of the element to match.
|
||||
@returns A Greater-Than selector.
|
||||
*/
|
||||
extern CSSSelector * gtSelector(NSInteger index);
|
||||
|
||||
/**
|
||||
Equal selector, e.g. 'eq(3)'
|
||||
|
||||
Selects the element at the specified index. A negative index counts backwards from the last element.
|
||||
|
||||
@param index The zero-based index of the element to match.
|
||||
@returns An Equal selector.
|
||||
*/
|
||||
extern CSSSelector * eqSelector(NSInteger index);
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -1,9 +0,0 @@
|
||||
//
|
||||
// Prefix header
|
||||
//
|
||||
// The contents of this file are implicitly included at the beginning of every source file.
|
||||
//
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// HTMLParseErrorToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
///------------------------------------------------------
|
||||
/// HTMLKit private header
|
||||
///------------------------------------------------------
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
/**
|
||||
HTML Parse Error Token
|
||||
*/
|
||||
@interface HTMLParseErrorToken : HTMLToken
|
||||
|
||||
/** @brief The error's reason message. */
|
||||
@property (nonatomic, copy) NSString *reason;
|
||||
|
||||
/** @brief The error's location in the stream. */
|
||||
@property (nonatomic, assign) NSUInteger location;
|
||||
|
||||
/**
|
||||
Initializes a new Parse Error token.
|
||||
|
||||
@param reason The error's reason message.
|
||||
@param location The error's location in the stream.
|
||||
@returns A new instance of a parse error token.
|
||||
*/
|
||||
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location;
|
||||
|
||||
@end
|
||||
@@ -1,38 +0,0 @@
|
||||
//
|
||||
// HTMLParseErrorToken.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLParseErrorToken.h"
|
||||
|
||||
@interface HTMLParseErrorToken ()
|
||||
{
|
||||
NSString *_reason;
|
||||
NSUInteger _location;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLParseErrorToken
|
||||
@synthesize reason = _reason;
|
||||
@synthesize location = _location;
|
||||
|
||||
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.type = HTMLTokenTypeParseError;
|
||||
_reason = [reason copy];
|
||||
_location = location;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p Reason='%@' Location='%lu'>", self.class, self, _reason, _location];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,79 +0,0 @@
|
||||
//
|
||||
// HTMLText.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
@implementation HTMLText
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithData:@""];
|
||||
}
|
||||
|
||||
- (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 ?: @""];
|
||||
}
|
||||
|
||||
- (void)appendString:(NSString *)string
|
||||
{
|
||||
[self.data appendString:string];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLText *copy = [super copyWithZone:zone];
|
||||
copy.data = self.data;
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - Serialization
|
||||
|
||||
- (NSString *)outerHTML
|
||||
{
|
||||
if ([self.parentElement.tagName isEqualToAny:@"style", @"script", @"xmp", @"iframe", @"noembed", @"noframes",
|
||||
@"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];
|
||||
return escaped;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p \"%@\">", self.class, self, self.data];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,2 +0,0 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
Submodule HTMLKitTests/html5lib-tests deleted from 56c435f033
@@ -0,0 +1,6 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "HTMLKit",
|
||||
exclude: ["Tests/Fixtures", "Tests/css-tests", "Tests/html5lib-tests"]
|
||||
)
|
||||
@@ -1,8 +1,23 @@
|
||||
# HTMLKit
|
||||
|
||||

|
||||

|
||||
|
||||
An Objective-C framework for your everyday HTML needs.
|
||||
|
||||
[](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.
|
||||
@@ -15,15 +30,94 @@ DOM mutations are validated as described in the [WHATWG DOM Standard](https://do
|
||||
|
||||
## Tests
|
||||
|
||||
HTMLKit passes all of the [HTML5Lib](https://github.com/html5lib/html5lib-tests) Tokenizer and Tree Construction tests except the Blink changes introduced on 16.09.2015. The `html5lib-tests` is configured as a git-submodule. If you plan to run the tests, do not forget to pull it too.
|
||||
HTMLKit passes all of the [HTML5Lib](https://github.com/html5lib/html5lib-tests) Tokenizer and Tree Construction tests. The `html5lib-tests` is configured as a git-submodule. If you plan to run the tests, do not forget to pull it too.
|
||||
|
||||
The CSS3 Selector implementation is tested with the an adapted version of the [CSS3 Selectors Test Suite](http://www.w3.org/Style/CSS/Test/CSS3/Selectors/current/html/full/flat/index.html), ignoring the tests that require user interaction, session history, and scripting.
|
||||
The CSS3 Selector implementation is tested with an adapted version of the [CSS3 Selectors Test Suite](http://www.w3.org/Style/CSS/Test/CSS3/Selectors/current/html/full/flat/index.html), ignoring the tests that require user interaction, session history, and scripting.
|
||||
|
||||
## Does it Swift?
|
||||
|
||||
Check out the playground!
|
||||
|
||||
# Parsing Documents
|
||||
# Installation
|
||||
|
||||
## Carthage
|
||||
|
||||
[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
|
||||
|
||||
If you don't have Carthage yet, you can install it with Homebrew using the following command:
|
||||
|
||||
```bash
|
||||
$ brew update
|
||||
$ brew install carthage
|
||||
```
|
||||
|
||||
To add `HTMLKit` as a dependency into your project using Carthage just add the following line in your `Cartfile`:
|
||||
|
||||
```
|
||||
github "iabudiab/HTMLKit"
|
||||
```
|
||||
|
||||
Then run the following command to build the framework and drag the built `HTMLKit.framework` into your Xcode project.
|
||||
|
||||
```bash
|
||||
$ carthage update
|
||||
```
|
||||
|
||||
## CocoaPods
|
||||
|
||||
[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects.
|
||||
|
||||
If you don't have CocoaPods yet, you can install it with the following command:
|
||||
|
||||
```bash
|
||||
$ gem install cocoapods
|
||||
```
|
||||
|
||||
To add `HTMLKit` as a dependency into your project using CocoaPods just add the following in your `Podfile`:
|
||||
|
||||
```ruby
|
||||
target 'MyTarget' do
|
||||
pod 'HTMLKit', '~> 2.1'
|
||||
end
|
||||
```
|
||||
|
||||
Then, run the following command:
|
||||
|
||||
```bash
|
||||
$ pod install
|
||||
```
|
||||
|
||||
## 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: 2)
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
$ swift build
|
||||
```
|
||||
|
||||
## Manually
|
||||
|
||||
1- Add `HTMLKit` as git submodule
|
||||
|
||||
```bash
|
||||
$ git submodule add https://github.com/iabudiab/HTMLKit.git
|
||||
```
|
||||
|
||||
2- Open the `HTMLKit` folder and drag'n'drop the `HTMLKit.xcodeproj` into the Project Navigator in Xcode to add it as a sub-project.
|
||||
|
||||
3- In the General panel of your target add `HTMLKit.framework` under the `Embedded Binaries`
|
||||
|
||||
# Parsing
|
||||
|
||||
## Parsing Documents
|
||||
|
||||
Given some HTML content, you can parse it either via the `HTMLParser` or instatiate a `HTMLDocument` directly:
|
||||
|
||||
@@ -38,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:
|
||||
|
||||
@@ -61,12 +155,12 @@ 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
|
||||
|
||||
```objective-c
|
||||
HTMLElement *description = [[HTMLElement alloc] initWithTagName:@"body" attributes: @{@"name": @"description"}];
|
||||
HTMLElement *description = [[HTMLElement alloc] initWithTagName:@"meta" attributes: @{@"name": @"description"}];
|
||||
description[@"content"] = @"HTMLKit for iOS & OSX";
|
||||
```
|
||||
|
||||
@@ -85,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) {
|
||||
@@ -105,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
|
||||
@@ -112,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
|
||||
@@ -133,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:
|
||||
@@ -177,7 +282,7 @@ NSArray *hasSiblingParagraph = [document elementsMatchingSelector:generalSibling
|
||||
NSArray *nonParagraphChildOfDiv = [document elementsMatchingSelector:
|
||||
allOf(@[
|
||||
childOfElementSelector(typeSelector(@"div")),
|
||||
nay(typeSelector(@"p"))
|
||||
not(typeSelector(@"p"))
|
||||
])
|
||||
];
|
||||
```
|
||||
@@ -187,17 +292,17 @@ Here are more examples:
|
||||
```objective-c
|
||||
HTMLNode *firstDivElement = [document firstElementMatchingSelector:typeSelector(@"div")];
|
||||
|
||||
var secondChildOfDiv = [firstDivElement querySelectorAll:@":nth-child(2)"];
|
||||
var secondOfType = [firstDivElement querySelectorAll:@":nth-of-type(2n)"];
|
||||
NSArray *secondChildOfDiv = [firstDivElement querySelectorAll:@":nth-child(2)"];
|
||||
NSArray *secondOfType = [firstDivElement querySelectorAll:@":nth-of-type(2n)"];
|
||||
|
||||
secondChildOfDiv = [firstDivElement elementsMatchingSelector:nthChildSelector(CSSNthExpressionMake(0, 2))];
|
||||
secondOfType = [firstDivElement elementsMatchingSelector:nthOfTypeSelector(CSSNthExpressionMake(2, 0))];
|
||||
|
||||
var notParagraphAndNotDiv = [firstDivElement querySelectorAll:@":not(p):not(div)"];
|
||||
NSArray *notParagraphAndNotDiv = [firstDivElement querySelectorAll:@":not(p):not(div)"];
|
||||
notParagraphAndNotDiv = [firstDivElement elementsMatchingSelector:
|
||||
allOf([
|
||||
nay(typeSelector(@"p")),
|
||||
nay(typeSelector(@"div"))
|
||||
not(typeSelector(@"p")),
|
||||
not(typeSelector(@"div"))
|
||||
])
|
||||
];
|
||||
```
|
||||
@@ -211,6 +316,10 @@ CSSSelector *myAwesomeSelector = namedBlockSelector(@"myAwesomeSelector", ^BOOL
|
||||
notParagraphAndNotDiv = [firstDivElement elementsMatchingSelector:myAwesomeSelector];
|
||||
```
|
||||
|
||||
# Change Log
|
||||
|
||||
See the [CHANGELOG.md](CHANGELOG.md) for more info.
|
||||
|
||||
# License
|
||||
|
||||
HTMLKit is available under the MIT license. See the LICENSE file for more info.
|
||||
HTMLKit is available under the MIT license. See the [LICENSE](LICENSE) file for more info.
|
||||
|
||||
@@ -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,8 +115,8 @@
|
||||
[self consumeNextInputCharacter];
|
||||
}
|
||||
|
||||
NSScanner *scanner = [NSScanner scannerWithString:(__bridge NSString *)(hexString)];
|
||||
UTF32Char number;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:(__bridge_transfer NSString *)(hexString)];
|
||||
unsigned int number;
|
||||
[scanner scanHexInt:&number];
|
||||
|
||||
return isValidEscapedCodePoint(number) ? number : REPLACEMENT_CHARACTER;
|
||||
@@ -27,18 +27,18 @@ NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression)
|
||||
}
|
||||
|
||||
if (expression.an == 0) {
|
||||
return [NSString stringWithFormat:@"%ld", expression.b];
|
||||
return [NSString stringWithFormat:@"%ld", (long)expression.b];
|
||||
}
|
||||
if (expression.b == 0) {
|
||||
return [NSString stringWithFormat:@"%ldn", expression.an];
|
||||
return [NSString stringWithFormat:@"%ldn", (long)expression.an];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%ldn%+ld", expression.an, expression.b];
|
||||
return [NSString stringWithFormat:@"%ldn%+ld", (long)expression.an, (long)expression.b];
|
||||
}
|
||||
|
||||
#pragma mark - Implementation
|
||||
|
||||
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,25 @@
|
||||
//
|
||||
// HTMLAttributePolicy.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 28.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NSString * _Nullable (^ SanitizeAttrbiuteValueBlock) (NSString * _Nullable value, NSString * key);
|
||||
|
||||
@interface HTMLAttributePolicy : NSObject
|
||||
|
||||
+ (instancetype)identity;
|
||||
+ (instancetype)rejectAll;
|
||||
+ (instancetype)policyWithBlock:(SanitizeAttrbiuteValueBlock)block;
|
||||
|
||||
- (NSString *)sanitizeValue:(NSString *)value forKey:(NSString *)key;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// HTMLAttributePolicy.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 28.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLAttributePolicy.h"
|
||||
|
||||
@interface HTMLAttributePolicy()
|
||||
{
|
||||
SanitizeAttrbiuteValueBlock _block;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLAttributePolicy
|
||||
|
||||
+ (instancetype)identity
|
||||
{
|
||||
return [self policyWithBlock:^NSString * _Nullable (NSString * _Nullable value, NSString * _Nonnull key) {
|
||||
return value;
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)rejectAll
|
||||
{
|
||||
return [self policyWithBlock:^NSString * _Nullable (NSString * _Nullable value, NSString * _Nonnull key) {
|
||||
return nil;
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)policyWithBlock:(SanitizeAttrbiuteValueBlock)block
|
||||
{
|
||||
HTMLAttributePolicy *policy = [[HTMLAttributePolicy alloc] initWithBlock:block];
|
||||
return policy;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBlock:(SanitizeAttrbiuteValueBlock)block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_block = block;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)sanitizeValue:(NSString *)value forKey:(NSString *)key
|
||||
{
|
||||
if (_block) {
|
||||
return _block(value, key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}];
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// HTMLElementPolicy.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLSanitizingPolicy.h"
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NSString * _Nullable (^ SanitizeElementNameBlock) (NSString *);
|
||||
typedef HTMLOrderedDictionary * _Nullable (^ SanitizeElementAttributesBlock) (HTMLOrderedDictionary * _Nullable);
|
||||
|
||||
@interface HTMLElementPolicy : NSObject
|
||||
|
||||
+ (instancetype)identity;
|
||||
+ (instancetype)rejectAll;
|
||||
+ (instancetype)policyWithNameBlock:(SanitizeElementNameBlock)nameBlock;
|
||||
+ (instancetype)policyWithNameBlock:(SanitizeElementNameBlock)nameBlock
|
||||
attributesBlock:(nullable SanitizeElementAttributesBlock)attributesBlock;
|
||||
|
||||
- (NSString *)sanitizeName:(NSString *)name;
|
||||
- (HTMLOrderedDictionary *)sanitzeAttributes:(HTMLOrderedDictionary *)attributes;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// HTMLElementPolicy.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLElementPolicy.h"
|
||||
|
||||
@interface HTMLElementPolicy()
|
||||
{
|
||||
SanitizeElementNameBlock _nameBlock;
|
||||
SanitizeElementAttributesBlock _attributesBlock;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLElementPolicy
|
||||
|
||||
+ (instancetype)identity
|
||||
{
|
||||
return [self policyWithNameBlock:^NSString * _Nullable (NSString * _Nonnull name) {
|
||||
return name;
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)rejectAll
|
||||
{
|
||||
return [self policyWithNameBlock:^NSString *_Nullable (NSString * _Nonnull name) {
|
||||
return nil;
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)policyWithNameBlock:(NSString * _Nonnull (^)(NSString * _Nonnull))nameBlock
|
||||
{
|
||||
return [self policyWithNameBlock:nameBlock attributesBlock:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)policyWithNameBlock:(SanitizeElementNameBlock)nameBlock
|
||||
attributesBlock:(SanitizeElementAttributesBlock)attributesBlock
|
||||
{
|
||||
HTMLElementPolicy *policy = [[HTMLElementPolicy alloc] initWithNameBlock:nameBlock attributesBlock:attributesBlock];
|
||||
return policy;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNameBlock:(SanitizeElementNameBlock)nameBlock
|
||||
attributesBlock:(SanitizeElementAttributesBlock)attributesBlock
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_nameBlock = nameBlock;
|
||||
_attributesBlock = attributesBlock;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)sanitizeName:(NSString *)name
|
||||
{
|
||||
if (_nameBlock) {
|
||||
return _nameBlock(name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
- (HTMLOrderedDictionary *)sanitzeAttributes:(HTMLOrderedDictionary *)attributes
|
||||
{
|
||||
if (_attributesBlock) {
|
||||
return _attributesBlock(attributes);
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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)", 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)", 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)", 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;
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.braincookie.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>2.1.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
@@ -6,6 +6,7 @@
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
|
||||
NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError";
|
||||
@@ -13,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";
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// HTMLParseErrorToken.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLParseErrorToken.h"
|
||||
|
||||
@interface HTMLParseErrorToken ()
|
||||
{
|
||||
NSString *_code;
|
||||
NSString *_details;
|
||||
NSUInteger _location;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLParseErrorToken
|
||||
@synthesize code = _code;
|
||||
@synthesize details = _details;
|
||||
@synthesize location = _location;
|
||||
|
||||
- (instancetype)initWithCode:(NSString *)code details:(NSString *)details location:(NSUInteger)location
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.type = HTMLTokenTypeParseError;
|
||||
_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 Code='%@' Details='%@' Location='%lu'>", self.class, self, _code, _details, (unsigned long)_location];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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];
|
||||
@@ -588,20 +586,29 @@
|
||||
HTMLElement *lastNode = furthestBlock;
|
||||
|
||||
NSUInteger index = [_stackOfOpenElements indexOfElement:node];
|
||||
for (int innerLoopCounter = 0; innerLoopCounter < 3; innerLoopCounter ++) {
|
||||
|
||||
index--;
|
||||
int innerLoopCounter = 0;
|
||||
while (YES) {
|
||||
|
||||
innerLoopCounter += 1;
|
||||
index -= 1;
|
||||
|
||||
node = _stackOfOpenElements[index];
|
||||
|
||||
if ([node isEqual:formattingElement]) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (innerLoopCounter > 3 && [_listOfActiveFormattingElements containsElement:node]) {
|
||||
[_listOfActiveFormattingElements removeElement:node];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (![_listOfActiveFormattingElements containsElement:node]) {
|
||||
[_stackOfOpenElements removeElement:node];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([node isEqual:formattingElement]) {
|
||||
break;
|
||||
}
|
||||
|
||||
HTMLElement *newElement = [node copy];
|
||||
[_listOfActiveFormattingElements replaceElementAtIndex:[_listOfActiveFormattingElements indexOfElement:node]
|
||||
withElement:newElement];
|
||||
@@ -1268,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]) {
|
||||
@@ -1364,7 +1371,7 @@
|
||||
if (type == nil || ![type isEqualToStringIgnoringCase:@"hidden"]) {
|
||||
_framesetOkFlag = NO;
|
||||
}
|
||||
} else if ([tagName isEqualToAny:@"menuitem", @"param", @"source", @"track", nil]) {
|
||||
} else if ([tagName isEqualToAny:@"param", @"source", @"track", nil]) {
|
||||
[self insertElementForToken:token];
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
} else if ([tagName isEqualToString:@"hr"]) {
|
||||
@@ -1378,52 +1385,6 @@
|
||||
[self emitParseError:@"Image Start Tag Token with tagname <image> should be <img>. Don't ask."];
|
||||
token.tagName = @"img";
|
||||
[self reprocessToken:token];
|
||||
} else if ([tagName isEqualToString:@"isindex"]) {
|
||||
[self emitParseError:@"Unexpected start tag <isindex> in <body>"];
|
||||
if (_formElementPointer != nil && ![_stackOfOpenElements containsElementWithTagName:@"template"]) {
|
||||
return;
|
||||
}
|
||||
_framesetOkFlag = NO;
|
||||
if ([_stackOfOpenElements hasElementInButtonScopeWithTagName:@"p"]) {
|
||||
[self closePElement];
|
||||
}
|
||||
|
||||
HTMLStartTagToken *formToken = [[HTMLStartTagToken alloc] initWithTagName:@"form"];
|
||||
HTMLElement *form = [self insertElementForToken:formToken];
|
||||
if (![_stackOfOpenElements containsElementWithTagName:@"template"]) {
|
||||
_formElementPointer = form;
|
||||
}
|
||||
NSString *action = token.attributes[@"action"];
|
||||
if (action != nil) {
|
||||
form.attributes[@"action"] = action;
|
||||
}
|
||||
|
||||
HTMLStartTagToken *hrToken = [[HTMLStartTagToken alloc] initWithTagName:@"hr"];
|
||||
[self insertElementForToken:hrToken];
|
||||
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
[self reconstructActiveFormattingElements];
|
||||
|
||||
HTMLStartTagToken *labelToken = [[HTMLStartTagToken alloc] initWithTagName:@"label"];
|
||||
[self insertElementForToken:labelToken];
|
||||
|
||||
NSString *prompt = token.attributes[@"prompt"] ?: @"This is a searchable index. Enter search keywords: ";
|
||||
[self insertCharacters:prompt];
|
||||
|
||||
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:token.attributes];
|
||||
attributes[@"name"] = @"isindex";
|
||||
[attributes removeObjectForKey:@"action"];
|
||||
[attributes removeObjectForKey:@"prompt"];
|
||||
|
||||
HTMLStartTagToken *inputToken = [[HTMLStartTagToken alloc] initWithTagName:@"input" attributes:attributes];
|
||||
[self insertElementForToken:inputToken];
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
[self insertElementForToken:hrToken];
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
_formElementPointer = nil;
|
||||
} else if ([tagName isEqualToString:@"textarea"]) {
|
||||
[self insertElementForToken:token];
|
||||
_ignoreNextLineFeedCharacterToken = YES;
|
||||
@@ -1482,7 +1443,6 @@
|
||||
} else if ([tagName isEqualToString:@"math"]) {
|
||||
[self reconstructActiveFormattingElements];
|
||||
AdjustMathMLAttributes(token);
|
||||
AdjustForeignAttributes(token);
|
||||
[self insertForeignElementForToken:token inNamespace:HTMLNamespaceMathML];
|
||||
if (token.isSelfClosing) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
@@ -1490,7 +1450,6 @@
|
||||
} else if ([tagName isEqualToString:@"svg"]) {
|
||||
[self reconstructActiveFormattingElements];
|
||||
AdjustSVGAttributes(token);
|
||||
AdjustForeignAttributes(token);
|
||||
[self insertForeignElementForToken:token inNamespace:HTMLNamespaceSVG];
|
||||
if (token.isSelfClosing) {
|
||||
[_stackOfOpenElements popCurrentNode];
|
||||
@@ -1571,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;
|
||||
}
|
||||
@@ -1591,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;
|
||||
}
|
||||
@@ -1611,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;
|
||||
}
|
||||
@@ -1632,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]) {
|
||||
@@ -2572,7 +2531,7 @@
|
||||
return;
|
||||
case HTMLTokenTypeStartTag:
|
||||
{
|
||||
void (^ anythingElse)() = ^ {
|
||||
void (^ anythingElse)(void) = ^ {
|
||||
if (self.adjustedCurrentNode.htmlNamespace == HTMLNamespaceMathML) {
|
||||
AdjustMathMLAttributes(token.asTagToken);
|
||||
}
|
||||
@@ -2580,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
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// HTMLSanitizer.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLSanitizingPolicyBuilder.h"
|
||||
#import "HTMLSanitizingPolicy.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HTMLSanitizer : NSObject
|
||||
|
||||
+ (instancetype)sanitizerWithPolicy:(void (^)(HTMLSanitizingPolicyBuilder *))block;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// HTMLSanitizer.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLSanitizer.h"
|
||||
#import "HTMLTokenizer.h"
|
||||
#import "HTMLTokens.h"
|
||||
|
||||
@interface HTMLSanitizer()
|
||||
{
|
||||
HTMLTokenizer *_tokenizer;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation HTMLSanitizer
|
||||
|
||||
+ (instancetype)sanitizerWithPolicy:(void (^)(HTMLSanitizingPolicyBuilder *))block
|
||||
{
|
||||
HTMLSanitizingPolicyBuilder *builder = [HTMLSanitizingPolicyBuilder new];
|
||||
block(builder);
|
||||
return nil; //[[HTMLSanitizingPolicy alloc] initWithBuilder:builder];
|
||||
}
|
||||
|
||||
- (instancetype)initWithString:(NSString *)string
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_tokenizer = [[HTMLTokenizer alloc] initWithString:string ?: @""];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)sanitize
|
||||
{
|
||||
// for (HTMLToken *token in _tokenizer) {
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// HTMLSanitizingPolicy.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HTMLSanitizingPolicy : NSObject
|
||||
|
||||
- (HTMLSanitizingPolicy *)combineWith:(nullable HTMLSanitizingPolicy *)other;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// HTMLSanitizingPolicy.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLSanitizingPolicy.h"
|
||||
|
||||
@interface HTMLSanitizingPolicy()
|
||||
{
|
||||
NSMutableArray *_policies;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLSanitizingPolicy
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_policies = [NSMutableArray new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (HTMLSanitizingPolicy *)combineWith:(HTMLSanitizingPolicy *)other
|
||||
{
|
||||
if (other) {
|
||||
[_policies addObject:other];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// HTMLSanitizingPolicyBuilder.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "HTMLElementPolicy.h"
|
||||
#import "HTMLTokenPolicy.h"
|
||||
#import "HTMLAttributePolicy.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HTMLSanitizingPolicyBuilder : NSObject
|
||||
|
||||
- (HTMLSanitizingPolicyBuilder *)allowElements:(NSArray<NSString *> *)elementNames;
|
||||
- (HTMLSanitizingPolicyBuilder *)disallowElements:(NSArray<NSString *> *)elementNames;
|
||||
- (HTMLSanitizingPolicyBuilder *)allowPolicy:(HTMLElementPolicy *)policy onElements:(NSArray<NSString *> *)elementNames;
|
||||
- (HTMLSanitizingPolicyBuilder *)allowCommonInlineFormattingElements;
|
||||
- (HTMLSanitizingPolicyBuilder *)allowCommonBlockElements;
|
||||
- (HTMLSanitizingPolicyBuilder *)allowTextInElements:(NSArray<NSString *> *)elementNames;
|
||||
- (HTMLSanitizingPolicyBuilder *)disallowTextInElements:(NSArray<NSString *> *)elementNames;
|
||||
|
||||
//- (HTMLSanitizingPolicyBuilder *)allowAttributes:(NSArray<NSString *> *)attributeName
|
||||
// onElements:(NSArray<NSString *> *)elementNames;
|
||||
//- (HTMLSanitizingPolicyBuilder *)disallowAttributes:(NSArray<NSString *> *)attributeName
|
||||
// onElements:(NSArray<NSString *> *)elementNames;
|
||||
//
|
||||
//- (HTMLSanitizingPolicyBuilder *)allowAttributePolicy:(HTMLAttributePolicy *)policy
|
||||
// onElements:(NSArray<NSString *> *)elementNames;
|
||||
//- (HTMLSanitizingPolicyBuilder *)disallowAttributePolicy:(HTMLAttributePolicy *)policy
|
||||
// onElements:(NSArray<NSString *> *)elementNames;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// HTMLSanitizingPolicyBuilder.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26.05.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLSanitizingPolicyBuilder.h"
|
||||
|
||||
@interface HTMLSanitizingPolicyBuilder()
|
||||
{
|
||||
NSMutableDictionary<NSString *, NSMutableArray<HTMLElementPolicy *> *> * elementPolicies;
|
||||
NSMutableDictionary<NSString *, HTMLAttributePolicy *> * attributePolicies;
|
||||
NSMutableDictionary<NSString *, NSNumber *> * textContainers;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLSanitizingPolicyBuilder
|
||||
|
||||
- (HTMLSanitizingPolicyBuilder *)allowElements:(NSArray<NSString *> *)elementNames
|
||||
{
|
||||
return [self allowPolicy:HTMLElementPolicy.identity onElements:elementNames];
|
||||
}
|
||||
|
||||
- (HTMLSanitizingPolicyBuilder *)disallowElements:(NSArray<NSString *> *)elementNames
|
||||
{
|
||||
return [self allowPolicy:HTMLElementPolicy.rejectAll onElements:elementNames];
|
||||
}
|
||||
|
||||
- (HTMLSanitizingPolicyBuilder *)allowPolicy:(HTMLElementPolicy *)policy onElements:(NSArray<NSString *> *)elementNames
|
||||
{
|
||||
for (NSString *name in elementNames) {
|
||||
NSMutableArray<HTMLElementPolicy *> *list = elementPolicies[name];
|
||||
if (list == nil) {
|
||||
list = [NSMutableArray new];
|
||||
}
|
||||
[list addObject:policy];
|
||||
elementPolicies[name] = list;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (HTMLSanitizingPolicyBuilder *)allowCommonInlineFormattingElements
|
||||
{
|
||||
return [self allowElements:@[@"b", @"i", @"font", @"s", @"u", @"o", @"sup", @"sub", @"ins", @"del",
|
||||
@"strong", @"strike", @"tt", @"code", @"big", @"small", @"br", @"span", @"em"]];
|
||||
}
|
||||
|
||||
- (HTMLSanitizingPolicyBuilder *)allowCommonBlockElements
|
||||
{
|
||||
return [self allowElements:@[@"p", @"div", @"h1", @"h2", @"h3", @"h4", @"h5", @"h6", @"ul", @"ol", @"li",
|
||||
@"blockquote"]];
|
||||
}
|
||||
|
||||
- (HTMLSanitizingPolicyBuilder *)allowTextInElements:(NSArray<NSString *> *)elementNames;
|
||||
{
|
||||
for (NSString *name in elementNames) {
|
||||
textContainers[name] = @YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (HTMLSanitizingPolicyBuilder *)disallowTextInElements:(NSArray<NSString *> *)elementNames
|
||||
{
|
||||
for (NSString *name in elementNames) {
|
||||
textContainers[name] = @NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
//- (HTMLSanitizingPolicyBuilder *)allowAttributes:(NSArray<NSString *> *)attributeNames
|
||||
// onElements:(NSArray<NSString *> *)elementNames
|
||||
//{
|
||||
// for (NSString *attribute in attributeNames) {
|
||||
// [self allowAttributePolicy:HTMLAttributePolicy.identity onElements:elementNames];
|
||||
// }
|
||||
// return self;
|
||||
//}
|
||||
//
|
||||
//- (HTMLSanitizingPolicyBuilder *)disallowAttributes:(NSArray<NSString *> *)attributeName
|
||||
// onElements:(NSArray<NSString *> *)elementNames;
|
||||
//
|
||||
//- (HTMLSanitizingPolicyBuilder *)allowAttributePolicy:(HTMLAttributePolicy *)policy onElements:(NSArray<NSString *> *)elementNames
|
||||
//{
|
||||
// return self;
|
||||
//}
|
||||
//
|
||||
//- (HTMLSanitizingPolicyBuilder *)disallowAttributePolicy:(HTMLAttributePolicy *)policy onElements:(NSArray<NSString *> *)elementNames
|
||||
//{
|
||||
// return self;
|
||||
//}
|
||||
|
||||
@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;
|
||||
}
|
||||
@@ -129,7 +108,11 @@
|
||||
|
||||
- (void)popElementsUntilElementPoppedWithTagName:(NSString *)tagName
|
||||
{
|
||||
while (self.currentNode && ![self.currentNode.tagName isEqualToString:tagName]) {
|
||||
while (self.currentNode) {
|
||||
if (self.currentNode.htmlNamespace == HTMLNamespaceHTML &&
|
||||
[self.currentNode.tagName isEqualToString:tagName]) {
|
||||
break;
|
||||
}
|
||||
[_stack removeLastObject];
|
||||
}
|
||||
[_stack removeLastObject];
|
||||
@@ -137,7 +120,11 @@
|
||||
|
||||
- (void)popElementsUntilAnElementPoppedWithAnyOfTagNames:(NSArray *)tagNames
|
||||
{
|
||||
while (self.currentNode && ![tagNames containsObject:self.currentNode.tagName]) {
|
||||
while (self.currentNode) {
|
||||
if (self.currentNode.htmlNamespace == HTMLNamespaceHTML &&
|
||||
[tagNames containsObject:self.currentNode.tagName]) {
|
||||
break;
|
||||
}
|
||||
[_stack removeLastObject];
|
||||
}
|
||||
[_stack removeLastObject];
|
||||
@@ -187,79 +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]) {
|
||||
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
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// HTMLText.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "HTMLCharacterData+Private.h"
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
#import "HTMLDocument+Private.h"
|
||||
|
||||
@implementation HTMLText
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithData:@""];
|
||||
}
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data
|
||||
{
|
||||
return [super initWithName:@"#text" type:HTMLNodeText data:data];
|
||||
}
|
||||
|
||||
- (void)appendString:(NSString *)string
|
||||
{
|
||||
[self appendData:string];
|
||||
}
|
||||
|
||||
NS_INLINE void CheckValidOffset(HTMLNode *node, NSUInteger offset, NSString *cmd)
|
||||
{
|
||||
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
|
||||
|
||||
- (NSString *)outerHTML
|
||||
{
|
||||
if ([self.parentElement.tagName isEqualToAny:@"style", @"script", @"xmp", @"iframe", @"noembed", @"noframes",
|
||||
@"plaintext", @"noscript", nil]) {
|
||||
return self.data;
|
||||
} else {
|
||||
NSMutableString *escaped = [self.data mutableCopy];
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p \"%@\">", self.class, self, self.data];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// HTMLTokenPolicy.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 01.06.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLTokens.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface HTMLTokenPolicy : NSObject
|
||||
|
||||
+ (instancetype)policy:(HTMLToken * _Nullable (^)(HTMLToken *))block;
|
||||
|
||||
- (nullable HTMLToken *)apply:(HTMLToken *)token;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// HTMLTokenPolicy.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 01.06.18.
|
||||
// Copyright © 2018 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLTokenPolicy.h"
|
||||
|
||||
@interface HTMLTokenPolicy()
|
||||
{
|
||||
HTMLToken * (^ _policyBlock)(HTMLToken *);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLTokenPolicy
|
||||
|
||||
+ (instancetype)policy:(HTMLToken * _Nullable (^)(HTMLToken * _Nonnull))block
|
||||
{
|
||||
return [[HTMLTokenPolicy alloc] initWithBlock:block];
|
||||
}
|
||||
|
||||
- (instancetype)initWithBlock:(HTMLToken * _Nullable (^)(HTMLToken * _Nonnull))block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_policyBlock = block;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (HTMLToken *)apply:(HTMLToken *)token
|
||||
{
|
||||
return _policyBlock(token);
|
||||
}
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user