added iOS source code
[wl-app.git] / iOS / Pods / FolioReaderKit / Source / Models / Highlight+Helper.swift
diff --git a/iOS/Pods/FolioReaderKit/Source/Models/Highlight+Helper.swift b/iOS/Pods/FolioReaderKit/Source/Models/Highlight+Helper.swift
new file mode 100644 (file)
index 0000000..30a4198
--- /dev/null
@@ -0,0 +1,313 @@
+//
+//  Highlight+Helper.swift
+//  FolioReaderKit
+//
+//  Created by Heberti Almeida on 06/07/16.
+//  Copyright (c) 2015 Folio Reader. All rights reserved.
+//
+
+import Foundation
+import RealmSwift
+
+/**
+ HighlightStyle type, default is .Yellow.
+ */
+public enum HighlightStyle: Int {
+    case yellow
+    case green
+    case blue
+    case pink
+    case underline
+
+    public init () {
+        // Default style is `.yellow`
+        self = .yellow
+    }
+
+    /**
+     Return HighlightStyle for CSS class.
+     */
+    public static func styleForClass(_ className: String) -> HighlightStyle {
+        switch className {
+        case "highlight-yellow":    return .yellow
+        case "highlight-green":     return .green
+        case "highlight-blue":      return .blue
+        case "highlight-pink":      return .pink
+        case "highlight-underline": return .underline
+        default:                    return .yellow
+        }
+    }
+
+    /**
+     Return CSS class for HighlightStyle.
+     */
+    public static func classForStyle(_ style: Int) -> String {
+
+        let enumStyle = (HighlightStyle(rawValue: style) ?? HighlightStyle())
+        switch enumStyle {
+        case .yellow:       return "highlight-yellow"
+        case .green:        return "highlight-green"
+        case .blue:         return "highlight-blue"
+        case .pink:         return "highlight-pink"
+        case .underline:    return "highlight-underline"
+        }
+    }
+
+    /// Color components for the style
+    ///
+    /// - Returns: Tuple of all color compnonents.
+    private func colorComponents() -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
+        switch self {
+        case .yellow:       return (red: 255, green: 235, blue: 107, alpha: 0.9)
+        case .green:        return (red: 192, green: 237, blue: 114, alpha: 0.9)
+        case .blue:         return (red: 173, green: 216, blue: 255, alpha: 0.9)
+        case .pink:         return (red: 255, green: 176, blue: 202, alpha: 0.9)
+        case .underline:    return (red: 240, green: 40, blue: 20, alpha: 0.6)
+        }
+    }
+
+    /**
+     Return CSS class for HighlightStyle.
+     */
+    public static func colorForStyle(_ style: Int, nightMode: Bool = false) -> UIColor {
+        let enumStyle = (HighlightStyle(rawValue: style) ?? HighlightStyle())
+        let colors = enumStyle.colorComponents()
+        return UIColor(red: colors.red/255, green: colors.green/255, blue: colors.blue/255, alpha: (nightMode ? colors.alpha : 1))
+    }
+}
+
+/// Completion block
+public typealias Completion = (_ error: NSError?) -> ()
+
+extension Highlight {
+
+    /// Save a Highlight with completion block
+    ///
+    /// - Parameters:
+    ///   - readerConfig: Current folio reader configuration.
+    ///   - completion: Completion block.
+    public func persist(withConfiguration readerConfig: FolioReaderConfig, completion: Completion? = nil) {
+        do {
+            let realm = try Realm(configuration: readerConfig.realmConfiguration)
+            realm.beginWrite()
+            realm.add(self, update: true)
+            try realm.commitWrite()
+            completion?(nil)
+        } catch let error as NSError {
+            print("Error on persist highlight: \(error)")
+            completion?(error)
+        }
+    }
+
+    /// Remove a Highlight
+    ///
+    /// - Parameter readerConfig: Current folio reader configuration.
+    public func remove(withConfiguration readerConfig: FolioReaderConfig) {
+        do {
+            guard let realm = try? Realm(configuration: readerConfig.realmConfiguration) else {
+                return
+            }
+            try realm.write {
+                realm.delete(self)
+                try realm.commitWrite()
+            }
+        } catch let error as NSError {
+            print("Error on remove highlight: \(error)")
+        }
+    }
+
+    /// Remove a Highlight by ID
+    ///
+    /// - Parameters:
+    ///   - readerConfig: Current folio reader configuration.
+    ///   - highlightId: The ID to be removed
+    public static func removeById(withConfiguration readerConfig: FolioReaderConfig, highlightId: String) {
+        var highlight: Highlight?
+        let predicate = NSPredicate(format:"highlightId = %@", highlightId)
+
+        do {
+            let realm = try Realm(configuration: readerConfig.realmConfiguration)
+            highlight = realm.objects(Highlight.self).filter(predicate).toArray(Highlight.self).first
+            highlight?.remove(withConfiguration: readerConfig)
+        } catch let error as NSError {
+            print("Error on remove highlight by id: \(error)")
+        }
+    }
+
+    /// Update a Highlight by ID
+    ///
+    /// - Parameters:
+    ///   - readerConfig: Current folio reader configuration.
+    ///   - highlightId: The ID to be removed
+    ///   - type: The `HighlightStyle`
+    public static func updateById(withConfiguration readerConfig: FolioReaderConfig, highlightId: String, type: HighlightStyle) {
+        var highlight: Highlight?
+        let predicate = NSPredicate(format:"highlightId = %@", highlightId)
+        do {
+            let realm = try Realm(configuration: readerConfig.realmConfiguration)
+            highlight = realm.objects(Highlight.self).filter(predicate).toArray(Highlight.self).first
+            realm.beginWrite()
+
+            highlight?.type = type.hashValue
+
+            try realm.commitWrite()
+            
+        } catch let error as NSError {
+            print("Error on updateById: \(error)")
+        }
+
+    }
+
+    /// Return a list of Highlights with a given ID
+    ///
+    /// - Parameters:
+    ///   - readerConfig: Current folio reader configuration.
+    ///   - bookId: Book ID
+    ///   - page: Page number
+    /// - Returns: Return a list of Highlights
+    public static func allByBookId(withConfiguration readerConfig: FolioReaderConfig, bookId: String, andPage page: NSNumber? = nil) -> [Highlight] {
+        var highlights: [Highlight]?
+        var predicate = NSPredicate(format: "bookId = %@", bookId)
+        if let page = page {
+            predicate = NSPredicate(format: "bookId = %@ && page = %@", bookId, page)
+        }
+
+        do {
+            let realm = try Realm(configuration: readerConfig.realmConfiguration)
+            highlights = realm.objects(Highlight.self).filter(predicate).toArray(Highlight.self)
+            return (highlights ?? [])
+        } catch let error as NSError {
+            print("Error on fetch all by book Id: \(error)")
+            return []
+        }
+    }
+
+    /// Return all Highlights
+    ///
+    /// - Parameter readerConfig: - readerConfig: Current folio reader configuration.
+    /// - Returns: Return all Highlights
+    public static func all(withConfiguration readerConfig: FolioReaderConfig) -> [Highlight] {
+        var highlights: [Highlight]?
+        do {
+            let realm = try Realm(configuration: readerConfig.realmConfiguration)
+            highlights = realm.objects(Highlight.self).toArray(Highlight.self)
+            return (highlights ?? [])
+        } catch let error as NSError {
+            print("Error on fetch all: \(error)")
+            return []
+        }
+    }
+}
+
+// MARK: - HTML Methods
+
+extension Highlight {
+
+    public struct MatchingHighlight {
+        var text: String
+        var id: String
+        var startOffset: String
+        var endOffset: String
+        var bookId: String
+        var currentPage: Int
+    }
+
+    /**
+     Match a highlight on string.
+     */
+    public static func matchHighlight(_ matchingHighlight: MatchingHighlight) -> Highlight? {
+        let pattern = "<highlight id=\"\(matchingHighlight.id)\" onclick=\".*?\" class=\"(.*?)\">((.|\\s)*?)</highlight>"
+        let regex = try? NSRegularExpression(pattern: pattern, options: [])
+        let matches = regex?.matches(in: matchingHighlight.text, options: [], range: NSRange(location: 0, length: matchingHighlight.text.utf16.count))
+        let str = (matchingHighlight.text as NSString)
+
+        let mapped = matches?.map { (match) -> Highlight in
+            var contentPre = str.substring(with: NSRange(location: match.range.location-kHighlightRange, length: kHighlightRange))
+            var contentPost = str.substring(with: NSRange(location: match.range.location + match.range.length, length: kHighlightRange))
+
+            // Normalize string before save
+            contentPre = Highlight.subString(ofContent: contentPre, fromRangeOfString: ">", withPattern: "((?=[^>]*$)(.|\\s)*$)")
+            contentPost = Highlight.subString(ofContent: contentPost, fromRangeOfString: "<", withPattern: "^((.|\\s)*?)(?=<)")
+
+            let highlight = Highlight()
+            highlight.highlightId = matchingHighlight.id
+            highlight.type = HighlightStyle.styleForClass(str.substring(with: match.range(at: 1))).rawValue
+            highlight.date = Date()
+            highlight.content = Highlight.removeSentenceSpam(str.substring(with: match.range(at: 2)))
+            highlight.contentPre = Highlight.removeSentenceSpam(contentPre)
+            highlight.contentPost = Highlight.removeSentenceSpam(contentPost)
+            highlight.page = matchingHighlight.currentPage
+            highlight.bookId = matchingHighlight.bookId
+            highlight.startOffset = (Int(matchingHighlight.startOffset) ?? -1)
+            highlight.endOffset = (Int(matchingHighlight.endOffset) ?? -1)
+
+            return highlight
+        }
+
+        return mapped?.first
+    }
+
+    private static func subString(ofContent content: String, fromRangeOfString rangeString: String, withPattern pattern: String) -> String {
+        var updatedContent = content
+        if updatedContent.range(of: rangeString) != nil {
+            let regex = try? NSRegularExpression(pattern: pattern, options: [])
+            let searchString = regex?.firstMatch(in: updatedContent, options: .reportProgress, range: NSRange(location: 0, length: updatedContent.count))
+
+            if let string = searchString, (string.range.location != NSNotFound) {
+                updatedContent = (updatedContent as NSString).substring(with: string.range)
+            }
+        }
+
+        return updatedContent
+    }
+
+    /// Remove a Highlight from HTML by ID
+    ///
+    /// - Parameters:
+    ///   - page: The page containing the HTML.
+    ///   - highlightId: The ID to be removed
+    /// - Returns: The removed id
+    @discardableResult public static func removeFromHTMLById(withinPage page: FolioReaderPage?, highlightId: String) -> String? {
+        guard let currentPage = page else { return nil }
+        
+        if let removedId = currentPage.webView?.js("removeHighlightById('\(highlightId)')") {
+            return removedId
+        } else {
+            print("Error removing Highlight from page")
+            return nil
+        }
+    }
+    
+    /**
+     Remove span tag before store the highlight, this span is added on JavaScript.
+     <span class=\"sentence\"></span>
+     
+     - parameter text: Text to analise
+     - returns: Striped text
+     */
+    public static func removeSentenceSpam(_ text: String) -> String {
+        
+        // Remove from text
+        func removeFrom(_ text: String, withPattern pattern: String) -> String {
+            var locator = text
+            let regex = try? NSRegularExpression(pattern: pattern, options: [])
+            let matches = regex?.matches(in: locator, options: [], range: NSRange(location: 0, length: locator.utf16.count))
+            let str = (locator as NSString)
+            
+            var newLocator = ""
+            matches?.forEach({ (match: NSTextCheckingResult) in
+                newLocator += str.substring(with: match.range(at: 1))
+            })
+            
+            if (matches?.count > 0 && newLocator.isEmpty == false) {
+                locator = newLocator
+            }
+            
+            return locator
+        }
+        
+        let pattern = "<span class=\"sentence\">((.|\\s)*?)</span>"
+        let cleanText = removeFrom(text, withPattern: pattern)
+        return cleanText
+    }
+}