©️ OverlookArt
首页 / AppleDevelop / EPUB

EPUB

阅读 EPUB 电子书的三板斧 解包器, 解析器, 阅读器
iOS EPUB 电子书 通过RPS(Reader Parser Server)实现在线阅读,并通过RSA,AES 双重加密保证数据的安全性
阅读器采用复用页面机制(Page Reuse Mechanism)用使程序开销降到最低并保证阅读体验
使用 WebKit 框架 作为swift代码与电子书html页面交互的桥梁,并编写辅助js脚本实现翻页模式,字号,主题的切换

sequenceDiagram box 离线阅读 participant A as 阅读器 participant B as 解析器 participant C as 解包器 end A->>C: 让解包器解压缩 EPUB 文件 C-->>B: 得到解压缩后的 EPUB 文件夹 B-->>A: 得到解析后的 EPUB 数据模型 box 在线阅读 participant A1 as Reader participant B1 as Parser participant C1 as Server end rect rgb(238, 180, 185) par A1->>C1: 请求 EPUB 基础数据 Note over A1,C1: 基础数据包括 Content, Toc, CSS, 密钥 等文件 C1-->>B1: 解析网络数据 B1-->>A1: 配置基础数据 A1->>C1: 请求最后一次阅读页面数据 C1-->>A1: 获得要渲染页面标识 end end rect rgb(243, 178, 128) par 加载与渲染页面数据 A1->>C1: 请求页面数据 create actor D as Decryptor C1->>D: 解密页面内容 D-->>A1: 得到页面的原始数据 loop A1->>C1: 请求页面图片资源 C1-->>A1: 将图片资源配置到页面数据中 end A1->>C1: 请求页面的书签与笔记数据 C1-->>A1: 将书签笔记配置到页面数据中进行最终的页面渲染 end end

Epub 解包器

EPUB 文件其实是一个压缩包,使用压缩工具可将其解压,得到一个 EPUB 文件夹。

在 iOS 平台使用开源的解压缩工具库 SSZipArchive ,

 1// Unpacker.swift
 2import SSZipArchive
 3import Foundation
 4class Unpacker: NSObject {
 5    /// Epub 文件解包
 6    /// - Parameters:
 7    ///   - epubFileURL: 原文件路径
 8    ///   - unPackageURL: 解包文件路径
 9    func unPackage(epubFileURL: URL, unPackageURL: URL) -> Bool {
10        guard FileManager.default.fileExists(atPath: epubFileURL.path) else {
11            debugPrint("未找到 epub 文件", epubFileURL.path)
12            return false
13        }
14        if FileManager.default.fileExists(atPath: unPackageURL.path) {
15            debugPrint("解包路径已存在:",unPackageURL.path)
16            return true
17        }
18        return SSZipArchive.unzipFile(atPath: epubFileURL.path, toDestination: unPackageURL.path, delegate: self)
19    }
20}
21
22/// 解压文件代理方法
23extension Unpacker: SSZipArchiveDelegate {
24    /// 将要解包
25    func zipArchiveWillUnzipArchive(atPath path: String, zipInfo: unz_global_info) { }
26    
27    /// 是否解包
28    func zipArchiveShouldUnzipFile(at fileIndex: Int, totalFiles: Int, archivePath: String, fileInfo: unz_file_info) -> Bool { return true }
29    
30    /// 将要生成解包后的文件路径
31    func zipArchiveWillUnzipFile(at fileIndex: Int, totalFiles: Int, archivePath: String, fileInfo: unz_file_info) { }
32    
33    /// 解包完成生成解压后的文件夹
34    func zipArchiveDidUnzipFile(at fileIndex: Int, totalFiles: Int, archivePath: String, fileInfo: unz_file_info) { }
35    
36    /// 解包进度
37    func zipArchiveProgressEvent(_ loaded: UInt64, total: UInt64) { }
38    
39    /// 解包完成
40    func zipArchiveDidUnzipArchive(atPath path: String, zipInfo: unz_global_info, unzippedPath: String) { }
41
42    /// 解包完成
43    func zipArchiveDidUnzipFile(at fileIndex: Int, totalFiles: Int, archivePath: String, unzippedFilePath: String) { }
44}
1/-
2 |-META-INF
3  |-container.xml (该文件EPUB阅读器读取EPUB内容(content.opf)文件路径)

/META-INF/container.xml 该文件始终存在,否则就是非法的 EPUB 文件,解析器会通过该文件逐步将 EPUB 文件内容解析出来。

Epub 解析器

根据 EPUB 文件夹结构来确定解析流程

需要解析的 EPUB 文件类型都是 XML 类型, 虽然有些文件的后缀不是 xml,但里面的内容都是由 xml 标签组成。
这里使用到的 XML 解析工具是 SwiftSoup。

解析器委托代理事件

 1// Parser.swift
 2protocol ParserDelegate {
 3    
 4    /// 开始解析 epub
 5    func beginParserEpub(url: URL)
 6    
 7    /// 已解析 container
 8    func didParserContainer()
 9    
10    /// 已解析 Content
11    func didParserContent()
12    
13    /// 已解析 TOC
14    func didParserToc()
15    
16    /// 解析 epub 完成
17    func endedParserEpub()
18    
19    /// 解析 Epub 出错
20    func errorParserEpub(error: ParserError)
21}

枚举解析失败错误

 1enum ParserError: Error {
 2    /// 无效的文件
 3    case FileInvalid(message: String, fileUrl: URL?)
 4    
 5    /// 转 String 失败
 6    case ToStrFailed(message: String, data: Data?)
 7    
 8    /// 转 XML 失败
 9    case ToXMLFailed(message: String, xmlStr: String)
10    
11    /// content 内容缺失
12    case ContentLack(message: String)
13}

解析器类实现

 1// Parser.swift
 2class Parser {
 3    /// 声明解析 EPUB 数据模型
 4    var parserData = ParserData()
 5    /// 声明代理
 6    var delegate: ParserDelegate?
 7
 8    /// 开始解析 Epub解压后的 文件内容
 9    private func beginParserEpub(url: URL) throws { ··· }  
10
11    /// 解析 container 文件
12    private func parseContainer() throws { ··· }  
13
14    /// 解析 content 文件
15    private func parseContent() throws { ··· }
16
17    /// 解析 TOC 目录
18    private func parseToc() throws { ··· }
19
20
21    /// 衍生方法
22
23    /// 解析外部的 container 文件
24    public func parseContainer(data: Data) throws  -> Container { ··· }
25    /// 解析外部的 Content 文件
26    public func parseContent(data: Data) throws -> Content { ··· }
27    /// 解析外部的 TOC 目录文件
28    public func parseToc(data: Data) throws -> Toc { ··· }  
29    
30}

解析后的数据

Epub 阅读器