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?.rd_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?.rd_nilIfEmpty != nil })?.idAssertion return RDEPUBCFIResolverResult( href: href, fileIndex: fileIndex, chapterOffset: cfi.characterOffset, fragmentID: fragmentID ) } }