第三章: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 转换和渲染提供了基础。