Merge branch 'release/0.1.0'

This commit is contained in:
iska
2015-04-20 21:51:38 +02:00
88 changed files with 99612 additions and 0 deletions
+3
View File
@@ -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>
@@ -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>
@@ -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>
+27
View File
@@ -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
+101
View File
@@ -0,0 +1,101 @@
//
// HTMLCharacterToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLCharacterToken.h"
#import "NSString+HTMLKit.h"
@interface HTMLCharacterToken ()
{
NSMutableString *_characters;
}
@end
@implementation HTMLCharacterToken
- (instancetype)initWithString:(NSString *)string
{
self = [super init];
if (self) {
_characters = [string mutableCopy];
}
return self;
}
- (void)appendString:(NSString *)string
{
if (_characters == nil) {
_characters = [NSMutableString new];
}
[_characters appendString:string];
}
- (BOOL)isWhitespaceToken
{
return [_characters 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
+17
View File
@@ -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
+55
View File
@@ -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
+20
View File
@@ -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
+59
View File
@@ -0,0 +1,59 @@
//
// HTMLCommentToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLCommentToken.h"
@interface HTMLCommentToken ()
{
NSMutableString *_data;
}
@end
@implementation HTMLCommentToken
@synthesize data = _data;
- (instancetype)initWithData:(NSString *)data
{
self = [super init];
if (self) {
self.type = HTMLTokenTypeComment;
_data = [data mutableCopy];
}
return self;
}
- (void)appendStringToData:(NSString *)string
{
if (_data == nil) {
_data = [NSMutableString new];
}
[_data appendString:string];
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLCommentToken *token = (HTMLCommentToken *)other;
return bothNilOrEqual(self.data, token.data);
}
return NO;
}
- (NSUInteger)hash
{
return self.data.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p Data='%@'>", self.class, self, _data];
}
@end
+25
View File
@@ -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
+84
View File
@@ -0,0 +1,84 @@
//
// HTMLDOCTYPEToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLDOCTYPEToken.h"
@interface HTMLDOCTYPEToken ()
{
NSMutableString *_name;
}
@end
@implementation HTMLDOCTYPEToken
@synthesize name = _name;
- (instancetype)init
{
return [self initWithName:nil];
}
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
self.type = HTMLTokenTypeDoctype;
_name = [name mutableCopy];
}
return self;
}
- (void)appendStringToName:(NSString *)string
{
if (_name == nil) {
_name = [NSMutableString new];
}
[_name appendString:string];
}
- (void)appendStringToPublicIdentifier:(NSString *)string
{
if (_publicIdentifier == nil) {
_publicIdentifier = [NSMutableString new];
}
[_publicIdentifier appendString:string];
}
- (void)appendStringToSystemIdentifier:(NSString *)string
{
if (_systemIdentifier == nil) {
_systemIdentifier = [NSMutableString new];
}
[_systemIdentifier appendString:string];
}
#pragma mark - NSObject
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLDOCTYPEToken *token = (HTMLDOCTYPEToken *)other;
return (bothNilOrEqual(self.name, token.name) &&
bothNilOrEqual(self.publicIdentifier, token.publicIdentifier) &&
bothNilOrEqual(self.systemIdentifier, token.systemIdentifier) &&
self.forceQuirks == token.forceQuirks);
}
return NO;
}
- (NSUInteger)hash
{
return self.name.hash + self.publicIdentifier.hash + self.systemIdentifier.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p Name='%@' Public='%@' System='%@' ForceQuirks='%@'>", self.class, self, _name, _publicIdentifier, _systemIdentifier, @(_forceQuirks)];
}
@end
+34
View File
@@ -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
+97
View File
@@ -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
+15
View File
@@ -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
+44
View File
@@ -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
+25
View File
@@ -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
+151
View File
@@ -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
+15
View File
@@ -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
+37
View File
@@ -0,0 +1,37 @@
//
// HTMLEOFToken.m
// HTMLKit
//
// Created by Iska on 15/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLEOFToken.h"
@implementation HTMLEOFToken
+ (instancetype)token
{
static dispatch_once_t onceToken;
static HTMLEOFToken *singleton = nil;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.type = HTMLTokenTypeEOF;
}
return self;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p EOF>", self.class, self];
}
@end
+34
View File
@@ -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
+174
View File
@@ -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:@"&amp;" options:0 range:range];
[escaped replaceOccurrencesOfString:@"\00A0" withString:@"&nbsp;" options:0 range:range];
[escaped replaceOccurrencesOfString:@"\"" withString:@"&quot;" 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
+166
View File
@@ -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;
}
+50
View File
@@ -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;
}
+44
View File
@@ -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
+238
View File
@@ -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
+30
View File
@@ -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>
+9
View File
@@ -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
+13
View File
@@ -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
+13
View File
@@ -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
+11
View File
@@ -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;
+13
View File
@@ -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
+15
View File
@@ -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
+23
View File
@@ -0,0 +1,23 @@
//
// HTMLMarker.m
// HTMLKit
//
// Created by Iska on 02/03/15.
// Copyright (c) 2015 BrainCookie. All rights reserved.
//
#import "HTMLMarker.h"
@implementation HTMLMarker
+ (instancetype)marker
{
static dispatch_once_t onceToken;
static HTMLMarker *singleton = nil;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
@end
+17
View File
@@ -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,
};
+98
View File
@@ -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
+482
View File
@@ -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
+17
View File
@@ -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
+54
View File
@@ -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
+16
View File
@@ -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"
+25
View File
@@ -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
+157
View File
@@ -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
+19
View File
@@ -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
+38
View File
@@ -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
+22
View File
@@ -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
+2640
View File
File diff suppressed because it is too large Load Diff
+39
View File
@@ -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
};
+89
View File
@@ -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;
}
+55
View File
@@ -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
+318
View File
@@ -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
+32
View File
@@ -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
+128
View File
@@ -0,0 +1,128 @@
//
// HTMLTagToken.m
// HTMLKit
//
// Created by Iska on 23/10/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLTagToken.h"
@interface HTMLTagToken ()
{
NSMutableString *_tagName;
HTMLOrderedDictionary *_attributes;
}
@end
@implementation HTMLTagToken
@synthesize tagName = _tagName;
- (instancetype)initWithTagName:(NSString *)tagName
{
return [self initWithTagName:tagName attributes:nil];
}
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes
{
self = [super init];
if (self) {
_tagName = [tagName mutableCopy];
if (attributes != nil) {
_attributes = [HTMLOrderedDictionary new];
[_attributes addEntriesFromDictionary:attributes];
}
}
return self;
}
- (void)appendStringToTagName:(NSString *)string
{
if (_tagName == nil) {
_tagName = [NSMutableString new];
}
[_tagName appendString:string];
}
@end
#pragma mark - Start Tag Token
@implementation HTMLStartTagToken
- (instancetype)initWithTagName:(NSString *)tagName
{
return [self initWithTagName:tagName attributes:nil];
}
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes
{
self = [super initWithTagName:tagName attributes:attributes];
if (self) {
self.type = HTMLTokenTypeStartTag;
}
return self;
}
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLStartTagToken *token = (HTMLStartTagToken *)other;
return (bothNilOrEqual(self.tagName, token.tagName) &&
bothNilOrEqual(self.attributes, token.attributes));
}
return NO;
}
- (NSUInteger)hash
{
return self.tagName.hash + self.attributes.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p TagName=%@ Attributes=%@>", self.class, self, self.tagName, self.attributes];
}
@end
#pragma mark - End Tag Token
@implementation HTMLEndTagToken
- (instancetype)initWithTagName:(NSString *)tagName
{
return [self initWithTagName:tagName attributes:nil];
}
- (instancetype)initWithTagName:(NSString *)tagName attributes:(NSMutableDictionary *)attributes
{
self = [super initWithTagName:tagName attributes:attributes];
if (self) {
self.type = HTMLTokenTypeEndTag;
}
return self;
}
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[self class]]) {
HTMLStartTagToken *token = (HTMLStartTagToken *)other;
return bothNilOrEqual(self.tagName, token.tagName);
}
return NO;
}
- (NSUInteger)hash
{
return self.tagName.hash;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p TagName=%@ Attributes=%@>", self.class, self, self.tagName, self.attributes];
}
@end
+16
View File
@@ -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
+44
View File
@@ -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
+19
View File
@@ -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
+78
View File
@@ -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:@"&amp;" options:0 range:range];
[escaped replaceOccurrencesOfString:@"\00A0" withString:@"&nbsp;" options:0 range:range];
[escaped replaceOccurrencesOfString:@"<" withString:@"&lt;" options:0 range:range];
[escaped replaceOccurrencesOfString:@">" withString:@"&gt;" options:0 range:range];
return escaped;
}
}
#pragma mark - Description
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p \"%@\">", self.class, self, self.data];
}
@end
+54
View File
@@ -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
+91
View File
@@ -0,0 +1,91 @@
//
// HTMLToken.m
// HTMLKit
//
// Created by Iska on 20/09/14.
// Copyright (c) 2014 BrainCookie. All rights reserved.
//
#import "HTMLToken.h"
@interface HTMLToken ()
{
HTMLTokenType _type;
}
@end
@implementation HTMLToken
@synthesize type = _type;
- (BOOL)isDoctypeToken
{
return _type == HTMLTokenTypeDoctype;
}
- (BOOL)isStartTagToken
{
return _type == HTMLTokenTypeStartTag;
}
- (BOOL)isEndTagToken
{
return _type == HTMLTokenTypeEndTag;
}
- (BOOL)isCommentToken
{
return _type == HTMLTokenTypeComment;
}
- (BOOL)isCharacterToken
{
return _type == HTMLTokenTypeCharacter;
}
- (BOOL)isEOFToken
{
return _type == HTMLTokenTypeEOF;
}
- (BOOL)isParseError
{
return _type == HTMLTokenTypeParseError;
}
- (HTMLDOCTYPEToken *)asDoctypeToken
{
return (HTMLDOCTYPEToken *)self;
}
- (HTMLTagToken *)asTagToken
{
return (HTMLTagToken *)self;
}
- (HTMLStartTagToken *)asStartTagToken
{
return (HTMLStartTagToken *)self;
}
- (HTMLEndTagToken *)asEndTagToken
{
return (HTMLEndTagToken *)self;
}
- (HTMLCommentToken *)asCommentToken
{
return (HTMLCommentToken *)self;
}
- (HTMLCharacterToken *)asCharacterToken
{
return (HTMLCharacterToken *)self;
}
- (HTMLParseErrorToken *)asParseError
{
return (HTMLParseErrorToken *)self;
}
@end
+28
View File
@@ -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
+181
View File
@@ -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)];
}
+16
View File
@@ -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
+84
View File
@@ -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
};
+15
View File
@@ -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"
+19
View File
@@ -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
+61
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
/* Localized versions of Info.plist keys */
File diff suppressed because one or more lines are too long
+25
View File
@@ -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
+198
View File
@@ -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
+313
View File
@@ -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
+31
View File
@@ -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
+82
View File
@@ -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
+22
View File
@@ -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
+114
View File
@@ -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
+170
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
/* Localized versions of Info.plist keys */
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Iskandar Abudiab
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+11
View File
@@ -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.