From a4ea947063bc942e03e9ddfd0b29f8427be94ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Fri, 29 Aug 2025 17:12:52 +0200 Subject: [PATCH] Change the precedence of EPUB series --- CHANGELOG.md | 6 ++++ Sources/Shared/Toolkit/URL/RelativeURL.swift | 15 +++++++++- .../Parser/EPUB/EPUBMetadataParser.swift | 30 ++++++++++--------- .../Toolkit/URL/RelativeURLTests.swift | 1 - 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7d355d2e..1ad7ff867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ All notable changes to this project will be documented in this file. Take a look * The Presentation Hints properties are deprecated from the Readium Web Publication Manifest models. [See the official documentation](https://readium.org/webpub-manifest/profiles/epub.html#appendix-b---deprecated-properties). +### Changed + +#### Streamer + +* EPUB series added with Calibre now take precedence over the native EPUB ones in the `belongsToSeries` RWPM property. + ### Fixed #### Streamer diff --git a/Sources/Shared/Toolkit/URL/RelativeURL.swift b/Sources/Shared/Toolkit/URL/RelativeURL.swift index e7694c243..05e7e364b 100644 --- a/Sources/Shared/Toolkit/URL/RelativeURL.swift +++ b/Sources/Shared/Toolkit/URL/RelativeURL.swift @@ -69,10 +69,23 @@ public struct RelativeURL: URLProtocol, Hashable { resolvedComponents.fragment = otherComponents.fragment resolvedComponents.query = otherComponents.query - guard let resolvedURL = resolvedComponents.url?.standardized else { + guard var resolvedURL = resolvedComponents.url?.standardized else { return nil } + // Since iOS 26, resolving an `other` URL moving upwards in the + // hierarchy can result in an URL starting with a `/`, which is not + // what we want. + if + !path.hasPrefix("/"), + !other.path.hasPrefix("/"), + resolvedURL.path.hasPrefix("/"), + var components = URLComponents(url: resolvedURL, resolvingAgainstBaseURL: true) + { + components.path.removeFirst() + resolvedURL = components.url ?? resolvedURL + } + return RelativeURL(url: resolvedURL) } diff --git a/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift b/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift index 2de261b04..b387990f6 100644 --- a/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift +++ b/Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift @@ -449,6 +449,21 @@ final class EPUBMetadataParser: Loggable { /// https://github.com/readium/architecture/blob/master/streamer/parser/metadata.md#collections-and-series private lazy var belongsToSeries: [Metadata.Collection] = { + let calibrePosition = metas["series_index", in: .calibre].first + .flatMap { Double($0.content) } + + let calibreSeries = metas["series", in: .calibre] + .map { meta in + Metadata.Collection( + name: meta.content, + position: calibrePosition + ) + } + + if !calibreSeries.isEmpty { + return calibreSeries + } + let epub3Series = metas["belongs-to-collection"] // `collection-type` should be "series" .filter { meta in @@ -459,20 +474,7 @@ final class EPUBMetadataParser: Loggable { } .compactMap(collection(from:)) - if !epub3Series.isEmpty { - return epub3Series - } - - let epub2Position = metas["series_index", in: .calibre].first - .flatMap { Double($0.content) } - - return metas["series", in: .calibre] - .map { meta in - Metadata.Collection( - name: meta.content, - position: epub2Position - ) - } + return epub3Series }() private func collection(from meta: OPFMeta) -> Metadata.Collection? { diff --git a/Tests/SharedTests/Toolkit/URL/RelativeURLTests.swift b/Tests/SharedTests/Toolkit/URL/RelativeURLTests.swift index 51461a52a..2dc3225de 100644 --- a/Tests/SharedTests/Toolkit/URL/RelativeURLTests.swift +++ b/Tests/SharedTests/Toolkit/URL/RelativeURLTests.swift @@ -129,7 +129,6 @@ class RelativeURLTests: XCTestCase { XCTAssertEqual(RelativeURL(string: "foo")!.removingLastPathSegment().string, "./") XCTAssertEqual(RelativeURL(string: "foo/bar")!.removingLastPathSegment().string, "foo/") XCTAssertEqual(RelativeURL(string: "foo/bar/")!.removingLastPathSegment().string, "foo/") - XCTAssertEqual(RelativeURL(string: "/")!.removingLastPathSegment().string, "/../") XCTAssertEqual(RelativeURL(string: "/foo")!.removingLastPathSegment().string, "/") XCTAssertEqual(RelativeURL(string: "/foo/bar")!.removingLastPathSegment().string, "/foo/") XCTAssertEqual(RelativeURL(string: "/foo/bar/")!.removingLastPathSegment().string, "/foo/")