深色模式
html解析转换
目标是把epub的xhtml文档转换为List<Widget>
,这里先参考pub.dev上的flutter_widget_from_html_core
库,分析其工作原理。
HTML 文档中 Node 与 Element 的区别
Node(节点):是 DOM 中的最基本概念,它是一个宽泛的概念,代表文档中的任意一个对象。Node 可以是元素节点(对应 HTML 标签)、文本节点(标签内的文本内容)、属性节点(标签的属性)、注释节点(HTML 注释)等。
Element(元素):专指 HTML 标签所创建的节点,例如 <div>
、<p>
、<a>
等标签在 DOM 中对应的就是 Element 对象。元素节点有自己的标签名、属性和子节点。
思路
- HTML解析为NodeList
- NodeList转换为BuildTree
- BuildTree转Widget list
过程分析
入口
入口是 HtmlWidget
的状态类 HtmlWidgetState
。
有2个方法,分同步构建或异步构建:_buildSync()
_buildAsync()
它们核心逻辑是一样的,以同步构建为例:
dart
Widget _buildSync() {
// ...
Widget built;
try {
final domNodes = _parseHtml(widget.html);
built = _buildBody(this, domNodes);
} catch (error, stackTrace) {
built =
_wf.onErrorBuilder(context, _rootTree, error, stackTrace) ?? widget0;
}
// ...
return built;
}
创建_RootResolvers
- 它持有
state
- 它的
resolve()
方法中,调用了context.dependOnInheritedWidgetOfExactType<_RootWidget>();
创建WidgetFactory
.xhtml
->dom.NodeList
dart
dom.NodeList _parseHtml(String html) =>
parser.HtmlParser(html, parseMeta: false).parseFragment().nodes;
dom.NodeList
->CoreBuildTree
rootTree.addBitsFromNodes(domNodes);
_addBitsFromNode(domNode);
- 用
domNodes
填充rootTree
,递归
CoreBuildTree
->Widget
rootTree.build()
这里Flattener起作用,扁平化
->_RootWidget
WidgetFactory
有各种 build 开头的方法,这些方法分为几类:
- 第一个参数是 BuildTree
- 第一个参数是 BuildContext
- 其它辅助方法
- _css相关的静态方法
HtmlWidget
HtmlWidget
HtmlWidgetState 生命周期
分析一下 HtmlWidgetState
中,生命周期相关方法
initState()
创建_RootResolvers
创建WidgetFactory
如果是异步构建,则开始构建_buildAsync()
dispose()
WidgetFactory
清理
didChangeDependencies()
_rootProperties
置空
didUpdateWidget()
判断是否需要重新build,判断依据是列表rebuildTriggers
中的元素,有无变化
build()
如果是异步构建,则返回FutureBuilder
如果是同步构建,则走_buildSync()
_RootWidget
继承InheritedWidget
,明显,就是为了通知下层Widget
更新。
dart
class _RootWidget extends InheritedWidget {
...
}
HtmlWidgetState
的 build()
方法,最终会用 _RootWidget
包裹生成的内容。
dart
@override
Widget build(BuildContext context) {
final future = _future;
if (future != null) {
return FutureBuilder<Widget>(
builder: // ...
future: future.then(_wrapper),
);
}
if (!enableCaching || _cache == null) {
_cache = _buildSync();
}
return _wrapper(_cache!);
}
dart
Widget _wrapper(Widget child) =>
_RootWidget(resolved: _rootProperties, child: child);
WidgetPlaceHolder
继承StatelessWidget
,作用是包装一个Widget
。
构造方法:
dart
WidgetPlaceholder({
WidgetPlaceholderBuilder? builder,
Widget? child,
this.debugLabel,
super.key,
}) : _builders = builder != null ? [builder] : [],
_firstChild = child;
builder
:child
:
成员:
_builders
: 存放函数_firstChild
:wrapWith()
: 把一个builder
添加到_builders
中static unwrap()
: 如果传入的Widget
是一个WidgetPlaceHolder
,则返回它build()
的结果,否则返回它本身。callBuilders()
:lazy()
:build()
:
构建树
类图
CoreBuildTree
构造方法相关:
构造方法 CoreBuildTree._()
被调用:
.root()
构造方法copyWith()
参数:
element
:inheritanceResolvers
:parent
:parentOps
:wf
:
构造方法 CoreBuildTree.root()
被调用:
仅被调用1次,创建根树。
内部实现:
它是通过调用私有构造方法._()
实现的。
被调用时传值:
_rootElement
是手动创建的root元素。
parent
为空。
parentOps
为默认值[]
。
复制 copyWith()
被调用:
copyWith()
内部递归调用sub()
append()
prepend()
成员:
_parent
:_parentOps
: 在构造方法里传入,默认值是[]
。_styles
:_buildOps
:register()
: 向_buildOps
里面添加一个_CoreBuildOp
。(如果_buildOps
为空,会判空初始化。)sub()
:
BuildBit
是一个接口,代表组成html的最小单元
get hasParent
: 是否有父结点get isInline
: 当前节点是否渲染行内元素,有可能返回空(当BuildTree
还未完全解析的时候)get parent
: 一定是BuildTree
get next
: 后一个 bit,不一定是兄弟结点get prev
: 前一个 bit,不一定是兄弟结点get swallowWhitespace
: 是否吞掉空白字符,默认情况下,如果是块元素,就吞abstract flatten()
: 将本节点扁平化
BuildTree
它继承 BuildBit
,可看作特殊的 BuildBit
,因为 BuildBit
没有孩子,而 BuildTree
是有孩子的 BuildBit
。
构造方法:
dart
BuildTree({
required this.element,
required this.inheritanceResolvers,
});
element
: 对应的dom元素inheritanceResolvers
:
组成树结构,对树进行操作:
_children
: 直接孩子,List<BuildBit>?
get children
: 访问_children
get bits
: 递归,所有下层BuildBit
get isEmpty
: 下层有任意元素,即非空get first
:BuildBit
get last
:BuildBit
append()
: 添加到_children
的最后面addWhitespace()
: 追加WhitespaceBit
,调用了append()
addText()
: 追加TextBit
,调用了append()
prepend()
: 添加到_children
的最前面inherit()
: 插入一个样式回调abstract register()
: 被CoreBuildTree
实现。abstract sub()
: 被CoreBuildTree
实现。
样式:
abstract get styles
: 被CoreBuildTree
实现。类型是
LockableList<css.Declaration>
样式的来源:
HtmlWidget.customStylesBuilder
BuildOp.defaultStyles
DOM
元素的style
属性
abstract getStyle(property)
: 被CoreBuildTree
实现。返回类型是
css.Declaration?
构建,转换:
abstract build()
: 被CoreBuildTree
实现。把当前BuildTree转换为
WidgetPlaceholder
。这个方法被CoreBuildTree
重写。
扁平化:
注意这个类没有实现flatten()
方法,交由CoreBuildTree
实现。
TextBit
文本元素
构造方法:
dart
const TextBit(this.parent, this.data);
data
: 文本内容parent
:BuildTree
扁平化:
flatten()
:
调用了Flattened
的方法,也就是说,逻辑在Flattened
中:
dart
@override
void flatten(Flattened f) => f.write(text: data);
这个方法在Flattener
类里面调用,Flattener
在调用时,会把自己传入这个方法的参数中:
dart
bit.flatten(this);
来看看flatten()
方法:调用方法的是Flattener
,实现逻辑也在Flattener
中,方法参数还是Flattener
...
写的什么鬼代码?
吐槽完了,记录一下,这个方法的作用,是把文本添加到Flattener
的_strings
中。
WidgetBit
组件元素,可能是块元素,或行内元素。
构造方法是私有的:
dart
const WidgetBit._(this.parent, this.child);
提供了2个静态方法,用于构造2个子类的实例:
static block()
: 构建_WidgetBitBlock
static inline()
: 构建_WidgetBitInline
扁平化:
- 对于
_WidgetBitBlock
,把Widget添加到_widgets
中。 - 对于
_WidgetBitInline
,用Widget构建一个builder,把builder添加到_childrenBuilder
中。
WhitespaceBit
空白字符元素。
构造方法:
dart
const WhitespaceBit(this.parent, this.data);
data
: 文本内容parent
:BuildTree
扁平化:
flatten()
:与TextBit
类似,作用是把文本添加到Flattener
的_strings
中。
扁平化
Flattened
是一个接口,有3个方法:
abstract inlineWidget()
abstract widget()
abstract write()
Flattener
实现Flattened
接口。
它的构造方法,要传入 BuildTree 和 WidgetFactory,并且会进行扁平化操作:
dart
Flattener(this.wf, this.tree) {
_loopSubTree(tree, flatten: false);
_resetLoop(tree.inheritanceResolvers);
for (final bit in tree.bits) {
_loop(bit);
}
_completeLoop();
}
构造方法在 CoreBuildTree 的 build 方法中调用:
dart
@override
WidgetPlaceholder? build() {
final children = Flattener(wf, this).widgets;
//...
}
- _widgets
- _strings
- _childrenBuilder
- _saveSpan()
构建操作
BuildOp
有3个构造方法,前2个构造方法都是通过调用第3个构造方法实现。
BuildOp()
: 其中有些参数票房为过时BuildOp.inline()
: 定义了,代码中没有使用BuildOp.v2()
: 新版构造方法,但是代码中有很多使用第1个构造方法的地方
构造方法的参数:
alwaysRenderBlock
:debugLabel
:defaultStyles
:onParsed
:onRenderBlock
:onRenderInline
:onRenderedBlock
:onRenderedChildren
:onVisitChild
:priority
:
过时的参数:
onChild
onTree
onTreeFlattening
onWidgets
onWidgetsIsOptional
_CoreBuildOp
没有继承BuildOp,而是组合了BuildOp。
在哪里被使用?
在CoreBuildTree
的register()
方法中使用。
register()
接收一个BuildOp
作为参数,然后把这个BuildOp
包装为_CoreBuildOp
,添加到CoreBuildTree
的_buildOps
中。
dart
@override
void register(BuildOp op) {
final scopedBuildOps = _buildOps ??= _CoreBuildOp._newSet();
scopedBuildOps.add(_CoreBuildOp(this, op));
_logger.finest(
'Registered ${op.debugLabel ?? 'a build op'} '
'for ${element.localName?.toUpperCase()} tag',
);
}
继承样式
_RootResolvers
多了一个state
。
把resolve()
方法重写了,并且,重写的时候,没有调用super.resolve()
。
InheritanceResolverCallback
dart
typedef InheritanceResolverCallback<T> = InheritedProperties Function(
InheritedProperties resolving,
T input,
);
函数类型,参数是InheritedProperties
和T
,返回是InheritedProperties
。
_InheritanceResolverCallbackWithInput
callback
:InheritanceResolverCallback
input
:T
InheritanceResolvers
InheritanceResolvers()
:parent
:_cachedParent
:_cachedResolved
:_callbacks
:enqueue()
: 参数是callback
和input
,作用是把callback
添加到_callbacks
中。copyWith()
:isIdenticalWith()
:resolve()
:sub()
:
InheritedProperties
继承的样式。
主要是 TextStyle ,和一些其它样式
InheritedProperties.root()
:InheritedProperties._()
:parent
:values
:_style
: 是一个TextStyle
copyWith()
: 复制当前的InheritedProperties
,可以指定parent
,把当前InheritedProperties
的values
和_style
与参数(如果参数非空)里的合并。get()
:prepareTextStyle()
: