Skip to content
This repository has been archived by the owner on Jul 29, 2022. It is now read-only.

Add a Publication Search Service for background search #154

Merged
merged 21 commits into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0717105
Add LocatorCollection
mickael-menu Apr 26, 2021
d0cd7a8
Add a basic search service and associated interfaces
mickael-menu Apr 27, 2021
12a4383
Fix Try to support nullable Success values
mickael-menu May 3, 2021
4ab1099
Add Publication helpers for the Search Service
mickael-menu May 3, 2021
3820c2b
Refactor SimpleSearchService
mickael-menu May 3, 2021
2bca62d
Refactor the SimpleSearchService into IcuSearchService
mickael-menu May 5, 2021
f079700
Implement search options for the IcuSearchService
mickael-menu May 6, 2021
9cd6d03
Add SuspendingCloseable
mickael-menu May 6, 2021
2fef3d9
Calculate the totalProgression for ICU search results
mickael-menu May 6, 2021
c2d0086
Compute titles for ICU search results
mickael-menu May 6, 2021
baefde2
Add a fallback NaiveSearchService when ICU components are not compati…
mickael-menu May 6, 2021
c8c8e71
Prevent cutting words when creating a search snippet
mickael-menu May 6, 2021
047390b
Update changelog
mickael-menu May 7, 2021
f83abec
Add missing Publication.searchOptions helper
mickael-menu May 7, 2021
40dde4d
Remove unused dependency
mickael-menu May 14, 2021
d80b6a3
Simplify SearchService.Options
mickael-menu May 17, 2021
6c002b1
Refactor StringSearchService to isolate search algorithms
mickael-menu May 17, 2021
283f465
Compute StringSearchService's iterator resultCount
mickael-menu May 17, 2021
98b9d7b
Simplify StringSearchService.Iterator.resultCount
mickael-menu May 19, 2021
74e8aa5
Merge branch 'develop' into feature/search
mickael-menu May 27, 2021
66650dd
Mark the Search API as alpha
mickael-menu May 27, 2021
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
Next Next commit
Add LocatorCollection
  • Loading branch information
mickael-menu committed Apr 26, 2021
commit 0717105b8159cf78a1cdca9435ce4eb99f1d9939
13 changes: 13 additions & 0 deletions r2-shared/src/main/java/org/readium/r2/shared/extensions/JSON.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ fun JSONObject.putIfNotEmpty(name: String, jsonObject: JSONObject?) {
put(name, jsonObject)
}

/**
* Maps [name] to [jsonArray], clobbering any existing name/value mapping with the same name. If
* the [JSONArray] is empty, any existing mapping for [name] is removed.
*/
fun JSONObject.putIfNotEmpty(name: String, jsonArray: JSONArray?) {
if (jsonArray == null || jsonArray.length() == 0) {
remove(name)
return
}

put(name, jsonArray)
}

/**
* Maps [name] to [jsonable] after converting it to a [JSONObject], clobbering any existing
* name/value mapping with the same name. If the [JSONObject] argument is empty, any existing mapping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ package org.readium.r2.shared.publication
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.WriteWith
import org.json.JSONArray
import org.json.JSONObject
import org.readium.r2.shared.JSONable
import org.readium.r2.shared.extensions.*
import org.readium.r2.shared.toJSON
import org.readium.r2.shared.util.logging.WarningLogger
import org.readium.r2.shared.util.logging.log

Expand Down Expand Up @@ -96,7 +98,6 @@ data class Locator(
otherLocations = json?.toMap() ?: emptyMap()
)
}

}

@Deprecated("Renamed to [fragments]", ReplaceWith("fragments"))
Expand Down Expand Up @@ -186,6 +187,12 @@ data class Locator(
)
}

fun fromJSONArray(
json: JSONArray?,
warnings: WarningLogger? = null
): List<Locator> {
return json.parseObjects { Locator.fromJSON(it as? JSONObject, warnings) }
}
}

}
Expand All @@ -204,3 +211,72 @@ fun Link.toLocator(): Locator {
)
)
}

/**
* Represents a sequential list of `Locator` objects.
*
* For example, a search result or a list of positions.
*/
@Parcelize
data class LocatorCollection(
val metadata: Metadata = Metadata(),
val links: List<Link> = emptyList(),
val locators: List<Locator> = emptyList(),
) : JSONable, Parcelable {

/**
* Holds the metadata of a `LocatorCollection`.
*
* @param numberOfItems Indicates the total number of locators in the collection.
*/
@Parcelize
data class Metadata(
val localizedTitle: LocalizedString? = null,
val numberOfItems: Int? = null,
val otherMetadata: @WriteWith<JSONParceler> Map<String, Any> = mapOf(),
) : JSONable, Parcelable {

/**
* Returns the default translation string for the [localizedTitle].
*/
val title: String? get() = localizedTitle?.string

override fun toJSON() = JSONObject(otherMetadata).apply {
putIfNotEmpty("title", localizedTitle)
putOpt("numberOfItems", numberOfItems)
}

companion object {

fun fromJSON(json: JSONObject?, warnings: WarningLogger? = null): Metadata {
json ?: return Metadata()

val localizedTitle = LocalizedString.fromJSON(json.remove("title"), warnings)
val numberOfItems = json.optPositiveInt("numberOfItems", remove = true)

return Metadata(
localizedTitle = localizedTitle,
numberOfItems = numberOfItems,
otherMetadata = json.toMap()
)
}
}
}

override fun toJSON() = JSONObject().apply {
putIfNotEmpty("metadata", metadata.toJSON())
putIfNotEmpty("links", links.toJSON())
put("locators", locators.toJSON())
}

companion object {

fun fromJSON(json: JSONObject?, warnings: WarningLogger? = null): LocatorCollection {
return LocatorCollection(
metadata = Metadata.fromJSON(json?.optJSONObject("metadata"), warnings),
links = Link.fromJSONArray(json?.optJSONArray("links"), warnings = warnings),
locators = Locator.fromJSONArray(json?.optJSONArray("locators"), warnings),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,213 @@ class LocatorTest {
}

}

class LocatorCollectionTest {

@Test fun `parse {LocatorCollection} minimal JSON`() {
assertEquals(
LocatorCollection(),
LocatorCollection.fromJSON(JSONObject("{}"))
)
}

@Test fun `parse {LocatorCollection} full JSON`() {
assertEquals(
LocatorCollection(
metadata = LocatorCollection.Metadata(
localizedTitle = LocalizedString.fromStrings(mapOf(
"en" to "Searching <riddle> in Alice in Wonderlands - Page 1",
"fr" to "Recherche <riddle> dans Alice in Wonderlands – Page 1"
)),
numberOfItems = 3,
otherMetadata = mapOf(
"extraMetadata" to "value"
)
),
links = listOf(
Link(rels = setOf("self"), href = "/978-1503222687/search?query=apple", type = "application/vnd.readium.locators+json"),
Link(rels = setOf("next"), href = "/978-1503222687/search?query=apple&page=2", type = "application/vnd.readium.locators+json"),
),
locators = listOf(
Locator(
href = "/978-1503222687/chap7.html",
type = "application/xhtml+xml",
locations = Locator.Locations(
fragments = listOf(":~:text=riddle,-yet%3F'"),
progression = 0.43
),
text = Locator.Text(
before = "'Have you guessed the ",
highlight = "riddle",
after = " yet?' the Hatter said, turning to Alice again."
)
),
Locator(
href = "/978-1503222687/chap7.html",
type = "application/xhtml+xml",
locations = Locator.Locations(
fragments = listOf(":~:text=in%20asking-,riddles"),
progression = 0.47
),
text = Locator.Text(
before = "I'm glad they've begun asking ",
highlight = "riddles",
after = ".--I believe I can guess that,"
)
)
)
),
LocatorCollection.fromJSON(JSONObject("""{
"metadata": {
"title": {
"en": "Searching <riddle> in Alice in Wonderlands - Page 1",
"fr": "Recherche <riddle> dans Alice in Wonderlands – Page 1"
},
"numberOfItems": 3,
"extraMetadata": "value"
},
"links": [
{"rel": "self", "href": "/978-1503222687/search?query=apple", "type": "application/vnd.readium.locators+json"},
{"rel": "next", "href": "/978-1503222687/search?query=apple&page=2", "type": "application/vnd.readium.locators+json"}
],
"locators": [
{
"href": "/978-1503222687/chap7.html",
"type": "application/xhtml+xml",
"locations": {
"fragments": [
":~:text=riddle,-yet%3F'"
],
"progression": 0.43
},
"text": {
"before": "'Have you guessed the ",
"highlight": "riddle",
"after": " yet?' the Hatter said, turning to Alice again."
}
},
{
"href": "/978-1503222687/chap7.html",
"type": "application/xhtml+xml",
"locations": {
"fragments": [
":~:text=in%20asking-,riddles"
],
"progression": 0.47
},
"text": {
"before": "I'm glad they've begun asking ",
"highlight": "riddles",
"after": ".--I believe I can guess that,"
}
}
]
}"""))
)
}

@Test fun `get {Locator} minimal JSON`() {
assertJSONEquals(
JSONObject("""{
"locators": []
}"""),
LocatorCollection().toJSON()
)
}

@Test fun `get {Locator} full JSON`() {
assertJSONEquals(
JSONObject("""{
"metadata": {
"title": {
"en": "Searching <riddle> in Alice in Wonderlands - Page 1",
"fr": "Recherche <riddle> dans Alice in Wonderlands – Page 1"
},
"numberOfItems": 3,
"extraMetadata": "value"
},
"links": [
{"rel": ["self"], "href": "/978-1503222687/search?query=apple", "type": "application/vnd.readium.locators+json", "templated": false},
{"rel": ["next"], "href": "/978-1503222687/search?query=apple&page=2", "type": "application/vnd.readium.locators+json", "templated": false}
],
"locators": [
{
"href": "/978-1503222687/chap7.html",
"type": "application/xhtml+xml",
"locations": {
"fragments": [
":~:text=riddle,-yet%3F'"
],
"progression": 0.43
},
"text": {
"before": "'Have you guessed the ",
"highlight": "riddle",
"after": " yet?' the Hatter said, turning to Alice again."
}
},
{
"href": "/978-1503222687/chap7.html",
"type": "application/xhtml+xml",
"locations": {
"fragments": [
":~:text=in%20asking-,riddles"
],
"progression": 0.47
},
"text": {
"before": "I'm glad they've begun asking ",
"highlight": "riddles",
"after": ".--I believe I can guess that,"
}
}
]
}"""),
LocatorCollection(
metadata = LocatorCollection.Metadata(
localizedTitle = LocalizedString.fromStrings(mapOf(
"en" to "Searching <riddle> in Alice in Wonderlands - Page 1",
"fr" to "Recherche <riddle> dans Alice in Wonderlands – Page 1"
)),
numberOfItems = 3,
otherMetadata = mapOf(
"extraMetadata" to "value"
)
),
links = listOf(
Link(rels = setOf("self"), href = "/978-1503222687/search?query=apple", type = "application/vnd.readium.locators+json"),
Link(rels = setOf("next"), href = "/978-1503222687/search?query=apple&page=2", type = "application/vnd.readium.locators+json"),
),
locators = listOf(
Locator(
href = "/978-1503222687/chap7.html",
type = "application/xhtml+xml",
locations = Locator.Locations(
fragments = listOf(":~:text=riddle,-yet%3F'"),
progression = 0.43
),
text = Locator.Text(
before = "'Have you guessed the ",
highlight = "riddle",
after = " yet?' the Hatter said, turning to Alice again."
)
),
Locator(
href = "/978-1503222687/chap7.html",
type = "application/xhtml+xml",
locations = Locator.Locations(
fragments = listOf(":~:text=in%20asking-,riddles"),
progression = 0.47
),
text = Locator.Text(
before = "I'm glad they've begun asking ",
highlight = "riddles",
after = ".--I believe I can guess that,"
)
)
)
).toJSON()
)
}

}