OPML export

This commit is contained in:
relikd
2018-12-29 01:26:06 +01:00
parent 62c5bef463
commit 0bd0e63f12
3 changed files with 106 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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;