- 实现EPUB阅读器搜索功能及选中注释功能 - 优化CFI模块,修复代码审查发现的11个问题 - 实现大书远距目录跳转与后台补全优化方案 - 优化设置面板与章节运行时联动 - 重构及大量改进优化
9.6 KiB
| title | type | created | baseline_commit | status | context | |
|---|---|---|---|---|---|---|
| CFI 模块 11 项问题修复 | bugfix | 2026-06-18 | 6ed3fcb |
done |
|
意图
问题: CFI 模块存在 11 个已验证的代码问题,涵盖数据一致性风险(rawValue 短路)、偏移计算错误(UTF-16/Character 混用)、健壮性缺陷(正则解析 HTML、token 宽泛匹配、转义缺失)和代码质量问题(重复扩展、线性扫描、混合错误处理)。
方法: 按 Phase 1→2→3 顺序修复全部 11 个问题,保持 API 向后兼容。高优先级问题从根源修正数据模型,中优先级加固边界处理,低优先级提取共享代码和优化性能。
边界与约束
始终:
- 保持
publicAPI 签名不变,仅修改内部实现和internal/private代码 - 所有偏移计算统一使用 UTF-16 基准(与 NSRange/NSString 一致)
- 修复必须保持现有正确行为的测试不回归
先询问:
- 是否为 #3(content path 硬编码)引入新的
contentPath参数到makeOffsetCFI公开 API - 是否为 #10(线性扫描)将
markers从[RDEPUBCFIMarker]改为含索引字典的结构
永不:
- 不引入新的外部依赖(如 libxml2、DTCoreText)
- 不改变 CFI 字符串的序列化格式
- 不修改
RDEPUBCFIMarker、RDEPUBCFITokenAnchor等 Codable 结构的字段名(保持序列化兼容)
代码映射
Sources/RDReaderView/EPUBCore/CFI/RDEPUBCFI.swift-- #1 rawValue 存储模型Sources/RDReaderView/EPUBCore/CFI/RDEPUBCFISerializer.swift-- #1 rawValue 短路序列化;#9 nilIfEmptySources/RDReaderView/EPUBCore/CFI/RDEPUBCFIGenerator.swift-- #3 content path 硬编码;#8 end offset 边界Sources/RDReaderView/EPUBCore/CFI/RDEPUBCFIDOMPathBuilder.swift-- #2 偏移混用;#5 HTML 正则;#9 nilIfEmptySources/RDReaderView/EPUBCore/CFI/RDEPUBCFIRecoveryEngine.swift-- #2 calibratedOffset;#7 token 匹配Sources/RDReaderView/EPUBCore/CFI/RDEPUBCFIResolver.swift-- #4 结构假设Sources/RDReaderView/EPUBCore/CFI/RDEPUBCFIParser.swift-- #6 text assertion 转义;#11 parent 静默失败;#9 nilIfEmptySources/RDReaderView/EPUBCore/CFI/RDEPUBCFIMap.swift-- #10 线性扫描Sources/RDReaderView/EPUBCore/CFI/RDEPUBCFIPath.swift-- #9 nilIfEmptySources/RDReaderView/EPUBCore/CFI/RDEPUBCFITextAssertion.swift-- #9 nilIfEmpty
任务与验收
Phase 1 — 高优先级(影响正确性):
RDEPUBCFI.swift+RDEPUBCFISerializer.swift-- 将rawValue从存储属性改为 computed property(基于组件实时序列化),移除 Generator 中的 rawValue="" workaround -- #1 rawValue 短路导致序列化返回过期值RDEPUBCFIDOMPathBuilder.swift-- 将tokenSamples()的 offset 从 Character 索引改为 UTF-16 索引,确保与normalizedToChapterOffsets数组下标对齐;同步修正calibratedOffset中 NSString 搜索与 Character 偏移的一致性 -- #2 UTF-16/Character 偏移混用导致 emoji/CJK 扩展字符定位错误RDEPUBCFIGenerator.swift-- 为makeOffsetCFI添加可选contentPath参数(默认仍为/4/2),makeOffsetRangeCFI透传该参数 -- #3 content path 硬编码/4/2在非标准 DOM 结构下定位失败
Phase 2 — 中优先级(影响健壮性):
RDEPUBCFIResolver.swift-- 用steps.count >= 3 ? steps[2] : steps.last替换last(where:)查找 manifest 步骤;用steps.count >= 2 ? steps[1] : nil替换dropFirst().last;将idAssertion?.isEmpty == false改为idAssertion?.nilIfEmpty != nil-- #4 Resolver 假设不健壮RDEPUBCFIDOMPathBuilder.swift-- 在正则匹配前先移除 HTML 注释<!--...-->和 CDATA<![CDATA[...]]>内容;对属性值中的>增加hasSuffix("/>")对自闭合标签的精确判断 -- #5 HTML 正则脆弱RDEPUBCFIParser.swift-- 在parseTextAssertion中实现反斜杠转义处理:用扫描替代firstIndex/lastIndex,遇到\时跳过下一个字符 -- #6 text assertion 不处理转义RDEPUBCFIRecoveryEngine.swift-- 将双向contains匹配改为前缀+后缀匹配,设置最小 token 长度阈值 ≥ 4,要求 token 与 exact 长度比在 0.3~3.0 范围内 -- #7 token 匹配过于宽泛RDEPUBCFIGenerator.swift-- 在makeOffsetRangeCFI入口添加precondition(startOffset <= endOffset)或交换两者确保 start ≤ end -- #8 反向 range 退化为点
Phase 3 — 低优先级(代码质量):
- 新建
Sources/RDReaderView/EPUBCore/CFI/RDEPUBCFIUtilities.swift-- 提取共享internal extension String { var nilIfEmpty }-- #9 四处重复的 private nilIfEmpty RDEPUBCFIMap.swift-- 添加lazy var markerByPath: [RDEPUBCFIPath: Int]索引字典,将marker(matching:)查找改为 O(1) -- #10 线性扫描性能瓶颈RDEPUBCFIParser.swift-- 将parent: try? parse(parentRaw)改为parent: try parse(parentRaw),统一抛出错误;或三者均改为try?并在RDEPUBCFIRange初始化中加注释说明容错策略 -- #11 parent 静默失败混合错误处理
验收标准:
- 含 emoji(如 😀)的文本在 CFI 恢复时定位偏移正确
- 修改 rawValue 后序列化返回新值而非旧值
makeOffsetCFI接受自定义 contentPath 时能正确定位嵌套文本节点parseTextAssertion正确解析含\[\]转义的 CFImakeOffsetRangeCFI(startOffset: 10, endOffset: 5)不会静默产生退化 rangemarker(matching:)在 500+ markers 场景下性能优于 O(n)
规范变更日志
设计说明
#1 rawValue 改为 computed property: 当前 RDEPUBCFI 同时存储 rawValue 和组件,序列化时优先返回 rawValue 导致数据不一致。改为 computed property 后,rawValue 始终由 RDEPUBCFISerializer.serialize(self) 计算得出,消除缓存过期风险。由于 RDEPUBCFI 是 struct 且 rawValue 原本就参与 Codable,需同步调整 Codable 实现。
#2 偏移统一: tokenSamples() 当前用 Array(normalizedText) 生成 Character 索引,但 normalizedToChapterOffsets 数组按 UTF-16 code unit 索引构建。修正方式:tokenSamples 改为基于 UTF-16 偏移生成 token,使用 nsSource.length 迭代而非 Swift Character 迭代。
#5 HTML 预处理: 不引入外部 HTML 解析器,而是在正则匹配前用简单的字符串操作移除注释和 CDATA:<!--...--> 和 <![CDATA[...]]>。这是最小侵入的修复,覆盖最常见的边界情况。
验证
命令:
swift build-- 预期:编译成功,无错误无警告swift test-- 预期:所有现有测试通过(如有 CFI 相关测试,需覆盖 emoji 和转义场景)
手动检查:
- 确认
RDEPUBCFIUtilities.swift中只有一个internalnilIfEmpty 扩展 - 确认
RDEPUBCFI.rawValue在 struct 定义中为 computed property 而非 stored property - 确认
tokenSamples返回的 offset 类型与normalizedToChapterOffsets索引一致(均为 UTF-16 基准)
Suggested Review Order
数据模型核心(rawValue → computed property)
-
rawValue 改为 computed property,移除短路逻辑,Codable 自定义实现
RDEPUBCFI.swift:11 -
rawValue 改为 computed property,Codable 自定义实现,init(rawValue:) 文档说明
RDEPUBCFIRange.swift:9 -
序列化移除 rawValue 短路缓存,直接从组件构建
RDEPUBCFISerializer.swift:5 -
Generator 移除 rawValue="" workaround,添加 contentPath 参数,precondition 校验
RDEPUBCFIGenerator.swift:5
偏移计算统一(UTF-16/Character 修复)
-
tokenSamples 改用 NSString UTF-16 索引替代 Character 索引
RDEPUBCFIDOMPathBuilder.swift:435 -
textMarker 改用 NSRange 搜索,searchLocation 从 String.Index 改为 Int
RDEPUBCFIDOMPathBuilder.swift:212 -
HTML 预处理:stripCommentsAndCDATA 方法
RDEPUBCFIDOMPathBuilder.swift:81
解析与健壮性修复
-
Resolver 明确取固定索引步骤,改善可读性
RDEPUBCFIResolver.swift:23 -
text assertion 转义处理:firstUnescapedIndex + 括号深度 + unescapeCFIText
RDEPUBCFIParser.swift:131 -
parent 统一为 try 抛出错误
RDEPUBCFIParser.swift:50 -
token 匹配改为前缀+后缀匹配,最小长度阈值 2,长度比 0.3~3.0
RDEPUBCFIRecoveryEngine.swift:136
代码质量与性能
-
共享 internal nilIfEmpty 扩展
RDEPUBCFIUtilities.swift:1 -
RDEPUBCFIMap 索引字典 + 自定义 Equatable/Codable
RDEPUBCFIMap.swift:11 -
外部调用方移除 rawValue workaround
RDEPUBTextIndexTable.swift:211