2017-05-27 19:43:27 +02:00
|
|
|
//
|
|
|
|
// TimelineStringUtilities.swift
|
|
|
|
// Evergreen
|
|
|
|
//
|
|
|
|
// Created by Brent Simmons on 8/31/15.
|
|
|
|
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
2017-09-17 21:22:15 +02:00
|
|
|
import Data
|
2017-09-17 21:54:08 +02:00
|
|
|
import RSParser
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
private let truncatedFeedNameCache = NSMutableDictionary()
|
|
|
|
private let truncatedTitleCache = NSMutableDictionary()
|
|
|
|
private let normalizedTextCache = NSMutableDictionary()
|
|
|
|
private let textCache = NSMutableDictionary()
|
|
|
|
private let summaryCache = NSMutableDictionary()
|
|
|
|
//private var summaryCache = [String: String]()
|
|
|
|
|
|
|
|
func timelineEmptyCaches() {
|
|
|
|
|
|
|
|
truncatedFeedNameCache.removeAllObjects()
|
|
|
|
truncatedTitleCache.removeAllObjects()
|
|
|
|
normalizedTextCache.removeAllObjects()
|
|
|
|
textCache.removeAllObjects()
|
|
|
|
summaryCache.removeAllObjects()
|
|
|
|
}
|
|
|
|
|
|
|
|
func timelineTruncatedFeedName(_ feed: Feed) -> String {
|
|
|
|
|
|
|
|
let feedName = feed.nameForDisplay
|
|
|
|
if let cachedFeedName = truncatedFeedNameCache[feedName] as? String {
|
|
|
|
return cachedFeedName
|
|
|
|
}
|
|
|
|
|
|
|
|
let maxFeedNameLength = 100
|
2017-10-05 22:09:09 +02:00
|
|
|
if feedName.count < maxFeedNameLength {
|
2017-05-27 19:43:27 +02:00
|
|
|
truncatedFeedNameCache[feedName] = feedName
|
|
|
|
return feedName
|
|
|
|
}
|
|
|
|
|
|
|
|
let s = (feedName as NSString).substring(to: maxFeedNameLength)
|
|
|
|
truncatedFeedNameCache[feedName] = s
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
func timelineTruncatedTitle(_ article: Article) -> String {
|
|
|
|
|
|
|
|
guard let title = article.title else {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if let cachedTitle = truncatedTitleCache[title] as? String {
|
|
|
|
return cachedTitle
|
|
|
|
}
|
|
|
|
|
|
|
|
var s = title.replacingOccurrences(of: "\n", with: "")
|
|
|
|
s = s.replacingOccurrences(of: "\r", with: "")
|
|
|
|
s = s.replacingOccurrences(of: "\t", with: "")
|
|
|
|
s = s.replacingOccurrences(of: "↦", with: "")
|
|
|
|
s = s.rs_stringByTrimmingWhitespace()
|
|
|
|
|
|
|
|
let maxLength = 1000
|
2017-10-05 22:09:09 +02:00
|
|
|
if s.count < maxLength {
|
2017-05-27 19:43:27 +02:00
|
|
|
truncatedTitleCache[title] = s
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
s = (s as NSString).substring(to: maxLength)
|
|
|
|
truncatedTitleCache[title] = s
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
func timelineTruncatedSummary(_ article: Article) -> String {
|
|
|
|
|
|
|
|
return timelineSummaryForArticle(article)
|
|
|
|
}
|
|
|
|
|
|
|
|
func timelineNormalizedText(_ text: String) -> String {
|
|
|
|
|
|
|
|
if text.isEmpty {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if let cachedText = normalizedTextCache[text] as? String {
|
|
|
|
return cachedText
|
|
|
|
}
|
|
|
|
|
|
|
|
var s = (text as NSString).rs_stringByTrimmingWhitespace()
|
|
|
|
s = s.rs_stringWithCollapsedWhitespace()
|
|
|
|
|
|
|
|
let result = s as String
|
|
|
|
normalizedTextCache[text] = result
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func timelineNormalizedTextTruncated(_ text: String) -> String {
|
|
|
|
|
|
|
|
if text.isEmpty {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if let cachedText = textCache[text] as? String {
|
|
|
|
return cachedText
|
|
|
|
}
|
|
|
|
|
2017-09-17 21:54:08 +02:00
|
|
|
var s: NSString = (text as NSString).rsparser_stringByDecodingHTMLEntities() as NSString
|
2017-05-27 19:43:27 +02:00
|
|
|
s = s.rs_stringByTrimmingWhitespace() as NSString
|
|
|
|
s = s.rs_stringWithCollapsedWhitespace() as NSString
|
|
|
|
|
|
|
|
let maxLength = 512
|
|
|
|
if s.length > maxLength {
|
|
|
|
s = s.substring(to: maxLength) as NSString
|
|
|
|
}
|
|
|
|
|
|
|
|
textCache[text] = String(s)
|
|
|
|
return s as String
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func timelineSummaryForArticle(_ article: Article) -> String {
|
|
|
|
|
|
|
|
guard let body = article.body else {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if let cachedBody = summaryCache[body] as? String {
|
|
|
|
return cachedBody
|
|
|
|
}
|
|
|
|
|
|
|
|
var s = body.rs_string(byStrippingHTML: 300)
|
|
|
|
s = timelineNormalizedText(s)
|
|
|
|
|
|
|
|
summaryCache[body] = s
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
private let dateFormatter: DateFormatter = {
|
|
|
|
|
|
|
|
let formatter = DateFormatter()
|
|
|
|
formatter.dateStyle = .medium
|
|
|
|
formatter.timeStyle = .none
|
|
|
|
return formatter
|
|
|
|
}()
|
|
|
|
|
|
|
|
private let timeFormatter: DateFormatter = {
|
|
|
|
|
|
|
|
let formatter = DateFormatter()
|
|
|
|
formatter.dateStyle = .none
|
|
|
|
formatter.timeStyle = .short
|
|
|
|
return formatter
|
|
|
|
}()
|
|
|
|
|
|
|
|
private var token: Int = 0
|
|
|
|
|
|
|
|
func timelineDateString(_ date: Date) -> String {
|
|
|
|
|
|
|
|
if NSCalendar.rs_dateIsToday(date) {
|
|
|
|
return timeFormatter.string(from: date)
|
|
|
|
}
|
|
|
|
|
|
|
|
return dateFormatter.string(from: date)
|
|
|
|
}
|
|
|
|
|