Create iOS Base application

This commit is contained in:
2020-11-30 12:18:10 +01:00
parent 48ee2c3c89
commit 3acaae03bd
193 changed files with 23510 additions and 0 deletions

92
ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,92 @@
# Created by .ignore support plugin (hsz.mobi)
### Swift template
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/

7
ios/circolapp/Podfile Normal file
View File

@@ -0,0 +1,7 @@
target 'circolapp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
platform :ios, '14.2'
# Pods for iosApp
pod 'shared', :path => '../../shared'
end

View File

@@ -0,0 +1,23 @@
PODS:
- HTMLKit (3.1.0)
- shared (1.0):
- HTMLKit (~> 3.1.0)
DEPENDENCIES:
- shared (from `../../shared`)
SPEC REPOS:
trunk:
- HTMLKit
EXTERNAL SOURCES:
shared:
:path: "../../shared"
SPEC CHECKSUMS:
HTMLKit: 4ed10a911462cbf972329ec6aaaa827208dfd70c
shared: fb3154b4def038b2b13aa88e01dd9f91bb2de9b5
PODFILE CHECKSUM: 90ac34abdd7012dde25705873407c94d0f26fa6b
COCOAPODS: 1.10.0

21
ios/circolapp/Pods/HTMLKit/LICENSE generated Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Iskandar Abudiab
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

325
ios/circolapp/Pods/HTMLKit/README.md generated Normal file
View File

@@ -0,0 +1,325 @@
# HTMLKit
![HTMLKit Logo](https://raw.githubusercontent.com/iabudiab/HTMLKit/master/HTMLKit.png)
An Objective-C framework for your everyday HTML needs.
[![Build Status](https://img.shields.io/travis/iabudiab/HTMLKit/develop.svg?style=flat)](https://travis-ci.org/iabudiab/HTMLKit)
[![codecov](https://codecov.io/gh/iabudiab/HTMLKit/branch/develop/graph/badge.svg)](https://codecov.io/gh/iabudiab/HTMLKit)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/HTMLKit.svg?style=flat)](https://cocoapods.org/pods/HTMLKit)
[![CocoaDocs](https://img.shields.io/cocoapods/metrics/doc-percent/HTMLKit.svg?style=flat)](http://cocoadocs.org/docsets/HTMLKit)
[![Platform](https://img.shields.io/cocoapods/p/HTMLKit.svg?style=flat)](http://cocoadocs.org/docsets/HTMLKit)
[![License MIT](https://img.shields.io/badge/license-MIT-4481C7.svg?style=flat)](https://opensource.org/licenses/MIT)
- [Quick Overview](#overview)
- [Installation](#installation)
- [Parsing](#parsing)
- [The DOM](#the-dom)
- [CSS3 Selectors](#css3-selectors)
# Quick Overview
HTMLKit is a [WHATWG specification](https://html.spec.whatwg.org/multipage/)-compliant framework for parsing and serializing HTML documents and document fragments for iOS and OSX. HTMLKit parses real-world HTML the same way modern web browsers would.
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
target 'MyTarget' do
pod 'HTMLKit', '~> 3.1'
end
```
Then, run the following command:
```bash
$ pod install
```
## Swift Package Manager
[Swift Package Manager](https://github.com/apple/swift-package-manager) is the package manager for the Swift programming language.
Add `HTMLKit` to your `Package.swift` dependecies:
```swift
.Package(url: "https://github.com/iabudiab/HTMLKit", majorVersion: 3)
```
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](LICENSE) file for more info.

View File

@@ -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 htmlkit_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

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,138 @@
//
// 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
{
if (!isValidIdentifierStart([self inputCharacterPointAtOffset:0],
[self inputCharacterPointAtOffset:1],
[self inputCharacterPointAtOffset:2])) {
return nil;
}
CFMutableStringRef value = CFStringCreateMutable(kCFAllocatorDefault, 0);
while (YES) {
UTF32Char codePoint = [self consumeNextInputCharacter];
if (codePoint == EOF) {
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;
}
}
if (CFStringGetLength(value) > 0) {
return (__bridge_transfer NSString *)value;
}
CFRelease(value);
return 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;
}
}
if (CFStringGetLength(value) > 0) {
return (__bridge_transfer NSString *)value;
}
CFRelease(value);
return 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_transfer 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

View File

@@ -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+Private.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 htmlkit_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

View File

@@ -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
NS_INLINE 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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+Private.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

View File

@@ -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];
}

View File

@@ -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+Private.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;
}
});
}

View File

@@ -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+Private.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

View File

@@ -0,0 +1,114 @@
//
// HTMLCharacterData.m
// HTMLKit
//
// Created by Iska on 26/11/16.
// Copyright © 2016 BrainCookie. All rights reserved.
//
#import "HTMLCharacterData.h"
#import "HTMLNode+Private.h"
#import "HTMLDocument+Private.h"
#import "HTMLKitDOMExceptions.h"
@interface HTMLCharacterData ()
{
NSMutableString *_data;
}
@end
@implementation HTMLCharacterData
@synthesize data = _data;
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type data:(NSString *)data
{
self = [super initWithName:name type:type];
if (self) {
if (data) {
_data = [[NSMutableString alloc] initWithString:data];
}
}
return self;
}
- (NSString *)data
{
if (_data == nil) {
_data = [[NSMutableString alloc] initWithString:@""];
}
return _data;
}
- (NSString *)textContent
{
return [self.data copy];
}
- (void)setTextContent:(NSString *)textContent
{
[self setData:textContent];
}
- (NSUInteger)length
{
return self.data.length;
}
#pragma mark - Data
NS_INLINE void CheckValidOffset(HTMLCharacterData *node, NSUInteger offset, NSString *cmd)
{
if (offset > node.length) {
[NSException raise:HTMLKitIndexSizeError
format:@"%@: Index Size Error, invalid index %lu for character data node %@.",
cmd, (unsigned long)offset, node];
}
}
- (void)setData:(NSString *)data
{
[self replaceDataInRange:NSMakeRange(0, self.length) withData:data];
}
- (void)appendData:(NSString *)data
{
[self replaceDataInRange:NSMakeRange(self.length, 0) withData:data];
}
- (void)insertData:(NSString *)data atOffset:(NSUInteger)offset
{
[self replaceDataInRange:NSMakeRange(offset, 0) withData:data];
}
- (void)deleteDataInRange:(NSRange)range
{
[self replaceDataInRange:range withData:@""];
}
- (void)replaceDataInRange:(NSRange)range withData:(NSString *)data
{
CheckValidOffset(self, range.location, NSStringFromSelector(_cmd));
range.length = MIN(range.length, self.length - range.location);
[(NSMutableString *)self.data replaceCharactersInRange:range withString:data];
[self.ownerDocument didRemoveCharacterDataInNode:self atOffset:range.location withLength:range.length];
[self.ownerDocument didAddCharacterDataToNode:self atOffset:range.location withLength:data.length];
}
- (NSString *)substringDataWithRange:(NSRange)range
{
return [_data substringWithRange:range];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
HTMLCharacterData *copy = [super copyWithZone:zone];
copy->_data = [_data mutableCopy];
return copy;
}
@end

View File

@@ -0,0 +1,101 @@
//
// HTMLCharacterToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLCharacterToken.h"
#import "NSString+HTMLKit.h"
@interface HTMLCharacterToken ()
{
NSMutableString *_characters;
}
@end
@implementation HTMLCharacterToken
- (instancetype)initWithString:(NSString *)string
{
self = [super init];
if (self) {
_characters = [string mutableCopy];
}
return self;
}
- (void)appendString:(NSString *)string
{
if (_characters == nil) {
_characters = [NSMutableString new];
}
[_characters appendString:string];
}
- (BOOL)isWhitespaceToken
{
return [_characters htmlkit_isHTMLWhitespaceString];
}
- (BOOL)isEmpty
{
return _characters.length == 0;
}
- (void)retainLeadingWhitespace
{
NSUInteger index = _characters.htmlkit_leadingHTMLWhitespaceLength;
if (index > 0) {
[_characters setString:[_characters substringToIndex:index]];
}
}
- (void)trimLeadingWhitespace
{
NSUInteger index = _characters.htmlkit_leadingHTMLWhitespaceLength;
if (index > 0) {
[_characters setString:[_characters substringFromIndex:index]];
}
}
- (void)trimFormIndex:(NSUInteger)index
{
[_characters setString:[_characters substringFromIndex:index]];
}
- (HTMLCharacterToken *)tokenBySplitingLeadingWhiteSpace
{
NSUInteger index = _characters.htmlkit_leadingHTMLWhitespaceLength;
if (index > 0) {
NSString *leading = [_characters substringToIndex:index];
[_characters setString:[_characters substringFromIndex:index]];
return [[HTMLCharacterToken alloc] initWithString:leading];
}
return nil;
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLCharacterToken *token = (HTMLCharacterToken *)other;
return bothNilOrEqual(self.characters, token.characters);
}
return NO;
}
- (NSUInteger)hash
{
return self.characters.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p Characters='%@'>", self.class, self, _characters];
}
@end

View File

@@ -0,0 +1,31 @@
//
// HTMLComment.m
// HTMLKit
//
// Created by Iska on 25/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLComment.h"
#import "HTMLCharacterData+Private.h"
@implementation HTMLComment
- (instancetype)init
{
return [self initWithData:@""];
}
- (instancetype)initWithData:(NSString *)data
{
return [super initWithName:@"#comment" type:HTMLNodeComment data:data];
}
#pragma mark - Description
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p <!-- %@ -->>", self.class, self, self.data];
}
@end

View File

@@ -0,0 +1,59 @@
//
// HTMLCommentToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLCommentToken.h"
@interface HTMLCommentToken ()
{
NSMutableString *_data;
}
@end
@implementation HTMLCommentToken
@synthesize data = _data;
- (instancetype)initWithData:(NSString *)data
{
self = [super init];
if (self) {
self.type = HTMLTokenTypeComment;
_data = [data mutableCopy];
}
return self;
}
- (void)appendStringToData:(NSString *)string
{
if (_data == nil) {
_data = [NSMutableString new];
}
[_data appendString:string];
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLCommentToken *token = (HTMLCommentToken *)other;
return bothNilOrEqual(self.data, token.data);
}
return NO;
}
- (NSUInteger)hash
{
return self.data.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p Data='%@'>", self.class, self, _data];
}
@end

View File

@@ -0,0 +1,84 @@
//
// HTMLDOCTYPEToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLDOCTYPEToken.h"
@interface HTMLDOCTYPEToken ()
{
NSMutableString *_name;
}
@end
@implementation HTMLDOCTYPEToken
@synthesize name = _name;
- (instancetype)init
{
return [self initWithName:nil];
}
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
self.type = HTMLTokenTypeDoctype;
_name = [name mutableCopy];
}
return self;
}
- (void)appendStringToName:(NSString *)string
{
if (_name == nil) {
_name = [NSMutableString new];
}
[_name appendString:string];
}
- (void)appendStringToPublicIdentifier:(NSString *)string
{
if (_publicIdentifier == nil) {
_publicIdentifier = [NSMutableString new];
}
[_publicIdentifier appendString:string];
}
- (void)appendStringToSystemIdentifier:(NSString *)string
{
if (_systemIdentifier == nil) {
_systemIdentifier = [NSMutableString new];
}
[_systemIdentifier appendString:string];
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLDOCTYPEToken *token = (HTMLDOCTYPEToken *)other;
return (bothNilOrEqual(self.name, token.name) &&
bothNilOrEqual(self.publicIdentifier, token.publicIdentifier) &&
bothNilOrEqual(self.systemIdentifier, token.systemIdentifier) &&
self.forceQuirks == token.forceQuirks);
}
return NO;
}
- (NSUInteger)hash
{
return self.name.hash + self.publicIdentifier.hash + self.systemIdentifier.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p Name='%@' Public='%@' System='%@' ForceQuirks='%@'>", self.class, self, _name, _publicIdentifier, _systemIdentifier, @(_forceQuirks)];
}
@end

View File

@@ -0,0 +1,109 @@
//
// HTMLDOMTokenList.m
// HTMLKit
//
// Created by Iska on 30/11/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "HTMLDOMTokenList.h"
#import "HTMLElement.h"
@interface HTMLDOMTokenList ()
{
HTMLElement *_element;
NSString *_attribute;
NSMutableOrderedSet *_tokens;
}
@end
@implementation HTMLDOMTokenList
@synthesize element = _element;
@synthesize attribute = _attribute;
#pragma mark - Init
- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value
{
self = [super init];
if (self) {
_element = element;
_attribute = [attribute copy];
_tokens = [NSMutableOrderedSet new];
[self add:[value componentsSeparatedByString:@" "]];
}
return self;
}
#pragma mark - Access
- (void)updateValue
{
_element[_attribute] = self.stringify;
}
- (NSUInteger)length
{
return _tokens.count;
}
- (BOOL)contains:(NSString *)token
{
return [_tokens containsObject:token];
}
- (void)add:(NSArray<NSString *> *)tokens
{
for (NSString *token in tokens) {
if (![token isEqualToString:@""]) {
[_tokens addObject:token];
}
}
[self updateValue];
}
- (void)remove:(NSArray<NSString *> *)tokens
{
for (NSString *token in tokens) {
[_tokens removeObject:token];
}
[self updateValue];
}
- (BOOL)toggle:(NSString *)token
{
if ([_tokens containsObject:token]) {
[_tokens removeObject:token];
[self updateValue];
return NO;
} else {
[_tokens addObject:token];
[self updateValue];
return YES;
}
}
- (void)replaceToken:(NSString *)token withToken:(NSString *)newToken
{
NSUInteger index = [_tokens indexOfObject:token];
_tokens[index] = newToken;
[self updateValue];
}
- (NSString *)objectAtIndexedSubscript:(NSUInteger)index
{
return _tokens[index];
}
- (void)setObject:(NSString *)obj atIndexedSubscript:(NSUInteger)index
{
_tokens[index] = obj;
[self updateValue];
}
- (NSString *)stringify
{
return [_tokens.array componentsJoinedByString:@" "];
}
@end

View File

@@ -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;
}

View File

@@ -0,0 +1,243 @@
//
// HTMLDocument.m
// HTMLKit
//
// Created by Iska on 25/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLDocument.h"
#import "HTMLParser.h"
#import "HTMLNodeIterator.h"
#import "HTMLRange.h"
#import "HTMLCharacterData.h"
#import "HTMLText.h"
#import "HTMLKitDOMExceptions.h"
#import "HTMLNode+Private.h"
#import "HTMLNodeIterator+Private.h"
#import "HTMLRange+Private.h"
@interface HTMLDocument ()
{
HTMLDocument *_inertTemplateDocument;
NSHashTable *_nodeIterators;
NSHashTable *_ranges;
}
@property (nonatomic, assign) HTMLDocumentReadyState readyState;
@end
@implementation HTMLDocument
#pragma mark - Init
+ (instancetype)documentWithString:(NSString *)string
{
HTMLParser *parser = [[HTMLParser alloc] initWithString:string];
return [parser parseDocument];
}
- (instancetype)init
{
self = [super initWithName:@"#document" type:HTMLNodeDocument];
if (self) {
_readyState = HTMLDocumentLoading;
_nodeIterators = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:10];
_ranges = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory capacity:10];
}
return self;
}
#pragma mark - Accessors
- (void)setOwnerDocument:(HTMLDocument *)ownerDocument
{
[self doesNotRecognizeSelector:_cmd];
}
- (void)setDocumentType:(HTMLDocumentType *)documentType
{
if (documentType == nil) {
if (self.documentType != nil) {
[self removeChildNode:self.documentType];
}
return;
}
if (self.documentType != nil) {
[self replaceChildNode:self.documentType withNode:documentType];
} else {
[self appendNode:documentType];
}
}
#pragma mark -
- (HTMLElement *)rootElement
{
for (HTMLNode *node = self.firstChild; node; node = node.nextSibling) {
if (node.nodeType == HTMLNodeElement) {
return node.asElement;
}
}
return nil;
}
- (void)setRootElement:(HTMLElement *)rootElement
{
[self replaceChildNode:self.rootElement withNode:rootElement];
}
- (HTMLElement *)documentElement
{
for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
if ([node.asElement.tagName isEqualToString:@"html"]) {
return node.asElement;
}
}
return nil;
}
- (void)setDocumentElement:(HTMLElement *)documentElement
{
[self replaceChildNode:self.documentElement withNode:documentElement];
}
- (HTMLElement *)head
{
for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
if ([node.asElement.tagName isEqualToString:@"head"]) {
return node.asElement;
}
}
return nil;
}
- (void)setHead:(HTMLElement *)head
{
[self replaceChildNode:self.head withNode:head];
}
- (HTMLElement *)body
{
for (HTMLNode *node in [self nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) {
if ([node.asElement.tagName isEqualToString:@"body"]) {
return node.asElement;
}
}
return nil;
}
- (void)setBody:(HTMLElement *)body
{
[self replaceChildNode:self.body withNode:body];
}
#pragma mark - Node Iterators
- (void)attachNodeIterator:(HTMLNodeIterator *)iterator
{
[_nodeIterators addObject:iterator];
}
- (void)detachNodeIterator:(HTMLNodeIterator *)iterator
{
// NOOP
}
#pragma mark - Ranges
- (void)attachRange:(HTMLRange *)range
{
[_ranges addObject:range];
}
- (void)detachRange:(HTMLRange *)range
{
// NOOP
}
- (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];
}
}
#pragma mark - Mutation Algorithms
- (HTMLNode *)adoptNode:(HTMLNode *)node
{
if (node == nil) {
return nil;
}
if (node.nodeType == HTMLNodeDocument) {
[NSException raise:HTMLKitNotSupportedError
format:@"%@: Not Fount Error, adopting a document node. The operation is not supported.", NSStringFromSelector(_cmd)];
}
[node.parentNode removeChildNode:node];
node.ownerDocument = self;
return node;
}
#pragma mark - Template
- (HTMLDocument *)associatedInertTemplateDocument
{
if (_inertTemplateDocument == nil) {
_inertTemplateDocument = [HTMLDocument new];
_inertTemplateDocument.readyState = HTMLDocumentComplete;
}
return _inertTemplateDocument;
}
#pragma mark - Description
- (id)debugQuickLookObject
{
return [[NSAttributedString alloc] initWithString:self.innerHTML];
}
@end

View File

@@ -0,0 +1,46 @@
//
// HTMLDocumentFragment.m
// HTMLKit
//
// Created by Iska on 12/04/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLDocumentFragment.h"
#import "HTMLText.h"
#import "HTMLNode+Private.h"
@implementation HTMLDocumentFragment
- (instancetype)init
{
return [self initWithDocument:nil];
}
- (instancetype)initWithDocument:(HTMLDocument *)document
{
self = [super initWithName:@"#document-fragment" type:HTMLNodeDocumentFragment];
if (self) {
self.ownerDocument = document;
}
return self;
}
- (NSString *)textContent
{
NSMutableString *content = [NSMutableString string];
for (HTMLNode *node in self.nodeIterator) {
if (node.nodeType == HTMLNodeText) {
[content appendString:[(HTMLText *)node data]];
}
}
return content;
}
- (void)setTextContent:(NSString *)textContent
{
HTMLText *node = [[HTMLText alloc] initWithData:textContent];
[self replaceAllChildNodesWithNode:node];
}
@end

View File

@@ -0,0 +1,155 @@
//
// HTMLDocumentType.m
// HTMLKit
//
// Created by Iska on 25/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLDocumentType.h"
#import "NSString+Private.h"
#import "HTMLNode+Private.h"
NS_INLINE BOOL nilOrEqual(id first, id second) {
return (first == nil) || ([first isEqual:second]);
}
@interface HTMLDocumentType ()
{
NSString *_publicIdentifier;
NSString *_systemIdentifier;
}
@end
@implementation HTMLDocumentType
- (instancetype)init
{
return [self initWithName:@"html" publicIdentifier:nil systemIdentifier:nil];
}
- (instancetype)initWithName:(NSString *)name
publicIdentifier:(NSString *)publicIdentifier
systemIdentifier:(NSString *)systemIdentifier
{
self = [super initWithName:name type:HTMLNodeDocumentType];
if (self) {
_publicIdentifier = [publicIdentifier copy];
_systemIdentifier = [systemIdentifier copy];
}
return self;
}
- (NSString *)publicIdentifier
{
return _publicIdentifier ?: @"";
}
- (NSString *)systemIdentifier
{
return _systemIdentifier ?: @"";
}
- (BOOL)isValid
{
if (![self.name isEqualToString:@"html"]) {
return NO;
}
if ([_publicIdentifier isEqualToString:@"-//W3C//DTD HTML 4.0//EN"] &&
nilOrEqual(_systemIdentifier, @"http://www.w3.org/TR/REC-html40/strict.dtd")) {
return YES;
}
if ([_publicIdentifier isEqualToString:@"-//W3C//DTD HTML 4.01//EN"] &&
nilOrEqual(_systemIdentifier, @"http://www.w3.org/TR/html4/strict.dtd")) {
return YES;
}
if ([_publicIdentifier isEqualToString:@"-//W3C//DTD XHTML 1.0 Strict//EN"] &&
nilOrEqual(_systemIdentifier, @"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd")) {
return YES;
}
if ([_publicIdentifier isEqualToString:@"-//W3C//DTD XHTML 1.1//EN"] &&
nilOrEqual(_systemIdentifier, @"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd")) {
return YES;
}
if (_publicIdentifier != nil) {
return NO;
}
if (_systemIdentifier && ![_systemIdentifier isEqualToString:@"about:legacy-compat"]) {
return NO;
}
return YES;
}
- (HTMLQuirksMode)quirksMode
{
if (![self.name isEqualToString:@"html"]) {
return HTMLQuirksModeQuirks;
}
if ([_publicIdentifier isEqualToStringIgnoringCase:@"-//W3O//DTD W3 HTML Strict 3.0//EN//"] ||
[_publicIdentifier isEqualToStringIgnoringCase:@"-/W3C/DTD HTML 4.0 Transitional/EN"] ||
[_publicIdentifier isEqualToStringIgnoringCase:@"HTML"]) {
return HTMLQuirksModeQuirks;
}
if ([_publicIdentifier isEqualToStringIgnoringCase:@"http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"]) {
return HTMLQuirksModeQuirks;
}
if (QuirksModePrefixMatch(_publicIdentifier)) {
return HTMLQuirksModeQuirks;
}
if (_systemIdentifier == nil) {
if ([_publicIdentifier hasPrefixIgnoringCase:@"-//W3C//DTD HTML 4.01 Frameset//"] ||
[_publicIdentifier hasPrefixIgnoringCase:@"-//W3C//DTD HTML 4.01 Transitional//"]) {
return HTMLQuirksModeQuirks;
}
}
if ([_publicIdentifier hasPrefixIgnoringCase:@"-//W3C//DTD XHTML 1.0 Frameset//"] ||
[_publicIdentifier hasPrefixIgnoringCase:@"-//W3C//DTD XHTML 1.0 Transitional//"]) {
return HTMLQuirksModeLimitedQuirks;
}
if (_systemIdentifier != nil) {
if ([_publicIdentifier hasPrefixIgnoringCase:@"-//W3C//DTD HTML 4.01 Frameset//"] ||
[_publicIdentifier hasPrefixIgnoringCase:@"-//W3C//DTD HTML 4.01 Transitional//"]) {
return HTMLQuirksModeLimitedQuirks;
}
}
return HTMLQuirksModeNoQuirks;
}
- (NSUInteger)length
{
return 0;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
HTMLDocumentType *copy = [super copyWithZone:zone];
copy->_publicIdentifier = [_publicIdentifier copy];
copy->_systemIdentifier = [_systemIdentifier copy];
return copy;
}
#pragma mark - Description
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p <!DOCTYPE %@ \"%@\" \"%@\">>",
self.class, self, self.name, self.publicIdentifier, self.systemIdentifier];
}
@end

View File

@@ -0,0 +1,37 @@
//
// HTMLEOFToken.m
// HTMLKit
//
// Created by Iska on 15/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLEOFToken.h"
@implementation HTMLEOFToken
+ (instancetype)token
{
static dispatch_once_t onceToken;
static HTMLEOFToken *singleton = nil;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.type = HTMLTokenTypeEOF;
}
return self;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p EOF>", self.class, self];
}
@end

View File

@@ -0,0 +1,179 @@
//
// HTMLElement.m
// HTMLKit
//
// Created by Iska on 05/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLElement.h"
#import "HTMLParser.h"
#import "HTMLDocument.h"
#import "HTMLText.h"
#import "HTMLDOMTokenList.h"
#import "HTMLOrderedDictionary.h"
#import "NSString+Private.h"
#import "HTMLNode+Private.h"
@interface HTMLElement ()
{
HTMLOrderedDictionary *_attributes;
}
@end
@implementation HTMLElement
#pragma mark - Init
- (instancetype)init
{
return [self initWithTagName:@""];
}
- (instancetype)initWithTagName:(NSString *)tagName
{
return [self initWithTagName:tagName attributes:nil];
}
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes
{
return [self initWithTagName:tagName namespace:HTMLNamespaceHTML attributes:attributes];
}
- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(NSDictionary *)attributes
{
self = [super initWithName:tagName type:HTMLNodeElement];
if (self) {
_tagName = [tagName copy];
_attributes = nil;
if (attributes != nil) {
_attributes = [HTMLOrderedDictionary new];
[_attributes addEntriesFromDictionary:attributes];
}
_htmlNamespace = htmlNamespace;
}
return self;
}
#pragma mark - Special Attributes
- (NSMutableDictionary<NSString *,NSString *> *)attributes
{
if (_attributes == nil) {
_attributes = [HTMLOrderedDictionary new];
}
return _attributes;
}
- (NSString *)elementId
{
return self.attributes[@"id"] ?: @"";
}
- (void)setElementId:(NSString *)elementId
{
self.attributes[@"id"] = elementId;
}
- (NSString *)className
{
return self.attributes[@"class"] ?: @"";
}
- (void)setClassName:(NSString *)className
{
self.attributes[@"class"] = className;
}
- (HTMLDOMTokenList *)classList
{
return [[HTMLDOMTokenList alloc] initWithElement:self attribute:@"class" value:self.className];
}
#pragma mark - Attributes
- (BOOL)hasAttribute:(NSString *)name
{
return self.attributes[name] != nil;
}
- (NSString *)objectForKeyedSubscript:(NSString *)name;
{
return self.attributes[name];
}
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute
{
self.attributes[attribute] = value;
}
- (void)removeAttribute:(NSString *)name
{
[self.attributes removeObjectForKey:name];
}
- (NSString *)textContent
{
NSMutableString *content = [NSMutableString string];
for (HTMLNode *node in self.nodeIterator) {
if (node.nodeType == HTMLNodeText) {
[content appendString:[(HTMLText *)node data]];
}
}
return content;
}
- (void)setTextContent:(NSString *)textContent
{
HTMLText *node = [[HTMLText alloc] initWithData:textContent];
[self replaceAllChildNodesWithNode:node];
}
- (void)setInnerHTML:(NSString *)innerHTML
{
HTMLParser *parser = [[HTMLParser alloc] initWithString:innerHTML];
NSArray *fragmentNodes = [parser parseFragmentWithContextElement:self];
[self removeAllChildNodes];
[self appendNodes:fragmentNodes];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
HTMLElement *copy = [super copyWithZone:zone];
copy->_tagName = [_tagName copy];
copy->_attributes = [_attributes mutableCopy];
copy->_htmlNamespace = _htmlNamespace;
return copy;
}
#pragma mark - Description
- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: %p <", self.class, self];
if (self.htmlNamespace == HTMLNamespaceMathML) {
[description appendString:@"math "];
} else if (self.htmlNamespace == HTMLNamespaceSVG) {
[description appendString:@"svg "];
}
[description appendString:self.tagName];
[self.attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[description appendFormat:@" %@=\"%@\"", key, obj];
}];
[description appendString:@">>"];
return description;
}
- (NSString *)debugDescription
{
return self.description;
}
@end

View File

@@ -0,0 +1,283 @@
//
// HTMLInputStreamReader.m
// HTMLKit
//
// Created by Iska on 15/09/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLInputStreamReader.h"
#import "HTMLTokenizerCharacters.h"
#import "NSCharacterSet+HTMLKit.h"
#pragma mark - HTMLInputStreamReader
@interface HTMLInputStreamReader ()
{
NSString *_string;
NSScanner *_scanner;
CFStringInlineBuffer _buffer;
NSUInteger _location;
NSUInteger _mark;
UTF32Char _currentInputCharacter;
NSUInteger _consume;
HTMLStreamReaderErrorCallback _errorCallback;
BOOL _reconsume;
}
@end
@implementation HTMLInputStreamReader
@synthesize string = _string;
@synthesize currentLocation = _location;
@synthesize errorCallback = _errorCallback;
#pragma mark - Lifecycle
- (id)initWithString:(NSString *)string
{
self = [super init];
if (self) {
_string = [string copy];
_scanner = [[NSScanner alloc] initWithString:string];
_scanner.charactersToBeSkipped = nil;
CFStringInitInlineBuffer((CFStringRef)_string, &_buffer, CFRangeMake(0, _string.length));
}
return self;
}
#pragma mark - Errors
- (void)emitParseError:(NSString *)code details:(NSString *)details
{
if (self.errorCallback) {
self.errorCallback(code, details);
}
}
#pragma mark - Stream Processing
- (UTF32Char)currentInputCharacter
{
return _currentInputCharacter;
}
- (UTF32Char)nextInputCharacter
{
if (_reconsume) {
return _currentInputCharacter;
}
_consume = 0;
UTF32Char nextInputCharacter = CFStringGetCharacterFromInlineBuffer(&_buffer, _location);
if (nextInputCharacter == 0 && _location >= _string.length) return EOF;
_consume = 1;
if (nextInputCharacter == CARRIAGE_RETURN) {
UniChar next = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
if (next == LINE_FEED) {
_consume = 2;
}
return LINE_FEED;
}
if (CFStringIsSurrogateLowCharacter(nextInputCharacter)) {
NSString *details = [NSString stringWithFormat:@"Non-Unicode character found (an isolated low surrogate: 0x%X)", (unsigned int)nextInputCharacter];
[self emitParseError:@"surrogate-in-input-stream" details:details];
return nextInputCharacter;
}
if (CFStringIsSurrogateHighCharacter(nextInputCharacter)) {
UniChar surrogateLow = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) {
NSString *details = [NSString stringWithFormat:@"Non-Unicode character found (an isolated high surrogate: 0x%X)", (unsigned int)nextInputCharacter];
[self emitParseError:@"surrogate-in-input-stream" details:details];
return nextInputCharacter;
}
_consume = 2;
nextInputCharacter = CFStringGetLongCharacterForSurrogatePair(nextInputCharacter, surrogateLow);
}
if (isControlCharacter(nextInputCharacter)) {
NSString *details = [NSString stringWithFormat:@"A control character found: (0x%X)", (unsigned int)nextInputCharacter];
[self emitParseError:@"control-character-in-input-stream" details:details];
}
if (isNoncharacter(nextInputCharacter)) {
NSString *details = [NSString stringWithFormat:@"A noncharacter found: (0x%X)", (unsigned int)nextInputCharacter];
[self emitParseError:@"noncharacter-in-input-stream" details:details];
}
return nextInputCharacter;
}
- (UTF32Char)inputCharacterPointAtOffset:(NSUInteger)offset
{
return CFStringGetCharacterFromInlineBuffer(&_buffer, _location + offset);
}
- (UTF32Char)consumeNextInputCharacter
{
if (_reconsume) {
_reconsume = NO;
return _currentInputCharacter;
}
UTF32Char nextInputCharacter = [self nextInputCharacter];
_location += _consume;
_scanner.scanLocation = _location;
_currentInputCharacter = nextInputCharacter;
return nextInputCharacter;
}
- (BOOL)consumeCharacter:(UTF32Char)character
{
UTF32Char nextInputCharacter = [self nextInputCharacter];
if (nextInputCharacter == character) {
if (!_reconsume) {
_location += _consume;
_scanner.scanLocation = _location;
_currentInputCharacter = nextInputCharacter;
}
_reconsume = NO;
return YES;
}
return NO;
}
- (BOOL)consumeNumber:(unsigned long long *)result
{
unsigned long long scanned;
BOOL success = [_scanner scanUnsignedLongLong:&scanned];
if (success == NO) return NO;
*result = scanned;
_location = _scanner.scanLocation;
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 htmlkit_HTMLHexNumberCharacterSet];
NSString *string = nil;
BOOL success = [_scanner scanCharactersFromSet:set intoString:&string];
if (success == NO) return NO;
unsigned long long scanned = strtoull(string.UTF8String, NULL, 16);
*result = scanned;
_location = _scanner.scanLocation;
return success;
}
- (BOOL)consumeString:(NSString *)string caseSensitive:(BOOL)caseSensitive
{
_scanner.caseSensitive = caseSensitive;
BOOL success = [_scanner scanString:string intoString:nil];
_location = _scanner.scanLocation;
return success;
}
- (NSString *)consumeCharactersUpToCharactersInString:(NSString *)characters
{
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:characters];
NSMutableString *consumed = [NSMutableString string];
while (YES) {
UTF32Char nextCharacter = [self consumeNextInputCharacter];
if ([set longCharacterIsMember:nextCharacter] || nextCharacter == EOF) {
break;
}
[consumed appendString:StringFromUTF32Char(nextCharacter)];
}
[self unconsumeCurrentInputCharacter];
return consumed.length > 0 ? consumed : nil;
}
- (NSString *)consumeCharactersUpToString:(NSString *)string
{
NSString *consumed;
[_scanner scanUpToString:string intoString:&consumed];
_location = _scanner.scanLocation;
consumed = [consumed stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\r"];
consumed = [consumed stringByReplacingOccurrencesOfString:@"\r" withString:@"\n"];
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];
NSString *consumed = nil;
[_scanner scanCharactersFromSet:set intoString:&consumed];
_location = _scanner.scanLocation;
return consumed;
}
- (void)reconsumeCurrentInputCharacter
{
_reconsume = YES;
}
- (void)unconsumeCurrentInputCharacter
{
_location -= _consume;
_scanner.scanLocation = _location;
_consume = 0;
}
- (void)markCurrentLocation
{
_mark = _location;
}
- (void)rewindToMarkedLocation
{
_location = _mark;
_scanner.scanLocation = _mark;
_consume = 0;
}
- (void)reset
{
_mark = 0;
_location = 0;
_scanner.scanLocation = 0;
_consume = 0;
}
@end

View File

@@ -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";

View File

@@ -0,0 +1,158 @@
//
// HTMLListOfActiveFormattingElements.m
// HTMLKit
//
// Created by Iska on 22/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLListOfActiveFormattingElements.h"
#import "HTMLMarker.h"
@interface HTMLListOfActiveFormattingElements ()
{
NSMutableArray *_list;
}
@end
@implementation HTMLListOfActiveFormattingElements
- (instancetype)init
{
self = [super init];
if (self) {
_list = [NSMutableArray new];
}
return self;
}
#pragma mark - Access
- (id)objectAtIndexedSubscript:(NSUInteger)index;
{
return [_list objectAtIndex:index];
}
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
{
[_list setObject:obj atIndexedSubscript:idx];
}
- (NSUInteger)indexOfElement:(id)node
{
return [_list indexOfObject:node];
}
- (void)addElement:(HTMLElement *)element
{
NSUInteger existing = 0;
for (HTMLElement *node in _list.reverseObjectEnumerator) {
if ([node isEqual:[HTMLMarker marker]]) {
break;
}
if (node.htmlNamespace == element.htmlNamespace &&
[node.tagName isEqualToString:element.tagName] &&
[node.attributes isEqual:element.attributes]) {
existing++;
}
if (existing == 3) {
[_list removeObject:node];
break;
}
}
[_list addObject:element];
}
- (void)removeElement:(id)element
{
[_list removeObject:element];
}
- (BOOL)containsElement:(id)element
{
return [_list containsObject:element];
}
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index
{
if (index > _list.count) {
index = _list.count;
}
[_list insertObject:element atIndex:index];
}
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element
{
[_list replaceObjectAtIndex:index withObject:element];
}
- (id)lastEntry
{
return _list.lastObject;
}
#pragma mark - Acrions
- (void)addMarker
{
[_list addObject:[HTMLMarker marker]];
}
- (void)clearUptoLastMarker
{
while (_list.lastObject && _list.lastObject != [HTMLMarker marker]) {
[_list removeLastObject];
}
[_list removeLastObject];
}
- (HTMLElement *)formattingElementWithTagName:(NSString *)tagName
{
for (HTMLElement *element in _list.reverseObjectEnumerator) {
if ([element isEqual:[HTMLMarker marker]]) return nil;
if ([element.tagName isEqualToString:tagName]) {
return element;
}
}
return nil;
}
#pragma mark - Count
- (NSUInteger)count
{
return _list.count;
}
- (BOOL)isEmpty
{
return _list.count == 0;
}
#pragma mark - Enumeraiton
- (NSEnumerator *)enumerator
{
return _list.objectEnumerator;
}
- (NSEnumerator *)reverseObjectEnumerator
{
return _list.reverseObjectEnumerator;
}
#pragma mark - NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len
{
return [_list countByEnumeratingWithState:state objects:buffer count:len];
}
#pragma mark - Description
- (NSString *)description
{
return _list.description;
}
@end

View File

@@ -0,0 +1,23 @@
//
// HTMLMarker.m
// HTMLKit
//
// Created by Iska on 02/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLMarker.h"
@implementation HTMLMarker
+ (instancetype)marker
{
static dispatch_once_t onceToken;
static HTMLMarker *singleton = nil;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
@end

View File

@@ -0,0 +1,790 @@
//
// HTMLNode.m
// HTMLKit
//
// Created by Iska on 24/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLNode.h"
#import "HTMLNode+Private.h"
#import "HTMLDocument.h"
#import "HTMLDocumentType.h"
#import "HTMLElement.h"
#import "HTMLText.h"
#import "HTMLComment.h"
#import "HTMLKitDOMExceptions.h"
#import "HTMLNodeFilter.h"
#import "CSSSelector.h"
#import "HTMLDocument+Private.h"
#import "HTMLDOMUtils.h"
#import "HTMLSerializer.h"
NSString * const ValidationNodePreInsertion = @"-ensurePreInsertionValidityOfNode:beforeChildNode:";
NSString * const ValidationNodeReplacement = @"-ensureReplacementValidityOfChildNode:withNode:";
NSString * const RemoveChildNode = @"-removeChildNode:";
@interface HTMLNode ()
{
NSMutableOrderedSet *_childNodes;
}
@end
@implementation HTMLNode
@synthesize ownerDocument = _ownerDocument;
#pragma mark - Init
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type
{
self = [super init];
if (self) {
_name = name;
_nodeType = type;
_childNodes = nil;
}
return self;
}
#pragma mark - Properties
- (NSOrderedSet<HTMLNode *> *)childNodes
{
if (_childNodes == nil) {
_childNodes = [NSMutableOrderedSet new];
}
return _childNodes;
}
- (HTMLDocument *)ownerDocument
{
if (_nodeType == HTMLNodeDocument) {
return (HTMLDocument *)self;
} else {
return _ownerDocument;
}
}
- (void)setOwnerDocument:(HTMLDocument *)ownerDocument
{
_ownerDocument = ownerDocument;
for (HTMLNode *child in _childNodes) {
[child setOwnerDocument:ownerDocument];
}
}
- (HTMLNode *)rootNode
{
return _parentNode == nil ? self : _parentNode.rootNode;
}
- (void)setParentNode:(HTMLNode *)parentNode
{
_parentNode = parentNode;
}
- (HTMLElement *)parentElement
{
return _parentNode.nodeType == HTMLNodeElement ? (HTMLElement *)_parentNode : nil;
}
- (HTMLNode *)firstChild
{
return _childNodes.firstObject;
}
- (HTMLNode *)lastChild
{
return _childNodes.lastObject;
}
- (HTMLNode *)previousSibling
{
NSUInteger index = [_parentNode indexOfChildNode:self];
if (index <= 0) {
return nil;
}
return [_parentNode childNodeAtIndex:index - 1];
}
- (HTMLNode *)nextSibling
{
NSUInteger index = [_parentNode indexOfChildNode:self];
if (index >= _parentNode.childNodesCount - 1) {
return nil;
}
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.nextSibling;
while (node && node.nodeType != HTMLNodeElement) {
node = node.nextSibling;
}
return node.asElement;
}
- (NSUInteger)index
{
return [_parentNode indexOfChildNode:self];
}
- (NSString *)textContent
{
return nil;
}
- (NSUInteger)length
{
return self.childNodesCount;
}
#pragma mark - Cast
- (HTMLElement *)asElement
{
return (HTMLElement *)self;
}
- (HTMLText *)asText
{
return (HTMLText *)self;
}
- (HTMLComment *)asComment
{
return (HTMLComment *)self;
}
- (HTMLDocumentType *)asDocumentType
{
return (HTMLDocumentType *)self;
}
#pragma mark - Child Nodes
- (BOOL)hasChildNodes
{
return _childNodes.count > 0;
}
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type
{
if (_childNodes == nil) {
return NO;
}
NSUInteger index = [_childNodes indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if ([(HTMLNode *)obj nodeType] == type) {
*stop = YES;
return YES;
}
return NO;
}];
return index != NSNotFound;
}
- (NSUInteger)childNodesCount
{
return _childNodes.count;
}
- (BOOL)isEmpty
{
return self.length == 0;
}
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index
{
return [_childNodes objectAtIndex:index];
}
- (NSUInteger)childElementsCount
{
return [_childNodes indexesOfObjectsPassingTest:^BOOL(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
return node.nodeType == HTMLNodeElement;
}].count;
}
- (NSUInteger)indexOfChildNode:(HTMLNode *)node
{
return [_childNodes indexOfObject:node];
}
- (HTMLElement *)childElementAtIndex:(NSUInteger)index
{
NSUInteger counter = 0;
for (HTMLNode *node in _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 _childNodes) {
if (node.nodeType == HTMLNodeElement) {
if (node == element) {
return counter;
}
counter++;
}
}
return NSNotFound;
}
- (HTMLNode *)prependNode:(HTMLNode *)node
{
return [self insertNode:node beforeChildNode:self.firstChild];
}
- (void)prependNodes:(NSArray *)nodes
{
for (id node in nodes.reverseObjectEnumerator) {
[self insertNode:node beforeChildNode:self.firstChild];
}
}
- (HTMLNode *)appendNode:(HTMLNode *)node
{
return [self insertNode:node beforeChildNode:nil];
}
- (void)appendNodes:(NSArray *)nodes
{
for (id node in nodes) {
[self insertNode:node beforeChildNode:nil];
}
}
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
{
#ifndef HTMLKIT_NO_DOM_CHECKS
[self ensurePreInsertionValidityOfNode:node beforeChildNode:child];
#endif
[self.ownerDocument adoptNode:node];
NSArray *nodes = node.nodeType == HTMLNodeDocumentFragment ? [NSArray arrayWithArray:node.childNodes.array] : @[node];
NSUInteger index = [self indexOfChildNode:child];
if (index != NSNotFound) {
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, nodes.count)];
[(NSMutableOrderedSet *)self.childNodes insertObjects:nodes atIndexes:indexes];
} else {
[(NSMutableOrderedSet *)self.childNodes addObjectsFromArray:nodes];
}
if (node.nodeType == HTMLNodeDocumentFragment) {
[node removeAllChildNodes];
}
for (HTMLNode *node in nodes) {
[node setParentNode:self];
}
return node;
}
- (HTMLNode *)replaceChildNode:(HTMLNode *)child withNode:(HTMLNode *)node
{
#ifndef HTMLKIT_NO_DOM_CHECKS
[self ensureReplacementValidityOfChildNode:child withNode:node];
#endif
[self insertNode:node beforeChildNode:child];
[child removeFromParentNode];
return child;
}
- (void)replaceAllChildNodesWithNode:(HTMLNode *)node
{
[self removeAllChildNodes];
if (node != nil) {
[self.ownerDocument adoptNode:node];
[self insertNode:node beforeChildNode:nil];
}
}
- (void)removeFromParentNode
{
[_parentNode removeChildNode:self];
}
- (HTMLNode *)removeChildNode:(HTMLNode *)child
{
if (child.parentNode != self) {
[NSException raise:HTMLKitNotFoundError
format:@"%@: Not Fount Error, removing non-child node %@. The object can not be found here.",
RemoveChildNode, child];
}
HTMLNode *oldNode = child;
HTMLNode *oldParent = child.parentNode;
HTMLNode *oldPreviousSibling = child.previousSibling;
[(NSMutableOrderedSet *)self.childNodes removeObject:child];
child.parentNode = nil;
[self.ownerDocument runRemovingStepsForNode:oldNode
withOldParent:oldParent
andOldPreviousSibling:oldPreviousSibling];
return child;
}
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index
{
HTMLNode *node = [self childNodeAtIndex:index];
return [self removeChildNode:node];
}
- (void)reparentChildNodesIntoNode:(HTMLNode *)node
{
for (HTMLNode *child in _childNodes) {
[node appendNode:child];
}
[(NSMutableOrderedSet *)_childNodes removeAllObjects];
}
- (void)removeAllChildNodes
{
for (HTMLNode *child in _childNodes) {
[child setParentNode:nil];
}
[(NSMutableOrderedSet *)_childNodes removeAllObjects];
}
- (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)otherNode
{
if (otherNode == nil) {
return HTMLDocumentPositionDisconnected;
}
if (self == otherNode) {
return HTMLDocumentPositionEquivalent;
}
if (self.ownerDocument != otherNode.ownerDocument) {
return (HTMLDocumentPositionDisconnected | HTMLDocumentPositionImplementationSpecific |
self.hash < otherNode.hash ? HTMLDocumentPositionPreceding : HTMLDocumentPositionFollowing);
}
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 HTMLDocumentPositionPreceding;
}
}
return HTMLDocumentPositionFollowing;
}
}
if (ancestors1.count < ancestors2.count) {
return HTMLDocumentPositionContains | HTMLDocumentPositionPreceding;
} else {
return HTMLDocumentPositionContainedBy | HTMLDocumentPositionFollowing;
}
}
- (BOOL)isDescendantOfNode:(HTMLNode *)otherNode
{
if (otherNode == nil) {
return NO;
}
if (self.ownerDocument != otherNode.ownerDocument) {
return NO;
}
if (!otherNode.hasChildNodes) {
return NO;
}
if (otherNode.nodeType == HTMLNodeDocument) {
return self.nodeType != HTMLNodeDocument && self.ownerDocument == otherNode;
}
for (HTMLNode *parentNode = _parentNode; parentNode; parentNode = parentNode.parentNode) {
if (parentNode == otherNode) {
return YES;
}
}
return NO;
}
- (BOOL)containsNode:(HTMLNode *)otherNode
{
return self == otherNode || [otherNode isDescendantOfNode:self];
}
#pragma mark - Enumeration
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block
{
if (block == nil) {
return;
}
[_childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
block(obj, idx, stop);
}];
}
- (void)enumerateChildElementsUsingBlock:(void (^)(HTMLElement *element, NSUInteger idx, BOOL *stop))block
{
if (block == nil) {
return;
}
[_childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[HTMLElement class]]) {
block([obj asElement], idx, stop);
}
}];
}
- (HTMLNodeIterator *)nodeIterator
{
return [self nodeIteratorWithShowOptions:HTMLNodeFilterShowAll filter:nil];
}
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
filter:(id<HTMLNodeFilter>)filter
{
return [[HTMLNodeIterator alloc] initWithNode:self showOptions:showOptions filter:filter];
}
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block
{
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
#pragma mark - Validity Checks
NS_INLINE void CheckParentValid(HTMLNode *parent, NSString *cmd)
{
if (parent.nodeType != HTMLNodeDocument &&
parent.nodeType != HTMLNodeDocumentFragment &&
parent.nodeType != HTMLNodeElement) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting into %@ is not allowed. The operation would yield an incorrect node tree.",
cmd, parent.name];
}
}
NS_INLINE void CheckChildsParent(HTMLNode *parent, HTMLNode *child, NSString *cmd)
{
if (child != nil &&
child.parentNode != parent) {
[NSException raise:HTMLKitNotFoundError
format:@"%@: Not Fount Error, insering before non-child node %@. The object can not be found here.",
cmd, child];
}
}
NS_INLINE void CheckInsertedNodeValid(HTMLNode *node, NSString *cmd)
{
if (node.nodeType != HTMLNodeDocumentFragment &&
node.nodeType != HTMLNodeDocumentType &&
node.nodeType != HTMLNodeElement &&
node.nodeType != HTMLNodeText &&
node.nodeType != HTMLNodeComment) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting a %@ is not allowed. The operation would yield an incorrect node tree.",
cmd, node.name];
}
}
NS_INLINE void CheckInvalidCombination(HTMLNode *parent, HTMLNode *node, NSString *cmd)
{
if (node.nodeType == HTMLNodeText && parent.nodeType == HTMLNodeDocument) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting a text node %@ into docuement is not allowed. The operation would yield an incorrect node tree.",
cmd, parent.name];
}
if (node.nodeType == HTMLNodeDocumentType && parent.nodeType != HTMLNodeDocument) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting a doctype %@ into a non-document node is not allowed. The operation would yield an incorrect node tree.",
cmd, parent.name];
}
}
- (void)ensurePreInsertionValidityOfNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
{
CheckParentValid(self, ValidationNodePreInsertion);
CheckChildsParent(self, child, ValidationNodePreInsertion);
CheckInsertedNodeValid(node, ValidationNodePreInsertion);
CheckInvalidCombination(self, node, ValidationNodePreInsertion);
void (^ hierarchyError)(void) = ^{
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, inserting (%@) into (%@). The operation would yield an incorrect node tree.",
ValidationNodePreInsertion, self, node];
};
if (self.nodeType == HTMLNodeDocument) {
switch (node.nodeType) {
case HTMLNodeDocumentFragment:
if (node.childNodesCount > 1 ||
[node hasChildNodeOfType:HTMLNodeText]) {
hierarchyError();
} else if (node.childNodesCount == 1) {
if ([self hasChildNodeOfType:HTMLNodeElement] ||
child.nodeType == HTMLNodeDocumentType ||
child.nextSibling.nodeType == HTMLNodeDocumentType) {
hierarchyError();
}
}
break;
case HTMLNodeElement:
if ([self hasChildNodeOfType:HTMLNodeElement] ||
child.nodeType == HTMLNodeDocumentType ||
(child != nil && child.nextSibling.nodeType == HTMLNodeDocumentType)) {
hierarchyError();
}
break;
case HTMLNodeDocumentType:
if ([self hasChildNodeOfType:HTMLNodeDocumentType] ||
child.previousSibling != nil ||
(child == nil && [self hasChildNodeOfType:HTMLNodeElement])) {
hierarchyError();
}
break;
default:
break;
}
}
}
- (void)ensureReplacementValidityOfChildNode:(HTMLNode *)child withNode:(HTMLNode *)node
{
CheckParentValid(self, ValidationNodeReplacement);
CheckChildsParent(self, child, ValidationNodeReplacement);
CheckInsertedNodeValid(node, ValidationNodeReplacement);
CheckInvalidCombination(self, node, ValidationNodeReplacement);
void (^ hierarchyError)(void) = ^{
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error. The operation would yield an incorrect node tree.",
ValidationNodeReplacement];
};
void (^ checkParentHasAnotherChildOfType)(HTMLNodeType) = ^ void (HTMLNodeType type) {
[self enumerateChildNodesUsingBlock:^(HTMLNode *node, NSUInteger idx, BOOL *stop) {
if (node.nodeType == type && node != child) {
*stop = YES;
hierarchyError();
}
}];
};
if (self.nodeType == HTMLNodeDocument) {
switch (node.nodeType) {
case HTMLNodeDocumentFragment:
if (node.childNodesCount > 1 ||
[node hasChildNodeOfType:HTMLNodeText]) {
hierarchyError();
} else if (node.childNodesCount == 1) {
if (child.nextSibling.nodeType == HTMLNodeDocumentType) {
hierarchyError();
}
checkParentHasAnotherChildOfType(HTMLNodeElement);
}
break;
case HTMLNodeElement:
{
if (child.nextSibling.nodeType == HTMLNodeDocumentType) {
hierarchyError();
}
checkParentHasAnotherChildOfType(HTMLNodeElement);
break;
}
case HTMLNodeDocumentType:
{
if (child.previousSibling.nodeType == HTMLNodeElement) {
hierarchyError();
}
checkParentHasAnotherChildOfType(HTMLNodeDocumentType);
break;
}
default:
break;
}
}
}
#endif
#pragma mark - Clone
- (instancetype)cloneNodeDeep:(BOOL)deep
{
HTMLNode *copy = [self copy];
if (deep) {
for (HTMLNode *child in _childNodes) {
[copy appendNode:[child cloneNodeDeep:YES]];
}
}
return copy;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
HTMLNode *copy = [[self.class alloc] initWithName:self.name type:self.nodeType];
return copy;
}
#pragma mark - Serialization
- (NSString *)outerHTML
{
return [HTMLSerializer serializeNode:self scope:HTMLSerializationScopeIncludeRoot];
}
- (NSString *)innerHTML
{
return [HTMLSerializer serializeNode:self scope:HTMLSerializationScopeChildrenOnly];
}
- (void)setInnerHTML:(NSString *)outerHTML
{
[self doesNotRecognizeSelector:_cmd];
}
#pragma mark - Description
- (NSString *)treeDescription
{
NSMutableString *string = [NSMutableString string];
__weak __block void (^ weakAccumulator) (HTMLNode *, NSUInteger);
void (^ accumulator) (HTMLNode *, NSUInteger);
static NSString *prefix = @"| ";
weakAccumulator = accumulator = ^ (HTMLNode *node, NSUInteger level) {
NSString *indent = [prefix stringByPaddingToLength:level * 2 + prefix.length
withString:@" "
startingAtIndex:0];
if (level > 0) {
[string appendString:@"\n"];
}
[string appendString:indent];
[string appendString:node.description];
for (HTMLNode *child in node.childNodes) {
weakAccumulator(child, level + 1);
}
};
accumulator(self, 0);
return string;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p '%@'>", self.class, self, self.name];
}
- (id)debugQuickLookObject
{
return self.outerHTML;
}
@end

View File

@@ -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 ()
{
HTMLNodeFilterValue (^ _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

View File

@@ -0,0 +1,153 @@
//
// HTMLNodeIterator.m
// HTMLKit
//
// Created by Iska on 27/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLNodeIterator.h"
#import "HTMLDocument.h"
#import "HTMLNode.h"
#import "HTMLNodeFilter.h"
#import "HTMLNodeTraversal.h"
#import "HTMLDocument+Private.h"
typedef NS_ENUM(short, TraverseDirection)
{
TraverseDirectionNext,
TraverseDirectionPrevious
};
@interface HTMLNodeIterator ()
{
HTMLNode *_root;
}
@end
@implementation HTMLNodeIterator
#pragma mark - Lifecycle
- (instancetype)initWithNode:(HTMLNode *)node
{
return [self initWithNode:node filter:nil];
}
- (instancetype)initWithNode:(HTMLNode *)node
filter:(id<HTMLNodeFilter>)filter
{
return [self initWithNode:node showOptions:HTMLNodeFilterShowAll filter:filter];
}
- (instancetype)initWithNode:(HTMLNode *)node
showOptions:(HTMLNodeFilterShowOptions)showOptions
filter:(id<HTMLNodeFilter>)filter
{
self = [super init];
if (self) {
_root = node;
_filter = filter;
_whatToShow = showOptions;
_referenceNode = _root;
_pointerBeforeReferenceNode = YES;
[_root.ownerDocument attachNodeIterator:self];
}
return self;
}
- (void)dealloc
{
[_root.ownerDocument detachNodeIterator:self];
}
#pragma mark - Removing Steps
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
withOldParent:(HTMLNode *)oldParent
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling
{
if ([oldNode containsNode:_root]) {
return;
}
if (![oldNode containsNode:_referenceNode]) {
return;
}
if (_pointerBeforeReferenceNode) {
HTMLNode *nextSibling = oldPreviousSibling != nil ? oldPreviousSibling.nextSibling : oldParent.firstChild;
if (nextSibling != nil) {
_referenceNode = nextSibling;
return;
}
HTMLNode *next = FollowingNode(oldParent, _root);
if ([_root containsNode:next]) {
_referenceNode = next;
return;
}
_pointerBeforeReferenceNode = NO;
}
HTMLNode * (^ lastInclusiveDescendant) (HTMLNode *) = ^ HTMLNode * (HTMLNode *node) {
while (node.lastChild) {
node = node.lastChild;
}
return node;
};
_referenceNode = oldPreviousSibling != nil ? lastInclusiveDescendant(oldPreviousSibling) : oldParent;
}
#pragma mark - Traversal
- (HTMLNode *)traverseInDirection:(TraverseDirection)direction
{
HTMLNode *node = self.referenceNode;
BOOL beforeNode = self.pointerBeforeReferenceNode;
do {
if (direction == TraverseDirectionNext) {
if (!beforeNode) {
node = FollowingNode(node, self.root);
if (node == nil) {
return nil;
}
}
beforeNode = NO;
} else {
if (beforeNode) {
node = PrecedingNode(node, self.root);
if (node == nil) {
return nil;
}
}
beforeNode = YES;
}
} while (FilterNode(self.filter, self.whatToShow, node) != HTMLNodeFilterAccept);
_referenceNode = node;
_pointerBeforeReferenceNode = beforeNode;
return node;
}
- (HTMLNode *)nextNode
{
return [self traverseInDirection:TraverseDirectionNext];
}
- (HTMLNode *)previousNode
{
return [self traverseInDirection:TraverseDirectionPrevious];
}
#pragma mark - NSEnumerator
- (id)nextObject
{
return self.nextNode;
}
@end

View File

@@ -0,0 +1,76 @@
//
// HTMLNodeTraversal.m
// HTMLKit
//
// Created by Iska on 05/06/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLNodeTraversal.h"
#import "HTMLNode.h"
#import "HTMLNodeFilter.h"
HTMLNode * PrecedingNode(HTMLNode *node, HTMLNode *root)
{
HTMLNode *previous = node.previousSibling;
if (previous != nil) {
while (previous.lastChild != nil) {
previous = previous.lastChild;
}
return previous;
}
if (node == root) {
return nil;
}
return node.parentNode;
}
HTMLNode * FollowingNode(HTMLNode *node, HTMLNode *root)
{
if (node.firstChild != nil) {
return node.firstChild;
}
do {
if (node == root) {
return nil;
}
if (node.nextSibling != nil) {
return node.nextSibling;
}
node = node.parentNode;
} while (node != nil);
return nil;
}
HTMLNode * FollowingNodeSkippingChildren(HTMLNode *node, HTMLNode *root)
{
do {
if (node == root) {
return nil;
}
if (node.nextSibling != nil) {
return node.nextSibling;
}
node = node.parentNode;
} while (node != nil);
return nil;
}
HTMLNodeFilterValue FilterNode(id<HTMLNodeFilter> filter, HTMLNodeFilterShowOptions whatToShow, HTMLNode *node)
{
unsigned long nthBit = (1 << (node.nodeType - 1)) & whatToShow;
if (!nthBit) {
return HTMLNodeFilterSkip;
}
if (filter == nil) {
return HTMLNodeFilterAccept;
}
return [filter acceptNode:node];
}

View File

@@ -0,0 +1,54 @@
//
// HTMLNodeVisitor.m
// HTMLKit
//
// Created by Iska on 30.07.19.
// Copyright © 2019 BrainCookie. All rights reserved.
//
#import "HTMLNodeVisitor.h"
#pragma mark - Block Visitor
@interface HTMLNodeVisitorBlock ()
{
void (^ _enter)(HTMLNode *);
void (^ _leave)(HTMLNode *);
}
@end
@implementation HTMLNodeVisitorBlock
+ (instancetype)visitorWithEnterBlock:(void (^)(HTMLNode * _Nonnull))enterBlock
leaveBlock:(void (^)(HTMLNode * _Nonnull))leaveBlock
{
return [[HTMLNodeVisitorBlock alloc] initWithEnterBlock:enterBlock leaveBlock:leaveBlock];
}
- (instancetype)initWithEnterBlock:(void (^)(HTMLNode * _Nonnull))enterBlock
leaveBlock:(void (^)(HTMLNode * _Nonnull))leaveBlock
{
self = [super init];
if (self) {
_enter = [enterBlock copy];
_leave = [leaveBlock copy];
}
return self;
}
- (void)enter:(HTMLNode *)node
{
if (_enter) {
_enter(node);
}
}
- (void)leave:(HTMLNode *)node
{
if (_leave) {
_leave(node);
}
}
@end

View File

@@ -0,0 +1,164 @@
//
// HTMLOrderedDictionary.m
// HTMLKit
//
// Created by Iska on 14/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLOrderedDictionary.h"
@interface HTMLOrderedDictionary ()
{
NSMutableDictionary *_dictionary;
NSMutableArray *_keys;
}
@end
@implementation HTMLOrderedDictionary
#pragma mark - Init
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
- (instancetype)init
{
return [self initWithCapacity:0];
}
#pragma clang diagnostic pop
- (instancetype)initWithCapacity:(NSUInteger)capacity
{
self = [super init];
if (self) {
_dictionary = [[NSMutableDictionary alloc] initWithCapacity:capacity];
_keys = [[NSMutableArray alloc] initWithCapacity:capacity];
}
return self;
}
- (instancetype)initWithObjects:(NSArray *)objects forKeys:(NSArray *)keys
{
self = [self initWithCapacity:objects.count];
if (self) {
[objects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
_dictionary[keys[idx]] = obj;
}];
}
return self;
}
#pragma mark - Access
- (id)objectForKey:(id)aKey
{
return _dictionary[aKey];
}
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
{
if (_dictionary[aKey] == nil) {
[_keys addObject:aKey];
}
_dictionary[aKey] = anObject;
}
- (void)removeObjectForKey:(id)aKey
{
[_keys removeObject:aKey];
[_dictionary removeObjectForKey:aKey];
}
- (NSUInteger)count
{
return _keys.count;
}
#pragma mark - Indexed Access
- (id)objectAtIndex:(NSUInteger)index
{
return _dictionary[_keys[index]];
}
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey atIndex:(NSUInteger)index
{
if (_dictionary[aKey]) {
[_keys removeObject:aKey];
}
[_keys insertObject:aKey atIndex:index];
_dictionary[aKey] = anObject;
}
- (void)removeObjectAtIndex:(NSUInteger)index
{
if (_dictionary[_keys[index]]){
[_dictionary removeObjectForKey:_keys[index]];
[_keys removeObjectAtIndex:index];
}
}
- (void)replaceKeyValueAtIndex:(NSUInteger)index withObject:(id)anObject andKey:(id<NSCopying>)aKey
{
[_keys replaceObjectAtIndex:index withObject:aKey];
_dictionary[aKey] = anObject;
}
- (void)replaceKey:(id<NSCopying>)aKey withKey:(id<NSCopying>)newKey
{
id value = _dictionary[aKey];
if (value != nil) {
NSUInteger index = [_keys indexOfObject:aKey];
[_keys replaceObjectAtIndex:index withObject:newKey];
[_dictionary removeObjectForKey:aKey];
_dictionary[newKey] = value;
}
}
- (NSUInteger)indexOfKey:(id<NSCopying>)aKey
{
return [_keys indexOfObject:aKey];
}
#pragma mark - Subscript
- (id)objectAtIndexedSubscript:(NSUInteger)index
{
return _dictionary[_keys[index]];
}
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index
{
_dictionary[_keys[index]] = obj;
}
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
[self setObject:obj forKey:key];
}
#pragma mark - Enumeration
- (NSEnumerator *)keyEnumerator
{
return _keys.objectEnumerator;
}
- (NSEnumerator *)reverseKeyEnumerator
{
return _keys.reverseObjectEnumerator;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id [])buffer count:(NSUInteger)len
{
return [_keys countByEnumeratingWithState:state objects:buffer count:len];
}
#pragma mark - Copying
- (id)mutableCopy
{
return [[HTMLOrderedDictionary alloc] initWithDictionary:self];
}
@end

View File

@@ -0,0 +1,55 @@
//
// HTMLParseErrorToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLParseErrorToken.h"
@interface HTMLParseErrorToken ()
{
NSString *_code;
NSString *_details;
NSUInteger _location;
}
@end
@implementation HTMLParseErrorToken
@synthesize code = _code;
@synthesize details = _details;
@synthesize location = _location;
- (instancetype)initWithCode:(NSString *)code details:(NSString *)details location:(NSUInteger)location
{
self = [super init];
if (self) {
self.type = HTMLTokenTypeParseError;
_code = [code copy];
_details = [details copy];
_location = location;
}
return self;
}
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLParseErrorToken *token = (HTMLParseErrorToken *)other;
return bothNilOrEqual(self.code, token.code);
}
return NO;
}
- (NSUInteger)hash
{
return self.code.hash + self.code.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p Code='%@' Details='%@' Location='%lu'>", self.class, self, _code, _details, (unsigned long)_location];
}
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
//
// HTMLQuircksMode.m
// HTMLKit
//
// Created by Iska on 26.03.19.
// Copyright © 2019 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "HTMLQuirksMode.h"
#import "NSString+Private.h"
BOOL QuirksModePrefixMatch(NSString *publicIdentifier)
{
for (int i = 0; i < sizeof(HTMLQuirksModePrefixes) / sizeof(HTMLQuirksModePrefixes[0]); i++) {
if ([publicIdentifier hasPrefixIgnoringCase:HTMLQuirksModePrefixes[i]]) {
return YES;
}
}
return NO;
}

View File

@@ -0,0 +1,767 @@
//
// HTMLRange.m
// HTMLKit
//
// Created by Iska on 20/11/16.
// Copyright © 2016 BrainCookie. All rights reserved.
//
#import "HTMLRange.h"
#import "HTMLDocument.h"
#import "HTMLKitDOMExceptions.h"
#import "HTMLDocument+Private.h"
#import "HTMLDOMUtils.h"
#import "HTMLNodeTraversal.h"
@interface HTMLRange ()
{
HTMLDocument *_ownerDocument;
}
@end
@implementation HTMLRange
#pragma mark - Lifecycle
- (instancetype)initWithDocument:(HTMLDocument *)document
{
return [self initWithDocument:document startContainer:document startOffset:0 endContainer:document endOffset:0];
}
- (instancetype)initWithDowcument:(HTMLDocument *)document
{
return [self initWithDocument:document startContainer:document startOffset:0 endContainer:document endOffset:0];
}
- (instancetype)initWithDowcument:(HTMLDocument *)document
startContainer:(HTMLNode *)startContainer startOffset:(NSUInteger)startOffset
endContainer:(HTMLNode *)endContainer endOffset:(NSUInteger)endOffset
{
return [self initWithDocument:document
startContainer:startContainer startOffset:startOffset
endContainer:endContainer endOffset:endOffset];
}
- (instancetype)initWithDocument:(HTMLDocument *)document
startContainer:(HTMLNode *)startContainer startOffset:(NSUInteger)startOffset
endContainer:(HTMLNode *)endContainer endOffset:(NSUInteger)endOffset
{
self = [super init];
if (self) {
_ownerDocument = document;
[_ownerDocument attachRange:self];
[self setStartNode:startContainer startOffset:startOffset];
[self setEndNode:endContainer endOffset:endOffset];
}
return self;
}
- (void)dealloc
{
[_ownerDocument detachRange:self];
}
#pragma mark - Properties
- (BOOL)isCollapsed
{
return _startContainer == _endContainer && _startOffset == _endOffset;
}
- (HTMLNode *)commonAncestorContainer
{
return GetCommonAncestorContainer(_startContainer, _endContainer);
}
- (HTMLNode *)rootNode
{
return _startContainer.rootNode;
}
#pragma mark - Boundaries
NS_INLINE void CheckValidBoundaryNode(HTMLDocument *document, HTMLNode *node, NSString *cmd)
{
if (node.ownerDocument != document) {
[NSException raise:HTMLKitWrongDocumentError
format:@"%@: Invalid Node Error, %@ is not in the same document.",
cmd, node];
}
}
NS_INLINE void CheckValidBoundaryNodeType(HTMLNode *node, NSString *cmd)
{
if (node == nil || node.nodeType == HTMLNodeDocumentType) {
[NSException raise:HTMLKitInvalidNodeTypeError
format:@"%@: Invalid Node Type Error, %@ is not a valid range boundary node.",
cmd, node];
}
}
NS_INLINE void CheckValidBoundaryOffset(HTMLNode *node, NSUInteger offset, NSString *cmd)
{
if (node.length < offset) {
[NSException raise:HTMLKitIndexSizeError
format:@"%@: Index Size Error, invalid index %lu for range boundary node %@.",
cmd, (unsigned long)offset, node];
}
}
NS_INLINE void CheckValidDocument(HTMLRange *lhs, HTMLRange *rhs, NSString *cmd)
{
if (lhs.rootNode != rhs.rootNode) {
[NSException raise:HTMLKitWrongDocumentError
format:@"%@: Wrong Document Error, ranges %@ and %@ are not in the same document.",
cmd, lhs, rhs];
}
}
NS_INLINE NSComparisonResult CompareBoundaries(HTMLNode *startNode, NSUInteger startOffset, HTMLNode *endNode, NSUInteger endOffset)
{
if (startNode == endNode) {
if (startOffset == endOffset) {
return NSOrderedSame;
} else if (startOffset < endOffset) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}
HTMLDocumentPosition position = [startNode compareDocumentPositionWithNode:endNode];
if ((position & HTMLDocumentPositionFollowing) == HTMLDocumentPositionFollowing) {
if (CompareBoundaries(endNode, endOffset, startNode, startOffset) == NSOrderedAscending) {
return NSOrderedDescending;
} else {
return NSOrderedAscending;
}
}
if ((position & HTMLDocumentPositionContains) == HTMLDocumentPositionContains) {
HTMLNode *child = endNode;
while (child.parentNode != startNode) {
child = child.parentNode;
}
if (child.index < startOffset) {
return NSOrderedDescending;
}
}
return NSOrderedAscending;
}
- (void)setStartNode:(HTMLNode *)node startOffset:(NSUInteger)offset
{
CheckValidBoundaryNode(_ownerDocument, node, NSStringFromSelector(_cmd));
CheckValidBoundaryNodeType(node, NSStringFromSelector(_cmd));
CheckValidBoundaryOffset(node, offset, NSStringFromSelector(_cmd));
if (self.rootNode != node.rootNode ||
CompareBoundaries(node, offset, _endContainer, _endOffset) == NSOrderedDescending) {
_endContainer = node;
_endOffset = offset;
}
_startContainer = node;
_startOffset = offset;
}
- (void)setEndNode:(HTMLNode *)node endOffset:(NSUInteger)offset
{
CheckValidBoundaryNode(_ownerDocument, node, NSStringFromSelector(_cmd));
CheckValidBoundaryNodeType(node, NSStringFromSelector(_cmd));
CheckValidBoundaryOffset(node, offset, NSStringFromSelector(_cmd));
if (self.rootNode != node.rootNode ||
CompareBoundaries(node, offset, _startContainer, _startOffset) == NSOrderedAscending) {
_startContainer = node;
_startOffset = offset;
}
_endContainer = node;
_endOffset = offset;
}
- (void)setStartBeforeNode:(HTMLNode *)node
{
HTMLNode *parent = node.parentNode;
[self setStartNode:parent startOffset:node.index];
}
- (void)setStartAfterNode:(HTMLNode *)node
{
HTMLNode *parent = node.parentNode;
[self setStartNode:parent startOffset:node.index + 1];
}
- (void)setEndBeforeNode:(HTMLNode *)node
{
HTMLNode *parent = node.parentNode;
[self setEndNode:parent endOffset:node.index];
}
- (void)setEndAfterNode:(HTMLNode *)node
{
HTMLNode *parent = node.parentNode;
[self setEndNode:parent endOffset:node.index + 1];
}
- (void)collapseToStart
{
[self setEndNode:_startContainer endOffset:_startOffset];
}
- (void)collapseToEnd
{
[self setStartNode:_endContainer startOffset:_endOffset];
}
- (void)selectNode:(HTMLNode *)node
{
HTMLNode *parent = node.parentNode;
[self setStartNode:parent startOffset:node.index];
[self setEndNode:parent endOffset:node.index + 1];
}
- (void)selectNodeContents:(HTMLNode *)node
{
[self setStartNode:node startOffset:0];
[self setEndNode:node endOffset:node.length];
}
- (NSComparisonResult)compareBoundaryPoints:(HTMLRangeComparisonMethod)method sourceRange:(HTMLRange *)sourceRange
{
CheckValidDocument(self, sourceRange, NSStringFromSelector(_cmd));
switch (method) {
case HTMLRangeComparisonMethodStartToStart:
return CompareBoundaries(_startContainer, _startOffset, sourceRange.startContainer, sourceRange.startOffset);
case HTMLRangeComparisonMethodStartToEnd:
return CompareBoundaries(_endContainer, _endOffset, sourceRange.startContainer, sourceRange.startOffset);
case HTMLRangeComparisonMethodEndToEnd:
return CompareBoundaries(_endContainer, _endOffset, sourceRange.endContainer, sourceRange.endOffset);
case HTMLRangeComparisonMethodEndToStart:
return CompareBoundaries(_startContainer, _startOffset, sourceRange.endContainer, sourceRange.endOffset);
}
}
#pragma mark - Containment
- (NSComparisonResult)comparePoint:(HTMLNode *)node offset:(NSUInteger)offset
{
CheckValidBoundaryNode(_ownerDocument, node, NSStringFromSelector(_cmd));
CheckValidBoundaryNodeType(node, NSStringFromSelector(_cmd));
CheckValidBoundaryOffset(node, offset, NSStringFromSelector(_cmd));
if (CompareBoundaries(node, offset, _startContainer, _startOffset) == NSOrderedAscending) {
return NSOrderedAscending;
}
if (CompareBoundaries(node, offset, _endContainer, _endOffset) == NSOrderedDescending) {
return NSOrderedDescending;
}
return NSOrderedSame;
}
- (BOOL)containsPoint:(HTMLNode *)node offset:(NSUInteger)offset
{
return [self comparePoint:node offset:offset] == NSOrderedSame;
}
- (BOOL)intersectsNode:(HTMLNode *)node
{
if (self.rootNode != node.rootNode) {
return NO;
}
HTMLNode *parent = node.parentNode;
if (parent == nil) {
return YES;
}
NSUInteger offset = node.index;
if (CompareBoundaries(parent, offset, _endContainer, _endOffset) == NSOrderedAscending &&
CompareBoundaries(parent, offset + 1, _startContainer, _startOffset) == NSOrderedDescending) {
return YES;
}
return NO;
}
- (BOOL)containsNode:(HTMLNode *)node
{
return CompareBoundaries(_startContainer, _startOffset, node, 0) == NSOrderedAscending &&
CompareBoundaries(_endContainer, _endOffset, node, node.length) == NSOrderedDescending;
}
- (BOOL)partiallyContainsNode:(HTMLNode *)node
{
return [GetAncestorNodes(_startContainer) containsObject:node] || [GetAncestorNodes(_endContainer) containsObject:node];
}
- (NSArray *)containedNodes:(HTMLNode *)commonAncestor
{
NSMutableArray *containedNodes = [NSMutableArray array];
[commonAncestor.childNodes enumerateObjectsUsingBlock:^(HTMLNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
if (node.nodeType == HTMLNodeDocumentType) {
[NSException raise:HTMLKitHierarchyRequestError format:@"Hierarchy Request Error, encountered a DOCTYPE contained in range: %@", self];
}
if ([self containsNode:node]) {
[containedNodes addObject:node];
}
}];
return containedNodes;
}
#pragma mark - Update Callbacks
- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
{
if (_startContainer == node && _startOffset > offset) {
if (_startOffset <= offset + length) {
_startOffset = offset;
} else {
_startOffset = _startOffset - length;
}
}
if (_endContainer == node && _endOffset > offset) {
if (_endOffset <= offset + length) {
_endOffset = offset;
} else {
_endOffset = _endOffset - length;
}
}
}
- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length
{
if (_startContainer == node && _startOffset > offset) {
_startOffset = _startOffset + length;
}
if (_endContainer == node && _endOffset > offset) {
_endOffset = _endOffset + length;
}
}
- (void)didInsertNewTextNode:(HTMLText *)newNode intoParent:(HTMLNode *)parent afterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset
{
if (_startContainer == node && _startOffset > offset) {
_startContainer = newNode;
_startOffset -= offset;
}
if (_endContainer == node && _endOffset > offset) {
_endContainer = newNode;
_endOffset -= offset;
}
if (_startContainer == parent && _startOffset == node.index + 1) {
_startOffset += 1;
}
if (_endContainer == parent && _endOffset == node.index + 1) {
_endOffset += 1;
}
}
- (void)clampRangesAfterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset
{
if (_startContainer == node && _startOffset > offset) {
_startOffset = offset;
}
if (_endContainer == node && _endOffset > offset) {
_endOffset = offset;
}
}
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode withOldParent:(HTMLNode *)oldParent andOldPreviousSibling:(HTMLNode *)oldPreviousSibling
{
NSUInteger oldIndex = oldPreviousSibling.index + 1;
if ([_startContainer containsNode:oldNode]) {
[self setStartNode:oldNode startOffset:oldIndex];
}
if ([_endContainer containsNode:oldNode]) {
[self setEndNode:oldNode endOffset:oldIndex];
}
if (_startContainer == oldParent && _startOffset > oldIndex) {
_startOffset -= 1;
}
if (_endContainer == oldParent && _endOffset > oldIndex) {
_endOffset -= 1;
}
}
#pragma mark - Mutations
NS_INLINE HTMLNode * GetHighestPartiallyContainedChild(HTMLNode *node, HTMLNode *root)
{
if (node == root) {
return nil;
}
while (node.parentNode != root) {
node = node.parentNode;
}
return node;
}
NS_INLINE HTMLCharacterData * CloneCharachterData(HTMLNode *node, NSUInteger start, NSUInteger length, BOOL delete)
{
HTMLCharacterData *clone = (HTMLCharacterData *)[node copy];
NSRange range = NSMakeRange(start, length);
[clone setData:[clone.data substringWithRange:range]];
if (delete) {
[(HTMLCharacterData *)node deleteDataInRange:range];
}
return clone;
}
- (void)deleteContents
{
if (self.isCollapsed) {
return;
}
if (_startContainer == _endContainer && [_startContainer isKindOfClass:[HTMLCharacterData class]]) {
[(HTMLCharacterData *)_startContainer deleteDataInRange:NSMakeRange(_startOffset, _endOffset - _startOffset)];
return;
}
HTMLNode *commonAncestor = self.commonAncestorContainer;
NSMutableArray *containedNodes = [NSMutableArray array];
HTMLNode *node = FollowingNode(_startContainer, commonAncestor);
while (node) {
if ([self containsNode:node]) {
[containedNodes addObject:node];
node = FollowingNodeSkippingChildren(node, commonAncestor);
} else {
node = FollowingNode(node, commonAncestor);
}
}
HTMLNode *newNode = _startContainer;
NSUInteger newOffset = _startOffset;
if (![_startContainer containsNode:_endContainer]) {
HTMLNode *referenceNode = _startContainer;
while (referenceNode.parentNode) {
if ([referenceNode.parentNode containsNode:_endContainer]) {
newNode = referenceNode.parentNode;
newOffset = referenceNode.index + 1;
break;
}
referenceNode = referenceNode.parentNode;
}
}
if ([_startContainer isKindOfClass:[HTMLCharacterData class]]) {
[(HTMLCharacterData *)_startContainer deleteDataInRange:NSMakeRange(_startOffset, _startContainer.length - _startOffset)];
}
for (HTMLNode *node in containedNodes) {
[node removeFromParentNode];
}
if ([_endContainer isKindOfClass:[HTMLCharacterData class]]) {
[(HTMLCharacterData *)_endContainer deleteDataInRange:NSMakeRange(0, _endOffset)];
}
[self setStartNode:newNode startOffset:newOffset];
[self setEndNode:newNode endOffset:newOffset];
}
- (HTMLDocumentFragment *)extractContents
{
HTMLDocumentFragment *fragment = [[HTMLDocumentFragment alloc] initWithDocument:_ownerDocument];
// Nothing todo
if (self.isCollapsed) {
return fragment;
}
// Same character data container, handle that and return
if (_startContainer == _endContainer && [_startContainer isKindOfClass:[HTMLCharacterData class]]) {
HTMLCharacterData *clone = CloneCharachterData(_startContainer, _startOffset, _endOffset - _startOffset, YES);
[fragment appendNode:clone];
return fragment;
}
HTMLNode *commonAncestor = self.commonAncestorContainer;
HTMLNode *firstPartiallyContainedChild = GetHighestPartiallyContainedChild(_startContainer, commonAncestor);
HTMLNode *lastPartiallyContainedChild = GetHighestPartiallyContainedChild(_endContainer, commonAncestor);
NSArray *containedNodes = [self containedNodes:commonAncestor];
HTMLNode *newNode = _startContainer;
NSUInteger newOffset = _startOffset;
if (![_startContainer containsNode:_endContainer]) {
HTMLNode *referenceNode = _startContainer;
while (referenceNode.parentNode) {
if ([referenceNode.parentNode containsNode:_endContainer]) {
newNode = referenceNode.parentNode;
newOffset = referenceNode.index + 1;
break;
}
referenceNode = referenceNode.parentNode;
}
}
if ([firstPartiallyContainedChild isKindOfClass:[HTMLCharacterData class]]) {
HTMLCharacterData *clone = CloneCharachterData(_startContainer, _startOffset, _startContainer.length - _startOffset, YES);
[fragment appendNode:clone];
} else if (firstPartiallyContainedChild != nil) {
HTMLNode *clone = [firstPartiallyContainedChild copy];
[fragment appendNode:clone];
HTMLRange *subRange = [[HTMLRange alloc] initWithDocument:_ownerDocument
startContainer:_startContainer
startOffset:_startOffset
endContainer:firstPartiallyContainedChild
endOffset:firstPartiallyContainedChild.length];
HTMLDocumentFragment *subFragment = [subRange extractContents];
[clone appendNode:subFragment];
}
for (HTMLNode *node in containedNodes) {
[fragment appendNode:node];
}
if ([lastPartiallyContainedChild isKindOfClass:[HTMLCharacterData class]]) {
HTMLCharacterData *clone = CloneCharachterData(_endContainer, 0, _endOffset, YES);
[fragment appendNode:clone];
} else if (lastPartiallyContainedChild != nil) {
HTMLNode *clone = [lastPartiallyContainedChild copy];
[fragment appendNode:clone];
HTMLRange *subRange = [[HTMLRange alloc] initWithDocument:_ownerDocument
startContainer:lastPartiallyContainedChild
startOffset:0
endContainer:_endContainer
endOffset:_endOffset];
HTMLDocumentFragment *subFragment = [subRange extractContents];
[clone appendNode:subFragment];
}
[self setStartNode:newNode startOffset:newOffset];
[self setEndNode:newNode endOffset:newOffset];
return fragment;
}
- (HTMLDocumentFragment *)cloneContents
{
HTMLDocumentFragment *fragment = [[HTMLDocumentFragment alloc] initWithDocument:_ownerDocument];
// Nothing todo
if (self.isCollapsed) {
return fragment;
}
// Same character data container, handle that and return
if (_startContainer == _endContainer && [_startContainer isKindOfClass:[HTMLCharacterData class]]) {
HTMLCharacterData *clone = CloneCharachterData(_startContainer, _startOffset, _endOffset - _startOffset, NO);
[fragment appendNode:clone];
return fragment;
}
HTMLNode *commonAncestor = self.commonAncestorContainer;
HTMLNode *firstPartiallyContainedChild = GetHighestPartiallyContainedChild(_startContainer, commonAncestor);
HTMLNode *lastPartiallyContainedChild = GetHighestPartiallyContainedChild(_endContainer, commonAncestor);
NSArray *containedNodes = [self containedNodes:commonAncestor];
if ([firstPartiallyContainedChild isKindOfClass:[HTMLCharacterData class]]) {
HTMLCharacterData *clone = CloneCharachterData(_startContainer, _startOffset, _startContainer.length - _startOffset, NO);
[fragment appendNode:clone];
} else if (firstPartiallyContainedChild != nil) {
HTMLNode *clone = [firstPartiallyContainedChild copy];
[fragment appendNode:clone];
HTMLRange *subRange = [[HTMLRange alloc] initWithDocument:_ownerDocument
startContainer:_startContainer
startOffset:_startOffset
endContainer:firstPartiallyContainedChild
endOffset:firstPartiallyContainedChild.length];
HTMLDocumentFragment *subFragment = [subRange cloneContents];
[clone appendNode:subFragment];
}
for (HTMLNode *node in containedNodes) {
HTMLNode *clone = [node cloneNodeDeep:YES];
[fragment appendNode:clone];
}
if ([lastPartiallyContainedChild isKindOfClass:[HTMLCharacterData class]]) {
HTMLCharacterData *clone = CloneCharachterData(_endContainer, 0, _endOffset, NO);
[fragment appendNode:clone];
} else if (lastPartiallyContainedChild != nil) {
HTMLNode *clone = [lastPartiallyContainedChild copy];
[fragment appendNode:clone];
HTMLRange *subRange = [[HTMLRange alloc] initWithDocument:_ownerDocument
startContainer:lastPartiallyContainedChild
startOffset:0
endContainer:_endContainer
endOffset:_endOffset];
HTMLDocumentFragment *subFragment = [subRange cloneContents];
[clone appendNode:subFragment];
}
return fragment;
}
#pragma mark - Insertion & Surround
NS_INLINE void CheckValidInsertionNode(HTMLNode *startContainer, HTMLNode *node, NSString *cmd)
{
if (startContainer == node || startContainer.nodeType == HTMLNodeComment ||
(startContainer.nodeType == HTMLNodeText && startContainer.parentNode == nil)) {
[NSException raise:HTMLKitHierarchyRequestError
format:@"%@: Hierarchy Request Error, cannot insert node into range: %@", cmd, node];
}
}
- (void)insertNode:(HTMLNode *)node
{
CheckValidInsertionNode(_startContainer, node, NSStringFromSelector(_cmd));
HTMLNode *referenceNode = nil;
if (_startContainer.nodeType == HTMLNodeText) {
referenceNode = _startContainer;
} else {
referenceNode = [_startContainer childNodeAtIndex:_startOffset];
}
HTMLNode *parent = _startContainer;
if (referenceNode != nil) {
parent = referenceNode.parentNode;
}
if (_startContainer.nodeType == HTMLNodeText) {
referenceNode = [(HTMLText *)_startContainer splitTextAtOffset:_startOffset];
}
if (node == referenceNode) {
referenceNode = referenceNode.nextSibling;
}
[node removeFromParentNode];
NSUInteger newOffset = referenceNode ? referenceNode.index : parent.length;
newOffset += (node.nodeType == HTMLNodeDocumentFragment) ? node.length : 1;
[parent insertNode:node beforeChildNode:referenceNode];
if (self.isCollapsed) {
[self setEndNode:parent endOffset:newOffset];
}
}
NS_INLINE void CheckValidSurroundState(HTMLRange *range, NSString *cmd)
{
for (HTMLNode *node in GetAncestorNodes(range.startContainer)) {
if ([node containsNode:range.endContainer]) {
return;
}
if (node.nodeType != HTMLNodeText) {
[NSException raise:HTMLKitInvalidStateError
format:@"%@: Invalid State Error, cannot surround range with a partially-contaied non-text node.", cmd];
}
};
for (HTMLNode *node in GetAncestorNodes(range.endContainer)) {
if ([node containsNode:range.startContainer]) {
return;
}
if (node.nodeType != HTMLNodeText) {
[NSException raise:HTMLKitInvalidNodeTypeError
format:@"%@: Invalid State Error, cannot surround range with a partially-contaied non-text node.", cmd];
}
};
}
NS_INLINE void CheckValidSurroundNodeType(HTMLNode *node, NSString *cmd)
{
if (node == nil || node.nodeType == HTMLNodeDocumentType || node.nodeType == HTMLNodeDocument ||
node.nodeType == HTMLNodeDocumentFragment) {
[NSException raise:HTMLKitInvalidNodeTypeError
format:@"%@: Invalid Node Type Error, %@ is not a valid new parent for a range.",
cmd, node];
}
}
- (void)surroundContents:(HTMLNode *)newParent
{
CheckValidSurroundState(self, NSStringFromSelector(_cmd));
CheckValidSurroundNodeType(newParent, NSStringFromSelector(_cmd));
HTMLDocumentFragment *fragment = [self extractContents];
[newParent removeAllChildNodes];
[self insertNode:newParent];
[newParent appendNode:fragment];
[self selectNode:newParent];
}
#pragma mark - Description & Stringifier
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p (%@, %lu), (%@, %lu)>", self.class, self,
_startContainer, (unsigned long)_startOffset,
_endContainer, (unsigned long)_endOffset];
}
- (NSString *)textContent
{
HTMLNode *lastNode = nil;
if ([_endContainer isKindOfClass:[HTMLCharacterData class]]) {
lastNode = FollowingNodeSkippingChildren(_endContainer, _ownerDocument);
} else if (_endContainer.childNodesCount > _endOffset) {
lastNode = [_endContainer childNodeAtIndex:_endOffset];
} else {
lastNode = FollowingNodeSkippingChildren(_endContainer, _ownerDocument);
}
NSMutableString *content = [NSMutableString string];
for (HTMLNode *node = _startContainer; node != lastNode; node = FollowingNode(node, _ownerDocument)) {
if (node.nodeType == HTMLNodeText) {
HTMLText *text = (HTMLText *)node;
if (node == _startContainer) {
NSString *string = [text substringDataWithRange:NSMakeRange(_startOffset, _startContainer.length - _startOffset)];
[content appendString:string];
} else if (node == _endContainer) {
NSString *string = [text substringDataWithRange:NSMakeRange(0, _endOffset)];
[content appendString:string];
} else {
[content appendString:text.data];
}
}
}
return content;
}
@end

View File

@@ -0,0 +1,155 @@
//
// HTMLSerializer.m
// HTMLKit
//
// Created by Iska on 28.07.19.
// Copyright © 2019 BrainCookie. All rights reserved.
//
#import "HTMLSerializer.h"
#import "HTMLDOM.h"
#import "HTMLNode+Private.h"
#import "HTMLTreeVisitor.h"
#import "NSString+Private.h"
#pragma mark - Serializer
@interface HTMLSerializer ()
{
HTMLNode *_root;
HTMLTreeVisitor *_treeVisitor;
NSUInteger _ignore;
NSMutableString *_result;
}
- (instancetype)initWithNode:(HTMLNode *)node;
- (NSString *)serializeWithScope:(HTMLSerializationScope)scope;
@end
@implementation HTMLSerializer
+ (NSString *)serializeNode:(HTMLNode *)node scope:(HTMLSerializationScope)scope
{
HTMLSerializer *serializer = [[HTMLSerializer alloc] initWithNode:node];
return [serializer serializeWithScope:scope];
}
#pragma mark - Lifecycle
- (instancetype)initWithNode:(HTMLNode *)node
{
self = [super init];
if (self) {
_root = node;
_treeVisitor = [[HTMLTreeVisitor alloc] initWithNode:node];
_result = [NSMutableString new];
_ignore = 0;
}
return self;
}
#pragma mark - Serialization
- (NSString *)serializeWithScope:(HTMLSerializationScope)scope
{
[_result setString:@""];
HTMLNodeVisitorBlock *nodeVisitor = [HTMLNodeVisitorBlock visitorWithEnterBlock:^(HTMLNode * node) {
if (scope == HTMLSerializationScopeChildrenOnly && node == _root) {
return;
}
if (_ignore > 0) {
return;
}
switch (node.nodeType) {
case HTMLNodeElement:
[self openElement:node.asElement];
break;
case HTMLNodeComment:
[self serializeComment:node.asComment];
break;
case HTMLNodeText:
[self serializeText:node.asText];
break;
case HTMLNodeDocumentFragment:
[self serializeDocumentType:node.asDocumentType];
break;
default:
break;
}
} leaveBlock:^(HTMLNode * _Nonnull node) {
if (scope == HTMLSerializationScopeChildrenOnly && node == _root) {
return;
}
switch (node.nodeType) {
case HTMLNodeElement:
if ([node.asElement.tagName isEqualToAny:@"area", @"base", @"basefont", @"bgsound", @"br", @"col", @"embed",
@"frame", @"hr", @"img", @"input", @"keygen", @"link", @"menuitem", @"meta", @"param", @"source",
@"track", @"wbr", nil]) {
_ignore--;
break;
}
[self closeElement:node.asElement];
default:
break;
}
}];
[_treeVisitor walkWithNodeVisitor:nodeVisitor];
return [_result copy];
}
- (void)openElement:(HTMLElement *)element
{
[_result appendFormat:@"<%@", element.tagName];
[element.attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
NSMutableString *escaped = [value mutableCopy];
[escaped replaceOccurrencesOfString:@"&" withString:@"&amp;" options:0 range:NSMakeRange(0, escaped.length)];
[escaped replaceOccurrencesOfString:@"0x00A0" withString:@"&nbsp;" options:0 range:NSMakeRange(0, escaped.length)];
[escaped replaceOccurrencesOfString:@"\"" withString:@"&quot;" options:0 range:NSMakeRange(0, escaped.length)];
[_result appendFormat:@" %@=\"%@\"", key, escaped];
}];
[_result appendString:@">"];
if ([element.tagName isEqualToAny:@"area", @"base", @"basefont", @"bgsound", @"br", @"col", @"embed",
@"frame", @"hr", @"img", @"input", @"keygen", @"link", @"menuitem", @"meta", @"param", @"source",
@"track", @"wbr", nil]) {
_ignore++;
}
}
- (void)closeElement:(HTMLElement *)element
{
[_result appendFormat:@"</%@>", element.tagName];
}
- (void)serializeText:(HTMLText *)text
{
if ([text.parentElement.tagName isEqualToAny:@"style", @"script", @"xmp", @"iframe", @"noembed", @"noframes",
@"plaintext", @"noscript", nil]) {
[_result appendString:text.data];
} else {
NSMutableString *escaped = [text.data mutableCopy];
[escaped replaceOccurrencesOfString:@"&" withString:@"&amp;" options:0 range:NSMakeRange(0, escaped.length)];
[escaped replaceOccurrencesOfString:@"\00A0" withString:@"&nbsp;" options:0 range:NSMakeRange(0, escaped.length)];
[escaped replaceOccurrencesOfString:@"<" withString:@"&lt;" options:0 range:NSMakeRange(0, escaped.length)];
[escaped replaceOccurrencesOfString:@">" withString:@"&gt;" options:0 range:NSMakeRange(0, escaped.length)];
[_result appendString:escaped];
}
}
- (void)serializeComment:(HTMLComment *)comment
{
[_result appendFormat:@"<!--%@-->", comment.data];
}
- (void)serializeDocumentType:(HTMLDocumentType *)doctype
{
[_result appendFormat:@"<!DOCTYPE %@>", doctype.name];
}
@end

View File

@@ -0,0 +1,374 @@
//
// HTMLStackOfOpenElements.m
// HTMLKit
//
// Created by Iska on 08/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLStackOfOpenElements.h"
#import "NSString+HTMLKit.h"
#import "HTMLElementTypes.h"
#import "HTMLTemplate.h"
@interface HTMLStackOfOpenElements ()
{
NSMutableArray *_stack;
}
@end
@implementation HTMLStackOfOpenElements
#pragma mark - Init
- (instancetype)init
{
self = [super init];
if (self) {
_stack = [NSMutableArray new];
}
return self;
}
#pragma mark - Node Access
- (HTMLElement *)currentNode
{
return _stack.lastObject;
}
- (HTMLElement *)firstNode
{
return _stack.firstObject;
}
- (HTMLElement *)lastNode
{
return _stack.lastObject;
}
- (id)objectAtIndexedSubscript:(NSUInteger)index;
{
return [_stack objectAtIndex:index];
}
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
{
[_stack setObject:obj atIndexedSubscript:idx];
}
- (NSUInteger)indexOfElement:(id)node
{
return [_stack indexOfObject:node];
}
- (void)pushElement:(HTMLElement *)element
{
[_stack addObject:element];
}
- (void)removeElement:(id)element
{
[_stack removeObject:element];
}
- (BOOL)containsElement:(id)element
{
return [_stack containsObject:element];
}
- (BOOL)containsElementWithTagName:(NSString *)tagName
{
NSUInteger index = [_stack indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if ([[(HTMLElement *)obj tagName] isEqualToString:tagName]) {
*stop = YES;
return YES;
}
return NO;
}];
return index != NSNotFound;
}
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index
{
[_stack insertObject:element atIndex:index];
}
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element
{
[_stack replaceObjectAtIndex:index withObject:element];
}
#pragma mark - Pops
- (void)popCurrentNode
{
[_stack removeLastObject];
}
- (void)popElementsUntilElementPoppedWithTagName:(NSString *)tagName
{
while (self.currentNode) {
if (self.currentNode.htmlNamespace == HTMLNamespaceHTML &&
[self.currentNode.tagName isEqualToString:tagName]) {
break;
}
[_stack removeLastObject];
}
[_stack removeLastObject];
}
- (void)popElementsUntilAnElementPoppedWithAnyOfTagNames:(NSArray *)tagNames
{
while (self.currentNode) {
if (self.currentNode.htmlNamespace == HTMLNamespaceHTML &&
[tagNames containsObject:self.currentNode.tagName]) {
break;
}
[_stack removeLastObject];
}
[_stack removeLastObject];
}
- (void)popElementsUntilElementPopped:(HTMLElement *)element
{
while (self.currentNode && ![self.currentNode isEqual:element]) {
[_stack removeLastObject];
}
[_stack removeLastObject];
}
- (void)popElementsUntilTemplateElementPopped
{
while (self.currentNode && ![self.currentNode isKindOfClass:[HTMLTemplate class]]) {
[_stack removeLastObject];
}
[_stack removeLastObject];
}
- (void)clearBackToTableContext
{
while (self.currentNode && ![self.currentNode.tagName isEqualToAny:@"table", @"template", @"html", nil]) {
[_stack removeLastObject];
}
}
- (void)clearBackToTableBodyContext
{
while (![self.currentNode.tagName isEqualToAny:@"tbody", @"tfoot", @"thead", @"template", @"html", nil]) {
[_stack removeLastObject];
}
}
- (void)clearBackToTableRowContext
{
while (![self.currentNode.tagName isEqualToAny:@"tr", @"template", @"html", nil]) {
[_stack removeLastObject];
}
}
- (void)popAll
{
[_stack removeAllObjects];
}
#pragma mark - Element Scope
NS_INLINE BOOL IsSpecificScopeElement(HTMLElement *element)
{
switch (element.htmlNamespace) {
case HTMLNamespaceHTML:
return [element.tagName isEqualToAny:@"applet", @"caption", @"html", @"table", @"td", @"th", @"marquee", @"object", @"template", nil];
case HTMLNamespaceMathML:
return [element.tagName isEqualToAny:@"mi", @"mo", @"mn", @"ms", @"mtext", @"annotation-xml", nil];
case HTMLNamespaceSVG:
return [element.tagName isEqualToAny:@"foreignObject", @"desc", @"title", nil];
}
}
NS_INLINE BOOL IsHeaderElement(HTMLElement *element)
{
if (element.htmlNamespace != HTMLNamespaceHTML) {
return NO;
}
return [element.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil];
}
NS_INLINE BOOL IsTableScopeElement(HTMLElement *element)
{
if (element.htmlNamespace != HTMLNamespaceHTML) {
return NO;
}
return [element.tagName isEqualToAny:@"html", @"table", @"template", nil];
}
NS_INLINE BOOL IsListItemScopeElement(HTMLElement *element)
{
if (element.htmlNamespace != HTMLNamespaceHTML) {
return NO;
}
return [element.tagName isEqualToAny:@"ol", @"ul", nil];
}
NS_INLINE BOOL IsSelectScopeElement(HTMLElement *element)
{
if (element.htmlNamespace != HTMLNamespaceHTML) {
return NO;
}
return ![element.tagName isEqualToString:@"optgroup"] && ![element.tagName isEqualToString:@"option"];
}
NS_INLINE BOOL IsButtonScopeElement(HTMLElement *element)
{
if (element.htmlNamespace != HTMLNamespaceHTML) {
return NO;
}
return [element.tagName isEqualToString:@"button"];
}
- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName;
{
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
return node;
}
if (IsSpecificScopeElement(node)) {
return nil;
}
}
return nil;
}
- (HTMLElement *)hasHeaderElementInScope
{
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
if (IsHeaderElement(node)) {
return node;
}
if (IsSpecificScopeElement(node)) {
return nil;
}
}
return nil;
}
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName
{
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
return node;
}
if (IsTableScopeElement(node)) {
return nil;
}
}
return nil;
}
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames
{
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
if (node.htmlNamespace == HTMLNamespaceHTML && [tagNames containsObject:node.tagName]) {
return node;
}
if (IsTableScopeElement(node)) {
return nil;
}
}
return nil;
}
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName
{
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
return node;
}
if (IsSpecificScopeElement(node) || IsListItemScopeElement(node)) {
return nil;
}
}
return nil;
}
- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName
{
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
return node;
}
if (IsSpecificScopeElement(node) || IsButtonScopeElement(node)) {
return nil;
}
}
return nil;
}
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName
{
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
if (node.htmlNamespace == HTMLNamespaceHTML && [tagName isEqualToString:node.tagName]) {
return node;
}
if (IsSelectScopeElement(node)) {
return nil;
}
}
return nil;
}
- (HTMLElement *)furthestBlockAfterIndex:(NSUInteger)index
{
for (NSUInteger i = index; i < _stack.count; i++) {
HTMLElement *element = _stack[i];
if (IsSpecialElement(element)) {
return element;
}
}
return nil;
}
#pragma mark - Count
- (NSUInteger)count
{
return _stack.count;
}
- (BOOL)isEmpy
{
return _stack.count == 0;
}
#pragma mark - Enumeraiton
- (NSEnumerator *)enumerator
{
return _stack.objectEnumerator;
}
- (NSEnumerator *)reverseObjectEnumerator
{
return _stack.reverseObjectEnumerator;
}
#pragma mark - NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len
{
return [_stack countByEnumeratingWithState:state objects:buffer count:len];
}
#pragma mark - Description
- (NSString *)description
{
return _stack.description;
}
@end

View File

@@ -0,0 +1,128 @@
//
// HTMLTagToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLTagToken.h"
@interface HTMLTagToken ()
{
NSMutableString *_tagName;
HTMLOrderedDictionary *_attributes;
}
@end
@implementation HTMLTagToken
@synthesize tagName = _tagName;
- (instancetype)initWithTagName:(NSString *)tagName
{
return [self initWithTagName:tagName attributes:nil];
}
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes
{
self = [super init];
if (self) {
_tagName = [tagName mutableCopy];
if (attributes != nil) {
_attributes = [HTMLOrderedDictionary new];
[_attributes addEntriesFromDictionary:attributes];
}
}
return self;
}
- (void)appendStringToTagName:(NSString *)string
{
if (_tagName == nil) {
_tagName = [NSMutableString new];
}
[_tagName appendString:string];
}
@end
#pragma mark - Start Tag Token
@implementation HTMLStartTagToken
- (instancetype)initWithTagName:(NSString *)tagName
{
return [self initWithTagName:tagName attributes:nil];
}
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes
{
self = [super initWithTagName:tagName attributes:attributes];
if (self) {
self.type = HTMLTokenTypeStartTag;
}
return self;
}
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLStartTagToken *token = (HTMLStartTagToken *)other;
return (bothNilOrEqual(self.tagName, token.tagName) &&
bothNilOrEqual(self.attributes, token.attributes));
}
return NO;
}
- (NSUInteger)hash
{
return self.tagName.hash + self.attributes.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p TagName=%@ Attributes=%@>", self.class, self, self.tagName, self.attributes];
}
@end
#pragma mark - End Tag Token
@implementation HTMLEndTagToken
- (instancetype)initWithTagName:(NSString *)tagName
{
return [self initWithTagName:tagName attributes:nil];
}
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes
{
self = [super initWithTagName:tagName attributes:attributes];
if (self) {
self.type = HTMLTokenTypeEndTag;
}
return self;
}
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLStartTagToken *token = (HTMLStartTagToken *)other;
return bothNilOrEqual(self.tagName, token.tagName);
}
return NO;
}
- (NSUInteger)hash
{
return self.tagName.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p TagName=%@ Attributes=%@>", self.class, self, self.tagName, self.attributes];
}
@end

View File

@@ -0,0 +1,41 @@
//
// HTMLTemplate.m
// HTMLKit
//
// Created by Iska on 12/04/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLTemplate.h"
#import "HTMLDocument.h"
#import "HTMLNode+Private.h"
@implementation HTMLTemplate
- (instancetype)init
{
self = [super initWithTagName:@"template"];
return self;
}
- (void)setOwnerDocument:(HTMLDocument *)ownerDocument
{
[super setOwnerDocument:ownerDocument];
[self.ownerDocument adoptNode:self.content];
}
- (HTMLDocumentFragment *)content
{
if (_content == nil) {
_content = [[HTMLDocumentFragment alloc] initWithDocument:self.ownerDocument.associatedInertTemplateDocument];
}
return _content;
}
- (NSOrderedSet *)childNodes
{
return self.content.childNodes;
}
@end

View File

@@ -0,0 +1,76 @@
//
// 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+Private.h"
#import "HTMLCharacterData+Private.h"
#import "HTMLKitDOMExceptions.h"
#import "HTMLDocument+Private.h"
@implementation HTMLText
- (instancetype)init
{
return [self initWithData:@""];
}
- (instancetype)initWithData:(NSString *)data
{
return [super initWithName:@"#text" type:HTMLNodeText data:data];
}
- (void)appendString:(NSString *)string
{
[self appendData:string];
}
NS_INLINE void CheckValidOffset(HTMLNode *node, NSUInteger offset, NSString *cmd)
{
if (offset > node.length) {
[NSException raise:HTMLKitIndexSizeError
format:@"%@: Index Size Error, invalid offset %lu for splitting text node %@.",
cmd, (unsigned long)offset, node];
}
}
- (HTMLText *)splitTextAtOffset:(NSUInteger)offset
{
CheckValidOffset(self, offset, NSStringFromSelector(_cmd));
NSUInteger length = self.length;
NSUInteger count = length - offset;
NSRange range = NSMakeRange(offset, count);
NSString *newData = [self.data substringWithRange:range];
HTMLText *newNode = [[HTMLText alloc] initWithData:newData];
[self.ownerDocument adoptNode:newNode];
HTMLNode *parent = self.parentNode;
if (parent != nil) {
[parent insertNode:newNode beforeChildNode:self.nextSibling];
[self.ownerDocument didInsertNewTextNode:newNode intoParent:parent afterSplittingTextNode:self atOffset:offset];
}
[self deleteDataInRange:range];
if (parent != nil) {
[self.ownerDocument clampRangesAfterSplittingTextNode:self atOffset:offset];
}
return newNode;
}
#pragma mark - Description
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p \"%@\">", self.class, self, self.data];
}
@end

View File

@@ -0,0 +1,91 @@
//
// HTMLToken.m
// HTMLKit
//
// Created by Iska on 20/09/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLToken.h"
@interface HTMLToken ()
{
HTMLTokenType _type;
}
@end
@implementation HTMLToken
@synthesize type = _type;
- (BOOL)isDoctypeToken
{
return _type == HTMLTokenTypeDoctype;
}
- (BOOL)isStartTagToken
{
return _type == HTMLTokenTypeStartTag;
}
- (BOOL)isEndTagToken
{
return _type == HTMLTokenTypeEndTag;
}
- (BOOL)isCommentToken
{
return _type == HTMLTokenTypeComment;
}
- (BOOL)isCharacterToken
{
return _type == HTMLTokenTypeCharacter;
}
- (BOOL)isEOFToken
{
return _type == HTMLTokenTypeEOF;
}
- (BOOL)isParseError
{
return _type == HTMLTokenTypeParseError;
}
- (HTMLDOCTYPEToken *)asDoctypeToken
{
return (HTMLDOCTYPEToken *)self;
}
- (HTMLTagToken *)asTagToken
{
return (HTMLTagToken *)self;
}
- (HTMLStartTagToken *)asStartTagToken
{
return (HTMLStartTagToken *)self;
}
- (HTMLEndTagToken *)asEndTagToken
{
return (HTMLEndTagToken *)self;
}
- (HTMLCommentToken *)asCommentToken
{
return (HTMLCommentToken *)self;
}
- (HTMLCharacterToken *)asCharacterToken
{
return (HTMLCharacterToken *)self;
}
- (HTMLParseErrorToken *)asParseError
{
return (HTMLParseErrorToken *)self;
}
@end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
//
// HTMLTreeVisitor.m
// HTMLKit
//
// Created by Iska on 30.07.19.
// Copyright © 2019 BrainCookie. All rights reserved.
//
#import "HTMLTreeVisitor.h"
#import "HTMLNode.h"
#import "HTMLTreeWalker.h"
@interface HTMLTreeVisitor()
{
HTMLNode *_root;
HTMLTreeWalker *_treeWalker;
}
@end
@implementation HTMLTreeVisitor
- (instancetype)initWithNode:(HTMLNode *)node
{
self = [super init];
if (self) {
_root = node;
_treeWalker = [[HTMLTreeWalker alloc] initWithNode:node];
}
return self;
}
- (void)walkWithNodeVisitor:(id<HTMLNodeVisitor>)visitor
{
HTMLNode *currentNode = _treeWalker.currentNode;
while (currentNode) {
[visitor enter:currentNode];
if (currentNode.hasChildNodes) {
currentNode = [_treeWalker firstChild];
continue;
}
HTMLNode *next = [_treeWalker nextSibling];
if (next) {
[visitor leave:currentNode];
currentNode = next;
continue;
}
while (!next && _treeWalker.currentNode != _root) {
[visitor leave:_treeWalker.currentNode];
currentNode = [_treeWalker parentNode];
next = [_treeWalker nextSibling];
}
[visitor leave:currentNode];
currentNode = _treeWalker.currentNode;
if (currentNode == _root) {
break;
}
}
}
@end

View File

@@ -0,0 +1,236 @@
//
// HTMLTreeWalker.m
// HTMLKit
//
// Created by Iska on 05/06/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLTreeWalker.h"
#import "HTMLNode.h"
#import "HTMLNodeFilter.h"
#import "HTMLNodeTraversal.h"
typedef NS_ENUM(short, HTMLTreeWalkerChildrenType)
{
HTMLTreeWalkerChildrenTypeFirst,
HTMLTreeWalkerChildrenTypeLast
};
typedef NS_ENUM(short, HTMLTreeWalkerSiblingsType)
{
HTMLTreeWalkerSiblingsTypeNext,
HTMLTreeWalkerSiblingsTypePrevious
};
@implementation HTMLTreeWalker
#pragma mark - Lifecycle
- (instancetype)initWithNode:(HTMLNode *)node
{
return [self initWithNode:node filter:nil];
}
- (instancetype)initWithNode:(HTMLNode *)node
filter:(id<HTMLNodeFilter>)filter
{
return [self initWithNode:node showOptions:HTMLNodeFilterShowAll filter:filter];
}
- (instancetype)initWithNode:(HTMLNode *)node
showOptions:(HTMLNodeFilterShowOptions)showOptions
filter:(id<HTMLNodeFilter>)filter
{
self = [super init];
if (self) {
_root = node;
_filter = filter;
_whatToShow = showOptions;
_currentNode = _root;
}
return self;
}
#pragma mark - Traversal
- (HTMLNode *)parentNode
{
HTMLNode *node = _currentNode;
while (node != nil && node != _root) {
node = node.parentNode;
if (node != nil && FilterNode(self.filter, self.whatToShow, node) == HTMLNodeFilterAccept) {
_currentNode = node;
return node;
}
}
return nil;
}
- (HTMLNode *)traverseChildrenOfType:(HTMLTreeWalkerChildrenType)type
{
HTMLNode *node = _currentNode;
node = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.firstChild : node.lastChild;
while (node != nil) {
HTMLNodeFilterValue result = FilterNode(self.filter, self.whatToShow, node);
if (result == HTMLNodeFilterAccept) {
_currentNode = node;
return node;
}
if (result == HTMLNodeFilterSkip) {
HTMLNode *child = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.firstChild : node.lastChild;
if (child != nil) {
node = child;
continue;
}
}
while (node != nil) {
HTMLNode *sibling = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.nextSibling : node.previousSibling;
if (sibling != nil) {
node = sibling;
break;
}
HTMLNode *parent = node.parentNode;
if (parent == nil || parent == _root || parent == _currentNode) {
return nil;
}
node = parent;
}
}
return nil;
}
- (HTMLNode *)firstChild
{
return [self traverseChildrenOfType:HTMLTreeWalkerChildrenTypeFirst];
}
- (HTMLNode *)lastChild
{
return [self traverseChildrenOfType:HTMLTreeWalkerChildrenTypeLast];
}
- (HTMLNode *)traverseSiblingsOfType:(HTMLTreeWalkerSiblingsType)type
{
HTMLNode *node = _currentNode;
if (node == _root) {
return nil;
}
while (YES) {
HTMLNode *sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.nextSibling : node.previousSibling;
while (sibling != nil) {
node = sibling;
HTMLNodeFilterValue result = FilterNode(self.filter, self.whatToShow, node);
if (result == HTMLNodeFilterAccept) {
_currentNode = node;
return node;
}
sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.firstChild : node.lastChild;
if (result == HTMLNodeFilterReject || sibling == nil) {
sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.nextSibling : node.previousSibling;
}
}
node = node.parentNode;
if (node == nil || node == _root) {
return nil;
}
if (FilterNode(self.filter, self.whatToShow, node) == HTMLNodeFilterAccept) {
return nil;
}
}
return nil;
}
- (HTMLNode *)previousSibling
{
return [self traverseSiblingsOfType:HTMLTreeWalkerSiblingsTypePrevious];
}
- (HTMLNode *)nextSibling
{
return [self traverseSiblingsOfType:HTMLTreeWalkerSiblingsTypeNext];
}
- (HTMLNode *)previousNode
{
HTMLNode *node = _currentNode;
while (node != _root) {
HTMLNode *sibling = node.previousSibling;
while (sibling != nil) {
node = sibling;
HTMLNodeFilterValue result = FilterNode(self.filter, self.whatToShow, node);
while (result != HTMLNodeFilterReject && node.hasChildNodes) {
node = node.lastChild;
result = FilterNode(self.filter, self.whatToShow, node);
}
if (result == HTMLNodeFilterAccept) {
_currentNode = node;
return node;
}
sibling = node.previousSibling;
}
if (node == _root || node.parentNode == nil) {
return nil;
}
node = node.parentNode;
if (FilterNode(self.filter, self.whatToShow, node) == HTMLNodeFilterAccept) {
_currentNode = node;
return node;
}
}
return nil;
}
- (HTMLNode *)nextNode
{
HTMLNode *node = _currentNode;
HTMLNodeFilterValue result = YES;
while (YES) {
while (result != HTMLNodeFilterReject && node.hasChildNodes) {
node = node.firstChild;
result = FilterNode(self.filter, self.whatToShow, node);
if (result == HTMLNodeFilterAccept) {
_currentNode = node;
return node;
}
}
HTMLNode *nextSibling;
while ((nextSibling = FollowingNodeSkippingChildren(node, _root)) != nil) {
node = nextSibling;
result = FilterNode(self.filter, self.whatToShow, node);
if (result == HTMLNodeFilterAccept) {
_currentNode = node;
return node;
}
break;
}
}
return nil;
}
@end

View File

@@ -0,0 +1,43 @@
//
// NSCharacterSet+HTMLKit.m
// HTMLKit
//
// Created by Iska on 14/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "NSCharacterSet+HTMLKit.h"
@implementation NSCharacterSet (HTMLKit)
+ (instancetype)htmlkit_HTMLWhitespaceCharacterSet
{
static NSCharacterSet *set = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSCharacterSet characterSetWithCharactersInString:@" \t\n\r\f"];
});
return set;
}
+ (instancetype)htmlkit_HTMLHexNumberCharacterSet
{
static NSCharacterSet *set = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEFabcdef"];
});
return set;
}
+ (instancetype)htmlkit_CSSNthExpressionCharacterSet
{
static NSCharacterSet *set = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSCharacterSet characterSetWithCharactersInString:@" 0123456789nN-+"];
});
return set;
}
@end

View File

@@ -0,0 +1,36 @@
//
// NSString+HTMLKit.m
// HTMLKit
//
// Created by Iska on 02/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "NSString+HTMLKit.h"
NS_INLINE BOOL isHtmlWhitespaceChar(unichar c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r';
}
@implementation NSString (HTMLKit)
- (BOOL)htmlkit_isHTMLWhitespaceString
{
return self.htmlkit_leadingHTMLWhitespaceLength == self.length;
}
- (NSUInteger)htmlkit_leadingHTMLWhitespaceLength
{
size_t idx = 0;
NSUInteger length = self.length;
while (idx < length) {
if (!isHtmlWhitespaceChar([self characterAtIndex:idx])) {
return idx;
}
idx++;
}
return idx;
}
@end

View File

@@ -0,0 +1,38 @@
//
// NSString+Private.m
// HTMLKit
//
// Created by Iska on 26.03.19.
// Copyright © 2019 BrainCookie. All rights reserved.
//
#import "NSString+Private.h"
@implementation NSString (Private)
- (BOOL)isEqualToStringIgnoringCase:(NSString *)aString
{
return [self caseInsensitiveCompare:aString] == NSOrderedSame;
}
- (BOOL)isEqualToAny:(NSString *)first, ... NS_REQUIRES_NIL_TERMINATION
{
va_list list;
va_start(list, first);
for (NSString *next = first; next != nil; next = va_arg(list, NSString *)) {
if ([self isEqualToString:next]) {
return YES;
}
}
va_end(list);
return NO;
}
- (BOOL)hasPrefixIgnoringCase:(NSString *)aString
{
NSRange reange = [self rangeOfString:aString
options:NSAnchoredSearch|NSCaseInsensitiveSearch];
return reange.location != NSNotFound;
}
@end

View File

@@ -0,0 +1,72 @@
//
// CSSAttributeSelector.h
// HTMLKit
//
// Created by Iska on 14/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Attribute Selector.
*/
@interface CSSAttributeSelector : CSSSelector
/**
The selector type.
*/
@property (nonatomic, assign, readonly) CSSAttributeSelectorType type;
/**
The attribute name which should be matched.
*/
@property (nonatomic, strong, readonly) NSString *name;
/**
The attribute value against which should be checked.
*/
@property (nonatomic, strong, readonly) NSString *value;
/**
Intializes and returns a CSS class selector.
@param className The class name to match.
@return A new instance of class selector.
*/
+ (instancetype)classSelector:(NSString *)className;
/**
Intializes and returns a CSS id selector.
@param elementId The element id to match.
@return A new instance of id selector.
*/
+ (instancetype)idSelector:(NSString *)elementId;
/**
Intializes and returns a CSS has-attribute selector.
@param attributeName The attribute name to match.
@return A new instance of has-attribute selector.
*/
+ (instancetype)hasAttributeSelector:(NSString *)attributeName;
/**
Intializes and returns a CSS attribute selector.
@param type The selector type.
@param name The attribute name to match.
@param value The value to match.
@return A new instance of attribute selector.
*/
- (instancetype)initWithType:(CSSAttributeSelectorType)type
attributeName:(NSString *)name
attrbiuteValue:(NSString *)value;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,144 @@
//
// CSSTokenizer CODEPOINTs.h
// HTMLKit
//
// Created by Iska on 08/06/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
//------------------------------------------------------
#define CODEPOINTS \
CODEPOINT( CONTROL, 0x0080 ) \
CODEPOINT( CHARACTER_TABULATION, 0x0009 ) \
CODEPOINT( LINE_FEED, 0x000A ) \
CODEPOINT( SPACE, 0x0020 ) \
CODEPOINT( QUOTATION_MARK, 0x0022 ) \
CODEPOINT( NUMBER_SIGN, 0x0023 ) \
CODEPOINT( DOLLAR_SIGN, 0x0024 ) \
CODEPOINT( APOSTROPHE, 0x0027 ) \
CODEPOINT( LEFT_PARENTHESIS, 0x0028 ) \
CODEPOINT( RIGHT_PARENTHESIS, 0x0029 ) \
CODEPOINT( ASTERIX, 0x002A ) \
CODEPOINT( PLUS_SIGN, 0x002B ) \
CODEPOINT( COMMA, 0x002C ) \
CODEPOINT( HYPHEN_MINUS, 0x002D ) \
CODEPOINT( FULL_STOP, 0x002E ) \
CODEPOINT( DIGIT_ZERO, 0x0030 ) \
CODEPOINT( DIGIT_NINE, 0x0039 ) \
CODEPOINT( COLON, 0x003A ) \
CODEPOINT( EQUALS_SIGN, 0x003D ) \
CODEPOINT( GREATER_THAN_SIGN, 0x003E ) \
CODEPOINT( LATIN_CAPITAL_LETTER_A, 0x0041 ) \
CODEPOINT( LATIN_CAPITAL_LETTER_F, 0x0046 ) \
CODEPOINT( LATIN_CAPITAL_LETTER_Z, 0x005A ) \
CODEPOINT( LEFT_SQUARE_BRACKET, 0x005B ) \
CODEPOINT( REVERSE_SOLIDUS, 0x005C ) \
CODEPOINT( RIGHT_SQUARE_BRACKET, 0x005D ) \
CODEPOINT( CIRCUMFLEX_ACCENT, 0x005E ) \
CODEPOINT( LOW_LINE, 0x005F ) \
CODEPOINT( LATIN_SMALL_LETTER_A, 0x0061 ) \
CODEPOINT( LATIN_SMALL_LETTER_F, 0x0066 ) \
CODEPOINT( LATIN_SMALL_LETTER_Z, 0x007A ) \
CODEPOINT( VERTICAL_LINE, 0x007C ) \
CODEPOINT( TILDE, 0x007E ) \
CODEPOINT( REPLACEMENT_CHARACTER, 0xFFFD )
#define CODEPOINT( name, value ) static UniChar const name = value;
CODEPOINTS
#undef CODEPOINT
NS_INLINE BOOL isWhitespace(UTF32Char codePoint)
{
return (codePoint == CHARACTER_TABULATION ||
codePoint == LINE_FEED ||
codePoint == SPACE);
}
NS_INLINE BOOL isCombinator(UTF32Char codePoint)
{
return (codePoint == SPACE ||
codePoint == PLUS_SIGN ||
codePoint == COMMA ||
codePoint == GREATER_THAN_SIGN ||
codePoint == TILDE);
}
NS_INLINE BOOL isDigit(UTF32Char codePoint)
{
return codePoint >= DIGIT_ZERO && codePoint <= DIGIT_NINE;
}
NS_INLINE BOOL isHexDigit(UTF32Char codePoint)
{
return ((codePoint >= DIGIT_ZERO && codePoint <= DIGIT_NINE) ||
(codePoint >= LATIN_CAPITAL_LETTER_A && codePoint <= LATIN_CAPITAL_LETTER_F) ||
(codePoint >= LATIN_SMALL_LETTER_A && codePoint <= LATIN_SMALL_LETTER_F));
}
NS_INLINE BOOL isQuote(UTF32Char codePoint)
{
return codePoint == QUOTATION_MARK || codePoint == APOSTROPHE;
}
NS_INLINE BOOL isNewLine(UTF32Char codePoint)
{
return codePoint == LINE_FEED;
}
NS_INLINE BOOL isNameStart(UTF32Char codePoint)
{
return ((codePoint >= LATIN_CAPITAL_LETTER_A && codePoint <= LATIN_CAPITAL_LETTER_Z) ||
(codePoint >= LATIN_SMALL_LETTER_A && codePoint <= LATIN_SMALL_LETTER_Z) ||
codePoint >= CONTROL ||
codePoint == LOW_LINE);
}
NS_INLINE BOOL isName(UTF32Char codePoint)
{
return isNameStart(codePoint) || isDigit(codePoint) || codePoint == HYPHEN_MINUS;
}
NS_INLINE BOOL isValidEscape(UTF32Char first, UTF32Char second)
{
if (first != REVERSE_SOLIDUS || isNewLine(second)) {
return false;
}
return YES;
}
NS_INLINE BOOL isValidEscapedCodePoint(UTF32Char codePoint)
{
return (codePoint != 0 &&
!(codePoint >= 0xD800 && codePoint <= 0x0DFFF) &&
codePoint <= 0x10FFFF);
}
NS_INLINE BOOL isValidIdentifierStart(UTF32Char first, UTF32Char second, UTF32Char third)
{
if (first == HYPHEN_MINUS) {
if (isNameStart(second) ||
second == HYPHEN_MINUS ||
isValidEscape(second, third)) {
return YES;
} else {
return NO;
}
} else if (isNameStart(first)) {
return YES;
} else if (first == REVERSE_SOLIDUS) {
return isValidEscape(first, second);
} else {
return NO;
}
}
NS_INLINE void AppendCodePoint(CFMutableStringRef string, UTF32Char codePoint)
{
UniChar pair[2];
Boolean isPair = CFStringGetSurrogatePairForLongCharacter(codePoint, pair);
CFStringAppendCharacters(string, pair, isPair ? 2 : 1);
}

View File

@@ -0,0 +1,52 @@
//
// CSSCombinatorSelector.h
// HTMLKit
//
// Created by Iska on 12/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Combinator Selector.
*/
@interface CSSCombinatorSelector : CSSSelector
/**
Initializes and returns a CSS child-of-element selector, e.g. 'div > p'
@param selector The selector matching the parent element.
@return A new instance of the child of element selector.
*/
+ (instancetype)childOfElementCombinator:(CSSSelector *)selector;
/**
Initializes and returns a CSS descendant-of-element selector, e.g. 'div p'
@param selector The selector matching the ancestor element.
@return A new instance of the descendant of element selector.
*/
+ (instancetype)descendantOfElementCombinator:(CSSSelector *)selector;
/**
Initializes and returns a CSS adjacent sibling selector, e.g. 'p + a'
@param selector The selector matching the adjacent sibling element.
@return A new instance of the adjacent sibling selector.
*/
+ (instancetype)adjacentSiblingCombinator:(CSSSelector *)selector;
/**
Initializes and returns a CSS general sibling selector, e.g. 'p ~ a'
@param selector The selector matching the general sibling element.
@return A new instance of the general sibling selector.
*/
+ (instancetype)generalSiblingCombinator:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,43 @@
//
// CSSCompoundSelector.h
// HTMLKit
//
// Created by Iska on 18/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
A Compound Selector, groups other selectors with a 'all-of' or 'any-of' relationship.
*/
@interface CSSCompoundSelector : CSSSelector
/**
Initializes and returns a new compound selector matching only elements that match all of the specified selectors.
@param selectors The selectors list.
@return A new instance of the All-Of selector.
*/
+ (instancetype)andSelector:(NSArray<CSSSelector *> *)selectors;
/**
Initializes and returns a new compound selector matching all elements that match at least one of the specified selectors.
@param selectors The selectors list.
@return A new instance of the Any-Of selector.
*/
+ (instancetype)orSelector:(NSArray<CSSSelector *> *)selectors;
/**
Add the specified selector to the compound.
@param selector The selector to add.
*/
- (void)addSelector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,62 @@
//
// CSSInputStream.h
// HTMLKit
//
// Created by Iska on 07/06/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLInputStreamReader.h"
/**
The CSS Inpute Stream.
Extends the HTML Input Stream with methods relevant to CSS selectors tokenizing/parsing.
*/
@interface CSSInputStream : HTMLInputStreamReader
/**
Consumes leading whitespace characters.
*/
- (void)consumeWhitespace;
/**
Consumes a CSS identifier.
http://www.w3.org/TR/css-syntax-3/#consume-an-ident-like-token
http://www.w3.org/TR/css-syntax-3/#consume-a-string-token
http://www.w3.org/TR/css-syntax-3/#would-start-an-identifier
@return A consumed identifier, `nil` if the stream doesn't start with a valid identifier.
*/
- (NSString *)consumeIdentifier;
/**
Consumes characters until the specified code-point is met.
@param endingCodePoint The code-point at which the input stream stops consuming.
@return The consumed string, `nil` nothing was consumed.
*/
- (NSString *)consumeStringWithEndingCodePoint:(UTF32Char)endingCodePoint;
/**
Consumes an escaped code point.
http://www.w3.org/TR/css-syntax-3/#consume-an-escaped-code-point
http://www.w3.org/TR/css-syntax-3/#starts-with-a-valid-escape
@return The value of the escaped code-point.
*/
- (UTF32Char)consumeEscapedCodePoint;
/**
Consumes a CSS selector combinator.
@return The consumed combinator, `nil` if nothing was consumed.
*/
- (NSString *)consumeCombinator;
@end

View File

@@ -0,0 +1,31 @@
//
// CSSNthExpression.h
// HTMLKit
//
// Created by Iska on 10/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CSSSelectors.h"
NS_ASSUME_NONNULL_BEGIN
/**
The Nth-Expression Parser.
Parses CSS nth-expressions, e.g. '-2n+3', 'odd', ...etc.
*/
@interface CSSNthExpressionParser : NSObject
/**
Parses a CSS nth-exrepssion string.
@param expression The expression string to parse.
@see CSSNthExpression
*/
+ (CSSNthExpression)parseExpression:(NSString *)expression;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,72 @@
//
// CSSNthExpressionSelector.h
// HTMLKit
//
// Created by Iska on 10/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Nth-Expression Selector.
*/
@interface CSSNthExpressionSelector : CSSSelector
/**
The pseudo-class name.
*/
@property (nonatomic, strong, readonly) NSString *className;
/**
The nth-expression.
@see CSSNthExpression
*/
@property (nonatomic, assign, readonly) CSSNthExpression expression;
/**
Initializes a new CSS nth-child selector, e.g. ':nth-child(2n+3)'
@param expression The nth-expression.
@return Nth-Child selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthChildSelector:(CSSNthExpression)expression;
/**
Initializes a new CSS nth-last-child selector, e.g. ':nth-last-child(2n+3)'
@param expression The nth-expression.
@return Nth-Last-Child selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthLastChildSelector:(CSSNthExpression)expression;
/**
Initializes a new CSS nth-of-type selector, e.g. ':nth-of-type(2n+3)'
@param expression The nth-expression.
@return Nth-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthOfTypeSelector:(CSSNthExpression)expression;
/**
Initializes a new CSS nth-last-of-type selector, e.g. ':nth-last-of-type(2n+3)'
@param expression The nth-expression.
@return Nth-Last-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
+ (instancetype)nthLastOfTypeSelector:(CSSNthExpression)expression;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,34 @@
//
// CSSPseudoClassSelector.h
// HTMLKit
//
// Created by Iska on 06/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
Base class for CSS Pseudo Class Selectors. This is just a simple named wrapper around another selector.
*/
@interface CSSPseudoClassSelector : CSSSelector
/**
The pseudo-class name.
*/
@property (nonatomic, strong, readonly) NSString *className;
/**
Initializes and return a new pseudo-class selector.
@param className The pseudo class name.
@param selector The underlying selector.
@return A new instance of a pseudo-class selector.
*/
- (instancetype)initWithClassName:(NSString *)className selector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,38 @@
//
// CSSPseudoFunctionSelector.h
// HTMLKit
//
// Created by Iska on 07/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Pseudo-Function Selector
*/
@interface CSSPseudoFunctionSelector : CSSSelector
/**
Initializes and returns a CSS nagation selector, e.g. ':not(div)'
@param selector The selector which should be negated.
@return A new instance of the negation selector.
*/
+ (instancetype)notSelector:(CSSSelector *)selector;
/**
Initializes and returns a CSS has-descendant selector, e.g. 'div:has(p)'
@discussion 'div:has(p)' matches all &lt;div&gt; elements which have a descendant &lt;p&gt; element.
@param selector The selector matching a descendant element.
@return A new instance of the has-descendant selector.
*/
+ (instancetype)hasSelector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,90 @@
//
// HTMLSelector.h
// HTMLKit
//
// Created by Iska on 02/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Attribute Selector Type
/**
Attribute selector type.
*/
typedef NS_ENUM(NSUInteger, CSSAttributeSelectorType)
{
/** Attribute exists: '[src]' */
CSSAttributeSelectorExists,
/** Attribute has exact value: '[title="HTMLKit"]' */
CSSAttributeSelectorExactMatch,
/** Attribute includes value: '[title~="foo"]' */
CSSAttributeSelectorIncludes,
/** Attribute's value begins with: '[title^="HTML"]' */
CSSAttributeSelectorBegins,
/** Attribute's value ends with: '[title$="Kit"]' */
CSSAttributeSelectorEnds,
/** Attribute's value ends with: '[title*="ML"]' */
CSSAttributeSelectorContains,
/** Attribute's value ends with: '[title|="en"]' */
CSSAttributeSelectorHyphen,
/** Attribute's value does not equal: '[title!="foo"]' */
CSSAttributeSelectorNot
};
#pragma mark - CSS Nth-Expression
/**
CSS Nth-Expression
*/
typedef struct CSSNthExpression
{
NSInteger an;
NSInteger b;
} CSSNthExpression;
NS_INLINE CSSNthExpression CSSNthExpressionMake(NSInteger an, NSInteger b) {
return (CSSNthExpression){ .an = an, .b = b };
}
extern const CSSNthExpression CSSNthExpressionOdd;
extern const CSSNthExpression CSSNthExpressionEven;
extern NSString * _Nonnull NSStringFromNthExpression(CSSNthExpression expression);
#pragma mark - Base Selector Class
@class HTMLElement;
/**
Base class for all CSS Selector implementations
*/
@interface CSSSelector : NSObject
/**
Initializes and returns a new instance of CSS Selector.
@param string The selector string which will be parsed.
@return A new instance of a parsed CSS Selector, `nil` if the string is not a valid selector string.
*/
+ (nullable instancetype)selectorWithString:(NSString *)string;
/**
Implementations should override this method to provide the selector-sprecific logic for matching elements.
@abstract Use one of the concrete subclasses.
*/
- (BOOL)acceptElement:(HTMLElement *)element;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,31 @@
//
// CSSSelectorBlock.h
// HTMLKit
//
// Created by Iska on 20/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
@class HTMLElement;
/**
A block-based CSS Selector implementation
*/
@interface CSSSelectorBlock : CSSSelector
/**
Initializes and returns a new block-based selector.
@param name The name of the selector.
@param block The block that should match desired elements.
@return A new instance of the block-based selector.
*/
- (instancetype)initWithName:(NSString *)name block:(BOOL (^)(HTMLElement *))block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,36 @@
//
// CSSSelectorParser.h
// HTMLKit
//
// Created by Iska on 02/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class CSSSelector;
/**
The CSS Selectors Parser.
Parses CSS Level 3 Selectors:
http://www.w3.org/TR/css3-selectors/
*/
@interface CSSSelectorParser : NSObject
/**
Parses a CSS3 selector string.
@param string The CSS3 selector string.
@param error If an error occurs, upon return contains an `NSError` object that describes the problem.
@return A parsed CSSSelector, `nil` if an error occurred.
@see CSSelector
*/
+ (nullable CSSSelector *)parseSelector:(NSString *)string error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,277 @@
//
// CSSSelectors.h
// HTMLKit
//
// Created by Iska on 14/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "CSSSelector.h"
#import "CSSTypeSelector.h"
#import "CSSAttributeSelector.h"
#import "CSSPseudoClassSelector.h"
#import "CSSPseudoFunctionSelector.h"
#import "CSSNthExpressionSelector.h"
#import "CSSCombinatorSelector.h"
#import "CSSCompoundSelector.h"
#import "CSSSelectorBlock.h"
#import "CSSStructuralPseudoSelectors.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Type Selectors
/**
Universal CSS selector: '*'
@return The universal CSS selector.
*/
extern CSSSelector * universalSelector(void);
/**
CSS type selector, e.g. 'div', 'p', ...etc.
@param type The element type.
@return Type selector for the specified type.
*/
extern CSSSelector * typeSelector(NSString *type);
#pragma mark - Atribute Selectors
/**
CSS id selector, e.g. '#someId'
@param elementId The element id.
@return Id selector for the specified element id.
*/
extern CSSSelector * idSelector(NSString *elementId);
/**
CSS class selector, e.g. '.someClass'
@param className The class name.
@return Class selector for the specified class name.
*/
extern CSSSelector * classSelector(NSString *className);
/**
CSS has-attribute selector, e.g. '[href]'
@param attribute The attribute.
@return Has-Attribute selector for the specified attribute.
*/
extern CSSSelector * hasAttributeSelector(NSString *attribute);
/**
CSS attribute selector, e.g. '[src*="html"]', '[class^="top"]', '[title&="HTML"]', ...etc.
@param type The attribute selector type.
@param attribute The attribute.
@param value The value of the attribute.
@return Attribute selector.
@see CSSAttributeSelectorType
*/
extern CSSSelector * attributeSelector(CSSAttributeSelectorType type,
NSString *attribute,
NSString *value);
#pragma mark - Nth-Expression Selectors
/**
CSS nth-child selector, e.g. ':nth-child(2n+3)'
@param expression The nth-expression.
@return Nth-Child selector for the specified expression.
@see CSSNthExpression
*/
extern CSSSelector * nthChildSelector(CSSNthExpression expression);
/**
CSS nth-last-child selector, e.g. ':nth-last-child(2n+3)'
@param expression The nth-expression.
@return Nth-Last-Child selector for the specified expression.
@see CSSNthExpression
*/
extern CSSSelector * nthLastChildSelector(CSSNthExpression expression);
/**
CSS nth-of-type selector, e.g. ':nth-of-type(2n+3)'
@param expression The nth-expression.
@return Nth-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
extern CSSSelector * nthOfTypeSelector(CSSNthExpression expression);
/**
CSS nth-last-of-type selector, e.g. ':nth-last-of-type(2n+3)'
@param expression The nth-expression.
@return Nth-Last-Of-Type selector for the specified expression.
@see CSSNthExpression
*/
extern CSSSelector * nthLastOfTypeSelector(CSSNthExpression expression);
/**
CSS odd-child selector: ':nth-child(odd)'
This is analogous to ':nth-child(2n+1)'
@return Odd-Child selector.
*/
extern CSSSelector * oddSelector(void);
/**
CSS even-child selector: ':nth-child(even)'
This is analogous to ':nth-child(2n)'
@return Even-Child selector.
*/
extern CSSSelector * evenSlector(void);
/**
CSS first-child selector: ':nth-child(1)'
@return First-Child selector.
*/
extern CSSSelector * firstChildSelector(void);
/**
CSS first-child selector: ':nth-last-child(1)'
@return First-Child selector.
*/
extern CSSSelector * lastChildSelector(void);
/**
CSS first-of-type selector: ':nth-first-of-type(1)'
@return First-Of-Type selector.
*/
extern CSSSelector * firstOfTypeSelector(void);
/**
CSS last-of-type selector: ':nth-last-of-type(1)'
@return Last-Of-Type selector.
*/
extern CSSSelector * lastOfTypeSelector(void);
/**
CSS only-child selector: ':first-child:last-child'
@return Only-Child selector.
*/
extern CSSSelector * onlyChildSelector(void);
/**
CSS only-of-type selector: ':first-of-type:last-of-type'
@return Only-Of-Type selector.
*/
extern CSSSelector * onlyOfTypeSelector(void);
#pragma mark - Combinators
/**
CSS child-of-element selector, e.g. 'div > p'
@param selector The selector matching the parent element.
@return A child of element selector.
*/
extern CSSSelector * childOfElementSelector(CSSSelector *selector);
/**
CSS descendant-of-element selector, e.g. 'div p'
@param selector The selector matching the ancestor element.
@return A descendant of element selector.
*/
extern CSSSelector * descendantOfElementSelector(CSSSelector *selector);
/**
CSS adjacent sibling selector, e.g. 'p + a'
@param selector The selector matching the adjacent sibling element.
@return A adjacent sibling selector.
*/
extern CSSSelector * adjacentSiblingSelector(CSSSelector *selector);
/**
CSS general sibling selector, e.g. 'p ~ a'
@param selector The selector matching the general sibling element.
@return A general sibling selector.
*/
extern CSSSelector * generalSiblingSelector(CSSSelector *selector);
#pragma mark - Pseudo Functions
/**
CSS nagation selector: ':not(div)'
@param selector The selector which should be negated.
@return A negation selector.
*/
extern CSSSelector * not(CSSSelector *selector);
/**
CSS has-descendant selector, e.g. 'div:has(p)'
@discussion 'div:has(p)' matches all &lt;div&gt; elements which have a descendant &lt;p&gt; element.
@param selector The selector matching a descendant element.
@return A has-descendant selector.
*/
extern CSSSelector * has(CSSSelector *selector);
#pragma mark - Compound Selectors
/**
A compound selector matching only elements that match all of the specified selectors.
@param selectors The selectors list.
@return All-Of selector.
*/
extern CSSSelector * allOf(NSArray<CSSSelector *> *selectors);
/**
A compound selector matching all elements that match at least one of the specified selectors.
@param selectors The selectors list.
@return Any-Of selector.
*/
extern CSSSelector * anyOf(NSArray<CSSSelector *> *selectors);
#pragma mark - Pseudo
/**
Creates a new named-pseudo selector.
@discussion The name specified when creating a selector is prefixed with colon.
@param name The name of the selector.
@param selector The underlying selector.
@return A named-pseudo selector.
*/
extern CSSSelector * namedPseudoSelector(NSString *name, CSSSelector *selector);
#pragma mark - Block
/**
Creates a new named selector with a specified block.
@param name The name of the selector.
@param acceptBlock The block which provides the implementation for the accept-element logic.
@return A named-block selector.
*/
extern CSSSelector * namedBlockSelector(NSString *name, BOOL (^ acceptBlock)(HTMLElement *element));
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,147 @@
//
// CSSStructuralPseudoSelector.h
// HTMLKit
//
// Created by Iska on 11/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
@class CSSSelector;
NS_ASSUME_NONNULL_BEGIN
/**
@return Root element selector: ':root'
*/
extern CSSSelector * rootSelector(void);
/**
@return Empy element selector: ':empty'
*/
extern CSSSelector * emptySelector(void);
/**
@return A parent element selector: ':parent'
*/
extern CSSSelector * parentSelector(void);
/**
@return A button element selector: ':button'
*/
extern CSSSelector * buttonSelector(void);
/**
@return A checkbox element selector: ':checkbox'
*/
extern CSSSelector * checkboxSelector(void);
/**
@return A file element selector: ':file'
*/
extern CSSSelector * fileSelector(void);
/**
@return A header element selector: ':header'
*/
extern CSSSelector * headerSelector(void);
/**
@return An image element selector: ':image'
*/
extern CSSSelector * imageSelector(void);
/**
@return A parent element selector: ':parent'
*/
extern CSSSelector * inputSelector(void);
/**
@return A link element selector: ':link'
*/
extern CSSSelector * linkSelector(void);
/**
@return A password element selector: ':password'
*/
extern CSSSelector * passwordSelector(void);
/**
@return A radio element selector: ':radio'
*/
extern CSSSelector * radioSelector(void);
/**
@return A reset element selector: ':reset'
*/
extern CSSSelector * resetSelector(void);
/**
@return A submit element selector: ':submit'
*/
extern CSSSelector * submitSelector(void);
/**
@return A text element selector: ':text'
*/
extern CSSSelector * textSelector(void);
/**
@return An enabled element selector: ':enabled'
*/
extern CSSSelector * enabledSelector(void);
/**
@return A disabled element selector: ':disabled'
*/
extern CSSSelector * disabledSelector(void);
/**
@return A checked element selector: ':checked'
*/
extern CSSSelector * checkedSelector(void);
/**
@return An optional element selector: ':optional'
*/
extern CSSSelector * optionalSelector(void);
/**
@return A required element selector: ':required'
*/
extern CSSSelector * requiredSelector(void);
/**
Less-than selector, e.g. 'lt(2)'
Selects all elements at an index less than the specified index. A negative index counts backwards from the last element.
@param index The zero-based index of the element to match.
@return A Less-Than selector.
*/
extern CSSSelector * ltSelector(NSInteger index);
/**
Greater-than selector, e.g. 'gt(2)'
Selects all elements at an index greater than the specified index. A negative index counts backwards from the
last element.
@param index The zero-based index of the element to match.
@return A Greater-Than selector.
*/
extern CSSSelector * gtSelector(NSInteger index);
/**
Equal selector, e.g. 'eq(3)'
Selects the element at the specified index. A negative index counts backwards from the last element.
@param index The zero-based index of the element to match.
@return An Equal selector.
*/
extern CSSSelector * eqSelector(NSInteger index);
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,41 @@
//
// CSSTypeSelector.h
// HTMLKit
//
// Created by Iska on 13/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CSSSelector.h"
NS_ASSUME_NONNULL_BEGIN
/**
CSS Type Selector.
*/
@interface CSSTypeSelector : CSSSelector
/**
The type of elements being matched.
*/
@property (nonatomic, strong, readonly) NSString *type;
/**
Returns the universal selector.
@return A new instance of a universal selector that matches all elements.
*/
+ (instancetype)universalSelector;
/**
Initializes a new selector for the specified type.
@param type The type of elements that should be matched.
@return A new instance of a type selector.
*/
- (instancetype)initWithType:(NSString *)type;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,28 @@
//
// HTMLCharacterData+Private.h
// HTMLKit
//
// Created by Iska on 26/11/16.
// Copyright © 2016 BrainCookie. All rights reserved.
//
#import "HTMLCharacterData.h"
#import "HTMLNode.h"
/**
Private HTML Character Data methods which are not intended for public API.
*/
@interface HTMLCharacterData ()
/**
Designated initializer of the HTML CharacterData, which, however, should not be used directly. It is intended to be
called only by subclasses, i.e. HTMLText and HTMLComment.
@param name The node's name.
@param type The node's type.
@param data The node's data string.
@return A new instance of a HTML CharacterData.
*/
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type data:(NSString *)data NS_DESIGNATED_INITIALIZER;
@end

View File

@@ -0,0 +1,32 @@
//
// HTMLCharacterData.h
// HTMLKit
//
// Created by Iska on 26/11/16.
// Copyright © 2016 BrainCookie. All rights reserved.
//
#import "HTMLNode.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML CharacterData
https://dom.spec.whatwg.org/#characterdata
*/
@interface HTMLCharacterData : HTMLNode
/** @brief The associated data string. */
@property (nonatomic, copy, readonly) NSString *data;
- (void)setData:(NSString *)data;
- (void)appendData:(NSString *)data;
- (void)insertData:(NSString *)data atOffset:(NSUInteger)offset;
- (void)deleteDataInRange:(NSRange)range;
- (void)replaceDataInRange:(NSRange)range withData:(NSString *)data;
- (NSString *)substringDataWithRange:(NSRange)range;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,81 @@
//
// HTMLCharacterToken.h
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
/**
HTML Character Token
*/
@interface HTMLCharacterToken : HTMLToken
/** @brief The characters in this token. */
@property (nonatomic, copy) NSString *characters;
/**
Initializes a new character token.
@param string The string with which to initialize the token.
@return A new instance of a character token.
*/
- (instancetype)initWithString:(NSString *)string;
/**
Appends the given string to this token.
@param string The string to append.
*/
- (void)appendString:(NSString *)string;
/**
Checks whether this token is a whitespace character token.
@discussion HTML whitespace characters are: CHARACTER TABULATION U+0009, LINE FEED U+000A, FORM FEED U+000C,
CARRIAGE RETURN U+000D, and SPACE U+0020
@return `YES` if this token contains only whitespace characters, `NO` otherwise.
*/
- (BOOL)isWhitespaceToken;
/**
Checks whether this token is empty.
@return `YES` if this token is empty, `NO` otherwise.
*/
- (BOOL)isEmpty;
/**
Retains all leading whitespace characters in this token.
*/
- (void)retainLeadingWhitespace;
/**
Trims all leading whitespace characters in this token.
*/
- (void)trimLeadingWhitespace;
/**
Trims the characters in this token from a given index
@param index The start index from which to trim the token.
*/
- (void)trimFormIndex:(NSUInteger)index;
/**
Splits this token retaining only characters after the leading whitespace. The leading whitespace characters are then
returned a new characters token.
@return A characters token with leading whitespace characters. Returns 'nil` if no leading whitespace exists.
*/
- (HTMLCharacterToken *)tokenBySplitingLeadingWhiteSpace;
@end

View File

@@ -0,0 +1,28 @@
//
// HTMLComment.h
// HTMLKit
//
// Created by Iska on 25/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLCharacterData.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Comment node
*/
@interface HTMLComment : HTMLCharacterData
/**
Initializes a new HTML comment node.
@param data The comment string.
@return A new isntance of a HTML comment node.
*/
- (instancetype)initWithData:(NSString *)data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,39 @@
//
// HTMLCommentToken.h
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
/**
HTML Comment Token
*/
@interface HTMLCommentToken : HTMLToken
/** @brief The comment string in this token. */
@property (nonatomic, copy) NSString *data;
/**
Initializes a new comment token.
@param data The string with which to initialize the token.
@return A new instance of a comment token.
*/
- (instancetype)initWithData:(NSString *)data;
/**
Appends the given string to this token.
@param string The string to append.
*/
- (void)appendStringToData:(NSString *)string;
@end

View File

@@ -0,0 +1,62 @@
//
// HTMLDOCTYPEToken.h
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLToken.h"
/**
HTML DOCTYPE Token
*/
@interface HTMLDOCTYPEToken : HTMLToken
/** @brief The DOCTYPE's name. */
@property (nonatomic, copy) NSString *name;
/** @brief The DOCTYPE's public identifier. */
@property (nonatomic, strong) NSMutableString *publicIdentifier;
/** @brief The DOCTYPE's system identifier. */
@property (nonatomic, strong) NSMutableString *systemIdentifier;
/** @brief Flag whether this DOCTYPE forces quirks mode. */
@property (nonatomic, assign) BOOL forceQuirks;
/**
Initializes a new DOCTYPE token.
@param name The name with which to initialize the token.
@return A new instance of a DOCTYPE token.
*/
- (instancetype)initWithName:(NSString *)name;
/**
Appends the given string to this DOCTYPE's name.
@param string The string to append.
*/
- (void)appendStringToName:(NSString *)string;
/**
Appends the given string to this DOCTYPE's public identifier.
@param string The string to append.
*/
- (void)appendStringToPublicIdentifier:(NSString *)string;
/**
Appends the given string to this DOCTYPE's system identifier.
@param string The string to append.
*/
- (void)appendStringToSystemIdentifier:(NSString *)string;
@end

View File

@@ -0,0 +1,29 @@
//
// HTMLNodes.h
// HTMLKit
//
// Created by Iska on 27/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLNode.h"
#import "HTMLDocument.h"
#import "HTMLDocumentType.h"
#import "HTMLDocumentFragment.h"
#import "HTMLElement.h"
#import "HTMLCharacterData.h"
#import "HTMLComment.h"
#import "HTMLText.h"
#import "HTMLTemplate.h"
#import "HTMLRange.h"
#import "HTMLDOMTokenList.h"
#import "HTMLNodeIterator.h"
#import "HTMLTreeVisitor.h"
#import "HTMLTreeWalker.h"
#import "HTMLNodeFilter.h"
#import "HTMLKitDOMExceptions.h"
#import "HTMLNamespaces.h"
#import "HTMLQuirksMode.h"
#import "HTMLOrderedDictionary.h"

View File

@@ -0,0 +1,108 @@
//
// HTMLDOMTokenList.h
// HTMLKit
//
// Created by Iska on 30/11/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class HTMLElement;
/**
A HTML DOM Token List.
The DOM Token List is used for manipulating an element's attributes that contain muliplte values separated by a space.
https://dom.spec.whatwg.org/#interface-domtokenlist
*/
@interface HTMLDOMTokenList : NSObject
/** @brief The associated context element. */
@property (nonatomic, strong, readonly) HTMLElement *element;
/** @brief The associated attribute. */
@property (nonatomic, strong, readonly) NSString *attribute;
/**
Initializes a new DOM token list.
@param element The associated context element.
@param attribute The associated attribute.
@param value The initial attribute's value.
@return A new instance of the DOM token list.
*/
- (instancetype)initWithElement:(HTMLElement *)element attribute:(NSString *)attribute value:(NSString *)value;
/**
@return The length of this token list
*/
- (NSUInteger)length;
/**
Checks whether this list contains the given token.
@param token The token.
@return `YES` if the given token is in this list, `NO` otherwise.
*/
- (BOOL)contains:(NSString *)token;
/**
Add the given tokens to the list.
@param tokens The tokens to add.
*/
- (void)add:(NSArray<NSString *> *)tokens;
/**
Removes the given tokens from the list.
@param tokens The tokens to remove.
*/
- (void)remove:(NSArray<NSString *> *)tokens;
/**
Toggles the given token.
@param token The token to toggle.
@return `YES` if the token was added to the list, `NO` if it was removed from it.
*/
- (BOOL)toggle:(NSString *)token;
/**
Replaces the given token with new token.
@param token The token to replace.
@param newToken The replacement token.
*/
- (void)replaceToken:(NSString *)token withToken:(NSString *)newToken;
/**
Returns the value of the token at the given index.
@param index The index at which to return the token.
@return The token at the given index. If index is greater than or equal to the value returned by count, an
NSRangeException is raised.
*/
- (NSString *)objectAtIndexedSubscript:(NSUInteger)index;
/**
Set the token at the given index.
@param obj The token to set.
@param index The index at which to set the token. If index is greater than or equal to the value returned by count, an
NSRangeException is raised.
*/
- (void)setObject:(NSString *)obj atIndexedSubscript:(NSUInteger)index;
/**
@return The string representation of this token list, which can be used as the attribute's value.
*/
- (NSString *)stringify;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,15 @@
//
// HTMLDOMUtils.h
// HTMLKit
//
// Created by Iska on 03/12/16.
// Copyright © 2016 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "HTMLDOM.h"
@class HTMLNode;
extern HTMLNode * GetCommonAncestorContainer(HTMLNode *nodeA, HTMLNode *nodeB);
extern NSArray<HTMLNode *> * GetAncestorNodes(HTMLNode *node);

View File

@@ -0,0 +1,99 @@
//
// HTMLDocument+Private.h
// HTMLKit
//
// Created by Iska on 27/11/16.
// Copyright © 2016 BrainCookie. All rights reserved.
//
#import "HTMLDocument.h"
#import "HTMLNode.h"
#import "HTMLCharacterData.h"
#import "HTMLNodeIterator.h"
#import "HTMLRange.h"
#import "HTMLText.h"
/**
Private HTML Document methods which are not intended for public API.
*/
@interface HTMLDocument ()
@property (nonatomic, assign) HTMLDocumentReadyState readyState;
/**
Runs the necessary steps after removing a node from the DOM.
@param oldNode The old node that was removed.
@param oldParent The old parent of the node that was removed.
@param oldPreviousSibling The old previous sibling node of the node that was removed.
*/
- (void)runRemovingStepsForNode:(HTMLNode *)oldNode
withOldParent:(HTMLNode *)oldParent
andOldPreviousSibling:(HTMLNode *)oldPreviousSibling;
/**
Attaches a node iterator to this document.
@param iterator The iterator to attach.
*/
- (void)attachNodeIterator:(HTMLNodeIterator *)iterator;
/**
Detaches a node interator from this document.
@param iterator The iterator to detach.
*/
- (void)detachNodeIterator:(HTMLNodeIterator *)iterator;
/**
Attaches a range to this document.
@param range The range to attach.
*/
- (void)attachRange:(HTMLRange *)range;
/**
Detaches a range from this document.
@param range The range to detach.
*/
- (void)detachRange:(HTMLRange *)range;
/**
Callback on removing text from a CharacterData node.
@param node The CharacterData node.
@param offset The offset at which the data was removed.
@param length The length of the data that was removed.
*/
- (void)didRemoveCharacterDataInNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length;
/**
Callback on adding text from a CharacterData node.
@param node The CharacterData node.
@param offset The offset at which the data was added.
@param length The length of the data that was added.
*/
- (void)didAddCharacterDataToNode:(HTMLCharacterData *)node atOffset:(NSUInteger)offset withLength:(NSUInteger)length;
/**
Callback on inserting a new text node when an old text node is split.
@param newNode The new text node after splitting.
@param parent The parent where newNode was inserted.
@param node The old text node that was split.
@param offset The offset of splitting.
*/
- (void)didInsertNewTextNode:(HTMLText *)newNode intoParent:(HTMLNode *)parent afterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset;
/**
Callback for clamping current ranges whose end boundary is after the text node upon splitting it.
@param node The text node that was split.
@param offset The offset of splitting
*/
- (void)clampRangesAfterSplittingTextNode:(HTMLText *)node atOffset:(NSUInteger)offset;
@end

View File

@@ -0,0 +1,98 @@
//
// 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"
NS_ASSUME_NONNULL_BEGIN
/**
The document's ready state. The document is `Loading` while being parsed, `Complete` otherwise. The `Interactive` state
is not supported.
*/
typedef NS_ENUM(short, HTMLDocumentReadyState)
{
HTMLDocumentLoading,
HTMLDocumentInteractive, // Not used
HTMLDocumentComplete
};
/**
The HTML Document. This is the root of a parsed DOM tree.
https://html.spec.whatwg.org/multipage/dom.html#documents
*/
@interface HTMLDocument : HTMLNode
/**
The document's DOCTYPE.
@see HTMLDocumentType
*/
@property (nonatomic, strong, nullable) HTMLDocumentType *documentType;
/**
The document's quirks mode.
@see HTMLQuirksMode
*/
@property (nonatomic, assign) HTMLQuirksMode quirksMode;
/**
The document's ready state.
@see HTMLDocumentReadyState
*/
@property (nonatomic, assign, readonly) HTMLDocumentReadyState readyState;
/**
The document's root element, which is the first element in tree order, if any. Usually it is the <html> element.
*/
@property (nonatomic, strong, nullable) HTMLElement *rootElement;
/**
The document element, i.e. the <html> element, if it exists.
*/
@property (nonatomic, strong, nullable) HTMLElement *documentElement;
/**
The document's <head> element, if it exists.
*/
@property (nonatomic, strong, nullable) HTMLElement *head;
/**
The document's <body> element, if it exists.
*/
@property (nonatomic, strong, nullable) HTMLElement *body;
/**
Retunrs a new HTML Document instance with the given HTML string.
@param string The HTML string to parse into a document.
*/
+ (instancetype)documentWithString:(NSString *)string;
/**
Adopts a given node into this document, i.e. the document becomes the new owner of the node. Raises a HTMLKitNotSupportedError
exception if node is an instance of HTMLDocument.
@param node The node to adopt.
@return The adopted node
*/
- (HTMLNode *)adoptNode:(HTMLNode *)node;
/**
Returns the associated HTML Document proxy instance, which owns the template contents of all its template elements.
https://html.spec.whatwg.org/multipage/scripting.html#associated-inert-template-document
*/
- (HTMLDocument *)associatedInertTemplateDocument;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,31 @@
//
// HTMLDocumentFragment.h
// HTMLKit
//
// Created by Iska on 12/04/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLNode.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Document Fragment. Represents a minimal document object that has no parent. It is used as a light-weight
version of Document
https://dom.spec.whatwg.org/#interface-documentfragment
*/
@interface HTMLDocumentFragment : HTMLNode
/**
Initializes a new document fragment with the given document as owner.
@param document The owner document.
@return A new instance of a document fragment.
*/
- (instancetype)initWithDocument:(nullable HTMLDocument *)document;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,64 @@
//
// HTMLDocumentType.h
// HTMLKit
//
// Created by Iska on 25/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLNode.h"
#import "HTMLQuirksMode.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Document Type node. There is only one valid document type, which is `<!DOCTYPE html>`.
Other DOCTYPES, e.g. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
are obsolete but permitted.
https://dom.spec.whatwg.org/#interface-documenttype
*/
@interface HTMLDocumentType : HTMLNode
/**
The public identifier
*/
@property (nonatomic, copy, readonly) NSString *publicIdentifier;
/**
The system identifier
*/
@property (nonatomic, copy, readonly) NSString *systemIdentifier;
/**
Initializes and returns a new isntance of a Document Type node.
@param name The name.
@param publicIdentifier The public identifier.
@param systemIdentifier The system identigier
@return A new document type instance.
*/
- (instancetype)initWithName:(NSString *)name
publicIdentifier:(nullable NSString *)publicIdentifier
systemIdentifier:(nullable NSString *)systemIdentifier;
/**
Checks whether this DOCTYPE is valid.
@return `YES` if this is a valid DOCTYPE, `NO` otherwise.
*/
- (BOOL)isValid;
/**
Return the quirks mode of this DOCTYPE.
@return The quirks mode.
@see HTMLQuirksMode
*/
- (HTMLQuirksMode)quirksMode;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,23 @@
//
// HTMLEOFToken.h
// HTMLKit
//
// Created by Iska on 15/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import "HTMLToken.h"
/**
A HTML EOF Token.
*/
@interface HTMLEOFToken : HTMLToken
/** Returns the singleton instance of the EOF Token. */
+ (instancetype)token;
@end

View File

@@ -0,0 +1,123 @@
//
// 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"
#import "HTMLDOMTokenList.h"
NS_ASSUME_NONNULL_BEGIN
/**
A HTML Element.
https://html.spec.whatwg.org/multipage/dom.html#elements
https://html.spec.whatwg.org/multipage/syntax.html#elements-2
*/
@interface HTMLElement : HTMLNode
/**
The namesapce of this element.
@see HTMLNamespace
*/
@property (nonatomic, assign, readonly) HTMLNamespace htmlNamespace;
/**
The elemen's tag name.
*/
@property (nonatomic, copy, readonly) NSString *tagName;
/**
The elemen's id attribute value. Empty string if the element has no id attribute.
*/
@property (nonatomic, copy) NSString *elementId;
/**
The elemen's class attribute value. Empty string if the element has no class attribute.
*/
@property (nonatomic, copy) NSString *className;
/**
The element's class attribute as a DOM Token List
@see HTMLDOMTokenList
*/
@property (nonatomic, strong, readonly) HTMLDOMTokenList *classList;
/**
The element's attribites.
*/
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *attributes;
/**
@warning Use one of the initWithTagName: methods instead.
*/
- (instancetype)init NS_UNAVAILABLE;
/**
Initializes a new HTML element with the given tag name.
@param tagName The tag name.
@return A new HTML element.
*/
- (instancetype)initWithTagName:(NSString *)tagName;
/**
Initializes a new HTML element with the given tag name and attributes.
@param tagName The tag name.
@param attributes The attributes.
@return A new HTML element.
*/
- (instancetype)initWithTagName:(NSString *)tagName attributes:(nullable NSDictionary<NSString *, NSString *> *)attributes;
/**
Initializes a new HTML element with the given tag name, namespace, and attributes.
@param tagName The tag name.
@param htmlNamespace The HTML namespace.
@param attributes The attributes.
@return A new HTML element.
*/
- (instancetype)initWithTagName:(NSString *)tagName namespace:(HTMLNamespace)htmlNamespace attributes:(nullable NSDictionary<NSString *, NSString *> *)attributes;
/**
Checks whether this element has an attribute with the given name.
@param name The attribute name.
@return `YES` if the element has such an attributes, `NO` otherwise.
*/
- (BOOL)hasAttribute:(NSString *)name;
/**
Returns the value of the attribute with the given name.
@param name The attribute's name.
@return The attribute's value, `nil` if the element doesn't have such attribute.
*/
- (nullable NSString *)objectForKeyedSubscript:(NSString *)name;
/**
Set the value of the attribute with the given name.
@param value The value to set.
@param attribute The attribute's name.
*/
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute;
/**
Removes the attribute with the given name.
@param name The attribute to remove.
*/
- (void)removeAttribute:(NSString *)name;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,142 @@
//
// HTMLElementAdjustment.h
// HTMLKit
//
// Created by Iska on 14/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import "HTMLElement.h"
#import "HTMLTokens.h"
#import "HTMLNamespaces.h"
#import "NSString+HTMLKit.h"
NS_INLINE void AdjustMathMLAttributes(HTMLTagToken *token)
{
NSString *lowercase = token.attributes[@"definitionurl"];
if (lowercase != nil) {
[token.attributes replaceKey:@"definitionurl" withKey:@"definitionURL"];
}
}
NS_INLINE void AdjustSVGAttributes(HTMLTagToken *token)
{
if (token.attributes == nil) {
return;
}
NSDictionary *replacements = @{@"attributename": @"attributeName",
@"attributetype": @"attributeType",
@"basefrequency": @"baseFrequency",
@"baseprofile": @"baseProfile",
@"calcmode": @"calcMode",
@"clippathunits": @"clipPathUnits",
@"diffuseconstant": @"diffuseConstant",
@"edgemode": @"edgeMode",
@"filterunits": @"filterUnits",
@"glyphref": @"glyphRef",
@"gradienttransform": @"gradientTransform",
@"gradientunits": @"gradientUnits",
@"kernelmatrix": @"kernelMatrix",
@"kernelunitlength": @"kernelUnitLength",
@"keypoints": @"keyPoints",
@"keysplines": @"keySplines",
@"keytimes": @"keyTimes",
@"lengthadjust": @"lengthAdjust",
@"limitingconeangle": @"limitingConeAngle",
@"markerheight": @"markerHeight",
@"markerunits": @"markerUnits",
@"markerwidth": @"markerWidth",
@"maskcontentunits": @"maskContentUnits",
@"maskunits": @"maskUnits",
@"numoctaves": @"numOctaves",
@"pathlength": @"pathLength",
@"patterncontentunits": @"patternContentUnits",
@"patterntransform": @"patternTransform",
@"patternunits": @"patternUnits",
@"pointsatx": @"pointsAtX",
@"pointsaty": @"pointsAtY",
@"pointsatz": @"pointsAtZ",
@"preservealpha": @"preserveAlpha",
@"preserveaspectratio": @"preserveAspectRatio",
@"primitiveunits": @"primitiveUnits",
@"refx": @"refX",
@"refy": @"refY",
@"repeatcount": @"repeatCount",
@"repeatdur": @"repeatDur",
@"requiredextensions": @"requiredExtensions",
@"requiredfeatures": @"requiredFeatures",
@"specularconstant": @"specularConstant",
@"specularexponent": @"specularExponent",
@"spreadmethod": @"spreadMethod",
@"startoffset": @"startOffset",
@"stddeviation": @"stdDeviation",
@"stitchtiles": @"stitchTiles",
@"surfacescale": @"surfaceScale",
@"systemlanguage": @"systemLanguage",
@"tablevalues": @"tableValues",
@"targetx": @"targetX",
@"targety": @"targetY",
@"textlength": @"textLength",
@"viewbox": @"viewBox",
@"viewtarget": @"viewTarget",
@"xchannelselector": @"xChannelSelector",
@"ychannelselector": @"yChannelSelector",
@"zoomandpan": @"zoomAndPan"};
HTMLOrderedDictionary *adjusted = [HTMLOrderedDictionary new];
for (id key in token.attributes) {
NSString *replacement = replacements[key] ?: key;
adjusted[replacement] = token.attributes[key];
}
token.attributes = adjusted;
}
NS_INLINE void AdjustSVGNameCase(HTMLTagToken *token)
{
NSDictionary *replacements = @{
@"altglyph": @"altGlyph",
@"altglyphdef": @"altGlyphDef",
@"altglyphitem": @"altGlyphItem",
@"animatecolor": @"animateColor",
@"animatemotion": @"animateMotion",
@"animatetransform": @"animateTransform",
@"clippath": @"clipPath",
@"feblend": @"feBlend",
@"fecolormatrix": @"feColorMatrix",
@"fecomponenttransfer": @"feComponentTransfer",
@"fecomposite": @"feComposite",
@"feconvolvematrix": @"feConvolveMatrix",
@"fediffuselighting": @"feDiffuseLighting",
@"fedisplacementmap": @"feDisplacementMap",
@"fedistantlight": @"feDistantLight",
@"fedropshadow": @"feDropShadow",
@"feflood": @"feFlood",
@"fefunca": @"feFuncA",
@"fefuncb": @"feFuncB",
@"fefuncg": @"feFuncG",
@"fefuncr": @"feFuncR",
@"fegaussianblur": @"feGaussianBlur",
@"feimage": @"feImage",
@"femerge": @"feMerge",
@"femergenode": @"feMergeNode",
@"femorphology": @"feMorphology",
@"feoffset": @"feOffset",
@"fepointlight": @"fePointLight",
@"fespecularlighting": @"feSpecularLighting",
@"fespotlight": @"feSpotLight",
@"fetile": @"feTile",
@"feturbulence": @"feTurbulence",
@"foreignobject": @"foreignObject",
@"glyphref": @"glyphRef",
@"lineargradient": @"linearGradient",
@"radialgradient": @"radialGradient",
@"textpath": @"textPath"};
NSString *replacement = replacements[token.tagName] ?: token.tagName;
token.tagName = replacement;
}

View File

@@ -0,0 +1,66 @@
//
// HTMLElementTypes.h
// HTMLKit
//
// Created by Iska on 19/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import "HTMLNode+Private.h"
#import "HTMLElement.h"
#import "HTMLNamespaces.h"
#import "NSString+Private.h"
NS_INLINE BOOL IsNodeMathMLTextIntegrationPoint(HTMLElement *node)
{
return (node.htmlNamespace == HTMLNamespaceMathML && [node.tagName isEqualToAny:@"mi", @"mo", @"mn", @"ms", @"mtext", nil]);
}
NS_INLINE BOOL IsNodeHTMLIntegrationPoint(HTMLElement *node)
{
if (node.htmlNamespace == HTMLNamespaceMathML && [node.tagName isEqualToString:@"annotation-xml"]) {
NSString *encoding = node.attributes[@"encoding"];
return [encoding isEqualToStringIgnoringCase:@"text/html"] || [encoding isEqualToStringIgnoringCase:@"application/xhtml+xml"];
} else if (node.htmlNamespace == HTMLNamespaceSVG) {
return [node.tagName isEqualToAny:@"foreignObject", @"desc", @"title", nil];
}
return NO;
}
NS_INLINE BOOL IsSpecialElement(HTMLElement *element)
{
if (element.htmlNamespace == HTMLNamespaceHTML) {
return [element.tagName isEqualToAny:@"address", @"applet", @"area", @"article",
@"aside", @"base", @"basefont", @"bgsound", @"blockquote", @"body", @"br",
@"button", @"caption", @"center", @"col", @"colgroup", @"dd", @"details",
@"dir", @"div", @"dl", @"dt", @"embed", @"fieldset", @"figcaption",
@"figure", @"footer", @"form", @"frame", @"frameset", @"h1", @"h2", @"h3",
@"h4", @"h5", @"h6", @"head", @"header", @"hgroup", @"hr", @"html", @"iframe",
@"img", @"input", @"li", @"link", @"listing", @"main", @"marquee",
@"menu", @"meta", @"nav", @"noembed", @"noframes", @"noscript",
@"object", @"ol", @"p", @"param", @"plaintext", @"pre", @"script", @"section",
@"select", @"source", @"style", @"summary", @"table", @"tbody", @"td",
@"template", @"textarea", @"tfoot", @"th", @"thead", @"title", @"tr",
@"track", @"ul", @"wbr", @"xmp", nil];
} else if (element.htmlNamespace == HTMLNamespaceMathML) {
return [element.tagName isEqualToAny:@"mi", @"mo", @"mn", @"ms", @"mtext", @"annotation-xml", nil];
} else if (element.htmlNamespace == HTMLNamespaceSVG) {
return [element.tagName isEqualToAny:@"foreignObject", @"desc", @"title", nil];
}
return NO;
}
NS_INLINE BOOL DoesNodeSerializeAsVoid(HTMLNode *node)
{
if (node.nodeType != HTMLNodeElement) {
return false;
}
return [node.asElement.tagName isEqualToAny:@"area", @"base", @"basefont", @"bgsound", @"br", @"col", @"embed",
@"frame", @"hr", @"img", @"input", @"keygen", @"link", @"meta", @"param", @"source", @"track", @"wbr", nil];
}

View File

@@ -0,0 +1,168 @@
//
// HTMLInputStreamReader.h
// HTMLKit
//
// Created by Iska on 15/09/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
/**
Typedef for the error callback block.
@param code The standarized error-code
@param details The string describing the reason of the reported error.
*/
typedef void (^ HTMLStreamReaderErrorCallback)(NSString *code, NSString *details);
/**
* 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
/** @brief The underlying string with which this stream reader was initialized */
@property (nonatomic, readonly) NSString *string;
/** @brief The current scan location */
@property (nonatomic, readonly) NSUInteger currentLocation;
/** @brief An error callback block, which gets called when encountering errors while reading the stream */
@property (nonatomic, copy) HTMLStreamReaderErrorCallback errorCallback;
/**
Initializes a new Input Stream Reader with the given string.
@param string The HTML string
@return A new instance of the Input Stream Reader.
*/
- (id)initWithString:(NSString *)string;
/**
Returns the current input character.
@return The current code point in the input stream as a `UTF32Char`.
*/
- (UTF32Char)currentInputCharacter;
/**
Returns the next input character without consuming it.
@return The next code point in the input stream as a `UTF32Char`. Returns `EOF` if the stream is fully consumed.
*/
- (UTF32Char)nextInputCharacter;
/**
Returns the input character at a given offset without consuming it.
@param offset The offset of the character.
@return The code point in the input stream as a `UTF32Char` at the given offset.
*/
- (UTF32Char)inputCharacterPointAtOffset:(NSUInteger)offset;
/**
Consumes and returns the next input character. Consuming a characters advances the current scan location of the
input stream.
@return The next code point in the input stream as a `UTF32Char`. Returns `EOF` if the stream is fully consumed.
*/
- (UTF32Char)consumeNextInputCharacter;
/**
Causes the next input character to return the current input character.
*/
- (void)reconsumeCurrentInputCharacter;
/** @brief Unconsumes the current input character. */
- (void)unconsumeCurrentInputCharacter;
/**
Consumes the given character at the current location.
@param character The character to consume.
@return YES if the given character was consumed at the current location, NO otherwise.
*/
- (BOOL)consumeCharacter:(UTF32Char)character;
/**
Consumes characters at the current location matching an unsigned number.
@param result Upon return, contains the consumed unsigned number. Pass `NULL` to skip over an unsigned number at the
current location.
@return YES if an unsigned number could be consumed at the current location, NO otherwise.
*/
- (BOOL)consumeNumber:(unsigned long long *)result;
/**
Consumes characters at the current location matching a decimal number.
@param result Upon return, contains the consumed decimal number. Pass `NULL` to skip over a decimal number at the
current location.
@return YES if a decimal number could be consumed at the current location, NO otherwise.
*/
- (BOOL)consumeDecimalNumber:(NSDecimal *)result;
/**
Consumes characters at the current location matching a hexadecimal number.
@param result Upon return, contains the consumed hexadecimal number. Pass `NULL` to skip over a hexadecimal number at
the current location.
@return YES if a hexadecimal number could be consumed at the current location, NO otherwise.
*/
- (BOOL)consumeHexNumber:(unsigned long long *)result;
/**
Consumes the given string at the current location.
@param string The string to consume.
@param caseSensitive YES if the string's case should be ignored, NO otherwise
@return YES if the given string was consumed at the current location, NO otherwise.
*/
- (BOOL)consumeString:(NSString *)string caseSensitive:(BOOL)caseSensitive;
/**
Consumes characters starting at the current location until any character in a given string is encountered.
@param characters The string containing the characters to consume up to.
@return A string containing the consumed characters. Returns `nil` if none were consumed.
*/
- (NSString *)consumeCharactersUpToCharactersInString:(NSString *)characters;
/**
Consumes characters starting at the current location until a given string is encountered.
@param string The string to consume up to.
@return A string containing the consumed characters. Returns `nil` if none were consumed.
*/
- (NSString *)consumeCharactersUpToString:(NSString *)string;
/**
Consumes characters as long as the match the characters in the given string starting at the current location.
@param characters A string with the characters to consume.
@return A string containing the consumed characters. Returns `nil` if none were found.
*/
- (NSString *)consumeCharactersInString:(NSString *)characters;
/**
Consumes alphanumeric characters starting at the current location.
@return A string containing the consumed alphanumeric characters. Returns `nil` if none were found.
*/
- (NSString *)consumeAlphanumericCharacters;
/** @brief Marks the current stream scan location. */
- (void)markCurrentLocation;
/** @brief Resets the stream's scan location to the previously marked location. */
- (void)rewindToMarkedLocation;
/** @brief Resets the stream to its begining. */
- (void)reset;
@end

View File

@@ -0,0 +1,28 @@
//
// HTMLKit.h
// HTMLKit
//
// Created by Iska on 15/09/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for HTMLKit.
extern double HTMLKitVersionNumber;
//! Project version string for HTMLKit.
extern const unsigned char HTMLKitVersionString[];
#import "HTMLDOM.h"
#import "HTMLParser.h"
#import "HTMLSerializer.h"
#import "HTMLKitErrorDomain.h"
#import "HTMLOrderedDictionary.h"
#import "CSSSelectors.h"
#import "CSSSelectorParser.h"
#import "CSSNthExpressionParser.h"
#import "NSString+HTMLKit.h"
#import "NSCharacterSet+HTMLKit.h"

View File

@@ -0,0 +1,21 @@
//
// HTMLKitExceptions.h
// HTMLKit
//
// Created by Iska on 17/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSString * const HTMLKitHierarchyRequestError;
extern NSString * const HTMLKitNotFoundError;
extern NSString * const HTMLKitNotSupportedError;
extern NSString * const HTMLKitSyntaxError;
extern NSString * const HTMLKitInvalidCharacterError;
extern NSString * const HTMLKitInvalidNodeTypeError;
extern NSString * const HTMLKitIndexSizeError;
extern NSString * const HTMLKitWrongDocumentError;
extern NSString * const HTMLKitInvalidStateError;

View File

@@ -0,0 +1,23 @@
//
// HTMLKitErrorDomain.h
// HTMLKit
//
// Created by Iska on 24/11/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#ifndef HTMLKitErrorDomain_h
#define HTMLKitErrorDomain_h
static NSString *const HTMLKitErrorDomain = @"HTMLKit";
static NSString *const HTMLKitSelectorErrorDomain = @"HTMLKitSelector";
static NSString *const CSSSelectorStringKey = @"CSSSelectorString";
static NSString *const CSSSelectorErrorLocationKey = @"CSSSelectorErrorLocation";
NS_ENUM(NSInteger)
{
HTMLKitSelectorParseError = 4200
};
#endif /* HTMLKitErrorDomain_h */

View File

@@ -0,0 +1,139 @@
//
// HTMLListOfActiveFormattingElements.h
// HTMLKit
//
// Created by Iska on 22/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
#import "HTMLElement.h"
/**
The List of Active Formatting Elements. It is used to handle mis-nested formatting element tags.
https://html.spec.whatwg.org/multipage/syntax.html#the-list-of-active-formatting-elements
*/
@interface HTMLListOfActiveFormattingElements : NSObject
/**
Returns the object at the specified index.
@param index An index within the bounds of the list.
@return The node located at index.
*/
- (id)objectAtIndexedSubscript:(NSUInteger)index;
/**
Replaces the object at the index with the new object.
@param obj The node with which to replace the object at given index in the list.
@param idx The index of the object to be replaced.
*/
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
/**
Returns the index of the given node in the list.
@param node The node.
@return The index of the given node in the list.
*/
- (NSUInteger)indexOfElement:(id)node;
/**
Adds the given element to the list.
@param element The element to add.
*/
- (void)addElement:(HTMLElement *)element;
/**
Removes the given element from the list.
@param element The element to remove.
*/
- (void)removeElement:(id)element;
/**
Checks whether the given element is in the list.
@param element The element to check.
@return `YES` if element is in the list, `NO` otherwise.
*/
- (BOOL)containsElement:(id)element;
/**
Inserts the given element at the index into the list.
@param element The element to insert.
@param index The index at which the element should be inserted.
*/
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
/**
Replaces the element at the given index in the list with the new element.
@param index The index of the element to be replaced.
@param element The element with which to replace the element at given index in the list.
*/
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
/**
Returns the last element in this list.
@return The last entry.
*/
- (id)lastEntry;
/**
Adds a marker to the end of this list
*/
- (void)addMarker;
/**
Clears all elements from the end of this list upto the last marker.
*/
- (void)clearUptoLastMarker;
/**
Returns the last element in the list having the given tag name, that is between the end of the list and the last marker
in the list, if any, or the start of the list otherwise.
@param tagName The tag name.
@return The formatting element.
*/
- (HTMLElement *)formattingElementWithTagName:(NSString *)tagName;
/**
Returns the count of elements in this list.
@return The elements count.
*/
- (NSUInteger)count;
/**
Checks whether this list is empty.
@return `YES` if the stack is empty, `NO` otherwise.
*/
- (BOOL)isEmpty;
/**
Return an object enumerator over this list.
@return An enumerator
*/
- (NSEnumerator *)enumerator;
/**
Return an object enumerator over this list.
@return An enumerator
*/
- (NSEnumerator *)reverseObjectEnumerator;
@end

View File

@@ -0,0 +1,27 @@
//
// HTMLMarker.h
// HTMLKit
//
// Created by Iska on 02/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import <Foundation/Foundation.h>
/**
A Maker that is used in the List of Active Formatting Elements.
@see HTMLListOfActiveFormattingElements
*/
@interface HTMLMarker : NSObject
/**
Returns the singleton instance of the Marker.
*/
+ (instancetype)marker;
@end

View File

@@ -0,0 +1,23 @@
//
// HTMLNamespaces.h
// HTMLKit
//
// Created by Iska on 03/11/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
/**
HTML Namespaces
https://html.spec.whatwg.org/multipage/infrastructure.html#namespaces
*/
typedef NS_ENUM(NSInteger, HTMLNamespace)
{
/** The default HTML namespace. */
HTMLNamespaceHTML,
/** The namespace for most of the <math> elements. */
HTMLNamespaceMathML,
/** The namespace for most of the <svg> elements. */
HTMLNamespaceSVG
};

View File

@@ -0,0 +1,73 @@
//
// HTMLNode+Private.h
// HTMLKit
//
// Created by Iska on 20/12/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
///------------------------------------------------------
/// HTMLKit private header
///------------------------------------------------------
#import "HTMLNode.h"
@class HTMLText;
@class HTMLComment;
@class HTMLDocumentType;
/**
Private HTML Node methods which are not intended for public API.
*/
@interface HTMLNode ()
/**
A read-write redeclaration of the same property in the public API.
*/
@property (nonatomic, weak) HTMLDocument *ownerDocument;
/**
A read-write redeclaration of the same property in the public API.
*/
@property (nonatomic, weak) HTMLNode *parentNode;
/**
Designated initializer of the HTML Node, which, however, should not be used directly. It is intended to be called only
by subclasses.
@abstract Use concrete subclasses of the HTML Node.
@param name The node's name.
@param type The node's type.
@return A new instance of a HTML Node.
*/
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type NS_DESIGNATED_INITIALIZER;
/**
Casts this node to a HTML Element. This cast should only be performed after the appropriate check.
*/
- (HTMLElement *)asElement;
/**
Casts this node to a HTML Text. This cast should only be performed after the appropriate check.
*/
- (HTMLText *)asText;
/**
Casts this node to a HTML Comment. This cast should only be performed after the appropriate check.
*/
- (HTMLComment *)asComment;
/**
Casts this node to a HTML Document Type. This cast should only be performed after the appropriate check.
*/
- (HTMLDocumentType *)asDocumentType;
/**
Returns the same string representation of the DOM tree rooted at this node that is used by html5lib-tests.
@disucssion This method is indended for testing purposes.
*/
- (NSString *)treeDescription;
@end

View File

@@ -0,0 +1,458 @@
//
// HTMLNode.h
// HTMLKit
//
// Created by Iska on 24/02/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "HTMLNodeIterator.h"
#import "HTMLTreeVisitor.h"
NS_ASSUME_NONNULL_BEGIN
/**
The HTML node type
*/
typedef NS_ENUM(short, HTMLNodeType)
{
HTMLNodeElement = 1,
HTMLNodeText = 3,
HTMLNodeProcessingInstruction = 7,
HTMLNodeComment = 8,
HTMLNodeDocument = 9,
HTMLNodeDocumentType = 10,
HTMLNodeDocumentFragment = 11,
};
/**
A node's position in the HTML document when compared with other nodes.
*/
typedef NS_OPTIONS(unsigned short, HTMLDocumentPosition)
{
HTMLDocumentPositionEquivalent = 0x0,
HTMLDocumentPositionDisconnected = 0x01,
HTMLDocumentPositionPreceding = 0x02,
HTMLDocumentPositionFollowing = 0x04,
HTMLDocumentPositionContains = 0x08,
HTMLDocumentPositionContainedBy = 0x10,
HTMLDocumentPositionImplementationSpecific = 0x20
};
@class HTMLDocument;
@class HTMLElement;
@class CSSSelector;
/**
A HTML Node, the base class for all HTML DOM entities.
HTMLKit provides a partial implementation of the WHATWG DOM specification: https://dom.spec.whatwg.org/
*/
@interface HTMLNode : NSObject <NSCopying>
/**
The node type.
@see HTMLNodeType
*/
@property (nonatomic, assign, readonly) HTMLNodeType nodeType;
/**
The node name as described in https://dom.spec.whatwg.org/#dom-node-nodename
@warning This is not the HTML Element tag name.
*/
@property (nonatomic, strong, readonly) NSString *name;
/**
The owner document of this node.
@see HTMLDocument
*/
@property (nonatomic, weak, readonly, nullable) HTMLDocument *ownerDocument;
/**
The root node of this node, if any.
*/
@property (nonatomic, weak, readonly, nullable) HTMLNode *rootNode;
/**
The parent node of this node, if any.
*/
@property (nonatomic, weak, readonly, nullable) HTMLNode *parentNode;
/**
The parent element of this node, if any.
@discussion This property returns nil if the parent is a non-element node.
*/
@property (nonatomic, weak, readonly, nullable) HTMLElement *parentElement;
/**
A read-only ordered set of child nodes.
*/
@property (nonatomic, strong, readonly) NSOrderedSet<HTMLNode *> *childNodes;
/**
The first child node, if any.
*/
@property (nonatomic, strong, readonly, nullable) HTMLNode *firstChild;
/**
The last child node, if any.
*/
@property (nonatomic, strong, readonly, nullable) HTMLNode *lastChild;
/**
The previous sibling node in the document, if any.
*/
@property (nonatomic, strong, readonly, nullable) HTMLNode *previousSibling;
/**
The next sibling node in the document, if any.
*/
@property (nonatomic, strong, readonly, nullable) HTMLNode *nextSibling;
/**
The previous sibling element in the document, if any.
@discussion Previous non-element nodes will be skipped till an element is found.
*/
@property (nonatomic, strong, readonly, nullable) HTMLElement *previousSiblingElement;
/**
The next sibling element in the document, if any.
@discussion Next non-element nodes will be skipped till an element is found.
*/
@property (nonatomic, strong, readonly, nullable) HTMLElement *nextSiblingElement;
/**
The index of this node.
*/
@property (nonatomic, readonly, assign) NSUInteger index;
/**
The text content of this node.
*/
@property (nonatomic, copy) NSString *textContent;
/**
The outer HTML string.
*/
@property (nonatomic, strong, readonly) NSString *outerHTML;
/**
The inner HTML string.
*/
@property (nonatomic, copy) NSString *innerHTML;
/**
The length of the node as described in https://dom.spec.whatwg.org/#concept-node-length
*/
@property (nonatomic, assign) NSUInteger length;
/**
@abstract Use concrete subclasses of the HTML Node.
*/
- (instancetype)init NS_UNAVAILABLE;
/**
Checks whether this node has child nodes.
@return `YES` if this node has any children, `NO` otherwise.
*/
- (BOOL)hasChildNodes;
/**
Checks whether this node has child nodes of the given type.
@param type The type to check.
@return `YES` if this node has any children of the given type, `NO` otherwise.
*/
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type;
/**
Returns the cound of child nodes.
@return The child nodes count.
*/
- (NSUInteger)childNodesCount;
/**
Checks whether the node is empty as described in https://dom.spec.whatwg.org/#concept-node-length
@return `YES` if the node is empty, `NO` otherwise.
*/
- (BOOL)isEmpty;
/**
Clones this node.
@param deep If `YES` then also clones child nodes. Otherwise a shallow clone is returned, which behaves the same as `copy`.
@return A clone of this node.
*/
- (instancetype)cloneNodeDeep:(BOOL)deep;
/**
Returns the child node at a given index.
@param index The index at which to return the child node.
@return The child node at a index. If index is greater than or equal to the value returned by count, an
NSRangeException is raised.
*/
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index;
/**
Returns the index of the given child node in the set of child nodes.
@param node The node.
@return The index of the given node in the children set.
*/
- (NSUInteger)indexOfChildNode:(HTMLNode *)node;
/**
Returns the cound of child elements.
@discussion This method count only nodes of type HTMLNodeElement.
@return The child elements count.
*/
- (NSUInteger)childElementsCount;
/**
Returns the child element at a given index.
@param index The index at which to return the child element.
@return The child element at a index. If index is greater than or equal to the value returned by count, an
NSRangeException is raised.
*/
- (HTMLElement *)childElementAtIndex:(NSUInteger)index;
/**
Returns the index of the given child element in the set of child nodes.
@param element The element.
@return The index of the given element in the children set.
*/
- (NSUInteger)indexOfChildElement:(HTMLElement *)element;
/**
Prepends the given node to the set of child nodes.
@param node The node to prepend.
@return The node being prepended.
*/
- (HTMLNode *)prependNode:(HTMLNode *)node;
/**
Prepends the given array of nodes to the set of child nodes.
@param nodes The nodes to prepend.
*/
- (void)prependNodes:(NSArray<HTMLNode *> *)nodes;
/**
Appends the given node to the set of child nodes.
@param node The node to append.
@return The node being appended.
*/
- (HTMLNode *)appendNode:(HTMLNode *)node;
/**
Appends the given array of nodes to the set of child nodes.
@param nodes The nodes to append.
*/
- (void)appendNodes:(NSArray<HTMLNode *> *)nodes;
/**
Inserts a given node before a child node.
@param node The node to insert.
@param child A reference child node before which the new node should be inserted. If child is `nil` then the new node
will be inserted as the last child node.
@return The node being inserted.
*/
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(nullable HTMLNode *)child;
/**
Replaces a given child node whith new node.
@param child The child node to replace.
@param replacement The replacement node.
@return The replacement node.
*/
- (HTMLNode *)replaceChildNode:(HTMLNode *)child withNode:(HTMLNode *)replacement;
/**
Replaces all child nodes with the given node.
@param node The node which will replace all child nodes.
*/
- (void)replaceAllChildNodesWithNode:(HTMLNode *)node;
/**
Removes this node from its parent.
@discussion This will detach the node from its parent and in turn from its previous document.
*/
- (void)removeFromParentNode;
/**
Removes the given child node from children.
@param node The node to remove.
*/
- (HTMLNode *)removeChildNode:(HTMLNode *)node;
/**
Removes the child node at index from children.
@param index The index of the node to remove.
*/
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index;
/**
Changes children ownership from this node to the given node.
@discussion Running this method will append all children of this node to the given node. This node will have no children
afterwards.
@param node The node which will reparent children of this node.
*/
- (void)reparentChildNodesIntoNode:(HTMLNode *)node;
/**
Removes all child nodes.
*/
- (void)removeAllChildNodes;
/**
Compares the position of this node with the given node in the document.
@param node The node with which to comapre the position.
@return The HTMLDocumentPosition of this node in relation to the given node.
@see HTMLDocumentPosition
*/
- (HTMLDocumentPosition)compareDocumentPositionWithNode:(HTMLNode *)node;
/**
Checks whether this node is descendant of the given node.
@param node The node to check.
@return `YES` if this node is descendant of the gicen node, `NO` otherwsie.
*/
- (BOOL)isDescendantOfNode:(HTMLNode *)node;
/**
Checks whether this node contains the given node. This performs an `invlusive ancestor` check, i.e. it returns `YES`
if the given node is the same object as this node.
@param node The node to check.
@return `YES` if this node contains the given node, `NO` otherwsie.
*/
- (BOOL)containsNode:(HTMLNode *)node;
/**
Enumerates and applies `block` on each child node.
@block The block to run for each child node.
*/
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block;
/**
Enumerates and applies `block` on each child element.
@discussion This method only enumerates child elements.
@block The block to run for each child element.
*/
- (void)enumerateChildElementsUsingBlock:(void (^)(HTMLElement *element, NSUInteger idx, BOOL *stop))block;
/**
Returns a node iterator rooted at this node whith no filter and HTMLNodeFilterShowAll.
@return A new node iterator whose root is this node.
@see HTMLNodeIterator
@see HTMLNodeFilterShowOptions
*/
- (HTMLNodeIterator *)nodeIterator;
/**
Returns a node iterator rooted at this node.
@param showOptions The iterator's show options.
@param filter The iterator's filter.
@return A new node iterator whose root is this node.
@see HTMLNodeIterator
@see HTMLNodeFilterShowOptions
*/
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
filter:(nullable id<HTMLNodeFilter>)filter;
/**
Returns a node iterator rooted at this node.
@param showOptions The iterator's show options.
@param filter The iterator's filter block.
@return A new node iterator whose root is this node.
@see HTMLNodeIterator
@see HTMLNodeFilterShowOptions
*/
- (HTMLNodeIterator *)nodeIteratorWithShowOptions:(HTMLNodeFilterShowOptions)showOptions
filterBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))filter;
/**
Returns the first element in the DOM tree rooted at this node, that is matched by the given selector string.
@param selector The CSS seletor string.
@return The first element that is matched by the parsed selector. Rerturns `nil` if the selector could not be parsed
or no element was matched.
@see firstElementMatchingSelector:
@see CSSSelector
*/
- (nullable HTMLElement *)querySelector:(NSString *)selector;
/**
Returns all elements in the DOM tree rooted at this node, that are matched by the given selector string.
@param selector The CSS seletor string.
@return The elements that are matched by the parsed selector. Rerturns an empty array if the selector could not be parsed
or no elements were matched.
@see elementsMatchingSelector:
@see CSSSelector
*/
- (NSArray<HTMLElement *> *)querySelectorAll:(NSString *)selector;
/**
Returns the first element in the DOM tree rooted at this node, that is matched by the given selector.
@param selector The CSS seletor.
@return The first element that is matched by the parsed selector. Rerturns `nil` if no element was matched.
@see CSSSelector
*/
- (nullable HTMLElement *)firstElementMatchingSelector:(CSSSelector *)selector;
/**
Returns all elements in the DOM tree rooted at this node, that are matched by the given selector.
@param selector The CSS seletor.
@return The elements that are matched by the parsed selector. Rerturns an empty array if no elements were matched.
@see CSSSelector
*/
- (NSArray<HTMLElement *> *)elementsMatchingSelector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,98 @@
//
// HTMLNodeFilter.h
// HTMLKit
//
// Created by Iska on 27/05/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
The node filter's value when applied to a given HTML node. The node filter can either accept a node, skip it, or
reject it. Rejecting a node means skipping the node itself and all of it descendants.
*/
typedef NS_ENUM(unsigned short, HTMLNodeFilterValue)
{
HTMLNodeFilterAccept = 1,
HTMLNodeFilterReject = 2,
HTMLNodeFilterSkip = 3
};
/**
The show options for the HTML node iterator and tree walker.
@see HTMLNodeIterator
@see HTMLTreeWalker
*/
typedef NS_OPTIONS(unsigned long, HTMLNodeFilterShowOptions)
{
HTMLNodeFilterShowAll = 0xFFFFFFFF,
HTMLNodeFilterShowElement = 0x1,
HTMLNodeFilterShowText = 0x4,
HTMLNodeFilterShowComment = 0x80,
HTMLNodeFilterShowDocument = 0x100,
HTMLNodeFilterShowDocumentType = 0x200,
HTMLNodeFilterShowDocumentFragment = 0x400
};
#pragma mark - Node Filter
@class HTMLNode;
/**
A HTML Node Filter which can be used with a node iterator or a tree walker.
@see HTMLNodeIterator
@see HTMLTreeWalker
*/
@protocol HTMLNodeFilter <NSObject>
@required
/**
The implementation should return a HTMLNodeFilterValue to indicate accepting, skipping or rejecting a node.
@param node The node to be filtered.
@return `HTMLNodeFilterAccept` if accepted, `HTMLNodeFilterSkip` if skipped, or `HTMLNodeFilterReject` if rejected.
*/
- (HTMLNodeFilterValue)acceptNode:(HTMLNode *)node;
@end
#pragma mark - Block Filter
/**
A concrete block-based HTML Node Filter implementation.
*/
@interface HTMLNodeFilterBlock : NSObject <HTMLNodeFilter>
/**
Initializes and returns a new instance of this filter.
@param block The block to apply on each node to be filtered.
*/
+ (instancetype)filterWithBlock:(HTMLNodeFilterValue (^)(HTMLNode *node))block;
@end
#pragma mark - CSS Selector Filter
@class CSSSelector;
/**
A concrete css-selector-based HTML Node Filter implementation.
*/
@interface HTMLSelectorNodeFilter : NSObject <HTMLNodeFilter>
/**
Initializes and returns a new instance of this filter.
@param selector The selector to apply on each node to be filtered.
*/
+ (instancetype)filterWithSelector:(CSSSelector *)selector;
@end
NS_ASSUME_NONNULL_END

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