Epub阅读器0.0.1
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import Foundation
|
||||
import ZIPFoundation
|
||||
|
||||
extension RDEPUBParser {
|
||||
func parseContainerRootFile(at containerURL: URL) throws -> String {
|
||||
guard let parser = XMLParser(contentsOf: containerURL) else {
|
||||
throw RDEPUBParserError.invalidXML(containerURL)
|
||||
}
|
||||
|
||||
let delegate = ContainerXMLParserDelegate()
|
||||
parser.shouldProcessNamespaces = false
|
||||
parser.delegate = delegate
|
||||
|
||||
guard parser.parse() else {
|
||||
throw parser.parserError ?? RDEPUBParserError.invalidXML(containerURL)
|
||||
}
|
||||
|
||||
guard let rootFilePath = delegate.rootFilePath, !rootFilePath.isEmpty else {
|
||||
throw RDEPUBParserError.missingRootFile
|
||||
}
|
||||
|
||||
return rootFilePath
|
||||
}
|
||||
|
||||
func extractArchiveIfNeeded(epubURL: URL) throws -> URL {
|
||||
let fileManager = FileManager.default
|
||||
let extractionURL = temporaryExtractionDirectory(for: epubURL)
|
||||
|
||||
if fileManager.fileExists(atPath: extractionURL.path) {
|
||||
return extractionURL
|
||||
}
|
||||
|
||||
guard let archive = Archive(url: epubURL, accessMode: .read) else {
|
||||
throw RDEPUBParserError.archiveOpenFailed(epubURL)
|
||||
}
|
||||
|
||||
try fileManager.createDirectory(at: extractionURL, withIntermediateDirectories: true)
|
||||
|
||||
for entry in archive {
|
||||
let destinationURL = extractionURL.appendingPathComponent(entry.path)
|
||||
switch entry.type {
|
||||
case .directory:
|
||||
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true)
|
||||
case .file:
|
||||
try fileManager.createDirectory(at: destinationURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
_ = try archive.extract(entry, to: destinationURL)
|
||||
case .symlink:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return extractionURL
|
||||
}
|
||||
|
||||
func temporaryExtractionDirectory(for epubURL: URL) -> URL {
|
||||
let baseURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?
|
||||
.appendingPathComponent("ssreaderview-epub", isDirectory: true)
|
||||
?? FileManager.default.temporaryDirectory.appendingPathComponent("ssreaderview-epub", isDirectory: true)
|
||||
let fileAttributes = try? FileManager.default.attributesOfItem(atPath: epubURL.path)
|
||||
let fileSize = (fileAttributes?[.size] as? NSNumber)?.stringValue ?? "0"
|
||||
let modifiedAt = (fileAttributes?[.modificationDate] as? Date)?.timeIntervalSince1970 ?? 0
|
||||
let slug = epubURL.deletingPathExtension().lastPathComponent
|
||||
.replacingOccurrences(of: " ", with: "-")
|
||||
let signature = String(format: "%.0f", modifiedAt)
|
||||
return baseURL.appendingPathComponent("\(slug)-\(fileSize)-\(signature)", isDirectory: true)
|
||||
}
|
||||
|
||||
func reset() {
|
||||
extractionRootURL = nil
|
||||
opfURL = nil
|
||||
resetPublicationState()
|
||||
}
|
||||
|
||||
func resetPublicationState() {
|
||||
metadata = RDEPUBMetadata()
|
||||
manifest = [:]
|
||||
spine = []
|
||||
tableOfContents = []
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContainerXMLParserDelegate: NSObject, XMLParserDelegate {
|
||||
private(set) var rootFilePath: String?
|
||||
|
||||
func parser(
|
||||
_ parser: XMLParser,
|
||||
didStartElement elementName: String,
|
||||
namespaceURI: String?,
|
||||
qualifiedName qName: String?,
|
||||
attributes attributeDict: [String: String] = [:]
|
||||
) {
|
||||
let name = XMLName.localName(from: qName ?? elementName)
|
||||
guard name == "rootfile", rootFilePath == nil else {
|
||||
return
|
||||
}
|
||||
rootFilePath = attributeDict["full-path"]?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user