第三章: Flutter-quill 数据格式Delta

第三章:Flutter-Quill 数据格式 Delta

3.1 引言

flutter-quill 富文本编辑器中,Delta 是核心数据格式,用于描述文档的内容和变化。这一格式来源于 JavaScript Quill 编辑器,是一种轻量级、可序列化的操作序列结构,广泛应用于富文本编辑的增量更新和协作场景。本章将详细解析 Delta 的定义、其与 DocumentOperationDeltaIterator 的关系,并深入分析 dart_quill_delta 包中的三个文件(delta.dartdelta_iterator.dartoperation.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 交互机制

Documentflutter-quill 中富文本内容的存储模型,而 Delta 是其底层数据表示。两者之间的关系体现在以下几个方面:

  • 从 Delta 构建 Document

    • Document 提供了 fromDelta 工厂构造函数,接受一个 Delta 对象并将其内容映射到节点树。
    • 例如,Document.fromDelta(Delta()..insert("作", {header: 1})) 会创建一个包含 <h1>作</h1> 的文档。
  • 从 Document 生成 Delta

    • Document.toDelta() 方法将节点树(包括 LineLeaf 节点)序列化为 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_rootContainer)通过 compose 方法解析 Delta

      • insert 操作创建 TextNodeEmbedNode
      • 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 的定义

OperationDelta 的基本构建块,定义在 operation.dart 文件中,表示单次编辑操作。其核心属性包括:

  • data :操作的数据(如 "作" 或嵌入对象)。
  • attributes :附加的样式属性(如 {custom: "foreshadows"})。
  • length :操作影响的长度(适用于 deleteretain)。

Operation 分为三种子类:

  • InsertOperation:插入内容。
  • DeleteOperation:删除内容。
  • RetainOperation:保留内容并应用样式。

3.4.2 Delta 与 Operation 的关系

  • 组成Delta.operations 是一个 Operation 对象的列表,存储所有操作。
  • 动态生成Delta 提供 insertdeleteretain 方法动态添加 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,处理冲突。
  • 类型检查 :通过 isInsertisDeleteisRetain 方法区分操作类型。
  • 样式映射attributes 字段映射为 HTML 样式(如 {header: 1}<h1>)。

3.5 Delta 与 DeltaIterator 的关系

3.5.1 DeltaIterator 的定义

DeltaIterator 定义在 delta_iterator.dart 文件中,是一个迭代器类,用于逐一遍历 Delta 中的 Operation。其主要功能包括:

  • 按序访问 :通过 hasNextnext 方法返回操作。
  • 位置管理:跟踪当前遍历位置和剩余长度。
  • 分片处理:支持按需分割操作。

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}}]
  • 解析

    1. 插入 <h1>作家助手</h1>,其中 "家助" 带有 foreshadows 类。
    2. 插入 <h2>大前端开发</h2>,其中 "前端" 带有背景色。
    3. 插入 <h3>王金山</h3>
    4. 插入 <p>欢迎各位大佬</p>

3.8 小结

本章详细介绍了 Delta 的概念、其与 DocumentOperationDeltaIterator 的关系,并深入解析了 dart_quill_delta 包的三个文件。基于测试数据,我们展示了 Delta 如何表示复杂的富文本结构,为后续的 HTML 转换和渲染提供了基础。

相关推荐
忆江南5 小时前
iOS 深度解析
flutter·ios
明君879975 小时前
Flutter 实现 AI 聊天页面 —— 记一次 Markdown 数学公式显示的踩坑之旅
前端·flutter
恋猫de小郭7 小时前
移动端开发稳了?AI 目前还无法取代客户端开发,小红书的论文告诉你数据
前端·flutter·ai编程
MakeZero9 小时前
Flutter那些事-交互式组件
flutter
shankss9 小时前
pull_to_refresh_simple
flutter
shankss9 小时前
Flutter 下拉刷新库新特性:智能预加载 (enableSmartPreload) 详解
flutter
SoaringHeart2 天前
Flutter调试组件:打印任意组件尺寸位置信息 NRenderBox
前端·flutter
九狼2 天前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
_squirrel2 天前
记录一次 Flutter 升级遇到的问题
flutter
Haha_bj2 天前
Flutter——状态管理 Provider 详解
flutter·app