import Foundation public final class RDEPUBParser { public internal(set) var metadata = RDEPUBMetadata() public internal(set) var manifest: [String: RDEPUBManifestItem] = [:] public internal(set) var spine: [RDEPUBSpineItem] = [] public internal(set) var tableOfContents: [EPUBTableOfContentsItem] = [] public internal(set) var extractionRootURL: URL? public internal(set) var opfURL: URL? public var opfDirectoryURL: URL? { opfURL?.deletingLastPathComponent() } public init() {} public func makePublication() -> RDEPUBPublication { RDEPUBPublication(parser: self) } public func parse(epubURL: URL) throws { reset() let extractionURL = try extractArchiveIfNeeded(epubURL: epubURL) extractionRootURL = extractionURL let containerURL = extractionURL.appendingPathComponent("META-INF/container.xml") guard FileManager.default.fileExists(atPath: containerURL.path) else { throw RDEPUBParserError.missingContainerXML } let rootFilePath = try parseContainerRootFile(at: containerURL) let packageURL = extractionURL.appendingPathComponent(rootFilePath) guard FileManager.default.fileExists(atPath: packageURL.path) else { throw RDEPUBParserError.invalidRootFilePath(rootFilePath) } try parseOPF(at: packageURL) } public func parseOPF(at opfURL: URL) throws { resetPublicationState() guard let parser = XMLParser(contentsOf: opfURL) else { throw RDEPUBParserError.invalidXML(opfURL) } let delegate = OPFPackageParserDelegate() parser.shouldProcessNamespaces = false parser.delegate = delegate guard parser.parse() else { throw parser.parserError ?? RDEPUBParserError.invalidXML(opfURL) } let packageDocument = delegate.packageDocument() self.opfURL = opfURL self.metadata = packageDocument.metadata self.manifest = Dictionary(uniqueKeysWithValues: packageDocument.manifest.map { ($0.id, $0) }) self.spine = try buildSpine(from: packageDocument, opfURL: opfURL) self.tableOfContents = parseTOC(from: packageDocument, opfURL: opfURL) } public func parseTOC() -> [EPUBTableOfContentsItem] { tableOfContents } public func parseNavDocument(_ navURL: URL, baseURL: URL? = nil) -> [EPUBTableOfContentsItem] { parseNavDocumentItems(at: navURL, baseURL: baseURL ?? navURL.deletingLastPathComponent()) } }