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

相关推荐
哲科软件14 分钟前
跨平台开发的抉择:Flutter vs 原生安卓(Kotlin)的优劣对比与选型建议
android·flutter·kotlin
天涯海风16 分钟前
Kuikly 与 Flutter 的全面对比分析,结合技术架构、性能、开发体验等核心维度
flutter·kuikly
aiprtem20 分钟前
基于Flutter的web登录设计
前端·flutter
coder_pig4 小时前
跟🤡杰哥一起学Flutter (三十四、玩转Flutter手势✋)
前端·flutter·harmonyos
程序员老刘7 小时前
Android 16开发者全解读
android·flutter·客户端
Jalor7 小时前
Flutter + 鸿蒙 | Flutter 跳转鸿蒙原生界面
flutter·harmonyos
吴Wu涛涛涛涛涛Tao10 小时前
一步到位:用 Very Good CLI × Bloc × go_router 打好 Flutter 工程地基
flutter·ios
九丝城主10 小时前
2025使用VM虚拟机安装配置Macos苹果系统下Flutter开发环境保姆级教程--中篇
服务器·flutter·macos·vmware
ITfeib10 小时前
Flutter
开发语言·javascript·flutter
昱禹10 小时前
Flutter 3.29+使用isar构建失败
大数据·flutter