diff --git a/Modules/Parser/Sources/Parser/OPML/OPMLAttributes.swift b/Modules/Parser/Sources/Parser/OPML/OPMLAttributes.swift new file mode 100644 index 000000000..0669e0e53 --- /dev/null +++ b/Modules/Parser/Sources/Parser/OPML/OPMLAttributes.swift @@ -0,0 +1,53 @@ +// +// OPMLAttributes.swift +// +// +// Created by Brent Simmons on 8/18/24. +// + +import Foundation + +// OPML allows for arbitrary attributes. +// These are the common attributes in OPML files used as RSS subscription lists. + +private static let opmlTextKey = "text" +private static let opmlTitleKey = "title" +private static let opmlDescriptionKey = "description" +private static let opmlTypeKey = "type" +private static let opmlVersionKey = "version" +private static let opmlHMTLURLKey = "htmlUrl" +private static let opmlXMLURLKey = "xmlUrl" + +// A frequent error in OPML files is to mess up the capitalization, +// so these do a case-insensitive lookup. + +extension Dictionary where Key == String, Value == String { + + var opml_text: String? { + object(forCaseInsensitiveKey: opmlTextKey) + } + + var opml_title: String? { + object(forCaseInsensitiveKey: opmlTitleKey) + } + + var opml_description: String? { + object(forCaseInsensitiveKey: opmlDescriptionKey) + } + + var opml_type: String? { + object(forCaseInsensitiveKey: opmlTypeKey) + } + + var opml_version: String? { + object(forCaseInsensitiveKey: opmlVersionKey) + } + + var opml_htmlUrl: String? { + object(forCaseInsensitiveKey: opmlHMTLURLKey) + } + + var opml_xmlUrl: String? { + object(forCaseInsensitiveKey: opmlXMLURLKey) + } +} diff --git a/Modules/Parser/Sources/Parser/OPML/OPMLItem.swift b/Modules/Parser/Sources/Parser/OPML/OPMLItem.swift new file mode 100644 index 000000000..76a170060 --- /dev/null +++ b/Modules/Parser/Sources/Parser/OPML/OPMLItem.swift @@ -0,0 +1,42 @@ +// +// OPMLItem.swift +// +// +// Created by Brent Simmons on 8/18/24. +// + +import Foundation +import os + +public final class OPMLItem: Sendable { + + public let feedSpecifier: OPMLFeedSpecifier + + public let attributes: [String: String] + public let titleFromAttributes: String? + + public let isFolder: Bool + public let children: [OPMLItem]? + + init?(attributes: [String : String], children: [OPMLItem]?) { + + guard let feedURL = attributes.opml_xmlUrl, !feedURL.isEmpty else { + return nil + } + + let titleFromAttributes = { + if let title = attributes.opml_title { + return title + } + return attributes.opml_text + }() + self.titleFromAttributes = titleFromAttributes + + self.feedSpecifier = OPMLFeedSpecifier(title: titleFromAttributes, feedDescription: attributes.opml_description, homePageURL: attributes.opml_htmlUrl, feedURL: feedURL) + + self.attributes = attributes + + self.children = children + self.isFolder = (children?.count ?? 0) > 0 + } +} diff --git a/Modules/Parser/Sources/Parser/Utilities/Dictionary+Parser.swift b/Modules/Parser/Sources/Parser/Utilities/Dictionary+Parser.swift new file mode 100644 index 000000000..828316931 --- /dev/null +++ b/Modules/Parser/Sources/Parser/Utilities/Dictionary+Parser.swift @@ -0,0 +1,28 @@ +// +// Dictionary+Parser.swift +// +// +// Created by Brent Simmons on 8/18/24. +// + +import Foundation + +extension Dictionary where Key == String, Value == String { + + func object(forCaseInsensitiveKey key: String) -> String? { + + if let object = self[key] { + return object + } + + let lowercaseKey = key.lowercased() + + for (oneKey, oneValue) in self { + if lowercaseKey.caseInsensitiveCompare(oneKey) == .orderedSame { + return oneValue + } + } + + return nil + } +} diff --git a/Modules/Parser/Sources/Parser/Utilities/String+RSParser.swift b/Modules/Parser/Sources/Parser/Utilities/String+Parser.swift similarity index 100% rename from Modules/Parser/Sources/Parser/Utilities/String+RSParser.swift rename to Modules/Parser/Sources/Parser/Utilities/String+Parser.swift diff --git a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLAttributes.h b/Modules/ParserObjC/Sources/ParserObjC/RSOPMLAttributes.h deleted file mode 100755 index 688132fe5..000000000 --- a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLAttributes.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// RSOPMLAttributes.h -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -// OPML allows for arbitrary attributes. -// These are the common attributes in OPML files used as RSS subscription lists. - -extern NSString *OPMLTextKey; //text -extern NSString *OPMLTitleKey; //title -extern NSString *OPMLDescriptionKey; //description -extern NSString *OPMLTypeKey; //type -extern NSString *OPMLVersionKey; //version -extern NSString *OPMLHMTLURLKey; //htmlUrl -extern NSString *OPMLXMLURLKey; //xmlUrl - - -@interface NSDictionary (RSOPMLAttributes) - -// A frequent error in OPML files is to mess up the capitalization, -// so these do a case-insensitive lookup. - -@property (nonatomic, readonly) NSString *opml_text; -@property (nonatomic, readonly) NSString *opml_title; -@property (nonatomic, readonly) NSString *opml_description; -@property (nonatomic, readonly) NSString *opml_type; -@property (nonatomic, readonly) NSString *opml_version; -@property (nonatomic, readonly) NSString *opml_htmlUrl; -@property (nonatomic, readonly) NSString *opml_xmlUrl; - -@end diff --git a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLAttributes.m b/Modules/ParserObjC/Sources/ParserObjC/RSOPMLAttributes.m deleted file mode 100755 index db6508b24..000000000 --- a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLAttributes.m +++ /dev/null @@ -1,68 +0,0 @@ -// -// RSOPMLAttributes.m -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSOPMLAttributes.h" -#import "RSParserInternal.h" - - - - -NSString *OPMLTextKey = @"text"; -NSString *OPMLTitleKey = @"title"; -NSString *OPMLDescriptionKey = @"description"; -NSString *OPMLTypeKey = @"type"; -NSString *OPMLVersionKey = @"version"; -NSString *OPMLHMTLURLKey = @"htmlUrl"; -NSString *OPMLXMLURLKey = @"xmlUrl"; - - -@implementation NSDictionary (RSOPMLAttributes) - -- (NSString *)opml_text { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLTextKey]; -} - - -- (NSString *)opml_title { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLTitleKey]; -} - - -- (NSString *)opml_description { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLDescriptionKey]; -} - - -- (NSString *)opml_type { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLTypeKey]; -} - - -- (NSString *)opml_version { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLVersionKey]; -} - - -- (NSString *)opml_htmlUrl { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLHMTLURLKey]; -} - - -- (NSString *)opml_xmlUrl { - - return [self rsparser_objectForCaseInsensitiveKey:OPMLXMLURLKey]; -} - - -@end diff --git a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLItem.h b/Modules/ParserObjC/Sources/ParserObjC/RSOPMLItem.h deleted file mode 100755 index 15afa48f2..000000000 --- a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLItem.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// RSOPMLItem.h -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -@import Foundation; - -@class RSOPMLFeedSpecifier; - -NS_ASSUME_NONNULL_BEGIN - -@interface RSOPMLItem : NSObject - -@property (nonatomic, nullable) NSDictionary *attributes; -@property (nonatomic, nullable) NSArray *children; - -- (void)addChild:(RSOPMLItem *)child; - -@property (nonatomic, nullable, readonly) RSOPMLFeedSpecifier *feedSpecifier; - -@property (nonatomic, nullable, readonly) NSString *titleFromAttributes; -@property (nonatomic, readonly) BOOL isFolder; - -@end - -NS_ASSUME_NONNULL_END - diff --git a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLItem.m b/Modules/ParserObjC/Sources/ParserObjC/RSOPMLItem.m deleted file mode 100755 index a273cd317..000000000 --- a/Modules/ParserObjC/Sources/ParserObjC/RSOPMLItem.m +++ /dev/null @@ -1,87 +0,0 @@ -// -// RSOPMLItem.m -// RSParser -// -// Created by Brent Simmons on 2/28/16. -// Copyright © 2016 Ranchero Software, LLC. All rights reserved. -// - -#import "RSOPMLItem.h" -#import "RSOPMLAttributes.h" -#import "RSOPMLFeedSpecifier.h" -#import "RSParserInternal.h" - - - -@interface RSOPMLItem () - -@property (nonatomic) NSMutableArray *mutableChildren; - -@end - - -@implementation RSOPMLItem - -@synthesize children = _children; -@synthesize feedSpecifier = _feedSpecifier; - - -- (NSArray *)children { - - return [self.mutableChildren copy]; -} - - -- (void)setChildren:(NSArray *)children { - - _children = children; - self.mutableChildren = [_children mutableCopy]; -} - - -- (void)addChild:(RSOPMLItem *)child { - - if (!self.mutableChildren) { - self.mutableChildren = [NSMutableArray new]; - } - - [self.mutableChildren addObject:child]; -} - - -- (RSOPMLFeedSpecifier *)feedSpecifier { - - if (_feedSpecifier) { - return _feedSpecifier; - } - - NSString *feedURL = self.attributes.opml_xmlUrl; - if (RSParserObjectIsEmpty(feedURL)) { - return nil; - } - - _feedSpecifier = [[RSOPMLFeedSpecifier alloc] initWithTitle:self.titleFromAttributes feedDescription:self.attributes.opml_description homePageURL:self.attributes.opml_htmlUrl feedURL:feedURL]; - - return _feedSpecifier; -} - -- (NSString *)titleFromAttributes { - - NSString *title = self.attributes.opml_title; - if (title) { - return title; - } - title = self.attributes.opml_text; - if (title) { - return title; - } - - return nil; -} - -- (BOOL)isFolder { - - return self.mutableChildren.count > 0; -} - -@end