Flutter三方库适配OpenHarmony【doc_text】— FIB 解析与 Piece Table 文本提取
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net拿到了 WordDocument 流的数据,接下来要从中提取文本。这不是简单的"读字节"——Word 文档的文本存储方式相当复杂,涉及FIBCLX结构。doc_text 的 extractWordText 方法就是在处理这些东西。这篇是整个系列技术含量最高的一篇。FIB 是 WordDoc
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
拿到了 WordDocument 流的数据,接下来要从中提取文本。这不是简单的"读字节"——Word 文档的文本存储方式相当复杂,涉及 FIB(File Information Block)、Piece Table、CLX 结构。doc_text 的 extractWordText 方法就是在处理这些东西。这篇是整个系列技术含量最高的一篇。
一、extractWordText 方法入口
1.1 源码
private extractWordText(wordBytes: Uint8Array, ole: OLE2Parser): string | null {
if (wordBytes.length < 0x200) {
return null;
}
// 读取 FIB
const flags = this.readU16(wordBytes, 0x0A);
const isEncrypted = (flags & 0x0100) !== 0;
if (isEncrypted) {
return "[加密文档,无法读取]";
}
// 获取文本长度
const ccpText = this.readU32(wordBytes, 0x4C);
if (ccpText <= 0 || ccpText > 10000000) {
return null;
}
// 获取 CLX 信息
const fcClx = this.readU32(wordBytes, 0x1A2);
const lcbClx = this.readU32(wordBytes, 0x1A6);
// 查找 Table 流
let tableData: Uint8Array | null = null;
let tableEntry = ole.findEntry("1Table");
if (!tableEntry) {
tableEntry = ole.findEntry("0Table");
}
if (tableEntry) {
tableData = ole.readEntryData(tableEntry);
}
// 使用 piece table 提取文本
if (tableData && fcClx > 0 && lcbClx > 0) {
const text = this.extractTextWithPieceTable(wordBytes, tableData, fcClx, lcbClx, ccpText);
if (text) {
return this.cleanText(text);
}
}
// 回退:直接提取
return this.extractTextDirect(wordBytes, ccpText);
}
1.2 执行流程
extractWordText(wordBytes, ole)
│
├── 1. 检查最小长度(0x200 = 512 字节)
│
├── 2. 读取 FIB flags → 检查加密
│
├── 3. 读取 ccpText → 文本字符数
│
├── 4. 读取 fcClx / lcbClx → CLX 位置和大小
│
├── 5. 查找 1Table / 0Table 流
│
├── 6. 尝试 Piece Table 提取
│ │
│ ├── 成功 → cleanText → 返回
│ │
│ └── 失败 ↓
│
└── 7. 回退:extractTextDirect
二、FIB(File Information Block)
2.1 什么是 FIB
FIB 是 WordDocument 流的头部,包含了文档的各种元信息。它从偏移 0 开始,长度可变(几百到上千字节)。
2.2 doc_text 读取的 FIB 字段
| 偏移 | 长度 | 字段 | 代码 | 说明 |
|---|---|---|---|---|
| 0x0A | 2 | flags | readU16(wordBytes, 0x0A) |
文档标志位 |
| 0x4C | 4 | ccpText | readU32(wordBytes, 0x4C) |
正文字符数 |
| 0x1A2 | 4 | fcClx | readU32(wordBytes, 0x1A2) |
CLX 在 Table 流中的偏移 |
| 0x1A6 | 4 | lcbClx | readU32(wordBytes, 0x1A6) |
CLX 的大小 |
2.3 加密检测
const flags = this.readU16(wordBytes, 0x0A);
const isEncrypted = (flags & 0x0100) !== 0;
flags 的位布局:
位 0-7: 各种标志
位 8 (0x0100): fEncrypted — 文档是否加密
flags & 0x0100:
如果位 8 为 1 → 结果非零 → isEncrypted = true
如果位 8 为 0 → 结果为零 → isEncrypted = false
2.4 加密文档的处理
if (isEncrypted) {
return "[加密文档,无法读取]";
}
| 处理方式 | 说明 |
|---|---|
| 返回提示文字 | 告诉用户文档是加密的 |
| 不尝试解密 | 解密需要密码,超出插件范围 |
| 不返回 null | 区别于"无法解析"的情况 |
💡 返回 “[加密文档,无法读取]” 而不是 null——这样调用者可以区分"文件损坏"(null)和"文件加密"(有提示文字)。
三、ccpText:文本长度
3.1 读取
const ccpText = this.readU32(wordBytes, 0x4C);
if (ccpText <= 0 || ccpText > 10000000) {
return null;
}
3.2 ccpText 的含义
ccpText = Character Count of Plain Text
即文档正文的字符数(不包括页眉页脚、脚注等)
3.3 上限检查
if (ccpText > 10000000) {
return null; // 超过 1000 万字符,可能是格式错误
}
| 检查 | 值 | 原因 |
|---|---|---|
| ccpText <= 0 | 无文本 | 空文档或格式错误 |
| ccpText > 10000000 | 超大 | 可能是格式错误导致的异常值 |
📌 10000000(一千万)是一个安全上限。正常的 Word 文档不会有这么多字符。如果 ccpText 超过这个值,说明 FIB 解析可能出了问题。
四、CLX 定位
4.1 什么是 CLX
CLX(Complex)是 Table 流中的一个结构,包含了 Piece Table——告诉我们文本的每一段存储在 WordDocument 流的什么位置、用什么编码。
4.2 fcClx 和 lcbClx
const fcClx = this.readU32(wordBytes, 0x1A2); // CLX 在 Table 流中的偏移
const lcbClx = this.readU32(wordBytes, 0x1A6); // CLX 的大小(字节)
| 字段 | 含义 | 示例 |
|---|---|---|
| fcClx | File offset of CLX | 0x1234(CLX 从 Table 流偏移 0x1234 开始) |
| lcbClx | Length of CLX in bytes | 0x100(CLX 长度 256 字节) |
4.3 有效性检查
if (tableData && fcClx > 0 && lcbClx > 0) {
// CLX 信息有效,尝试 Piece Table 提取
}
如果 fcClx 或 lcbClx 为 0,说明文档没有 CLX 结构(可能是非常旧的格式),需要用回退策略。
五、Table 流查找
5.1 代码
let tableData: Uint8Array | null = null;
let tableEntry = ole.findEntry("1Table");
if (!tableEntry) {
tableEntry = ole.findEntry("0Table");
}
if (tableEntry) {
tableData = ole.readEntryData(tableEntry);
}
5.2 1Table vs 0Table 的选择
Word 文档的 FIB 中有一个标志位 fWhichTblStm:
- fWhichTblStm = 1 → 使用 1Table
- fWhichTblStm = 0 → 使用 0Table
doc_text 的简化策略:
- 先试 1Table(大多数文档用这个)
- 找不到再试 0Table
| 策略 | 准确性 | 复杂度 |
|---|---|---|
| 读取 FIB 标志位 | 高 | 中 |
| 先 1Table 后 0Table | 高(实际上几乎所有文档用 1Table) | 低 |
六、Piece Table 提取入口
6.1 调用
if (tableData && fcClx > 0 && lcbClx > 0) {
const text = this.extractTextWithPieceTable(wordBytes, tableData, fcClx, lcbClx, ccpText);
if (text) {
return this.cleanText(text);
}
}
6.2 参数说明
| 参数 | 类型 | 来源 | 说明 |
|---|---|---|---|
| wordBytes | Uint8Array | WordDocument 流 | 文本数据在这里 |
| tableBytes | Uint8Array | 1Table/0Table 流 | Piece Table 在这里 |
| fcClx | number | FIB 偏移 0x1A2 | CLX 在 Table 流中的偏移 |
| lcbClx | number | FIB 偏移 0x1A6 | CLX 的大小 |
| ccpText | number | FIB 偏移 0x4C | 文本字符数 |
6.3 成功与回退
Piece Table 提取
│
├── 成功(text 不为 null)→ cleanText → 返回
│
└── 失败(text 为 null)→ 回退到 extractTextDirect
七、回退策略
7.1 代码
// 回退:直接提取
return this.extractTextDirect(wordBytes, ccpText);
7.2 触发条件
| 条件 | 说明 |
|---|---|
| tableData 为 null | 找不到 Table 流 |
| fcClx <= 0 | CLX 偏移无效 |
| lcbClx <= 0 | CLX 大小无效 |
| extractTextWithPieceTable 返回 null | Piece Table 解析失败 |
7.3 回退策略的价值
正常流程(Piece Table):
准确性高,能正确处理编码
回退流程(直接提取):
准确性低,但能处理一些异常格式的文档
💡 双重策略是 doc_text 的一个重要设计——先用精确方法,失败了再用暴力方法。这样可以最大化成功率。
八、完整的 .doc 文本提取流程
8.1 流程图
.doc 文件
↓
OLE2Parser → FAT + 目录
↓
findEntry("WordDocument") → wordData
findEntry("1Table") → tableData
↓
extractWordText(wordData, ole)
├── FIB 解析 → flags, ccpText, fcClx, lcbClx
├── 加密检查 → "[加密文档]" 或继续
├── Piece Table 提取
│ ├── CLX 定位 → tableData[fcClx..fcClx+lcbClx]
│ ├── 解析 piece descriptors
│ ├── 根据编码标志提取 Unicode/ANSI 文本
│ └── 成功 → cleanText → 返回
└── 直接提取(回退)
├── 多偏移量探测
├── Unicode/ANSI 双通道
└── 取最长有效文本
8.2 各步骤的数据流
| 步骤 | 输入 | 输出 | 可能失败 |
|---|---|---|---|
| OLE2 解析 | 文件字节 | FAT + 目录 | ✅ 魔数不匹配 |
| 流查找 | 目录 | wordData + tableData | ✅ 流不存在 |
| FIB 解析 | wordData | flags + ccpText + CLX | ✅ 数据不足 |
| Piece Table | wordData + tableData | 文本 | ✅ CLX 无效 |
| 直接提取 | wordData | 文本 | ✅ 无有效文本 |
总结
FIB 解析与 Piece Table 提取是 .doc 文本提取的核心:
- FIB:从 WordDocument 流头部读取 flags、ccpText、fcClx、lcbClx
- 加密检测:flags 位 8 判断,加密文档返回提示文字
- ccpText 校验:上限 1000 万字符,防止异常值
- Table 流查找:先 1Table 后 0Table
- 双重策略:Piece Table 优先,失败回退到直接提取
下一篇我们深入 Piece Table 的内部结构——CLX、CP 数组、PCD 数组、编码判断。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- MS-DOC FIB 规范
- MS-DOC Piece Table
- doc_text Gitcode 仓库
- Word 二进制格式概述
- 位运算与标志位
- Apache POI FIB 实现
- 文档加密
- 开源鸿蒙跨平台社区

Word 文档 FIB 与 Piece Table 关系
更多推荐


所有评论(0)