Switch to new Parser.
This commit is contained in:
parent
c3f063ae4a
commit
85d1a8fe7a
@ -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",
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSParser
|
import Parser
|
||||||
|
|
||||||
struct FeedbinSubscription: Hashable, Codable {
|
struct FeedbinSubscription: Hashable, Codable {
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSParser
|
import Parser
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
protocol FeedlyEntryProviding {
|
protocol FeedlyEntryProviding {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSParser
|
import Parser
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
protocol FeedlyParsedItemsByFeedProviding {
|
protocol FeedlyParsedItemsByFeedProviding {
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSParser
|
import Parser
|
||||||
|
|
||||||
typealias NewsBlurFolder = NewsBlurFeedsResponse.Folder
|
typealias NewsBlurFolder = NewsBlurFeedsResponse.Folder
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSParser
|
import Parser
|
||||||
|
|
||||||
typealias NewsBlurStory = NewsBlurStoriesResponse.Story
|
typealias NewsBlurStory = NewsBlurStoriesResponse.Story
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSParser
|
import Parser
|
||||||
|
|
||||||
typealias NewsBlurStoryHash = NewsBlurStoryHashesResponse.StoryHash
|
typealias NewsBlurStoryHash = NewsBlurStoryHashesResponse.StoryHash
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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 doesn’t have a name, so it won’t be created, and its items will go one level up.
|
// Folder doesn’t have a name, so it won’t 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 {
|
||||||
|
@ -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
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSParser
|
import Parser
|
||||||
import RSCore
|
import RSCore
|
||||||
|
|
||||||
struct ReaderAPIEntryWrapper: Codable {
|
struct ReaderAPIEntryWrapper: Codable {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSCore
|
import RSCore
|
||||||
import RSParser
|
import Parser
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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"])]
|
||||||
|
@ -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.
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import RSParser
|
import Parser
|
||||||
import Articles
|
import Articles
|
||||||
|
|
||||||
extension ParsedItem {
|
extension ParsedItem {
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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 don’t yet have the result of the event yet, so we prevent the Apple event from returning by calling
|
// but we don’t yet have the result of the event yet, so we prevent the Apple event from returning by calling
|
||||||
|
@ -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 */,
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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>
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user