Skip to content

Align contributor and series parsing with the Kotlin toolkit #577

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 49 additions & 104 deletions Sources/Streamer/Parser/EPUB/EPUBMetadataParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ final class EPUBMetadataParser: Loggable {
otherMetadata["presentation"] = presentation.json
}

let contributors = parseContributors()
let contributorsWithRoles = findContributorElements()
.compactMap { createContributor(from: $0) }

let contributorsByRole = Dictionary(grouping: contributorsWithRoles, by: \.role)
.mapValues { $0.map(\.contributor) }

func contributorsForRole(role: String?) -> [Contributor] {
contributorsByRole[role] ?? []
}

return Metadata(
identifier: uniqueIdentifier,
Expand All @@ -47,15 +55,15 @@ final class EPUBMetadataParser: Loggable {
languages: languages,
sortAs: sortAs,
subjects: subjects,
authors: contributors.authors,
translators: contributors.translators,
editors: contributors.editors,
artists: contributors.artists,
illustrators: contributors.illustrators,
colorists: contributors.colorists,
narrators: contributors.narrators,
contributors: contributors.contributors,
publishers: contributors.publishers,
authors: contributorsForRole(role: "aut"),
translators: contributorsForRole(role: "trl"),
editors: contributorsForRole(role: "edt"),
artists: contributorsForRole(role: "art"),
illustrators: contributorsForRole(role: "ill"),
colorists: contributorsForRole(role: "clr"),
narrators: contributorsForRole(role: "nrt"),
contributors: contributorsForRole(role: nil),
publishers: contributorsForRole(role: "pbl"),
readingProgression: readingProgression,
description: description,
numberOfPages: numberOfPages,
Expand Down Expand Up @@ -389,91 +397,6 @@ final class EPUBMetadataParser: Loggable {
}
}()

/// Parse all the Contributors objects of the model (`creator`, `contributor`,
/// `publisher`) and add them to the metadata.
///
/// - Parameters:
/// - metadata: The Metadata object to fill (inout).
private func parseContributors() -> (
authors: [Contributor],
translators: [Contributor],
editors: [Contributor],
artists: [Contributor],
illustrators: [Contributor],
colorists: [Contributor],
narrators: [Contributor],
contributors: [Contributor],
publishers: [Contributor]
) {
var authors: [Contributor] = []
var translators: [Contributor] = []
var editors: [Contributor] = []
var artists: [Contributor] = []
var illustrators: [Contributor] = []
var colorists: [Contributor] = []
var narrators: [Contributor] = []
var contributors: [Contributor] = []
var publishers: [Contributor] = []

for element in findContributorElements() {
// Look up for possible meta refines for contributor's role.
let roles = element.attr("id")
.map { id in metas["role", refining: id].map(\.content) }
?? []

guard let contributor = createContributor(from: element, roles: roles) else {
continue
}
// Add the contributor to the proper property according to its `roles`
if !contributor.roles.isEmpty {
for role in contributor.roles {
switch role {
case "aut":
authors.append(contributor)
case "trl":
translators.append(contributor)
case "art":
artists.append(contributor)
case "edt":
editors.append(contributor)
case "ill":
illustrators.append(contributor)
case "clr":
colorists.append(contributor)
case "nrt":
narrators.append(contributor)
case "pbl":
publishers.append(contributor)
default:
contributors.append(contributor)
}
}
} else {
// No role, so do the branching using the element.name.
// The remaining ones go to to the contributors.
if element.tag == "creator" || element.attr("property") == "dcterms:creator" {
authors.append(contributor)
} else if element.tag == "publisher" || element.attr("property") == "dcterms:publisher" {
publishers.append(contributor)
} else {
contributors.append(contributor)
}
}
}

return (
authors: authors,
translators: translators,
editors: editors,
artists: artists,
illustrators: illustrators,
colorists: colorists,
narrators: narrators,
contributors: contributors,
publishers: publishers
)
}

/// Returns the XML elements about the contributors.
/// e.g. `<dc:publisher "property"=".." >value<\>`,
/// or `<meta property="dcterms:publisher/creator/contributor"`
Expand All @@ -484,6 +407,7 @@ final class EPUBMetadataParser: Loggable {
let contributors = metas["creator", in: .dcterms]
+ metas["publisher", in: .dcterms]
+ metas["contributor", in: .dcterms]
+ metas["narrator", in: .media]
return contributors.map(\.element)
}

Expand All @@ -494,21 +418,39 @@ final class EPUBMetadataParser: Loggable {
/// - Parameters:
/// - element: The XML element reprensenting the contributor.
/// - Returns: The newly created Contributor instance.
private func createContributor(from element: ReadiumFuzi.XMLElement, roles: [String] = []) -> Contributor? {
private func createContributor(from element: ReadiumFuzi.XMLElement) -> (role: String?, contributor: Contributor)? {
guard let name = localizedString(for: element) else {
return nil
}

var roles = roles
if let role = element.attr("role") {
roles.insert(role, at: 0)
}
let knownRoles: Set = ["aut", "trl", "edt", "pbl", "art", "ill", "clr", "nrt"]

// Look up for possible meta refines for contributor's role.
let role: String? = element.attr("id")
.map { id in metas["role", refining: id].map(\.content) }?.first
?? element.attr("role") // falls back to EPUB 2 role attribute

return Contributor(
let roles = role.map { role in knownRoles.contains(role) ? [] : [role] } ?? []

let contributor = Contributor(
name: name,
sortAs: element.attr("file-as"),
roles: roles
)

let type: String? = if element.tag == "creator" || element.attr("property") == "dcterms:creator" {
"aut"
} else if element.tag == "publisher" || element.attr("property") == "dcterms:publisher" {
"pbl"
} else if element.tag == "narrator" {
"nrt"
} else if role == nil {
nil
} else {
knownRoles.contains(role!) ? role : nil
}

return (role: type, contributor: contributor)
}

private lazy var readingProgression: ReadingProgression = {
Expand Down Expand Up @@ -548,17 +490,20 @@ final class EPUBMetadataParser: Loggable {
}
.compactMap(collection(from:))

if !epub3Series.isEmpty {
return epub3Series
}

let epub2Position = metas["series_index", in: .calibre].first
.flatMap { Double($0.content) }
let epub2Series = metas["series", in: .calibre]

return metas["series", in: .calibre]
.map { meta in
Metadata.Collection(
name: meta.content,
position: epub2Position
)
}

return epub3Series + epub2Series
}()

private func collection(from meta: OPFMeta) -> Metadata.Collection? {
Expand Down
6 changes: 1 addition & 5 deletions Support/Carthage/.xcodegen
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# XCODEGEN VERSION
2.38.0
2.43.0

# SPEC
{
Expand Down Expand Up @@ -441,13 +441,9 @@
../../Sources/Navigator/EPUB/Assets/Static/scripts
../../Sources/Navigator/EPUB/Assets/Static/scripts/.gitignore
../../Sources/Navigator/EPUB/Assets/Static/scripts/readium-fixed-wrapper-one.js
../../Sources/Navigator/EPUB/Assets/Static/scripts/readium-fixed-wrapper-one.js.map
../../Sources/Navigator/EPUB/Assets/Static/scripts/readium-fixed-wrapper-two.js
../../Sources/Navigator/EPUB/Assets/Static/scripts/readium-fixed-wrapper-two.js.map
../../Sources/Navigator/EPUB/Assets/Static/scripts/readium-fixed.js
../../Sources/Navigator/EPUB/Assets/Static/scripts/readium-fixed.js.map
../../Sources/Navigator/EPUB/Assets/Static/scripts/readium-reflowable.js
../../Sources/Navigator/EPUB/Assets/Static/scripts/readium-reflowable.js.map
../../Sources/Navigator/EPUB/CSS
../../Sources/Navigator/EPUB/CSS/CSSLayout.swift
../../Sources/Navigator/EPUB/CSS/CSSProperties.swift
Expand Down
18 changes: 18 additions & 0 deletions Support/Carthage/Readium.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,8 @@
5114D5C5742C86A35A45548E /* PBXTargetDependency */,
);
name = ReadiumAdapterLCPSQLite;
packageProductDependencies = (
);
productName = ReadiumAdapterLCPSQLite;
productReference = 53DAB92EBBB8031CA66B1E6F /* ReadiumAdapterLCPSQLite.framework */;
productType = "com.apple.product-type.framework";
Expand All @@ -2135,6 +2137,8 @@
39F2FBACF195331F2304F749 /* PBXTargetDependency */,
);
name = ReadiumStreamer;
packageProductDependencies = (
);
productName = ReadiumStreamer;
productReference = 9D2B24C3150D502382AAC939 /* ReadiumStreamer.framework */;
productType = "com.apple.product-type.framework";
Expand All @@ -2154,6 +2158,8 @@
E5CBAD29AB9C4B18E4DBDD12 /* PBXTargetDependency */,
);
name = ReadiumLCP;
packageProductDependencies = (
);
productName = ReadiumLCP;
productReference = 37120DA973F5C438B59BC014 /* ReadiumLCP.framework */;
productType = "com.apple.product-type.framework";
Expand All @@ -2172,6 +2178,8 @@
77444EEC3A2F55F018832CCB /* PBXTargetDependency */,
);
name = ReadiumAdapterGCDWebServer;
packageProductDependencies = (
);
productName = ReadiumAdapterGCDWebServer;
productReference = 031593240E2CCD681E652D7E /* ReadiumAdapterGCDWebServer.framework */;
productType = "com.apple.product-type.framework";
Expand All @@ -2189,6 +2197,8 @@
97DD2B3014EE5716A2144765 /* PBXTargetDependency */,
);
name = ReadiumShared;
packageProductDependencies = (
);
productName = ReadiumShared;
productReference = 97BC822B36D72EF548162129 /* ReadiumShared.framework */;
productType = "com.apple.product-type.framework";
Expand All @@ -2208,6 +2218,8 @@
87A07A91EEB59A5AF544D945 /* PBXTargetDependency */,
);
name = ReadiumNavigator;
packageProductDependencies = (
);
productName = ReadiumNavigator;
productReference = 9407E818636BEA4550E57F57 /* ReadiumNavigator.framework */;
productType = "com.apple.product-type.framework";
Expand All @@ -2226,6 +2238,8 @@
5D2138BC434F097D8B2074EC /* PBXTargetDependency */,
);
name = ReadiumOPDS;
packageProductDependencies = (
);
productName = ReadiumOPDS;
productReference = EC329362A0E8AC6CC018452A /* ReadiumOPDS.framework */;
productType = "com.apple.product-type.framework";
Expand All @@ -2241,6 +2255,8 @@
dependencies = (
);
name = ReadiumInternal;
packageProductDependencies = (
);
productName = ReadiumInternal;
productReference = 42FD63C2720614E558522675 /* ReadiumInternal.framework */;
productType = "com.apple.product-type.framework";
Expand All @@ -2265,6 +2281,8 @@
en,
);
mainGroup = 2C63ECC3CC1230CCA416F55F;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 54;
projectDirPath = "";
projectRoot = "";
targets = (
Expand Down
26 changes: 9 additions & 17 deletions Tests/StreamerTests/Fixtures/OPF/contributors.opf
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@
<!-- dc:contributor is by default a Contributor -->
<dc:contributor>Contributor 1</dc:contributor>

<!-- The refined roles override the tag, so Author 2 is not considered a Publisher -->
<dc:publisher id="author-2">Author 2</dc:publisher>
<dc:contributor id="author-2">Author 2</dc:contributor>
<meta refines="#author-2" property="role">aut</meta>

<dc:contributor id="author-3">Author 3</dc:contributor>
<meta refines="#author-3" property="role">aut</meta>

<dc:contributor id="translator">Translator</dc:contributor>
<meta refines="#translator" property="role">trl</meta>

Expand Down Expand Up @@ -51,16 +47,16 @@
<dc:contributor id="unknown">Unknown</dc:contributor>
<meta refines="#unknown" property="role">unknown</meta>

<!-- Contributor with several roles -->
<dc:contributor id="cameleon-1">Cameleon 1</dc:contributor>
<!-- Only the first role is used -->
<dc:contributor id="cameleon-1">Cameleon 1</dc:contributor>
<meta refines="#cameleon-1" property="role">aut</meta>
<meta refines="#cameleon-1" property="role">pbl</meta>

<!-- Without namespace prefix -->
<creator xmlns="http://purl.org/dc/elements/1.1/">Author 4</creator>
<creator xmlns="http://purl.org/dc/elements/1.1/">Author 3</creator>

<!-- With a "non-standard" namespace prefix -->
<dc-alias:creator>Author 5</dc-alias:creator>
<dc-alias:creator>Author 4</dc-alias:creator>


<!-- EPUB 3 contributors -->
Expand All @@ -72,17 +68,13 @@
<!-- dcterms:contributor is by default a Contributor -->
<meta property="dcterms:contributor">Contributor A</meta>

<!-- The refined roles override the tag, so Author B is not considered a Publisher -->
<meta id="author-b" property="dcterms:publisher">Author B</meta>
<meta refines="#author-b" property="role">aut</meta>

<meta id="author-c" property="dcterms:publisher">Author C</meta>
<meta refines="#author-c" property="role">aut</meta>
<meta id="publisher—b" property="dcterms:publisher">Publisher B</meta>
<meta refines="#publisher-b" property="role">aut</meta>

<!-- sortAs and role as attributes -->
<meta property="dcterms:contributor" opf:file-as="sorting" opf:role="ill">Illustrator A</meta>

<!-- Contributor with several roles -->
<!-- Only the first role is used -->
<meta id="cameleon-a" property="dcterms:contributor">Cameleon A</meta>
<meta refines="#cameleon-a" property="role">aut</meta>
<meta refines="#cameleon-a" property="role">pbl</meta>
Expand All @@ -94,4 +86,4 @@
<spine>
<itemref idref="titlepage"/>
</spine>
</package>
</package>
Loading