Phase 1 (correctness): - #1: rawValue changed from stored to computed property, eliminating stale cache risk - #2: tokenSamples/textMarker unified to UTF-16 offsets, fixing emoji/CJK positioning - #3: makeOffsetCFI now accepts optional contentPath parameter Phase 2 (robustness): - #4: Resolver uses fixed index access instead of last(where:) for manifest/spine steps - #5: HTML comments and CDATA stripped before regex matching - #6: Text assertion parser handles backslash escapes (\[ \] \) - #7: Token matching uses prefix/suffix with length ratio constraints - #8: makeOffsetRangeCFI validates startOffset <= endOffset Phase 3 (code quality): - #9: Shared internal nilIfEmpty extension in RDEPUBCFIUtilities.swift - #10: RDEPUBCFIMap has markerByPath index dictionary for O(1) lookup - #11: parseRange unified to use try (not try?) for parent parsing Review fixes: - Documented init(rawValue:) parameter is ignored (computed property) - Fixed escape unescaping to handle \\ → \ correctly - Lowered token minLength threshold from 4 to 2 Co-Authored-By: Claude <noreply@anthropic.com>
51 lines
1.6 KiB
Swift
51 lines
1.6 KiB
Swift
import Foundation
|
|
|
|
public struct RDEPUBCFIResolverResult: Equatable {
|
|
|
|
public var href: String?
|
|
|
|
public var fileIndex: Int?
|
|
|
|
public var chapterOffset: Int?
|
|
|
|
public var fragmentID: String?
|
|
|
|
public init(href: String? = nil, fileIndex: Int? = nil, chapterOffset: Int? = nil, fragmentID: String? = nil) {
|
|
self.href = href
|
|
self.fileIndex = fileIndex
|
|
self.chapterOffset = chapterOffset
|
|
self.fragmentID = fragmentID
|
|
}
|
|
}
|
|
|
|
public enum RDEPUBCFIResolver {
|
|
|
|
public static func resolve(_ cfi: RDEPUBCFI) -> RDEPUBCFIResolverResult {
|
|
|
|
// EPUB CFI spec: package path structure is /6/2[spine]/2n[manifest-id]
|
|
// Manifest step is always the 3rd step (index 2) if present, or the last step with an id assertion.
|
|
let manifestStep: RDEPUBCFIStep?
|
|
if cfi.packagePath.steps.count >= 3 {
|
|
manifestStep = cfi.packagePath.steps[2]
|
|
} else {
|
|
manifestStep = cfi.packagePath.steps.last(where: { $0.idAssertion?.nilIfEmpty != nil })
|
|
}
|
|
let href = manifestStep?.idAssertion
|
|
|
|
// Spine step is the 2nd step (index 1) in the package path
|
|
let fileIndex: Int?
|
|
if cfi.packagePath.steps.count >= 2 {
|
|
fileIndex = max((cfi.packagePath.steps[1].index / 2) - 1, 0)
|
|
} else {
|
|
fileIndex = nil
|
|
}
|
|
|
|
let fragmentID = cfi.contentPath.steps.last(where: { $0.idAssertion?.nilIfEmpty != nil })?.idAssertion
|
|
return RDEPUBCFIResolverResult(
|
|
href: href,
|
|
fileIndex: fileIndex,
|
|
chapterOffset: cfi.characterOffset,
|
|
fragmentID: fragmentID
|
|
)
|
|
}
|
|
} |