Switch to new Parser.

This commit is contained in:
Brent Simmons 2024-11-15 22:59:51 -08:00
parent c3f063ae4a
commit 85d1a8fe7a
79 changed files with 187 additions and 219 deletions

View File

@ -19,7 +19,7 @@ let package = Package(
.package(path: "../SyncDatabase"), .package(path: "../SyncDatabase"),
.package(path: "../RSCore"), .package(path: "../RSCore"),
.package(path: "../RSDatabase"), .package(path: "../RSDatabase"),
.package(path: "../RSParser"), .package(path: "../Parser"),
], ],
targets: [ targets: [
.target( .target(
@ -27,7 +27,7 @@ let package = Package(
dependencies: [ dependencies: [
"RSCore", "RSCore",
"RSDatabase", "RSDatabase",
"RSParser", "Parser",
"RSWeb", "RSWeb",
"Articles", "Articles",
"ArticlesDatabase", "ArticlesDatabase",

View File

@ -13,7 +13,7 @@ import UIKit
import Foundation import Foundation
import RSCore import RSCore
import Articles import Articles
import RSParser import Parser
import RSDatabase import RSDatabase
import ArticlesDatabase import ArticlesDatabase
import RSWeb import RSWeb
@ -484,14 +484,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
delegate.accountWillBeDeleted(self) delegate.accountWillBeDeleted(self)
} }
func addOPMLItems(_ items: [RSOPMLItem]) { func addOPMLItems(_ items: [OPMLItem]) {
for item in items { for item in items {
if let feedSpecifier = item.feedSpecifier { if let feedSpecifier = item.feedSpecifier {
addFeed(newFeed(with: feedSpecifier)) addFeed(newFeed(with: feedSpecifier))
} else { } else {
if let title = item.titleFromAttributes, let folder = ensureFolder(with: title) { if let title = item.titleFromAttributes, let folder = ensureFolder(with: title) {
folder.externalID = item.attributes?["nnw_externalID"] as? String folder.externalID = item.attributes?["nnw_externalID"] as? String
item.children?.forEach { itemChild in item.items?.forEach { itemChild in
if let feedSpecifier = itemChild.feedSpecifier { if let feedSpecifier = itemChild.feedSpecifier {
folder.addFeed(newFeed(with: feedSpecifier)) folder.addFeed(newFeed(with: feedSpecifier))
} }
@ -501,7 +501,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
} }
func loadOPMLItems(_ items: [RSOPMLItem]) { func loadOPMLItems(_ items: [OPMLItem]) {
addOPMLItems(OPMLNormalizer.normalize(items)) addOPMLItems(OPMLNormalizer.normalize(items))
} }
@ -567,7 +567,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return folders?.first(where: { $0.externalID == externalID }) return folders?.first(where: { $0.externalID == externalID })
} }
func newFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed { func newFeed(with opmlFeedSpecifier: OPMLFeedSpecifier) -> Feed {
let feedURL = opmlFeedSpecifier.feedURL let feedURL = opmlFeedSpecifier.feedURL
let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL) let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL)
let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata) let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)

View File

@ -12,7 +12,7 @@ import SystemConfiguration
import os.log import os.log
import SyncDatabase import SyncDatabase
import RSCore import RSCore
import RSParser import Parser
import Articles import Articles
import ArticlesDatabase import ArticlesDatabase
import RSWeb import RSWeb
@ -147,21 +147,14 @@ final class CloudKitAccountDelegate: AccountDelegate {
} }
let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData)
var opmlDocument: RSOPMLDocument? let opmlDocument = OPMLParser.document(with: parserData)
do {
opmlDocument = try RSOPMLParser.parseOPML(with: parserData)
} catch {
completion(.failure(error))
return
}
guard let loadDocument = opmlDocument else { guard let loadDocument = opmlDocument else {
completion(.success(())) completion(.success(()))
return return
} }
guard let opmlItems = loadDocument.children, let rootExternalID = account.externalID else { guard let opmlItems = loadDocument.items, let rootExternalID = account.externalID else {
return return
} }

View File

@ -10,7 +10,7 @@ import Foundation
import os.log import os.log
import RSCore import RSCore
import RSWeb import RSWeb
import RSParser import Parser
import CloudKit import CloudKit
enum CloudKitAccountZoneError: LocalizedError { enum CloudKitAccountZoneError: LocalizedError {
@ -55,11 +55,11 @@ final class CloudKitAccountZone: CloudKitZone {
migrateChangeToken() migrateChangeToken()
} }
func importOPML(rootExternalID: String, items: [RSOPMLItem], completion: @escaping (Result<Void, Error>) -> Void) { func importOPML(rootExternalID: String, items: [OPMLItem], completion: @escaping (Result<Void, Error>) -> Void) {
var records = [CKRecord]() var records = [CKRecord]()
var feedRecords = [String: CKRecord]() var feedRecords = [String: CKRecord]()
func processFeed(feedSpecifier: RSOPMLFeedSpecifier, containerExternalID: String) { func processFeed(feedSpecifier: OPMLFeedSpecifier, containerExternalID: String) {
if let feedRecord = feedRecords[feedSpecifier.feedURL], var containerExternalIDs = feedRecord[CloudKitFeed.Fields.containerExternalIDs] as? [String] { if let feedRecord = feedRecords[feedSpecifier.feedURL], var containerExternalIDs = feedRecord[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
containerExternalIDs.append(containerExternalID) containerExternalIDs.append(containerExternalID)
feedRecord[CloudKitFeed.Fields.containerExternalIDs] = containerExternalIDs feedRecord[CloudKitFeed.Fields.containerExternalIDs] = containerExternalIDs
@ -77,7 +77,7 @@ final class CloudKitAccountZone: CloudKitZone {
if let title = item.titleFromAttributes { if let title = item.titleFromAttributes {
let containerRecord = newContainerCKRecord(name: title) let containerRecord = newContainerCKRecord(name: title)
records.append(containerRecord) records.append(containerRecord)
item.children?.forEach { itemChild in item.items?.forEach { itemChild in
if let feedSpecifier = itemChild.feedSpecifier { if let feedSpecifier = itemChild.feedSpecifier {
processFeed(feedSpecifier: feedSpecifier, containerExternalID: containerRecord.externalID) processFeed(feedSpecifier: feedSpecifier, containerExternalID: containerRecord.externalID)
} }
@ -344,7 +344,7 @@ final class CloudKitAccountZone: CloudKitZone {
private extension CloudKitAccountZone { private extension CloudKitAccountZone {
func newFeedCKRecord(feedSpecifier: RSOPMLFeedSpecifier, containerExternalID: String) -> CKRecord { func newFeedCKRecord(feedSpecifier: OPMLFeedSpecifier, containerExternalID: String) -> CKRecord {
let record = CKRecord(recordType: CloudKitFeed.recordType, recordID: generateRecordID()) let record = CKRecord(recordType: CloudKitFeed.recordType, recordID: generateRecordID())
record[CloudKitFeed.Fields.url] = feedSpecifier.feedURL record[CloudKitFeed.Fields.url] = feedSpecifier.feedURL
if let editedName = feedSpecifier.title { if let editedName = feedSpecifier.title {

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
import os.log import os.log
import RSCore import RSCore
import RSParser import Parser
import RSWeb import RSWeb
import CloudKit import CloudKit
import Articles import Articles

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
import os.log import os.log
import RSCore import RSCore
import RSParser import Parser
import RSWeb import RSWeb
import CloudKit import CloudKit
import SyncDatabase import SyncDatabase

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import Articles import Articles
import RSParser import Parser
public extension Notification.Name { public extension Notification.Name {
static let FeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification") static let FeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification")

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import RSWeb import RSWeb
import RSCore import RSCore
@ -44,7 +44,7 @@ class FeedFinder {
return return
} }
if FeedFinder.isFeed(data, url.absoluteString) { if FeedFinder.isFeed(data) {
let feedSpecifier = FeedSpecifier(title: nil, urlString: url.absoluteString, source: .UserEntered, orderFound: 1) let feedSpecifier = FeedSpecifier(title: nil, urlString: url.absoluteString, source: .UserEntered, orderFound: 1)
completion(.success(Set([feedSpecifier]))) completion(.success(Set([feedSpecifier])))
return return
@ -149,7 +149,7 @@ private extension FeedFinder {
group.enter() group.enter()
downloadUsingCache(url) { (data, response, error) in downloadUsingCache(url) { (data, response, error) in
if let data = data, let response = response, response.statusIsOK, error == nil { if let data = data, let response = response, response.statusIsOK, error == nil {
if self.isFeed(data, downloadFeedSpecifier.urlString) { if self.isFeed(data) {
addFeedSpecifier(downloadFeedSpecifier, feedSpecifiers: &resultFeedSpecifiers) addFeedSpecifier(downloadFeedSpecifier, feedSpecifiers: &resultFeedSpecifiers)
} }
} }
@ -163,8 +163,7 @@ private extension FeedFinder {
} }
} }
static func isFeed(_ data: Data, _ urlString: String) -> Bool { static func isFeed(_ data: Data) -> Bool {
let parserData = ParserData(url: urlString, data: data) return FeedParser.canParse(data)
return FeedParser.canParse(parserData)
} }
} }

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
private let feedURLWordsToMatch = ["feed", "xml", "rss", "atom", "json"] private let feedURLWordsToMatch = ["feed", "xml", "rss", "atom", "json"]
@ -20,18 +20,20 @@ class HTMLFeedFinder {
private var feedSpecifiersDictionary = [String: FeedSpecifier]() private var feedSpecifiersDictionary = [String: FeedSpecifier]()
init(parserData: ParserData) { init(parserData: ParserData) {
let metadata = RSHTMLMetadataParser.htmlMetadata(with: parserData) let metadata = HTMLMetadataParser.metadata(with: parserData)
var orderFound = 0 var orderFound = 0
for oneFeedLink in metadata.feedLinks { if let feedLinks = metadata.feedLinks {
for oneFeedLink in feedLinks {
if let oneURLString = oneFeedLink.urlString?.normalizedURL { if let oneURLString = oneFeedLink.urlString?.normalizedURL {
orderFound = orderFound + 1 orderFound = orderFound + 1
let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .HTMLHead, orderFound: orderFound) let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .HTMLHead, orderFound: orderFound)
addFeedSpecifier(oneFeedSpecifier) addFeedSpecifier(oneFeedSpecifier)
} }
} }
}
let bodyLinks = RSHTMLLinkParser.htmlLinks(with: parserData) let bodyLinks = HTMLLinkParser.htmlLinks(with: parserData)
for oneBodyLink in bodyLinks { for oneBodyLink in bodyLinks {
if linkMightBeFeed(oneBodyLink), let normalizedURL = oneBodyLink.urlString?.normalizedURL { if linkMightBeFeed(oneBodyLink), let normalizedURL = oneBodyLink.urlString?.normalizedURL {
orderFound = orderFound + 1 orderFound = orderFound + 1
@ -69,7 +71,7 @@ private extension HTMLFeedFinder {
return false return false
} }
func linkMightBeFeed(_ link: RSHTMLLink) -> Bool { func linkMightBeFeed(_ link: HTMLLink) -> Bool {
if let linkURLString = link.urlString, urlStringMightBeFeed(linkURLString) { if let linkURLString = link.urlString, urlStringMightBeFeed(linkURLString) {
return true return true
} }

View File

@ -6,10 +6,11 @@
// Copyright © 2019 Ranchero Software, LLC. All rights reserved. // Copyright © 2019 Ranchero Software, LLC. All rights reserved.
// //
import Foundation
import Articles import Articles
import RSCore import RSCore
import RSDatabase import RSDatabase
import RSParser import Parser
import RSWeb import RSWeb
import SyncDatabase import SyncDatabase
import os.log import os.log

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import RSCore import RSCore
final class FeedbinEntry: Decodable { final class FeedbinEntry: Decodable {
@ -29,7 +29,7 @@ final class FeedbinEntry: Decodable {
// and letting the one date fail when parsed. // and letting the one date fail when parsed.
lazy var parsedDatePublished: Date? = { lazy var parsedDatePublished: Date? = {
if let datePublished = datePublished { if let datePublished = datePublished {
return RSDateWithString(datePublished) return DateParser.date(string: datePublished)
} }
else { else {
return nil return nil

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import RSCore import RSCore
import RSParser import Parser
struct FeedbinSubscription: Hashable, Codable { struct FeedbinSubscription: Hashable, Codable {

View File

@ -6,9 +6,10 @@
// Copyright © 2019 Ranchero Software, LLC. All rights reserved. // Copyright © 2019 Ranchero Software, LLC. All rights reserved.
// //
import Foundation
import Articles import Articles
import RSCore import RSCore
import RSParser import Parser
import RSWeb import RSWeb
import SyncDatabase import SyncDatabase
import os.log import os.log

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import Articles import Articles
import RSParser import Parser
struct FeedlyEntryParser { struct FeedlyEntryParser {
let entry: FeedlyEntry let entry: FeedlyEntry

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import os.log import os.log
import RSParser import Parser
/// Get full entries for the entry identifiers. /// Get full entries for the entry identifiers.
final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding { final class FeedlyGetEntriesOperation: FeedlyOperation, FeedlyEntryProviding, FeedlyParsedItemProviding {

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import os.log import os.log
protocol FeedlyEntryProviding { protocol FeedlyEntryProviding {

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import os.log import os.log
import RSParser import Parser
import SyncDatabase import SyncDatabase
import Secrets import Secrets

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import os.log import os.log
protocol FeedlyParsedItemsByFeedProviding { protocol FeedlyParsedItemsByFeedProviding {

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import os.log import os.log
import RSParser import Parser
import RSCore import RSCore
import RSWeb import RSWeb
import Secrets import Secrets

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import os.log import os.log
/// Combine the articles with their feeds for a specific account. /// Combine the articles with their feeds for a specific account.

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import RSWeb import RSWeb
struct InitialFeedDownloader { struct InitialFeedDownloader {
@ -20,10 +20,12 @@ struct InitialFeedDownloader {
return return
} }
let parserData = ParserData(url: url.absoluteString, data: data) Task.detached {
FeedParser.parse(parserData) { (parsedFeed, error) in let parsedFeed = try? FeedParser.parse(urlString: url.absoluteString, data: data)
Task { @MainActor in
completion(parsedFeed) completion(parsedFeed)
} }
} }
} }
} }
}

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
import os.log import os.log
import RSCore import RSCore
import RSParser import Parser
import Articles import Articles
import ArticlesDatabase import ArticlesDatabase
import RSWeb import RSWeb
@ -96,21 +96,13 @@ final class LocalAccountDelegate: AccountDelegate {
} }
let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData)
var opmlDocument: RSOPMLDocument? let opmlDocument = OPMLParser.document(with: parserData)
do {
opmlDocument = try RSOPMLParser.parseOPML(with: parserData)
} catch {
completion(.failure(error))
return
}
guard let loadDocument = opmlDocument else { guard let loadDocument = opmlDocument else {
completion(.success(())) completion(.success(()))
return return
} }
guard let children = loadDocument.children else { guard let children = loadDocument.items else {
return return
} }

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import RSCore import RSCore
import RSParser import Parser
import RSWeb import RSWeb
import Articles import Articles
import ArticlesDatabase import ArticlesDatabase

View File

@ -7,10 +7,11 @@
// Copyright (c) 2020 Ranchero Software, LLC. All rights reserved. // Copyright (c) 2020 Ranchero Software, LLC. All rights reserved.
// //
import Foundation
import Articles import Articles
import RSCore import RSCore
import RSDatabase import RSDatabase
import RSParser import Parser
import RSWeb import RSWeb
import SyncDatabase import SyncDatabase
import os.log import os.log

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import RSCore import RSCore
import RSParser import Parser
typealias NewsBlurFolder = NewsBlurFeedsResponse.Folder typealias NewsBlurFolder = NewsBlurFeedsResponse.Folder

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import RSCore import RSCore
import RSParser import Parser
typealias NewsBlurStory = NewsBlurStoriesResponse.Story typealias NewsBlurStory = NewsBlurStoriesResponse.Story

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import RSCore import RSCore
import RSParser import Parser
typealias NewsBlurStoryHash = NewsBlurStoryHashesResponse.StoryHash typealias NewsBlurStoryHash = NewsBlurStoryHashesResponse.StoryHash

View File

@ -6,10 +6,11 @@
// Copyright (c) 2020 Ranchero Software, LLC. All rights reserved. // Copyright (c) 2020 Ranchero Software, LLC. All rights reserved.
// //
import Foundation
import Articles import Articles
import RSCore import RSCore
import RSDatabase import RSDatabase
import RSParser import Parser
import RSWeb import RSWeb
import SyncDatabase import SyncDatabase
import os.log import os.log

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
import os.log import os.log
import RSCore import RSCore
import RSParser import Parser
final class OPMLFile { final class OPMLFile {
@ -82,18 +82,15 @@ private extension OPMLFile {
return fileData return fileData
} }
func parsedOPMLItems(fileData: Data) -> [RSOPMLItem]? { func parsedOPMLItems(fileData: Data) -> [OPMLItem]? {
let parserData = ParserData(url: fileURL.absoluteString, data: fileData)
var opmlDocument: RSOPMLDocument?
do { let parserData = ParserData(url: fileURL.absoluteString, data: fileData)
opmlDocument = try RSOPMLParser.parseOPML(with: parserData) guard let opmlDocument = OPMLParser.document(with: parserData) else {
} catch { os_log(.error, log: log, "OPML Import failed")
os_log(.error, log: log, "OPML Import failed: %@.", error.localizedDescription)
return nil return nil
} }
return opmlDocument?.children return opmlDocument.items
} }
func opmlDocument() -> String { func opmlDocument() -> String {

View File

@ -7,20 +7,20 @@
// //
import Foundation import Foundation
import RSParser import Parser
final class OPMLNormalizer { final class OPMLNormalizer {
var normalizedOPMLItems = [RSOPMLItem]() var normalizedOPMLItems = [OPMLItem]()
static func normalize(_ items: [RSOPMLItem]) -> [RSOPMLItem] { static func normalize(_ items: [OPMLItem]) -> [OPMLItem] {
let opmlNormalizer = OPMLNormalizer() let opmlNormalizer = OPMLNormalizer()
opmlNormalizer.normalize(items) opmlNormalizer.normalize(items)
return opmlNormalizer.normalizedOPMLItems return opmlNormalizer.normalizedOPMLItems
} }
private func normalize(_ items: [RSOPMLItem], parentFolder: RSOPMLItem? = nil) { private func normalize(_ items: [OPMLItem], parentFolder: OPMLItem? = nil) {
var feedsToAdd = [RSOPMLItem]() var feedsToAdd = [OPMLItem]()
items.forEach { (item) in items.forEach { (item) in
@ -33,14 +33,14 @@ final class OPMLNormalizer {
guard let _ = item.titleFromAttributes else { guard let _ = item.titleFromAttributes else {
// Folder doesnt have a name, so it wont be created, and its items will go one level up. // Folder doesnt have a name, so it wont be created, and its items will go one level up.
if let itemChildren = item.children { if let itemChildren = item.items {
normalize(itemChildren, parentFolder: parentFolder) normalize(itemChildren, parentFolder: parentFolder)
} }
return return
} }
feedsToAdd.append(item) feedsToAdd.append(item)
if let itemChildren = item.children { if let itemChildren = item.items {
if let parentFolder = parentFolder { if let parentFolder = parentFolder {
normalize(itemChildren, parentFolder: parentFolder) normalize(itemChildren, parentFolder: parentFolder)
} else { } else {
@ -51,8 +51,8 @@ final class OPMLNormalizer {
if let parentFolder = parentFolder { if let parentFolder = parentFolder {
for feed in feedsToAdd { for feed in feedsToAdd {
if !(parentFolder.children?.contains(where: { $0.feedSpecifier?.feedURL == feed.feedSpecifier?.feedURL}) ?? false) { if !(parentFolder.items?.contains(where: { $0.feedSpecifier?.feedURL == feed.feedSpecifier?.feedURL}) ?? false) {
parentFolder.addChild(feed) parentFolder.add(feed)
} }
} }
} else { } else {

View File

@ -6,9 +6,10 @@
// Copyright © 2019 Ranchero Software, LLC. All rights reserved. // Copyright © 2019 Ranchero Software, LLC. All rights reserved.
// //
import Foundation
import Articles import Articles
import RSCore import RSCore
import RSParser import Parser
import RSWeb import RSWeb
import SyncDatabase import SyncDatabase
import os.log import os.log

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import RSCore import RSCore
struct ReaderAPIEntryWrapper: Codable { struct ReaderAPIEntryWrapper: Codable {

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import RSCore import RSCore
import RSParser import Parser
/* /*

View File

@ -8,7 +8,7 @@
import XCTest import XCTest
@testable import Account @testable import Account
import RSParser import Parser
import RSCore import RSCore
class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase { class FeedlyOrganiseParsedItemsByFeedOperationTests: XCTestCase {

View File

@ -7,7 +7,7 @@
// //
import XCTest import XCTest
import RSParser import Parser
import Secrets import Secrets
@testable import Account @testable import Account
import os.log import os.log

View File

@ -13,7 +13,7 @@ let package = Package(
], ],
dependencies: [ dependencies: [
.package(path: "../RSDatabase"), .package(path: "../RSDatabase"),
.package(path: "../RSParser"), .package(path: "../Parser"),
.package(path: "../RSCore"), .package(path: "../RSCore"),
.package(path: "../Articles"), .package(path: "../Articles"),
], ],
@ -23,7 +23,7 @@ let package = Package(
dependencies: [ dependencies: [
"RSCore", "RSCore",
"RSDatabase", "RSDatabase",
"RSParser", "Parser",
"Articles", "Articles",
], ],
swiftSettings: [.unsafeFlags(["-warnings-as-errors"])] swiftSettings: [.unsafeFlags(["-warnings-as-errors"])]

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
import RSCore import RSCore
import RSDatabase import RSDatabase
import RSParser import Parser
import Articles import Articles
// This file is the entirety of the public API for ArticlesDatabase.framework. // This file is the entirety of the public API for ArticlesDatabase.framework.

View File

@ -10,7 +10,7 @@ import Foundation
import RSCore import RSCore
import RSDatabase import RSDatabase
import RSDatabaseObjC import RSDatabaseObjC
import RSParser import Parser
import Articles import Articles
final class ArticlesTable: DatabaseTable { final class ArticlesTable: DatabaseTable {

View File

@ -10,7 +10,7 @@ import Foundation
import RSDatabase import RSDatabase
import RSDatabaseObjC import RSDatabaseObjC
import Articles import Articles
import RSParser import Parser
extension Article { extension Article {

View File

@ -10,7 +10,7 @@ import Foundation
import Articles import Articles
import RSDatabase import RSDatabase
import RSDatabaseObjC import RSDatabaseObjC
import RSParser import Parser
// MARK: - DatabaseObject // MARK: - DatabaseObject

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import Articles import Articles
extension ParsedItem { extension ParsedItem {

View File

@ -11,7 +11,7 @@ import RSCore
import RSDatabase import RSDatabase
import RSDatabaseObjC import RSDatabaseObjC
import Articles import Articles
import RSParser import Parser
final class ArticleSearchInfo: Hashable { final class ArticleSearchInfo: Hashable {
@ -34,7 +34,7 @@ final class ArticleSearchInfo: Hashable {
} }
lazy var bodyForIndex: String = { lazy var bodyForIndex: String = {
let s = preferredText.rsparser_stringByDecodingHTMLEntities() let s = HTMLEntityDecoder.decodedString(preferredText)
let sanitizedBody = s.strippingHTML().collapsingWhitespace let sanitizedBody = s.strippingHTML().collapsingWhitespace
if let authorsNames = authorsNames { if let authorsNames = authorsNames {

View File

@ -12,7 +12,7 @@ import RSCoreResources
import RSTree import RSTree
import Articles import Articles
import Account import Account
import RSParser import Parser
// Run add-feed sheet. // Run add-feed sheet.
// If it returns with URL and optional name, // If it returns with URL and optional name,

View File

@ -7,7 +7,7 @@
// //
import Foundation import Foundation
import RSParser import Parser
import Account import Account
import Articles import Articles
@ -95,7 +95,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
let container: Container = folder != nil ? folder! : account let container: Container = folder != nil ? folder! : account
// We need to download the feed and parse it. // We need to download the feed and parse it.
// RSParser does the callback for the download on the main thread. // Parser does the callback for the download on the main thread.
// Because we can't wait here (on the main thread) for the callback, we have to return from this function. // Because we can't wait here (on the main thread) for the callback, we have to return from this function.
// Generally, returning from an AppleEvent handler function means that handling the Apple event is over, // Generally, returning from an AppleEvent handler function means that handling the Apple event is over,
// but we dont yet have the result of the event yet, so we prevent the Apple event from returning by calling // but we dont yet have the result of the event yet, so we prevent the Apple event from returning by calling

View File

@ -414,7 +414,6 @@
84C1ECEB2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = { 84C1ECEB2CDFE49100C7456A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
Images/FeaturedImageDownloader.swift,
Resources/NewsFax.nnwtheme, Resources/NewsFax.nnwtheme,
Secrets.swift.gyb, Secrets.swift.gyb,
ShareExtension/SafariExt.js, ShareExtension/SafariExt.js,
@ -433,7 +432,6 @@
ExtensionPoints/SendToMarsEditCommand.swift, ExtensionPoints/SendToMarsEditCommand.swift,
ExtensionPoints/SendToMicroBlogCommand.swift, ExtensionPoints/SendToMicroBlogCommand.swift,
"Extensions/NSView-Extensions.swift", "Extensions/NSView-Extensions.swift",
Images/FeaturedImageDownloader.swift,
Secrets.swift.gyb, Secrets.swift.gyb,
ShareExtension/SafariExt.js, ShareExtension/SafariExt.js,
ShareExtension/ShareDefaultContainer.swift, ShareExtension/ShareDefaultContainer.swift,
@ -672,8 +670,8 @@
51CD32C324D2CD57009ABAEF /* ArticlesDatabase */, 51CD32C324D2CD57009ABAEF /* ArticlesDatabase */,
51CD32C724D2E06C009ABAEF /* Secrets */, 51CD32C724D2E06C009ABAEF /* Secrets */,
51CD32A824D2CB25009ABAEF /* SyncDatabase */, 51CD32A824D2CB25009ABAEF /* SyncDatabase */,
8417FA3E2CDF2E31005F989B /* RSDatabase */,
849E61B82CE85D09008AF514 /* Parser */, 849E61B82CE85D09008AF514 /* Parser */,
8417FA3E2CDF2E31005F989B /* RSDatabase */,
847C4C0C2CDF22DD008BF5FE /* RSTree */, 847C4C0C2CDF22DD008BF5FE /* RSTree */,
8413878C2CDC78EE00E8490F /* RSWeb */, 8413878C2CDC78EE00E8490F /* RSWeb */,
8413876B2CD896E000E8490F /* RSCore */, 8413876B2CD896E000E8490F /* RSCore */,

View File

@ -1,6 +1,6 @@
// //
// FeedParser.swift // FeedParser.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/20/17. // Created by Brent Simmons on 6/20/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.
@ -50,6 +50,22 @@ public struct FeedParser {
} }
} }
public static func parse(_ parserData: ParserData, _ completion: @Sendable @escaping (ParsedFeed?, Error?) -> Void) {
Task {
do {
let parsedFeed = try await parseAsync(urlString: parserData.url, data: parserData.data)
Task { @MainActor in
completion(parsedFeed, nil)
}
} catch {
Task { @MainActor in
completion(nil, error)
}
}
}
}
public static func parseAsync(urlString: String, data: Data) async throws -> ParsedFeed? { public static func parseAsync(urlString: String, data: Data) async throws -> ParsedFeed? {
try parse(urlString: urlString, data: data) try parse(urlString: urlString, data: data)

View File

@ -1,6 +1,6 @@
// //
// FeedParserError.swift // FeedParserError.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/24/17. // Created by Brent Simmons on 6/24/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// FeedType.swift // FeedType.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/20/17. // Created by Brent Simmons on 6/20/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// JSONFeedParser.swift // JSONFeedParser.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/25/17. // Created by Brent Simmons on 6/25/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// RSSInJSONParser.swift // RSSInJSONParser.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/24/17. // Created by Brent Simmons on 6/24/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// ParsedAttachment.swift // ParsedAttachment.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/20/17. // Created by Brent Simmons on 6/20/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// ParsedAuthor.swift // ParsedAuthor.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/20/17. // Created by Brent Simmons on 6/20/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// ParsedFeed.swift // ParsedFeed.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/20/17. // Created by Brent Simmons on 6/20/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// ParsedHub.swift // ParsedHub.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/20/17. // Created by Brent Simmons on 6/20/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// ParsedItem.swift // ParsedItem.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/20/17. // Created by Brent Simmons on 6/20/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// AtomParser.swift // AtomParser.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/25/17. // Created by Brent Simmons on 6/25/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// RSSFeedTransformer.swift // RSSFeedTransformer.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/25/17. // Created by Brent Simmons on 6/25/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// RSSParser.swift // RSSParser.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/25/17. // Created by Brent Simmons on 6/25/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// JSONDictionary.swift // JSONDictionary.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/24/17. // Created by Brent Simmons on 6/24/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// JSONUtilities.swift // JSONUtilities.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 12/10/17. // Created by Brent Simmons on 12/10/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// String+RSParser.swift // String+Parser.swift
// RSParser // Parser
// //
// Created by Nate Weaver on 2020-01-19. // Created by Nate Weaver on 2020-01-19.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved. // Copyright © 2020 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// AtomParserTests.swift // AtomParserTests.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/26/17. // Created by Brent Simmons on 6/26/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// EntityDecodingTests.swift // EntityDecodingTests.swift
// RSParserTests // ParserTests
// //
// Created by Brent Simmons on 12/30/17. // Created by Brent Simmons on 12/30/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// FeedParserTypeTests.swift // FeedParserTypeTests.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/25/17. // Created by Brent Simmons on 6/25/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// HTMLLinkTests.swift // HTMLLinkTests.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/25/17. // Created by Brent Simmons on 6/25/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// HTMLMetadataTests.swift // HTMLMetadataTests.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/25/17. // Created by Brent Simmons on 6/25/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// JSONFeedParserTests.swift // JSONFeedParserTests.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/26/17. // Created by Brent Simmons on 6/26/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// OPMLTests.swift // OPMLTests.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/25/17. // Created by Brent Simmons on 6/25/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// RSSInJSONParserTests.swift // RSSInJSONParserTests.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/26/17. // Created by Brent Simmons on 6/26/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -1,6 +1,6 @@
// //
// RSSParserTests.swift // RSSParserTests.swift
// RSParser // Parser
// //
// Created by Brent Simmons on 6/26/17. // Created by Brent Simmons on 6/26/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved. // Copyright © 2017 Ranchero Software, LLC. All rights reserved.

View File

@ -15,9 +15,9 @@
buildForAnalyzing = "YES"> buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "Parser" BlueprintIdentifier = "RSDatabase"
BuildableName = "Parser" BuildableName = "RSDatabase"
BlueprintName = "Parser" BlueprintName = "RSDatabase"
ReferencedContainer = "container:"> ReferencedContainer = "container:">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
@ -50,9 +50,9 @@
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "Parser" BlueprintIdentifier = "RSDatabase"
BuildableName = "Parser" BuildableName = "RSDatabase"
BlueprintName = "Parser" BlueprintName = "RSDatabase"
ReferencedContainer = "container:"> ReferencedContainer = "container:">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import Articles import Articles
import RSParser import Parser
struct ArticleStringFormatter { struct ArticleStringFormatter {
@ -66,7 +66,7 @@ struct ArticleStringFormatter {
s = s.replacingOccurrences(of: "\t", with: "") s = s.replacingOccurrences(of: "\t", with: "")
if !forHTML { if !forHTML {
s = s.rsparser_stringByDecodingHTMLEntities() s = HTMLEntityDecoder.decodedString(s)
} }
s = s.trimmingWhitespace s = s.trimmingWhitespace
@ -98,7 +98,7 @@ struct ArticleStringFormatter {
if let cachedBody = summaryCache[key] { if let cachedBody = summaryCache[key] {
return cachedBody return cachedBody
} }
var s = body.rsparser_stringByDecodingHTMLEntities() var s = HTMLEntityDecoder.decodedString(body)
s = s.strippingHTML(maxCharacters: 250) s = s.strippingHTML(maxCharacters: 250)
s = s.trimmingWhitespace s = s.trimmingWhitespace
s = s.collapsingWhitespace s = s.collapsingWhitespace

View File

@ -6,7 +6,7 @@
// Copyright © 2020 Ranchero Software. All rights reserved. // Copyright © 2020 Ranchero Software. All rights reserved.
// //
import RSParser import Parser
#if canImport(AppKit) #if canImport(AppKit)
import AppKit import AppKit
@ -310,6 +310,6 @@ private struct CountedSet<Element> where Element: Hashable {
private extension String { private extension String {
var decodedEntity: String { var decodedEntity: String {
// It's possible the implementation will change, but for now it just calls this. // It's possible the implementation will change, but for now it just calls this.
(self as NSString).rsparser_stringByDecodingHTMLEntities() as String HTMLEntityDecoder.decodedString(self)
} }
} }

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import CoreServices import CoreServices
import RSParser import Parser
import UniformTypeIdentifiers import UniformTypeIdentifiers
// The favicon URLs may be specified in the head section of the home page. // The favicon URLs may be specified in the head section of the home page.
@ -20,7 +20,7 @@ struct FaviconURLFinder {
/// - homePageURL: The page to search. /// - homePageURL: The page to search.
/// - completion: A closure called when the links have been found. /// - completion: A closure called when the links have been found.
/// - urls: An array of favicon URLs as strings. /// - urls: An array of favicon URLs as strings.
static func findFaviconURLs(with homePageURL: String, _ completion: @escaping (_ urls: [String]?) -> Void) { static func findFaviconURLs(with homePageURL: String, _ completion: @escaping ([String]?) -> Void) {
guard let _ = URL(string: homePageURL) else { guard let _ = URL(string: homePageURL) else {
completion(nil) completion(nil)
@ -29,7 +29,13 @@ struct FaviconURLFinder {
// If the favicon has an explicit type, check that for an ignored type; otherwise, check the file extension. // If the favicon has an explicit type, check that for an ignored type; otherwise, check the file extension.
HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (htmlMetadata) in HTMLMetadataDownloader.downloadMetadata(for: homePageURL) { (htmlMetadata) in
let faviconURLs = htmlMetadata?.favicons.compactMap {
guard let favicons = htmlMetadata?.favicons else {
completion(nil)
return
}
let faviconURLs = favicons.compactMap {
shouldAllowFavicon($0) ? $0.urlString : nil shouldAllowFavicon($0) ? $0.urlString : nil
} }
@ -39,7 +45,7 @@ struct FaviconURLFinder {
private static let ignoredTypes = [UTType.svg] private static let ignoredTypes = [UTType.svg]
private static func shouldAllowFavicon(_ favicon: RSHTMLMetadataFavicon) -> Bool { private static func shouldAllowFavicon(_ favicon: HTMLMetadataFavicon) -> Bool {
// Check mime type. // Check mime type.
if let mimeType = favicon.type, let utType = UTType(mimeType: mimeType) { if let mimeType = favicon.type, let utType = UTType(mimeType: mimeType) {

View File

@ -8,13 +8,13 @@
import Foundation import Foundation
import RSWeb import RSWeb
import RSParser import Parser
struct HTMLMetadataDownloader { struct HTMLMetadataDownloader {
static let serialDispatchQueue = DispatchQueue(label: "HTMLMetadataDownloader") static let serialDispatchQueue = DispatchQueue(label: "HTMLMetadataDownloader")
static func downloadMetadata(for url: String, _ completion: @escaping (RSHTMLMetadata?) -> Void) { static func downloadMetadata(for url: String, _ completion: @escaping (HTMLMetadata?) -> Void) {
guard let actualURL = URL(string: url) else { guard let actualURL = URL(string: url) else {
completion(nil) completion(nil)
return return
@ -32,9 +32,9 @@ struct HTMLMetadataDownloader {
} }
} }
private static func parseMetadata(with parserData: ParserData, _ completion: @escaping (RSHTMLMetadata?) -> Void) { private static func parseMetadata(with parserData: ParserData, _ completion: @escaping (HTMLMetadata?) -> Void) {
serialDispatchQueue.async { serialDispatchQueue.async {
let htmlMetadata = RSHTMLMetadataParser.htmlMetadata(with: parserData) let htmlMetadata = HTMLMetadataParser.metadata(with: parserData)
DispatchQueue.main.async { DispatchQueue.main.async {
completion(htmlMetadata) completion(htmlMetadata)
} }

View File

@ -11,7 +11,7 @@ import Articles
import Account import Account
import RSCore import RSCore
import RSWeb import RSWeb
import RSParser import Parser
extension Notification.Name { extension Notification.Name {
@ -214,7 +214,7 @@ private extension FeedIconDownloader {
} }
} }
func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: Feed) { func pullIconURL(from metadata: HTMLMetadata, homePageURL: String, feed: Feed) {
if let url = metadata.bestWebsiteIconURL() { if let url = metadata.bestWebsiteIconURL() {
cacheIconURL(for: homePageURL, url) cacheIconURL(for: homePageURL, url)

View File

@ -7,67 +7,34 @@
// //
import Foundation import Foundation
import RSParser import Parser
extension RSHTMLMetadata { extension HTMLMetadata {
func largestOpenGraphImageURL() -> String? {
let openGraphImages = openGraphProperties.images
guard !openGraphImages.isEmpty else {
return nil
}
var bestImage: RSHTMLOpenGraphImage? = nil
for image in openGraphImages {
if image.width / image.height > 2 {
continue
}
if bestImage == nil {
bestImage = image
continue
}
if image.height > bestImage!.height && image.width > bestImage!.width {
bestImage = image
}
}
guard let url = bestImage?.secureURL ?? bestImage?.url else {
return nil
}
// Bad ones we should ignore.
let badURLs = Set(["https://s0.wp.com/i/blank.jpg"])
guard !badURLs.contains(url) else {
return nil
}
return url
}
func largestAppleTouchIcon() -> String? { func largestAppleTouchIcon() -> String? {
let icons = appleTouchIcons guard let icons = appleTouchIcons, !icons.isEmpty else {
guard !icons.isEmpty else {
return nil return nil
} }
var bestImage: RSHTMLMetadataAppleTouchIcon? = nil var bestImage: HTMLMetadataAppleTouchIcon? = nil
for image in icons { for image in icons {
if image.size.width / image.size.height > 2 { if let size = image.size {
if size.width / size.height > 2 {
continue continue
} }
}
if bestImage == nil { if bestImage == nil {
bestImage = image bestImage = image
continue continue
} }
if image.size.height > bestImage!.size.height && image.size.width > bestImage!.size.width { if let size = image.size, let bestImageSize = bestImage!.size {
if size.height > bestImageSize.height && size.width > bestImageSize.width {
bestImage = image; bestImage = image;
} }
} }
}
return bestImage?.urlString return bestImage?.urlString
} }
@ -80,19 +47,10 @@ extension RSHTMLMetadata {
return appleTouchIcon return appleTouchIcon
} }
if let openGraphImageURL = largestOpenGraphImageURL() { if let openGraphImageURL = openGraphProperties?.image?.url {
return openGraphImageURL return openGraphImageURL
} }
return twitterProperties.imageURL return twitterProperties?.imageURL
}
func bestFeaturedImageURL() -> String? {
if let openGraphImageURL = largestOpenGraphImageURL() {
return openGraphImageURL
}
return twitterProperties.imageURL
} }
} }

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
import os.log import os.log
import RSCore import RSCore
import RSParser import Parser
import Account import Account
final class ExtensionContainersFile { final class ExtensionContainersFile {

View File

@ -10,7 +10,7 @@ import UIKit
import Account import Account
import RSCore import RSCore
import RSTree import RSTree
import RSParser import Parser
enum AddFeedType { enum AddFeedType {
case web case web