Flutter三方库适配OpenHarmony【doc_text】— 文件格式路由:.doc 与 .docx 的分流策略

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

一个 extractTextFromDoc 方法要同时处理 .doc 和 .docx 两种完全不同的格式。怎么分流?doc_text 用了最简单直接的方式------看文件扩展名。听起来不够"高级",但在这个场景下确实是最合理的选择。

一、格式判断逻辑

1.1 源码

typescript 复制代码
private async extractTextFromDoc(filePath: string): Promise<string | null> {
  try {
    if (filePath.toLowerCase().endsWith(".doc")) {
      return await this.extractTextFromOldDoc(filePath);
    } else if (filePath.toLowerCase().endsWith(".docx")) {
      return await this.extractTextFromDocx(filePath);
    }
    return null;
  } catch (e) {
    console.error("DocTextPlugin: Error extracting text", e);
    return null;
  }
}

1.2 执行流程

复制代码
filePath = "/data/storage/test.docx"
    ↓
filePath.toLowerCase() → "/data/storage/test.docx"
    ↓
endsWith(".doc") → false(注意 .docx 不匹配 .doc)
    ↓
endsWith(".docx") → true
    ↓
extractTextFromDocx(filePath)

1.3 判断顺序的重要性

typescript 复制代码
// 当前顺序:先 .doc 后 .docx
if (filePath.toLowerCase().endsWith(".doc")) {     // 1. 先检查 .doc
  // ...
} else if (filePath.toLowerCase().endsWith(".docx")) {  // 2. 再检查 .docx
  // ...
}

💡 这里有个细节"test.docx".endsWith(".doc") 返回 false ,因为 .docx 的最后四个字符是 docx,不是 doc\0。所以先检查 .doc 不会误匹配 .docx 文件。如果用 contains(".doc") 就会出问题了。

二、toLowerCase() 的防御作用

2.1 为什么需要

复制代码
用户可能传入的文件名:
- test.doc      ← 标准
- test.DOC      ← Windows 大写
- test.Doc      ← 混合大小写
- test.DOCX     ← 大写
- TEST.docx     ← 文件名大写

2.2 不加 toLowerCase 的风险

typescript 复制代码
// ❌ 不加 toLowerCase
if (filePath.endsWith(".doc")) {
  // "test.DOC" 不会匹配!
}

// ✅ 加了 toLowerCase
if (filePath.toLowerCase().endsWith(".doc")) {
  // "test.DOC" → "test.doc" → 匹配 ✅
}

2.3 性能考虑

typescript 复制代码
// 当前实现:调用了两次 toLowerCase
filePath.toLowerCase().endsWith(".doc")
filePath.toLowerCase().endsWith(".docx")

// 优化写法:只调用一次
const lowerPath = filePath.toLowerCase();
if (lowerPath.endsWith(".doc")) {
  // ...
} else if (lowerPath.endsWith(".docx")) {
  // ...
}

实际上这个优化意义不大------toLowerCase 对于一个文件路径字符串来说几乎不耗时。但如果追求代码整洁,可以提取一个局部变量。

三、不支持的格式处理

3.1 静默返回 null

typescript 复制代码
if (filePath.toLowerCase().endsWith(".doc")) {
  return await this.extractTextFromOldDoc(filePath);
} else if (filePath.toLowerCase().endsWith(".docx")) {
  return await this.extractTextFromDocx(filePath);
}
return null;  // 不支持的格式,静默返回 null

3.2 null 的传播链

复制代码
extractTextFromDoc("test.txt") → return null
    ↓
onMethodCall 中的 then 回调:
if (text) {  // null 是 falsy
  result.success(text);
} else {
  result.error("UNAVAILABLE", "Could not extract text.", null);
}
    ↓
Dart 层收到 PlatformException(code: "UNAVAILABLE")

3.3 是否应该返回更明确的错误

方案 行为 优点 缺点
当前:返回 null UNAVAILABLE 错误 简单 不知道是格式不支持还是解析失败
改进:返回特定错误 UNSUPPORTED_FORMAT 明确 多一种错误码
typescript 复制代码
// 可能的改进
if (!lowerPath.endsWith(".doc") && !lowerPath.endsWith(".docx")) {
  throw new Error("Unsupported format: " + filePath.split('.').pop());
}

📌 当前的静默返回 null 策略是合理的------调用者通过 UNAVAILABLE 错误就知道"无法提取",具体原因可以自己判断(检查文件扩展名)。

四、try-catch 顶层异常兜底

4.1 代码

typescript 复制代码
private async extractTextFromDoc(filePath: string): Promise<string | null> {
  try {
    // 所有解析逻辑
  } catch (e) {
    console.error("DocTextPlugin: Error extracting text", e);
    return null;
  }
}

4.2 这个 try-catch 能捕获什么

异常来源 示例 能否捕获
文件不存在 fs.openSync 失败
权限不足 无读取权限
格式损坏 OLE2 魔数不匹配
内存不足 超大文件
ZIP 解压失败 docx 文件损坏
正则匹配异常 极端输入

4.3 异常处理策略

复制代码
extractTextFromDoc
├── try
│   ├── extractTextFromOldDoc
│   │   ├── 内部有自己的 try-catch → 返回 null
│   │   └── 未捕获的异常 → 冒泡到顶层 catch
│   └── extractTextFromDocx
│       ├── 内部有自己的 try-catch → 返回 null
│       └── 未捕获的异常 → 冒泡到顶层 catch
└── catch
    └── 记录日志 + 返回 null

4.4 双重 try-catch 的设计

typescript 复制代码
// 顶层
private async extractTextFromDoc(filePath: string): Promise<string | null> {
  try {
    if (filePath.toLowerCase().endsWith(".docx")) {
      return await this.extractTextFromDocx(filePath);  // 内部也有 try-catch
    }
  } catch (e) {
    console.error("DocTextPlugin: Error extracting text", e);
    return null;
  }
}

// 内层
private async extractTextFromDocx(filePath: string): Promise<string | null> {
  try {
    // 解析逻辑
  } catch (e) {
    console.error("DocTextPlugin: Error parsing docx", e);
    return null;  // 内层捕获,返回 null
  }
}

内层 catch 处理已知的解析错误,顶层 catch 兜底处理所有未预期的异常。这是防御性编程的典型模式。

五、与 Android 端格式判断的对比

5.1 Android 端

java 复制代码
// Android 端也是通过扩展名判断
if (filePath.endsWith(".doc")) {
    HWPFDocument doc = new HWPFDocument(new FileInputStream(filePath));
    // ...
} else if (filePath.endsWith(".docx")) {
    XWPFDocument docx = new XWPFDocument(new FileInputStream(filePath));
    // ...
}

5.2 POI 的自动检测

java 复制代码
// POI 其实可以自动检测格式
// 但 doc_text 没有用这个功能
OPCPackage pkg = OPCPackage.open(filePath);  // 自动判断 ZIP 还是 OLE2

5.3 对比

维度 doc_text (OpenHarmony) doc_text (Android)
判断方式 扩展名 扩展名
大小写处理 toLowerCase() 取决于实现
不支持格式 返回 null 返回 null
异常兜底 try-catch try-catch

两端的格式判断逻辑基本一致,这保证了行为的一致性。

六、魔数检测 vs 扩展名检测

6.1 两种方案对比

typescript 复制代码
// 方案1:扩展名检测(当前实现)
if (filePath.toLowerCase().endsWith(".doc")) { ... }

// 方案2:魔数检测(更准确)
const bytes = readFileBytes(filePath);
if (bytes[0] === 0xD0 && bytes[1] === 0xCF) {
  // OLE2 格式 (.doc)
} else if (bytes[0] === 0x50 && bytes[1] === 0x4B) {
  // ZIP 格式 (.docx)
}
方案 准确性 性能 复杂度
扩展名 中(可能被改名) 高(不读文件)
魔数 高(看实际内容) 低(需要读文件)

6.2 为什么扩展名检测够用

复制代码
实际场景中:
1. 用户从文件选择器选文件 → 扩展名正确
2. 从网络下载的文件 → 扩展名通常正确
3. 程序生成的文件 → 扩展名正确

扩展名不正确的场景:
1. 用户手动改了扩展名 → 极少见
2. 文件传输过程中扩展名丢失 → 极少见

📌 doc_text 的 OLE2Parser 内部其实做了魔数验证------如果一个 .doc 文件的魔数不对,OLE2Parser 会返回 null。所以即使扩展名判断错了,也不会产生错误结果,只是返回 null。

七、extractTextFromDocx 和 extractTextFromOldDoc 的入口

7.1 两个方法的签名

typescript 复制代码
// .docx 解析入口
private async extractTextFromDocx(filePath: string): Promise<string | null>

// .doc 解析入口
private async extractTextFromOldDoc(filePath: string): Promise<string | null>

7.2 统一的返回值

返回值 含义
string 成功提取的文本
null 无法提取(格式错误、文件损坏、加密等)

两个方法都返回 Promise<string | null>,调用者不需要关心具体是哪种格式------统一的接口屏蔽了格式差异。

7.3 方法命名

typescript 复制代码
extractTextFromDocx    // docx → 新格式
extractTextFromOldDoc  // OldDoc → 旧格式(.doc)

OldDoc 这个命名很直观------.doc 确实是"旧的" Word 格式。

八、完整的格式路由流程图

8.1 流程图

总结

doc_text 的文件格式路由设计简单实用:

  1. 扩展名判断:toLowerCase().endsWith() 覆盖大小写
  2. 两条路径:.doc → OLE2 解析,.docx → ZIP+XML 解析
  3. 静默处理:不支持的格式返回 null
  4. 双重 try-catch:内层处理已知错误,外层兜底未预期异常
  5. 统一返回值:Promise<string | null> 屏蔽格式差异

下一篇我们深入 .docx 解析------从 ZIP 解压到 XML 文本提取的完整流程。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— FlutterPlugin 与 AbilityAware 双接口实现
flutter·harmonyos
LawrenceLan2 小时前
31.Flutter 零基础入门(三十一):Stack 与 Positioned —— 悬浮、角标与覆盖布局
开发语言·前端·flutter·dart
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— openLink API 与浏览器启动策略
flutter
lili-felicity2 小时前
基础入门 Flutter for OpenHarmony:第三方库实战 cryptography_flutter 加密解密详解
flutter
lqj_本人2 小时前
Flutter三方库适配OpenHarmony【apple_product_name】构建设备信息展示页面
flutter
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— 深度链接(Deep Link)机制全解析
flutter
星空22232 小时前
鸿蒙跨平台实战day49:React Native在OpenHarmony上的Font字体降级策略详解
react native·华为·harmonyos
松叶似针2 小时前
Flutter三方库适配OpenHarmony【doc_text】— FlutterPlugin 接口实现与 MethodChannel 注册
flutter·harmonyos
lqj_本人2 小时前
Flutter三方库适配OpenHarmony【apple_product_name】插件架构设计解析
flutter