// RDEPUBParser+ReadingProfile.swift // EPUB 阅读配置文件判断 // 根据 metadata 的 layout 属性和 manifest/spine 内容, // 将出版物分类为 webFixedLayout(固定版式)、webInteractive(含脚本/多媒体)或 textReflowable(纯文本重排)。 import Foundation extension RDEPUBParser { /// 判断出版物的阅读配置文件 /// 优先判断固定版式,其次检测交互内容(脚本/iframe/视频等),默认为文本重排 public func readingProfile() -> RDEPUBReadingProfile { if metadata.layout == .fixed { return .webFixedLayout } return hasInteractiveContent() ? .webInteractive : .textReflowable } /// 检测出版物是否包含交互内容 /// 通过 manifest 中的脚本媒体类型和 "scripted" 属性, /// 以及扫描 HTML 中的 script/iframe/video/audio/canvas/svg/form 等标签 public func hasInteractiveContent() -> Bool { let interactiveMediaTypes = [ "application/javascript", "text/javascript", "application/ecmascript" ] if manifest.values.contains(where: { item in interactiveMediaTypes.contains(item.mediaType.lowercased()) || item.properties.contains("scripted") }) { return true } for item in spine where item.linear && (item.mediaType.contains("html") || item.mediaType.contains("xhtml")) { guard let html = htmlString(forRelativePath: item.href) else { continue } if containsInteractiveMarkup(html) { return true } } return false } /// 检测 HTML 是否包含需要走 Web 交互渲染的特征。 /// 注意:普通小说 EPUB 常用静态 SVG 包封面图,不能仅因 `` 就误判为 interactive。 private func containsInteractiveMarkup(_ html: String) -> Bool { let interactivePattern = #"<(script|iframe|video|audio|canvas|object|embed)\b|\bon(load|click|touchstart|touchend|mouseover|submit|change|input)=|hype_generated_script|swiper|webview"# if html.range(of: interactivePattern, options: [.regularExpression, .caseInsensitive]) != nil { return true } let dynamicSVGPattern = #"]*>[\s\S]*?(<(script|foreignObject|animate|animateMotion|animateTransform|set)\b|\bon(load|click|touchstart|touchend|mouseover)=)"# return html.range(of: dynamicSVGPattern, options: [.regularExpression, .caseInsensitive]) != nil } }