OPML export
This commit is contained in:
@@ -41,9 +41,12 @@ extern NSString *OPMLXMLURLKey; //xmlUrl
|
|||||||
@property (nonatomic, readonly) BOOL isFolder; // true if children.count > 0
|
@property (nonatomic, readonly) BOOL isFolder; // true if children.count > 0
|
||||||
@property (nonatomic, readonly) NSString *displayName; //May be nil.
|
@property (nonatomic, readonly) NSString *displayName; //May be nil.
|
||||||
|
|
||||||
|
+ (instancetype)itemWithAttributes:(NSDictionary *)attribs;
|
||||||
|
|
||||||
- (void)addChild:(RSOPMLItem *)child;
|
- (void)addChild:(RSOPMLItem *)child;
|
||||||
- (void)setAttribute:(id)value forKey:(NSString *)key;
|
- (void)setAttribute:(id)value forKey:(NSString *)key;
|
||||||
- (id)attributeForKey:(NSString *)key;
|
- (id)attributeForKey:(NSString *)key;
|
||||||
|
|
||||||
- (NSString *)recursiveDescription;
|
- (NSString *)recursiveDescription;
|
||||||
|
- (NSString *)exportOPMLAsString;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -42,26 +42,38 @@ NSString *OPMLXMLURLKey = @"xmlUrl";
|
|||||||
|
|
||||||
@implementation RSOPMLItem
|
@implementation RSOPMLItem
|
||||||
|
|
||||||
|
+ (instancetype)itemWithAttributes:(NSDictionary *)attribs {
|
||||||
|
RSOPMLItem *item = [[super alloc] init];
|
||||||
|
[item setAttributes:attribs];
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return A copy of the internal array.
|
||||||
- (NSArray *)children {
|
- (NSArray *)children {
|
||||||
return [self.mutableChildren copy];
|
return [self.mutableChildren copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace internal array with new one.
|
||||||
- (void)setChildren:(NSArray<RSOPMLItem*>*)children {
|
- (void)setChildren:(NSArray<RSOPMLItem*>*)children {
|
||||||
self.mutableChildren = [children mutableCopy];
|
self.mutableChildren = [children mutableCopy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return A copy of the internal dictionary.
|
||||||
- (NSDictionary *)attributes {
|
- (NSDictionary *)attributes {
|
||||||
return [self.mutableAttributes copy];
|
return [self.mutableAttributes copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace internal dictionary with new one.
|
||||||
- (void)setAttributes:(NSDictionary *)attributes {
|
- (void)setAttributes:(NSDictionary *)attributes {
|
||||||
self.mutableAttributes = [attributes mutableCopy];
|
self.mutableAttributes = [attributes mutableCopy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return @c YES if @c children.count @c > @c 0.
|
||||||
- (BOOL)isFolder {
|
- (BOOL)isFolder {
|
||||||
return self.mutableChildren.count > 0;
|
return self.mutableChildren.count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return Value for @c OPMLTitleKey. If not set, use @c OPMLTextKey, else return @c nil.
|
||||||
- (NSString *)displayName {
|
- (NSString *)displayName {
|
||||||
NSString *title = [self attributeForKey:OPMLTitleKey];
|
NSString *title = [self attributeForKey:OPMLTitleKey];
|
||||||
if (!title) {
|
if (!title) {
|
||||||
@@ -70,6 +82,7 @@ NSString *OPMLXMLURLKey = @"xmlUrl";
|
|||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends one child to the internal children array (creates new empty array if necessary).
|
||||||
- (void)addChild:(RSOPMLItem *)child {
|
- (void)addChild:(RSOPMLItem *)child {
|
||||||
if (!self.mutableChildren) {
|
if (!self.mutableChildren) {
|
||||||
self.mutableChildren = [NSMutableArray new];
|
self.mutableChildren = [NSMutableArray new];
|
||||||
@@ -77,6 +90,7 @@ NSString *OPMLXMLURLKey = @"xmlUrl";
|
|||||||
[self.mutableChildren addObject:child];
|
[self.mutableChildren addObject:child];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a value in the internal dictionary (creates new empty dictionary if necessary).
|
||||||
- (void)setAttribute:(id)value forKey:(NSString *)key {
|
- (void)setAttribute:(id)value forKey:(NSString *)key {
|
||||||
if (!self.mutableAttributes) {
|
if (!self.mutableAttributes) {
|
||||||
self.mutableAttributes = [NSMutableDictionary new];
|
self.mutableAttributes = [NSMutableDictionary new];
|
||||||
@@ -84,6 +98,7 @@ NSString *OPMLXMLURLKey = @"xmlUrl";
|
|||||||
[self.mutableAttributes setValue:value forKey:key];
|
[self.mutableAttributes setValue:value forKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return Value for key (case-independent).
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
if (self.mutableAttributes.count > 0 && key && key.length > 0) {
|
if (self.mutableAttributes.count > 0 && key && key.length > 0) {
|
||||||
return [self.mutableAttributes rsxml_objectForCaseInsensitiveKey:key];
|
return [self.mutableAttributes rsxml_objectForCaseInsensitiveKey:key];
|
||||||
@@ -102,6 +117,7 @@ NSString *OPMLXMLURLKey = @"xmlUrl";
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used by @c recursiveDescription.
|
||||||
- (void)appendStringRecursive:(NSMutableString *)str indent:(NSString *)prefix {
|
- (void)appendStringRecursive:(NSMutableString *)str indent:(NSString *)prefix {
|
||||||
[str appendFormat:@"%@%@\n", prefix, self];
|
[str appendFormat:@"%@%@\n", prefix, self];
|
||||||
if (self.isFolder) {
|
if (self.isFolder) {
|
||||||
@@ -112,10 +128,65 @@ NSString *OPMLXMLURLKey = @"xmlUrl";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print object description for debugging purposes.
|
||||||
- (NSString *)recursiveDescription {
|
- (NSString *)recursiveDescription {
|
||||||
NSMutableString *mStr = [NSMutableString new];
|
NSMutableString *mStr = [NSMutableString new];
|
||||||
[self appendStringRecursive:mStr indent:@""];
|
[self appendStringRecursive:mStr indent:@""];
|
||||||
return mStr;
|
return mStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return Nicely formatted string that can be used to export as @c .opml file.
|
||||||
|
- (NSString *)exportOPMLAsString {
|
||||||
|
NSMutableString *str = [NSMutableString new];
|
||||||
|
[str appendString:
|
||||||
|
@"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
|
@"<opml version=\"1.0\">\n"
|
||||||
|
@" <head>\n"];
|
||||||
|
[self appendHeaderTagsToString:str prefix:@" "];
|
||||||
|
[str appendString:
|
||||||
|
@" </head>\n"
|
||||||
|
@" <body>\n"];
|
||||||
|
for (RSOPMLItem *child in _mutableChildren) {
|
||||||
|
[child appendChildAttributesToString:str prefix:@" "];
|
||||||
|
}
|
||||||
|
[str appendString:
|
||||||
|
@" </body>\n"
|
||||||
|
@"</opml>"];
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The header attributes are added as separate tags. Quite opposite to outline items.
|
||||||
|
@note Used by @c exportOPMLAsString.
|
||||||
|
*/
|
||||||
|
- (void)appendHeaderTagsToString:(NSMutableString *)str prefix:(NSString *)prefix {
|
||||||
|
for (NSString *key in _mutableAttributes) {
|
||||||
|
[str appendFormat:@"%1$@<%2$@>%3$@</%2$@>\n", prefix, key, _mutableAttributes[key]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create outline items for this @c RSOPMLItem and all children recursively.
|
||||||
|
@note Used by @c exportOPMLAsString.
|
||||||
|
*/
|
||||||
|
- (void)appendChildAttributesToString:(NSMutableString *)str prefix:(NSString *)prefix {
|
||||||
|
NSString *name = [self displayName];
|
||||||
|
[str appendFormat:@"%1$@<outline title=\"%2$@\" text=\"%2$@\"", prefix, name]; // name comes first
|
||||||
|
for (NSString *key in _mutableAttributes) {
|
||||||
|
if ([key isEqualToString:OPMLTitleKey] || [key isEqualToString:OPMLTextKey]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[str appendFormat:@" %@=\"%@\"", key, _mutableAttributes[key]];
|
||||||
|
}
|
||||||
|
[str appendString:@">"];
|
||||||
|
if (_mutableChildren.count > 0) {
|
||||||
|
[str appendString:@"\n"];
|
||||||
|
for (RSOPMLItem *child in _mutableChildren) {
|
||||||
|
[child appendChildAttributesToString:str prefix:[prefix stringByAppendingString:@" "]];
|
||||||
|
}
|
||||||
|
[str appendString:prefix];
|
||||||
|
}
|
||||||
|
[str appendString:@"</outline>\n"];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -42,6 +42,38 @@
|
|||||||
return [[RSXMLData alloc] initWithData:d urlString:[NSString stringWithFormat:@"%@.%@", name, ext]];
|
return [[RSXMLData alloc] initWithData:d urlString:[NSString stringWithFormat:@"%@.%@", name, ext]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testOPMLExport {
|
||||||
|
RSOPMLItem *doc = [RSOPMLItem itemWithAttributes:@{OPMLTitleKey : @"Greetings from CCC",
|
||||||
|
@"dateCreated" : @"2018-12-27 23:12:04 +0100",
|
||||||
|
@"ownerName" : @"RSXML Parser"}];
|
||||||
|
[doc addChild:[RSOPMLItem itemWithAttributes:@{OPMLTitleKey : @"Feed Title 1",
|
||||||
|
OPMLHMTLURLKey : @"http://www.feed1.com/",
|
||||||
|
OPMLXMLURLKey : @"http://www.feed1.com/feed.rss",
|
||||||
|
OPMLTypeKey : @"rss"}]];
|
||||||
|
[doc addChild:[RSOPMLItem itemWithAttributes:@{OPMLTitleKey : @"Feed Title 2",
|
||||||
|
OPMLHMTLURLKey : @"http://www.feed2.com/",
|
||||||
|
OPMLXMLURLKey : @"http://www.feed2.com/feed.atom",
|
||||||
|
OPMLTypeKey : @"rss"}]];
|
||||||
|
|
||||||
|
NSString *exportString = [doc exportOPMLAsString];
|
||||||
|
NSLog(@"%@", exportString);
|
||||||
|
|
||||||
|
NSData *importData = [exportString dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
RSXMLData *xmlData = [[RSXMLData alloc] initWithData:importData urlString:@"none"];
|
||||||
|
XCTAssertEqual(xmlData.parserClass, [RSOPMLParser class]);
|
||||||
|
RSOPMLParser *parser = [[RSOPMLParser alloc] initWithXMLData:xmlData];
|
||||||
|
XCTAssertNotNil(parser);
|
||||||
|
NSError *error;
|
||||||
|
RSOPMLItem *document = [parser parseSync:&error];
|
||||||
|
XCTAssertNil(error);
|
||||||
|
XCTAssertEqual(document.children.count, 2u);
|
||||||
|
XCTAssertEqualObjects(document.displayName, @"Greetings from CCC");
|
||||||
|
XCTAssertEqualObjects(document.children.firstObject.displayName, @"Feed Title 1");
|
||||||
|
XCTAssertEqualObjects([document.children.lastObject attributeForKey:OPMLXMLURLKey], @"http://www.feed2.com/feed.atom");
|
||||||
|
|
||||||
|
NSLog(@"%@", [document recursiveDescription]);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)testNotOPML {
|
- (void)testNotOPML {
|
||||||
|
|
||||||
NSError *error;
|
NSError *error;
|
||||||
|
|||||||
Reference in New Issue
Block a user