HTMLKit
HTMLKit 是一个针对日常HTML需求的Objective-C框架。
快速概览
HTMLKit 是一个符合 WHATWG 规范 框架,用于解析和序列化iOS和OSX上的HTML文档和文档片段。HTMLKit 会像现代网页浏览器一样解析真实的HTML。
HTMLKit 提供了一个丰富的DOM实现,用于操作和导航文档树。它还支持 CSS3选择器,使得节点选择和查询DOM变得轻而易举。
DOM验证
DOM变更会按照WHATWG DOM标准进行验证。无效的DOM操作会抛出与层次结构相关的异常。您可以通过定义编译器常量HTMLKIT_NO_DOM_CHECKS
来禁用这些验证,这将提高性能约20-30%。
测试
HTMLKit通过了所有HTML5Lib标记解析器和树构建测试。)html5lib-tests
配置为git子模块。如果你打算运行测试,别忘了也要拉取它。
CSS3选择器实现是通过修改版本的CSS3选择器测试套件进行测试的,忽略需要用户交互、会话历史和脚本的测试。
如何Swift?
查看游乐场!
安装
Carthage
Carthage是一个去中心化的依赖管理器,用于构建你的依赖并提供二进制框架。
如果你还没有Carthage,你可以使用以下命令通过Homebrew安装它
$ brew update
$ brew install carthage
要使用Carthage将HTMLKit
作为依赖添加到你的项目,只需在你的Cartfile
中添加以下行
github "iabudiab/HTMLKit"
然后,运行以下命令构建框架并将构建的HTMLKit.framework
拖到你的Xcode项目中。
$ carthage update
CocoaPods
CocoaPods是Cocoa项目的依赖管理器。
如果你还没有CocoaPods,你可以使用以下命令安装它
$ gem install cocoapods
要将HTMLKit
作为依赖添加到你的项目,只需在你的Podfile
中添加以下内容
target 'MyTarget' do
pod 'HTMLKit', '~> 3.1'
end
然后,运行以下命令
$ pod install
Swift包管理器
Swift包管理器是Swift编程语言的包管理器。
将HTMLKit
添加到您的Package.swift
依赖项中
.Package(url: "https://github.com/iabudiab/HTMLKit", majorVersion: 3)
然后运行
$ swift build
手动操作
1- 将HTMLKit
作为git子模块添加
$ git submodule add https://github.com/iabudiab/HTMLKit.git
2- 打开HTMLKit
文件夹,将HTMLKit.xcodeproj
拖放到Xcode的项目导航器中以将其添加为子项目。
3- 在您的目标的“通用”面板中,在“嵌入的二进制文件”下添加HTMLKit.framework
。
解析
解析文档
给定一些HTML内容,您可以通过HTMLParser
或直接实例化一个HTMLDocument
来解析它
NSString *htmlString = @"<div><h1>HTMLKit</h1><p>Hello there!</p></div>";
// Via parser
HTMLParser *parser = [[HTMLParser alloc] initWithString:htmlString];
HTMLDocument *document = [parser parseDocument];
// Via static initializer
HTMLDocument *document = [HTMLDocument documentWithString:htmlString];
解析片段
您也可以将HTML内容作为具有指定上下文元素的文档片段进行解析。
NSString *htmlString = @"<div><h1>HTMLKit</h1><p>Hello there!</p></div>";
HTMLParser *parser = [[HTMLParser alloc] initWithString: htmlString];
HTMLElement *tableContext = [[HTMLElement alloc] initWithTagName:@"table"];
NSArray *nodes = [parser parseFragmentWithContextElement:tableContext];
for (HTMLNode *node in nodes) {
NSLog(@"%@", node.outerHTML);
}
// The same parser instance can be reusued:
HTMLElement *bodyContext = [[HTMLElement alloc] initWithTagName:@"body"];
nodes = [parser parseFragmentWithContextElement:bodyContext];
DOM树
DOM树可以通过多种方式操作,以下是一些例子:
- 创建新元素并分配属性
HTMLElement *description = [[HTMLElement alloc] initWithTagName:@"meta" attributes: @{@"name": @"description"}];
description[@"content"] = @"HTMLKit for iOS & OSX";
- 将节点添加到文档中
HTMLElement *head = document.head;
[head appendNode:description];
HTMLElement *body = document.body;
NSArray *nodes = @[
[[HTMLElement alloc] initWithTagName:@"div" attributes: @{@"class": @"red"}],
[[HTMLElement alloc] initWithTagName:@"div" attributes: @{@"class": @"green"}],
[[HTMLElement alloc] initWithTagName:@"div" attributes: @{@"class": @"blue"}]
];
[body appendNodes:nodes];
- 枚举子元素并进行DOM编辑
[body enumerateChildElementsUsingBlock:^(HTMLElement *element, NSUInteger idx, BOOL *stop) {
if ([element.tagName isEqualToString:@"div"]) {
HTMLElement *lorem = [[HTMLElement alloc] initWithTagName:@"p"];
lorem.textContent = [NSString stringWithFormat:@"Lorem ipsum: %lu", (unsigned long)idx];
[element appendNode:lorem];
}
}];
- 从文档中删除节点
[body removeChildNodeAtIndex:1];
[head removeAllChildNodes];
[body.lastChild removeFromParentNode];
- 直接操作HTML
greenDiv.innerHTML = @"<ul><li>item 1<li>item 2";
- 导航到子节点和兄弟节点
HTMLNode *firstChild = body.firstChild;
HTMLNode *greenDiv = firstChild.nextSibling;
- 使用自定义过滤器迭代DOM树
HTMLNodeFilterBlock *filter =[HTMLNodeFilterBlock filterWithBlock:^ HTMLNodeFilterValue (HTMLNode *node) {
if (node.childNodesCount != 1) {
return HTMLNodeFilterReject;
}
return HTMLNodeFilterAccept;
}];
for (HTMLElement *element in [body nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:filter]) {
NSLog(@"%@", element.outerHTML);
}
- 创建和操作DOM范围
HTMLDocument *document = [HTMLDocument documentWithString:@"<div><h1>HTMLKit</h1><p id='foo'>Hello there!</p></div>"];
HTMLRange *range = [[HTMLRange alloc] initWithDocument:document];
HTMLNode *paragraph = [document querySelector:@"#foo"];
[range selectNode:paragraph];
[range extractContents];
CSS3选择器
除了伪元素(::first-line
、::first-letter
等)之外,所有CSS3选择器都受支持。您可以使用它们像以往一样使用
// Given the document:
NSString *htmlString = @"<div><h1>HTMLKit</h1><p class='greeting'>Hello there!</p><p class='description'>This is a demo of HTMLKit</p></div>";
HTMLDocument *document = [HTMLDocument documentWithString: htmlString];
// Here are some of the supported selectors
NSArray *paragraphs = [document querySelectorAll:@"p"];
NSArray *paragraphsOrHeaders = [document querySelectorAll:@"p, h1"];
NSArray *hasClassAttribute = [document querySelectorAll:@"[class]"];
NSArray *greetings = [document querySelectorAll:@".greeting"];
NSArray *classNameStartsWith_de = [document querySelectorAll:@"[class^='de']"];
NSArray *hasAdjacentHeader = [document querySelectorAll:@"h1 + *"];
NSArray *hasSiblingHeader = [document querySelectorAll:@"h1 ~ *"];
NSArray *hasSiblingParagraph = [document querySelectorAll:@"p ~ *"];
NSArray *nonParagraphChildOfDiv = [document querySelectorAll:@"div :not(p)"];
HTMLKit还提供了API,以类型安全的方式创建选择器实例,而无需首先解析它们。前面的示例可以像这样
NSArray *paragraphs = [document elementsMatchingSelector:typeSelector(@"p")];
NSArray *paragraphsOrHeaders = [document elementsMatchingSelector:
anyOf(@[
typeSelector(@"p"), typeSelector(@"h1")
])
];
NSArray *hasClassAttribute = [document elementsMatchingSelector:hasAttributeSelector(@"class")];
NSArray *greetings = [document elementsMatchingSelector:classSelector(@"greeting")];
NSArray *classNameStartsWith_de = [document elementsMatchingSelector:attributeSelector(CSSAttributeSelectorBegins, @"class", @"de")];
NSArray *hasAdjacentHeader = [document elementsMatchingSelector:adjacentSiblingSelector(typeSelector(@"h1"))];
NSArray *hasSiblingHeader = [document elementsMatchingSelector:generalSiblingSelector(typeSelector(@"h1"))];
NSArray *hasSiblingParagraph = [document elementsMatchingSelector:generalSiblingSelector(typeSelector(@"p"))];
NSArray *nonParagraphChildOfDiv = [document elementsMatchingSelector:
allOf(@[
childOfElementSelector(typeSelector(@"div")),
not(typeSelector(@"p"))
])
];
以下是一些更多示例
HTMLNode *firstDivElement = [document firstElementMatchingSelector:typeSelector(@"div")];
NSArray *secondChildOfDiv = [firstDivElement querySelectorAll:@":nth-child(2)"];
NSArray *secondOfType = [firstDivElement querySelectorAll:@":nth-of-type(2n)"];
secondChildOfDiv = [firstDivElement elementsMatchingSelector:nthChildSelector(CSSNthExpressionMake(0, 2))];
secondOfType = [firstDivElement elementsMatchingSelector:nthOfTypeSelector(CSSNthExpressionMake(2, 0))];
NSArray *notParagraphAndNotDiv = [firstDivElement querySelectorAll:@":not(p):not(div)"];
notParagraphAndNotDiv = [firstDivElement elementsMatchingSelector:
allOf([
not(typeSelector(@"p")),
not(typeSelector(@"div"))
])
];
还有一件事!您也可以创建自己的选择器。您可以子类化CSSSelector,或者仅使用基于块的包装器。例如,前面的选择器可以像这样实现
CSSSelector *myAwesomeSelector = namedBlockSelector(@"myAwesomeSelector", ^BOOL (HTMLElement *element) {
return ![element.tagName isEqualToString:@"p"] && ![element.tagName isEqualToString:@"div"];
});
notParagraphAndNotDiv = [firstDivElement elementsMatchingSelector:myAwesomeSelector];
变更日志
更多详细信息,请参阅CHANGELOG.md。
许可
HTMLKit遵循MIT许可协议。更多详细信息,请参阅LICENSE文件。