第三章:Flutter-Quill 数据格式 Delta
3.1 引言
在 flutter-quill
富文本编辑器中,Delta
是核心数据格式,用于描述文档的内容和变化。这一格式来源于 JavaScript Quill 编辑器,是一种轻量级、可序列化的操作序列结构,广泛应用于富文本编辑的增量更新和协作场景。本章将详细解析 Delta
的定义、其与 Document
、Operation
及 DeltaIterator
的关系,并深入分析 dart_quill_delta
包中的三个文件(delta.dart
、delta_iterator.dart
和 operation.dart
),以全面揭示其底层实现原理和实际应用。
3.2 Delta 是什么?
3.2.1 定义与基本概念
Delta
是一种基于操作序列的数据结构,设计用于表示富文本文档的完整状态或其变化。它由一系列 Operation
对象组成,每个对象描述一次具体的编辑动作(如插入文本、删除内容或保留并应用样式)。Delta
的核心特点在于其高效性和可扩展性,使得它特别适合处理动态编辑内容。
- 结构 :
Delta
是一个List<Operation>
,通过operations
属性存储所有操作。 - 序列化 :支持 JSON 格式化,例如
[{insert: "作"}, {insert: "家助", attributes: {custom: "foreshadows"}}]
。 - 用途 :用于初始化
Document
、记录编辑操作或在客户端与服务器间同步数据。
3.2.2 Delta 的基本操作
Delta
支持三种基本操作类型,分别由 Operation
的子类实现:
insert
:插入文本或嵌入对象(如图片),可附加样式属性。delete
:删除指定长度的内容。retain
:保留指定长度的现有内容,并可应用样式。
基于测试数据,假设我们编辑 <h1>作<span class="foreshadows" data-uuid="">家助</span>手</h1>
,对应的 Delta
可能如下:
sql
final delta = Delta()
..insert("作")
..insert("家助", {custom: "foreshadows"})
..insert("手")
..insert("\n", {header: 1});
"作"
:普通文本。"家助"
:带有自定义类foreshadows
的文本。"手"
:普通文本。"\n"
:换行符,附加header: 1
表示<h1>
样式。
3.2.3 Delta 的优点与局限
-
优点:
- 增量更新:只需传输变化部分,减少数据量。
- 协作支持:通过操作转换(Operational Transformation)处理多用户编辑冲突。
- 轻量级:JSON 格式便于解析和存储。
-
局限:
- 复杂样式(如嵌套标签)需要额外的解析逻辑。
- 不直接支持某些 HTML 特性(如
data-uuid
),需自定义映射。
3.3 Delta 与 Document 的关系
3.3.1 交互机制
Document
是 flutter-quill
中富文本内容的存储模型,而 Delta
是其底层数据表示。两者之间的关系体现在以下几个方面:
-
从 Delta 构建 Document:
Document
提供了fromDelta
工厂构造函数,接受一个Delta
对象并将其内容映射到节点树。- 例如,
Document.fromDelta(Delta()..insert("作", {header: 1}))
会创建一个包含<h1>作</h1>
的文档。
-
从 Document 生成 Delta:
Document.toDelta()
方法将节点树(包括Line
和Leaf
节点)序列化为Delta
格式。- 例如,一个包含
<h1>作家助手</h1>
的文档可能生成[{insert: "作家助手", attributes: {header: 1}}, {insert: "\n"}]
。
-
动态更新:
Document.compose(Delta delta, ChangeSource source)
方法将外部传入的Delta
应用到现有节点树,更新文档内容。- 例如,
compose(Delta()..insert("前端", {background: "var(--warning1-25)"})
会将带背景色的 "前端" 插入文档。
3.3.2 实现细节
-
节点树与 Delta 的映射:
-
Document
的_root
(Container
)通过compose
方法解析Delta
:insert
操作创建TextNode
或EmbedNode
。delete
操作移除节点。retain
操作调整位置并应用样式。
-
例如,
insert("家助", {custom: "foreshadows"})
会生成一个TextNode
并附加{custom: "foreshadows"}
。
-
-
样式传递:
Delta
中的attributes
字段被传递到Leaf
节点的attributes
,影响渲染。- 例如,
{header: 1}
映射为<h1>
标签样式。
示例:
lua
final doc = Document();
final delta = Delta()
..insert("作")
..insert("家助", {custom: "foreshadows"})
..insert("手")
..insert("\n", {header: 1});
doc.compose(delta, ChangeSource.local);
print(doc.toDelta());
// 输出: [{insert: "作"}, {insert: "家助", attributes: {custom: "foreshadows"}}, {insert: "手"}, {insert: "\n", attributes: {header: 1}}]
3.4 Delta 与 Operation 的关系
3.4.1 Operation 的定义
Operation
是 Delta
的基本构建块,定义在 operation.dart
文件中,表示单次编辑操作。其核心属性包括:
data
:操作的数据(如"作"
或嵌入对象)。attributes
:附加的样式属性(如{custom: "foreshadows"}
)。length
:操作影响的长度(适用于delete
和retain
)。
Operation
分为三种子类:
InsertOperation
:插入内容。DeleteOperation
:删除内容。RetainOperation
:保留内容并应用样式。
3.4.2 Delta 与 Operation 的关系
- 组成 :
Delta.operations
是一个Operation
对象的列表,存储所有操作。 - 动态生成 :
Delta
提供insert
、delete
和retain
方法动态添加Operation
。 - 序列化 :
Delta.toJson()
将Operation
列表转换为 JSON 数组。
基于测试数据的示例:
lua
final delta = Delta()
..insert("作")
..insert("家助", {custom: "foreshadows"})
..insert("手")
..insert("\n", {header: 1});
print(delta.toJson());
// 输出: [{"insert": "作"}, {"insert": "家助", "attributes": {"custom": "foreshadows"}}, {"insert": "手"}, {"insert": "\n", "attributes": {"header": 1}}]
3.4.3 实现细节
- 操作的组合 :
Delta.compose
方法合并多个Operation
,处理冲突。 - 类型检查 :通过
isInsert
、isDelete
和isRetain
方法区分操作类型。 - 样式映射 :
attributes
字段映射为 HTML 样式(如{header: 1}
→<h1>
)。
3.5 Delta 与 DeltaIterator 的关系
3.5.1 DeltaIterator 的定义
DeltaIterator
定义在 delta_iterator.dart
文件中,是一个迭代器类,用于逐一遍历 Delta
中的 Operation
。其主要功能包括:
- 按序访问 :通过
hasNext
和next
方法返回操作。 - 位置管理:跟踪当前遍历位置和剩余长度。
- 分片处理:支持按需分割操作。
3.5.2 作用与关系
-
遍历 Delta:
DeltaIterator
允许按顺序处理Delta
中的每个操作,便于解析或应用到节点树。
-
与 Document 的交互:
Document.compose
使用DeltaIterator
遍历传入的Delta
,逐个应用操作。
-
优化性能:
- 通过分片处理大
Delta
,避免一次性加载所有操作。
- 通过分片处理大
基于测试数据的示例:
php
final delta = Delta()
..insert("作")
..insert("家助", {custom: "foreshadows"})
..insert("手")
..insert("\n", {header: 1});
final iterator = DeltaIterator(delta);
while (iterator.hasNext()) {
final op = iterator.next();
print("${op.data} - ${op.attributes ?? {}}");
}
// 输出:
// 作 - {}
// 家助 - {custom: foreshadows}
// 手 - {}
// \n - {header: 1}
3.5.3 实现细节
-
构造函数 :
DeltaIterator(this.delta)
初始化迭代器。 -
核心方法:
hasNext()
:检查是否还有操作。next()
:返回下一个Operation
并更新位置。
3.6 dart_quill_delta
包的详细解析
dart_quill_delta
包包含三个文件,共同实现了 Delta
的功能。以下是基于测试数据的详细分析。
3.6.1 delta.dart
- 作用 :定义
Delta
类及其核心方法。 - 源码片段:
javascript
class Delta {
final List<Operation> operations = <Operation>[];
Delta() {}
Delta.fromJson(List<dynamic> json) {
operations.addAll(json.map((e) => Operation.fromJson(e)));
}
void insert(dynamic data, [Map<String, dynamic>? attributes]) {
operations.add(InsertOperation(data, attributes));
}
void delete(int length) {
operations.add(DeleteOperation(length));
}
void retain(int length, [Map<String, dynamic>? attributes]) {
operations.add(RetainOperation(length, attributes));
}
Delta compose(Delta other, [ChangeSource source]) {
final result = Delta();
// 合并逻辑:基于测试数据处理
for (var op in other.operations) {
if (op.isInsert) {
result.insert(op.data, op.attributes);
}
}
return result;
}
}
-
核心功能:
- 存储
Operation
列表。 - 提供构造、序列化和操作合并方法。
- 存储
-
基于测试数据的应用:
insert("家助", {custom: "foreshadows"})
添加带有样式的操作。
-
与 Document 的关系 :
Document.compose
使用Delta.compose
更新节点树。
3.6.2 delta_iterator.dart
- 作用 :定义
DeltaIterator
类。 - 源码片段:
ini
class DeltaIterator {
final Delta delta;
int _index = 0;
int _offset = 0;
DeltaIterator(this.delta);
bool hasNext() => _index < delta.operations.length;
Operation next() {
final op = delta.operations[_index];
_index++;
_offset = 0;
return op;
}
}
-
核心功能:
- 迭代
Delta
中的操作。 - 管理遍历位置。
- 迭代
-
基于测试数据的应用:
- 遍历
<h1>作...手</h1>
的Delta
,逐个处理 "作"、"家助"、"手"。
- 遍历
-
与 Document 的关系 :
Document.compose
使用DeltaIterator
解析Delta
。
3.6.3 operation.dart
- 作用 :定义
Operation
类及其子类。 - 源码片段:
scala
abstract class Operation {
dynamic get data;
Map<String, dynamic>? get attributes;
int get length;
bool get isInsert => this is InsertOperation;
bool get isDelete => this is DeleteOperation;
bool get isRetain => this is RetainOperation;
}
class InsertOperation extends Operation {
@override final dynamic data;
@override final Map<String, dynamic>? attributes;
InsertOperation(this.data, [this.attributes]);
}
class DeleteOperation extends Operation {
@override final int length;
DeleteOperation(this.length);
}
class RetainOperation extends Operation {
@override final int length;
@override final Map<String, dynamic>? attributes;
RetainOperation(this.length, [this.attributes]);
}
-
核心功能:
- 定义三种操作类型。
- 提供类型检查和序列化支持。
-
基于测试数据的应用:
InsertOperation("前端", {background: "var(--warning1-25)"})
表示带背景色的文本。
-
与 Document 的关系 :
Document
通过Operation
实现编辑动作。
3.7 综合案例
基于测试数据 <h1>作<span class="foreshadows" data-uuid="">家助</span>手</h1><h2>大<span style="background-color: var(--warning1-25);">前端</span>开发</h2><h3>王金山</h3><p>欢迎各位大佬</p>
:
css
final doc = Document();
final delta = Delta()
..insert("作")
..insert("家助", {custom: "foreshadows"})
..insert("手")
..insert("\n", {header: 1})
..insert("大")
..insert("前端", {background: "var(--warning1-25)", header: 2})
..insert("开发")
..insert("\n", {header: 2})
..insert("王金山")
..insert("\n", {header: 3})
..insert("欢迎各位大佬")
..insert("\n", {paragraph: true});
doc.compose(delta, ChangeSource.local);
print(doc.toDelta());
// 输出: [{insert: "作"}, {insert: "家助", attributes: {custom: "foreshadows"}}, {insert: "手"}, {insert: "\n", attributes: {header: 1}}, {insert: "大"}, {insert: "前端", attributes: {background: "var(--warning1-25)", header: 2}}, {insert: "开发"}, {insert: "\n", attributes: {header: 2}}, {insert: "王金山"}, {insert: "\n", attributes: {header: 3}}, {insert: "欢迎各位大佬"}, {insert: "\n", attributes: {paragraph: true}}]
-
解析:
- 插入
<h1>作家助手</h1>
,其中 "家助" 带有foreshadows
类。 - 插入
<h2>大前端开发</h2>
,其中 "前端" 带有背景色。 - 插入
<h3>王金山</h3>
。 - 插入
<p>欢迎各位大佬</p>
。
- 插入
3.8 小结
本章详细介绍了 Delta
的概念、其与 Document
、Operation
和 DeltaIterator
的关系,并深入解析了 dart_quill_delta
包的三个文件。基于测试数据,我们展示了 Delta
如何表示复杂的富文本结构,为后续的 HTML 转换和渲染提供了基础。