// RDEPUBAssetRepository.swift // EPUBCore 静态资源仓库 // 负责从资源包中加载 EPUB 渲染所需的 JS 桥接脚本和固定版式 HTML 模板, // 支持模板变量替换({{token}} 占位符)。 import Foundation /// EPUB 内置资源枚举,定义桥接脚本和固定版式模板的文件名及扩展名 enum RDEPUBAsset: String { /// JS 桥接脚本(epub-bridge.js),注入到 WebView 中实现原生-JS 通信 case bridgeScript = "epub-bridge" /// 固定版式 HTML 模板(epub-fixed-layout.html),用于渲染 pre-paginated 类型的 EPUB case fixedLayoutTemplate = "epub-fixed-layout" /// 对应的文件扩展名 var fileExtension: String { switch self { case .bridgeScript: return "js" case .fixedLayoutTemplate: return "html" } } } /// 静态资源加载器,负责从资源包中读取模板文件并执行变量替换 enum RDEPUBAssetRepository { /// 加载指定资源文件的内容,并将 {{key}} 占位符替换为对应的值 /// - Parameters: /// - asset: 要加载的资源类型 /// - replacements: 模板变量字典,key 为占位符名称,value 为替换值 /// - Returns: 替换后的文件内容字符串 static func string(for asset: RDEPUBAsset, replacements: [String: String] = [:]) -> String { guard let url = resourceBundle.url(forResource: asset.rawValue, withExtension: asset.fileExtension), var content = try? String(contentsOf: url, encoding: .utf8) else { assertionFailure("Missing EPUB asset: \(asset.rawValue).\(asset.fileExtension)") return "" } for (token, value) in replacements { content = content.replacingOccurrences(of: "{{\(token)}}", with: value) } return content } /// 获取资源所在的 Bundle(优先使用已解析的子 bundle,兜底到宿主 bundle) private static var resourceBundle: Bundle { if let bundle = resolvedBundle { return bundle } return Bundle(for: RDEPUBAssetBundleToken.self) } /// 延迟解析 RDReaderViewAssets.bundle 的位置,在宿主 bundle 和所有 framework 中查找 private static var resolvedBundle: Bundle? = { let hostBundles = [Bundle(for: RDEPUBAssetBundleToken.self), Bundle.main] + Bundle.allFrameworks + Bundle.allBundles for hostBundle in hostBundles { if let url = hostBundle.url(forResource: "RDReaderViewAssets", withExtension: "bundle"), let bundle = Bundle(url: url) { return bundle } } return nil }() } /// 用于定位当前模块 Bundle 的标记类 private final class RDEPUBAssetBundleToken {}