Refactored OPML

This commit is contained in:
relikd
2018-12-16 19:18:49 +01:00
parent 3118747fa0
commit f9e672661a
21 changed files with 249 additions and 569 deletions

View File

@@ -10,14 +10,8 @@
54FCE5F421493B5E00FABB65 /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 54FCE5F321493B5E00FABB65 /* Resources */; };
8400B0F01B8C20A9004C4CFF /* RSXMLData.h in Headers */ = {isa = PBXBuildFile; fileRef = 8400B0EE1B8C20A9004C4CFF /* RSXMLData.h */; settings = {ATTRIBUTES = (Public, ); }; };
8400B0F11B8C20A9004C4CFF /* RSXMLData.m in Sources */ = {isa = PBXBuildFile; fileRef = 8400B0EF1B8C20A9004C4CFF /* RSXMLData.m */; };
8429D1AC1C839FFC00F97695 /* RSOPMLDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8429D1AA1C839FFC00F97695 /* RSOPMLDocument.h */; settings = {ATTRIBUTES = (Public, ); }; };
8429D1AD1C839FFC00F97695 /* RSOPMLDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1AB1C839FFC00F97695 /* RSOPMLDocument.m */; };
8429D1B61C83A03100F97695 /* RSOPMLItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 8429D1B41C83A03100F97695 /* RSOPMLItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
8429D1B71C83A03100F97695 /* RSOPMLItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1B51C83A03100F97695 /* RSOPMLItem.m */; };
8429D1BA1C83A31C00F97695 /* RSOPMLAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 8429D1B81C83A31C00F97695 /* RSOPMLAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; };
8429D1BB1C83A31C00F97695 /* RSOPMLAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1B91C83A31C00F97695 /* RSOPMLAttributes.m */; };
8429D1BE1C83AD0F00F97695 /* RSOPMLFeedSpecifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 8429D1BC1C83AD0F00F97695 /* RSOPMLFeedSpecifier.h */; settings = {ATTRIBUTES = (Public, ); }; };
8429D1BF1C83AD0F00F97695 /* RSOPMLFeedSpecifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1BD1C83AD0F00F97695 /* RSOPMLFeedSpecifier.m */; };
8429D1C31C83BCCB00F97695 /* RSOPMLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1C21C83BCCB00F97695 /* RSOPMLTests.m */; };
842D514C1B52E7FC00E63D52 /* RSAtomParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 842D514A1B52E7FC00E63D52 /* RSAtomParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
842D514D1B52E7FC00E63D52 /* RSAtomParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 842D514B1B52E7FC00E63D52 /* RSAtomParser.m */; };
@@ -55,14 +49,8 @@
84AD0C171E11B8CA00B38510 /* RSDateParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 84AD0BF41E11A6FB00B38510 /* RSDateParser.m */; };
84AD0C181E11B8CF00B38510 /* RSOPMLParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 842D51781B5311AD00E63D52 /* RSOPMLParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
84AD0C191E11B8CF00B38510 /* RSOPMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 842D51791B5311AD00E63D52 /* RSOPMLParser.m */; };
84AD0C1A1E11B8CF00B38510 /* RSOPMLDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 8429D1AA1C839FFC00F97695 /* RSOPMLDocument.h */; settings = {ATTRIBUTES = (Public, ); }; };
84AD0C1B1E11B8CF00B38510 /* RSOPMLDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1AB1C839FFC00F97695 /* RSOPMLDocument.m */; };
84AD0C1C1E11B8CF00B38510 /* RSOPMLItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 8429D1B41C83A03100F97695 /* RSOPMLItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
84AD0C1D1E11B8CF00B38510 /* RSOPMLItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1B51C83A03100F97695 /* RSOPMLItem.m */; };
84AD0C1E1E11B8CF00B38510 /* RSOPMLFeedSpecifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 8429D1BC1C83AD0F00F97695 /* RSOPMLFeedSpecifier.h */; settings = {ATTRIBUTES = (Public, ); }; };
84AD0C1F1E11B8CF00B38510 /* RSOPMLFeedSpecifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1BD1C83AD0F00F97695 /* RSOPMLFeedSpecifier.m */; };
84AD0C201E11B8CF00B38510 /* RSOPMLAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 8429D1B81C83A31C00F97695 /* RSOPMLAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; };
84AD0C211E11B8CF00B38510 /* RSOPMLAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 8429D1B91C83A31C00F97695 /* RSOPMLAttributes.m */; };
84AD0C221E11B8D400B38510 /* RSFeedParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 842D51501B52E80100E63D52 /* RSFeedParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
84AD0C231E11B8D400B38510 /* RSFeedParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 842D51511B52E80100E63D52 /* RSFeedParser.m */; };
84AD0C241E11B8D400B38510 /* FeedParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 842D516D1B5308BD00E63D52 /* FeedParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -116,14 +104,8 @@
54FCE5F321493B5E00FABB65 /* Resources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Resources; sourceTree = "<group>"; };
8400B0EE1B8C20A9004C4CFF /* RSXMLData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RSXMLData.h; path = RSXML/RSXMLData.h; sourceTree = "<group>"; };
8400B0EF1B8C20A9004C4CFF /* RSXMLData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSXMLData.m; path = RSXML/RSXMLData.m; sourceTree = "<group>"; };
8429D1AA1C839FFC00F97695 /* RSOPMLDocument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSOPMLDocument.h; sourceTree = "<group>"; };
8429D1AB1C839FFC00F97695 /* RSOPMLDocument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSOPMLDocument.m; sourceTree = "<group>"; };
8429D1B41C83A03100F97695 /* RSOPMLItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSOPMLItem.h; sourceTree = "<group>"; };
8429D1B51C83A03100F97695 /* RSOPMLItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSOPMLItem.m; sourceTree = "<group>"; };
8429D1B81C83A31C00F97695 /* RSOPMLAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSOPMLAttributes.h; sourceTree = "<group>"; };
8429D1B91C83A31C00F97695 /* RSOPMLAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RSOPMLAttributes.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
8429D1BC1C83AD0F00F97695 /* RSOPMLFeedSpecifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSOPMLFeedSpecifier.h; sourceTree = "<group>"; };
8429D1BD1C83AD0F00F97695 /* RSOPMLFeedSpecifier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSOPMLFeedSpecifier.m; sourceTree = "<group>"; };
8429D1C21C83BCCB00F97695 /* RSOPMLTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSOPMLTests.m; sourceTree = "<group>"; };
842D514A1B52E7FC00E63D52 /* RSAtomParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSAtomParser.h; sourceTree = "<group>"; };
842D514B1B52E7FC00E63D52 /* RSAtomParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSAtomParser.m; sourceTree = "<group>"; };
@@ -223,14 +205,8 @@
children = (
842D51781B5311AD00E63D52 /* RSOPMLParser.h */,
842D51791B5311AD00E63D52 /* RSOPMLParser.m */,
8429D1AA1C839FFC00F97695 /* RSOPMLDocument.h */,
8429D1AB1C839FFC00F97695 /* RSOPMLDocument.m */,
8429D1B41C83A03100F97695 /* RSOPMLItem.h */,
8429D1B51C83A03100F97695 /* RSOPMLItem.m */,
8429D1BC1C83AD0F00F97695 /* RSOPMLFeedSpecifier.h */,
8429D1BD1C83AD0F00F97695 /* RSOPMLFeedSpecifier.m */,
8429D1B81C83A31C00F97695 /* RSOPMLAttributes.h */,
8429D1B91C83A31C00F97695 /* RSOPMLAttributes.m */,
);
name = OPML;
path = RSXML;
@@ -332,9 +308,7 @@
84AD0C161E11B8CA00B38510 /* RSDateParser.h in Headers */,
84AD0C101E11B8CA00B38510 /* RSSAXParser.h in Headers */,
84AD0C331E11B8DA00B38510 /* RSHTMLLinkParser.h in Headers */,
84AD0C201E11B8CF00B38510 /* RSOPMLAttributes.h in Headers */,
84AD0C2F1E11B8DA00B38510 /* RSHTMLMetadataParser.h in Headers */,
84AD0C1A1E11B8CF00B38510 /* RSOPMLDocument.h in Headers */,
84AD0C251E11B8D400B38510 /* RSAtomParser.h in Headers */,
84AD0C121E11B8CA00B38510 /* RSXMLData.h in Headers */,
84AD0C311E11B8DA00B38510 /* RSHTMLMetadata.h in Headers */,
@@ -342,7 +316,6 @@
84AD0C221E11B8D400B38510 /* RSFeedParser.h in Headers */,
84AD0C2D1E11B8DA00B38510 /* RSSAXHTMLParser.h in Headers */,
84AD0C0E1E11B8CA00B38510 /* RSXMLError.h in Headers */,
84AD0C1E1E11B8CF00B38510 /* RSOPMLFeedSpecifier.h in Headers */,
84AD0C2B1E11B8D400B38510 /* RSParsedArticle.h in Headers */,
84AD0C291E11B8D400B38510 /* RSParsedFeed.h in Headers */,
84AD0C181E11B8CF00B38510 /* RSOPMLParser.h in Headers */,
@@ -361,16 +334,13 @@
8400B0F01B8C20A9004C4CFF /* RSXMLData.h in Headers */,
842D51631B53058B00E63D52 /* RSParsedArticle.h in Headers */,
842D517A1B5311AD00E63D52 /* RSOPMLParser.h in Headers */,
8429D1AC1C839FFC00F97695 /* RSOPMLDocument.h in Headers */,
84BF3E161C8CDD1A005562D8 /* RSHTMLMetadataParser.h in Headers */,
842D51761B530BF200E63D52 /* RSParsedFeed.h in Headers */,
8429D1BA1C83A31C00F97695 /* RSOPMLAttributes.h in Headers */,
84BF3E1C1C8CDD6D005562D8 /* RSHTMLMetadata.h in Headers */,
8429D1B61C83A03100F97695 /* RSOPMLItem.h in Headers */,
843819001C8CB00400E2A1DD /* RSSAXHTMLParser.h in Headers */,
842D51521B52E80100E63D52 /* RSFeedParser.h in Headers */,
84E4BE451C8B8FE400A90B41 /* RSXMLError.h in Headers */,
8429D1BE1C83AD0F00F97695 /* RSOPMLFeedSpecifier.h in Headers */,
842D516F1B5308BD00E63D52 /* FeedParser.h in Headers */,
84F22C111B52DDEA000060CE /* RSXML.h in Headers */,
842D514C1B52E7FC00E63D52 /* RSAtomParser.h in Headers */,
@@ -524,11 +494,8 @@
84AD0C261E11B8D400B38510 /* RSAtomParser.m in Sources */,
84AD0C1D1E11B8CF00B38510 /* RSOPMLItem.m in Sources */,
84AD0C131E11B8CA00B38510 /* RSXMLData.m in Sources */,
84AD0C211E11B8CF00B38510 /* RSOPMLAttributes.m in Sources */,
84AD0C321E11B8DA00B38510 /* RSHTMLMetadata.m in Sources */,
84AD0C1F1E11B8CF00B38510 /* RSOPMLFeedSpecifier.m in Sources */,
84AD0C111E11B8CA00B38510 /* RSSAXParser.m in Sources */,
84AD0C1B1E11B8CF00B38510 /* RSOPMLDocument.m in Sources */,
84AD0C0F1E11B8CA00B38510 /* RSXMLError.m in Sources */,
84AD0C2E1E11B8DA00B38510 /* RSSAXHTMLParser.m in Sources */,
);
@@ -542,14 +509,11 @@
842D515B1B52E81B00E63D52 /* RSRSSParser.m in Sources */,
842D517B1B5311AD00E63D52 /* RSOPMLParser.m in Sources */,
8486F1161BB646140092794F /* NSString+RSXML.m in Sources */,
8429D1BB1C83A31C00F97695 /* RSOPMLAttributes.m in Sources */,
84AD0BF61E11A6FB00B38510 /* RSDateParser.m in Sources */,
84BF3E171C8CDD1A005562D8 /* RSHTMLMetadataParser.m in Sources */,
842D51641B53058B00E63D52 /* RSParsedArticle.m in Sources */,
84E4BE461C8B8FE400A90B41 /* RSXMLError.m in Sources */,
8475C4091D57AB4C0076751E /* RSHTMLLinkParser.m in Sources */,
8429D1BF1C83AD0F00F97695 /* RSOPMLFeedSpecifier.m in Sources */,
8429D1AD1C839FFC00F97695 /* RSOPMLDocument.m in Sources */,
8429D1B71C83A03100F97695 /* RSOPMLItem.m in Sources */,
84BF3E1D1C8CDD6D005562D8 /* RSHTMLMetadata.m in Sources */,
842D51531B52E80100E63D52 /* RSFeedParser.m in Sources */,

View File

@@ -552,12 +552,6 @@ static const NSInteger kSelfLength = 5;
}
static BOOL equalBytes(const void *bytes1, const void *bytes2, NSUInteger length) {
return memcmp(bytes1, bytes2, length) == 0;
}
- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length {
static const NSUInteger alternateLength = kAlternateLength - 1;
@@ -569,35 +563,35 @@ static BOOL equalBytes(const void *bytes1, const void *bytes2, NSUInteger length
static const NSUInteger textLength = kTextLength - 1;
static const NSUInteger selfLength = kSelfLength - 1;
if (length == alternateLength && equalBytes(bytes, kAlternate, alternateLength)) {
if (length == alternateLength && RSSAXEqualBytes(bytes, kAlternate, alternateLength)) {
return kAlternateValue;
}
if (length == textHTMLLength && equalBytes(bytes, kTextHTML, textHTMLLength)) {
if (length == textHTMLLength && RSSAXEqualBytes(bytes, kTextHTML, textHTMLLength)) {
return kTextHTMLValue;
}
if (length == relatedLength && equalBytes(bytes, kRelated, relatedLength)) {
if (length == relatedLength && RSSAXEqualBytes(bytes, kRelated, relatedLength)) {
return kRelatedValue;
}
if (length == shortURLLength && equalBytes(bytes, kShortURL, shortURLLength)) {
if (length == shortURLLength && RSSAXEqualBytes(bytes, kShortURL, shortURLLength)) {
return kShortURLValue;
}
if (length == htmlLength && equalBytes(bytes, kHTML, htmlLength)) {
if (length == htmlLength && RSSAXEqualBytes(bytes, kHTML, htmlLength)) {
return kHTMLValue;
}
if (length == enLength && equalBytes(bytes, kEn, enLength)) {
if (length == enLength && RSSAXEqualBytes(bytes, kEn, enLength)) {
return kEnValue;
}
if (length == textLength && equalBytes(bytes, kText, textLength)) {
if (length == textLength && RSSAXEqualBytes(bytes, kText, textLength)) {
return kTextValue;
}
if (length == selfLength && equalBytes(bytes, kSelf, selfLength)) {
if (length == selfLength && RSSAXEqualBytes(bytes, kSelf, selfLength)) {
return kSelfValue;
}

View File

@@ -16,9 +16,6 @@
NS_ASSUME_NONNULL_BEGIN
static NSString *kLIBXMLParserErrorDomain = @"LIBXMLParserErrorDomain";
static NSString *kRSXMLParserErrorDomain = @"RSXMLParserErrorDomain";
BOOL RSCanParseFeed(RSXMLData *xmlData);

View File

@@ -6,7 +6,7 @@
// Copyright (c) 2015 Ranchero Software LLC. All rights reserved.
//
#import <libxml/xmlerror.h>
#import "RSXMLError.h"
#import "RSFeedParser.h"
#import "FeedParser.h"
#import "RSXMLData.h"
@@ -49,43 +49,10 @@ static BOOL dataHasLeftCaret(const char *bytes, NSUInteger numberOfBytes);
static const NSUInteger maxNumberOfBytesToSearch = 4096;
static const NSUInteger minNumberOfBytesToSearch = 20;
typedef enum {
RSXMLErrorNoData = 100,
RSXMLErrorMissingLeftCaret,
RSXMLErrorProbablyHTML,
RSXMLErrorContainsXMLErrorsTag,
RSXMLErrorNoSuitableParser
} RSXMLError;
static void setError(NSError **error, RSXMLError code) {
if (!error) {
return;
}
NSString *msg = @"";
switch (code) { // switch statement will warn if an enum value is missing
case RSXMLErrorNoData:
msg = @"Couldn't parse feed. No data available.";
break;
case RSXMLErrorMissingLeftCaret:
msg = @"Couldn't parse feed. Missing left caret character ('<').";
break;
case RSXMLErrorProbablyHTML:
msg = @"Couldn't parse feed. Expecting XML data but found html data.";
break;
case RSXMLErrorContainsXMLErrorsTag:
msg = @"Couldn't parse feed. XML contains 'errors' tag.";
break;
case RSXMLErrorNoSuitableParser:
msg = @"Couldn't parse feed. No suitable parser found. XML document not well-formed.";
break;
}
*error = [NSError errorWithDomain:kRSXMLParserErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey: msg}];
}
static Class parserClassForXMLData(RSXMLData *xmlData, NSError **error) {
if (!feedMayBeParseable(xmlData)) {
setError(error, RSXMLErrorNoData);
RSXMLSetError(error, RSXMLErrorNoData, nil);
return nil;
}
@@ -101,7 +68,7 @@ static Class parserClassForXMLData(RSXMLData *xmlData, NSError **error) {
}
if (!dataHasLeftCaret(bytes, numberOfBytes)) {
setError(error, RSXMLErrorMissingLeftCaret);
RSXMLSetError(error, RSXMLErrorMissingLeftCaret, nil);
return nil;
}
if (optimisticCanParseRSSData(bytes, numberOfBytes)) {
@@ -114,11 +81,11 @@ static Class parserClassForXMLData(RSXMLData *xmlData, NSError **error) {
return [RSRSSParser class]; //TODO: parse RDF feeds, using RSS parser so far ...
}
if (dataIsProbablyHTML(bytes, numberOfBytes)) {
setError(error, RSXMLErrorProbablyHTML);
RSXMLSetError(error, RSXMLErrorProbablyHTML, nil);
return nil;
}
if (dataIsSomeWeirdException(bytes, numberOfBytes)) {
setError(error, RSXMLErrorContainsXMLErrorsTag);
RSXMLSetError(error, RSXMLErrorContainsXMLErrorsTag, nil);
return nil;
}
}
@@ -130,7 +97,7 @@ static Class parserClassForXMLData(RSXMLData *xmlData, NSError **error) {
}
}
// Try RSS anyway? libxml would return a parsing error
setError(error, RSXMLErrorNoSuitableParser);
RSXMLSetError(error, RSXMLErrorNoSuitableParser, nil);
return nil;
}
@@ -250,19 +217,11 @@ RSParsedFeed *RSParseFeedSync(RSXMLData *xmlData, NSError **error) {
xmlResetLastError();
id<FeedParser> parser = parserForXMLData(xmlData, error);
if (error && *error) {
//printf("ERROR in parserForXMLData(): %s\n", [[*error localizedDescription] UTF8String]);
return nil;
}
RSParsedFeed *parsedResult = [parser parseFeed];
xmlErrorPtr err = xmlGetLastError();
if (err && error) {
int errCode = err->code;
char * msg = err->message;
//if (err->level == XML_ERR_FATAL)
NSString *errMsg = [[NSString stringWithFormat:@"%s", msg] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
*error = [NSError errorWithDomain:kLIBXMLParserErrorDomain code:errCode userInfo:@{NSLocalizedDescriptionKey: errMsg}];
//printf("ERROR in [parseFeed] (%d): %s\n", err->level, [[*error localizedDescription] UTF8String]);
if (error) {
*error = RSXMLMakeErrorFromLIBXMLError(xmlGetLastError());
xmlResetLastError();
}
return parsedResult;

View File

@@ -1,36 +0,0 @@
//
// RSOPMLAttributes.h
// RSXML
//
// 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

View File

@@ -1,66 +0,0 @@
//
// RSOPMLAttributes.m
// RSXML
//
// Created by Brent Simmons on 2/28/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
#import "RSOPMLAttributes.h"
#import "RSXMLInternal.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 rsxml_objectForCaseInsensitiveKey:OPMLTextKey];
}
- (NSString *)opml_title {
return [self rsxml_objectForCaseInsensitiveKey:OPMLTitleKey];
}
- (NSString *)opml_description {
return [self rsxml_objectForCaseInsensitiveKey:OPMLDescriptionKey];
}
- (NSString *)opml_type {
return [self rsxml_objectForCaseInsensitiveKey:OPMLTypeKey];
}
- (NSString *)opml_version {
return [self rsxml_objectForCaseInsensitiveKey:OPMLVersionKey];
}
- (NSString *)opml_htmlUrl {
return [self rsxml_objectForCaseInsensitiveKey:OPMLHMTLURLKey];
}
- (NSString *)opml_xmlUrl {
return [self rsxml_objectForCaseInsensitiveKey:OPMLXMLURLKey];
}
@end

View File

@@ -1,17 +0,0 @@
//
// RSOPMLDocument.h
// RSXML
//
// Created by Brent Simmons on 2/28/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
#import "RSOPMLItem.h"
@interface RSOPMLDocument : RSOPMLItem
@property (nonatomic) NSString *title;
@end

View File

@@ -1,13 +0,0 @@
//
// RSOPMLDocument.m
// RSXML
//
// Created by Brent Simmons on 2/28/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
#import "RSOPMLDocument.h"
@implementation RSOPMLDocument
@end

View File

@@ -1,23 +0,0 @@
//
// RSOPMLFeedSpecifier.h
// RSXML
//
// Created by Brent Simmons on 2/28/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@interface RSOPMLFeedSpecifier : NSObject
- (instancetype)initWithTitle:(NSString *)title feedDescription:(NSString *)feedDescription homePageURL:(NSString *)homePageURL feedURL:(NSString *)feedURL;
@property (nonatomic, readonly) NSString *title;
@property (nonatomic, readonly) NSString *feedDescription;
@property (nonatomic, readonly) NSString *homePageURL;
@property (nonatomic, readonly) NSString *feedURL;
@end

View File

@@ -1,50 +0,0 @@
//
// RSOPMLFeedSpecifier.m
// RSXML
//
// Created by Brent Simmons on 2/28/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
#import "RSOPMLFeedSpecifier.h"
#import "RSXMLInternal.h"
@implementation RSOPMLFeedSpecifier
- (instancetype)initWithTitle:(NSString *)title feedDescription:(NSString *)feedDescription homePageURL:(NSString *)homePageURL feedURL:(NSString *)feedURL {
NSParameterAssert(!RSXMLIsEmpty(feedURL));
self = [super init];
if (!self) {
return nil;
}
if (RSXMLIsEmpty(title)) {
_title = nil;
}
else {
_title = title;
}
if (RSXMLIsEmpty(feedDescription)) {
_feedDescription = nil;
}
else {
_feedDescription = feedDescription;
}
if (RSXMLIsEmpty(homePageURL)) {
_homePageURL = nil;
}
else {
_homePageURL = homePageURL;
}
_feedURL = feedURL;
return self;
}
@end

View File

@@ -1,26 +1,27 @@
//
// RSOPMLItem.h
// RSXML
//
// Created by Brent Simmons on 2/28/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
@class RSOPMLFeedSpecifier;
// 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 RSOPMLItem : NSObject
@property (nonatomic) NSArray<RSOPMLItem*> *children;
@property (nonatomic) NSDictionary *attributes;
@property (nonatomic) NSArray *children;
@property (nonatomic, readonly) BOOL isFolder; // true if children.count > 0
@property (nonatomic, readonly) NSString *displayName; //May be nil.
- (void)addChild:(RSOPMLItem *)child;
- (void)setAttribute:(id)value forKey:(NSString *)key;
- (id)attributeForKey:(NSString *)key;
@property (nonatomic, readonly) RSOPMLFeedSpecifier *OPMLFeedSpecifier; //May be nil.
@property (nonatomic, readonly) NSString *titleFromAttributes; //May be nil.
@property (nonatomic, readonly) BOOL isFolder;
- (NSString *)recursiveDescription;
@end

View File

@@ -1,86 +1,99 @@
//
// RSOPMLItem.m
// RSXML
//
// 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 "RSXMLInternal.h"
NSString *OPMLTextKey = @"text";
NSString *OPMLTitleKey = @"title";
NSString *OPMLDescriptionKey = @"description";
NSString *OPMLTypeKey = @"type";
NSString *OPMLVersionKey = @"version";
NSString *OPMLHMTLURLKey = @"htmlUrl";
NSString *OPMLXMLURLKey = @"xmlUrl";
@interface RSOPMLItem ()
@property (nonatomic) NSMutableArray *mutableChildren;
@property (nonatomic) NSMutableArray<RSOPMLItem*> *mutableChildren;
@property (nonatomic) NSMutableDictionary *mutableAttributes;
@end
@implementation RSOPMLItem
@synthesize children = _children;
@synthesize OPMLFeedSpecifier = _OPMLFeedSpecifier;
- (NSArray *)children {
return [self.mutableChildren copy];
}
- (void)setChildren:(NSArray *)children {
_children = children;
self.mutableChildren = [_children mutableCopy];
- (void)setChildren:(NSArray<RSOPMLItem*>*)children {
self.mutableChildren = [children mutableCopy];
}
- (void)addChild:(RSOPMLItem *)child {
if (!self.mutableChildren) {
self.mutableChildren = [NSMutableArray new];
}
[self.mutableChildren addObject:child];
- (NSDictionary *)attributes {
return [self.mutableAttributes copy];
}
- (RSOPMLFeedSpecifier *)OPMLFeedSpecifier {
if (_OPMLFeedSpecifier) {
return _OPMLFeedSpecifier;
}
NSString *feedURL = self.attributes.opml_xmlUrl;
if (RSXMLIsEmpty(feedURL)) {
return nil;
}
_OPMLFeedSpecifier = [[RSOPMLFeedSpecifier alloc] initWithTitle:self.attributes.opml_title feedDescription:self.attributes.opml_description homePageURL:self.attributes.opml_htmlUrl feedURL:feedURL];
return _OPMLFeedSpecifier;
}
- (NSString *)titleFromAttributes {
NSString *title = self.attributes.opml_title;
if (title) {
return title;
}
title = self.attributes.opml_text;
if (title) {
return title;
}
return nil;
- (void)setAttributes:(NSDictionary *)attributes {
self.mutableAttributes = [attributes mutableCopy];
}
- (BOOL)isFolder {
return self.mutableChildren.count > 0;
}
- (NSString *)displayName {
NSString *title = [self attributeForKey:OPMLTitleKey];
if (!title) {
title = [self attributeForKey:OPMLTextKey];
}
return title;
}
- (void)addChild:(RSOPMLItem *)child {
if (!self.mutableChildren) {
self.mutableChildren = [NSMutableArray new];
}
[self.mutableChildren addObject:child];
}
- (void)setAttribute:(id)value forKey:(NSString *)key {
if (!self.mutableAttributes) {
self.mutableAttributes = [NSMutableDictionary new];
}
[self.mutableAttributes setValue:value forKey:key];
}
- (id)attributeForKey:(NSString *)key {
if (self.attributes.count > 0 && !RSXMLStringIsEmpty(key)) {
return [self.attributes rsxml_objectForCaseInsensitiveKey:key];
}
return nil;
}
#pragma mark - Printing
- (NSString *)description {
NSMutableString *str = [NSMutableString stringWithFormat:@"<%@ group: %d", [self class], self.isFolder];
for (NSString *key in _mutableAttributes) {
[str appendFormat:@", %@: '%@'", key, _mutableAttributes[key]];
}
[str appendString:@">"];
return str;
}
- (void)appendStringRecursive:(NSMutableString *)str indent:(NSString *)prefix {
[str appendFormat:@"%@%@\n", prefix, self];
if (self.isFolder) {
for (RSOPMLItem *child in self.children) {
[child appendStringRecursive:str indent:[prefix stringByAppendingString:@" "]];
}
[str appendFormat:@"%@</group>\n", prefix];
}
}
- (NSString *)recursiveDescription {
NSMutableString *mStr = [NSMutableString new];
[self appendStringRecursive:mStr indent:@""];
return mStr;
}
@end

View File

@@ -10,10 +10,10 @@
@class RSXMLData;
@class RSOPMLDocument;
@class RSOPMLItem;
typedef void (^RSParsedOPMLBlock)(RSOPMLDocument *OPMLDocument, NSError *error);
typedef void (^RSParsedOPMLBlock)(RSOPMLItem *opmlDocument, NSError *error);
void RSParseOPML(RSXMLData *xmlData, RSParsedOPMLBlock callback); //async; calls back on main thread.
@@ -22,7 +22,7 @@ void RSParseOPML(RSXMLData *xmlData, RSParsedOPMLBlock callback); //async; calls
- (instancetype)initWithXMLData:(RSXMLData *)xmlData;
@property (nonatomic, readonly) RSOPMLDocument *OPMLDocument;
@property (nonatomic, readonly) RSOPMLItem *opmlDocument;
@property (nonatomic, readonly) NSError *error;
@end

View File

@@ -11,8 +11,6 @@
#import "RSXMLData.h"
#import "RSSAXParser.h"
#import "RSOPMLItem.h"
#import "RSOPMLDocument.h"
#import "RSOPMLAttributes.h"
#import "RSXMLError.h"
@@ -27,7 +25,7 @@ void RSParseOPML(RSXMLData *xmlData, RSParsedOPMLBlock callback) {
RSOPMLParser *parser = [[RSOPMLParser alloc] initWithXMLData:xmlData];
RSOPMLDocument *document = parser.OPMLDocument;
RSOPMLItem *document = parser.opmlDocument;
NSError *error = parser.error;
dispatch_async(dispatch_get_main_queue(), ^{
@@ -41,9 +39,9 @@ void RSParseOPML(RSXMLData *xmlData, RSParsedOPMLBlock callback) {
@interface RSOPMLParser () <RSSAXParserDelegate>
@property (nonatomic, readwrite) RSOPMLDocument *OPMLDocument;
@property (nonatomic, readwrite) RSOPMLItem *opmlDocument;
@property (nonatomic, readwrite) NSError *error;
@property (nonatomic) NSMutableArray *itemStack;
@property (nonatomic) NSMutableArray<RSOPMLItem*> *itemStack;
@end
@@ -72,31 +70,28 @@ void RSParseOPML(RSXMLData *xmlData, RSParsedOPMLBlock callback) {
@autoreleasepool {
if (![self canParseData:XMLData.data]) {
if ([self canParseData:XMLData.data]) {
RSSAXParser *parser = [[RSSAXParser alloc] initWithDelegate:self];
self.itemStack = [NSMutableArray new];
self.opmlDocument = [RSOPMLItem new];
[self.itemStack addObject:self.opmlDocument];
[parser parseData:XMLData.data];
[parser finishParsing];
} else {
NSString *filename = nil;
NSURL *url = [NSURL URLWithString:XMLData.urlString];
if (url && url.isFileURL) {
filename = url.path.lastPathComponent;
}
if ([XMLData.urlString hasPrefix:@"http"]) {
filename = XMLData.urlString;
}
if (!filename) {
filename = XMLData.urlString;
}
self.error = RSOPMLWrongFormatError(filename);
return;
self.error = RSXMLMakeError(RSXMLErrorFileNotOPML, filename);
}
RSSAXParser *parser = [[RSSAXParser alloc] initWithDelegate:self];
self.itemStack = [NSMutableArray new];
self.OPMLDocument = [RSOPMLDocument new];
[self pushItem:self.OPMLDocument];
[parser parseData:XMLData.data];
[parser finishParsing];
}
}
@@ -122,12 +117,12 @@ void RSParseOPML(RSXMLData *xmlData, RSParsedOPMLBlock callback) {
}
NSRange opmlRange = [s rangeOfString:@"<opml" options:NSCaseInsensitiveSearch range:rangeToSearch];
if (opmlRange.length < 1) {
if (opmlRange.location == NSNotFound) {
return NO;
}
NSRange outlineRange = [s rangeOfString:@"<outline" options:NSLiteralSearch range:rangeToSearch];
if (outlineRange.length < 1) {
if (outlineRange.location == NSNotFound) {
return NO;
}
@@ -139,11 +134,6 @@ void RSParseOPML(RSXMLData *xmlData, RSParsedOPMLBlock callback) {
return YES;
}
- (void)pushItem:(RSOPMLItem *)item {
[self.itemStack addObject:item];
}
- (void)popItem {
@@ -158,28 +148,27 @@ void RSParseOPML(RSXMLData *xmlData, RSParsedOPMLBlock callback) {
}
- (RSOPMLItem *)currentItem {
return self.itemStack.lastObject;
}
#pragma mark - RSSAXParserDelegate
static const char *kOutline = "outline";
static const char kOutlineLength = 8;
static const char *kHead = "head";
static const char kHeadLength = 5;
static BOOL isHead = NO;
- (void)saxParser:(RSSAXParser *)SAXParser XMLStartElement:(const xmlChar *)localName prefix:(const xmlChar *)prefix uri:(const xmlChar *)uri numberOfNamespaces:(NSInteger)numberOfNamespaces namespaces:(const xmlChar **)namespaces numberOfAttributes:(NSInteger)numberOfAttributes numberDefaulted:(int)numberDefaulted attributes:(const xmlChar **)attributes {
if (!RSSAXEqualTags(localName, kOutline, kOutlineLength)) {
return;
if (RSSAXEqualTags(localName, kOutline, kOutlineLength)) {
RSOPMLItem *item = [RSOPMLItem new];
item.attributes = [SAXParser attributesDictionary:attributes numberOfAttributes:numberOfAttributes];
[self.itemStack.lastObject addChild:item];
[self.itemStack addObject:item];
} else if (RSSAXEqualTags(localName, kHead, kHeadLength)) {
isHead = YES;
} else if (isHead) {
[SAXParser beginStoringCharacters];
}
RSOPMLItem *item = [RSOPMLItem new];
item.attributes = [SAXParser attributesDictionary:attributes numberOfAttributes:numberOfAttributes];
[[self currentItem] addChild:item];
[self pushItem:item];
}
@@ -187,31 +176,15 @@ static const char kOutlineLength = 8;
if (RSSAXEqualTags(localName, kOutline, kOutlineLength)) {
[self popItem];
} else if (RSSAXEqualTags(localName, kHead, kHeadLength)) {
isHead = NO;
} else if (isHead) {
NSString *key = [NSString stringWithFormat:@"%s", localName];
[self.itemStack.lastObject setAttribute:[SAXParser currentString] forKey:key];
}
}
static const char *kText = "text";
static const NSInteger kTextLength = 5;
static const char *kTitle = "title";
static const NSInteger kTitleLength = 6;
static const char *kDescription = "description";
static const NSInteger kDescriptionLength = 12;
static const char *kType = "type";
static const NSInteger kTypeLength = 5;
static const char *kVersion = "version";
static const NSInteger kVersionLength = 8;
static const char *kHTMLURL = "htmlUrl";
static const NSInteger kHTMLURLLength = 8;
static const char *kXMLURL = "xmlUrl";
static const NSInteger kXMLURLLength = 7;
- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForName:(const xmlChar *)name prefix:(const xmlChar *)prefix {
if (prefix) {
@@ -219,77 +192,37 @@ static const NSInteger kXMLURLLength = 7;
}
size_t nameLength = strlen((const char *)name);
if (nameLength == kTextLength - 1) {
if (RSSAXEqualTags(name, kText, kTextLength)) {
return OPMLTextKey;
}
if (RSSAXEqualTags(name, kType, kTypeLength)) {
return OPMLTypeKey;
}
switch (nameLength) {
case 4:
if (RSSAXEqualTags(name, "text", 5)) return OPMLTextKey;
if (RSSAXEqualTags(name, "type", 5)) return OPMLTypeKey;
break;
case 5:
if (RSSAXEqualTags(name, "title", 6)) return OPMLTitleKey;
break;
case 6:
if (RSSAXEqualTags(name, "xmlUrl", 7)) return OPMLXMLURLKey;
break;
case 7:
if (RSSAXEqualTags(name, "version", 8)) return OPMLVersionKey;
if (RSSAXEqualTags(name, "htmlUrl", 8)) return OPMLHMTLURLKey;
break;
case 11:
if (RSSAXEqualTags(name, "description", 12)) return OPMLDescriptionKey;
break;
}
else if (nameLength == kTitleLength - 1) {
if (RSSAXEqualTags(name, kTitle, kTitleLength)) {
return OPMLTitleKey;
}
}
else if (nameLength == kXMLURLLength - 1) {
if (RSSAXEqualTags(name, kXMLURL, kXMLURLLength)) {
return OPMLXMLURLKey;
}
}
else if (nameLength == kVersionLength - 1) {
if (RSSAXEqualTags(name, kVersion, kVersionLength)) {
return OPMLVersionKey;
}
if (RSSAXEqualTags(name, kHTMLURL, kHTMLURLLength)) {
return OPMLHMTLURLKey;
}
}
else if (nameLength == kDescriptionLength - 1) {
if (RSSAXEqualTags(name, kDescription, kDescriptionLength)) {
return OPMLDescriptionKey;
}
}
return nil;
}
static const char *kRSSUppercase = "RSS";
static const char *kRSSLowercase = "rss";
static const NSUInteger kRSSLength = 3;
static NSString *RSSUppercaseValue = @"RSS";
static NSString *RSSLowercaseValue = @"rss";
static NSString *emptyString = @"";
static BOOL equalBytes(const void *bytes1, const void *bytes2, NSUInteger length) {
return memcmp(bytes1, bytes2, length) == 0;
}
- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length {
if (length < 1) {
return emptyString;
return @"";
} else if (length == 3) {
if (RSSAXEqualBytes(bytes, "RSS", 3)) return @"RSS";
if (RSSAXEqualBytes(bytes, "rss", 3)) return @"rss";
}
if (length == kRSSLength) {
if (equalBytes(bytes, kRSSUppercase, kRSSLength)) {
return RSSUppercaseValue;
}
else if (equalBytes(bytes, kRSSLowercase, kRSSLength)) {
return RSSLowercaseValue;
}
}
return nil;
}

View File

@@ -446,22 +446,16 @@ static const NSInteger kTrueLength = 5;
}
static BOOL equalBytes(const void *bytes1, const void *bytes2, NSUInteger length) {
return memcmp(bytes1, bytes2, length) == 0;
}
- (NSString *)saxParser:(RSSAXParser *)SAXParser internedStringForValue:(const void *)bytes length:(NSUInteger)length {
static const NSUInteger falseLength = kFalseLength - 1;
static const NSUInteger trueLength = kTrueLength - 1;
if (length == falseLength && equalBytes(bytes, kFalse, falseLength)) {
if (length == falseLength && RSSAXEqualBytes(bytes, kFalse, falseLength)) {
return kFalseValue;
}
if (length == trueLength && equalBytes(bytes, kTrue, trueLength)) {
if (length == trueLength && RSSAXEqualBytes(bytes, kTrue, trueLength)) {
return kTrueValue;
}

View File

@@ -44,6 +44,7 @@ void RSSAXInitLibXMLParser(void); // Needed by RSSAXHTMLParser.
/*For use by delegate.*/
BOOL RSSAXEqualTags(const unsigned char *localName, const char *tag, NSInteger tagLength);
BOOL RSSAXEqualBytes(const void *bytes1, const void *bytes2, NSUInteger length);
@interface RSSAXParser : NSObject

View File

@@ -225,6 +225,11 @@ BOOL RSSAXEqualTags(const xmlChar *localName, const char *tag, NSInteger tagLeng
return !strncmp((const char *)localName, tag, (size_t)tagLength);
}
BOOL RSSAXEqualBytes(const void *bytes1, const void *bytes2, NSUInteger length) {
return memcmp(bytes1, bytes2, length) == 0;
}
#pragma mark - Callbacks

View File

@@ -20,10 +20,7 @@
#import <RSXML/RSParsedArticle.h>
#import <RSXML/RSOPMLParser.h>
#import <RSXML/RSOPMLDocument.h>
#import <RSXML/RSOPMLItem.h>
#import <RSXML/RSOPMLAttributes.h>
#import <RSXML/RSOPMLFeedSpecifier.h>
#import <RSXML/RSXMLError.h>

View File

@@ -1,19 +1,21 @@
//
// RSXMLError.h
// RSXML
//
// Created by Brent Simmons on 2/28/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
@import Foundation;
#import <libxml/xmlerror.h>
extern NSString *RSXMLErrorDomain;
extern NSErrorDomain kLIBXMLParserErrorDomain;
extern NSErrorDomain kRSXMLParserErrorDomain;
typedef NS_ENUM(NSInteger, RSXMLErrorCode) {
RSXMLErrorCodeDataIsWrongFormat = 1024
/// Error codes for RSXML error domain @c (kRSXMLParserErrorDomain)
typedef NS_ENUM(NSInteger, RSXMLError) {
/// Error codes
RSXMLErrorNoData = 100,
RSXMLErrorMissingLeftCaret = 110,
RSXMLErrorProbablyHTML = 120,
RSXMLErrorContainsXMLErrorsTag = 130,
RSXMLErrorNoSuitableParser = 140,
RSXMLErrorFileNotOPML = 1024 // original value
};
NSError *RSOPMLWrongFormatError(NSString *fileName);
void RSXMLSetError(NSError **error, RSXMLError code, NSString *filename);
NSError * RSXMLMakeError(RSXMLError code, NSString *filename);
NSError * RSXMLMakeErrorFromLIBXMLError(xmlErrorPtr err);

View File

@@ -1,22 +1,48 @@
//
// RSXMLError.m
// RSXML
//
// Created by Brent Simmons on 2/28/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
#import "RSXMLError.h"
NSString *RSXMLErrorDomain = @"com.ranchero.RSXML";
NSErrorDomain kLIBXMLParserErrorDomain = @"LIBXMLParserErrorDomain";
NSErrorDomain kRSXMLParserErrorDomain = @"RSXMLParserErrorDomain";
NSError *RSOPMLWrongFormatError(NSString *fileName) {
NSString *localizedDescriptionFormatString = NSLocalizedString(@"The file %@ cant be parsed because its not an OPML file.", @"OPML wrong format");
NSString *localizedDescription = [NSString stringWithFormat:localizedDescriptionFormatString, fileName];
NSString *localizedFailureString = NSLocalizedString(@"The file is not an OPML file.", @"OPML wrong format");
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: localizedDescription, NSLocalizedFailureReasonErrorKey: localizedFailureString};
return [[NSError alloc] initWithDomain:RSXMLErrorDomain code:RSXMLErrorCodeDataIsWrongFormat userInfo:userInfo];
NSString * getErrorMessageForRSXMLError(RSXMLError code, id paramA);
NSString * getErrorMessageForRSXMLError(RSXMLError code, id paramA) {
switch (code) { // switch statement will warn if an enum value is missing
case RSXMLErrorNoData:
return @"Couldn't parse feed. No data available.";
case RSXMLErrorMissingLeftCaret:
return @"Couldn't parse feed. Missing left caret character ('<').";
case RSXMLErrorProbablyHTML:
return @"Couldn't parse feed. Expecting XML data but found html data.";
case RSXMLErrorContainsXMLErrorsTag:
return @"Couldn't parse feed. XML contains 'errors' tag.";
case RSXMLErrorNoSuitableParser:
return @"Couldn't parse feed. No suitable parser found. XML document not well-formed.";
case RSXMLErrorFileNotOPML:
if (paramA) {
return [NSString stringWithFormat:@"The file %@ can't be parsed because it's not an OPML file.", paramA];
}
return @"The file can't be parsed because it's not an OPML file.";
}
}
void RSXMLSetError(NSError **error, RSXMLError code, NSString *filename) {
if (error) {
*error = RSXMLMakeError(code, filename);
}
}
NSError * RSXMLMakeError(RSXMLError code, NSString *filename) {
return [NSError errorWithDomain:kRSXMLParserErrorDomain code:code
userInfo:@{NSLocalizedDescriptionKey: getErrorMessageForRSXMLError(code, nil)}];
}
NSError * RSXMLMakeErrorFromLIBXMLError(xmlErrorPtr err) {
if (err) {
int errCode = err->code;
char * msg = err->message;
//if (err->level == XML_ERR_FATAL)
NSString *errMsg = [[NSString stringWithFormat:@"%s", msg] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
return [NSError errorWithDomain:kLIBXMLParserErrorDomain code:errCode userInfo:@{NSLocalizedDescriptionKey: errMsg}];
}
return nil;
}

View File

@@ -36,6 +36,8 @@
RSXMLData *xmlData = [[RSXMLData alloc] initWithData:d urlString:@"http://example.org/"];
RSOPMLParser *parser = [[RSOPMLParser alloc] initWithXMLData:xmlData];
XCTAssertNotNil(parser.error);
XCTAssert(parser.error.code == RSXMLErrorFileNotOPML);
XCTAssert([parser.error.domain isEqualTo:kRSXMLParserErrorDomain]);
d = [[NSData alloc] initWithContentsOfFile:@"/System/Library/Kernels/kernel"];
xmlData = [[RSXMLData alloc] initWithData:d urlString:@"/System/Library/Kernels/kernel"];
@@ -61,40 +63,37 @@
RSOPMLParser *parser = [[RSOPMLParser alloc] initWithXMLData:xmlData];
XCTAssertNotNil(parser);
RSOPMLDocument *document = parser.OPMLDocument;
RSOPMLItem *document = parser.opmlDocument;
XCTAssertNotNil(document);
[self checkStructureForOPMLItem:document];
XCTAssert([document.displayName isEqualToString:@"Subs"]);
XCTAssert([document.children.firstObject.displayName isEqualToString:@"Daring Fireball"]);
XCTAssert([document.children.lastObject.displayName isEqualToString:@"Writers"]);
XCTAssert([document.children.lastObject.children.lastObject.displayName isEqualToString:@"Gerrold"]);
[self checkStructureForOPMLItem:document isRoot:YES];
//NSLog(@"\n%@", [document recursiveDescription]);
}
- (void)checkStructureForOPMLItem:(RSOPMLItem *)item {
- (void)checkStructureForOPMLItem:(RSOPMLItem *)item isRoot:(BOOL)root {
RSOPMLFeedSpecifier *feedSpecifier = item.OPMLFeedSpecifier;
if (![item isKindOfClass:[RSOPMLDocument class]]) {
XCTAssertNotNil(item.attributes.opml_text);
if (!root) {
XCTAssertNotNil([item attributeForKey:OPMLTextKey]);
XCTAssertNotNil([item attributeForKey:OPMLTitleKey]);
}
// If it has no children, it should have a feed specifier. The converse is also true.
BOOL isFolder = (item.children.count > 0);
if (!isFolder && [item.attributes.opml_title isEqualToString:@"Skip"]) {
if (!isFolder && [[item attributeForKey:OPMLTitleKey] isEqualToString:@"Skip"]) {
isFolder = YES;
}
if (!isFolder) {
XCTAssertNotNil(feedSpecifier.title);
XCTAssertNotNil(feedSpecifier.feedURL);
}
else {
XCTAssertNil(feedSpecifier);
if (![item isKindOfClass:[RSOPMLDocument class]]) {
XCTAssertNotNil(item.attributes.opml_title);
}
XCTAssertNotNil([item attributeForKey:OPMLHMTLURLKey]);
}
if (item.children.count > 0) {
for (RSOPMLItem *oneItem in item.children) {
[self checkStructureForOPMLItem:oneItem];
[self checkStructureForOPMLItem:oneItem isRoot:NO];
}
}
}