Compare commits
373 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 963a2b9c09 | |||
| cb9886cbf7 | |||
| 717f62a5f1 | |||
| f5476e0c4c | |||
| b9099a26ed | |||
| b57e27137a | |||
| dbe9c575a1 | |||
| 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 | |||
| d980203741 | |||
| c169f0ed07 | |||
| ba02239207 | |||
| 830fa06a55 | |||
| 30c528a220 | |||
| 695ff67dd8 | |||
| f883b0d906 | |||
| be45558e86 | |||
| 46629ada01 | |||
| 700587b101 | |||
| 6c28dad930 | |||
| 572918e59b | |||
| 85edf33950 | |||
| e80188dd00 | |||
| b64cfc9c1e | |||
| 409b5502ae | |||
| 7377ec550f | |||
| 3d9e657be4 | |||
| 766c901e38 | |||
| 974e5a1615 | |||
| 518fd5eacb | |||
| f57e265cba | |||
| 4bb681eb78 | |||
| c4a5aa1bd9 | |||
| be34a57e6f | |||
| 01694a5fdf | |||
| b55ca26c7e | |||
| 846b6114fc | |||
| 1208ddf5c7 | |||
| c2bbb7d9f6 | |||
| 244dd726b4 | |||
| 032c428725 | |||
| 8d672cf0cf | |||
| 5354c7c934 | |||
| b99cb0c9d4 | |||
| 3c89df333c | |||
| ad34deb7ac | |||
| 7ee8057d38 | |||
| f2aff5f2ce | |||
| 7a1bb21b33 | |||
| fd965e014d | |||
| 9670e11fa5 | |||
| f4bd5420c0 | |||
| e773117f1f | |||
| d7476ef22d | |||
| 4fabcdf76a | |||
| bf147a6bb6 | |||
| 0a620d74b6 | |||
| 8893f28e4d | |||
| d924063b02 | |||
| f9065ab8c3 | |||
| 9815d3b39e | |||
| 1e30e4d86f | |||
| 188e5a0e56 | |||
| 991e868d14 | |||
| f3af320096 | |||
| 9d60a26622 | |||
| 936348d300 | |||
| 6410eb672a | |||
| 49e060cd3c | |||
| dac3cee3d8 | |||
| 5ee3b9b614 | |||
| e788c832c6 | |||
| a33f4e0834 | |||
| 9e4007dcba | |||
| 50b67cfa94 | |||
| 47d244289e | |||
| 6b81d6e465 | |||
| 13b8fe7808 | |||
| 94fed8afa1 | |||
| 30dc6cf343 | |||
| badf070907 | |||
| 3443321459 | |||
| 60dc6bfc43 | |||
| 60a5821a2c | |||
| 621dbe36d6 | |||
| 50ae2c5283 | |||
| 0cb374681c | |||
| c09adc2f96 | |||
| 09991a149f | |||
| f735fcdfd6 | |||
| 5a3c1063e7 | |||
| 8cb088367d | |||
| 7ec90d6470 | |||
| 002564f9ae | |||
| 4c80c520d4 | |||
| 85abf35099 | |||
| 578806ec2f | |||
| 7923efb02b | |||
| f4d42e0753 | |||
| 56f99f9168 | |||
| c8dd1d7791 | |||
| c57d3453fc | |||
| a413156514 | |||
| 203d153487 | |||
| 4ceb913b9f | |||
| 7896f1687e | |||
| 55bb8effb2 | |||
| 2e10c23f52 | |||
| 4f47a750e0 | |||
| 46beae1b2d | |||
| 2e441727bc | |||
| cc101b9f4e | |||
| 0635744a2d | |||
| b5bb0a48b6 | |||
| 95ffccf67b | |||
| 6af01a1214 | |||
| 667693939f | |||
| f05adfb56b | |||
| bed2edf22a | |||
| 2574736359 | |||
| ab7de014cf | |||
| 15e768267a | |||
| ee82cacc48 | |||
| 61d7c683fb | |||
| bd84884bf7 | |||
| f248b39d26 | |||
| 118ea137f4 | |||
| 0366f39d1a | |||
| d107c05a44 | |||
| 5ca3bf1192 | |||
| 02393ddedd | |||
| 33df9e7fb2 | |||
| 6020b2bdd9 | |||
| c44c77d63d | |||
| 9905f45e27 | |||
| c868bd1a56 | |||
| 8f38aed6c6 | |||
| 1917fb1f1c | |||
| dc93bb07f6 | |||
| a74be4dfa9 | |||
| 7cd47486ac | |||
| ad902e3138 | |||
| 6cfe3b6f83 | |||
| a2096b0e54 | |||
| 79fda9fdbe | |||
| 1a05c41f86 | |||
| bf751d94b1 | |||
| 43705dac69 | |||
| 3c7e6e1913 | |||
| af4444e9a8 | |||
| 23d0d62295 | |||
| b0668e121e | |||
| aa95bbd9f2 | |||
| 1544f71c50 | |||
| b1ddbc5f75 | |||
| 576dc4dfb2 | |||
| c4aacfae2b | |||
| a54cae8829 | |||
| 852dfad19c | |||
| a5d53b3eef | |||
| 5e5b903c52 | |||
| 40b6e41e12 | |||
| 05e224283a | |||
| 8490d4d2ca | |||
| 52b850317e | |||
| f5d970ba87 | |||
| 0c8fd754c3 | |||
| 4f03e00003 | |||
| 62b385d9b4 | |||
| a7a30a7cbf | |||
| 221b085fe5 | |||
| a2d9e65f0e | |||
| 745d0f72a7 | |||
| c401c3ca2c | |||
| 3cb5ed9a42 | |||
| acec99ffea | |||
| 7c58268dfd | |||
| c87076b470 | |||
| 81a20f0333 | |||
| 4fcaf4c810 |
+28
-6
@@ -1,8 +1,12 @@
|
||||
# OS X
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
@@ -12,13 +16,31 @@ build/
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
|
||||
## Other
|
||||
*.xccheckout
|
||||
profile
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.xcuserstate
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
Pods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
Pods/
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
Carthage/Checkouts
|
||||
Carthage/Build
|
||||
|
||||
# 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.0.1
|
||||
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: xcode8.2
|
||||
|
||||
branches:
|
||||
except:
|
||||
- gh-pages
|
||||
|
||||
install:
|
||||
- gem install xcpretty
|
||||
|
||||
env:
|
||||
global:
|
||||
- LC_CTYPE=en_US.UTF-8
|
||||
- LANG=en_US.UTF-8
|
||||
- WORKSPACE=HTMLKit.xcworkspace
|
||||
- IOS_FRAMEWORK_SCHEME=HTMLKit-iOS
|
||||
- MACOS_FRAMEWORK_SCHEME=HTMLKit-macOS
|
||||
- WATCHOS_FRAMEWORK_SCHEME="HTMLKit-watchOS"
|
||||
- TVOS_FRAMEWORK_SCHEME="HTMLKit-tvOS"
|
||||
- IOS_SDK=iphonesimulator10.2
|
||||
- MACOS_SDK=macosx10.12
|
||||
- WATCHOS_SDK=watchsimulator3.1
|
||||
- TVOS_SDK=appletvsimulator10.1
|
||||
matrix:
|
||||
- DESTINATION="arch=x86_64" SIMULATOR="" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK"
|
||||
- DESTINATION="OS=9.0,name=iPhone 6" SIMULATOR="iPhone 6 (9.0)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=9.1,name=iPhone 6 Plus" SIMULATOR="iPhone 6 Plus (9.1)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=9.2,name=iPhone 6S" SIMULATOR="iPhone 6S (9.2)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=9.3,name=iPhone 6S Plus" SIMULATOR="iPhone 6S Plus (9.3)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=10.1,name=iPhone 7 Plus" SIMULATOR="iPhone 7 Plus (10.1)" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK"
|
||||
- DESTINATION="OS=2.2,name=Apple Watch - 42mm" SIMULATOR="Apple Watch - 42mm (2.2)" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK"
|
||||
- DESTINATION="OS=3.1,name=Apple Watch Series 2 - 42mm" SIMULATOR="Apple Watch Series 2 - 42mm (3.1)" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK"
|
||||
- DESTINATION="OS=9.0,name=Apple TV 1080p" SIMULATOR="Apple TV 1080p (9.2)" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
|
||||
- DESTINATION="OS=10.0,name=Apple TV 1080p" SIMULATOR="Apple TV 1080p (10.0)" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK"
|
||||
|
||||
script:
|
||||
- set -o pipefail
|
||||
- xcodebuild -version
|
||||
- xcodebuild -showsdks
|
||||
- SIMULATOR_ID=$(xcrun instruments -s devices | grep -io "$SIMULATOR \[.*\]" | grep -o "\[.*\]" | sed "s/^\[\(.*\)\]$/\1/")
|
||||
- open -b com.apple.iphonesimulator --args -CurrentDeviceUDID $SIMULATOR_ID
|
||||
- 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 test | xcpretty -c;
|
||||
fi
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
# Change Log
|
||||
|
||||
## [2.0.1](https://github.com/iabudiab/HTMLKit/releases/tag/2.0.0)
|
||||
|
||||
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,257 @@
|
||||
// !$*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 = 0800;
|
||||
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_BOOL_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
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_BOOL_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
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;
|
||||
};
|
||||
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;
|
||||
};
|
||||
/* 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>
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
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"
|
||||
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"
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//: [Previous](@previous)
|
||||
/*:
|
||||
# CSS3 Selectors
|
||||
|
||||
HTMLKit understands CSS3 selectors making node-selection a piece of cake:
|
||||
*/
|
||||
|
||||
import HTMLKit
|
||||
|
||||
let htmlString = "<div><h1>HTMLKit</h1><p class='greeting'>Hello there!</p><p class='description'>This is a demo of HTMLKit</p></div>"
|
||||
let document = HTMLDocument(string: htmlString)
|
||||
|
||||
/*:
|
||||
All CSS3 selectors are supported and you use them the way you always have:
|
||||
*/
|
||||
var paragraphs = document.querySelectorAll("p")
|
||||
var paragraphsOrHeaders = document.querySelectorAll("p, h1")
|
||||
var hasClassAttribute = document.querySelectorAll("[class]")
|
||||
var greetings = document.querySelectorAll(".greeting")
|
||||
var classNameStartsWith_de = document.querySelectorAll("[class^='de']")
|
||||
|
||||
var hasAdjacentHeader = document.querySelectorAll("h1 + *")
|
||||
var hasSiblingHeader = document.querySelectorAll("h1 ~ *")
|
||||
var hasSiblingParagraph = document.querySelectorAll("p ~ *")
|
||||
|
||||
var nonParagraphChildOfDiv = document.querySelectorAll("div :not(p)")
|
||||
|
||||
/*:
|
||||
HTMLKit also provides API to create selector instances in a type-safe manner without the need to parse them first. The previous examples would like this:
|
||||
*/
|
||||
paragraphs = document.elements(matching: typeSelector("p"))
|
||||
paragraphsOrHeaders = document.elements(matching:
|
||||
anyOf([
|
||||
typeSelector("p"), typeSelector("h1")
|
||||
])
|
||||
)
|
||||
|
||||
hasClassAttribute = document.elements(matching: hasAttributeSelector("class"))
|
||||
greetings = document.elements(matching: classSelector("greeting"))
|
||||
classNameStartsWith_de = document.elements(matching: attributeSelector(.begins, "class", "de"))
|
||||
|
||||
hasAdjacentHeader = document.elements(matching: adjacentSiblingSelector(typeSelector("h1")))
|
||||
hasAdjacentHeader = document.elements(matching: generalSiblingSelector(typeSelector("h1")))
|
||||
hasAdjacentHeader = document.elements(matching: generalSiblingSelector(typeSelector("p")))
|
||||
|
||||
nonParagraphChildOfDiv = document.elements(matching:
|
||||
allOf([
|
||||
childOfElementSelector(typeSelector("div")),
|
||||
not(typeSelector("p"))
|
||||
])
|
||||
)
|
||||
|
||||
/*:
|
||||
Here are more examples
|
||||
*/
|
||||
|
||||
let firstDivElement = document.firstElement(matching: typeSelector("div"))!
|
||||
|
||||
var secondChildOfDiv = firstDivElement.querySelectorAll(":nth-child(2)")
|
||||
var secondOfType = firstDivElement.querySelectorAll(":nth-of-type(2n)")
|
||||
|
||||
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.elements(matching:
|
||||
allOf([
|
||||
not(typeSelector("p")),
|
||||
not(typeSelector("div"))
|
||||
])
|
||||
)
|
||||
|
||||
/*:
|
||||
One more thing! You can also create your own selectors. You either subclass the CSSSelector or just use the block-based wrapper. For example the previous selector can be implemented like this:
|
||||
*/
|
||||
let myAwesomeSelector = namedBlockSelector("myAwesomeSelector", { (element) -> Bool in
|
||||
return element.tagName != "p" && element.tagName != "div"
|
||||
})
|
||||
firstDivElement.elements(matching: myAwesomeSelector)
|
||||
@@ -0,0 +1,30 @@
|
||||
/*:
|
||||
# HTMLKit
|
||||
|
||||

|
||||
|
||||
****
|
||||
|
||||
An Objective-C kit for your everyday HTML needs.
|
||||
|
||||
****
|
||||
|
||||
HTMLKit is a [WHATWG](https://html.spec.whatwg.org/multipage/) specification-compliant framework for parsing and serializing HTML documents and document fragments for iOS and OSX. HTMLKit parses real-world HTML the same way modern web browsers would.
|
||||
|
||||
****
|
||||
|
||||
HTMLKit is available under the MIT License
|
||||
|
||||
****
|
||||
|
||||
# Table of Contents
|
||||
|
||||
- [Parsing Document](Parsing%20Documents)
|
||||
- [Parsing Fragments](Parsing%20Fragments)
|
||||
- [The DOM](The%20DOM)
|
||||
- [CSS Selectors](CSS%20Selectors)
|
||||
|
||||
****
|
||||
|
||||
[Next](@next)
|
||||
*/
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=572&EndingColumnNumber=5&EndingLineNumber=27&StartingColumnNumber=1&StartingLineNumber=26&Timestamp=472575682.16404"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=572&EndingColumnNumber=9&EndingLineNumber=27&StartingColumnNumber=5&StartingLineNumber=26&Timestamp=472575682.164357"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=572&EndingColumnNumber=9&EndingLineNumber=27&StartingColumnNumber=5&StartingLineNumber=26&Timestamp=472575682.16458"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=572&EndingColumnNumber=9&EndingLineNumber=1&StartingColumnNumber=5&StartingLineNumber=1&Timestamp=472575682.164803"
|
||||
lockedSize = "{309, 236}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -0,0 +1,30 @@
|
||||
//: [Previous](@previous)
|
||||
/*:
|
||||
# Parsing HTML Documents
|
||||
*/
|
||||
|
||||
import HTMLKit
|
||||
|
||||
/*:
|
||||
Given some HTML content
|
||||
*/
|
||||
|
||||
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:
|
||||
*/
|
||||
|
||||
let parser = HTMLParser(string: htmlString)
|
||||
let documentViaParser = parser.parseDocument()
|
||||
documentViaParser.innerHTML
|
||||
|
||||
/*:
|
||||
You can also create a document from a given HTML string directly:
|
||||
*/
|
||||
|
||||
let document = HTMLDocument(string: htmlString)
|
||||
document.innerHTML
|
||||
|
||||
//: [Next](@next)
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
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>
|
||||
@@ -0,0 +1,38 @@
|
||||
//: [Previous](@previous)
|
||||
/*:
|
||||
# Parsing Document Fragments
|
||||
*/
|
||||
|
||||
import HTMLKit
|
||||
|
||||
/*:
|
||||
Given some HTML content
|
||||
*/
|
||||
|
||||
let htmlString = "<div><p>Hello HTMLKit</p></div><td>some table data"
|
||||
|
||||
/*:
|
||||
You can prase it as a document fragment in a specified context element:
|
||||
*/
|
||||
|
||||
let parser = HTMLParser(string: htmlString)
|
||||
|
||||
let tableContext = HTMLElement(tagName: "table")
|
||||
var elements = parser.parseFragment(withContextElement: tableContext)
|
||||
|
||||
for element in elements {
|
||||
print(element.outerHTML)
|
||||
}
|
||||
|
||||
/*:
|
||||
The same parser instance can be reusued:
|
||||
*/
|
||||
|
||||
let bodyContext = HTMLElement(tagName: "body")
|
||||
elements = parser.parseFragment(withContextElement: bodyContext)
|
||||
|
||||
for element in elements {
|
||||
print(element.outerHTML)
|
||||
}
|
||||
|
||||
//: [Next](@next)
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=356&EndingColumnNumber=16&EndingLineNumber=23&StartingColumnNumber=2&StartingLineNumber=23&Timestamp=472578641.006172"
|
||||
lockedSize = "{775, 114}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=0&CharacterRangeLoc=356&EndingColumnNumber=23&EndingLineNumber=23&StartingColumnNumber=2&StartingLineNumber=23&Timestamp=472578641.006502"
|
||||
lockedSize = "{775, 80}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=21&CharacterRangeLoc=262&EndingColumnNumber=23&EndingLineNumber=13&StartingColumnNumber=2&StartingLineNumber=13&Timestamp=472578641.006717"
|
||||
lockedSize = "{775, 76}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=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=672&EndingColumnNumber=26&EndingLineNumber=34&StartingColumnNumber=2&StartingLineNumber=34&Timestamp=495493308.359844"
|
||||
lockedSize = "{775, 76}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=281&EndingColumnNumber=26&EndingLineNumber=13&StartingColumnNumber=2&StartingLineNumber=13&Timestamp=472579861.71569"
|
||||
lockedSize = "{775, 68}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=24&CharacterRangeLoc=450&EndingColumnNumber=26&EndingLineNumber=21&StartingColumnNumber=2&StartingLineNumber=21&Timestamp=495492881.372527"
|
||||
lockedSize = "{775, 68}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "YES">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -0,0 +1,81 @@
|
||||
//: [Previous](@previous)
|
||||
/*:
|
||||
# The DOM
|
||||
|
||||
HTMLKit provides a rich DOM implementation for manipulating and navigating the document tree. Here are some of the features:
|
||||
*/
|
||||
|
||||
import HTMLKit
|
||||
|
||||
let htmlString = "<div><h1>HTMLKit</h1><p>Hello there!</p></div>"
|
||||
let document = HTMLDocument(string: htmlString)
|
||||
|
||||
/*:
|
||||
Create new elements and assign attributes
|
||||
*/
|
||||
|
||||
let description = HTMLElement(tagName:"meta", attributes: ["name": "description"])
|
||||
description["content"] = "HTMLKit for iOS & OSX"
|
||||
|
||||
/*:
|
||||
Append nodes to the document
|
||||
*/
|
||||
let head = document.head!
|
||||
head.append(description)
|
||||
document.innerHTML
|
||||
|
||||
let body = document.body!
|
||||
let nodes = [
|
||||
HTMLElement(tagName: "div", attributes: ["class": "red"]),
|
||||
HTMLElement(tagName: "div", attributes: ["class": "green"]),
|
||||
HTMLElement(tagName: "div", attributes: ["class": "blue"])
|
||||
]
|
||||
body.append(nodes)
|
||||
body.innerHTML
|
||||
|
||||
/*:
|
||||
Enumerate child elements and perform DOM manipulation
|
||||
*/
|
||||
body.enumerateChildElements { (element, index, stop) -> Void in
|
||||
if element.tagName == "div" {
|
||||
let lorem = HTMLElement(tagName: "p")
|
||||
lorem.textContent = "Lorem ipsum: \(index)"
|
||||
element.append(lorem)
|
||||
}
|
||||
}
|
||||
body.innerHTML
|
||||
|
||||
/*:
|
||||
Remove nodes from the document
|
||||
*/
|
||||
body.removeChildNode(at: 1)
|
||||
body.innerHTML
|
||||
|
||||
/*:
|
||||
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.filter { (node) -> HTMLNodeFilterValue in
|
||||
if node.childNodesCount() != 1 {
|
||||
return .reject
|
||||
}
|
||||
return .accept
|
||||
}
|
||||
|
||||
for element in body.nodeIterator(showOptions: .element, filter: filter) {
|
||||
(element as! AnyObject).outerHTML
|
||||
}
|
||||
|
||||
//: [Next](@next)
|
||||
@@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
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=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=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=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=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=1774&EndingColumnNumber=16&EndingLineNumber=77&StartingColumnNumber=2&StartingLineNumber=77&Timestamp=496085091.151491"
|
||||
lockedSize = "{763, 102}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
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=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=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=1122&EndingColumnNumber=15&EndingLineNumber=45&StartingColumnNumber=1&StartingLineNumber=45&Timestamp=495493427.982381"
|
||||
lockedSize = "{760, 97}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=18&CharacterRangeLoc=397&EndingColumnNumber=19&EndingLineNumber=14&StartingColumnNumber=1&StartingLineNumber=14&Timestamp=472578662.199111"
|
||||
lockedSize = "{762, 90}"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "#CharacterRangeLen=14&CharacterRangeLoc=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=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=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=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=1601&EndingColumnNumber=19&EndingLineNumber=70&StartingColumnNumber=2&StartingLineNumber=70&Timestamp=496085091.153628"
|
||||
lockedSize = "{763, 92}"
|
||||
selectedRepresentationIndex = "1"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
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=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=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=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=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=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>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='6.0' target-platform='macos' display-mode='rendered' last-migration='0800'>
|
||||
<pages>
|
||||
<page name='Intro'/>
|
||||
<page name='Parsing Documents'/>
|
||||
<page name='Parsing Fragments'/>
|
||||
<page name='The DOM'/>
|
||||
<page name='CSS Selectors'/>
|
||||
</pages>
|
||||
</playground>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
@@ -0,0 +1,23 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "HTMLKit"
|
||||
s.version = "2.0.1"
|
||||
s.summary = "HTMLKit, an Objective-C framework for your everyday HTML needs."
|
||||
s.license = "MIT"
|
||||
s.homepage = "https://github.com/iabudiab/HTMLKit"
|
||||
s.author = "iabudiab"
|
||||
s.social_media_url = "https://twitter.com/_iabudiab"
|
||||
|
||||
s.ios.deployment_target = "8.0"
|
||||
s.osx.deployment_target = "10.9"
|
||||
s.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
|
||||
+1871
-462
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF551C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKitTests-iOS.xctest"
|
||||
BlueprintName = "HTMLKitTests-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance/testParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance/testTokenizerPerformance">
|
||||
</Test>
|
||||
</SkippedTests>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "62ECBF4C1C0B6C7600AF847B"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-iOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14C219C7829400AD0C32"
|
||||
BuildableName = "HTMLKitTests-macOS.xctest"
|
||||
BlueprintName = "HTMLKitTests-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
<SkippedTests>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitParserPerformance/testParserPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance">
|
||||
</Test>
|
||||
<Test
|
||||
Identifier = "HTMLKitTokenizerPerformance/testTokenizerPerformance">
|
||||
</Test>
|
||||
</SkippedTests>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "625A14AB19C7829400AD0C32"
|
||||
BuildableName = "HTMLKit.framework"
|
||||
BlueprintName = "HTMLKit-macOS"
|
||||
ReferencedContainer = "container:HTMLKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
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"
|
||||
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"
|
||||
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,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
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"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "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>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:HTMLKit.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:HTMLKit.playground">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Example/HTMLKitExample/HTMLKitExample.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// HTMLCharacterToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLCharacterToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *characters;
|
||||
|
||||
- (instancetype)initWithString:(NSString *)string;
|
||||
|
||||
- (void)appendString:(NSString *)string;
|
||||
- (BOOL)isWhitespaceToken;
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
- (void)retainLeadingWhitespace;
|
||||
- (void)trimLeadingWhitespace;
|
||||
- (void)trimFormIndex:(NSUInteger)index;
|
||||
- (HTMLCharacterToken *)tokenBySplitingLeadingWhiteSpace;
|
||||
|
||||
@end
|
||||
@@ -1,17 +0,0 @@
|
||||
//
|
||||
// HTMLComment.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLComment : HTMLNode
|
||||
|
||||
@property (nonatomic, copy) NSString *data;
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// HTMLCommentToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLCommentToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *data;
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
- (void)appendStringToData:(NSString *)string;
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// HTMLDOCTYPEToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLDOCTYPEToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
@property (nonatomic, strong) NSMutableString *publicIdentifier;
|
||||
@property (nonatomic, strong) NSMutableString *systemIdentifier;
|
||||
@property (nonatomic, assign) BOOL forceQuirks;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name;
|
||||
|
||||
- (void)appendStringToName:(NSString *)string;
|
||||
- (void)appendStringToPublicIdentifier:(NSString *)string;
|
||||
- (void)appendStringToSystemIdentifier:(NSString *)string;
|
||||
|
||||
@end
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// HTMLDocument.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLQuirksMode.h"
|
||||
|
||||
typedef NS_ENUM(short, HTMLDocumentReadyState)
|
||||
{
|
||||
HTMLDocumentLoading,
|
||||
HTMLDocumentInteractive, // Not used
|
||||
HTMLDocumentComplete
|
||||
};
|
||||
|
||||
@interface HTMLDocument : HTMLNode
|
||||
|
||||
@property (nonatomic, strong) HTMLDocumentType *documentType;
|
||||
|
||||
@property (nonatomic, assign) HTMLQuirksMode quirksMode;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *compatMode;
|
||||
|
||||
@property (nonatomic, assign, readonly) HTMLDocumentReadyState readyState;
|
||||
|
||||
@property (nonatomic, strong) HTMLElement *rootElement;
|
||||
|
||||
@property (nonatomic, strong) HTMLElement *documentElement;
|
||||
|
||||
@property (nonatomic, strong) HTMLElement *head;
|
||||
|
||||
@property (nonatomic, strong) HTMLElement *body;
|
||||
|
||||
+ (instancetype)documentWithString:(NSString *)string;
|
||||
|
||||
- (HTMLNode *)adoptNode:(HTMLNode *)node;
|
||||
|
||||
- (HTMLDocument *)associatedInertTemplateDocument;
|
||||
|
||||
@end
|
||||
@@ -1,15 +0,0 @@
|
||||
//
|
||||
// HTMLDocumentFragment.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 12/04/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLDocumentFragment : HTMLNode
|
||||
|
||||
- (instancetype)initWithDocument:(HTMLDocument *)document;
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// HTMLDocumentType.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLQuirksMode.h"
|
||||
|
||||
@interface HTMLDocumentType : HTMLNode
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *publicIdentifier;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *systemIdentifier;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
publicIdentifier:(NSString *)publicIdentifier
|
||||
systemIdentifier:(NSString *)systemIdentifier;
|
||||
|
||||
- (BOOL)isValid;
|
||||
- (HTMLQuirksMode)quirksMode;
|
||||
|
||||
@end
|
||||
@@ -1,15 +0,0 @@
|
||||
//
|
||||
// HTMLEOFToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLEOFToken : HTMLToken
|
||||
|
||||
+ (instancetype)token;
|
||||
|
||||
@end
|
||||
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// HTMLElement.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 05/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNamespaces.h"
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLElement : HTMLNode
|
||||
|
||||
@property (nonatomic, assign, readonly) HTMLNamespace htmlNamespace;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *tagName;
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary *attributes;
|
||||
|
||||
@property (nonatomic, copy) NSString *elementId;
|
||||
|
||||
@property (nonatomic, copy) NSString *className;
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName;
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes;
|
||||
- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(NSDictionary *)attributes;
|
||||
|
||||
- (BOOL)hasAttribute:(NSString *)name;
|
||||
- (NSString *)objectForKeyedSubscript:(NSString *)name;
|
||||
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute;
|
||||
- (void)removeAttribute:(NSString *)name;
|
||||
|
||||
@end
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// HTMLInputStreamReader.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef void (^ HTMLStreamReaderErrorCallback)(NSString *reason);
|
||||
|
||||
/**
|
||||
* HTML Input Stream Reader processor conforming to the HTML standard
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#preprocessing-the-input-stream
|
||||
*/
|
||||
@interface HTMLInputStreamReader : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSString *string;
|
||||
@property (nonatomic, readonly) NSUInteger currentLocation;
|
||||
@property (nonatomic, copy) HTMLStreamReaderErrorCallback errorCallback;
|
||||
|
||||
- (id)initWithString:(NSString *)string;
|
||||
|
||||
- (UTF32Char)currentInputCharacter;
|
||||
- (UTF32Char)nextInputCharacter;
|
||||
|
||||
- (UTF32Char)consumeNextInputCharacter;
|
||||
- (void)reconsumeCurrentInputCharacter;
|
||||
- (void)unconsumeCurrentInputCharacter;
|
||||
|
||||
- (BOOL)consumeCharacter:(UTF32Char)character;
|
||||
- (BOOL)consumeNumber:(unsigned long long *)result;
|
||||
- (BOOL)consumeHexNumber:(unsigned long long *)result;
|
||||
- (BOOL)consumeString:(NSString *)string caseSensitive:(BOOL)caseSensitive;
|
||||
- (NSString *)consumeCharactersUpToCharactersInString:(NSString *)characters;
|
||||
- (NSString *)consumeCharactersUpToString:(NSString *)string;
|
||||
- (NSString *)consumeAlphanumericCharacters;
|
||||
|
||||
- (void)markCurrentLocation;
|
||||
- (void)rewindToMarkedLocation;
|
||||
- (void)reset;
|
||||
|
||||
@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,13 +0,0 @@
|
||||
//
|
||||
// HTMLKit.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLKit : NSObject
|
||||
|
||||
@end
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// HTMLKit.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLKit.h"
|
||||
|
||||
@implementation HTMLKit
|
||||
|
||||
@end
|
||||
@@ -1,11 +0,0 @@
|
||||
//
|
||||
// HTMLKitExceptions.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 17/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
FOUNDATION_EXPORT NSString * const HTMLKitHierarchyRequestError;
|
||||
FOUNDATION_EXPORT NSString * const HTMLKitNotFoundError;
|
||||
FOUNDATION_EXPORT NSString * const HTMLKitNotSupportedError;
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// HTMLKitExceptions.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 17/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
|
||||
NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError";
|
||||
NSString * const HTMLKitNotFoundError = @"NotFoundError";
|
||||
NSString * const HTMLKitNotSupportedError = @"NotSupportedError";
|
||||
@@ -1,38 +0,0 @@
|
||||
//
|
||||
// HTMLListOfActiveFormattingElements.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 22/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
@interface HTMLListOfActiveFormattingElements : NSObject
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||
- (NSUInteger)indexOfElement:(id)node;
|
||||
|
||||
- (void)addElement:(HTMLElement *)element;
|
||||
- (void)removeElement:(id)element;
|
||||
- (BOOL)containsElement:(id)element;
|
||||
|
||||
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
|
||||
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
|
||||
|
||||
- (id)lastEntry;
|
||||
|
||||
- (void)addMarker;
|
||||
- (void)clearUptoLastMarker;
|
||||
|
||||
- (HTMLElement *)formattingElementWithTagName:(NSString *)tagName;
|
||||
|
||||
- (NSUInteger)count;
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
- (NSEnumerator *)enumerator;
|
||||
- (NSEnumerator *)reverseObjectEnumerator;
|
||||
|
||||
@end
|
||||
@@ -1,15 +0,0 @@
|
||||
//
|
||||
// HTMLMarker.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 02/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLMarker : NSObject
|
||||
|
||||
+ (instancetype)marker;
|
||||
|
||||
@end
|
||||
@@ -1,17 +0,0 @@
|
||||
//
|
||||
// HTMLNamespaces.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 03/11/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
typedef NS_ENUM(NSInteger, HTMLNamespace)
|
||||
{
|
||||
HTMLNamespaceHTML,
|
||||
HTMLNamespaceMathML,
|
||||
HTMLNamespaceSVG,
|
||||
HTMLNamespaceXLink,
|
||||
HTMLNamespaceXML,
|
||||
HTMLNamespaceXMLNS,
|
||||
};
|
||||
@@ -1,126 +0,0 @@
|
||||
//
|
||||
// HTMLNode.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 24/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNodeIterator.h"
|
||||
|
||||
typedef NS_ENUM(short, HTMLNodeType)
|
||||
{
|
||||
HTMLNodeElement = 1,
|
||||
HTMLNodeAttribute = 2, // historical
|
||||
HTMLNodeText = 3,
|
||||
HTMLNodeCDATASection = 4, // historical
|
||||
HTMLNodeEntityReference = 5, // historical
|
||||
HTMLNodeEntity = 6, // historical
|
||||
HTMLNodeProcessingInstruction = 7,
|
||||
HTMLNodeComment = 8,
|
||||
HTMLNodeDocument = 9,
|
||||
HTMLNodeDocumentType = 10,
|
||||
HTMLNodeDocumentFragment = 11,
|
||||
HTMLNodeNotation = 12 // historical
|
||||
};
|
||||
|
||||
typedef NS_ENUM(unsigned short, HTMLDocumentPosition)
|
||||
{
|
||||
HTMLDocumentPositionEquivalent = 0x0,
|
||||
HTMLDocumentPositionDisconnected = 0x01,
|
||||
HTMLDocumentPositionPreceding = 0x02,
|
||||
HTMLDocumentPositionFollowing = 0x04,
|
||||
HTMLDocumentPositionContains = 0x08,
|
||||
HTMLDocumentPositionContainedBy = 0x10,
|
||||
HTMLDocumentPositionImplementationSpecific = 0x20
|
||||
};
|
||||
|
||||
@class HTMLDocument;
|
||||
@class HTMLElement;
|
||||
|
||||
@interface HTMLNode : NSObject <NSCopying>
|
||||
|
||||
@property (nonatomic, assign, readonly) HTMLNodeType nodeType;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString *name;
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLDocument *ownerDocument;
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLNode *parentNode;
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLElement *parentElement;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSOrderedSet *childNodes;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *firstChild;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *lastChild;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *previousSibling;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *nextSibling;
|
||||
|
||||
@property (nonatomic, copy) NSString *textContent;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString *outerHTML;
|
||||
|
||||
@property (nonatomic, copy) NSString *innerHTML;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type;
|
||||
|
||||
- (HTMLElement *)asElement;
|
||||
|
||||
- (BOOL)hasChildNodes;
|
||||
|
||||
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type;
|
||||
|
||||
- (NSUInteger)childNodesCount;
|
||||
|
||||
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index;
|
||||
|
||||
- (NSUInteger)indexOfChildNode:(HTMLNode *)node;
|
||||
|
||||
- (HTMLNode *)prependNode:(HTMLNode *)node;
|
||||
|
||||
- (void)prependNodes:(NSArray *)nodes;
|
||||
|
||||
- (HTMLNode *)appendNode:(HTMLNode *)node;
|
||||
|
||||
- (void)appendNodes:(NSArray *)nodes;
|
||||
|
||||
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child;
|
||||
|
||||
- (HTMLNode *)replaceChildNode:(HTMLNode *)node withNode:(HTMLNode *)replacement;
|
||||
|
||||
- (void)replaceAllChildNodesWithNode:(HTMLNode *)node;
|
||||
|
||||
- (void)removeFromParentNode;
|
||||
|
||||
- (HTMLNode *)removeChildNode:(HTMLNode *)node;
|
||||
|
||||
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index;
|
||||
|
||||
- (void)reparentChildNodesIntoNode:(HTMLNode *)node;
|
||||
|
||||
- (void)removeAllChildNodes;
|
||||
|
||||
- (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)node;
|
||||
- (BOOL)isDescendantOfNode:(HTMLNode *)node;
|
||||
- (BOOL)containsNode:(HTMLNode *)node;
|
||||
|
||||
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block;
|
||||
|
||||
- (void)enumerateChildElementsUsingBlock:(void (^)(HTMLElement *element, NSUInteger idx, BOOL *stop))block;
|
||||
|
||||
- (HTMLNodeIterator *)nodeIterator;
|
||||
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filter:(id<HTMLNodeFilter>)filter;
|
||||
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter;
|
||||
|
||||
- (NSString *)treeDescription;
|
||||
|
||||
@end
|
||||
@@ -1,41 +0,0 @@
|
||||
//
|
||||
// HTMLNodeFilter.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 27/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(unsigned short, HTMLNodeFilterValue)
|
||||
{
|
||||
HTMLNodeFilterAccept = 1,
|
||||
HTMLNodeFilterReject = 2,
|
||||
HTMLNodeFilterSkip = 3
|
||||
};
|
||||
|
||||
typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions)
|
||||
{
|
||||
HTMLNodeFilterShowAll = 0xFFFFFFFF,
|
||||
HTMLNodeFilterShowElement = 0x1,
|
||||
HTMLNodeFilterShowText = 0x4,
|
||||
HTMLNodeFilterShowComment = 0x80,
|
||||
HTMLNodeFilterShowDocument = 0x100,
|
||||
HTMLNodeFilterShowDocumentType = 0x200,
|
||||
HTMLNodeFilterShowDocumentFragment = 0x400
|
||||
};
|
||||
|
||||
@class HTMLNode;
|
||||
|
||||
@protocol HTMLNodeFilter <NSObject>
|
||||
|
||||
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node;
|
||||
|
||||
@end
|
||||
|
||||
@interface HTMLNodeFilterBlock : NSObject <HTMLNodeFilter>
|
||||
|
||||
+ (instancetype)filterWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block;
|
||||
|
||||
@end
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// HTMLNodeFilter.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 05/06/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNodeFilter.h"
|
||||
|
||||
@interface HTMLNodeFilterBlock ()
|
||||
{
|
||||
BOOL (^ _block)(HTMLNode *);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLNodeFilterBlock
|
||||
|
||||
+ (instancetype)filterWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *))block
|
||||
{
|
||||
return [[self alloc] initWithBlock:block];
|
||||
}
|
||||
|
||||
- (instancetype)initWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *))block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_block = [block copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node
|
||||
{
|
||||
if (!_block) {
|
||||
return HTMLNodeFilterSkip;
|
||||
}
|
||||
|
||||
return _block(node);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// HTMLNodeIterator.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 27/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNodeFilter.h"
|
||||
|
||||
@class HTMLNode;
|
||||
|
||||
@interface HTMLNodeIterator : NSEnumerator
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *root;
|
||||
@property (nonatomic, strong, readonly) HTMLNode *referenceNode;
|
||||
@property (nonatomic, assign, readonly) BOOL pointerBeforeReferenceNode;
|
||||
@property (nonatomic, assign, readonly) HTMLNodeFilterShowOptions whatToShow;
|
||||
@property (nonatomic, strong, readonly) id<HTMLNodeFilter> filter;
|
||||
|
||||
+ (instancetype)iteratorWithNode:(HTMLNode *)node
|
||||
showOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filter:(HTMLNodeFilterValue (^)(HTMLNode *node))filter;
|
||||
|
||||
- (instancetype)initWithNode:(HTMLNode *)node;
|
||||
- (instancetype)initWithNode:(HTMLNode *)node
|
||||
filter:(id<HTMLNodeFilter>)filter;
|
||||
- (instancetype)initWithNode:(HTMLNode *)node
|
||||
showOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filter:(id<HTMLNodeFilter>)filter;
|
||||
|
||||
- (HTMLNode *)nextNode;
|
||||
- (HTMLNode *)previousNode;
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// HTMLOrderedDictionary.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 14/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLOrderedDictionary : NSMutableDictionary
|
||||
|
||||
- (id)objectAtIndex:(NSUInteger)index;
|
||||
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey atIndex:(NSUInteger)index;
|
||||
- (void)removeObjectAtIndex:(NSUInteger)index;
|
||||
- (void)replaceKeyValueAtIndex:(NSUInteger)index withObject:(id)anObject andKey:(id<NSCopying>)aKey;
|
||||
- (void)replaceKey:(id<NSCopying>)aKey withKey:(id<NSCopying>)newKey;
|
||||
- (NSUInteger)indexOfKey:(id<NSCopying>)aKey;
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index;
|
||||
|
||||
- (NSEnumerator *)reverseKeyEnumerator;
|
||||
|
||||
@end
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// HTMLParseErrorToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLParseErrorToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *reason;
|
||||
@property (nonatomic, assign) NSUInteger location;
|
||||
|
||||
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location;
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// HTMLParser.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 04/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
@interface HTMLParser : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSArray *parseErrors;
|
||||
@property (nonatomic, strong, readonly) HTMLDocument *document;
|
||||
|
||||
- (instancetype)initWithString:(NSString *)string;
|
||||
|
||||
- (HTMLDocument *)parseDocument;
|
||||
- (NSArray *)parseFragmentWithContextElement:(HTMLElement *)contextElement;
|
||||
|
||||
@end
|
||||
@@ -1,55 +0,0 @@
|
||||
//
|
||||
// HTMLStackOfOpenElements.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 08/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
@interface HTMLStackOfOpenElements : NSObject <NSFastEnumeration>
|
||||
|
||||
- (HTMLElement *)currentNode;
|
||||
- (HTMLElement *)firstNode;
|
||||
- (HTMLElement *)lastNode;
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||
- (NSUInteger)indexOfElement:(id)node;
|
||||
|
||||
- (void)pushElement:(HTMLElement *)element;
|
||||
- (void)removeElement:(id)element;
|
||||
- (BOOL)containsElement:(id)element;
|
||||
- (BOOL)containsElementWithTagName:(NSString *)tagName;
|
||||
|
||||
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
|
||||
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
|
||||
|
||||
- (void)popCurrentNode;
|
||||
- (void)popElementsUntilElementPoppedWithTagName:(NSString *)tagName;
|
||||
- (void)popElementsUntilAnElementPoppedWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (void)popElementsUntilElementPopped:(HTMLElement *)element;
|
||||
- (void)popElementsUntilTemplateElementPopped;
|
||||
- (void)clearBackToTableContext;
|
||||
- (void)clearBackToTableBodyContext;
|
||||
- (void)clearBackToTableRowContext;
|
||||
- (void)popAll;
|
||||
|
||||
- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)furthestBlockAfterIndex:(NSUInteger)index;
|
||||
|
||||
- (NSUInteger)count;
|
||||
- (BOOL)isEmpy;
|
||||
|
||||
- (NSEnumerator *)enumerator;
|
||||
- (NSEnumerator *)reverseObjectEnumerator;
|
||||
|
||||
@end
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// HTMLText.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLText : HTMLNode
|
||||
|
||||
@property (nonatomic, copy) NSMutableString *data;
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
- (void)appendString:(NSString *)string;
|
||||
|
||||
@end
|
||||
@@ -1,78 +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"
|
||||
|
||||
@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,54 +0,0 @@
|
||||
//
|
||||
// HTMLToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class HTMLDOCTYPEToken;
|
||||
@class HTMLTagToken;
|
||||
@class HTMLStartTagToken;
|
||||
@class HTMLEndTagToken;
|
||||
@class HTMLCommentToken;
|
||||
@class HTMLCharacterToken;
|
||||
@class HTMLParseErrorToken;
|
||||
|
||||
NS_INLINE BOOL bothNilOrEqual(id first, id second) {
|
||||
return (first == nil && second == nil) || ([first isEqual:second]);
|
||||
}
|
||||
|
||||
typedef NS_ENUM(NSUInteger, HTMLTokenType)
|
||||
{
|
||||
HTMLTokenTypeCharacter,
|
||||
HTMLTokenTypeComment,
|
||||
HTMLTokenTypeDoctype,
|
||||
HTMLTokenTypeEndTag,
|
||||
HTMLTokenTypeEOF,
|
||||
HTMLTokenTypeParseError,
|
||||
HTMLTokenTypeStartTag
|
||||
};
|
||||
|
||||
@interface HTMLToken : NSObject
|
||||
|
||||
@property (nonatomic, assign) HTMLTokenType type;
|
||||
|
||||
- (BOOL)isDoctypeToken;
|
||||
- (BOOL)isStartTagToken;
|
||||
- (BOOL)isEndTagToken;
|
||||
- (BOOL)isCommentToken;
|
||||
- (BOOL)isCharacterToken;
|
||||
- (BOOL)isEOFToken;
|
||||
- (BOOL)isParseError;
|
||||
|
||||
- (HTMLDOCTYPEToken *)asDoctypeToken;
|
||||
- (HTMLTagToken *)asTagToken;
|
||||
- (HTMLStartTagToken *)asStartTagToken;
|
||||
- (HTMLEndTagToken *)asEndTagToken;
|
||||
- (HTMLCommentToken *)asCommentToken;
|
||||
- (HTMLCharacterToken *)asCharacterToken;
|
||||
- (HTMLParseErrorToken *)asParseError;
|
||||
|
||||
@end
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// HTMLTokenizerEntities.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 11/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLTokenizerEntities : NSObject
|
||||
|
||||
+ (NSArray *)entities;
|
||||
+ (NSString *)replacementAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// HTMLTreeWalker.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 05/06/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNodeFilter.h"
|
||||
|
||||
@class HTMLNode;
|
||||
|
||||
@interface HTMLTreeWalker : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *root;
|
||||
@property (nonatomic, assign, readonly) HTMLNodeFilterShowOptions whatToShow;
|
||||
@property (nonatomic, strong, readonly) id<HTMLNodeFilter> filter;
|
||||
@property (nonatomic, strong) HTMLNode *currentNode;
|
||||
|
||||
- (instancetype)initWithNode:(HTMLNode *)node;
|
||||
- (instancetype)initWithNode:(HTMLNode *)node
|
||||
filter:(id<HTMLNodeFilter>)filter;
|
||||
- (instancetype)initWithNode:(HTMLNode *)node
|
||||
showOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filter:(id<HTMLNodeFilter>)filter;
|
||||
|
||||
- (HTMLNode *)parentNode;
|
||||
- (HTMLNode *)firstChild;
|
||||
- (HTMLNode *)lastChild;
|
||||
- (HTMLNode *)previousSibling;
|
||||
- (HTMLNode *)nextSibling;
|
||||
- (HTMLNode *)previousNode;
|
||||
- (HTMLNode *)nextNode;
|
||||
|
||||
@end
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// NSString+HTMLKit.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 02/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSString (HTMLKit)
|
||||
|
||||
- (BOOL)isEqualToStringIgnoringCase:(NSString *)aString;
|
||||
- (BOOL)isEqualToAny:(NSString *)first, ... NS_REQUIRES_NIL_TERMINATION;
|
||||
- (BOOL)hasPrefixIgnoringCase:(NSString *)aString;
|
||||
- (BOOL)isHTMLWhitespaceString;
|
||||
- (NSUInteger)leadingHTMLWhitespaceLength;
|
||||
|
||||
@end
|
||||
@@ -1,2 +0,0 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
Submodule HTMLKitTests/html5lib-tests deleted from e633ddfeb0
@@ -0,0 +1,5 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "HTMLKit"
|
||||
)
|
||||
@@ -1,11 +1,326 @@
|
||||
# HTMLKit
|
||||
|
||||
An Objective-C kit for your everyday HTML needs.
|
||||

|
||||
|
||||
An Objective-C framework for your everyday HTML needs.
|
||||
|
||||
[](https://travis-ci.org/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](https://html.spec.whatwg.org/multipage/) specification-compliant library for parsing and serializing HTML documents and document fragments for OSX and iOS. HTMLKit parses real-world HTML the same way modern web browsers would.
|
||||
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.
|
||||
|
||||
HTMLKit provides a rich DOM implementation for manipulating and navigating the document tree. It also understands [CSS3 selectors](http://www.w3.org/TR/css3-selectors/) making node-selection and querying the DOM a piece of cake.
|
||||
|
||||
## DOM Validation
|
||||
|
||||
DOM mutations are validated as described in the [WHATWG DOM Standard](https://dom.spec.whatwg.org). Invalid DOM manipulations throw hierarchy-related exceptions. You can disable these validations, which will also increase the performance by about 20-30%, by defining the `HTMLKIT_NO_DOM_CHECKS` compiler constant.
|
||||
|
||||
## Tests
|
||||
|
||||
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 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!
|
||||
|
||||
# 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
|
||||
use_frameworks!
|
||||
|
||||
target 'MyTarget' do
|
||||
pod 'HTMLKit', '~> 2.0'
|
||||
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:
|
||||
|
||||
```objective-c
|
||||
NSString *htmlString = @"<div><h1>HTMLKit</h1><p>Hello there!</p></div>";
|
||||
|
||||
// Via parser
|
||||
HTMLParser *parser = [[HTMLParser alloc] initWithString:htmlString];
|
||||
HTMLDocument *document = [parser parseDocument];
|
||||
|
||||
// Via static initializer
|
||||
HTMLDocument *document = [HTMLDocument documentWithString:htmlString];
|
||||
```
|
||||
|
||||
## Parsing Fragments
|
||||
|
||||
You can also prase HTML content as a document fragment with a specified context element:
|
||||
|
||||
```objective-c
|
||||
NSString *htmlString = @"<div><h1>HTMLKit</h1><p>Hello there!</p></div>";
|
||||
|
||||
HTMLParser *parser = [[HTMLParser alloc] initWithString: htmlString];
|
||||
|
||||
HTMLElement *tableContext = [[HTMLElement alloc] initWithTagName:@"table"];
|
||||
NSArray *nodes = [parser parseFragmentWithContextElement:tableContext];
|
||||
|
||||
for (HTMLNode *node in nodes) {
|
||||
NSLog(@"%@", node.outerHTML);
|
||||
}
|
||||
|
||||
// The same parser instance can be reusued:
|
||||
HTMLElement *bodyContext = [[HTMLElement alloc] initWithTagName:@"body"];
|
||||
nodes = [parser parseFragmentWithContextElement:bodyContext];
|
||||
```
|
||||
|
||||
# The DOM
|
||||
|
||||
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:@"meta" attributes: @{@"name": @"description"}];
|
||||
description[@"content"] = @"HTMLKit for iOS & OSX";
|
||||
```
|
||||
|
||||
* Append nodes to the document
|
||||
|
||||
```objective-c
|
||||
HTMLElement *head = document.head;
|
||||
[head appendNode:description];
|
||||
|
||||
HTMLElement *body = document.body;
|
||||
NSArray *nodes = @[
|
||||
[[HTMLElement alloc] initWithTagName:@"div" attributes: @{@"class": @"red"}],
|
||||
[[HTMLElement alloc] initWithTagName:@"div" attributes: @{@"class": @"green"}],
|
||||
[[HTMLElement alloc] initWithTagName:@"div" attributes: @{@"class": @"blue"}]
|
||||
];
|
||||
[body appendNodes:nodes];
|
||||
```
|
||||
|
||||
* Enumerate child elements and perform DOM editing
|
||||
|
||||
```objective-c
|
||||
[body enumerateChildElementsUsingBlock:^(HTMLElement *element, NSUInteger idx, BOOL *stop) {
|
||||
if ([element.tagName isEqualToString:@"div"]) {
|
||||
HTMLElement *lorem = [[HTMLElement alloc] initWithTagName:@"p"];
|
||||
lorem.textContent = [NSString stringWithFormat:@"Lorem ipsum: %lu", (unsigned long)idx];
|
||||
[element appendNode:lorem];
|
||||
}
|
||||
}];
|
||||
```
|
||||
|
||||
* Remove nodes from the document
|
||||
|
||||
```objective-c
|
||||
[body removeChildNodeAtIndex:1];
|
||||
[head removeAllChildNodes];
|
||||
[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
|
||||
HTMLNode *firstChild = body.firstChild;
|
||||
HTMLNode *greenDiv = firstChild.nextSibling;
|
||||
```
|
||||
|
||||
* Iterate the DOM tree with custom filters
|
||||
|
||||
```objective-c
|
||||
HTMLNodeFilterBlock *filter =[HTMLNodeFilterBlock filterWithBlock:^ HTMLNodeFilterValue (HTMLNode *node) {
|
||||
if (node.childNodesCount != 1) {
|
||||
return HTMLNodeFilterReject;
|
||||
}
|
||||
return HTMLNodeFilterAccept;
|
||||
}];
|
||||
|
||||
for (HTMLElement *element in [body nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:filter]) {
|
||||
NSLog(@"%@", element.outerHTML);
|
||||
}
|
||||
```
|
||||
|
||||
* 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:
|
||||
|
||||
```objective-c
|
||||
// Given the document:
|
||||
NSString *htmlString = @"<div><h1>HTMLKit</h1><p class='greeting'>Hello there!</p><p class='description'>This is a demo of HTMLKit</p></div>";
|
||||
HTMLDocument *document = [HTMLDocument documentWithString: htmlString];
|
||||
|
||||
// Here are some of the supported selectors
|
||||
NSArray *paragraphs = [document querySelectorAll:@"p"];
|
||||
NSArray *paragraphsOrHeaders = [document querySelectorAll:@"p, h1"];
|
||||
NSArray *hasClassAttribute = [document querySelectorAll:@"[class]"];
|
||||
NSArray *greetings = [document querySelectorAll:@".greeting"];
|
||||
NSArray *classNameStartsWith_de = [document querySelectorAll:@"[class^='de']"];
|
||||
|
||||
NSArray *hasAdjacentHeader = [document querySelectorAll:@"h1 + *"];
|
||||
NSArray *hasSiblingHeader = [document querySelectorAll:@"h1 ~ *"];
|
||||
NSArray *hasSiblingParagraph = [document querySelectorAll:@"p ~ *"];
|
||||
|
||||
NSArray *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:
|
||||
|
||||
```objective-c
|
||||
NSArray *paragraphs = [document elementsMatchingSelector:typeSelector(@"p")];
|
||||
NSArray *paragraphsOrHeaders = [document elementsMatchingSelector:
|
||||
anyOf(@[
|
||||
typeSelector(@"p"), typeSelector(@"h1")
|
||||
])
|
||||
];
|
||||
|
||||
NSArray *hasClassAttribute = [document elementsMatchingSelector:hasAttributeSelector(@"class")];
|
||||
NSArray *greetings = [document elementsMatchingSelector:classSelector(@"greeting")];
|
||||
NSArray *classNameStartsWith_de = [document elementsMatchingSelector:attributeSelector(CSSAttributeSelectorBegins, @"class", @"de")];
|
||||
|
||||
NSArray *hasAdjacentHeader = [document elementsMatchingSelector:adjacentSiblingSelector(typeSelector(@"h1"))];
|
||||
NSArray *hasSiblingHeader = [document elementsMatchingSelector:generalSiblingSelector(typeSelector(@"h1"))];
|
||||
NSArray *hasSiblingParagraph = [document elementsMatchingSelector:generalSiblingSelector(typeSelector(@"p"))];
|
||||
|
||||
NSArray *nonParagraphChildOfDiv = [document elementsMatchingSelector:
|
||||
allOf(@[
|
||||
childOfElementSelector(typeSelector(@"div")),
|
||||
not(typeSelector(@"p"))
|
||||
])
|
||||
];
|
||||
```
|
||||
|
||||
Here are more examples:
|
||||
|
||||
```objective-c
|
||||
HTMLNode *firstDivElement = [document firstElementMatchingSelector:typeSelector(@"div")];
|
||||
|
||||
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))];
|
||||
|
||||
NSArray *notParagraphAndNotDiv = [firstDivElement querySelectorAll:@":not(p):not(div)"];
|
||||
notParagraphAndNotDiv = [firstDivElement elementsMatchingSelector:
|
||||
allOf([
|
||||
not(typeSelector(@"p")),
|
||||
not(typeSelector(@"div"))
|
||||
])
|
||||
];
|
||||
```
|
||||
|
||||
One more thing! You can also create your own selectors. You either subclass the CSSSelector or just use the block-based wrapper. For example the previous selector can be implemented like this:
|
||||
|
||||
```objective-c
|
||||
CSSSelector *myAwesomeSelector = namedBlockSelector(@"myAwesomeSelector", ^BOOL (HTMLElement *element) {
|
||||
return ![element.tagName isEqualToString:@"p"] && ![element.tagName isEqualToString:@"div"];
|
||||
});
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// CSSAttributeSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 14/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSAttributeSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSCharacterSet+HTMLKit.h"
|
||||
|
||||
@interface CSSAttributeSelector ()
|
||||
{
|
||||
CSSAttributeSelectorType _type;
|
||||
NSString *_name;
|
||||
NSString *_value;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSAttributeSelector
|
||||
|
||||
+ (instancetype)classSelector:(NSString *)className
|
||||
{
|
||||
return [[self alloc] initWithType:CSSAttributeSelectorIncludes attributeName:@"class" attrbiuteValue:className];
|
||||
}
|
||||
|
||||
+ (instancetype)idSelector:(NSString *)elementId
|
||||
{
|
||||
return [[self alloc] initWithType:CSSAttributeSelectorExactMatch attributeName:@"id" attrbiuteValue:elementId];
|
||||
}
|
||||
|
||||
+ (instancetype)hasAttributeSelector:(NSString *)attributeName
|
||||
{
|
||||
return [[self alloc] initWithType:CSSAttributeSelectorExists attributeName:attributeName attrbiuteValue:@""];
|
||||
}
|
||||
|
||||
- (instancetype)initWithType:(CSSAttributeSelectorType)type
|
||||
attributeName:(NSString *)name
|
||||
attrbiuteValue:(NSString *)value
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_type = type;
|
||||
_name = [name copy];
|
||||
_value = value ? [value copy]: @"";
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
switch (_type) {
|
||||
case CSSAttributeSelectorExists:
|
||||
{
|
||||
return !!element[_name];
|
||||
}
|
||||
case CSSAttributeSelectorExactMatch:
|
||||
{
|
||||
return [element[_name] isEqualToString:_value];
|
||||
}
|
||||
case CSSAttributeSelectorIncludes:
|
||||
{
|
||||
NSArray *components = [element[_name] componentsSeparatedByCharactersInSet:[NSCharacterSet HTMLWhitespaceCharacterSet]];
|
||||
return [components containsObject:_value];
|
||||
}
|
||||
case CSSAttributeSelectorBegins:
|
||||
{
|
||||
return [element[_name] hasPrefix:_value];
|
||||
}
|
||||
case CSSAttributeSelectorEnds:
|
||||
{
|
||||
return [element[_name] hasSuffix:_value];
|
||||
}
|
||||
case CSSAttributeSelectorContains:
|
||||
{
|
||||
return [element[_name] containsString:_value];
|
||||
}
|
||||
case CSSAttributeSelectorHyphen:
|
||||
{
|
||||
return [element[_name] isEqualToString:_value] || [element[_name] hasPrefix:[_value stringByAppendingString:@"-"]];
|
||||
}
|
||||
case CSSAttributeSelectorNot:
|
||||
{
|
||||
return ![element[_name] isEqualToString:_value];
|
||||
}
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
if (self.type == CSSAttributeSelectorExists) {
|
||||
return [NSString stringWithFormat:@"[%@]", self.name];
|
||||
}
|
||||
|
||||
NSString *matcher = @{@(CSSAttributeSelectorExactMatch): @"=",
|
||||
@(CSSAttributeSelectorIncludes): @"~=",
|
||||
@(CSSAttributeSelectorBegins): @"^=",
|
||||
@(CSSAttributeSelectorEnds): @"$=",
|
||||
@(CSSAttributeSelectorContains): @"*=",
|
||||
@(CSSAttributeSelectorHyphen): @"|=",
|
||||
@(CSSAttributeSelectorNot): @"!="}[@(self.type)];
|
||||
|
||||
return [NSString stringWithFormat:@"[%@%@'%@']", self.name, matcher, self.value];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// CSSCombinatorSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 12/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSCombinatorSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
#pragma mark - Declarations
|
||||
|
||||
@interface CSSChildOfElementCombinatorSelector : CSSCombinatorSelector
|
||||
@end
|
||||
|
||||
@interface CSSDecendantOfElementCombinatorSelector : CSSCombinatorSelector
|
||||
@end
|
||||
|
||||
@interface CSSAdjacentSiblingCombinatorSelector : CSSCombinatorSelector
|
||||
@end
|
||||
|
||||
@interface CSSGeneralSiblingCombinatorSelector : CSSCombinatorSelector
|
||||
@end
|
||||
|
||||
#pragma mark - Base Combinator
|
||||
|
||||
@interface CSSCombinatorSelector ()
|
||||
{
|
||||
CSSSelector *_selector;
|
||||
}
|
||||
@property (nonatomic, strong, readonly) CSSSelector *selector;
|
||||
@end
|
||||
|
||||
@implementation CSSCombinatorSelector
|
||||
@synthesize selector = _selector;
|
||||
|
||||
+ (instancetype)childOfElementCombinator:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSChildOfElementCombinatorSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
+ (instancetype)descendantOfElementCombinator:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSDecendantOfElementCombinatorSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
+ (instancetype)adjacentSiblingCombinator:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSAdjacentSiblingCombinatorSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
+ (instancetype)generalSiblingCombinator:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSGeneralSiblingCombinatorSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelector:(CSSSelector *)selector
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Child OfElement Combinator
|
||||
|
||||
@implementation CSSChildOfElementCombinatorSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLElement *parent = element.parentElement;
|
||||
return parent != nil && [self.selector acceptElement:parent];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ > ", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Decendant Of Element Combinator
|
||||
|
||||
@implementation CSSDecendantOfElementCombinatorSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLElement *parent = element.parentElement;
|
||||
|
||||
while (parent != nil) {
|
||||
if ([self.selector acceptElement:parent]) {
|
||||
return YES;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ ", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Adjacent Sibling Combinator
|
||||
|
||||
@implementation CSSAdjacentSiblingCombinatorSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLNode *previous = element.previousSiblingElement;
|
||||
if (previous == nil || previous.nodeType != HTMLNodeElement) {
|
||||
return NO;
|
||||
}
|
||||
return [self.selector acceptElement:previous.asElement];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ + ", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - General Sibling Combinator
|
||||
|
||||
@implementation CSSGeneralSiblingCombinatorSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLNode *previous = element.previousSiblingElement;
|
||||
|
||||
while (previous != nil && previous.nodeType == HTMLNodeElement) {
|
||||
if ([self.selector acceptElement:previous.asElement]) {
|
||||
return YES;
|
||||
}
|
||||
previous = previous.previousSiblingElement;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ ~ ", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// CSSCompoundSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 18/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSCompoundSelector.h"
|
||||
|
||||
#pragma mark - Declarations
|
||||
|
||||
@interface CSSAndCompoundSelector : CSSCompoundSelector
|
||||
@end
|
||||
|
||||
@interface CSSOrCompoundSelector : CSSCompoundSelector
|
||||
@end
|
||||
|
||||
#pragma mark - Base Combinator
|
||||
|
||||
@interface CSSCompoundSelector ()
|
||||
{
|
||||
NSMutableArray *_selectors;
|
||||
}
|
||||
@property (nonatomic, strong, readonly) NSArray *selectors;
|
||||
@end
|
||||
|
||||
@implementation CSSCompoundSelector
|
||||
@synthesize selectors = _selectors;
|
||||
|
||||
+ (instancetype)andSelector:(NSArray *)selectors
|
||||
{
|
||||
return [[CSSAndCompoundSelector alloc] initWithSelectors:selectors];
|
||||
}
|
||||
|
||||
+ (instancetype)orSelector:(NSArray *)selectors
|
||||
{
|
||||
return [[CSSOrCompoundSelector alloc] initWithSelectors:selectors];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelectors:(NSArray *)selectors
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selectors = [[NSMutableArray alloc] initWithArray:selectors];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addSelector:(CSSSelector *)selector
|
||||
{
|
||||
[_selectors addObject:selector];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - And Compound Selector
|
||||
|
||||
@implementation CSSAndCompoundSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
for (CSSSelector *selector in self.selectors) {
|
||||
if (![selector acceptElement:element]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
NSArray *descriptions = [self.selectors valueForKey:@"debugDescription"];
|
||||
return [descriptions componentsJoinedByString:@""];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Or Compound Selector
|
||||
|
||||
@implementation CSSOrCompoundSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
for (CSSSelector *selector in self.selectors) {
|
||||
if ([selector acceptElement:element]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
NSArray *descriptions = [self.selectors valueForKey:@"debugDescription"];
|
||||
return [descriptions componentsJoinedByString:@","];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// CSSInputStream.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 07/06/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSInputStream.h"
|
||||
#import "CSSCodePoints.h"
|
||||
|
||||
@interface HTMLInputStreamReader ()
|
||||
- (void)emitParseError:(NSString *)reason;
|
||||
@end
|
||||
|
||||
@implementation CSSInputStream
|
||||
|
||||
- (void)consumeWhitespace
|
||||
{
|
||||
while (isWhitespace(self.nextInputCharacter)) {
|
||||
[self consumeNextInputCharacter];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)consumeIdentifier
|
||||
{
|
||||
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
|
||||
|
||||
if (!isValidIdentifierStart([self inputCharacterPointAtOffset:0],
|
||||
[self inputCharacterPointAtOffset:1],
|
||||
[self inputCharacterPointAtOffset:2])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
while (YES) {
|
||||
UTF32Char codePoint = [self consumeNextInputCharacter];
|
||||
if (codePoint == EOF) {
|
||||
break;
|
||||
} else if (isName(codePoint)) {
|
||||
AppendCodePoint(value, codePoint);
|
||||
} else if (isValidEscape(codePoint, [self inputCharacterPointAtOffset:1])) {
|
||||
UTF32Char escapedCodePoint = [self consumeEscapedCodePoint];
|
||||
AppendCodePoint(value, escapedCodePoint);
|
||||
} else {
|
||||
[self reconsumeCurrentInputCharacter];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil);
|
||||
}
|
||||
|
||||
- (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint
|
||||
{
|
||||
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
|
||||
|
||||
while (YES) {
|
||||
UTF32Char codePoint = [self consumeNextInputCharacter];
|
||||
if (codePoint == endingCodePoint) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (codePoint) {
|
||||
case EOF:
|
||||
break;
|
||||
case LINE_FEED:
|
||||
[self emitParseError:@"New-line character (0x000A) in CSS attribute value"];
|
||||
[self reconsumeCurrentInputCharacter];
|
||||
break;
|
||||
case REVERSE_SOLIDUS:
|
||||
{
|
||||
UTF32Char next = self.nextInputCharacter;
|
||||
if (next == EOF) {
|
||||
continue;
|
||||
} else if (next == LINE_FEED) {
|
||||
[self consumeNextInputCharacter];
|
||||
} else {
|
||||
UTF32Char escapedCodePoint = [self consumeNextInputCharacter];
|
||||
AppendCodePoint(value, escapedCodePoint);
|
||||
}
|
||||
}
|
||||
default:
|
||||
AppendCodePoint(value, codePoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (__bridge NSString *)(CFStringGetLength(value) > 0 ? value : nil);
|
||||
}
|
||||
|
||||
- (UTF32Char)consumeEscapedCodePoint
|
||||
{
|
||||
UTF32Char codePoint = [self consumeNextInputCharacter];
|
||||
|
||||
if (isHexDigit(codePoint)) {
|
||||
CFMutableStringRef hexString = CFStringCreateMutable(kCFAllocatorDefault, 6);
|
||||
AppendCodePoint(hexString, codePoint);
|
||||
|
||||
while (isHexDigit(self.nextInputCharacter) && CFStringGetLength(hexString) <= 6) {
|
||||
UniChar codePoint = [self consumeNextInputCharacter];
|
||||
CFStringAppendCharacters(hexString, &codePoint, 1);
|
||||
}
|
||||
|
||||
if (isWhitespace(self.nextInputCharacter)) {
|
||||
[self consumeNextInputCharacter];
|
||||
}
|
||||
|
||||
NSScanner *scanner = [NSScanner scannerWithString:(__bridge NSString *)(hexString)];
|
||||
unsigned int number;
|
||||
[scanner scanHexInt:&number];
|
||||
|
||||
return isValidEscapedCodePoint(number) ? number : REPLACEMENT_CHARACTER;
|
||||
} else if (codePoint == EOF) {
|
||||
return REPLACEMENT_CHARACTER;
|
||||
}
|
||||
|
||||
return codePoint;
|
||||
}
|
||||
|
||||
- (NSString *)consumeCombinator
|
||||
{
|
||||
NSString *combinator = [self consumeCharactersInString:@" >+~"];
|
||||
combinator = [combinator stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
|
||||
return combinator;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// CSSNthExpression.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 10/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSNthExpressionParser.h"
|
||||
#import "CSSCodePoints.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "NSCharacterSet+HTMLKit.h"
|
||||
|
||||
@implementation CSSNthExpressionParser
|
||||
|
||||
+ (CSSNthExpression)parseExpression:(NSString *)expression
|
||||
{
|
||||
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
||||
|
||||
NSString *string = [expression.lowercaseString copy];
|
||||
string = [[string stringByTrimmingCharactersInSet:whitespace] copy];
|
||||
|
||||
if ([string isEqualToStringIgnoringCase:@"odd"]) {
|
||||
return CSSNthExpressionOdd;
|
||||
} else if ([string isEqualToStringIgnoringCase:@"even"]) {
|
||||
return CSSNthExpressionEven;
|
||||
}
|
||||
|
||||
NSCharacterSet *set = [[NSCharacterSet CSSNthExpressionCharacterSet] invertedSet];
|
||||
if ([string rangeOfCharacterFromSet:set].location != NSNotFound) {
|
||||
return CSSNthExpressionMake(0, 0);
|
||||
}
|
||||
NSArray *parts = [string componentsSeparatedByString:@"n"];
|
||||
|
||||
if (parts.count == 1) {
|
||||
NSInteger b = [parts[0] integerValue];
|
||||
return CSSNthExpressionMake(0, b);
|
||||
} else if (parts.count == 2) {
|
||||
NSInteger a = [parts[0] integerValue];
|
||||
if (a == 0) {
|
||||
a = [parts[0] isEqualToString:@"-"] ? -1 : 1;
|
||||
}
|
||||
NSInteger b = [parts[1] integerValue];
|
||||
return CSSNthExpressionMake(a, b);
|
||||
} else {
|
||||
return CSSNthExpressionMake(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// CSSNthExpressionSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 10/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSNthExpressionSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
#pragma mark - Nth-Expression
|
||||
|
||||
const CSSNthExpression CSSNthExpressionOdd = (CSSNthExpression) {
|
||||
.an = 2, .b = 1
|
||||
};
|
||||
|
||||
const CSSNthExpression CSSNthExpressionEven = (CSSNthExpression) {
|
||||
.an = 2, .b = 0
|
||||
};
|
||||
|
||||
NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression)
|
||||
{
|
||||
if (expression.an == 0 && expression.b == 0) {
|
||||
return @"invalid";
|
||||
}
|
||||
|
||||
if (expression.an == 0) {
|
||||
return [NSString stringWithFormat:@"%ld", (long)expression.b];
|
||||
}
|
||||
if (expression.b == 0) {
|
||||
return [NSString stringWithFormat:@"%ldn", (long)expression.an];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%ldn%+ld", (long)expression.an, (long)expression.b];
|
||||
}
|
||||
|
||||
#pragma mark - Implementation
|
||||
|
||||
NSInteger computeIndex(NSEnumerator *enumerator, HTMLElement *element)
|
||||
{
|
||||
NSInteger index = 0;
|
||||
for (HTMLNode *node in enumerator) {
|
||||
if (node.nodeType != HTMLNodeElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([node.asElement.tagName isEqualToString:element.tagName]) {
|
||||
index++;
|
||||
}
|
||||
|
||||
if (node == element) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
@interface CSSNthExpressionSelector ()
|
||||
{
|
||||
NSString *_className;
|
||||
CSSNthExpression _expression;
|
||||
NSInteger (^ _computeIndex)(HTMLElement *);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSNthExpressionSelector
|
||||
@synthesize expression = _expression;
|
||||
@synthesize className = _className;
|
||||
|
||||
+ (instancetype)nthChildSelector:(CSSNthExpression)expression
|
||||
{
|
||||
return [[self alloc] initWithClassName:@"nth-child" expression:expression block:^NSInteger(HTMLElement *element) {
|
||||
return [element.parentElement indexOfChildElement:element] + 1;
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)nthLastChildSelector:(CSSNthExpression)expression
|
||||
{
|
||||
return [[self alloc] initWithClassName:@"nth-last-child" expression:expression block:^NSInteger(HTMLElement *element) {
|
||||
return element.parentElement.childElementsCount - [element.parentElement indexOfChildElement:element];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)nthOfTypeSelector:(CSSNthExpression)expression
|
||||
{
|
||||
return [[self alloc] initWithClassName:@"nth-of-type" expression:expression block:^NSInteger(HTMLElement *element) {
|
||||
return computeIndex(element.parentElement.childNodes.array.objectEnumerator, element);
|
||||
}];
|
||||
}
|
||||
|
||||
+ (instancetype)nthLastOfTypeSelector:(CSSNthExpression)expression
|
||||
{
|
||||
return [[self alloc] initWithClassName:@"nth-last-of-type" expression:expression block:^NSInteger(HTMLElement *element) {
|
||||
return computeIndex(element.parentElement.childNodes.array.reverseObjectEnumerator, element);
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithClassName:(NSString *)className
|
||||
expression:(CSSNthExpression)expression
|
||||
block:(NSInteger (^)(HTMLElement *element))block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_className = [className copy];
|
||||
_expression = expression;
|
||||
_computeIndex = [block copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
NSInteger index = _computeIndex(element);
|
||||
|
||||
if (_expression.an == 0) {
|
||||
return index == _expression.b;
|
||||
} else {
|
||||
NSInteger diff = (index - _expression.b);
|
||||
return (diff * _expression.an >= 0) && (diff % _expression.an == 0);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@":%@(%@)", self.className, NSStringFromNthExpression(self.expression)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// CSSPseudoClassSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 06/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSPseudoClassSelector.h"
|
||||
|
||||
@interface CSSPseudoClassSelector ()
|
||||
{
|
||||
NSString *_className;
|
||||
CSSSelector *_selector;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSPseudoClassSelector
|
||||
@synthesize className = _className;
|
||||
|
||||
- (instancetype)initWithClassName:(NSString *)className selector:(CSSSelector *)selector
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_className = [className copy];
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
return [_selector acceptElement:element];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@":%@", self.className];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// CSSPseudoFunctionSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 07/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSPseudoFunctionSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
#pragma mark - Declarations
|
||||
|
||||
@interface CSSNotSelector : CSSPseudoFunctionSelector
|
||||
@end
|
||||
|
||||
@interface CSSHasSelector : CSSPseudoFunctionSelector
|
||||
@end
|
||||
|
||||
#pragma mark - Base Function Selector
|
||||
|
||||
@interface CSSPseudoFunctionSelector ()
|
||||
{
|
||||
CSSSelector *_selector;
|
||||
}
|
||||
@property (nonatomic, strong, readonly) CSSSelector *selector;
|
||||
@end
|
||||
|
||||
@implementation CSSPseudoFunctionSelector
|
||||
@synthesize selector = _selector;
|
||||
|
||||
+ (instancetype)notSelector:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSNotSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
+ (instancetype)hasSelector:(CSSSelector *)selector
|
||||
{
|
||||
return [[CSSHasSelector alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelector:(CSSSelector *)selector
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Not Selector
|
||||
|
||||
@implementation CSSNotSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
return ![self.selector acceptElement:element];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@":not(%@)", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Has Selector
|
||||
|
||||
@implementation CSSHasSelector
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
HTMLNodeIterator *iterator = [element nodeIteratorWithShowOptions:HTMLNodeFilterShowAll filter:nil];
|
||||
for (HTMLNode *descendant in iterator) {
|
||||
if (descendant.nodeType == HTMLNodeElement && [self.selector acceptElement:descendant.asElement]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return [NSString stringWithFormat:@":has(%@)", self.selector.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// CSSSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/10/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CSSSelector.h"
|
||||
#import "CSSSelectorParser.h"
|
||||
|
||||
@implementation CSSSelector
|
||||
|
||||
+ (instancetype)selectorWithString:(NSString *)string
|
||||
{
|
||||
NSError *error = nil;
|
||||
CSSSelector *instance = [CSSSelectorParser parseSelector:string error:&error];
|
||||
if (error) {
|
||||
return nil;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.debugDescription];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// CSSSelectorBlock.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelectorBlock.h"
|
||||
|
||||
@interface CSSSelectorBlock ()
|
||||
{
|
||||
NSString *_name;
|
||||
BOOL (^ _acceptBlock)(HTMLElement *);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSSelectorBlock
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name block:(BOOL (^)(HTMLElement *))block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = [name copy];
|
||||
_acceptBlock = [block copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
return _acceptBlock ? _acceptBlock(element) : NO;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,426 @@
|
||||
//
|
||||
// CSSSelectorParser.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 02/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelectorParser.h"
|
||||
#import "CSSInputStream.h"
|
||||
#import "CSSCodePoints.h"
|
||||
#import "CSSSelectors.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "NSCharacterSet+HTMLKit.h"
|
||||
#import "CSSNthExpressionParser.h"
|
||||
#import "CSSCompoundSelector.h"
|
||||
#import "HTMLKitErrorDomain.h"
|
||||
|
||||
@interface CSSSelectorParser ()
|
||||
{
|
||||
NSString *_string;
|
||||
CSSInputStream *_inputStream;
|
||||
NSUInteger _location;
|
||||
|
||||
NSMutableArray *_selectors;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSSelectorParser
|
||||
|
||||
+ (CSSSelector *)parseSelector:(NSString *)string error:(NSError * __autoreleasing *)error
|
||||
{
|
||||
CSSSelectorParser *parser = [[CSSSelectorParser alloc] initWithString:string];
|
||||
CSSSelector *selector = [parser parse:error];
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)initWithString:(NSString *)string
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_string = [self preprocessInput:string];
|
||||
_location = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)preprocessInput:(NSString *)string
|
||||
{
|
||||
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"\r" withString:@"\n"];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"\f" withString:@"\n"];
|
||||
string = [string stringByReplacingOccurrencesOfString:@"\0" withString:@"\uFFFD"];
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
#pragma mark - Errors
|
||||
|
||||
- (void)emitError:(NSError * __autoreleasing *)error reason:(NSString *)reason
|
||||
{
|
||||
[self emitError:error reason:reason location:_location + _inputStream.currentLocation];
|
||||
}
|
||||
|
||||
- (void)emitError:(NSError * __autoreleasing *)error reason:(NSString *)reason location:(NSUInteger)location
|
||||
{
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: @"Error parsing selector",
|
||||
NSLocalizedFailureReasonErrorKey: reason,
|
||||
CSSSelectorStringKey: _string,
|
||||
CSSSelectorErrorLocationKey: @(location)
|
||||
};
|
||||
|
||||
if(error && *error == nil) {
|
||||
*error = [NSError errorWithDomain:HTMLKitSelectorErrorDomain code:HTMLKitSelectorParseError userInfo:userInfo];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Parsing
|
||||
|
||||
- (CSSSelector *)parse:(NSError * __autoreleasing *)error
|
||||
{
|
||||
if (_string.length == 0) {
|
||||
[self emitError:error reason:@"Empty selector" location:0];
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSArray *allSubSelectors = [_string componentsSeparatedByString:@","];
|
||||
NSMutableArray *parsed = [NSMutableArray array];
|
||||
|
||||
for (NSString *subSelector in allSubSelectors) {
|
||||
if ([subSelector isEqualToString:@""]) {
|
||||
[self emitError:error reason:@"Empty selector" location:_location];
|
||||
break;
|
||||
}
|
||||
|
||||
CSSSelector *selector = [self parseSelector:subSelector error:error];
|
||||
if (selector == nil) {
|
||||
break;
|
||||
}
|
||||
[parsed addObject:selector];
|
||||
|
||||
_location += subSelector.length;
|
||||
}
|
||||
|
||||
if (error && *error != nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (parsed.count > 1) {
|
||||
return anyOf(parsed);
|
||||
}
|
||||
|
||||
return parsed.firstObject;
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseSelector:(NSString *)selectorString error:(NSError * __autoreleasing *)error
|
||||
{
|
||||
_inputStream = [[CSSInputStream alloc] initWithString:selectorString];
|
||||
[_inputStream consumeWhitespace];
|
||||
|
||||
CSSSelector *result = nil;
|
||||
|
||||
while (YES) {
|
||||
CSSSelector *selector = [self parseSequenceOfSimpleSelectors:error];
|
||||
if (selector == nil) {
|
||||
break;
|
||||
}
|
||||
|
||||
result = result ? allOf(@[result, selector]) : selector;
|
||||
|
||||
UTF32Char next = _inputStream.nextInputCharacter;
|
||||
|
||||
if (isCombinator(next)) {
|
||||
NSString *combinator = [_inputStream consumeCombinator];
|
||||
|
||||
if ([combinator isEqualToString:@""]) {
|
||||
result = descendantOfElementSelector(result);
|
||||
} else if ([combinator isEqualToString:@">"]) {
|
||||
result = childOfElementSelector(result);
|
||||
} else if ([combinator isEqualToString:@"+"]) {
|
||||
result = adjacentSiblingSelector(result);
|
||||
} else if ([combinator isEqualToString:@"~"]) {
|
||||
result = generalSiblingSelector(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseSequenceOfSimpleSelectors:(NSError * __autoreleasing *)error
|
||||
{
|
||||
NSMutableArray *selectors = [NSMutableArray array];
|
||||
|
||||
CSSSelector *typeSelector = [self parseTypeSelector:error];
|
||||
if (typeSelector != nil) {
|
||||
[selectors addObject:typeSelector];
|
||||
}
|
||||
|
||||
while (YES) {
|
||||
UTF32Char next = _inputStream.nextInputCharacter;
|
||||
if (next == EOF || isCombinator(next)) {
|
||||
break;
|
||||
}
|
||||
|
||||
CSSSelector *simpleSelector = [self parseSimpleSelector:error];
|
||||
if (simpleSelector == nil) {
|
||||
return nil;
|
||||
}
|
||||
[selectors addObject:simpleSelector];
|
||||
}
|
||||
|
||||
if (selectors.count > 1) {
|
||||
return allOf(selectors);
|
||||
}
|
||||
|
||||
return selectors.firstObject;
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseTypeSelector:(NSError * __autoreleasing *)error
|
||||
{
|
||||
NSString *identifier = [_inputStream consumeIdentifier];
|
||||
if (identifier != nil) {
|
||||
return typeSelector(identifier);
|
||||
}
|
||||
|
||||
if ([_inputStream consumeCharacter:ASTERIX]) {
|
||||
return universalSelector();
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseSimpleSelector:(NSError * __autoreleasing *)error
|
||||
{
|
||||
CSSSelector *typeSelector = [self parseTypeSelector:error];
|
||||
if (typeSelector != nil) {
|
||||
return typeSelector;
|
||||
}
|
||||
|
||||
UTF32Char codePoint = [_inputStream consumeNextInputCharacter];
|
||||
switch (codePoint) {
|
||||
case NUMBER_SIGN:
|
||||
{
|
||||
NSString *elementId = [_inputStream consumeIdentifier];
|
||||
if (elementId == nil) {
|
||||
[self emitError:error reason:@"Invalid character"];
|
||||
return nil;
|
||||
}
|
||||
return idSelector(elementId);
|
||||
}
|
||||
case FULL_STOP:
|
||||
{
|
||||
NSString *className = [_inputStream consumeIdentifier];
|
||||
if (className == nil) {
|
||||
[self emitError:error reason:@"Invalid character"];
|
||||
return nil;
|
||||
}
|
||||
return classSelector(className);
|
||||
}
|
||||
case LEFT_SQUARE_BRACKET:
|
||||
{
|
||||
return [self parseAttributeSelector:error];
|
||||
}
|
||||
case COLON:
|
||||
{
|
||||
return [self parsePseudoSelector:error];
|
||||
}
|
||||
default:
|
||||
{
|
||||
[self emitError:error reason:@"Invalid character"];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CSSSelector *)parseAttributeSelector:(NSError * __autoreleasing *)error
|
||||
{
|
||||
NSString *attribute = [_inputStream consumeIdentifier];
|
||||
if (attribute == nil) {
|
||||
[self emitError:error reason:@"Invalid character" location:_location + _inputStream.currentLocation + 1];
|
||||
return nil;
|
||||
}
|
||||
[_inputStream consumeWhitespace];
|
||||
|
||||
CSSAttributeSelectorType type = CSSAttributeSelectorExists;
|
||||
|
||||
NSString *operator = [_inputStream consumeCharactersInString:@"=~|^$*!"];
|
||||
|
||||
if ([operator isEqualToString:@"="]) {
|
||||
type = CSSAttributeSelectorExactMatch;
|
||||
} else if ([operator isEqualToString:@"~="]) {
|
||||
type = CSSAttributeSelectorIncludes;
|
||||
} else if ([operator isEqualToString:@"|="]) {
|
||||
type = CSSAttributeSelectorHyphen;
|
||||
} else if ([operator isEqualToString:@"^="]) {
|
||||
type = CSSAttributeSelectorBegins;
|
||||
} else if ([operator isEqualToString:@"$="]) {
|
||||
type = CSSAttributeSelectorEnds;
|
||||
} else if ([operator isEqualToString:@"*="]) {
|
||||
type = CSSAttributeSelectorContains;
|
||||
} else if ([operator isEqualToString:@"!="]) {
|
||||
type = CSSAttributeSelectorNot;
|
||||
}
|
||||
|
||||
NSString *value = nil;
|
||||
[_inputStream consumeWhitespace];
|
||||
|
||||
UTF32Char next = _inputStream.nextInputCharacter;
|
||||
if (isQuote(next)) {
|
||||
UTF32Char quote = [_inputStream consumeNextInputCharacter];
|
||||
value = [_inputStream consumeStringWithEndingCodePoint:quote];
|
||||
} else {
|
||||
value = [_inputStream consumeIdentifier];
|
||||
}
|
||||
|
||||
[_inputStream consumeWhitespace];
|
||||
|
||||
// Consume RIGHT_SQUARE_BRACKET
|
||||
if (![_inputStream consumeCharacter:RIGHT_SQUARE_BRACKET]) {
|
||||
[self emitError:error reason:@"Expected closing right square bracket ']'"];
|
||||
}
|
||||
|
||||
if (type == CSSAttributeSelectorExists) {
|
||||
return hasAttributeSelector(attribute);
|
||||
}
|
||||
|
||||
return attributeSelector(type, attribute, value);
|
||||
}
|
||||
|
||||
- (CSSSelector *)parsePseudoSelector:(NSError * __autoreleasing *)error
|
||||
{
|
||||
NSString *pseudoClass = [_inputStream consumeIdentifier];
|
||||
|
||||
if ([pseudoClass hasPrefix:@"nth"]) {
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected opening left parenthesis '('"];
|
||||
}
|
||||
|
||||
NSString *functionExpression = [_inputStream consumeCharactersUpToString:@")"];
|
||||
CSSNthExpression expression = [CSSNthExpressionParser parseExpression:functionExpression];
|
||||
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
|
||||
}
|
||||
|
||||
if ([pseudoClass isEqualToString:@"nth-child"]) {
|
||||
return nthChildSelector(expression);
|
||||
} else if ([pseudoClass isEqualToString:@"nth-last-child"]) {
|
||||
return nthLastChildSelector(expression);
|
||||
} else if ([pseudoClass isEqualToString:@"nth-of-type"]) {
|
||||
return nthOfTypeSelector(expression);
|
||||
} else if ([pseudoClass isEqualToString:@"nth-last-of-type"]) {
|
||||
return nthLastOfTypeSelector(expression);
|
||||
}
|
||||
} else if ([pseudoClass isEqualToString:@"not"]) {
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected opening left parenthesis '('"];
|
||||
}
|
||||
|
||||
CSSSelector *subSelector = [self parseSimpleSelector:error];
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
|
||||
}
|
||||
|
||||
return not(subSelector);
|
||||
} else if ([pseudoClass isEqualToAny:@"lt", @"gt", @"eq", nil]) {
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:LEFT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected opening left parenthesis '('"];
|
||||
}
|
||||
|
||||
NSDecimal decimal;
|
||||
if (![_inputStream consumeDecimalNumber:&decimal]) {
|
||||
[self emitError:error reason:@"Expected a decimal number"];
|
||||
}
|
||||
|
||||
[_inputStream consumeWhitespace];
|
||||
if (![_inputStream consumeCharacter:RIGHT_PARENTHESIS]) {
|
||||
[self emitError:error reason:@"Expected closing right parenthesis ')'"];
|
||||
}
|
||||
|
||||
NSDecimalNumber *number = [[NSDecimalNumber alloc] initWithDecimal:decimal];
|
||||
if ([pseudoClass isEqualToString:@"lt"]) {
|
||||
return ltSelector(number.integerValue);
|
||||
} else if ([pseudoClass isEqualToString:@"gt"]) {
|
||||
return gtSelector(number.integerValue);
|
||||
} else if ([pseudoClass isEqualToString:@"eq"]) {
|
||||
return eqSelector(number.integerValue);
|
||||
}
|
||||
} else {
|
||||
if ([pseudoClass isEqualToString:@"even"]) {
|
||||
return evenSlector();
|
||||
} else if ([pseudoClass isEqualToString:@"odd"]) {
|
||||
return oddSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"first-child"]) {
|
||||
return firstChildSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"last-child"]) {
|
||||
return lastChildSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"first-of-type"]) {
|
||||
return firstOfTypeSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"last-of-type"]) {
|
||||
return lastOfTypeSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"only-child"]) {
|
||||
return onlyChildSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"only-of-type"]) {
|
||||
return onlyOfTypeSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"root"]) {
|
||||
return rootSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"empty"]) {
|
||||
return emptySelector();
|
||||
} else if ([pseudoClass isEqualToString:@"link"]) {
|
||||
return linkSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"enabled"]) {
|
||||
return enabledSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"disabled"]) {
|
||||
return disabledSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"checked"]) {
|
||||
return checkedSelector();
|
||||
}
|
||||
|
||||
else if ([pseudoClass isEqualToString:@"button"]) {
|
||||
return buttonSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"checkbox"]) {
|
||||
return checkboxSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"file"]) {
|
||||
return fileSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"header"]) {
|
||||
return headerSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"image"]) {
|
||||
return imageSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"optional"]) {
|
||||
return optionalSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"parent"]) {
|
||||
return parentSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"password"]) {
|
||||
return passwordSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"radio"]) {
|
||||
return radioSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"reset"]) {
|
||||
return resetSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"submit"]) {
|
||||
return submitSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"text"]) {
|
||||
return textSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"required"]) {
|
||||
return requiredSelector();
|
||||
} else if ([pseudoClass isEqualToString:@"reset"]) {
|
||||
return resetSelector();
|
||||
}
|
||||
}
|
||||
NSString *reason = [NSString stringWithFormat:@"Unknown pseudo class: %@", pseudoClass];
|
||||
[self emitError:error reason:reason];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// CSSSelectors.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 19/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSSelectors.h"
|
||||
#import "CSSTypeSelector.h"
|
||||
#import "CSSAttributeSelector.h"
|
||||
#import "CSSPseudoClassSelector.h"
|
||||
#import "CSSPseudoFunctionSelector.h"
|
||||
#import "CSSNthExpressionSelector.h"
|
||||
#import "CSSCombinatorSelector.h"
|
||||
#import "CSSCompoundSelector.h"
|
||||
#import "CSSSelectorBlock.h"
|
||||
|
||||
#pragma mark - Type Selectors
|
||||
|
||||
CSSSelector * universalSelector()
|
||||
{
|
||||
return [CSSTypeSelector universalSelector];
|
||||
}
|
||||
|
||||
CSSSelector * typeSelector(NSString *type)
|
||||
{
|
||||
return [[CSSTypeSelector alloc] initWithType:type];
|
||||
}
|
||||
|
||||
#pragma mark - Atribute Selectors
|
||||
|
||||
CSSSelector * idSelector(NSString *elementId)
|
||||
{
|
||||
return [CSSAttributeSelector idSelector:elementId];
|
||||
}
|
||||
|
||||
CSSSelector * classSelector(NSString *className)
|
||||
{
|
||||
return [CSSAttributeSelector classSelector:className];
|
||||
}
|
||||
|
||||
CSSSelector * hasAttributeSelector(NSString *attribute)
|
||||
{
|
||||
return [CSSAttributeSelector hasAttributeSelector:attribute];
|
||||
}
|
||||
|
||||
CSSSelector * attributeSelector(CSSAttributeSelectorType type,
|
||||
NSString *attribute,
|
||||
NSString *value)
|
||||
{
|
||||
return [[CSSAttributeSelector alloc] initWithType:type attributeName:attribute attrbiuteValue:value];
|
||||
}
|
||||
|
||||
#pragma mark - Nth-Expression Selectors
|
||||
|
||||
CSSSelector * nthChildSelector(CSSNthExpression expression)
|
||||
{
|
||||
return [CSSNthExpressionSelector nthChildSelector:expression];
|
||||
}
|
||||
|
||||
CSSSelector * nthLastChildSelector(CSSNthExpression expression)
|
||||
{
|
||||
return [CSSNthExpressionSelector nthLastChildSelector:expression];
|
||||
}
|
||||
|
||||
CSSSelector * nthOfTypeSelector(CSSNthExpression expression)
|
||||
{
|
||||
return [CSSNthExpressionSelector nthOfTypeSelector:expression];
|
||||
}
|
||||
|
||||
CSSSelector * nthLastOfTypeSelector(CSSNthExpression expression)
|
||||
{
|
||||
return [CSSNthExpressionSelector nthLastOfTypeSelector:expression];
|
||||
}
|
||||
|
||||
#pragma mark - Nth-Expression Shorthand
|
||||
|
||||
CSSSelector * oddSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"odd", nthChildSelector(CSSNthExpressionOdd));
|
||||
}
|
||||
|
||||
CSSSelector * evenSlector()
|
||||
{
|
||||
return namedPseudoSelector(@"even", nthChildSelector(CSSNthExpressionEven));
|
||||
}
|
||||
|
||||
CSSSelector * firstChildSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"first-child", nthChildSelector(CSSNthExpressionMake(0, 1)));
|
||||
}
|
||||
|
||||
CSSSelector * lastChildSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"last-child", nthLastChildSelector(CSSNthExpressionMake(0, 1)));
|
||||
}
|
||||
|
||||
CSSSelector * firstOfTypeSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"first-of-type", nthOfTypeSelector(CSSNthExpressionMake(0, 1)));
|
||||
}
|
||||
|
||||
CSSSelector * lastOfTypeSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"last-of-type", nthLastOfTypeSelector(CSSNthExpressionMake(0, 1)));
|
||||
}
|
||||
|
||||
CSSSelector * onlyChildSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"only-child", allOf(@[firstChildSelector(), lastChildSelector()]));
|
||||
}
|
||||
|
||||
CSSSelector * onlyOfTypeSelector()
|
||||
{
|
||||
return namedPseudoSelector(@"only-of-type", allOf(@[firstOfTypeSelector(), lastOfTypeSelector()]));
|
||||
}
|
||||
|
||||
#pragma mark - Combinators
|
||||
|
||||
CSSSelector * childOfElementSelector(CSSSelector *selector)
|
||||
{
|
||||
return [CSSCombinatorSelector childOfElementCombinator:selector];
|
||||
}
|
||||
|
||||
CSSSelector * descendantOfElementSelector(CSSSelector *selector)
|
||||
{
|
||||
return [CSSCombinatorSelector descendantOfElementCombinator:selector];
|
||||
}
|
||||
|
||||
CSSSelector * adjacentSiblingSelector(CSSSelector *selector)
|
||||
{
|
||||
return [CSSCombinatorSelector adjacentSiblingCombinator:selector];
|
||||
}
|
||||
|
||||
CSSSelector * generalSiblingSelector(CSSSelector *selector)
|
||||
{
|
||||
return [CSSCombinatorSelector generalSiblingCombinator:selector];
|
||||
}
|
||||
|
||||
#pragma mark - Pseudo Functions
|
||||
|
||||
CSSSelector * not(CSSSelector *selector)
|
||||
{
|
||||
return [CSSPseudoFunctionSelector notSelector:selector];
|
||||
}
|
||||
|
||||
CSSSelector * has(CSSSelector *selector)
|
||||
{
|
||||
return [CSSPseudoFunctionSelector hasSelector:selector];
|
||||
}
|
||||
|
||||
#pragma mark - Compound Selectors
|
||||
|
||||
CSSSelector * allOf( NSArray<CSSSelector *> * selectors)
|
||||
{
|
||||
return [CSSCompoundSelector andSelector:selectors];
|
||||
}
|
||||
|
||||
CSSSelector * anyOf( NSArray<CSSSelector *> * selectors)
|
||||
{
|
||||
return [CSSCompoundSelector orSelector:selectors];
|
||||
}
|
||||
|
||||
#pragma mark - Pseudo
|
||||
|
||||
CSSSelector * namedPseudoSelector(NSString *name, CSSSelector *selector)
|
||||
{
|
||||
return [[CSSPseudoClassSelector alloc] initWithClassName:name selector:selector];
|
||||
}
|
||||
|
||||
#pragma mark - Block
|
||||
|
||||
CSSSelector * namedBlockSelector(NSString *name, BOOL (^ acceptBlock)(HTMLElement *element))
|
||||
{
|
||||
return [[CSSSelectorBlock alloc] initWithName:name block:acceptBlock];
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
//
|
||||
// CSSStructuralPseudoSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 11/10/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSStructuralPseudoSelectors.h"
|
||||
#import "CSSSelectors.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
#pragma mark - Elements
|
||||
|
||||
CSSSelector * rootSelector()
|
||||
{
|
||||
return namedBlockSelector(@":root", ^BOOL(HTMLElement * element) {
|
||||
return element.parentElement == nil;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * emptySelector()
|
||||
{
|
||||
return namedBlockSelector(@":empty", ^BOOL(HTMLElement * element) {
|
||||
for (HTMLNode *child in element.childNodes) {
|
||||
if (child.nodeType == HTMLNodeElement) {
|
||||
return NO;
|
||||
} else if (child.nodeType == HTMLNodeText && child.textContent.length > 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * parentSelector()
|
||||
{
|
||||
return namedBlockSelector(@":parent", ^BOOL(HTMLElement * element) {
|
||||
return element.childNodesCount > 0;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * buttonSelector()
|
||||
{
|
||||
return namedBlockSelector(@":button", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element.tagName isEqualToString:@"button"]) {
|
||||
return YES;
|
||||
}
|
||||
if ([element.tagName isEqualToString:@"input"] && [element[@"type"] isEqualToString:@"button"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * checkboxSelector()
|
||||
{
|
||||
return namedBlockSelector(@":checkbox", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"checkbox"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * fileSelector()
|
||||
{
|
||||
return namedBlockSelector(@":file", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"file"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * headerSelector()
|
||||
{
|
||||
return namedBlockSelector(@":header", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * imageSelector()
|
||||
{
|
||||
return namedBlockSelector(@":image", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"image"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * inputSelector()
|
||||
{
|
||||
return namedBlockSelector(@":input", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element.tagName isEqualToAny:@"button", @"input", @"select", @"textarea", nil]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * linkSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-link
|
||||
return namedBlockSelector(@":link", ^BOOL(HTMLElement * element) {
|
||||
if ([element hasAttribute:@"href"]) {
|
||||
return [element.tagName isEqualToAny:@"a", @"area", @"link", nil];
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * passwordSelector()
|
||||
{
|
||||
return namedBlockSelector(@":password", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"password"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * radioSelector()
|
||||
{
|
||||
return namedBlockSelector(@":radio", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"radio"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * resetSelector()
|
||||
{
|
||||
return namedBlockSelector(@":reset", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"reset"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * submitSelector()
|
||||
{
|
||||
return namedBlockSelector(@":submit", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element.tagName isEqualToString:@"input"] && [element[@"type"] isEqualToString:@"submit"]) {
|
||||
return YES;
|
||||
}
|
||||
if ([element.tagName isEqualToString:@"button"] && [element[@"type"] isEqualToString:@"submit"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * textSelector()
|
||||
{
|
||||
return namedBlockSelector(@":text", ^BOOL(HTMLElement * _Nonnull element) {
|
||||
if ([element[@"type"] isEqualToString:@"text"]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - State
|
||||
|
||||
CSSSelector * enabledSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
|
||||
CSSSelector *candiate = anyOf(@[
|
||||
typeSelector(@"button"),
|
||||
typeSelector(@"input"),
|
||||
typeSelector(@"select"),
|
||||
typeSelector(@"textarea"),
|
||||
typeSelector(@"optgroup"),
|
||||
typeSelector(@"option"),
|
||||
typeSelector(@"menuitem"),
|
||||
typeSelector(@"fieldset"),
|
||||
]);
|
||||
return namedPseudoSelector(@"enabled", allOf(@[candiate, not(disabledSelector())]));
|
||||
}
|
||||
|
||||
CSSSelector * disabledSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
|
||||
CSSSelector *disabledAttribute = hasAttributeSelector(@"disabled");
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-fieldset-disabled
|
||||
CSSSelector *disabledFieldset = allOf(@[typeSelector(@"fieldset"), disabledAttribute]);
|
||||
CSSSelector *firstLegend = allOf(@[typeSelector(@"legend"), firstOfTypeSelector()]);
|
||||
CSSSelector *firstLegendDecendantDisabledFieldSet = allOf(@[firstLegend, descendantOfElementSelector(disabledFieldset)]);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
|
||||
CSSSelector *disabledForm = anyOf(@[
|
||||
anyOf(@[
|
||||
allOf(@[typeSelector(@"button"), disabledAttribute]),
|
||||
allOf(@[typeSelector(@"input"), disabledAttribute]),
|
||||
allOf(@[typeSelector(@"select"), disabledAttribute]),
|
||||
allOf(@[typeSelector(@"textarea"), disabledAttribute])
|
||||
]),
|
||||
allOf(@[
|
||||
descendantOfElementSelector(disabledFieldset),
|
||||
not(firstLegendDecendantDisabledFieldSet)
|
||||
])
|
||||
]);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
|
||||
CSSSelector *disabledMenuItem = allOf(@[typeSelector(@"menuitem"), disabledAttribute]);
|
||||
CSSSelector *disabledOptgroup = allOf(@[typeSelector(@"optgroup"), disabledAttribute]);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
|
||||
CSSSelector *disabledOption = allOf(@[
|
||||
typeSelector(@"option"),
|
||||
anyOf(@[
|
||||
disabledAttribute,
|
||||
descendantOfElementSelector(disabledOptgroup)])
|
||||
]);
|
||||
return namedPseudoSelector(@"disabled",
|
||||
anyOf(@[disabledOption, disabledOptgroup, disabledMenuItem, disabledForm, disabledFieldset]));
|
||||
}
|
||||
|
||||
CSSSelector * checkedSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-checked
|
||||
CSSSelector *candidate = anyOf(@[
|
||||
typeSelector(@"input"),
|
||||
typeSelector(@"option"),
|
||||
typeSelector(@"menutitem")
|
||||
]);
|
||||
CSSSelector *hasAttribute = anyOf(@[
|
||||
hasAttributeSelector(@"checked"),
|
||||
hasAttributeSelector(@"selected")
|
||||
]);
|
||||
|
||||
return namedPseudoSelector(@"checked", allOf(@[candidate, hasAttribute]));
|
||||
}
|
||||
|
||||
CSSSelector * optionalSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-optional
|
||||
CSSSelector *candidate = anyOf(@[
|
||||
typeSelector(@"input"),
|
||||
typeSelector(@"select"),
|
||||
typeSelector(@"textarea")
|
||||
]);
|
||||
CSSSelector *noAttribute = not(hasAttributeSelector(@"required"));
|
||||
|
||||
return namedPseudoSelector(@"optional", allOf(@[candidate, noAttribute]));
|
||||
}
|
||||
|
||||
CSSSelector * requiredSelector()
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#selector-required
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#concept-input-required
|
||||
CSSSelector *candidate = anyOf(@[
|
||||
typeSelector(@"input"),
|
||||
typeSelector(@"select"),
|
||||
typeSelector(@"textarea")
|
||||
]);
|
||||
CSSSelector *hasAttribute = hasAttributeSelector(@"required");
|
||||
|
||||
return namedPseudoSelector(@"required", allOf(@[candidate, hasAttribute]));
|
||||
}
|
||||
|
||||
#pragma mark - Positional
|
||||
|
||||
CSSSelector * ltSelector(NSInteger index)
|
||||
{
|
||||
NSString *name = [NSString stringWithFormat:@":lt(%ld)", (long)index];
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
return elementIndex < index;
|
||||
} else {
|
||||
return elementIndex < element.parentElement.childNodesCount - index - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * gtSelector(NSInteger index)
|
||||
{
|
||||
NSString *name = [NSString stringWithFormat:@":gt(%ld)", (long)index];
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
return elementIndex > index;
|
||||
} else {
|
||||
return elementIndex > element.parentElement.childNodesCount - index - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CSSSelector * eqSelector(NSInteger index)
|
||||
{
|
||||
NSString *name = [NSString stringWithFormat:@":eq(%ld)", (long)index];
|
||||
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
|
||||
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
|
||||
|
||||
if (index > 0) {
|
||||
return elementIndex == index;
|
||||
} else {
|
||||
return elementIndex == element.parentElement.childNodesCount - index - 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// CSSTypeSelector.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 13/05/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSSTypeSelector.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
@interface CSSTypeSelector ()
|
||||
{
|
||||
NSString *_type;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CSSTypeSelector
|
||||
|
||||
+ (instancetype)universalSelector
|
||||
{
|
||||
return [[self alloc] initWithType:@"*"];
|
||||
}
|
||||
|
||||
- (instancetype)initWithType:(NSString *)type
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_type = [type copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)acceptElement:(HTMLElement *)element
|
||||
{
|
||||
if ([_type isEqualToString:@"*"] || [_type isEqualToStringIgnoringCase:element.tagName]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return self.type;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// 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) {
|
||||
_data = [[NSMutableString alloc] initWithString:data ?: @""];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return [_data copy];
|
||||
}
|
||||
|
||||
- (void)setTextContent:(NSString *)textContent
|
||||
{
|
||||
[self setData:textContent];
|
||||
}
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
return _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);
|
||||
|
||||
[_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,40 +7,18 @@
|
||||
//
|
||||
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLCharacterData+Private.h"
|
||||
|
||||
@implementation HTMLComment
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithData:nil];
|
||||
return [self initWithData:@""];
|
||||
}
|
||||
|
||||
- (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,109 @@
|
||||
//
|
||||
// HTMLDOMTokenList.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 30/11/15.
|
||||
// Copyright © 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLDOMTokenList.h"
|
||||
#import "HTMLElement.h"
|
||||
|
||||
@interface HTMLDOMTokenList ()
|
||||
{
|
||||
HTMLElement *_element;
|
||||
NSString *_attribute;
|
||||
NSMutableOrderedSet *_tokens;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLDOMTokenList
|
||||
@synthesize element = _element;
|
||||
@synthesize attribute = _attribute;
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_element = element;
|
||||
_attribute = [attribute copy];
|
||||
_tokens = [NSMutableOrderedSet new];
|
||||
[self add:[value componentsSeparatedByString:@" "]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Access
|
||||
|
||||
- (void)updateValue
|
||||
{
|
||||
_element[_attribute] = self.stringify;
|
||||
}
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
return _tokens.count;
|
||||
}
|
||||
|
||||
- (BOOL)contains:(NSString *)token
|
||||
{
|
||||
return [_tokens containsObject:token];
|
||||
}
|
||||
|
||||
- (void)add:(NSArray<NSString *> *)tokens
|
||||
{
|
||||
for (NSString *token in tokens) {
|
||||
if (![token isEqualToString:@""]) {
|
||||
[_tokens addObject:token];
|
||||
}
|
||||
}
|
||||
[self updateValue];
|
||||
}
|
||||
|
||||
- (void)remove:(NSArray<NSString *> *)tokens
|
||||
{
|
||||
for (NSString *token in tokens) {
|
||||
[_tokens removeObject:token];
|
||||
}
|
||||
[self updateValue];
|
||||
}
|
||||
|
||||
- (BOOL)toggle:(NSString *)token
|
||||
{
|
||||
if ([_tokens containsObject:token]) {
|
||||
[_tokens removeObject:token];
|
||||
[self updateValue];
|
||||
return NO;
|
||||
} else {
|
||||
[_tokens addObject:token];
|
||||
[self updateValue];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)replaceToken:(NSString *)token withToken:(NSString *)newToken
|
||||
{
|
||||
NSUInteger index = [_tokens indexOfObject:token];
|
||||
_tokens[index] = newToken;
|
||||
[self updateValue];
|
||||
}
|
||||
|
||||
- (NSString *)objectAtIndexedSubscript:(NSUInteger)index
|
||||
{
|
||||
return _tokens[index];
|
||||
}
|
||||
|
||||
- (void)setObject:(NSString *)obj atIndexedSubscript:(NSUInteger)index
|
||||
{
|
||||
_tokens[index] = obj;
|
||||
[self updateValue];
|
||||
}
|
||||
|
||||
- (NSString *)stringify
|
||||
{
|
||||
return [_tokens.array componentsJoinedByString:@" "];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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,23 +9,19 @@
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLParser.h"
|
||||
#import "HTMLNodeIterator.h"
|
||||
#import "HTMLRange.h"
|
||||
#import "HTMLCharacterData.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
|
||||
@interface HTMLNode (Private)
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@property (nonatomic, weak) HTMLNode *parentNode;
|
||||
@end
|
||||
|
||||
@interface HTMLNodeIterator (Private)
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
withOldParent:(HTMLNode *)oldParent
|
||||
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling;
|
||||
@end
|
||||
#import "HTMLNode+Private.h"
|
||||
#import "HTMLNodeIterator+Private.h"
|
||||
#import "HTMLRange+Private.h"
|
||||
|
||||
@interface HTMLDocument ()
|
||||
{
|
||||
HTMLDocument *_inertTemplateDocument;
|
||||
NSMutableArray *_nodeIterators;
|
||||
NSMutableArray *_ranges;
|
||||
}
|
||||
@property (nonatomic, assign) HTMLDocumentReadyState readyState;
|
||||
@end
|
||||
@@ -46,6 +42,7 @@
|
||||
if (self) {
|
||||
_readyState = HTMLDocumentLoading;
|
||||
_nodeIterators = [NSMutableArray new];
|
||||
_ranges = [NSMutableArray new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -147,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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
|
||||
#import "HTMLDocumentFragment.h"
|
||||
#import "HTMLText.h"
|
||||
|
||||
@interface HTMLNode ()
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@end
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
@implementation HTMLDocumentFragment
|
||||
|
||||
@@ -32,7 +29,7 @@
|
||||
- (NSString *)textContent
|
||||
{
|
||||
NSMutableString *content = [NSMutableString string];
|
||||
for (HTMLNode *node in self.treeEnumerator) {
|
||||
for (HTMLNode *node in self.nodeIterator) {
|
||||
if (node.nodeType == HTMLNodeText) {
|
||||
[content appendString:[(HTMLText *)node data]];
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
NS_INLINE BOOL nilOrEqual(id first, id second) {
|
||||
return (first == nil) || ([first isEqual:second]);
|
||||
@@ -128,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;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
#import "HTMLParser.h"
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLText.h"
|
||||
|
||||
#import "HTMLDOMTokenList.h"
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
|
||||
@interface HTMLElement ()
|
||||
{
|
||||
@@ -26,12 +27,12 @@
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithTagName:nil];
|
||||
return [self initWithTagName:@""];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName
|
||||
{
|
||||
return [self initWithTagName:tagName attributes:nil];
|
||||
return [self initWithTagName:tagName attributes:@{}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes
|
||||
@@ -53,18 +54,35 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Attributes
|
||||
#pragma mark - Special Attributes
|
||||
|
||||
- (NSString *)elementId
|
||||
{
|
||||
return _attributes[@"id"] ?: @"";
|
||||
}
|
||||
|
||||
- (void)setElementId:(NSString *)elementId
|
||||
{
|
||||
_attributes[@"id"] = elementId;
|
||||
}
|
||||
|
||||
- (NSString *)className
|
||||
{
|
||||
return _attributes[@"class"];
|
||||
return _attributes[@"class"] ?: @"";
|
||||
}
|
||||
|
||||
- (void)setClassName:(NSString *)className
|
||||
{
|
||||
_attributes[@"class"] = className;
|
||||
}
|
||||
|
||||
- (HTMLDOMTokenList *)classList
|
||||
{
|
||||
return [[HTMLDOMTokenList alloc] initWithElement:self attribute:@"class" value:self.className];
|
||||
}
|
||||
|
||||
#pragma mark - Attributes
|
||||
|
||||
- (BOOL)hasAttribute:(NSString *)name
|
||||
{
|
||||
return _attributes[name] != nil;
|
||||
@@ -180,4 +198,9 @@
|
||||
return description;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return self.description;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#import "HTMLInputStreamReader.h"
|
||||
#import "HTMLTokenizerCharacters.h"
|
||||
#import "NSCharacterSet+HTMLKit.h"
|
||||
|
||||
#pragma mark - HTMLInputStreamReader
|
||||
|
||||
@@ -39,6 +40,7 @@
|
||||
if (self) {
|
||||
_string = [string copy];
|
||||
_scanner = [[NSScanner alloc] initWithString:string];
|
||||
_scanner.charactersToBeSkipped = nil;
|
||||
CFStringInitInlineBuffer((CFStringRef)_string, &_buffer, CFRangeMake(0, _string.length));
|
||||
}
|
||||
return self;
|
||||
@@ -63,7 +65,6 @@
|
||||
- (UTF32Char)nextInputCharacter
|
||||
{
|
||||
if (_reconsume) {
|
||||
_reconsume = NO;
|
||||
return _currentInputCharacter;
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@
|
||||
return LINE_FEED;
|
||||
}
|
||||
if (CFStringIsSurrogateLowCharacter(nextInputCharacter)) {
|
||||
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated low surrogate: 0x%X)", nextInputCharacter];
|
||||
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated low surrogate: 0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
return nextInputCharacter;
|
||||
}
|
||||
@@ -89,7 +90,7 @@
|
||||
if (CFStringIsSurrogateHighCharacter(nextInputCharacter)) {
|
||||
UniChar surrogateLow = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
|
||||
if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) {
|
||||
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated high surrogate: 0x%X)", nextInputCharacter];
|
||||
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated high surrogate: 0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
return nextInputCharacter;
|
||||
}
|
||||
@@ -99,13 +100,18 @@
|
||||
}
|
||||
|
||||
if (isControlOrUndefinedCharacter(nextInputCharacter)) {
|
||||
NSString *reason = [NSString stringWithFormat:@"A control/undefined character found: (0x%X)", nextInputCharacter];
|
||||
NSString *reason = [NSString stringWithFormat:@"A control/undefined character found: (0x%X)", (unsigned int)nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
}
|
||||
|
||||
return nextInputCharacter;
|
||||
}
|
||||
|
||||
- (UTF32Char)inputCharacterPointAtOffset:(NSUInteger)offset
|
||||
{
|
||||
return CFStringGetCharacterFromInlineBuffer(&_buffer, _location + offset);
|
||||
}
|
||||
|
||||
- (UTF32Char)consumeNextInputCharacter
|
||||
{
|
||||
if (_reconsume) {
|
||||
@@ -124,9 +130,12 @@
|
||||
{
|
||||
UTF32Char nextInputCharacter = [self nextInputCharacter];
|
||||
if (nextInputCharacter == character) {
|
||||
_location += _consume;
|
||||
_scanner.scanLocation = _location;
|
||||
_currentInputCharacter = nextInputCharacter;
|
||||
if (!_reconsume) {
|
||||
_location += _consume;
|
||||
_scanner.scanLocation = _location;
|
||||
_currentInputCharacter = nextInputCharacter;
|
||||
}
|
||||
_reconsume = NO;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
@@ -143,9 +152,20 @@
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)consumeDecimalNumber:(NSDecimal *)result
|
||||
{
|
||||
NSDecimal scanned;
|
||||
BOOL success = [_scanner scanDecimal:&scanned];
|
||||
if (success == NO) return NO;
|
||||
|
||||
*result = scanned;
|
||||
_location = _scanner.scanLocation;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)consumeHexNumber:(unsigned long long *)result
|
||||
{
|
||||
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEFabcdef"];
|
||||
NSCharacterSet *set = [NSCharacterSet HTMLHexNumberCharacterSet];
|
||||
|
||||
NSString *string = nil;
|
||||
BOOL success = [_scanner scanCharactersFromSet:set intoString:&string];
|
||||
@@ -193,6 +213,26 @@
|
||||
return consumed;
|
||||
}
|
||||
|
||||
- (NSString *)consumeCharactersInString:(NSString *)characters
|
||||
{
|
||||
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:characters];
|
||||
|
||||
if (_reconsume) {
|
||||
_scanner.scanLocation--;
|
||||
}
|
||||
|
||||
NSString *string = nil;
|
||||
BOOL success = [_scanner scanCharactersFromSet:set intoString:&string];
|
||||
if (success == NO) {
|
||||
_scanner.scanLocation++;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_reconsume = NO;
|
||||
_location = _scanner.scanLocation;
|
||||
return string;
|
||||
}
|
||||
|
||||
- (NSString *)consumeAlphanumericCharacters
|
||||
{
|
||||
NSCharacterSet *set = [NSCharacterSet alphanumericCharacterSet];
|
||||
@@ -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.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// HTMLKitExceptions.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 17/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
|
||||
NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError";
|
||||
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";
|
||||
@@ -7,24 +7,22 @@
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLKitDOMExceptions.h"
|
||||
|
||||
@interface HTMLDocument (Private)
|
||||
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
|
||||
withOldParent:(HTMLNode *)oldParent
|
||||
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling;
|
||||
@end
|
||||
#import "HTMLNodeFilter.h"
|
||||
#import "CSSSelector.h"
|
||||
#import "HTMLDocument+Private.h"
|
||||
#import "HTMLDOMUtils.h"
|
||||
|
||||
@interface HTMLNode ()
|
||||
{
|
||||
NSMutableOrderedSet *_childNodes;
|
||||
}
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@end
|
||||
|
||||
@implementation HTMLNode
|
||||
@@ -60,6 +58,11 @@
|
||||
[self.childNodes.array makeObjectsPerformSelector:@selector(setOwnerDocument:) withObject:ownerDocument];
|
||||
}
|
||||
|
||||
- (HTMLNode *)rootNode
|
||||
{
|
||||
return _parentNode == nil ? self : _parentNode.rootNode;
|
||||
}
|
||||
|
||||
- (void)setParentNode:(HTMLNode *)parentNode
|
||||
{
|
||||
_parentNode = parentNode;
|
||||
@@ -98,11 +101,39 @@
|
||||
return [_parentNode childNodeAtIndex:index + 1];
|
||||
}
|
||||
|
||||
- (HTMLElement *)previousSiblingElement
|
||||
{
|
||||
HTMLNode *node = self.previousSibling;
|
||||
while (node && node.nodeType != HTMLNodeElement) {
|
||||
node = node.previousSibling;
|
||||
}
|
||||
return node.asElement;
|
||||
}
|
||||
|
||||
- (HTMLElement *)nextSiblingElement
|
||||
{
|
||||
HTMLNode *node = self.previousSibling;
|
||||
while (node && node.nodeType != HTMLNodeElement) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return node.asElement;
|
||||
}
|
||||
|
||||
- (NSUInteger)index
|
||||
{
|
||||
return [self.parentNode indexOfChildNode:self];
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
return self.childNodesCount;
|
||||
}
|
||||
|
||||
#pragma mark - Cast
|
||||
|
||||
- (HTMLElement *)asElement
|
||||
@@ -135,16 +166,56 @@
|
||||
return self.childNodes.count;
|
||||
}
|
||||
|
||||
- (BOOL)isEmpty
|
||||
{
|
||||
return self.length == 0;
|
||||
}
|
||||
|
||||
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [self.childNodes objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (NSUInteger)childElementsCount
|
||||
{
|
||||
return [self.childNodes indexesOfObjectsPassingTest:^BOOL(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
return node.nodeType == HTMLNodeElement;
|
||||
}].count;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildNode:(HTMLNode *)node
|
||||
{
|
||||
return [self.childNodes indexOfObject:node];
|
||||
}
|
||||
|
||||
- (HTMLElement *)childElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
NSUInteger counter = 0;
|
||||
for (HTMLNode *node in self.childNodes) {
|
||||
if (node.nodeType == HTMLNodeElement) {
|
||||
if (counter == index) {
|
||||
return node.asElement;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildElement:(HTMLElement *)element
|
||||
{
|
||||
NSUInteger counter = 0;
|
||||
for (HTMLNode *node in self.childNodes) {
|
||||
if (node.nodeType == HTMLNodeElement) {
|
||||
if (node == element) {
|
||||
return counter;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
- (HTMLNode *)prependNode:(HTMLNode *)node
|
||||
{
|
||||
return [self insertNode:node beforeChildNode:self.firstChild];
|
||||
@@ -273,41 +344,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,9 +454,53 @@
|
||||
}
|
||||
|
||||
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter
|
||||
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block
|
||||
{
|
||||
return [HTMLNodeIterator iteratorWithNode:self showOptions:showOptions filter:filter];
|
||||
HTMLNodeFilterBlock *filter = [HTMLNodeFilterBlock filterWithBlock:block];
|
||||
return [[HTMLNodeIterator alloc] initWithNode:self showOptions:showOptions filter:filter];
|
||||
}
|
||||
|
||||
#pragma mark - Selectors
|
||||
|
||||
- (HTMLElement *)querySelector:(NSString *)selectorString
|
||||
{
|
||||
CSSSelector *selector = [CSSSelector selectorWithString:selectorString];
|
||||
return [self firstElementMatchingSelector:selector];
|
||||
}
|
||||
|
||||
- (NSArray<HTMLElement *> *)querySelectorAll:(NSString *)selectorString
|
||||
{
|
||||
CSSSelector *selector = [CSSSelector selectorWithString:selectorString];
|
||||
return [self elementsMatchingSelector:selector];
|
||||
}
|
||||
|
||||
- (HTMLElement *)firstElementMatchingSelector:(CSSSelector *)selector
|
||||
{
|
||||
if (selector == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
for (HTMLElement *element in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
|
||||
if ([selector acceptElement:element]) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray<HTMLElement *> *)elementsMatchingSelector:(CSSSelector *)selector
|
||||
{
|
||||
if (selector == nil) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
NSMutableArray *result = [NSMutableArray array];
|
||||
for (HTMLElement *element in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
|
||||
if ([selector acceptElement:element]) {
|
||||
[result addObject:element];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef HTMLKIT_NO_DOM_CHECKS
|
||||
@@ -551,6 +668,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 self.childNodes) {
|
||||
[copy appendNode:[child cloneNodeDeep:YES]];
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
@@ -610,12 +742,7 @@ NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSStrin
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p %@>", self.class, self, self.name];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return self.treeDescription;
|
||||
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.name];
|
||||
}
|
||||
|
||||
- (id)debugQuickLookObject
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// HTMLNodeFilter.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 05/06/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNodeFilter.h"
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLNode+Private.h"
|
||||
#import "CSSSelector.h"
|
||||
|
||||
#pragma mark - Block Filter
|
||||
|
||||
@interface HTMLNodeFilterBlock ()
|
||||
{
|
||||
BOOL (^ _block)(HTMLNode *);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLNodeFilterBlock
|
||||
|
||||
+ (instancetype)filterWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *))block
|
||||
{
|
||||
return [[self alloc] initWithBlock:block];
|
||||
}
|
||||
|
||||
- (instancetype)initWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *))block
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_block = [block copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node
|
||||
{
|
||||
if (!_block) {
|
||||
return HTMLNodeFilterSkip;
|
||||
}
|
||||
|
||||
return _block(node);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CSS Selector Filter
|
||||
|
||||
@interface HTMLSelectorNodeFilter ()
|
||||
{
|
||||
CSSSelector *_selector;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLSelectorNodeFilter
|
||||
|
||||
+ (instancetype)filterWithSelector:(CSSSelector *)selector
|
||||
{
|
||||
return [[self alloc] initWithSelector:selector];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelector:(CSSSelector *)selector
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node
|
||||
{
|
||||
if (node.nodeType != HTMLNodeElement) {
|
||||
return HTMLNodeFilterSkip;
|
||||
}
|
||||
|
||||
if ([_selector acceptElement:node.asElement]) {
|
||||
return HTMLNodeFilterAccept;
|
||||
}
|
||||
|
||||
return HTMLNodeFilterSkip;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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;
|
||||
@@ -33,15 +29,6 @@ typedef NS_ENUM(short, TraverseDirection)
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
+ (instancetype)iteratorWithNode:(HTMLNode *)node
|
||||
showOptions:(HTMLNodeFilterShowOptions)showOptions
|
||||
filter:(HTMLNodeFilterValue (^)(HTMLNode *))filter
|
||||
{
|
||||
return [[self alloc] initWithNode:node
|
||||
showOptions:showOptions
|
||||
filter:[HTMLNodeFilterBlock filterWithBlock:filter]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithNode:(HTMLNode *)node
|
||||
{
|
||||
return [self initWithNode:node filter:nil];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user