diff --git a/Modules/Account/Sources/Account/Account.swift b/Modules/Account/Sources/Account/Account.swift index efc42748b..f6343c755 100644 --- a/Modules/Account/Sources/Account/Account.swift +++ b/Modules/Account/Sources/Account/Account.swift @@ -464,14 +464,14 @@ public enum FetchType { delegate.accountWillBeDeleted(self) } - func addOPMLItems(_ items: [RSOPMLItem]) { + func addOPMLItems(_ items: [OPMLItem]) { for item in items { if let feedSpecifier = item.feedSpecifier { addFeed(newFeed(with: feedSpecifier)) } else { if let title = item.titleFromAttributes, let folder = ensureFolder(with: title) { folder.externalID = item.attributes?["nnw_externalID"] as? String - if let children = item.children { + if let children = item.items { for itemChild in children { if let feedSpecifier = itemChild.feedSpecifier { folder.addFeed(newFeed(with: feedSpecifier)) @@ -483,7 +483,7 @@ public enum FetchType { } } - func loadOPMLItems(_ items: [RSOPMLItem]) { + func loadOPMLItems(_ items: [OPMLItem]) { addOPMLItems(OPMLNormalizer.normalize(items)) } @@ -560,7 +560,7 @@ public enum FetchType { return folders?.first(where: { $0.externalID == externalID }) } - func newFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed { + func newFeed(with opmlFeedSpecifier: OPMLFeedSpecifier) -> Feed { let feedURL = opmlFeedSpecifier.feedURL let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL) let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata) diff --git a/Modules/Account/Sources/Account/AccountDelegates/CloudKitAccountDelegate.swift b/Modules/Account/Sources/Account/AccountDelegates/CloudKitAccountDelegate.swift index 544a30e93..e65cc424a 100644 --- a/Modules/Account/Sources/Account/AccountDelegates/CloudKitAccountDelegate.swift +++ b/Modules/Account/Sources/Account/AccountDelegates/CloudKitAccountDelegate.swift @@ -175,9 +175,9 @@ enum CloudKitAccountDelegateError: LocalizedError { let opmlData = try Data(contentsOf: opmlFile) let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) - let opmlDocument = try RSOPMLParser.parseOPML(with: parserData) + let opmlDocument = OPMLParser.document(with: parserData) - guard let opmlItems = opmlDocument.children, let rootExternalID = account.externalID else { + guard let opmlItems = opmlDocument?.items, let rootExternalID = account.externalID else { return } diff --git a/Modules/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift b/Modules/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift index 00a9b05f0..1695d12c7 100644 --- a/Modules/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift +++ b/Modules/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift @@ -77,8 +77,8 @@ final class LocalAccountDelegate: AccountDelegate { let opmlData = try Data(contentsOf: opmlFile) let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) - let opmlDocument = try RSOPMLParser.parseOPML(with: parserData) - guard let children = opmlDocument.children else { + let opmlDocument = OPMLParser.document(with: parserData) + guard let children = opmlDocument?.items else { return } @@ -267,9 +267,7 @@ private extension LocalAccountDelegate { return } - let parserData = ParserData(url: feed.url, data: data) - - guard let parsedFeed = try? await FeedParser.parse(parserData) else { + guard let parsedFeed = try? await FeedParser.parseAsync(urlString: feed.url, data: data) else { return } diff --git a/Modules/Account/Sources/Account/OPMLFile.swift b/Modules/Account/Sources/Account/OPMLFile.swift index a8fe0593f..39221856e 100644 --- a/Modules/Account/Sources/Account/OPMLFile.swift +++ b/Modules/Account/Sources/Account/OPMLFile.swift @@ -33,7 +33,7 @@ import Core dataFile.markAsDirty() } - func opmlItems() -> [RSOPMLItem]? { + func opmlItems() -> [OPMLItem]? { guard let fileData = opmlFileData() else { return nil } @@ -61,18 +61,10 @@ private extension OPMLFile { return fileData } - func parsedOPMLItems(fileData: Data) -> [RSOPMLItem]? { + func parsedOPMLItems(fileData: Data) -> [OPMLItem]? { let parserData = ParserData(url: fileURL.absoluteString, data: fileData) - var opmlDocument: RSOPMLDocument? - - do { - opmlDocument = try RSOPMLParser.parseOPML(with: parserData) - } catch { - logger.error("OPML Import failed for \(self.fileURL): \(error.localizedDescription)") - return nil - } - - return opmlDocument?.children + let opmlDocument = OPMLParser.document(with: parserData) + return opmlDocument?.items } func opmlDocument() -> String { diff --git a/Modules/Account/Sources/Account/OPMLNormalizer.swift b/Modules/Account/Sources/Account/OPMLNormalizer.swift index ab6111f89..2d5de573f 100644 --- a/Modules/Account/Sources/Account/OPMLNormalizer.swift +++ b/Modules/Account/Sources/Account/OPMLNormalizer.swift @@ -11,16 +11,16 @@ import Parser final class OPMLNormalizer { - var normalizedOPMLItems = [RSOPMLItem]() + var normalizedOPMLItems = [OPMLItem]() - static func normalize(_ items: [RSOPMLItem]) -> [RSOPMLItem] { + static func normalize(_ items: [OPMLItem]) -> [OPMLItem] { let opmlNormalizer = OPMLNormalizer() opmlNormalizer.normalize(items) return opmlNormalizer.normalizedOPMLItems } - private func normalize(_ items: [RSOPMLItem], parentFolder: RSOPMLItem? = nil) { - var feedsToAdd = [RSOPMLItem]() + private func normalize(_ items: [OPMLItem], parentFolder: OPMLItem? = nil) { + var feedsToAdd = [OPMLItem]() for item in items { @@ -33,14 +33,14 @@ final class OPMLNormalizer { 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. - if let itemChildren = item.children { + if let itemChildren = item.items { normalize(itemChildren, parentFolder: parentFolder) } continue } feedsToAdd.append(item) - if let itemChildren = item.children { + if let itemChildren = item.items { if let parentFolder = parentFolder { normalize(itemChildren, parentFolder: parentFolder) } else { @@ -51,8 +51,8 @@ final class OPMLNormalizer { if let parentFolder = parentFolder { for feed in feedsToAdd { - if !(parentFolder.children?.contains(where: { $0.feedSpecifier?.feedURL == feed.feedSpecifier?.feedURL}) ?? false) { - parentFolder.addChild(feed) + if !(parentFolder.items?.contains(where: { $0.feedSpecifier?.feedURL == feed.feedSpecifier?.feedURL}) ?? false) { + parentFolder.add(feed) } } } else { diff --git a/Modules/ArticlesDatabase/Sources/ArticlesDatabase/SearchTable.swift b/Modules/ArticlesDatabase/Sources/ArticlesDatabase/SearchTable.swift index fa78de31c..b96fbf432 100644 --- a/Modules/ArticlesDatabase/Sources/ArticlesDatabase/SearchTable.swift +++ b/Modules/ArticlesDatabase/Sources/ArticlesDatabase/SearchTable.swift @@ -33,7 +33,7 @@ final class ArticleSearchInfo: Hashable { } lazy var bodyForIndex: String = { - let s = preferredText.rsparser_stringByDecodingHTMLEntities() + let s = HTMLEntityDecoder.decodedString(preferredText) let sanitizedBody = s.strippingHTML().collapsingWhitespace if let authorsNames = authorsNames { diff --git a/Modules/CloudKitSync/Sources/CloudKitSync/CloudKitAccountZone.swift b/Modules/CloudKitSync/Sources/CloudKitSync/CloudKitAccountZone.swift index e4a18cc51..fea5368d4 100644 --- a/Modules/CloudKitSync/Sources/CloudKitSync/CloudKitAccountZone.swift +++ b/Modules/CloudKitSync/Sources/CloudKitSync/CloudKitAccountZone.swift @@ -56,12 +56,12 @@ enum CloudKitAccountZoneError: LocalizedError { migrateChangeToken() } - public func importOPML(rootExternalID: String, items: [RSOPMLItem]) async throws { + public func importOPML(rootExternalID: String, items: [OPMLItem]) async throws { var records = [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] { containerExternalIDs.append(containerExternalID) feedRecord[CloudKitFeed.Fields.containerExternalIDs] = containerExternalIDs @@ -79,7 +79,7 @@ enum CloudKitAccountZoneError: LocalizedError { if let title = item.titleFromAttributes { let containerRecord = newContainerCKRecord(name: title) records.append(containerRecord) - item.children?.forEach { itemChild in + item.items?.forEach { itemChild in if let feedSpecifier = itemChild.feedSpecifier { processFeed(feedSpecifier: feedSpecifier, containerExternalID: containerRecord.externalID) } @@ -288,7 +288,7 @@ enum CloudKitAccountZoneError: LocalizedError { 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()) record[CloudKitFeed.Fields.url] = feedSpecifier.feedURL diff --git a/Modules/FeedFinder/Sources/FeedFinder/FeedFinder.swift b/Modules/FeedFinder/Sources/FeedFinder/FeedFinder.swift index d2dd7cfc1..3d8503c5c 100644 --- a/Modules/FeedFinder/Sources/FeedFinder/FeedFinder.swift +++ b/Modules/FeedFinder/Sources/FeedFinder/FeedFinder.swift @@ -55,7 +55,7 @@ public final class FeedFinder { throw AccountError.createErrorNotFound } - if FeedFinder.isFeed(data, url.absoluteString) { + if FeedFinder.isFeed(data) { logger.info("FeedFinder: is feed \(url)") let feedSpecifier = FeedSpecifier(title: nil, urlString: url.absoluteString, source: .UserEntered, orderFound: 1) return Set([feedSpecifier]) @@ -156,7 +156,7 @@ private extension FeedFinder { if let downloadData = try? await DownloadWithCacheManager.shared.download(url) { if let data = downloadData.data, let response = downloadData.response, response.statusIsOK { - if isFeed(data, downloadFeedSpecifier.urlString) { + if isFeed(data) { addFeedSpecifier(downloadFeedSpecifier, feedSpecifiers: &resultFeedSpecifiers) } } @@ -166,8 +166,7 @@ private extension FeedFinder { return Set(resultFeedSpecifiers.values) } - static func isFeed(_ data: Data, _ urlString: String) -> Bool { - let parserData = ParserData(url: urlString, data: data) - return FeedParser.canParse(parserData) + static func isFeed(_ data: Data) -> Bool { + return FeedParser.canParse(data) } } diff --git a/Modules/FeedFinder/Sources/FeedFinder/HTMLFeedFinder.swift b/Modules/FeedFinder/Sources/FeedFinder/HTMLFeedFinder.swift index 805bc047b..6c51c9b2f 100644 --- a/Modules/FeedFinder/Sources/FeedFinder/HTMLFeedFinder.swift +++ b/Modules/FeedFinder/Sources/FeedFinder/HTMLFeedFinder.swift @@ -21,18 +21,20 @@ class HTMLFeedFinder { private var feedSpecifiersDictionary = [String: FeedSpecifier]() init(parserData: ParserData) { - let metadata = RSHTMLMetadataParser.htmlMetadata(with: parserData) + let metadata = HTMLMetadataParser.metadata(with: parserData) var orderFound = 0 - for oneFeedLink in metadata.feedLinks { - if let oneURLString = oneFeedLink.urlString?.normalizedURL { - orderFound = orderFound + 1 - let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .HTMLHead, orderFound: orderFound) - addFeedSpecifier(oneFeedSpecifier) + if let feedLinks = metadata.feedLinks { + for oneFeedLink in feedLinks { + if let oneURLString = oneFeedLink.urlString?.normalizedURL { + orderFound = orderFound + 1 + let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .HTMLHead, orderFound: orderFound) + addFeedSpecifier(oneFeedSpecifier) + } } } - let bodyLinks = RSHTMLLinkParser.htmlLinks(with: parserData) + let bodyLinks = HTMLLinkParser.htmlLinks(with: parserData) for oneBodyLink in bodyLinks { if linkMightBeFeed(oneBodyLink), let normalizedURL = oneBodyLink.urlString?.normalizedURL { orderFound = orderFound + 1 @@ -70,7 +72,7 @@ private extension HTMLFeedFinder { return false } - func linkMightBeFeed(_ link: RSHTMLLink) -> Bool { + func linkMightBeFeed(_ link: HTMLLink) -> Bool { if let linkURLString = link.urlString, urlStringMightBeFeed(linkURLString) { return true } diff --git a/Modules/Feedbin/Sources/Feedbin/FeedbinEntry.swift b/Modules/Feedbin/Sources/Feedbin/FeedbinEntry.swift index 03e90215a..a7675c585 100644 --- a/Modules/Feedbin/Sources/Feedbin/FeedbinEntry.swift +++ b/Modules/Feedbin/Sources/Feedbin/FeedbinEntry.swift @@ -28,7 +28,7 @@ public final class FeedbinEntry: Decodable, @unchecked Sendable { // and letting the one date fail when parsed. public lazy var parsedDatePublished: Date? = { if let datePublished = datePublished { - return RSDateWithString(datePublished) + return DateParser.date(string: datePublished) } else { return nil diff --git a/Modules/Feedly/Package.swift b/Modules/Feedly/Package.swift index 80db9dc0c..dbe60617a 100644 --- a/Modules/Feedly/Package.swift +++ b/Modules/Feedly/Package.swift @@ -22,7 +22,7 @@ let package = Package( .target( name: "Feedly", dependencies: [ - .product(name: "FeedParser", package: "Parser"), + "Parser", "Articles", "Secrets", "Core", diff --git a/Modules/Feedly/Sources/Feedly/FeedlyModel.swift b/Modules/Feedly/Sources/Feedly/FeedlyModel.swift index 8a692941f..265830848 100644 --- a/Modules/Feedly/Sources/Feedly/FeedlyModel.swift +++ b/Modules/Feedly/Sources/Feedly/FeedlyModel.swift @@ -8,7 +8,7 @@ import Foundation import Articles -import FeedParser +import Parser public struct FeedlyCategory: Decodable, Sendable, Equatable { diff --git a/Modules/Feedly/Sources/Feedly/FeedlyUtilities.swift b/Modules/Feedly/Sources/Feedly/FeedlyUtilities.swift index fe67be899..cae0b4f5c 100644 --- a/Modules/Feedly/Sources/Feedly/FeedlyUtilities.swift +++ b/Modules/Feedly/Sources/Feedly/FeedlyUtilities.swift @@ -6,7 +6,7 @@ // import Foundation -import FeedParser +import Parser public final class FeedlyUtilities { diff --git a/Modules/Images/Sources/Images/Favicons/FaviconDownloader.swift b/Modules/Images/Sources/Images/Favicons/FaviconDownloader.swift index 4055b7b73..64ebf4b31 100644 --- a/Modules/Images/Sources/Images/Favicons/FaviconDownloader.swift +++ b/Modules/Images/Sources/Images/Favicons/FaviconDownloader.swift @@ -12,6 +12,7 @@ import Articles import Account import UniformTypeIdentifiers import Core +import Parser public extension Notification.Name { static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL @@ -21,7 +22,7 @@ public protocol FaviconDownloaderDelegate { @MainActor var appIconImage: IconImage? { get } - @MainActor func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata? + @MainActor func downloadMetadata(_ url: String) async throws -> HTMLMetadata? } @MainActor public final class FaviconDownloader { diff --git a/Modules/Images/Sources/Images/Favicons/FaviconURLFinder.swift b/Modules/Images/Sources/Images/Favicons/FaviconURLFinder.swift index f959ff3b1..4c9689b18 100644 --- a/Modules/Images/Sources/Images/Favicons/FaviconURLFinder.swift +++ b/Modules/Images/Sources/Images/Favicons/FaviconURLFinder.swift @@ -22,7 +22,7 @@ import UniformTypeIdentifiers /// - Parameters: /// - homePageURL: The page to search. /// - urls: An array of favicon URLs as strings. - static func findFaviconURLs(with homePageURL: String, downloadMetadata: ((String) async throws -> RSHTMLMetadata?)) async -> [String]? { + static func findFaviconURLs(with homePageURL: String, downloadMetadata: ((String) async throws -> HTMLMetadata?)) async -> [String]? { guard let _ = URL(string: homePageURL) else { return nil @@ -31,14 +31,14 @@ import UniformTypeIdentifiers // If the favicon has an explicit type, check that for an ignored type; otherwise, check the file extension. let htmlMetadata = try? await downloadMetadata(homePageURL) - let faviconURLs = htmlMetadata?.favicons.compactMap { favicon -> String? in + let faviconURLs = htmlMetadata?.favicons?.compactMap { favicon -> String? in shouldAllowFavicon(favicon) ? favicon.urlString : nil } return faviconURLs } - static func shouldAllowFavicon(_ favicon: RSHTMLMetadataFavicon) -> Bool { + static func shouldAllowFavicon(_ favicon: HTMLMetadataFavicon) -> Bool { // Check mime type. if let mimeType = favicon.type, let utType = UTType(mimeType: mimeType) { diff --git a/Modules/Images/Sources/Images/FeaturedImageDownloader.swift b/Modules/Images/Sources/Images/FeaturedImageDownloader.swift index 1746cde36..992c6902e 100644 --- a/Modules/Images/Sources/Images/FeaturedImageDownloader.swift +++ b/Modules/Images/Sources/Images/FeaturedImageDownloader.swift @@ -87,7 +87,7 @@ // } // } // -// func pullFeaturedImageURL(from metadata: RSHTMLMetadata, articleURL: String) { +// func pullFeaturedImageURL(from metadata: HTMLMetadata, articleURL: String) { // // if let url = metadata.bestFeaturedImageURL() { // cacheURL(for: articleURL, url) diff --git a/Modules/Images/Sources/Images/FeedIconDownloader.swift b/Modules/Images/Sources/Images/FeedIconDownloader.swift index 55a66363c..c833531ae 100644 --- a/Modules/Images/Sources/Images/FeedIconDownloader.swift +++ b/Modules/Images/Sources/Images/FeedIconDownloader.swift @@ -22,7 +22,7 @@ public protocol FeedIconDownloaderDelegate: Sendable { @MainActor var appIconImage: IconImage? { get } - func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata? + func downloadMetadata(_ url: String) async throws -> HTMLMetadata? } @MainActor public final class FeedIconDownloader { @@ -217,7 +217,7 @@ private extension FeedIconDownloader { homePageToIconURLCacheDirty = true } - func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed, downloadMetadata: @escaping (String) async throws -> RSHTMLMetadata?) { + func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed, downloadMetadata: @escaping (String) async throws -> HTMLMetadata?) { guard !urlsInProgress.contains(homePageURL) else { return @@ -236,7 +236,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() { cacheIconURL(for: homePageURL, url) diff --git a/Modules/Images/Sources/Images/HTMLMetadata+Extension.swift b/Modules/Images/Sources/Images/HTMLMetadata+Extension.swift new file mode 100644 index 000000000..188791b61 --- /dev/null +++ b/Modules/Images/Sources/Images/HTMLMetadata+Extension.swift @@ -0,0 +1,67 @@ +// +// HTMLMetadata+Extension.swift +// NetNewsWire +// +// Created by Brent Simmons on 11/26/17. +// Copyright © 2017 Ranchero Software. All rights reserved. +// + +import Foundation +import Parser + +extension HTMLMetadata { + + func largestAppleTouchIcon() -> String? { + + guard let icons = appleTouchIcons, !icons.isEmpty else { + return nil + } + + var bestImage: HTMLMetadataAppleTouchIcon? = nil + + for image in icons { + + guard let imageSize = image.size else { + continue + } + if imageSize.width / imageSize.height > 2 { + continue + } + + guard let currentBestImage = bestImage, let bestImageSize = currentBestImage.size else { + bestImage = image + continue + } + + if imageSize.height > bestImageSize.height && imageSize.width > bestImageSize.width { + bestImage = image + } + } + + return bestImage?.urlString ?? icons.first?.urlString + } + + func bestWebsiteIconURL() -> String? { + + // TODO: metadata icons — sometimes they’re large enough to use here. + + if let appleTouchIcon = largestAppleTouchIcon() { + return appleTouchIcon + } + + if let openGraphImageURL = openGraphProperties?.image { + return openGraphImageURL.url + } + + return twitterProperties?.imageURL + } + + func bestFeaturedImageURL() -> String? { + + if let openGraphImageURL = openGraphProperties?.image { + return openGraphImageURL.url + } + + return twitterProperties?.imageURL + } +} diff --git a/Modules/Images/Sources/Images/RSHTMLMetadata+Extension.swift b/Modules/Images/Sources/Images/RSHTMLMetadata+Extension.swift deleted file mode 100644 index fd5da45d7..000000000 --- a/Modules/Images/Sources/Images/RSHTMLMetadata+Extension.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// RSHTMLMetadata+Extension.swift -// NetNewsWire -// -// Created by Brent Simmons on 11/26/17. -// Copyright © 2017 Ranchero Software. All rights reserved. -// - -import Foundation -import Parser - -extension RSHTMLMetadata { - - 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? { - - let icons = appleTouchIcons - - guard !icons.isEmpty else { - return nil - } - - var bestImage: RSHTMLMetadataAppleTouchIcon? = nil - - for image in icons { - if image.size.width / image.size.height > 2 { - continue - } - if bestImage == nil { - bestImage = image - continue - } - if image.size.height > bestImage!.size.height && image.size.width > bestImage!.size.width { - bestImage = image; - } - } - - return bestImage?.urlString - } - - func bestWebsiteIconURL() -> String? { - - // TODO: metadata icons — sometimes they’re large enough to use here. - - if let appleTouchIcon = largestAppleTouchIcon() { - return appleTouchIcon - } - - if let openGraphImageURL = largestOpenGraphImageURL() { - return openGraphImageURL - } - - return twitterProperties.imageURL - } - - func bestFeaturedImageURL() -> String? { - - if let openGraphImageURL = largestOpenGraphImageURL() { - return openGraphImageURL - } - - return twitterProperties.imageURL - } -} diff --git a/Modules/LocalAccount/Package.swift b/Modules/LocalAccount/Package.swift index ff06f3c41..f35444936 100644 --- a/Modules/LocalAccount/Package.swift +++ b/Modules/LocalAccount/Package.swift @@ -18,7 +18,7 @@ let package = Package( .target( name: "LocalAccount", dependencies: [ - .product(name: "FeedParser", package: "Parser"), + "Parser", "Web" ], swiftSettings: [ diff --git a/Modules/LocalAccount/Sources/LocalAccount/InitialFeedDownloader.swift b/Modules/LocalAccount/Sources/LocalAccount/InitialFeedDownloader.swift index b9df46c72..a384fec4d 100644 --- a/Modules/LocalAccount/Sources/LocalAccount/InitialFeedDownloader.swift +++ b/Modules/LocalAccount/Sources/LocalAccount/InitialFeedDownloader.swift @@ -7,7 +7,7 @@ // import Foundation -import FeedParser +import Parser import Web public struct InitialFeedDownloader { diff --git a/Modules/Parser/Sources/Parser/FeedParser/Feeds/FeedParser.swift b/Modules/Parser/Sources/Parser/FeedParser/Feeds/FeedParser.swift index cc124c43b..ea91797bb 100644 --- a/Modules/Parser/Sources/Parser/FeedParser/Feeds/FeedParser.swift +++ b/Modules/Parser/Sources/Parser/FeedParser/Feeds/FeedParser.swift @@ -49,4 +49,9 @@ public struct FeedParser { return nil } } + + public static func parseAsync(urlString: String, data: Data) async throws -> ParsedFeed? { + + try parse(urlString: urlString, data: data) + } } diff --git a/Modules/Parser/Sources/Parser/SAX/HTMLEntityDecoder.swift b/Modules/Parser/Sources/Parser/HTMLParser/HTMLEntityDecoder.swift similarity index 100% rename from Modules/Parser/Sources/Parser/SAX/HTMLEntityDecoder.swift rename to Modules/Parser/Sources/Parser/HTMLParser/HTMLEntityDecoder.swift diff --git a/Modules/Parser/Sources/Parser/OPMLParser/OPMLFeedSpecifier.swift b/Modules/Parser/Sources/Parser/OPMLParser/OPMLFeedSpecifier.swift index 2b5e43856..a0cd12df6 100644 --- a/Modules/Parser/Sources/Parser/OPMLParser/OPMLFeedSpecifier.swift +++ b/Modules/Parser/Sources/Parser/OPMLParser/OPMLFeedSpecifier.swift @@ -9,10 +9,10 @@ import Foundation public struct OPMLFeedSpecifier: Sendable { - let title: String? - let feedDescription: String? - let homePageURL: String? - let feedURL: String + public let title: String? + public let feedDescription: String? + public let homePageURL: String? + public let feedURL: String init(title: String?, feedDescription: String?, homePageURL: String?, feedURL: String) { diff --git a/Modules/Parser/Sources/Parser/OPMLParser/OPMLItem.swift b/Modules/Parser/Sources/Parser/OPMLParser/OPMLItem.swift index 2f0e972e9..57e4232dc 100644 --- a/Modules/Parser/Sources/Parser/OPMLParser/OPMLItem.swift +++ b/Modules/Parser/Sources/Parser/OPMLParser/OPMLItem.swift @@ -32,7 +32,7 @@ public class OPMLItem { } } - func add(_ item: OPMLItem) { + public func add(_ item: OPMLItem) { if items == nil { items = [OPMLItem]() diff --git a/Shared/AppDelegate+Shared.swift b/Shared/AppDelegate+Shared.swift index ebfe1a8d4..02484da2a 100644 --- a/Shared/AppDelegate+Shared.swift +++ b/Shared/AppDelegate+Shared.swift @@ -9,6 +9,7 @@ import Foundation import Images import Account +import Parser extension AppDelegate: FaviconDownloaderDelegate, FeedIconDownloaderDelegate { @@ -16,7 +17,7 @@ extension AppDelegate: FaviconDownloaderDelegate, FeedIconDownloaderDelegate { IconImage.appIcon } - func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata? { + func downloadMetadata(_ url: String) async throws -> HTMLMetadata? { await HTMLMetadataDownloader.downloadMetadata(for: url) } diff --git a/Shared/Extensions/ArticleStringFormatter.swift b/Shared/Extensions/ArticleStringFormatter.swift index a0d0c11e7..4d0484d08 100644 --- a/Shared/Extensions/ArticleStringFormatter.swift +++ b/Shared/Extensions/ArticleStringFormatter.swift @@ -66,7 +66,7 @@ import Parser s = s.replacingOccurrences(of: "\t", with: "") if !forHTML { - s = s.rsparser_stringByDecodingHTMLEntities() + s = HTMLEntityDecoder.decodedString(s) } s = s.trimmingWhitespace @@ -98,8 +98,9 @@ import Parser if let cachedBody = summaryCache[key] { return cachedBody } - var s = body.rsparser_stringByDecodingHTMLEntities() + var s = body s = s.strippingHTML(maxCharacters: 250) + s = HTMLEntityDecoder.decodedString(s) s = s.trimmingWhitespace s = s.collapsingWhitespace if s == "Comments" { // Hacker News. diff --git a/Shared/Extensions/NSAttributedString+NetNewsWire.swift b/Shared/Extensions/NSAttributedString+NetNewsWire.swift index d5b29c347..3e852d694 100644 --- a/Shared/Extensions/NSAttributedString+NetNewsWire.swift +++ b/Shared/Extensions/NSAttributedString+NetNewsWire.swift @@ -289,6 +289,6 @@ private struct CountedSet where Element: Hashable { private extension String { var decodedEntity: String { // It's possible the implementation will change, but for now it just calls this. - (self as NSString).rsparser_stringByDecodingHTMLEntities() as String + HTMLEntityDecoder.decodedString(self) } } diff --git a/Shared/HTMLMetadata/HTMLMetadataDownloader.swift b/Shared/HTMLMetadata/HTMLMetadataDownloader.swift index 87268b3cd..ce6b88176 100644 --- a/Shared/HTMLMetadata/HTMLMetadataDownloader.swift +++ b/Shared/HTMLMetadata/HTMLMetadataDownloader.swift @@ -10,11 +10,11 @@ import Foundation import Web import Parser -extension RSHTMLMetadata: @unchecked Sendable {} +extension HTMLMetadata: @unchecked Sendable {} struct HTMLMetadataDownloader { - @MainActor static func downloadMetadata(for url: String) async -> RSHTMLMetadata? { + @MainActor static func downloadMetadata(for url: String) async -> HTMLMetadata? { guard let actualURL = URL(string: url) else { return nil @@ -33,10 +33,10 @@ struct HTMLMetadataDownloader { return nil } - @MainActor private static func parseMetadata(with parserData: ParserData) async -> RSHTMLMetadata? { + @MainActor private static func parseMetadata(with parserData: ParserData) async -> HTMLMetadata? { - let task = Task.detached { () -> RSHTMLMetadata? in - RSHTMLMetadataParser.htmlMetadata(with: parserData) + let task = Task.detached { () -> HTMLMetadata? in + HTMLMetadataParser.metadata(with: parserData) } return await task.value