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

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