Skip to content

Commit 72991f7

Browse files
authored
Support for checking a Publication Profile conformance (#61)
1 parent d1a713b commit 72991f7

File tree

23 files changed

+250
-22
lines changed

23 files changed

+250
-22
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ All notable changes to this project will be documented in this file. Take a look
88

99
### Added
1010

11+
#### Shared
12+
13+
* A new `Publication.conformsTo()` API to identify the profile of a publication.
14+
* Support for the [`conformsTo` RWPM metadata](https://github.com/readium/webpub-manifest/issues/65), to identify the profile of a `Publication`.
15+
1116
#### Navigator
1217

1318
* The PDF navigator now honors the publication reading progression with support for right-to-left and horizontal scrolling.
@@ -28,6 +33,13 @@ All notable changes to this project will be documented in this file. Take a look
2833
* For example, it is called when clicking on internal links or programmatically calling `Navigator.go()`, but not when turning pages.
2934
* You can use this callback to implement a navigation history by differentiating between continuous and discontinuous moves.
3035

36+
### Deprecated
37+
38+
#### Shared
39+
40+
* `Publication.type` is now deprecated in favor of the new `Publication.conformsTo()` API which is more accurate.
41+
* For example, replace `publication.type == Publication.TYPE.EPUB` with `publication.conformsTo(Publication.Profile.EPUB)` before opening a publication with the `EpubNavigatorFragment`.
42+
3143
### Fixed
3244

3345
* Fix building with Kotlin 1.6.

readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.readium.r2.shared.toJSON
2424
import org.readium.r2.shared.util.Href
2525
import org.readium.r2.shared.util.logging.WarningLogger
2626
import org.readium.r2.shared.util.logging.log
27+
import org.readium.r2.shared.util.mediatype.MediaType
2728
import java.io.File
2829

2930
/**
@@ -43,6 +44,26 @@ data class Manifest(
4344

4445
) : JSONable, Parcelable {
4546

47+
/**
48+
* Returns whether this manifest conforms to the given Readium Web Publication Profile.
49+
*/
50+
fun conformsTo(profile: Publication.Profile): Boolean {
51+
if (readingOrder.isEmpty()) {
52+
return false
53+
}
54+
55+
return when (profile) {
56+
Publication.Profile.AUDIOBOOK -> readingOrder.allAreAudio
57+
Publication.Profile.DIVINA -> readingOrder.allAreBitmap
58+
Publication.Profile.EPUB ->
59+
// EPUB needs to be explicitly indicated in `conformsTo`, otherwise
60+
// it could be a regular Web Publication.
61+
readingOrder.allAreHtml && metadata.conformsTo.contains(Publication.Profile.EPUB)
62+
Publication.Profile.PDF -> readingOrder.allMatchMediaType(MediaType.PDF)
63+
else -> metadata.conformsTo.contains(profile)
64+
}
65+
}
66+
4667
/**
4768
* Finds the first [Link] with the given relation in the manifest's links.
4869
*/

readium/shared/src/main/java/org/readium/r2/shared/publication/Metadata.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import java.util.*
3434
data class Metadata(
3535
val identifier: String? = null, // URI
3636
val type: String? = null, // URI (@type)
37+
val conformsTo: Set<Publication.Profile> = emptySet(),
3738
val localizedTitle: LocalizedString,
3839
val localizedSubtitle: LocalizedString? = null,
3940
val localizedSortAs: LocalizedString? = null,
@@ -65,6 +66,7 @@ data class Metadata(
6566
constructor(
6667
identifier: String? = null, // URI
6768
type: String? = null, // URI (@type)
69+
conformsTo: Set<Publication.Profile> = emptySet(),
6870
localizedTitle: LocalizedString,
6971
localizedSubtitle: LocalizedString? = null,
7072
localizedSortAs: LocalizedString? = null,
@@ -96,6 +98,7 @@ data class Metadata(
9698
): this(
9799
identifier = identifier,
98100
type = type,
101+
conformsTo = conformsTo,
99102
localizedTitle = localizedTitle,
100103
localizedSubtitle = localizedSubtitle,
101104
localizedSortAs = localizedSortAs,
@@ -187,6 +190,7 @@ data class Metadata(
187190
override fun toJSON() = JSONObject(otherMetadata).apply {
188191
put("identifier", identifier)
189192
put("@type", type)
193+
putIfNotEmpty("conformsTo", conformsTo.map { it.uri })
190194
putIfNotEmpty("title", localizedTitle)
191195
putIfNotEmpty("subtitle", localizedSubtitle)
192196
put("modified", modified?.toIso8601String())
@@ -241,6 +245,9 @@ data class Metadata(
241245

242246
val identifier = json.remove("identifier") as? String
243247
val type = json.remove("@type") as? String
248+
val conformsTo = json.optStringsFromArrayOrSingle("conformsTo", remove = true)
249+
.map { Publication.Profile(it) }
250+
.toSet()
244251
val localizedSubtitle = LocalizedString.fromJSON(json.remove("subtitle"), warnings)
245252
val modified = (json.remove("modified") as? String)?.iso8601ToDate()
246253
val published = (json.remove("published") as? String)?.iso8601ToDate()
@@ -282,6 +289,7 @@ data class Metadata(
282289
return Metadata(
283290
identifier = identifier,
284291
type = type,
292+
conformsTo = conformsTo,
285293
localizedTitle = localizedTitle,
286294
localizedSubtitle = localizedSubtitle,
287295
localizedSortAs = localizedSortAs,

readium/shared/src/main/java/org/readium/r2/shared/publication/Publication.kt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
package org.readium.r2.shared.publication
88

99
import android.net.Uri
10+
import android.os.Parcelable
1011
import androidx.annotation.StringRes
1112
import kotlinx.coroutines.DelicateCoroutinesApi
1213
import kotlinx.coroutines.GlobalScope
1314
import kotlinx.coroutines.launch
15+
import kotlinx.parcelize.Parcelize
1416
import org.json.JSONObject
1517
import org.readium.r2.shared.BuildConfig.DEBUG
1618
import org.readium.r2.shared.R
@@ -105,7 +107,7 @@ class Publication(
105107

106108
val subcollections: Map<String, List<PublicationCollection>> get() = _manifest.subcollections
107109

108-
// FIXME: To be refactored, with the TYPE and EXTENSION enums as well
110+
@Deprecated("Use conformsTo() to check the kind of a publication.", level = DeprecationLevel.WARNING)
109111
var type: TYPE = when {
110112
metadata.type == "http://schema.org/Audiobook" || readingOrder.allAreAudio -> TYPE.AUDIO
111113
readingOrder.allAreBitmap -> TYPE.DiViNa
@@ -129,6 +131,12 @@ class Publication(
129131
get() = links.firstWithRel("self")
130132
?.let { it.href.toUrlOrNull()?.removeLastComponent() }
131133

134+
/**
135+
* Returns whether this publication conforms to the given Readium Web Publication Profile.
136+
*/
137+
fun conformsTo(profile: Profile): Boolean =
138+
manifest.conformsTo(profile)
139+
132140
/**
133141
* Finds the first [Link] with the given HREF in the publication's links.
134142
*
@@ -286,6 +294,26 @@ class Publication(
286294

287295
}
288296

297+
/**
298+
* Represents a Readium Web Publication Profile a [Publication] can conform to.
299+
*
300+
* For a list of supported profiles, see the registry:
301+
* https://readium.org/webpub-manifest/profiles/
302+
*/
303+
@Parcelize
304+
data class Profile(val uri: String) : Parcelable {
305+
companion object {
306+
/** Profile for EPUB publications. */
307+
val EPUB = Profile("https://readium.org/webpub-manifest/profiles/epub")
308+
/** Profile for audiobooks. */
309+
val AUDIOBOOK = Profile("https://readium.org/webpub-manifest/profiles/audiobook")
310+
/** Profile for visual narratives (comics, manga and bandes dessinées). */
311+
val DIVINA = Profile("https://readium.org/webpub-manifest/profiles/divina")
312+
/** Profile for PDF documents. */
313+
val PDF = Profile("https://readium.org/webpub-manifest/profiles/pdf")
314+
}
315+
}
316+
289317
/**
290318
* Base interface to be implemented by all publication services.
291319
*/

readium/shared/src/main/java/org/readium/r2/shared/util/mediatype/Sniffer.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,14 +204,14 @@ object Sniffers {
204204
if (manifest != null) {
205205
val isLcpProtected = context.containsArchiveEntryAt("license.lcpl")
206206

207-
if (manifest.metadata.type == "http://schema.org/Audiobook" || manifest.readingOrder.allAreAudio) {
207+
if (manifest.conformsTo(Publication.Profile.AUDIOBOOK)) {
208208
return if (isManifest) MediaType.READIUM_AUDIOBOOK_MANIFEST
209209
else (if (isLcpProtected) MediaType.LCP_PROTECTED_AUDIOBOOK else MediaType.READIUM_AUDIOBOOK)
210210
}
211-
if (manifest.readingOrder.allAreBitmap) {
211+
if (manifest.conformsTo(Publication.Profile.DIVINA)) {
212212
return if (isManifest) MediaType.DIVINA_MANIFEST else MediaType.DIVINA
213213
}
214-
if (isLcpProtected && manifest.readingOrder.allMatchMediaType(MediaType.PDF)) {
214+
if (isLcpProtected && manifest.conformsTo(Publication.Profile.PDF)) {
215215
return MediaType.LCP_PROTECTED_PDF
216216
}
217217
if (manifest.linkWithRel("self")?.mediaType?.matches("application/webpub+json") == true) {

readium/shared/src/test/java/org/readium/r2/shared/publication/LinkTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.junit.Test
1717
import org.junit.runner.RunWith
1818
import org.readium.r2.shared.assertJSONEquals
1919
import org.readium.r2.shared.toJSON
20+
import org.readium.r2.shared.util.mediatype.MediaType
2021
import org.robolectric.RobolectricTestRunner
2122

2223
@RunWith(RobolectricTestRunner::class)
@@ -249,6 +250,15 @@ class LinkTest {
249250
)
250251
}
251252

253+
@Test fun `get unknown media type`() {
254+
assertEquals(MediaType.BINARY, Link(href = "file").mediaType)
255+
}
256+
257+
@Test fun `get media type from type`() {
258+
assertEquals(MediaType.EPUB, Link(href = "file", type = "application/epub+zip").mediaType)
259+
assertEquals(MediaType.PDF, Link(href = "file", type = "application/pdf").mediaType)
260+
}
261+
252262
@Test
253263
fun `to URL relative to base URL`() {
254264
assertEquals(

readium/shared/src/test/java/org/readium/r2/shared/publication/MetadataTest.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class MetadataTest {
2929
Metadata(
3030
identifier = "1234",
3131
type = "epub",
32+
conformsTo = setOf(Publication.Profile.EPUB, Publication.Profile.PDF),
3233
localizedTitle = LocalizedString.fromStrings(mapOf(
3334
"en" to "Title",
3435
"fr" to "Titre"
@@ -76,6 +77,10 @@ class MetadataTest {
7677
Metadata.fromJSON(JSONObject("""{
7778
"identifier": "1234",
7879
"@type": "epub",
80+
"conformsTo": [
81+
"https://readium.org/webpub-manifest/profiles/epub",
82+
"https://readium.org/webpub-manifest/profiles/pdf",
83+
],
7984
"title": {"en": "Title", "fr": "Titre"},
8085
"subtitle": {"en": "Subtitle", "fr": "Sous-titre"},
8186
"modified": "2001-01-01T12:36:27.000Z",
@@ -116,6 +121,19 @@ class MetadataTest {
116121
assertNull(Metadata.fromJSON(null))
117122
}
118123

124+
@Test fun `parse JSON with single profile`() {
125+
assertEquals(
126+
Metadata(
127+
conformsTo = setOf(Publication.Profile.DIVINA),
128+
localizedTitle = LocalizedString("Title"),
129+
),
130+
Metadata.fromJSON(JSONObject("""{
131+
"title": "Title",
132+
"conformsTo": "https://readium.org/webpub-manifest/profiles/divina"
133+
}"""))
134+
)
135+
}
136+
119137
@Test fun `parse JSON with single language`() {
120138
assertEquals(
121139
Metadata(
@@ -162,6 +180,10 @@ class MetadataTest {
162180
JSONObject("""{
163181
"identifier": "1234",
164182
"@type": "epub",
183+
"conformsTo": [
184+
"https://readium.org/webpub-manifest/profiles/epub",
185+
"https://readium.org/webpub-manifest/profiles/pdf",
186+
],
165187
"title": {"en": "Title", "fr": "Titre"},
166188
"subtitle": {"en": "Subtitle", "fr": "Sous-titre"},
167189
"modified": "2001-01-01T12:36:27.000Z",
@@ -200,6 +222,7 @@ class MetadataTest {
200222
Metadata(
201223
identifier = "1234",
202224
type = "epub",
225+
conformsTo = setOf(Publication.Profile.EPUB, Publication.Profile.PDF),
203226
localizedTitle = LocalizedString.fromStrings(mapOf(
204227
"en" to "Title",
205228
"fr" to "Titre"

0 commit comments

Comments
 (0)