Merge branch 'release/0.1.0'
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[submodule "HTMLKitTests/html5lib-tests"]
|
||||
path = HTMLKitTests/html5lib-tests
|
||||
url = https://github.com/html5lib/html5lib-tests.git
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:HTMLKit.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>classNames</key>
|
||||
<dict>
|
||||
<key>HTMLKitParserPerformance</key>
|
||||
<dict>
|
||||
<key>testParserPerformance</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>18.23</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>HTMLKitTokenizerPerformance</key>
|
||||
<dict>
|
||||
<key>testTokenizerPerformance</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>14.55</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>HTMLTokenizerTests</key>
|
||||
<dict>
|
||||
<key>testTokenizerPerformance</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>15.47</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>runDestinationsByUUID</key>
|
||||
<dict>
|
||||
<key>5AB53A08-CDD0-43FB-B47F-2EE38B3FE707</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>1600</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>2</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>Quad-Core Intel Xeon</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>2800</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacPro3,1</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>x86_64</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// HTMLCharacterToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLCharacterToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *characters;
|
||||
|
||||
- (instancetype)initWithString:(NSString *)string;
|
||||
|
||||
- (void)appendString:(NSString *)string;
|
||||
- (BOOL)isWhitespaceToken;
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
- (void)retainLeadingWhitespace;
|
||||
- (void)trimLeadingWhitespace;
|
||||
- (void)trimFormIndex:(NSUInteger)index;
|
||||
- (HTMLCharacterToken *)tokenBySplitingLeadingWhiteSpace;
|
||||
|
||||
@end
|
||||
@@ -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 isHTMLWhitespaceString];
|
||||
}
|
||||
|
||||
- (BOOL)isEmpty
|
||||
{
|
||||
return _characters.length == 0;
|
||||
}
|
||||
|
||||
- (void)retainLeadingWhitespace
|
||||
{
|
||||
NSUInteger index = _characters.leadingHTMLWhitespaceLength;
|
||||
if (index > 0) {
|
||||
[_characters setString:[_characters substringToIndex:index]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)trimLeadingWhitespace
|
||||
{
|
||||
NSUInteger index = _characters.leadingHTMLWhitespaceLength;
|
||||
if (index > 0) {
|
||||
[_characters setString:[_characters substringFromIndex:index]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)trimFormIndex:(NSUInteger)index
|
||||
{
|
||||
[_characters setString:[_characters substringFromIndex:index]];
|
||||
}
|
||||
|
||||
- (HTMLCharacterToken *)tokenBySplitingLeadingWhiteSpace
|
||||
{
|
||||
NSUInteger index = _characters.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
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// HTMLComment.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLComment : HTMLNode
|
||||
|
||||
@property (nonatomic, copy) NSString *data;
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// HTMLComment.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLComment.h"
|
||||
|
||||
@implementation HTMLComment
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data
|
||||
{
|
||||
self = [super initWithName:@"#comment" type:HTMLNodeComment];
|
||||
if (self) {
|
||||
self.data = data ?: @"";
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return self.data;
|
||||
}
|
||||
|
||||
- (void)setTextContent:(NSString *)textContent
|
||||
{
|
||||
self.data = textContent ?: @"";
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLComment *copy = [super copyWithZone:zone];
|
||||
copy.data = self.data;
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - Serialization
|
||||
|
||||
- (NSString *)outerHTML
|
||||
{
|
||||
return [NSString stringWithFormat:@"<!--%@-->", self.data];
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p <!-- %@ -->>", self.class, self, self.data];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// HTMLCommentToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLCommentToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *data;
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
- (void)appendStringToData:(NSString *)string;
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// HTMLDOCTYPEToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLDOCTYPEToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
@property (nonatomic, strong) NSMutableString *publicIdentifier;
|
||||
@property (nonatomic, strong) NSMutableString *systemIdentifier;
|
||||
@property (nonatomic, assign) BOOL forceQuirks;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name;
|
||||
|
||||
- (void)appendStringToName:(NSString *)string;
|
||||
- (void)appendStringToPublicIdentifier:(NSString *)string;
|
||||
- (void)appendStringToSystemIdentifier:(NSString *)string;
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// HTMLDocument.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLQuirksMode.h"
|
||||
|
||||
typedef NS_ENUM(short, HTMLDocumentReadyState)
|
||||
{
|
||||
HTMLDocumentLoading,
|
||||
HTMLDocumentInteractive, // Not used
|
||||
HTMLDocumentComplete
|
||||
};
|
||||
|
||||
@interface HTMLDocument : HTMLNode
|
||||
|
||||
@property (nonatomic, strong) HTMLDocumentType *documentType;
|
||||
|
||||
@property (nonatomic, assign) HTMLQuirksMode quirksMode;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *compatMode;
|
||||
|
||||
@property (nonatomic, assign, readonly) HTMLDocumentReadyState readyState;
|
||||
|
||||
- (HTMLNode *)adoptNode:(HTMLNode *)node;
|
||||
|
||||
- (HTMLDocument *)associatedInertTemplateDocument;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// HTMLDocument.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLKitExceptions.h"
|
||||
|
||||
@interface HTMLNode (Private)
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@property (nonatomic, weak) HTMLNode *parentNode;
|
||||
@end
|
||||
|
||||
@interface HTMLDocument ()
|
||||
{
|
||||
HTMLDocument *_inertTemplateDocument;
|
||||
}
|
||||
@property (nonatomic, assign) HTMLDocumentReadyState readyState;
|
||||
@end
|
||||
|
||||
@implementation HTMLDocument
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super initWithName:@"#document" type:HTMLNodeDocument];
|
||||
if (self) {
|
||||
_readyState = HTMLDocumentLoading;
|
||||
}
|
||||
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 - Mutation Algorithms
|
||||
|
||||
- (HTMLNode *)adoptNode:(HTMLNode *)node
|
||||
{
|
||||
if (node == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (node.type == 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
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// HTMLDocumentFragment.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 12/04/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLDocumentFragment : HTMLNode
|
||||
|
||||
- (instancetype)initWithDocument:(HTMLDocument *)document;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// HTMLDocumentFragment.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 12/04/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLDocumentFragment.h"
|
||||
#import "HTMLText.h"
|
||||
|
||||
@interface HTMLNode ()
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@end
|
||||
|
||||
@implementation HTMLDocumentFragment
|
||||
|
||||
- (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.treeEnumerator) {
|
||||
if (node.type == HTMLNodeText) {
|
||||
[content appendString:[(HTMLText *)node data]];
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
- (void)setTextContent:(NSString *)textContent
|
||||
{
|
||||
HTMLText *node = [[HTMLText alloc] initWithData:textContent];
|
||||
[self replaceAllChildNodesWithNode:node];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// HTMLDocumentType.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLQuirksMode.h"
|
||||
|
||||
@interface HTMLDocumentType : HTMLNode
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *publicIdentifier;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *systemIdentifier;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
publicIdentifier:(NSString *)publicIdentifier
|
||||
systemIdentifier:(NSString *)systemIdentifier;
|
||||
|
||||
- (BOOL)isValid;
|
||||
- (HTMLQuirksMode)quirksMode;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// HTMLDocumentType.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "NSString+HTMLKit.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)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;
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLDocumentType *copy = [super copyWithZone:zone];
|
||||
copy->_publicIdentifier = self.publicIdentifier;
|
||||
copy->_systemIdentifier = self.systemIdentifier;
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - Serialization
|
||||
|
||||
- (NSString *)outerHTML
|
||||
{
|
||||
return [NSString stringWithFormat:@"<!DOCTYPE %@>", self.name];
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p <!DOCTYPE %@ \"%@\" \"%@\">>",
|
||||
self.class, self, self.name, self.publicIdentifier, self.systemIdentifier];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// HTMLEOFToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLEOFToken : HTMLToken
|
||||
|
||||
+ (instancetype)token;
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// HTMLElement.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 05/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLNamespaces.h"
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLElement : HTMLNode
|
||||
|
||||
@property (nonatomic, assign, readonly) HTMLNamespace htmlNamespace;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *tagName;
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary *attributes;
|
||||
|
||||
@property (nonatomic, copy) NSString *elementId;
|
||||
|
||||
@property (nonatomic, copy) NSString *className;
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName;
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes;
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes namespace:(HTMLNamespace)htmlNamespace;
|
||||
|
||||
- (BOOL)hasAttribute:(NSString *)name;
|
||||
- (NSString *)objectForKeyedSubscript:(NSString *)name;
|
||||
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute;
|
||||
- (void)removeAttribute:(NSString *)name;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// HTMLElement.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 05/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLText.h"
|
||||
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
@interface HTMLElement ()
|
||||
{
|
||||
HTMLOrderedDictionary *_attributes;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLElement
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithTagName:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName
|
||||
{
|
||||
return [self initWithTagName:tagName attributes:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes
|
||||
{
|
||||
return [self initWithTagName:tagName attributes:attributes namespace:HTMLNamespaceHTML];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSDictionary *)attributes namespace:(HTMLNamespace)htmlNamespace
|
||||
{
|
||||
self = [super initWithName:tagName type:HTMLNodeElement];
|
||||
if (self) {
|
||||
_tagName = [tagName copy];
|
||||
_attributes = [HTMLOrderedDictionary new];
|
||||
if (attributes != nil) {
|
||||
[_attributes addEntriesFromDictionary:attributes];
|
||||
}
|
||||
_htmlNamespace = htmlNamespace;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Attributes
|
||||
|
||||
- (NSString *)elementId
|
||||
{
|
||||
return _attributes[@"id"] ?: @"";
|
||||
}
|
||||
|
||||
- (NSString *)className
|
||||
{
|
||||
return _attributes[@"class"];
|
||||
}
|
||||
|
||||
- (BOOL)hasAttribute:(NSString *)name
|
||||
{
|
||||
return _attributes[name] != nil;
|
||||
}
|
||||
|
||||
- (NSString *)objectForKeyedSubscript:(NSString *)name;
|
||||
{
|
||||
return _attributes[name];
|
||||
}
|
||||
|
||||
- (void)setObject:(NSString *)value forKeyedSubscript:(NSString *)attribute
|
||||
{
|
||||
_attributes[attribute] = value;
|
||||
}
|
||||
|
||||
- (void)removeAttribute:(NSString *)name
|
||||
{
|
||||
[_attributes removeObjectForKey:name];
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
NSMutableString *content = [NSMutableString string];
|
||||
for (HTMLNode *node in self.treeEnumerator) {
|
||||
if (node.type == HTMLNodeText) {
|
||||
[content appendString:[(HTMLText *)node data]];
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
- (void)setTextContent:(NSString *)textContent
|
||||
{
|
||||
HTMLText *node = [[HTMLText alloc] initWithData:textContent];
|
||||
[self replaceAllChildNodesWithNode:node];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLElement *copy = [super copyWithZone:zone];
|
||||
copy->_tagName = [_tagName copy];
|
||||
copy->_attributes = [_attributes copy];
|
||||
copy->_htmlNamespace = _htmlNamespace;
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - Serialization
|
||||
|
||||
- (NSString *)outerHTML
|
||||
{
|
||||
NSMutableString *result = [NSMutableString string];
|
||||
|
||||
[result appendFormat:@"<%@", self.tagName];
|
||||
[self.attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
|
||||
NSRange range = NSMakeRange(0, value.length);
|
||||
NSMutableString *escaped = [value mutableCopy];
|
||||
[escaped replaceOccurrencesOfString:@"&" withString:@"&" options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"\00A0" withString:@" " options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"\"" withString:@""" options:0 range:range];
|
||||
|
||||
[result appendFormat:@" %@=\"%@\"", key, escaped];
|
||||
}];
|
||||
|
||||
[result appendString:@">"];
|
||||
|
||||
if ([self.tagName isEqualToAny:@"area", @"base", @"basefont", @"bgsound", @"br", @"col", @"embed",
|
||||
@"frame", @"hr", @"img", @"input", @"keygen", @"link", @"menuitem", @"meta", @"param", @"source",
|
||||
@"track", @"wbr", nil]) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if ([self.tagName isEqualToAny:@"pre", @"textarea", @"listing", nil] && self.firstChiledNode.type == HTMLNodeText) {
|
||||
HTMLText *textNode = (HTMLText *)self.firstChiledNode;
|
||||
if ([textNode.data hasPrefix:@"\n"]) {
|
||||
[result appendString:@"\n"];
|
||||
}
|
||||
}
|
||||
[result appendString:self.innerHTML];
|
||||
[result appendFormat:@"</%@>", self.tagName];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// HTMLElementAdjustment.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 14/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
NS_INLINE void AdjustForeignAttributes(HTMLTagToken *token)
|
||||
{
|
||||
if (token.attributes == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *replacements = @{ @"xlink:actuate": @"xlink actuate",
|
||||
@"xlink:arcrole": @"xlink arcrole",
|
||||
@"xlink:href": @"xlink href",
|
||||
@"xlink:role": @"xlink role",
|
||||
@"xlink:show": @"xlink show",
|
||||
@"xlink:title": @"xlink title",
|
||||
@"xlink:type": @"xlink type",
|
||||
@"xml:base": @"xml base",
|
||||
@"xml:lang": @"xml lang",
|
||||
@"xml:space": @"xml space",
|
||||
@"xmlns": @"xmlns",
|
||||
@"xmlns:xlink": @"xmlns xlink"};
|
||||
|
||||
HTMLOrderedDictionary *adjusted = [HTMLOrderedDictionary new];
|
||||
for (id key in token.attributes) {
|
||||
NSString *replacement = replacements[key] ?: key;
|
||||
adjusted[replacement] = token.attributes[key];
|
||||
}
|
||||
token.attributes = adjusted;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// HTMLElementTypes.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 19/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLNamespaces.h"
|
||||
#import "NSString+HTMLKit.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", @"isindex", @"li", @"link", @"listing", @"main", @"marquee",
|
||||
@"menu", @"menuitem", @"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;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// HTMLInputStreamReader.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef void (^ HTMLStreamReaderErrorCallback)(NSString *reason);
|
||||
|
||||
/**
|
||||
* HTML Input Stream Reader processor conforming to the HTML standard
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#preprocessing-the-input-stream
|
||||
*/
|
||||
@interface HTMLInputStreamReader : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSString *string;
|
||||
@property (nonatomic, readonly) NSUInteger currentLocation;
|
||||
@property (nonatomic, copy) HTMLStreamReaderErrorCallback errorCallback;
|
||||
|
||||
- (id)initWithString:(NSString *)string;
|
||||
|
||||
- (UTF32Char)currentInputCharacter;
|
||||
- (UTF32Char)nextInputCharacter;
|
||||
|
||||
- (UTF32Char)consumeNextInputCharacter;
|
||||
- (void)reconsumeCurrentInputCharacter;
|
||||
- (void)unconsumeCurrentInputCharacter;
|
||||
|
||||
- (BOOL)consumeCharacter:(UTF32Char)character;
|
||||
- (BOOL)consumeNumber:(unsigned long long *)result;
|
||||
- (BOOL)consumeHexNumber:(unsigned long long *)result;
|
||||
- (BOOL)consumeString:(NSString *)string caseSensitive:(BOOL)caseSensitive;
|
||||
- (NSString *)consumeCharactersUpToCharactersInString:(NSString *)characters;
|
||||
- (NSString *)consumeCharactersUpToString:(NSString *)string;
|
||||
- (NSString *)consumeAlphanumericCharacters;
|
||||
|
||||
- (void)markCurrentLocation;
|
||||
- (void)rewindToMarkedLocation;
|
||||
- (void)reset;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,238 @@
|
||||
//
|
||||
// HTMLInputStreamReader.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLInputStreamReader.h"
|
||||
#import "HTMLTokenizerCharacters.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];
|
||||
CFStringInitInlineBuffer((CFStringRef)_string, &_buffer, CFRangeMake(0, _string.length));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Errors
|
||||
|
||||
- (void)emitParseError:(NSString *)reason
|
||||
{
|
||||
if (self.errorCallback) {
|
||||
self.errorCallback(reason);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Stream Processing
|
||||
|
||||
- (UTF32Char)currentInputCharacter
|
||||
{
|
||||
return _currentInputCharacter;
|
||||
}
|
||||
|
||||
- (UTF32Char)nextInputCharacter
|
||||
{
|
||||
if (_reconsume) {
|
||||
_reconsume = NO;
|
||||
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 *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated low surrogate: 0x%X)", nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
return nextInputCharacter;
|
||||
}
|
||||
|
||||
if (CFStringIsSurrogateHighCharacter(nextInputCharacter)) {
|
||||
UniChar surrogateLow = CFStringGetCharacterFromInlineBuffer(&_buffer, _location + 1);
|
||||
if (CFStringIsSurrogateLowCharacter(surrogateLow) == NO) {
|
||||
NSString *reason = [NSString stringWithFormat:@"Non-Unicode character found (an isolated high surrogate: 0x%X)", nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
return nextInputCharacter;
|
||||
}
|
||||
|
||||
_consume = 2;
|
||||
nextInputCharacter = CFStringGetLongCharacterForSurrogatePair(nextInputCharacter, surrogateLow);
|
||||
}
|
||||
|
||||
if (isControlOrUndefinedCharacter(nextInputCharacter)) {
|
||||
NSString *reason = [NSString stringWithFormat:@"A control/undefined character found: (0x%X)", nextInputCharacter];
|
||||
[self emitParseError:reason];
|
||||
}
|
||||
|
||||
return nextInputCharacter;
|
||||
}
|
||||
|
||||
- (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) {
|
||||
_location += _consume;
|
||||
_scanner.scanLocation = _location;
|
||||
_currentInputCharacter = nextInputCharacter;
|
||||
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)consumeHexNumber:(unsigned long long *)result
|
||||
{
|
||||
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEFabcdef"];
|
||||
|
||||
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 *)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
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.braincookie.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2014 BrainCookie. All rights reserved.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Prefix header
|
||||
//
|
||||
// The contents of this file are implicitly included at the beginning of every source file.
|
||||
//
|
||||
|
||||
#ifdef __OBJC__
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// HTMLKit.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLKit : NSObject
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// HTMLKit.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 15/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLKit.h"
|
||||
|
||||
@implementation HTMLKit
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// HTMLKitExceptions.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 17/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
FOUNDATION_EXPORT NSString * const HTMLKitHierarchyRequestError;
|
||||
FOUNDATION_EXPORT NSString * const HTMLKitNotFoundError;
|
||||
FOUNDATION_EXPORT NSString * const HTMLKitNotSupportedError;
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// HTMLKitExceptions.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 17/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLKitExceptions.h"
|
||||
|
||||
NSString * const HTMLKitHierarchyRequestError = @"HierarchyRequestError";
|
||||
NSString * const HTMLKitNotFoundError = @"NotFoundError";
|
||||
NSString * const HTMLKitNotSupportedError = @"NotSupportedError";
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// HTMLListOfActiveFormattingElements.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 22/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
@interface HTMLListOfActiveFormattingElements : NSObject
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||
- (NSUInteger)indexOfElement:(id)node;
|
||||
|
||||
- (void)addElement:(HTMLElement *)element;
|
||||
- (void)removeElement:(id)element;
|
||||
- (BOOL)containsElement:(id)element;
|
||||
|
||||
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
|
||||
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
|
||||
|
||||
- (id)lastEntry;
|
||||
|
||||
- (void)addMarker;
|
||||
- (void)clearUptoLastMarker;
|
||||
|
||||
- (HTMLElement *)formattingElementWithTagName:(NSString *)tagName;
|
||||
|
||||
- (NSUInteger)count;
|
||||
- (BOOL)isEmpty;
|
||||
|
||||
- (NSEnumerator *)enumerator;
|
||||
- (NSEnumerator *)reverseObjectEnumerator;
|
||||
|
||||
@end
|
||||
@@ -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.allObjects) {
|
||||
if ([node isEqual:[HTMLMarker marker]]) {
|
||||
break;
|
||||
}
|
||||
if (node.htmlNamespace == element.htmlNamespace &&
|
||||
[node.tagName isEqualToString:element.tagName] &&
|
||||
[node.attributes isEqualTo: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
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// HTMLMarker.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 02/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLMarker : NSObject
|
||||
|
||||
+ (instancetype)marker;
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// HTMLNamespaces.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 03/11/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
typedef NS_ENUM(NSInteger, HTMLNamespace)
|
||||
{
|
||||
HTMLNamespaceHTML,
|
||||
HTMLNamespaceMathML,
|
||||
HTMLNamespaceSVG,
|
||||
HTMLNamespaceXLink,
|
||||
HTMLNamespaceXML,
|
||||
HTMLNamespaceXMLNS,
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// HTMLNode.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 24/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(short, HTMLNodeType)
|
||||
{
|
||||
HTMLNodeElement = 1,
|
||||
HTMLNodeAttribute = 2, // historical
|
||||
HTMLNodeText = 3,
|
||||
HTMLNodeCDATASection = 4, // historical
|
||||
HTMLNodeEntityReference = 5, // historical
|
||||
HTMLNodeEntity = 6, // historical
|
||||
HTMLNodeProcessingInstruction = 7,
|
||||
HTMLNodeComment = 8,
|
||||
HTMLNodeDocument = 9,
|
||||
HTMLNodeDocumentType = 10,
|
||||
HTMLNodeDocumentFragment = 11,
|
||||
HTMLNodeNotation = 12 // historical
|
||||
};
|
||||
|
||||
@class HTMLDocument;
|
||||
@class HTMLElement;
|
||||
|
||||
@interface HTMLNode : NSObject <NSCopying>
|
||||
|
||||
@property (nonatomic, assign, readonly) HTMLNodeType type;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString *name;
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLDocument *ownerDocument;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString *baseURI;
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLNode *parentNode;
|
||||
|
||||
@property (nonatomic, weak, readonly) HTMLElement *parentElement;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSOrderedSet *childNodes;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *firstChiledNode;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *lastChildNode;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *previousSibling;
|
||||
|
||||
@property (nonatomic, strong, readonly) HTMLNode *nextSibling;
|
||||
|
||||
@property (nonatomic, copy) NSString *textContent;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type;
|
||||
|
||||
- (BOOL)hasChildNodes;
|
||||
|
||||
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type;
|
||||
|
||||
- (NSUInteger)childNodesCount;
|
||||
|
||||
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index;
|
||||
|
||||
- (NSUInteger)indexOfChildNode:(HTMLNode *)node;
|
||||
|
||||
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child;
|
||||
|
||||
- (HTMLNode *)appendNode:(HTMLNode *)node;
|
||||
|
||||
- (void)appendNodes:(NSArray *)nodes;
|
||||
|
||||
- (HTMLNode *)replaceChildNode:(HTMLNode *)node withNode:(HTMLNode *)replacement;
|
||||
|
||||
- (void)replaceAllChildNodesWithNode:(HTMLNode *)node;
|
||||
|
||||
- (HTMLNode *)removeChildNode:(HTMLNode *)node;
|
||||
|
||||
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index;
|
||||
|
||||
- (void)reparentChildNodesIntoNode:(HTMLNode *)node;
|
||||
|
||||
- (void)removeAllChildNodes;
|
||||
|
||||
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block;
|
||||
|
||||
- (NSEnumerator *)treeEnumerator;
|
||||
|
||||
- (NSEnumerator *)reverseTreeEnumerator;
|
||||
|
||||
- (NSString *)outerHTML;
|
||||
|
||||
- (NSString *)innerHTML;
|
||||
|
||||
- (NSString *)treeDescription;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,482 @@
|
||||
//
|
||||
// HTMLNode.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 24/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
#import "HTMLDocument.h"
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLKitExceptions.h"
|
||||
#import "HTMLNodeTreeEnumerator.h"
|
||||
|
||||
@interface HTMLNode ()
|
||||
{
|
||||
NSMutableOrderedSet *_childNodes;
|
||||
}
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@end
|
||||
|
||||
@implementation HTMLNode
|
||||
@synthesize ownerDocument = _ownerDocument;
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name type:(HTMLNodeType)type
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = name;
|
||||
_type = type;
|
||||
_childNodes = [NSMutableOrderedSet new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (HTMLDocument *)ownerDocument
|
||||
{
|
||||
if (_type == HTMLNodeDocument) {
|
||||
return (HTMLDocument *)self;
|
||||
} else {
|
||||
return _ownerDocument;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setOwnerDocument:(HTMLDocument *)ownerDocument
|
||||
{
|
||||
_ownerDocument = ownerDocument;
|
||||
[self.childNodes.array makeObjectsPerformSelector:@selector(setOwnerDocument:) withObject:ownerDocument];
|
||||
}
|
||||
|
||||
- (void)setBaseURI:(NSString *)baseURI
|
||||
{
|
||||
_baseURI = [baseURI copy];
|
||||
[self.childNodes.array makeObjectsPerformSelector:@selector(setBaseURI:) withObject:baseURI];
|
||||
}
|
||||
|
||||
- (void)setParentNode:(HTMLNode *)parentNode
|
||||
{
|
||||
_parentNode = parentNode;
|
||||
}
|
||||
|
||||
- (HTMLElement *)parentElement
|
||||
{
|
||||
return _parentNode.type == HTMLNodeElement ? (HTMLElement *)_parentNode : nil;
|
||||
}
|
||||
|
||||
- (HTMLNode *)firstChiledNode
|
||||
{
|
||||
return self.childNodes.firstObject;
|
||||
}
|
||||
|
||||
- (HTMLNode *)lastChildNode
|
||||
{
|
||||
return self.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];
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Child Nodes
|
||||
|
||||
- (BOOL)hasChildNodes
|
||||
{
|
||||
return self.childNodes.count > 0;
|
||||
}
|
||||
|
||||
- (BOOL)hasChildNodeOfType:(HTMLNodeType)type
|
||||
{
|
||||
NSUInteger index = [self.childNodes indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([(HTMLNode *)obj type] == type) {
|
||||
*stop = YES;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}];
|
||||
|
||||
return index != NSNotFound;
|
||||
}
|
||||
|
||||
- (NSUInteger)childNodesCount
|
||||
{
|
||||
return self.childNodes.count;
|
||||
}
|
||||
|
||||
- (HTMLNode *)childNodeAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [self.childNodes objectAtIndex:index];
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildNode:(HTMLNode *)node
|
||||
{
|
||||
return [self.childNodes indexOfObject:node];
|
||||
}
|
||||
|
||||
- (HTMLNode *)insertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
|
||||
{
|
||||
node = [self preInsertNode:node beforeChildNode:child];
|
||||
node.parentNode = self;
|
||||
return node;
|
||||
}
|
||||
|
||||
- (HTMLNode *)appendNode:(HTMLNode *)node
|
||||
{
|
||||
node = [self preInsertNode:node beforeChildNode:nil];
|
||||
node.parentNode = self;
|
||||
return node;
|
||||
}
|
||||
|
||||
- (void)appendNodes:(NSArray *)nodes
|
||||
{
|
||||
for (id node in nodes) {
|
||||
[self appendNode:node];
|
||||
}
|
||||
}
|
||||
|
||||
- (HTMLNode *)replaceChildNode:(HTMLNode *)child withNode:(HTMLNode *)node
|
||||
{
|
||||
[self ensureReplacementValidityOfChildNode:child withNode:node];
|
||||
|
||||
[self.ownerDocument adoptNode:node];
|
||||
NSUInteger index = [self indexOfChildNode:child];
|
||||
node.parentNode = self;
|
||||
[(NSMutableOrderedSet *)self.childNodes replaceObjectAtIndex:index withObject:node];
|
||||
return child;
|
||||
}
|
||||
|
||||
- (void)replaceAllChildNodesWithNode:(HTMLNode *)node
|
||||
{
|
||||
[self removeAllChildNodes];
|
||||
|
||||
if (node != nil) {
|
||||
[self.ownerDocument adoptNode:node];
|
||||
[self insertNode:node beforeChildNode:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (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.",
|
||||
NSStringFromSelector(_cmd), child];
|
||||
}
|
||||
|
||||
[(NSMutableOrderedSet *)self.childNodes removeObject:child];
|
||||
child.parentNode = nil;
|
||||
return child;
|
||||
}
|
||||
|
||||
- (HTMLNode *)removeChildNodeAtIndex:(NSUInteger)index
|
||||
{
|
||||
HTMLNode *node = [self childNodeAtIndex:index];
|
||||
return [self removeChildNode:node];
|
||||
}
|
||||
|
||||
- (void)reparentChildNodesIntoNode:(HTMLNode *)node
|
||||
{
|
||||
for (HTMLNode *child in self.childNodes.array) {
|
||||
[node appendNode:child];
|
||||
}
|
||||
[self removeAllChildNodes];
|
||||
}
|
||||
|
||||
- (void)removeAllChildNodes
|
||||
{
|
||||
[(NSMutableOrderedSet *)self.childNodes removeAllObjects];
|
||||
}
|
||||
|
||||
#pragma mark - Enumeration
|
||||
|
||||
- (void)enumerateChildNodesUsingBlock:(void (^)(HTMLNode *node, NSUInteger idx, BOOL *stop))block
|
||||
{
|
||||
if (block == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.childNodes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
block(obj, idx, stop);
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSEnumerator *)treeEnumerator
|
||||
{
|
||||
return [[HTMLNodeTreeEnumerator alloc] initWithNode:self reverse:NO];
|
||||
}
|
||||
|
||||
- (NSEnumerator *)reverseTreeEnumerator
|
||||
{
|
||||
return [[HTMLNodeTreeEnumerator alloc] initWithNode:self reverse:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Mutation Algorithms
|
||||
|
||||
- (HTMLNode *)preInsertNode:(HTMLNode *)node beforeChildNode:(HTMLNode *)child
|
||||
{
|
||||
[self ensurePreInsertionValidityOfNode:node beforeChildNode:child];
|
||||
[self.ownerDocument adoptNode:node];
|
||||
NSUInteger index = [self indexOfChildNode:child];
|
||||
if (index != NSNotFound) {
|
||||
[(NSMutableOrderedSet *)self.childNodes insertObject:node atIndex:index];
|
||||
} else {
|
||||
[(NSMutableOrderedSet *)self.childNodes addObject:node];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
NS_INLINE void CheckParentValid(HTMLNode *node, NSString *cmd)
|
||||
{
|
||||
if (node.type != HTMLNodeDocument &&
|
||||
node.type != HTMLNodeDocumentFragment &&
|
||||
node.type != HTMLNodeElement) {
|
||||
[NSException raise:HTMLKitHierarchyRequestError
|
||||
format:@"%@: Hierarchy Request Error, inserting into %@ is not allowed. The operation would yield an incorrect node tree.",
|
||||
cmd, node.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.type != HTMLNodeDocumentFragment &&
|
||||
node.type != HTMLNodeDocumentType &&
|
||||
node.type != HTMLNodeElement &&
|
||||
node.type != HTMLNodeText &&
|
||||
node.type != 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.type == HTMLNodeText && parent.type == 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.type == HTMLNodeDocumentType && parent.type != 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, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckChildsParent(self, child, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckInsertedNodeValid(node, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckInvalidCombination(self, node, NSStringFromSelector(_cmd));
|
||||
|
||||
void (^ hierarchyError)() = ^{
|
||||
[NSException raise:HTMLKitHierarchyRequestError
|
||||
format:@"%@: Hierarchy Request Error. The operation would yield an incorrect node tree.",
|
||||
NSStringFromSelector(_cmd)];
|
||||
};
|
||||
|
||||
if (self.type == HTMLNodeDocument) {
|
||||
switch (node.type) {
|
||||
case HTMLNodeDocumentFragment:
|
||||
if (self.childNodesCount > 1 ||
|
||||
[self hasChildNodeOfType:HTMLNodeText]) {
|
||||
hierarchyError();
|
||||
} else if (self.childNodesCount == 1) {
|
||||
if (self.hasChildNodes ||
|
||||
child.type == HTMLNodeDocumentType ||
|
||||
child.nextSibling.type == HTMLNodeDocumentType) {
|
||||
hierarchyError();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HTMLNodeElement:
|
||||
if ([self hasChildNodeOfType:HTMLNodeElement] ||
|
||||
child.type == HTMLNodeDocumentType ||
|
||||
(child != nil && child.nextSibling.type == 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, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckChildsParent(self, child, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckInsertedNodeValid(node, NSStringFromSelector(_cmd));
|
||||
|
||||
CheckInvalidCombination(self, node, NSStringFromSelector(_cmd));
|
||||
|
||||
void (^ hierarchyError)() = ^{
|
||||
[NSException raise:HTMLKitHierarchyRequestError
|
||||
format:@"%@: Hierarchy Request Error. The operation would yield an incorrect node tree.",
|
||||
NSStringFromSelector(_cmd)];
|
||||
};
|
||||
|
||||
if (self.type == HTMLNodeDocument) {
|
||||
switch (node.type) {
|
||||
case HTMLNodeDocumentFragment:
|
||||
if (self.childNodesCount > 1 ||
|
||||
[self hasChildNodeOfType:HTMLNodeText]) {
|
||||
hierarchyError();
|
||||
} else if (self.childNodesCount == 1) {
|
||||
if (self.firstChiledNode != node ||
|
||||
child.nextSibling.type == HTMLNodeDocumentType) {
|
||||
hierarchyError();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HTMLNodeElement:
|
||||
{
|
||||
if (child.nextSibling.type == HTMLNodeDocumentType) {
|
||||
hierarchyError();
|
||||
}
|
||||
[self enumerateChildNodesUsingBlock:^(HTMLNode *node, NSUInteger idx, BOOL *stop) {
|
||||
if (node.type == HTMLNodeElement && node != child) {
|
||||
*stop = YES;
|
||||
hierarchyError();
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case HTMLNodeDocumentType:
|
||||
{
|
||||
if (child.previousSibling.type == HTMLNodeElement) {
|
||||
hierarchyError();
|
||||
}
|
||||
[self enumerateChildNodesUsingBlock:^(HTMLNode *node, NSUInteger idx, BOOL *stop) {
|
||||
if (node.type == HTMLNodeDocument && node != child) {
|
||||
*stop = YES;
|
||||
hierarchyError();
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLNode *copy = [[self.class alloc] initWithName:self.name type:self.type];
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - Serialization
|
||||
|
||||
- (NSString *)outerHTML
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)innerHTML
|
||||
{
|
||||
return [[self.childNodes.array valueForKey:@"outerHTML"] componentsJoinedByString:@""];
|
||||
}
|
||||
|
||||
#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];
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
{
|
||||
return self.treeDescription;
|
||||
}
|
||||
|
||||
- (id)debugQuickLookObject
|
||||
{
|
||||
return self.outerHTML;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// HTMLNodeTreeEnumerator.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 28/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class HTMLNode;
|
||||
|
||||
@interface HTMLNodeTreeEnumerator : NSEnumerator
|
||||
|
||||
- (instancetype)initWithNode:(HTMLNode *)node reverse:(BOOL)reverse;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// HTMLNodeTreeEnumerator.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 28/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNodeTreeEnumerator.h"
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLNodeTreeEnumerator ()
|
||||
{
|
||||
BOOL _reverse;
|
||||
NSMutableArray *_stack;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLNodeTreeEnumerator
|
||||
|
||||
- (instancetype)initWithNode:(HTMLNode *)node reverse:(BOOL)reverse
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_reverse = reverse;
|
||||
_stack = [[NSMutableArray alloc] initWithObjects:node, nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)nextObject
|
||||
{
|
||||
if (_stack.count == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
HTMLNode *node = _stack.lastObject;
|
||||
[_stack removeLastObject];
|
||||
|
||||
NSArray *childNodes = node.childNodes.array;
|
||||
if (childNodes != nil && childNodes.count > 0) {
|
||||
if (childNodes.count > 1) {
|
||||
NSRange range = NSMakeRange(_reverse ? 0 : 1, childNodes.count - 1);
|
||||
NSArray *rest = [childNodes subarrayWithRange:range];
|
||||
|
||||
[_stack addObjectsFromArray:_reverse ? rest : rest.reverseObjectEnumerator.allObjects];
|
||||
}
|
||||
[_stack addObject:_reverse ? childNodes.lastObject : childNodes.firstObject];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// 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 "HTMLElement.h"
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLTemplate.h"
|
||||
#import "HTMLDocumentFragment.h"
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// HTMLOrderedDictionary.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 14/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLOrderedDictionary : NSMutableDictionary
|
||||
|
||||
- (id)objectAtIndex:(NSUInteger)index;
|
||||
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey atIndex:(NSUInteger)index;
|
||||
- (void)removeObjectAtIndex:(NSUInteger)index;
|
||||
- (void)replaceKeyValueAtIndex:(NSUInteger)index withObject:(id)anObject andKey:(id<NSCopying>)aKey;
|
||||
- (void)replaceKey:(id<NSCopying>)aKey withKey:(id<NSCopying>)newKey;
|
||||
- (NSUInteger)indexOfKey:(id<NSCopying>)aKey;
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index;
|
||||
|
||||
- (NSEnumerator *)reverseKeyEnumerator;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// 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];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// HTMLParseErrorToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
|
||||
@interface HTMLParseErrorToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *reason;
|
||||
@property (nonatomic, assign) NSUInteger location;
|
||||
|
||||
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// HTMLParseErrorToken.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLParseErrorToken.h"
|
||||
|
||||
@interface HTMLParseErrorToken ()
|
||||
{
|
||||
NSString *_reason;
|
||||
NSUInteger _location;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLParseErrorToken
|
||||
@synthesize reason = _reason;
|
||||
@synthesize location = _location;
|
||||
|
||||
- (instancetype)initWithReasonMessage:(NSString *)reason andStreamLocation:(NSUInteger)location
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.type = HTMLTokenTypeParseError;
|
||||
_reason = [reason copy];
|
||||
_location = location;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p Reason='%@' Location='%lu'>", self.class, self, _reason, _location];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// HTMLParser.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 04/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
@interface HTMLParser : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSArray *parseErrors;
|
||||
@property (nonatomic, strong, readonly) HTMLDocument *document;
|
||||
|
||||
- (instancetype)initWithString:(NSString *)string;
|
||||
|
||||
- (HTMLDocument *)parseDocument;
|
||||
- (NSArray *)parseFragmentWithContextElement:(HTMLElement *)contextElement;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// HTMLParserInsertionMode.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 05/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#define INSERTION_MODES \
|
||||
MODE_ENTRY( HTMLInsertionModeInitial, = 0 ) \
|
||||
MODE_ENTRY( HTMLInsertionModeBeforeHTML, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeBeforeHead, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInHead, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInHeadNoscript, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeAfterHead, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInBody, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeText, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInTable, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInTableText, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInCaption, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInColumnGroup, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInTableBody, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInRow, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInCell, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInSelect, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInSelectInTable, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInTemplate, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeAfterBody, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeInFrameset, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeAfterFrameset, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeAfterAfterBody, ) \
|
||||
MODE_ENTRY( HTMLInsertionModeAfterAfterFrameset, ) \
|
||||
|
||||
typedef NS_ENUM(NSUInteger, HTMLInsertionMode)
|
||||
{
|
||||
#define MODE_ENTRY( name, value ) name value,
|
||||
INSERTION_MODES
|
||||
#undef MODE_ENTRY
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// HTMLQuirksMode.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 28/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
typedef NS_ENUM(short, HTMLQuirksMode)
|
||||
{
|
||||
HTMLQuirksModeNoQuirks,
|
||||
HTMLQuirksModeQuirks,
|
||||
HTMLQuirksModeLimitedQuirks
|
||||
};
|
||||
|
||||
#define QUIRKS_MODE_PREFIXES \
|
||||
QUIRKS_ENTRY( "+//Silmaril//dtd html Pro v0r11 19970101//" ) \
|
||||
QUIRKS_ENTRY( "-//AS//DTD HTML 3.0 asWedit + extensions//" ) \
|
||||
QUIRKS_ENTRY( "-//AdvaSoft Ltd//DTD HTML 3.0 asWedit + extensions//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 2.0 Level 1//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 2.0 Level 2//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 2.0 Strict Level 1//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 2.0 Strict Level 2//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 2.0 Strict//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 2.0//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 2.1E//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 3.0//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 3.2 Final//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 3.2//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML 3//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Level 0//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Level 1//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Level 2//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Level 3//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Strict Level 0//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Strict Level 1//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Strict Level 2//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Strict Level 3//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML Strict//" ) \
|
||||
QUIRKS_ENTRY( "-//IETF//DTD HTML//" ) \
|
||||
QUIRKS_ENTRY( "-//Metrius//DTD Metrius Presentational//" ) \
|
||||
QUIRKS_ENTRY( "-//Microsoft//DTD Internet Explorer 2.0 HTML Strict//" ) \
|
||||
QUIRKS_ENTRY( "-//Microsoft//DTD Internet Explorer 2.0 HTML//" ) \
|
||||
QUIRKS_ENTRY( "-//Microsoft//DTD Internet Explorer 2.0 Tables//" ) \
|
||||
QUIRKS_ENTRY( "-//Microsoft//DTD Internet Explorer 3.0 HTML Strict//" ) \
|
||||
QUIRKS_ENTRY( "-//Microsoft//DTD Internet Explorer 3.0 HTML//" ) \
|
||||
QUIRKS_ENTRY( "-//Microsoft//DTD Internet Explorer 3.0 Tables//" ) \
|
||||
QUIRKS_ENTRY( "-//Netscape Comm. Corp.//DTD HTML//" ) \
|
||||
QUIRKS_ENTRY( "-//Netscape Comm. Corp.//DTD Strict HTML//" ) \
|
||||
QUIRKS_ENTRY( "-//O'Reilly and Associates//DTD HTML 2.0//" ) \
|
||||
QUIRKS_ENTRY( "-//O'Reilly and Associates//DTD HTML Extended 1.0//" ) \
|
||||
QUIRKS_ENTRY( "-//O'Reilly and Associates//DTD HTML Extended Relaxed 1.0//" ) \
|
||||
QUIRKS_ENTRY( "-//SQ//DTD HTML 2.0 HoTMetaL + extensions//" ) \
|
||||
QUIRKS_ENTRY( "-//SoftQuad Software//DTD HoTMetaL PRO 6.0::19990601::extensions to HTML 4.0//" ) \
|
||||
QUIRKS_ENTRY( "-//SoftQuad//DTD HoTMetaL PRO 4.0::19971010::extensions to HTML 4.0//" ) \
|
||||
QUIRKS_ENTRY( "-//Spyglass//DTD HTML 2.0 Extended//" ) \
|
||||
QUIRKS_ENTRY( "-//Sun Microsystems Corp.//DTD HotJava HTML//" ) \
|
||||
QUIRKS_ENTRY( "-//Sun Microsystems Corp.//DTD HotJava Strict HTML//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML 3 1995-03-24//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML 3.2 Draft//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML 3.2 Final//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML 3.2//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML 3.2S Draft//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML 4.0 Frameset//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML 4.0 Transitional//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML Experimental 19960712//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD HTML Experimental 970421//" ) \
|
||||
QUIRKS_ENTRY( "-//W3C//DTD W3 HTML//" ) \
|
||||
QUIRKS_ENTRY( "-//W3O//DTD W3 HTML 3.0//" ) \
|
||||
QUIRKS_ENTRY( "-//WebTechs//DTD Mozilla HTML 2.0//" ) \
|
||||
QUIRKS_ENTRY( "-//WebTechs//DTD Mozilla HTML//" )
|
||||
|
||||
static NSString * HTMLQuirksModePrefixes[] = {
|
||||
#define QUIRKS_ENTRY( prefix ) @prefix,
|
||||
QUIRKS_MODE_PREFIXES
|
||||
#undef QUIRKS_ENTRY
|
||||
};
|
||||
|
||||
NS_INLINE BOOL QuirksModePrefixMatch(NSString *publicIdentifier)
|
||||
{
|
||||
for (int i = 0; i < sizeof(HTMLQuirksModePrefixes) / sizeof(HTMLQuirksModePrefixes[0]); i++) {
|
||||
if ([publicIdentifier hasPrefixIgnoringCase:HTMLQuirksModePrefixes[i]]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// HTMLStackOfOpenElements.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 08/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLElement.h"
|
||||
|
||||
@interface HTMLStackOfOpenElements : NSObject <NSFastEnumeration>
|
||||
|
||||
- (HTMLElement *)currentNode;
|
||||
- (HTMLElement *)firstNode;
|
||||
- (HTMLElement *)lastNode;
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)index;
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||
- (NSUInteger)indexOfElement:(id)node;
|
||||
|
||||
- (void)pushElement:(HTMLElement *)element;
|
||||
- (void)removeElement:(id)element;
|
||||
- (BOOL)containsElement:(id)element;
|
||||
- (BOOL)containsElementWithTagName:(NSString *)tagName;
|
||||
|
||||
- (void)insertElement:(HTMLElement *)element atIndex:(NSUInteger)index;
|
||||
- (void)replaceElementAtIndex:(NSUInteger)index withElement:(HTMLElement *)element;
|
||||
|
||||
- (void)popCurrentNode;
|
||||
- (void)popElementsUntilElementPoppedWithTagName:(NSString *)tagName;
|
||||
- (void)popElementsUntilAnElementPoppedWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (void)popElementsUntilElementPopped:(HTMLElement *)element;
|
||||
- (void)popElementsUntilTemplateElementPopped;
|
||||
- (void)clearBackToTableContext;
|
||||
- (void)clearBackToTableBodyContext;
|
||||
- (void)clearBackToTableRowContext;
|
||||
- (void)popAll;
|
||||
|
||||
- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames;
|
||||
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName;
|
||||
- (HTMLElement *)furthestBlockAfterIndex:(NSUInteger)index;
|
||||
|
||||
- (NSUInteger)count;
|
||||
- (BOOL)isEmpy;
|
||||
|
||||
- (NSEnumerator *)enumerator;
|
||||
- (NSEnumerator *)reverseObjectEnumerator;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,318 @@
|
||||
//
|
||||
// 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;
|
||||
NSDictionary *_specificScopeElementTypes;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLStackOfOpenElements
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_stack = [NSMutableArray new];
|
||||
_specificScopeElementTypes = @{
|
||||
@"applet": @(HTMLNamespaceHTML),
|
||||
@"caption": @(HTMLNamespaceHTML),
|
||||
@"html": @(HTMLNamespaceHTML),
|
||||
@"table": @(HTMLNamespaceHTML),
|
||||
@"td": @(HTMLNamespaceHTML),
|
||||
@"th": @(HTMLNamespaceHTML),
|
||||
@"marquee": @(HTMLNamespaceHTML),
|
||||
@"object": @(HTMLNamespaceHTML),
|
||||
@"template": @(HTMLNamespaceHTML),
|
||||
@"mi": @(HTMLNamespaceMathML),
|
||||
@"mo": @(HTMLNamespaceMathML),
|
||||
@"mn": @(HTMLNamespaceMathML),
|
||||
@"ms": @(HTMLNamespaceMathML),
|
||||
@"mtext": @(HTMLNamespaceMathML),
|
||||
@"annotation-xml": @(HTMLNamespaceMathML),
|
||||
@"foreignObject": @(HTMLNamespaceSVG),
|
||||
@"desc": @(HTMLNamespaceSVG),
|
||||
@"title": @(HTMLNamespaceSVG)
|
||||
};
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#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 && ![self.currentNode.tagName isEqualToString:tagName]) {
|
||||
[_stack removeLastObject];
|
||||
}
|
||||
[_stack removeLastObject];
|
||||
}
|
||||
|
||||
- (void)popElementsUntilAnElementPoppedWithAnyOfTagNames:(NSArray *)tagNames
|
||||
{
|
||||
while (self.currentNode && ![tagNames containsObject:self.currentNode.tagName]) {
|
||||
[_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
|
||||
|
||||
- (HTMLElement *)hasElementInScopeWithTagName:(NSString *)tagName;
|
||||
{
|
||||
return [self hasAnyElementInSpecificScopeWithTagNames:@[tagName] andElementTypes:_specificScopeElementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasAnyElementInScopeWithAnyOfTagNames:(NSArray *)tagNames
|
||||
{
|
||||
return [self hasAnyElementInSpecificScopeWithTagNames:tagNames andElementTypes:_specificScopeElementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInListItemScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:_specificScopeElementTypes];
|
||||
[elementTypes addEntriesFromDictionary:@{@"ol": @(HTMLNamespaceHTML),
|
||||
@"ul": @(HTMLNamespaceHTML)}];
|
||||
|
||||
return [self hasElementInSpecificScopeWithTagName:tagName
|
||||
andElementTypes:elementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInButtonScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
NSMutableDictionary *elementTypes = [NSMutableDictionary dictionaryWithDictionary:_specificScopeElementTypes];
|
||||
[elementTypes addEntriesFromDictionary:@{@"button": @(HTMLNamespaceHTML)}];
|
||||
|
||||
return [self hasElementInSpecificScopeWithTagName:tagName
|
||||
andElementTypes:elementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInTableScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
return [self hasElementInSpecificScopeWithTagName:tagName
|
||||
andElementTypes:@{@"html": @(HTMLNamespaceHTML),
|
||||
@"table": @(HTMLNamespaceHTML),
|
||||
@"template": @(HTMLNamespaceHTML)}];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInTableScopeWithAnyOfTagNames:(NSArray *)tagNames
|
||||
{
|
||||
return [self hasAnyElementInSpecificScopeWithTagNames:tagNames
|
||||
andElementTypes:@{@"html": @(HTMLNamespaceHTML),
|
||||
@"table": @(HTMLNamespaceHTML),
|
||||
@"template": @(HTMLNamespaceHTML)}];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInSelectScopeWithTagName:(NSString *)tagName
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if ([node.tagName isEqualToString:tagName]) {
|
||||
return node;
|
||||
}
|
||||
if (!(node.htmlNamespace == HTMLNamespaceHTML &&
|
||||
[node.tagName isEqualToAny:@"optgroup", @"option", nil])) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasElementInSpecificScopeWithTagName:(NSString *)tagName
|
||||
andElementTypes:(NSDictionary *)elementTypes
|
||||
{
|
||||
return [self hasAnyElementInSpecificScopeWithTagNames:@[tagName] andElementTypes:elementTypes];
|
||||
}
|
||||
|
||||
- (HTMLElement *)hasAnyElementInSpecificScopeWithTagNames:(NSArray *)tagNames
|
||||
andElementTypes:(NSDictionary *)elementTypes
|
||||
{
|
||||
for (HTMLElement *node in _stack.reverseObjectEnumerator) {
|
||||
if ([tagNames containsObject:node.tagName]) {
|
||||
return node;
|
||||
}
|
||||
if ([elementTypes[node.tagName] isEqual:@(node.htmlNamespace)]) {
|
||||
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
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// HTMLTagToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
|
||||
@interface HTMLTagToken : HTMLToken
|
||||
|
||||
@property (nonatomic, copy) NSString *tagName;
|
||||
@property (nonatomic, strong) HTMLOrderedDictionary *attributes;
|
||||
@property (nonatomic, assign, getter = isSelfClosing) BOOL selfClosing;
|
||||
|
||||
- (instancetype)initWithTagName:(NSString *)tagName;
|
||||
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes;
|
||||
|
||||
- (void)appendStringToTagName:(NSString *)string;
|
||||
|
||||
@end
|
||||
|
||||
@interface HTMLStartTagToken : HTMLTagToken
|
||||
|
||||
@end
|
||||
|
||||
@interface HTMLEndTagToken : HTMLTagToken
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// HTMLTemplate.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 12/04/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLDocumentFragment.h"
|
||||
|
||||
@interface HTMLTemplate : HTMLElement
|
||||
|
||||
@property (nonatomic, strong) HTMLDocumentFragment *content;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// HTMLTemplate.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 12/04/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLTemplate.h"
|
||||
#import "HTMLDocument.h"
|
||||
|
||||
@interface HTMLNode (Private)
|
||||
@property (nonatomic, weak) HTMLDocument *ownerDocument;
|
||||
@end
|
||||
|
||||
@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
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// HTMLText.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLNode.h"
|
||||
|
||||
@interface HTMLText : HTMLNode
|
||||
|
||||
@property (nonatomic, copy) NSMutableString *data;
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data;
|
||||
|
||||
- (void)appendString:(NSString *)string;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// HTMLText.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 26/02/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
@implementation HTMLText
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithData:@""];
|
||||
}
|
||||
|
||||
- (instancetype)initWithData:(NSString *)data
|
||||
{
|
||||
self = [super initWithName:@"#text" type:HTMLNodeText];
|
||||
if (self) {
|
||||
_data = [[NSMutableString alloc] initWithString:data ?: @""];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)textContent
|
||||
{
|
||||
return [self.data copy];
|
||||
}
|
||||
|
||||
- (void)setTextContent:(NSString *)textContent
|
||||
{
|
||||
[self.data setString:textContent ?: @""];
|
||||
}
|
||||
|
||||
- (void)appendString:(NSString *)string
|
||||
{
|
||||
[self.data appendString:string];
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
HTMLText *copy = [super copyWithZone:zone];
|
||||
copy.data = self.data;
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark - Serialization
|
||||
|
||||
- (NSString *)outerHTML
|
||||
{
|
||||
if ([self.parentElement.tagName isEqualToAny:@"style", @"script", @"xmp", @"iframe", @"noembed", @"noframes",
|
||||
@"plaintext", @"noscript", nil]) {
|
||||
return self.data;
|
||||
} else {
|
||||
NSRange range = NSMakeRange(0, self.data.length);
|
||||
NSMutableString *escaped = [self.data mutableCopy];
|
||||
[escaped replaceOccurrencesOfString:@"&" withString:@"&" options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"\00A0" withString:@" " options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@"<" withString:@"<" options:0 range:range];
|
||||
[escaped replaceOccurrencesOfString:@">" withString:@">" options:0 range:range];
|
||||
return escaped;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p \"%@\">", self.class, self, self.data];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// HTMLToken.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class HTMLDOCTYPEToken;
|
||||
@class HTMLTagToken;
|
||||
@class HTMLStartTagToken;
|
||||
@class HTMLEndTagToken;
|
||||
@class HTMLCommentToken;
|
||||
@class HTMLCharacterToken;
|
||||
@class HTMLParseErrorToken;
|
||||
|
||||
NS_INLINE BOOL bothNilOrEqual(id first, id second) {
|
||||
return (first == nil && second == nil) || ([first isEqual:second]);
|
||||
}
|
||||
|
||||
typedef NS_ENUM(NSUInteger, HTMLTokenType)
|
||||
{
|
||||
HTMLTokenTypeCharacter,
|
||||
HTMLTokenTypeComment,
|
||||
HTMLTokenTypeDoctype,
|
||||
HTMLTokenTypeEndTag,
|
||||
HTMLTokenTypeEOF,
|
||||
HTMLTokenTypeParseError,
|
||||
HTMLTokenTypeStartTag
|
||||
};
|
||||
|
||||
@interface HTMLToken : NSObject
|
||||
|
||||
@property (nonatomic, assign) HTMLTokenType type;
|
||||
|
||||
- (BOOL)isDoctypeToken;
|
||||
- (BOOL)isStartTagToken;
|
||||
- (BOOL)isEndTagToken;
|
||||
- (BOOL)isCommentToken;
|
||||
- (BOOL)isCharacterToken;
|
||||
- (BOOL)isEOFToken;
|
||||
- (BOOL)isParseError;
|
||||
|
||||
- (HTMLDOCTYPEToken *)asDoctypeToken;
|
||||
- (HTMLTagToken *)asTagToken;
|
||||
- (HTMLStartTagToken *)asStartTagToken;
|
||||
- (HTMLEndTagToken *)asEndTagToken;
|
||||
- (HTMLCommentToken *)asCommentToken;
|
||||
- (HTMLCharacterToken *)asCharacterToken;
|
||||
- (HTMLParseErrorToken *)asParseError;
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// HTMLTokenizer.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 19/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTMLToken.h"
|
||||
#import "HTMLTokenizerStates.h"
|
||||
|
||||
/**
|
||||
* HTML Tokenizer
|
||||
* https://html.spec.whatwg.org/multipage/syntax.html#tokenization
|
||||
*/
|
||||
|
||||
@class HTMLParser;
|
||||
|
||||
@interface HTMLTokenizer : NSEnumerator
|
||||
|
||||
@property (nonatomic, readonly) NSString *string;
|
||||
@property (nonatomic, assign) HTMLTokenizerState state;
|
||||
@property (nonatomic, weak, readonly) HTMLParser *parser;
|
||||
|
||||
- (instancetype)initWithString:(NSString *)string;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// Header.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#define CHARACTERS \
|
||||
CHAR( NULL_CHAR, 0x0000 ) \
|
||||
CHAR( CHARACTER_TABULATION, 0x0009 ) \
|
||||
CHAR( LINE_FEED, 0x000A ) \
|
||||
CHAR( FORM_FEED, 0x000C ) \
|
||||
CHAR( CARRIAGE_RETURN, 0x000D ) \
|
||||
CHAR( SPACE, 0x0020 ) \
|
||||
CHAR( EXCLAMATION_MARK, 0x0021 ) \
|
||||
CHAR( QUOTATION_MARK, 0x0022 ) \
|
||||
CHAR( NUMBER_SIGN, 0x0023 ) \
|
||||
CHAR( AMPERSAND, 0x0026 ) \
|
||||
CHAR( APOSTROPHE, 0x0027 ) \
|
||||
CHAR( SOLIDUS, 0x002F ) \
|
||||
CHAR( DIGIT_ZERO, 0x0030 ) \
|
||||
CHAR( DIGIT_NINE, 0x0039 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_A, 0x0041 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_F, 0x0046 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_X, 0x0058 ) \
|
||||
CHAR( LATIN_CAPITAL_LETTER_Z, 0x005A ) \
|
||||
CHAR( GRAVE_ACCENT, 0x0060 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_A, 0x0061 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_F, 0x0066 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_X, 0x0078 ) \
|
||||
CHAR( LATIN_SMALL_LETTER_Z, 0x007A ) \
|
||||
CHAR( HYPHEN_MINUS, 0x002D ) \
|
||||
CHAR( SEMICOLON, 0x003B ) \
|
||||
CHAR( LESS_THAN_SIGN, 0x003C ) \
|
||||
CHAR( EQUALS_SIGN, 0x003D ) \
|
||||
CHAR( GREATER_THAN_SIGN, 0x003E ) \
|
||||
CHAR( QUESTION_MARK, 0x003F ) \
|
||||
CHAR( REPLACEMENT_CHAR, 0xFFFD )
|
||||
|
||||
#define CHAR( name, value ) static UTF32Char const name = value;
|
||||
CHARACTERS
|
||||
#undef CHAR
|
||||
|
||||
#define NUMERIC_REPLACEMENT_CHARACTERS \
|
||||
CHAR( 0x0080, 0x20AC /* EURO SIGN */ ) \
|
||||
CHAR( 0x0081, 0x0000 /* NO REPLACEMENT */ ) \
|
||||
CHAR( 0x0082, 0x201A /* SINGLE LOW-9 QUOTATION MARK */ ) \
|
||||
CHAR( 0x0083, 0x0192 /* LATIN SMALL LETTER F WITH HOOK */ ) \
|
||||
CHAR( 0x0084, 0x201E /* DOUBLE LOW-9 QUOTATION MARK */ ) \
|
||||
CHAR( 0x0085, 0x2026 /* HORIZONTAL ELLIPSIS */ ) \
|
||||
CHAR( 0x0086, 0x2020 /* DAGGER */ ) \
|
||||
CHAR( 0x0087, 0x2021 /* DOUBLE DAGGER */ ) \
|
||||
CHAR( 0x0088, 0x02C6 /* MODIFIER LETTER CIRCUMFLEX ACCENT */ ) \
|
||||
CHAR( 0x0089, 0x2030 /* PER MILLE SIGN */ ) \
|
||||
CHAR( 0x008A, 0x0160 /* LATIN CAPITAL LETTER S WITH CARON */ ) \
|
||||
CHAR( 0x008B, 0x2039 /* SINGLE LEFT-POINTING ANGLE QUOTATION MARK */ ) \
|
||||
CHAR( 0x008C, 0x0152 /* LATIN CAPITAL LIGATURE OE */ ) \
|
||||
CHAR( 0x008D, 0x0000 /* NO REPLACEMENT */ ) \
|
||||
CHAR( 0x008E, 0x017D /* LATIN CAPITAL LETTER Z WITH CARON */ ) \
|
||||
CHAR( 0x008F, 0x0000 /* NO REPLACEMENT */ ) \
|
||||
CHAR( 0x0090, 0x0000 /* NO REPLACEMENT */ ) \
|
||||
CHAR( 0x0091, 0x2018 /* LEFT SINGLE QUOTATION MARK */ ) \
|
||||
CHAR( 0x0092, 0x2019 /* RIGHT SINGLE QUOTATION MARK */ ) \
|
||||
CHAR( 0x0093, 0x201C /* LEFT DOUBLE QUOTATION MARK */ ) \
|
||||
CHAR( 0x0094, 0x201D /* RIGHT DOUBLE QUOTATION MARK */ ) \
|
||||
CHAR( 0x0095, 0x2022 /* BULLET */ ) \
|
||||
CHAR( 0x0096, 0x2013 /* EN DASH */ ) \
|
||||
CHAR( 0x0097, 0x2014 /* EM DASH */ ) \
|
||||
CHAR( 0x0098, 0x02DC /* SMALL TILDE */ ) \
|
||||
CHAR( 0x0099, 0x2122 /* TRADE MARK SIGN */ ) \
|
||||
CHAR( 0x009A, 0x0161 /* LATIN SMALL LETTER S WITH CARON */ ) \
|
||||
CHAR( 0x009B, 0x203A /* SINGLE RIGHT-POINTING ANGLE QUOTATION MARK */ ) \
|
||||
CHAR( 0x009C, 0x0153 /* LATIN SMALL LIGATURE OE */ ) \
|
||||
CHAR( 0x009D, 0x0000 /* NO REPLACEMENT */ ) \
|
||||
CHAR( 0x009E, 0x017E /* LATIN SMALL LETTER Z WITH CARON */ ) \
|
||||
CHAR( 0x009F, 0x0178 /* LATIN CAPITAL LETTER Y WITH DIAERESIS */ )
|
||||
|
||||
static unichar NumericReplacementTable[] = {
|
||||
#define CHAR( character, replacement ) replacement,
|
||||
NUMERIC_REPLACEMENT_CHARACTERS
|
||||
#undef CHAR
|
||||
};
|
||||
|
||||
NS_INLINE BOOL isControlOrUndefinedCharacter(UTF32Char character)
|
||||
{
|
||||
return ((character >= 0x0001 && character <= 0x0008) ||
|
||||
(character >= 0x000D && character <= 0x001F) ||
|
||||
(character >= 0x007F && character <= 0x009F) ||
|
||||
(character >= 0xFDD0 && character <= 0xFDEF) ||
|
||||
character == 0x000B ||
|
||||
character == 0xFFFE ||
|
||||
character == 0xFFFF ||
|
||||
character == 0x1FFFE ||
|
||||
character == 0x1FFFF ||
|
||||
character == 0x2FFFE ||
|
||||
character == 0x2FFFF ||
|
||||
character == 0x3FFFE ||
|
||||
character == 0x3FFFF ||
|
||||
character == 0x4FFFE ||
|
||||
character == 0x4FFFF ||
|
||||
character == 0x5FFFE ||
|
||||
character == 0x5FFFF ||
|
||||
character == 0x6FFFE ||
|
||||
character == 0x6FFFF ||
|
||||
character == 0x7FFFE ||
|
||||
character == 0x7FFFF ||
|
||||
character == 0x8FFFE ||
|
||||
character == 0x8FFFF ||
|
||||
character == 0x9FFFE ||
|
||||
character == 0x9FFFF ||
|
||||
character == 0xAFFFE ||
|
||||
character == 0xAFFFF ||
|
||||
character == 0xBFFFE ||
|
||||
character == 0xBFFFF ||
|
||||
character == 0xCFFFE ||
|
||||
character == 0xCFFFF ||
|
||||
character == 0xDFFFE ||
|
||||
character == 0xDFFFF ||
|
||||
character == 0xEFFFE ||
|
||||
character == 0xEFFFF ||
|
||||
character == 0xFFFFE ||
|
||||
character == 0xFFFFF ||
|
||||
character == 0x10FFFE ||
|
||||
character == 0x10FFFF);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isDigit(UTF32Char character)
|
||||
{
|
||||
return (character >= DIGIT_ZERO && character <= DIGIT_NINE);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isHexDigit(UTF32Char character)
|
||||
{
|
||||
return ((character >= DIGIT_ZERO && character <= DIGIT_NINE) ||
|
||||
(character >= LATIN_CAPITAL_LETTER_A && character <= LATIN_CAPITAL_LETTER_F) ||
|
||||
(character >= LATIN_SMALL_LETTER_A && character <= LATIN_SMALL_LETTER_F));
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isAlphanumeric(UTF32Char character)
|
||||
{
|
||||
return ((character >= DIGIT_ZERO && character <= DIGIT_NINE) ||
|
||||
(character >= LATIN_CAPITAL_LETTER_A && character <= LATIN_CAPITAL_LETTER_Z) ||
|
||||
(character >= LATIN_SMALL_LETTER_A && character <= LATIN_SMALL_LETTER_Z));
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isStringAlphanumeric(NSString *string)
|
||||
{
|
||||
NSCharacterSet *set = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
|
||||
|
||||
return ([string rangeOfCharacterFromSet:set].location == NSNotFound);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL isInvalidNumericRange(unsigned long long numeric)
|
||||
{
|
||||
return ((numeric >= 0xD800 && numeric <= 0xDFFF) ||
|
||||
numeric > 0x10FFFF);
|
||||
}
|
||||
|
||||
NS_INLINE unichar NumericReplacementCharacter(UTF32Char character)
|
||||
{
|
||||
if (character == NULL_CHAR) {
|
||||
return REPLACEMENT_CHAR;
|
||||
} else if (character >= 0x0080 && character <= 0x009F) {
|
||||
return NumericReplacementTable[character - 0x0080];
|
||||
} else {
|
||||
return NULL_CHAR;
|
||||
}
|
||||
}
|
||||
|
||||
NS_INLINE NSString * StringFromUniChar(unichar character)
|
||||
{
|
||||
return [[NSString alloc] initWithCharacters:&character length:1];
|
||||
}
|
||||
|
||||
NS_INLINE NSString * StringFromUTF32Char(UTF32Char character)
|
||||
{
|
||||
unichar pair[2];
|
||||
Boolean isPair = CFStringGetSurrogatePairForLongCharacter(character, pair);
|
||||
return [[NSString alloc] initWithCharacters:(const unichar *)&pair length:(isPair ? 2 : 1)];
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// HTMLTokenizerEntities.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 11/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTMLTokenizerEntities : NSObject
|
||||
|
||||
+ (NSArray *)entities;
|
||||
+ (NSString *)replacementAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// HTMLTokenizerStates.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 20/09/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#define TOKENIZER_STATES \
|
||||
STATE_ENTRY( HTMLTokenizerStateData, = 0) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCharacterReferenceInData, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRCDATA, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCharacterReferenceInRCDATA, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRAWTEXT, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptData, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStatePLAINTEXT, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateTagOpen, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateEndTagOpen, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateTagName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRCDATALessThanSign, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRCDATAEndTagOpen, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRCDATAEndTagName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRAWTEXTLessThanSign, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRAWTEXTEndTagOpen, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateRAWTEXTEndTagName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataLessThanSign, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEndTagOpen, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEndTagName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEscapeStart, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEscapeStartDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEscaped, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEscapedDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEscapedDashDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEscapedLessThanSign, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEscapedEndTagOpen, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataEscapedEndTagName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataDoubleEscapeStart, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataDoubleEscaped, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataDoubleEscapedDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataDoubleEscapedDashDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataDoubleEscapedLessThanSign, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateScriptDataDoubleEscapeEnd, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBeforeAttributeName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAttributeName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAfterAttributeName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBeforeAttributeValue, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAttributeValueDoubleQuoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAttributeValueSingleQuoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAttributeValueUnquoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCharacterReferenceInAttributeValue, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAfterAttributeValueQuoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateSelfClosingStartTag, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBogusComment, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateMarkupDeclarationOpen, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentStart, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentStartDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateComment, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentEndDash, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentEnd, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCommentEndBang, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateDOCTYPE, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBeforeDOCTYPEName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateDOCTYPEName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAfterDOCTYPEName, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAfterDOCTYPEPublicKeyword, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBeforeDOCTYPEPublicIdentifier, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateDOCTYPEPublicIdentifierDoubleQuoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateDOCTYPEPublicIdentifierSingleQuoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAfterDOCTYPEPublicIdentifier, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBetweenDOCTYPEPublicAndSystemIdentifiers, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAfterDOCTYPESystemKeyword, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBeforeDOCTYPESystemIdentifier, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateDOCTYPESystemIdentifierDoubleQuoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateDOCTYPESystemIdentifierSingleQuoted, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateAfterDOCTYPESystemIdentifier, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateBogusDOCTYPE, ) \
|
||||
STATE_ENTRY( HTMLTokenizerStateCDATASection, )
|
||||
|
||||
typedef NS_ENUM(NSUInteger, HTMLTokenizerState)
|
||||
{
|
||||
#define STATE_ENTRY( name, value ) name value,
|
||||
TOKENIZER_STATES
|
||||
#undef STATE_ENTRY
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// HTMLTokens.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTMLToken.h"
|
||||
#import "HTMLCharacterToken.h"
|
||||
#import "HTMLCommentToken.h"
|
||||
#import "HTMLDOCTYPEToken.h"
|
||||
#import "HTMLParseErrorToken.h"
|
||||
#import "HTMLTagToken.h"
|
||||
#import "HTMLEOFToken.h"
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// NSString+HTMLKit.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 02/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSString (HTMLKit)
|
||||
|
||||
- (BOOL)isEqualToStringIgnoringCase:(NSString *)aString;
|
||||
- (BOOL)isEqualToAny:(NSString *)first, ... NS_REQUIRES_NIL_TERMINATION;
|
||||
- (BOOL)hasPrefixIgnoringCase:(NSString *)aString;
|
||||
- (BOOL)isHTMLWhitespaceString;
|
||||
- (NSUInteger)leadingHTMLWhitespaceLength;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// 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)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;
|
||||
}
|
||||
|
||||
- (BOOL)isHTMLWhitespaceString
|
||||
{
|
||||
return self.leadingHTMLWhitespaceLength == self.length;
|
||||
}
|
||||
|
||||
- (NSUInteger)leadingHTMLWhitespaceLength
|
||||
{
|
||||
size_t idx = 0;
|
||||
NSUInteger length = self.length;
|
||||
while (idx < length) {
|
||||
if (!isHtmlWhitespaceChar([self characterAtIndex:idx])) {
|
||||
return idx;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,2 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// HTML5LibTest.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface HTML5LibTokenizerTest : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString *testName;
|
||||
@property (nonatomic, copy) NSString *title;
|
||||
@property (nonatomic, copy) NSString *input;
|
||||
@property (nonatomic, strong) NSArray *output;
|
||||
@property (nonatomic, strong) NSArray *initialStates;
|
||||
@property (nonatomic, copy) NSString *lastStartTag;
|
||||
@property (nonatomic, assign) BOOL ignoreErrorOrder;
|
||||
|
||||
+ (NSDictionary *)loadHTML5LibTokenizerTests;
|
||||
|
||||
- (instancetype)initWithTestDictionary:(NSDictionary *)dictionary;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,198 @@
|
||||
//
|
||||
// HTML5LibTest.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTML5LibTokenizerTest.h"
|
||||
#import "HTMLTokenizerStates.h"
|
||||
#import "HTMLTokens.h"
|
||||
|
||||
static NSString * const HTML5LibTests = @"html5lib-tests";
|
||||
static NSString * const TOKENIZER = @"tokenizer";
|
||||
|
||||
@implementation HTML5LibTokenizerTest
|
||||
|
||||
+ (NSDictionary *)loadHTML5LibTokenizerTests
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
path = [path stringByAppendingPathComponent:HTML5LibTests];
|
||||
path = [path stringByAppendingPathComponent:TOKENIZER];
|
||||
|
||||
NSMutableDictionary *testsMap = [NSMutableDictionary dictionary];
|
||||
NSArray *testFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
||||
|
||||
for (NSString *testFile in testFiles) {
|
||||
if (![testFile.pathExtension isEqualToString:@"test"]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *jsonPath = [path stringByAppendingPathComponent:testFile];
|
||||
NSArray *tests = [HTML5LibTokenizerTest loadTestsWithFileAtPath:jsonPath];
|
||||
[testsMap setObject:tests forKey:testFile];
|
||||
}
|
||||
|
||||
return testsMap;
|
||||
}
|
||||
|
||||
+ (NSArray *)loadTestsWithFileAtPath:(NSString *)filePath
|
||||
{
|
||||
NSString *testName = filePath.lastPathComponent.stringByDeletingLastPathComponent;
|
||||
|
||||
NSString *json = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
|
||||
NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data
|
||||
options:0
|
||||
error:nil];
|
||||
NSArray *jsonTests = [dictionary objectForKey:@"tests"];
|
||||
NSMutableArray *tests = [NSMutableArray array];
|
||||
|
||||
for (NSDictionary *test in jsonTests) {
|
||||
HTML5LibTokenizerTest *html5libTest = [[HTML5LibTokenizerTest alloc] initWithTestDictionary:test];
|
||||
html5libTest.testName = testName;
|
||||
[tests addObject:html5libTest];
|
||||
}
|
||||
return tests;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTestDictionary:(NSDictionary *)dictionary
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self loadTest:dictionary];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadTest:(NSDictionary *)test
|
||||
{
|
||||
BOOL doubleEscaped = [test[@"doubleEscaped"] boolValue];
|
||||
|
||||
// Test Title
|
||||
self.title = test[@"description"];
|
||||
|
||||
// Test Input
|
||||
self.input = test[@"input"];
|
||||
if (doubleEscaped) {
|
||||
self.input = [self processDoubleEscaped:self.input];
|
||||
}
|
||||
|
||||
// Test Output
|
||||
NSMutableArray *tokens = [NSMutableArray array];
|
||||
NSArray *outputs = test[@"output"];
|
||||
for (NSArray *output in outputs) {
|
||||
HTMLToken *token = [self processOutputToken:output doubleEscaped:doubleEscaped];
|
||||
[tokens addObject:token];
|
||||
}
|
||||
[tokens addObject:[HTMLEOFToken token]];
|
||||
self.output = tokens;
|
||||
|
||||
// Test Initial States
|
||||
NSMutableArray *initialStates = [NSMutableArray array];
|
||||
|
||||
NSArray *states = test[@"initialStates"];
|
||||
for (NSString *name in states) {
|
||||
HTMLTokenizerState state = HTMLTokenizerStateData;
|
||||
if ([name isEqualToString:@"PLAINTEXT state"]) {
|
||||
state = HTMLTokenizerStatePLAINTEXT;
|
||||
} else if ([name isEqualToString:@"RCDATA state"]) {
|
||||
state = HTMLTokenizerStateRCDATA;
|
||||
} else if ([name isEqualToString:@"RAWTEXT state"]) {
|
||||
state = HTMLTokenizerStateRAWTEXT;
|
||||
}
|
||||
[initialStates addObject:@(state)];
|
||||
}
|
||||
if (initialStates.count == 0) {
|
||||
[initialStates addObject:@(HTMLTokenizerStateData)];
|
||||
}
|
||||
|
||||
self.initialStates = initialStates;
|
||||
|
||||
// Test Last Start Tag
|
||||
self.lastStartTag = test[@"lastStartTag"];
|
||||
|
||||
// Ignore Error Order
|
||||
self.ignoreErrorOrder = [test[@"ignoreErrorOrder"] boolValue];
|
||||
}
|
||||
|
||||
- (HTMLToken *)processOutputToken:(id)output doubleEscaped:(BOOL)doubleEscaped
|
||||
{
|
||||
if ([output isKindOfClass:[NSString class]] && [output isEqualToString:@"ParseError"]) {
|
||||
return [HTMLParseErrorToken new];
|
||||
}
|
||||
|
||||
NSString *type = [output firstObject];
|
||||
|
||||
NSString *data = nil;
|
||||
if ([output count] > 1) {
|
||||
data = output[1];
|
||||
if (doubleEscaped) {
|
||||
data = [self processDoubleEscaped:data];
|
||||
}
|
||||
}
|
||||
|
||||
if ([type isEqualToString:@"Character"]) {
|
||||
return [[HTMLCharacterToken alloc] initWithString:data];
|
||||
} else if ([type isEqualToString:@"Comment"]) {
|
||||
return [[HTMLCommentToken alloc] initWithData:data];
|
||||
} else if ([type isEqualToString:@"DOCTYPE"]) {
|
||||
data = [[NSNull null] isEqual:data] ? nil : data;
|
||||
HTMLDOCTYPEToken *token = [[HTMLDOCTYPEToken alloc] initWithName:data];
|
||||
token.publicIdentifier = [[NSNull null] isEqual:output[2]] ? nil : output[2];
|
||||
token.systemIdentifier = [[NSNull null] isEqual:output[3]] ? nil : output[3];
|
||||
token.forceQuirks = ([output[4] boolValue] == NO);
|
||||
return token;
|
||||
} else if ([type isEqualToString:@"EndTag"]) {
|
||||
return [[HTMLEndTagToken alloc] initWithTagName:data];
|
||||
} else if ([type isEqualToString:@"ParseError"]) {
|
||||
return [HTMLParseErrorToken new];
|
||||
} else if ([type isEqualToString:@"StartTag"]) {
|
||||
HTMLStartTagToken *token = [[HTMLStartTagToken alloc] initWithTagName:data];
|
||||
NSDictionary *attributes = output[2];
|
||||
if (attributes && attributes.allKeys.count > 0) {
|
||||
token.attributes = [HTMLOrderedDictionary new];
|
||||
}
|
||||
for (NSString *name in attributes) {
|
||||
NSString *value = [attributes objectForKey:name];
|
||||
[token.attributes setObject:value forKey:name];
|
||||
}
|
||||
token.selfClosing = ([output count] == 4);
|
||||
return token;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)processDoubleEscaped:(NSString *)string
|
||||
{
|
||||
NSError *error = nil;
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\\\u([0-9a-f]{4})"
|
||||
options:NSRegularExpressionCaseInsensitive
|
||||
error:&error];
|
||||
|
||||
NSArray *matches = [regex matchesInString:string options:0 range:NSMakeRange(0, string.length)];
|
||||
|
||||
for(NSTextCheckingResult *match in [matches reverseObjectEnumerator]) {
|
||||
|
||||
NSRange hexRange = [match rangeAtIndex:1];
|
||||
NSString *hexString = [string substringWithRange:hexRange];
|
||||
NSScanner *scanner = [NSScanner scannerWithString:hexString];
|
||||
unsigned int codepint;
|
||||
[scanner scanHexInt:&codepint];
|
||||
NSString *replacement = [NSString stringWithFormat:@"%C", (unichar)codepint];
|
||||
|
||||
NSRange matchRange = [match rangeAtIndex:0];
|
||||
string = [string stringByReplacingCharactersInRange:matchRange withString:replacement];
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return self.title;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// HTML5LibTreeConstructionTest.h
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class HTMLElement;
|
||||
|
||||
@interface HTML5LibTreeConstructionTest : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString *testFile;
|
||||
@property (nonatomic, copy) NSString *data;
|
||||
@property (nonatomic, strong) NSArray *errors;
|
||||
@property (nonatomic, strong) HTMLElement *documentFragment;
|
||||
@property (nonatomic, strong) NSArray *nodes;
|
||||
|
||||
+ (NSDictionary *)loadHTML5LibTreeConstructionTests;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,313 @@
|
||||
//
|
||||
// HTML5LibTreeConstructionTest.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HTML5LibTreeConstructionTest.h"
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "HTMLDocumentType.h"
|
||||
#import "HTMLElement.h"
|
||||
#import "HTMLText.h"
|
||||
#import "HTMLComment.h"
|
||||
#import "HTMLNodeTreeEnumerator.h"
|
||||
|
||||
static NSString * const HTML5LibTests = @"html5lib-tests";
|
||||
static NSString * const TreeConstruction = @"tree-construction";
|
||||
|
||||
@implementation HTML5LibTreeConstructionTest
|
||||
|
||||
+ (NSDictionary *)loadHTML5LibTreeConstructionTests
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
path = [path stringByAppendingPathComponent:HTML5LibTests];
|
||||
path = [path stringByAppendingPathComponent:TreeConstruction];
|
||||
|
||||
NSMutableDictionary *testsMap = [NSMutableDictionary dictionary];
|
||||
NSArray *testFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
||||
|
||||
for (NSString *testFile in testFiles) {
|
||||
if (![testFile.pathExtension isEqualToString:@"dat"]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([testFile hasPrefix:@"ruby"]) {
|
||||
// <ruby> and friends are not yet completely supported
|
||||
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=26189
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString *testFilePath = [path stringByAppendingPathComponent:testFile];
|
||||
NSArray *tests = [HTML5LibTreeConstructionTest loadTestsWithFileAtPath:testFilePath];
|
||||
[testsMap setObject:tests forKey:testFile];
|
||||
}
|
||||
|
||||
return testsMap;
|
||||
}
|
||||
|
||||
+ (NSArray *)loadTestsWithFileAtPath:(NSString *)filePath
|
||||
{
|
||||
NSString *contents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
NSMutableArray *tests = [NSMutableArray array];
|
||||
|
||||
NSScanner *scanner = [NSScanner scannerWithString:contents];
|
||||
NSString * (^ nextTest)() = ^ NSString * () {
|
||||
NSString *str;
|
||||
[scanner scanUpToString:@"\n\n#data" intoString:&str];
|
||||
return str;
|
||||
};
|
||||
|
||||
NSRegularExpressionOptions options = NSRegularExpressionDotMatchesLineSeparators|
|
||||
NSRegularExpressionUseUnixLineSeparators|
|
||||
NSRegularExpressionAnchorsMatchLines;
|
||||
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(^#(.(?!^#))+)"
|
||||
options:options
|
||||
error:nil];
|
||||
|
||||
NSString *rawTest = nil;
|
||||
while ((rawTest = nextTest()) != nil) {
|
||||
|
||||
HTML5LibTreeConstructionTest *test = [HTML5LibTreeConstructionTest new];
|
||||
test.testFile = filePath.lastPathComponent;
|
||||
|
||||
if ([rawTest rangeOfString:@"ruby"].location != NSNotFound) {
|
||||
// <ruby> and friends are not yet completely supported
|
||||
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=26189
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([rawTest rangeOfString:@"#script-off"].location != NSNotFound) {
|
||||
// Ignore tests for "scripting flag disabled" case
|
||||
continue;
|
||||
}
|
||||
|
||||
NSArray *matches = [regex matchesInString:rawTest options:0 range:NSMakeRange(0, rawTest.length)];
|
||||
|
||||
for (NSTextCheckingResult *result in matches) {
|
||||
NSString *match = [rawTest substringWithRange:result.range];
|
||||
|
||||
if ([match hasPrefix:@"#data\n"]) {
|
||||
NSString *data = [match substringFromIndex:@"#data\n".length];
|
||||
if (data.length > 0) {
|
||||
data = [data substringToIndex:data.length];
|
||||
}
|
||||
test.data = data;
|
||||
} else if ([match hasPrefix:@"#errors\n"]) {
|
||||
NSArray *errors = [[match substringFromIndex:@"#errors\n".length] componentsSeparatedByString:@"\n"];
|
||||
test.errors = [errors subarrayWithRange:NSMakeRange(0, errors.count)];
|
||||
} else if ([match hasPrefix:@"#document-fragment\n"]) {
|
||||
NSString *fragment = [match substringFromIndex:@"#document-fragment\n".length];
|
||||
fragment = [fragment substringToIndex:fragment.length];
|
||||
HTMLNamespace namespace = HTMLNamespaceHTML;
|
||||
if ([fragment hasPrefix:@"math "]) {
|
||||
fragment = [fragment substringFromIndex:@"math ".length];
|
||||
namespace = HTMLNamespaceMathML;
|
||||
} else if ([fragment hasPrefix:@"svg "]) {
|
||||
fragment = [fragment substringFromIndex:@"svg ".length];
|
||||
namespace = HTMLNamespaceSVG;
|
||||
}
|
||||
test.documentFragment = [[HTMLElement alloc] initWithTagName:fragment attributes:nil namespace:namespace];
|
||||
} else if ([match hasPrefix:@"#document\n"]) {
|
||||
NSArray *parts = [[match substringFromIndex:@"#document\n".length] componentsSeparatedByString:@"| "];
|
||||
NSArray *nodes = [HTML5LibTreeConstructionTest parseDocument:parts];
|
||||
test.nodes = nodes;
|
||||
}
|
||||
}
|
||||
[tests addObject:test];
|
||||
}
|
||||
|
||||
return tests;
|
||||
}
|
||||
|
||||
+ (NSArray *)parseDocument:(NSArray *)parts
|
||||
{
|
||||
NSMutableArray *nodes = [NSMutableArray array];
|
||||
NSMutableArray *levels = [NSMutableArray array];
|
||||
NSMutableArray *stack = [NSMutableArray array];
|
||||
|
||||
for (NSString *part in parts) {
|
||||
if (part.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSUInteger level = 0;
|
||||
NSString *str = parseLevel(part, &level);
|
||||
|
||||
HTMLElement *currentParent = ^ HTMLElement * (NSUInteger childLevel) {
|
||||
for (NSNumber *level in levels.reverseObjectEnumerator.allObjects) {
|
||||
if (level.unsignedIntegerValue >= childLevel) {
|
||||
[levels removeLastObject];
|
||||
[stack removeLastObject];
|
||||
}
|
||||
}
|
||||
return stack.lastObject;
|
||||
}(level);
|
||||
|
||||
void (^ append)(id ) = ^ (id parsedResult){
|
||||
if (currentParent) {
|
||||
[currentParent appendNode:parsedResult];
|
||||
} else {
|
||||
[nodes addObject:parsedResult];
|
||||
}
|
||||
};
|
||||
|
||||
id parsedResult = nil;
|
||||
|
||||
if ((parsedResult = parseComment(str))) {
|
||||
append(parsedResult);
|
||||
} else if ((parsedResult = parseDocumentType(str))) {
|
||||
append(parsedResult);
|
||||
} else if ((parsedResult = parseTag(str))) {
|
||||
append(parsedResult);
|
||||
[levels addObject:@(level)];
|
||||
[stack addObject:parsedResult];
|
||||
} else if ((parsedResult = parseAttribute(str))) {
|
||||
HTMLElement *element = stack.lastObject;
|
||||
element[parsedResult[0]] = parsedResult[1];
|
||||
} else if ((parsedResult = parseText(str))) {
|
||||
append(parsedResult);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
NS_INLINE NSString * parseLevel(NSString *str, NSUInteger *level)
|
||||
{
|
||||
const char *cstr = str.UTF8String;
|
||||
NSUInteger idx = 0;
|
||||
while ((*cstr) == ' ') { cstr++; idx++; }
|
||||
*level = (idx / 2);
|
||||
return [str substringFromIndex:idx];
|
||||
}
|
||||
|
||||
NS_INLINE HTMLDocumentType * parseDocumentType(NSString *str)
|
||||
{
|
||||
if (![str hasPrefix:@"<!DOCTYPE "]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *rest = [str substringWithRange:NSMakeRange(@"<!DOCTYPE ".length, str.length - @"<!DOCTYPE ".length - 2)];
|
||||
|
||||
NSString *name = nil;
|
||||
NSString *publicIdentifier = nil;
|
||||
NSString *systemIdentifier = nil;
|
||||
|
||||
NSRange nameRange = [rest rangeOfString:@" "];
|
||||
if (nameRange.location != NSNotFound) {
|
||||
name = [rest substringToIndex:nameRange.location];
|
||||
rest = [rest substringFromIndex:nameRange.location + 1];
|
||||
} else {
|
||||
name = (rest.length == 0) ? nil : [rest substringToIndex:rest.length];
|
||||
}
|
||||
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(\".*\")? (\".*\")"
|
||||
options:NSRegularExpressionCaseInsensitive
|
||||
error:nil];
|
||||
|
||||
NSTextCheckingResult *match = [regex firstMatchInString:rest options:0 range:NSMakeRange(0, rest.length)];
|
||||
if (match.numberOfRanges > 0) {
|
||||
NSRange pubRange = [match rangeAtIndex:1];
|
||||
if (pubRange.location != NSNotFound) {
|
||||
publicIdentifier = [rest substringWithRange:NSMakeRange(pubRange.location + 1, pubRange.length - 2)];
|
||||
}
|
||||
|
||||
NSRange sysRange = [match rangeAtIndex:2];
|
||||
if (sysRange.location != NSNotFound) {
|
||||
systemIdentifier = [rest substringWithRange:NSMakeRange(sysRange.location + 1, sysRange.length - 2)];
|
||||
}
|
||||
}
|
||||
|
||||
HTMLDocumentType *doctype = [[HTMLDocumentType alloc] initWithName:name
|
||||
publicIdentifier:publicIdentifier
|
||||
systemIdentifier:systemIdentifier];
|
||||
return doctype;
|
||||
}
|
||||
|
||||
NS_INLINE HTMLElement * parseTag(NSString *str)
|
||||
{
|
||||
NSRegularExpression *tagRegex = [NSRegularExpression regularExpressionWithPattern:@"^(<.*>)$"
|
||||
options:NSRegularExpressionAnchorsMatchLines
|
||||
error:nil];
|
||||
if ([tagRegex numberOfMatchesInString:str options:0 range:NSMakeRange(0, str.length)] != 1) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSTextCheckingResult *match = [tagRegex firstMatchInString:str options:0 range:NSMakeRange(0, str.length)];
|
||||
NSRange range = NSMakeRange(match.range.location + 1, match.range.length - 2);
|
||||
|
||||
NSArray *parts = [[str substringWithRange:range] componentsSeparatedByString:@" "];
|
||||
NSString *tagName = parts.count == 2 ? parts[1] : parts[0];
|
||||
HTMLNamespace namespace = parts.count == 1 ? HTMLNamespaceHTML : ([parts[0] isEqualToString:@"math"] ? HTMLNamespaceMathML : HTMLNamespaceSVG);
|
||||
|
||||
HTMLElement *element = [[HTMLElement alloc] initWithTagName:tagName attributes:nil namespace:namespace];
|
||||
return element;
|
||||
}
|
||||
|
||||
NS_INLINE HTMLText * parseText(NSString *str)
|
||||
{
|
||||
NSRegularExpressionOptions options = NSRegularExpressionDotMatchesLineSeparators|
|
||||
NSRegularExpressionUseUnixLineSeparators|
|
||||
NSRegularExpressionAnchorsMatchLines;
|
||||
|
||||
NSRegularExpression *textRegex = [NSRegularExpression regularExpressionWithPattern:@"^(\".*\")"
|
||||
options:options
|
||||
error:nil];
|
||||
if ([textRegex numberOfMatchesInString:str options:0 range:NSMakeRange(0, str.length)] != 1) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSTextCheckingResult *match = [textRegex firstMatchInString:str options:0 range:NSMakeRange(0, str.length)];
|
||||
NSRange range = NSMakeRange(match.range.location + 1, match.range.length - 2);
|
||||
HTMLText *text = [[HTMLText alloc] initWithData:[str substringWithRange:range]];
|
||||
return text;
|
||||
}
|
||||
|
||||
NS_INLINE HTMLComment * parseComment(NSString *str)
|
||||
{
|
||||
NSRegularExpressionOptions options = NSRegularExpressionDotMatchesLineSeparators|
|
||||
NSRegularExpressionUseUnixLineSeparators|
|
||||
NSRegularExpressionAnchorsMatchLines;
|
||||
|
||||
NSRegularExpression *commentRegex = [NSRegularExpression regularExpressionWithPattern:@"^(<!--.*-->)$"
|
||||
options:options
|
||||
error:nil];
|
||||
if ([commentRegex numberOfMatchesInString:str options:0 range:NSMakeRange(0, str.length)] != 1) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSTextCheckingResult *match = [commentRegex firstMatchInString:str options:0 range:NSMakeRange(0, str.length)];
|
||||
NSString *data = [str substringWithRange:match.range];
|
||||
data = [data substringWithRange:NSMakeRange(@"<!-- ".length, data.length - @"<!-- ".length - @" -->".length)];
|
||||
HTMLComment *comment = [[HTMLComment alloc] initWithData:data];
|
||||
return comment;
|
||||
}
|
||||
|
||||
NS_INLINE NSArray * parseAttribute(NSString *str)
|
||||
{
|
||||
NSRegularExpressionOptions options = NSRegularExpressionDotMatchesLineSeparators | NSRegularExpressionUseUnixLineSeparators;
|
||||
|
||||
NSRegularExpression *attributeRegex = [NSRegularExpression regularExpressionWithPattern:@"^[^\"](.*=\".*\")$"
|
||||
options:options
|
||||
error:nil];
|
||||
if ([attributeRegex numberOfMatchesInString:str options:0 range:NSMakeRange(0, str.length)] != 1) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSTextCheckingResult *match = [attributeRegex firstMatchInString:str options:0 range:NSMakeRange(0, str.length)];
|
||||
str = [str substringWithRange:match.range];
|
||||
NSRange range = [str rangeOfString:@"=" options:0];
|
||||
|
||||
NSString *key = [str substringToIndex:range.location];
|
||||
NSString *value = [str substringFromIndex:range.location + 2];
|
||||
value = [value substringToIndex:value.length - 1];
|
||||
|
||||
return @[key, value];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// HTMLKitNodeTreeEnumratorTests.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 28/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "HTMLNodes.h"
|
||||
|
||||
@interface HTMLKitNodeTreeEnumratorTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation HTMLKitNodeTreeEnumratorTests
|
||||
|
||||
#pragma mark - Elements
|
||||
|
||||
- (HTMLElement *)div
|
||||
{
|
||||
return [[HTMLElement alloc] initWithTagName:@"div"];
|
||||
}
|
||||
|
||||
- (HTMLElement *)simpleTree
|
||||
{
|
||||
/*
|
||||
| div
|
||||
| a
|
||||
| b
|
||||
| c
|
||||
*/
|
||||
HTMLElement *div = self.div;
|
||||
[div appendNode:[[HTMLElement alloc] initWithTagName:@"a"]];
|
||||
[div appendNode:[[HTMLElement alloc] initWithTagName:@"b"]];
|
||||
[div appendNode:[[HTMLElement alloc] initWithTagName:@"c"]];
|
||||
return div;
|
||||
}
|
||||
|
||||
- (HTMLElement *)nestedSimpleTree
|
||||
{
|
||||
/*
|
||||
| div
|
||||
| div
|
||||
| a
|
||||
| b
|
||||
| c
|
||||
| div
|
||||
| a
|
||||
| b
|
||||
| c
|
||||
*/
|
||||
HTMLElement *div = self.div;
|
||||
[div appendNode:self.simpleTree];
|
||||
[div appendNode:self.simpleTree];
|
||||
return div;
|
||||
}
|
||||
|
||||
- (HTMLElement *)complexTree
|
||||
{
|
||||
/*
|
||||
| div
|
||||
| div
|
||||
| div
|
||||
| a
|
||||
| b
|
||||
| c
|
||||
| e
|
||||
| f
|
||||
| div
|
||||
| a
|
||||
| b
|
||||
| c
|
||||
*/
|
||||
HTMLElement *root = self.div;
|
||||
|
||||
HTMLElement *div = self.div;
|
||||
[div appendNode:self.simpleTree];
|
||||
[root appendNode:div];
|
||||
|
||||
HTMLElement *e = [[HTMLElement alloc] initWithTagName:@"e"];
|
||||
[e appendNode:[[HTMLElement alloc] initWithTagName:@"f"]];
|
||||
[root appendNode:e];
|
||||
[root appendNode:self.simpleTree];
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
#pragma mark - Tests
|
||||
|
||||
- (void)testSingle
|
||||
{
|
||||
HTMLElement *div = self.div;
|
||||
NSArray *result = div.treeEnumerator.allObjects;
|
||||
NSArray *expected = @[@"div"];
|
||||
XCTAssertEqualObjects([result valueForKey:@"name"], expected);
|
||||
}
|
||||
|
||||
- (void)testSimpleTree
|
||||
{
|
||||
HTMLElement *tree = self.simpleTree;
|
||||
NSArray *result = tree.treeEnumerator.allObjects;
|
||||
NSArray *expected = @[@"div", @"a", @"b", @"c"];
|
||||
XCTAssertEqualObjects([result valueForKey:@"name"], expected);
|
||||
}
|
||||
|
||||
- (void)testSimpleTreeReversed
|
||||
{
|
||||
HTMLElement *tree = self.simpleTree;
|
||||
NSArray *result = tree.reverseTreeEnumerator.allObjects;
|
||||
NSArray *expected = @[@"div", @"c", @"b", @"a"];
|
||||
XCTAssertEqualObjects([result valueForKey:@"name"], expected);
|
||||
}
|
||||
|
||||
- (void)testNestedSimpleTree
|
||||
{
|
||||
HTMLElement *tree = self.nestedSimpleTree;
|
||||
NSArray *result = tree.treeEnumerator.allObjects;
|
||||
NSArray *expected = @[@"div", @"div", @"a", @"b", @"c", @"div", @"a", @"b", @"c"];
|
||||
XCTAssertEqualObjects([result valueForKey:@"name"], expected);
|
||||
}
|
||||
|
||||
- (void)testNestedSimpleTreeReversed
|
||||
{
|
||||
HTMLElement *tree = self.nestedSimpleTree;
|
||||
NSArray *result = tree.reverseTreeEnumerator.allObjects;
|
||||
NSArray *expected = @[@"div", @"div", @"c", @"b", @"a", @"div", @"c", @"b", @"a"];
|
||||
XCTAssertEqualObjects([result valueForKey:@"name"], expected);
|
||||
}
|
||||
|
||||
- (void)testComplexSimpleTree
|
||||
{
|
||||
HTMLElement *tree = self.complexTree;
|
||||
NSArray *result = tree.treeEnumerator.allObjects;
|
||||
NSArray *expected = @[@"div", @"div",@"div", @"a", @"b", @"c", @"e", @"f", @"div", @"a", @"b", @"c"];
|
||||
XCTAssertEqualObjects([result valueForKey:@"name"], expected);
|
||||
}
|
||||
|
||||
- (void)testComplexSimpleTreeReversed
|
||||
{
|
||||
HTMLElement *tree = self.complexTree;
|
||||
NSArray *result = tree.reverseTreeEnumerator.allObjects;
|
||||
NSArray *expected = @[@"div", @"div", @"c", @"b", @"a", @"e", @"f", @"div", @"div", @"c", @"b", @"a"];
|
||||
XCTAssertEqualObjects([result valueForKey:@"name"], expected);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,184 @@
|
||||
//
|
||||
// HTMLKitOrderedDictionaryTests.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 16/04/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "HTMLOrderedDictionary.h"
|
||||
|
||||
@interface HTMLKitOrderedDictionaryTests : XCTestCase
|
||||
{
|
||||
HTMLOrderedDictionary *_dictionary;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation HTMLKitOrderedDictionaryTests
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
_dictionary = [HTMLOrderedDictionary new];
|
||||
}
|
||||
|
||||
- (void)testSetObjectForKey
|
||||
{
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, @[]);
|
||||
|
||||
[_dictionary setObject:@"1" forKey:@"A"];
|
||||
NSArray *expected = @[@"A"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
[_dictionary setObject:@"2" forKey:@"B"];
|
||||
expected = @[@"A", @"B"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
[_dictionary setObject:@"3" forKey:@"C"];
|
||||
expected = @[@"A", @"B", @"C"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
XCTAssertThrows([_dictionary setObject:nil forKey:@"Key"]);
|
||||
XCTAssertThrows([_dictionary setObject:@"Object" forKey:nil]);
|
||||
}
|
||||
|
||||
- (void)testIndexOfKey
|
||||
{
|
||||
[_dictionary setObject:@"1" forKey:@"A"];
|
||||
[_dictionary setObject:@"2" forKey:@"B"];
|
||||
[_dictionary setObject:@"3" forKey:@"C"];
|
||||
XCTAssertEqual([_dictionary indexOfKey:@"A"], 0);
|
||||
XCTAssertEqual([_dictionary indexOfKey:@"B"], 1);
|
||||
XCTAssertEqual([_dictionary indexOfKey:@"C"], 2);
|
||||
XCTAssertEqual([_dictionary indexOfKey:nil], NSNotFound);
|
||||
}
|
||||
|
||||
- (void)testObjectAtIndex
|
||||
{
|
||||
[_dictionary setObject:@"1" forKey:@"A"];
|
||||
[_dictionary setObject:@"2" forKey:@"B"];
|
||||
XCTAssertEqualObjects([_dictionary objectAtIndex:0], @"1");
|
||||
XCTAssertEqualObjects([_dictionary objectAtIndex:1], @"2");
|
||||
|
||||
[_dictionary setObject:@"3" forKey:@"C" atIndex:1];
|
||||
XCTAssertEqualObjects([_dictionary objectAtIndex:1], @"3");
|
||||
|
||||
XCTAssertThrows([_dictionary setObject:nil forKey:@"Key" atIndex:0]);
|
||||
XCTAssertThrows([_dictionary setObject:@"Object" forKey:nil atIndex:0]);
|
||||
XCTAssertThrows([_dictionary setObject:@"Object" forKey:@"Key" atIndex:100]);
|
||||
}
|
||||
|
||||
- (void)testIndexedSubscript
|
||||
{
|
||||
[_dictionary setObject:@"1" forKey:@"A"];
|
||||
[_dictionary setObject:@"2" forKey:@"B"];
|
||||
[_dictionary setObject:@"3" forKey:@"C"];
|
||||
XCTAssertEqualObjects(_dictionary[0], @"1");
|
||||
XCTAssertEqualObjects(_dictionary[1], @"2");
|
||||
XCTAssertEqualObjects(_dictionary[2], @"3");
|
||||
|
||||
_dictionary[1] = @"4";
|
||||
_dictionary[2] = @"5";
|
||||
XCTAssertEqualObjects(_dictionary[1], @"4");
|
||||
XCTAssertEqualObjects(_dictionary[2], @"5");
|
||||
|
||||
XCTAssertThrows(_dictionary[100]);
|
||||
}
|
||||
|
||||
- (void)testKeyedSubscript
|
||||
{
|
||||
_dictionary[@"A"] = @"1";
|
||||
_dictionary[@"B"] = @"2";
|
||||
_dictionary[@"C"] = @"3";
|
||||
NSArray *expected = @[@"A", @"B", @"C"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
XCTAssertEqualObjects(_dictionary[@"Key"], nil);
|
||||
}
|
||||
|
||||
- (void)testSetObjectForKeyAtIndex
|
||||
{
|
||||
_dictionary[@"A"] = @"1";
|
||||
_dictionary[@"B"] = @"2";
|
||||
NSArray *expected = @[@"A", @"B"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
[_dictionary setObject:@"3" forKey:@"C" atIndex:0];
|
||||
expected = @[@"C", @"A", @"B"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
[_dictionary setObject:@"4" forKey:@"C" atIndex:0];
|
||||
expected = @[@"C", @"A", @"B"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
XCTAssertEqualObjects(_dictionary[0], @"4");
|
||||
XCTAssertEqualObjects(_dictionary[@"C"], @"4");
|
||||
|
||||
[_dictionary setObject:@"5" forKey:@"A" atIndex:2];
|
||||
expected = @[@"C", @"B", @"A"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
XCTAssertEqualObjects(_dictionary[2], @"5");
|
||||
XCTAssertEqualObjects(_dictionary[@"A"], @"5");
|
||||
|
||||
XCTAssertThrows([_dictionary setObject:nil forKey:@"Key" atIndex:0]);
|
||||
XCTAssertThrows([_dictionary setObject:@"Object" forKey:nil atIndex:0]);
|
||||
XCTAssertThrows([_dictionary setObject:@"Object" forKey:@"Key" atIndex:100]);
|
||||
}
|
||||
|
||||
- (void)testRemoveObjectAtIndex
|
||||
{
|
||||
_dictionary[@"A"] = @"1";
|
||||
_dictionary[@"B"] = @"2";
|
||||
_dictionary[@"C"] = @"3";
|
||||
[_dictionary removeObjectAtIndex:1];
|
||||
NSArray *expected = @[@"A", @"C"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
[_dictionary removeObjectAtIndex:1];
|
||||
expected = @[@"A"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
XCTAssertThrows([_dictionary removeObjectAtIndex:100]);
|
||||
}
|
||||
|
||||
- (void)testReplaceKeyValuePairAtIndex
|
||||
{
|
||||
_dictionary[@"A"] = @"1";
|
||||
_dictionary[@"B"] = @"2";
|
||||
_dictionary[@"C"] = @"3";
|
||||
[_dictionary replaceKeyValueAtIndex:1 withObject:@"4" andKey:@"D"];
|
||||
NSArray *expected = @[@"A", @"D", @"C"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
XCTAssertEqualObjects(_dictionary[1], @"4");
|
||||
XCTAssertEqualObjects(_dictionary[@"D"], @"4");
|
||||
|
||||
[_dictionary replaceKeyValueAtIndex:0 withObject:@"5" andKey:@"E"];
|
||||
expected = @[@"E", @"D", @"C"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
XCTAssertEqualObjects(_dictionary[0], @"5");
|
||||
XCTAssertEqualObjects(_dictionary[@"E"], @"5");
|
||||
|
||||
XCTAssertThrows([_dictionary replaceKeyValueAtIndex:1 withObject:nil andKey:@"Key"]);
|
||||
XCTAssertThrows([_dictionary replaceKeyValueAtIndex:1 withObject:@"Object" andKey:nil]);
|
||||
XCTAssertThrows([_dictionary replaceKeyValueAtIndex:100 withObject:@"Object" andKey:@"Key"]);
|
||||
}
|
||||
|
||||
- (void)testReplaceKeyAtIndex
|
||||
{
|
||||
_dictionary[@"A"] = @"1";
|
||||
_dictionary[@"B"] = @"2";
|
||||
_dictionary[@"C"] = @"3";
|
||||
[_dictionary replaceKey:@"A" withKey:@"D"];
|
||||
NSArray *expected = @[@"D", @"B", @"C"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
[_dictionary replaceKey:@"B" withKey:@"E"];
|
||||
expected = @[@"D", @"E", @"C"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
|
||||
[_dictionary replaceKey:@"Key" withKey:@"F"];
|
||||
expected = @[@"D", @"E", @"C"];
|
||||
XCTAssertEqualObjects(_dictionary.keyEnumerator.allObjects, expected);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// HTMLKitParserPerformance.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 11/04/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "HTMLParser.h"
|
||||
|
||||
@interface HTMLKitParserPerformance : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation HTMLKitParserPerformance
|
||||
|
||||
- (void)testParserPerformance
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
path = [path stringByAppendingPathComponent:@"HTML Standard.html"];
|
||||
|
||||
NSString *string = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
[self measureBlock:^{
|
||||
HTMLParser *parser = [[HTMLParser alloc] initWithString:string];
|
||||
[parser parseDocument];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// HTMLKitStringCategoryTests.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 16/04/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "NSString+HTMLKit.h"
|
||||
|
||||
@interface HTMLKitStringCategoryTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation HTMLKitStringCategoryTests
|
||||
|
||||
- (void)testIsEqualToStringIgnoringCase
|
||||
{
|
||||
NSString *string = @"HTML Kit \0 String \u02605 Category";
|
||||
XCTAssertTrue([string isEqualToStringIgnoringCase:@"hTML Kit \0 String \u02605 Category"]);
|
||||
XCTAssertTrue([string isEqualToStringIgnoringCase:@"html KIT \0 String \u02605 Category"]);
|
||||
XCTAssertTrue([string isEqualToStringIgnoringCase:@"htML KiT \0 String \u02605 CategoRY"]);
|
||||
}
|
||||
|
||||
- (void)testIsEqualToAny
|
||||
{
|
||||
NSString *string = @"h\u02605tm\0l";
|
||||
BOOL equal = [string isEqualToAny:@"h\u02605tm\0l", @"kit", @"tests", nil];
|
||||
XCTAssertTrue(equal);
|
||||
|
||||
equal = [string isEqualToAny:@"kit", @"h\u02605tm\0l", @"tests", nil];
|
||||
XCTAssertTrue(equal);
|
||||
|
||||
equal = [string isEqualToAny:@"kit", @"tests", @"h\u02605tm\0l", nil];
|
||||
XCTAssertTrue(equal);
|
||||
|
||||
equal = [string isEqualToAny:@"H\u02605TM\0L", @"kit", @"tests", nil];
|
||||
XCTAssertFalse(equal);
|
||||
}
|
||||
|
||||
- (void)testHasPrefixIgnoringCase
|
||||
{
|
||||
NSString *string = @"HTM\0L \u02605 Kit String Category";
|
||||
XCTAssertTrue([string hasPrefixIgnoringCase:@"htm\0l"]);
|
||||
XCTAssertTrue([string hasPrefixIgnoringCase:@"htm\0l \u02605 kit"]);
|
||||
XCTAssertTrue([string hasPrefixIgnoringCase:@"htM\0L \u02605 Kit"]);
|
||||
XCTAssertFalse([string hasPrefixIgnoringCase:@"\0htm\0l"]);
|
||||
}
|
||||
|
||||
- (void)testIsHTMLWhitespaceString
|
||||
{
|
||||
XCTAssertTrue([@" " isHTMLWhitespaceString]);
|
||||
XCTAssertTrue([@"\t" isHTMLWhitespaceString]);
|
||||
XCTAssertTrue([@"\n" isHTMLWhitespaceString]);
|
||||
XCTAssertTrue([@"\f" isHTMLWhitespaceString]);
|
||||
XCTAssertTrue([@"\r" isHTMLWhitespaceString]);
|
||||
XCTAssertTrue([@" \t\n\f\r" isHTMLWhitespaceString]);
|
||||
XCTAssertTrue([@"\t\n\f\r " isHTMLWhitespaceString]);
|
||||
XCTAssertTrue([@" \t \n \f \r" isHTMLWhitespaceString]);
|
||||
XCTAssertFalse([@"html kit" isHTMLWhitespaceString]);
|
||||
}
|
||||
|
||||
- (void)testLeadingWhitespaceLength
|
||||
{
|
||||
XCTAssertEqual([@"" leadingHTMLWhitespaceLength], 0);
|
||||
XCTAssertEqual([@"\0" leadingHTMLWhitespaceLength], 0);
|
||||
|
||||
XCTAssertEqual([@" " leadingHTMLWhitespaceLength], 1);
|
||||
XCTAssertEqual([@"\0 " leadingHTMLWhitespaceLength], 0);
|
||||
|
||||
XCTAssertEqual([@" " leadingHTMLWhitespaceLength], 2);
|
||||
XCTAssertEqual([@" \0 " leadingHTMLWhitespaceLength], 1);
|
||||
|
||||
XCTAssertEqual([@"\t\r\n\f" leadingHTMLWhitespaceLength], 4);
|
||||
XCTAssertEqual([@"\t\r\n\0\f" leadingHTMLWhitespaceLength], 3);
|
||||
|
||||
XCTAssertEqual([@"\t\r\n\f " leadingHTMLWhitespaceLength], 5);
|
||||
XCTAssertEqual([@"\t\r\n\f\0 " leadingHTMLWhitespaceLength], 4);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.braincookie.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// HTMLKitTokenizerPerformance.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 23/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "HTMLTokenizer.h"
|
||||
#import "HTMLTokenizerStates.h"
|
||||
#import "HTMLTokens.h"
|
||||
|
||||
@interface HTMLKitTokenizerPerformance : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation HTMLKitTokenizerPerformance
|
||||
|
||||
- (void)testTokenizerPerformance
|
||||
{
|
||||
NSString *path = [[NSBundle bundleForClass:self.class] resourcePath];
|
||||
path = [path stringByAppendingPathComponent:@"HTML Standard.html"];
|
||||
|
||||
NSString *string = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
[self measureBlock:^{
|
||||
HTMLTokenizer *tokenizer = [[HTMLTokenizer alloc] initWithString:string];
|
||||
[tokenizer allObjects];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// HTMLTokenizerTests.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/10/14.
|
||||
// Copyright (c) 2014 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
#import "HTML5LibTokenizerTest.h"
|
||||
|
||||
#import "HTMLTokenizer.h"
|
||||
#import "HTMLTokenizerStates.h"
|
||||
#import "HTMLTokens.h"
|
||||
|
||||
#import "HTMLParser.h"
|
||||
#import "HTMLDocument.h"
|
||||
|
||||
#pragma mark - Extensions
|
||||
|
||||
@implementation HTMLParseErrorToken (Testing)
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
return [object isKindOfClass:[HTMLParseErrorToken class]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - HTML5Lib Test Suite
|
||||
|
||||
@interface HTMLKitTokenizerTests : XCTestCase
|
||||
@property (nonatomic, strong) NSString *testName;
|
||||
@property (nonatomic, strong) NSArray *testsList;
|
||||
@end
|
||||
|
||||
@implementation HTMLKitTokenizerTests
|
||||
|
||||
+ (XCTestSuite *)defaultTestSuite
|
||||
{
|
||||
XCTestSuite *suite = [[XCTestSuite alloc] initWithName:NSStringFromClass(self)];
|
||||
|
||||
NSDictionary *testsMap = [HTML5LibTokenizerTest loadHTML5LibTokenizerTests];
|
||||
[testsMap enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
[self addTestCaseForTestFile:key withTests:obj toTestSuite:suite];
|
||||
}];
|
||||
|
||||
return suite;
|
||||
}
|
||||
|
||||
+ (void)addTestCaseForTestFile:(NSString *)testFile withTests:(NSArray *)tests toTestSuite:(XCTestSuite *)suite
|
||||
{
|
||||
NSArray *allInvocations = [self testInvocations];
|
||||
for (NSInvocation *invocation in allInvocations) {
|
||||
XCTestCase *testCase = [[self alloc] initWithInvocation:invocation
|
||||
testName:testFile
|
||||
tests:tests];
|
||||
[suite addTest:testCase];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Instance
|
||||
|
||||
- (instancetype)initWithInvocation:(NSInvocation *)invocation
|
||||
testName:(NSString *)testName
|
||||
tests:(NSArray *)tests
|
||||
{
|
||||
self = [super initWithInvocation:invocation];
|
||||
if (self) {
|
||||
_testName = testName;
|
||||
_testsList = tests;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)name
|
||||
{
|
||||
NSInvocation *invocation = [self invocation];
|
||||
NSString *title = self.testName.stringByDeletingPathExtension;
|
||||
return [NSString stringWithFormat:@"-[%@ %@_%@]", self.class, NSStringFromSelector(invocation.selector), title];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return self.name;
|
||||
}
|
||||
|
||||
#pragma mark - Tests
|
||||
|
||||
- (void)testTokenizer
|
||||
{
|
||||
for (HTML5LibTokenizerTest *test in self.testsList) {
|
||||
|
||||
for (NSNumber *state in test.initialStates) {
|
||||
HTMLTokenizer *tokenizer = [[HTMLTokenizer alloc] initWithString:test.input];
|
||||
[tokenizer setValue:test.lastStartTag forKey:@"_lastStartTagName"];
|
||||
|
||||
tokenizer.state = [state integerValue];
|
||||
|
||||
NSArray *expectedTokens = test.output;
|
||||
NSArray *tokens = tokenizer.allObjects;
|
||||
|
||||
NSString *message = [NSString stringWithFormat:@"HTML5Lib test in file: \'%@\' Title: '%@'\nInput: '%@'\nExpected:\n%@\nActual:\n%@\n",
|
||||
self.testName,
|
||||
test.title,
|
||||
test.input,
|
||||
expectedTokens,
|
||||
tokens];
|
||||
XCTAssertEqualObjects(tokens, expectedTokens, @"%@", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// HTMLKitParserTests.m
|
||||
// HTMLKit
|
||||
//
|
||||
// Created by Iska on 25/03/15.
|
||||
// Copyright (c) 2015 BrainCookie. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "HTML5LibTreeConstructionTest.h"
|
||||
#import "HTMLNodes.h"
|
||||
#import "HTMLParser.h"
|
||||
|
||||
|
||||
#define AssertEqualNodes(a, b, c) \
|
||||
do { \
|
||||
[self assertNode:a isEqualToNode:b message:c]; \
|
||||
} while(0)
|
||||
|
||||
#define AssertEqualChildNodes(a, b, c) \
|
||||
do { \
|
||||
[self assertElementChildNodes:a areEqualToElementChildNode:b message:c]; \
|
||||
} while(0)
|
||||
|
||||
#pragma mark - HTML5Lib Test Suite
|
||||
|
||||
@interface HTMLKitTreeConstructionTests : XCTestCase
|
||||
@property (nonatomic, strong) NSString *testName;
|
||||
@property (nonatomic, strong) NSArray *testsList;
|
||||
@end
|
||||
|
||||
@implementation HTMLKitTreeConstructionTests
|
||||
|
||||
+ (XCTestSuite *)defaultTestSuite
|
||||
{
|
||||
XCTestSuite *suite = [[XCTestSuite alloc] initWithName:NSStringFromClass(self)];
|
||||
|
||||
NSDictionary *testsMap = [HTML5LibTreeConstructionTest loadHTML5LibTreeConstructionTests];
|
||||
[testsMap enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
[self addTestCaseForTestFile:key withTests:obj toTestSuite:suite];
|
||||
}];
|
||||
return suite;
|
||||
}
|
||||
|
||||
+ (void)addTestCaseForTestFile:(NSString *)testFile withTests:(NSArray *)tests toTestSuite:(XCTestSuite *)suite
|
||||
{
|
||||
NSArray *allInvocations = [self testInvocations];
|
||||
for (NSInvocation *invocation in allInvocations) {
|
||||
XCTestCase *testCase = [[self alloc] initWithInvocation:invocation
|
||||
testName:testFile
|
||||
tests:tests];
|
||||
[suite addTest:testCase];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Instance
|
||||
|
||||
- (instancetype)initWithInvocation:(NSInvocation *)invocation
|
||||
testName:(NSString *)testName
|
||||
tests:(NSArray *)tests
|
||||
{
|
||||
self = [super initWithInvocation:invocation];
|
||||
if (self) {
|
||||
_testName = testName;
|
||||
_testsList = tests;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)name
|
||||
{
|
||||
NSInvocation *invocation = [self invocation];
|
||||
NSString *title = self.testName.stringByDeletingPathExtension;
|
||||
return [NSString stringWithFormat:@"-[%@ %@_%@]", self.class, NSStringFromSelector(invocation.selector), title];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return self.name;
|
||||
}
|
||||
|
||||
#pragma mark - Tests
|
||||
|
||||
- (void)testParser
|
||||
{
|
||||
for (HTML5LibTreeConstructionTest *test in self.testsList) {
|
||||
HTMLElement *contextElement = test.documentFragment;
|
||||
|
||||
HTMLParser *parser = [[HTMLParser alloc] initWithString:test.data];
|
||||
|
||||
NSArray *actual = nil;
|
||||
if (contextElement == nil) {
|
||||
actual = [parser parseDocument].childNodes.array;
|
||||
} else {
|
||||
actual = [parser parseFragmentWithContextElement:contextElement];
|
||||
}
|
||||
|
||||
NSString *expectedNodes = [[test.nodes valueForKey:@"debugDescription"] componentsJoinedByString:@"\n"];
|
||||
NSString *actualNodes = [[parser.document.childNodes.array valueForKey:@"debugDescription"] componentsJoinedByString:@"\n"];
|
||||
|
||||
NSString *message = [NSString stringWithFormat:@"HTML5Lib test in file: \'%@\'\nInput:\n%@\nExpected:\n%@\nActual:\n%@\n",
|
||||
test.testFile,
|
||||
test.data,
|
||||
expectedNodes,
|
||||
actualNodes];
|
||||
|
||||
XCTAssertEqual(actual.count, test.nodes.count, @"Nodes mismatch:\n%@", message);
|
||||
if (actual.count != test.nodes.count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[actual enumerateObjectsUsingBlock:^(HTMLNode *actual, NSUInteger idx, BOOL *stop) {
|
||||
HTMLNode *expected = [test.nodes objectAtIndex:idx];
|
||||
AssertEqualNodes(actual, expected, message);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)assertNode:(HTMLNode *)actual isEqualToNode:(HTMLNode *)expected message:(NSString *)message
|
||||
{
|
||||
XCTAssertEqualObjects(actual.name, expected.name, @"Node name mismatch [%@ should be %@]:\n%@",
|
||||
actual.name, expected.name, message);
|
||||
XCTAssert(actual.type == expected.type, @"Node type mismatch [%hd should be %hd]:\n%@",
|
||||
actual.type, expected.type, message);
|
||||
|
||||
if (actual.type != expected.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (actual.type) {
|
||||
case HTMLNodeDocumentType:
|
||||
XCTAssertEqualObjects([(HTMLDocumentType *)actual publicIdentifier], [(HTMLDocumentType *)expected publicIdentifier], @"%@", message);
|
||||
XCTAssertEqualObjects([(HTMLDocumentType *)actual systemIdentifier], [(HTMLDocumentType *)expected systemIdentifier], @"%@", message);
|
||||
break;
|
||||
case HTMLNodeElement:
|
||||
XCTAssertEqualObjects([(HTMLElement *)actual attributes], [(HTMLElement *)expected attributes], @"%@", message);
|
||||
AssertEqualChildNodes((HTMLElement *)actual, (HTMLElement *)expected, message);
|
||||
break;
|
||||
case HTMLNodeComment:
|
||||
XCTAssertEqualObjects([(HTMLComment *)actual data], [(HTMLComment *)expected data], @"%@", message);
|
||||
break;
|
||||
case HTMLNodeText:
|
||||
XCTAssertEqualObjects([(HTMLText *)actual data], [(HTMLText *)expected data], @"%@", message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)assertElementChildNodes:(HTMLElement *)elemen1 areEqualToElementChildNode:(HTMLElement *)element2 message:(NSString *)message
|
||||
{
|
||||
XCTAssertEqual(elemen1.childNodes.count, element2.childNodes.count,
|
||||
@"Child nodes count mismatch [element %@ has %lu but should have %lu child nodes]\n%@",
|
||||
elemen1,
|
||||
(unsigned long)elemen1.childNodes.count,
|
||||
(unsigned long)element2.childNodes.count,
|
||||
message);
|
||||
|
||||
if (elemen1.childNodes.count != element2.childNodes.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
[elemen1.childNodes.array enumerateObjectsUsingBlock:^(HTMLNode *actual, NSUInteger idx, BOOL *stop) {
|
||||
HTMLNode *expected = [element2.childNodes.array objectAtIndex:idx];
|
||||
AssertEqualNodes(actual, expected, message);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,2 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
Submodule
+1
Submodule HTMLKitTests/html5lib-tests added at e633ddfeb0
@@ -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.
|
||||
@@ -0,0 +1,11 @@
|
||||
# HTMLKit
|
||||
|
||||
An Objective-C kit for your everyday HTML needs.
|
||||
|
||||
# Quick Overview
|
||||
|
||||
HTMLKit is a [WHATWG](https://html.spec.whatwg.org/multipage/) specification-compliant library for parsing and serializing HTML documents and document fragments for OSX and iOS. HTMLKit parses real-world HTML the same way modern web browsers would.
|
||||
|
||||
# License
|
||||
|
||||
HTMLKit is available under the MIT license. See the LICENSE file for more info.
|
||||
Reference in New Issue
Block a user