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

Commit

Permalink
Merge pull request #66 from readium/feature/positionList
Browse files Browse the repository at this point in the history
Add lazy-loaded positionList in Publication
  • Loading branch information
aferditamuriqi authored Sep 21, 2019
2 parents 585687f + 54f7482 commit 900ca80
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 80 deletions.
8 changes: 4 additions & 4 deletions r2-shared-swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
CA94291622BCD08C00305CDB /* ResourcesServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA94291522BCD08B00305CDB /* ResourcesServer.swift */; };
CA9A40D1221B0AA200531EA1 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9A40D0221B0AA200531EA1 /* Either.swift */; };
CA9E6BA12239823300ECF6E4 /* WP+Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9E6BA02239823300ECF6E4 /* WP+Deprecated.swift */; };
CA9E6BA4223A657900ECF6E4 /* JSONEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9E6BA3223A657900ECF6E4 /* JSONEquatable.swift */; };
CA9E6BA4223A657900ECF6E4 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9E6BA3223A657900ECF6E4 /* JSON.swift */; };
CA9E6BA9223A749900ECF6E4 /* EPUBPublication.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9E6BA8223A749900ECF6E4 /* EPUBPublication.swift */; };
CA9E6BAE223A76C600ECF6E4 /* OPDSPublication.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9E6BAD223A76C600ECF6E4 /* OPDSPublication.swift */; };
CAB88C90224E510000D36C99 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CAB88C8F224E510000D36C99 /* MobileCoreServices.framework */; };
Expand Down Expand Up @@ -139,7 +139,7 @@
CA94291522BCD08B00305CDB /* ResourcesServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesServer.swift; sourceTree = "<group>"; };
CA9A40D0221B0AA200531EA1 /* Either.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = "<group>"; };
CA9E6BA02239823300ECF6E4 /* WP+Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WP+Deprecated.swift"; sourceTree = "<group>"; };
CA9E6BA3223A657900ECF6E4 /* JSONEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONEquatable.swift; sourceTree = "<group>"; };
CA9E6BA3223A657900ECF6E4 /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
CA9E6BA6223A67D300ECF6E4 /* Publication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publication.swift; sourceTree = "<group>"; };
CA9E6BA8223A749900ECF6E4 /* EPUBPublication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPUBPublication.swift; sourceTree = "<group>"; };
CA9E6BAD223A76C600ECF6E4 /* OPDSPublication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPDSPublication.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -202,7 +202,7 @@
CA2006502225A1F300E6B3BD /* Observable.swift */,
CA9A40D0221B0AA200531EA1 /* Either.swift */,
57470F7D20ED0D1A000CDCA3 /* DownloadSession.swift */,
CA9E6BA3223A657900ECF6E4 /* JSONEquatable.swift */,
CA9E6BA3223A657900ECF6E4 /* JSON.swift */,
CA50B86D22B2A1CF003AFF24 /* R2LocalizedString.swift */,
CA94291522BCD08B00305CDB /* ResourcesServer.swift */,
CADD69E122C3B17500A4CADF /* DocumentTypes.swift */,
Expand Down Expand Up @@ -625,7 +625,7 @@
CA9E6BAE223A76C600ECF6E4 /* OPDSPublication.swift in Sources */,
CABEB3DC2215698600090B6C /* Deferred.swift in Sources */,
CA2006512225A1F300E6B3BD /* Observable.swift in Sources */,
CA9E6BA4223A657900ECF6E4 /* JSONEquatable.swift in Sources */,
CA9E6BA4223A657900ECF6E4 /* JSON.swift in Sources */,
CA2AE321221C1DCB008BD18F /* LoggerStub.swift in Sources */,
CA2AE322221C1DCB008BD18F /* Loggable.swift in Sources */,
CA9A40D1221B0AA200531EA1 /* Either.swift in Sources */,
Expand Down
10 changes: 10 additions & 0 deletions r2-shared-swift/Publication/ContentLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ public enum ReadingProgression: String {
case rtl
case ltr
case auto

/// Returns the leading Page for the reading progression.
public var leadingPage: Properties.Page {
switch self {
case .ltr, .auto:
return .left
case .rtl:
return .right
}
}
}


Expand Down
78 changes: 46 additions & 32 deletions r2-shared-swift/Publication/Locator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public struct Locator: Equatable, CustomStringConvertible, Loggable {
public var title: String?

/// One or more alternative expressions of the location.
public var locations: Locations?
public var locations: Locations

/// Textual context of the locator.
public var text: LocatorText?
public var text: LocatorText

public init(href: String, type: String, title: String? = nil, locations: Locations? = nil, text: LocatorText? = nil) {
public init(href: String, type: String, title: String? = nil, locations: Locations = .init(), text: LocatorText = .init()) {
self.href = href
self.type = type
self.title = title
Expand All @@ -52,12 +52,8 @@ public struct Locator: Equatable, CustomStringConvertible, Loggable {
self.href = href
self.type = type
self.title = json["title"] as? String
if let locations = json["locations"] {
self.locations = try Locations(json: locations)
}
if let text = json["text"] {
self.text = try LocatorText(json: text)
}
self.locations = try Locations(json: json["locations"])
self.text = try LocatorText(json: json["text"])
}

public init?(jsonString: String) throws {
Expand All @@ -74,9 +70,9 @@ public struct Locator: Equatable, CustomStringConvertible, Loggable {

public init(link: Link) {
let components = link.href.split(separator: "#", maxSplits: 1).map(String.init)
var locations: Locations?
var locations = Locations()
if components.count > 1 {
locations = Locations(fragment: String(components[1]))
locations.fragments = [String(components[1])]
}

self.init(
Expand All @@ -92,8 +88,8 @@ public struct Locator: Equatable, CustomStringConvertible, Loggable {
"href": href,
"type": type,
"title": encodeIfNotNil(title),
"locations": encodeIfNotEmpty(locations?.json),
"text": encodeIfNotEmpty(text?.json)
"locations": encodeIfNotEmpty(locations.json),
"text": encodeIfNotEmpty(text.json)
])
}

Expand All @@ -118,7 +114,10 @@ public struct LocatorText: Equatable, Loggable {
self.highlight = highlight
}

public init(json: Any) throws {
public init(json: Any?) throws {
if json == nil {
return
}
guard let json = json as? [String: Any] else {
throw JSONError.parsing(LocatorText.self)
}
Expand All @@ -137,7 +136,7 @@ public struct LocatorText: Equatable, Loggable {
}
}

public var json: [String: Any]? {
public var json: [String: Any] {
return makeJSON([
"after": encodeIfNotNil(after),
"before": encodeIfNotNil(before),
Expand All @@ -146,9 +145,6 @@ public struct LocatorText: Equatable, Loggable {
}

public var jsonString: String? {
guard let json = self.json else {
return nil
}
return serializeJSONString(json)
}

Expand All @@ -167,24 +163,35 @@ public struct LocatorText: Equatable, Loggable {
/// Location : Class that contain the different variables needed to localize a particular position
public struct Locations: Equatable, Loggable {
/// Contains one or more fragment in the resource referenced by the Locator Object.
public var fragment: String? // 1 = fragment identifier (toc, page lists, landmarks)
public var fragments: [String] = []
/// Progression in the resource expressed as a percentage.
public var progression: Double? // 2 = bookmarks
public var progression: Double?
/// Progression in the publication expressed as a percentage.
public var totalProgression: Double?
/// An index in the publication.
public var position: Int? // 3 = goto page
public var position: Int?

public init(fragment: String? = nil, progression: Double? = nil, position: Int? = nil) {
self.fragment = fragment
public init(fragments: [String] = [], progression: Double? = nil, totalProgression: Double? = nil, position: Int? = nil) {
self.fragments = fragments
self.progression = progression
self.totalProgression = totalProgression
self.position = position
}

public init(json: Any) throws {
public init(json: Any?) throws {
if json == nil {
return
}
guard let json = json as? [String: Any] else {
throw JSONError.parsing(Locations.self)
}
self.fragment = json["fragment"] as? String
var fragments = (json["fragments"] as? [String]) ?? []
if let fragment = json["fragment"] as? String {
fragments.append(fragment)
}
self.fragments = fragments
self.progression = json["progression"] as? Double
self.totalProgression = json["totalProgression"] as? Double
self.position = json["position"] as? Int
}

Expand All @@ -198,18 +205,20 @@ public struct Locations: Equatable, Loggable {
}
}

public var json: [String: Any]? {
public var isEmpty: Bool {
return json.isEmpty
}

public var json: [String: Any] {
return makeJSON([
"fragment": encodeIfNotNil(fragment),
"fragments": encodeIfNotEmpty(fragments),
"progression": encodeIfNotNil(progression),
"totalProgression": encodeIfNotNil(totalProgression),
"position": encodeIfNotNil(position)
])
}

public var jsonString: String? {
guard let json = self.json else {
return nil
}
return serializeJSONString(json)
}

Expand All @@ -223,6 +232,11 @@ public struct Locations: Equatable, Loggable {
return jsonString
}

@available(*, deprecated, message: "Use `fragments.first` instead")
public var fragment: String? {
return fragments.first
}

}


Expand Down Expand Up @@ -262,8 +276,8 @@ public class Bookmark {
public var resourceHref: String { return locator.href }
public var resourceType: String { return locator.type }
public var resourceTitle: String { return locator.title ?? "" }
public var location: Locations { return locator.locations ?? Locations() }
public var location: Locations { return locator.locations }
public var locations: Locations? { return locator.locations }
public var locatorText: LocatorText { return locator.text ?? LocatorText() }
public var locatorText: LocatorText { return locator.text }

}
25 changes: 0 additions & 25 deletions r2-shared-swift/Publication/Publication+JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,31 +143,6 @@ func parseDate(_ json: Any?) -> Date? {
}


// MARK: - JSON Serialization

func serializeJSONString(_ object: Any) -> String? {
var options: JSONSerialization.WritingOptions = []
if #available(iOS 11.0, *) {
options.insert(.sortedKeys)
}
guard let data = try? JSONSerialization.data(withJSONObject: object, options: options),
let string = String(data: data, encoding: .utf8) else
{
return nil
}

// Unescapes slashes
return string.replacingOccurrences(of: "\\/", with: "/")
}

func serializeJSONData(_ object: Any) -> Data? {
guard let string = serializeJSONString(object) else {
return nil
}
return string.data(using: .utf8)
}


/// Returns the given JSON object after removing any key with NSNull value.
/// To be used with `encodeIfX` functions for more compact serialization code.
func makeJSON(_ object: [String: Any], additional: [String: Any] = [:]) -> [String: Any] {
Expand Down
21 changes: 20 additions & 1 deletion r2-shared-swift/Publication/Publication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,25 @@ public class Publication: WebPublication, Loggable {
public var format: Format = .unknown
/// Version of the publication's format, eg. 3 for EPUB 3
public var formatVersion: String?

/// Factory used to build lazily the `positionList`.
/// By default, a parser will set this to parse the `positionList` from the publication. But the host app might want to overwrite this with a custom closure to implement for example a cache mechanism.
public var positionListFactory: (Publication) -> [Locator] = { _ in [] }

/// List of all the positions in the publication.
public lazy var positionList: [Locator] = positionListFactory(self)

/// List of all the positions in each resource, indexed by their `href`.
public lazy var positionListByResource: [String: [Locator]] = positionList
.reduce([:]) { mapping, position in
var mapping = mapping
if mapping[position.href] == nil {
mapping[position.href] = []
}
mapping[position.href]?.append(position)
return mapping
}

public var userProperties = UserProperties()

// The status of User Settings properties (enabled or disabled).
Expand All @@ -51,9 +69,10 @@ public class Publication: WebPublication, Loggable {
)
}

public init(format: Format = .unknown, formatVersion: String? = nil, context: [String] = [], metadata: Metadata, links: [Link] = [], readingOrder: [Link] = [], resources: [Link] = [], tableOfContents: [Link] = [], otherCollections: [PublicationCollection] = []) {
public init(format: Format = .unknown, formatVersion: String? = nil, positionListFactory: @escaping (Publication) -> [Locator] = { _ in [] }, context: [String] = [], metadata: Metadata, links: [Link] = [], readingOrder: [Link] = [], resources: [Link] = [], tableOfContents: [Link] = [], otherCollections: [PublicationCollection] = []) {
self.format = format
self.formatVersion = formatVersion
self.positionListFactory = positionListFactory
super.init(context: context, metadata: metadata, links: links, readingOrder: readingOrder, resources: resources, tableOfContents: tableOfContents, otherCollections: otherCollections)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Foundation
/// https://readium.org/webpub-manifest/schema/extensions/epub/metadata.schema.json
protocol EPUBMetadata {

var rendition: EPUBRendition? { get set }
var rendition: EPUBRendition { get set }

}

Expand All @@ -25,17 +25,18 @@ private let renditionKey = "rendition"

extension Metadata: EPUBMetadata {

public var rendition: EPUBRendition? {
public var rendition: EPUBRendition {
get {
do {
return try EPUBRendition(json: otherMetadata[renditionKey])
} catch {
log(.warning, error)
return nil
return EPUBRendition()
}
}
set {
if let json = newValue?.json, !json.isEmpty {
let json = newValue.json
if !json.isEmpty {
otherMetadata[renditionKey] = json
} else {
otherMetadata.removeValue(forKey: renditionKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,17 @@ public struct EPUBRendition: Equatable {

/// Indicates the condition to be met for the linked resource to be rendered within a synthetic spread.
public var spread: Spread?

public init(layout: Layout? = nil, orientation: Orientation? = nil, overflow: Overflow? = nil, spread: Spread? = nil) {
self.layout = layout
self.orientation = orientation
self.overflow = overflow
self.spread = spread
}

public init?(json: Any?) throws {
if json == nil {
return nil
public init(json: Any?) throws {
guard json != nil else {
return
}
guard let json = json as? [String: Any] else {
throw JSONError.parsing(EPUBRendition.self)
Expand All @@ -159,4 +159,12 @@ public struct EPUBRendition: Equatable {
])
}

/// Determines the layout of the given resource in this publication.
/// Default layout is reflowable.
public func layout(of link: Link) -> Layout {
return link.properties.layout
?? layout
?? .reflowable
}

}
Loading

0 comments on commit 900ca80

Please sign in to comment.