2017-06-21 07:00:19 +02:00
|
|
|
|
//
|
|
|
|
|
// FeedParser.swift
|
|
|
|
|
// RSParser
|
|
|
|
|
//
|
|
|
|
|
// Created by Brent Simmons on 6/20/17.
|
|
|
|
|
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
2017-06-26 01:32:07 +02:00
|
|
|
|
// FeedParser handles the various syndication feed types.
|
2017-06-25 19:23:30 +02:00
|
|
|
|
// It might be a good idea to do a plugin-style architecture here instead —
|
|
|
|
|
// but feed formats don’t appear all that often, so it’s probably not necessary.
|
|
|
|
|
|
2017-06-21 07:00:19 +02:00
|
|
|
|
public struct FeedParser {
|
|
|
|
|
|
2017-06-25 19:23:30 +02:00
|
|
|
|
static let minNumberOfBytesRequired = 128
|
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
public static func feedType(_ parserData: ParserData) -> FeedType {
|
2017-06-21 07:00:19 +02:00
|
|
|
|
|
2017-06-25 19:23:30 +02:00
|
|
|
|
// Can call with partial data — while still downloading, for instance.
|
2017-06-21 07:00:19 +02:00
|
|
|
|
// If there’s not enough data, return .unknown. Ask again when there’s more data.
|
|
|
|
|
// If it’s definitely not a feed, return .notAFeed.
|
2017-06-26 01:32:07 +02:00
|
|
|
|
//
|
|
|
|
|
// This should be fast enough to call on the main thread.
|
2017-06-21 07:00:19 +02:00
|
|
|
|
|
2017-06-25 19:23:30 +02:00
|
|
|
|
if parserData.data.count < minNumberOfBytesRequired {
|
|
|
|
|
return .unknown
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
let nsdata = parserData.data as NSData
|
|
|
|
|
if nsdata.isProbablyJSONFeed() {
|
2017-06-25 19:23:30 +02:00
|
|
|
|
return .jsonFeed
|
|
|
|
|
}
|
2017-06-25 23:06:01 +02:00
|
|
|
|
if nsdata.isProbablyRSSInJSON() {
|
2017-06-25 19:23:30 +02:00
|
|
|
|
return .rssInJSON
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
if nsdata.isProbablyHTML() {
|
2017-06-25 19:23:30 +02:00
|
|
|
|
return .notAFeed
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
if nsdata.isProbablyRSS() {
|
2017-06-25 19:23:30 +02:00
|
|
|
|
return .rss
|
|
|
|
|
}
|
2017-06-25 23:06:01 +02:00
|
|
|
|
if nsdata.isProbablyAtom() {
|
2017-06-25 19:23:30 +02:00
|
|
|
|
return .atom
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return .notAFeed
|
2017-06-21 07:00:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
public static func parseFeed(_ parserData: ParserData) throws -> ParsedFeed? {
|
2017-06-25 19:23:30 +02:00
|
|
|
|
|
2017-06-26 01:32:07 +02:00
|
|
|
|
// All the concrete parsers return a ParsedFeed struct.
|
|
|
|
|
// Related: ParsedItem, ParsedAuthor, ParsedHub, ParsedAttachment.
|
|
|
|
|
//
|
|
|
|
|
// This is probably fast enough to call on the main thread —
|
|
|
|
|
// but it’s probably a good idea to use a background queue if
|
|
|
|
|
// you might be doing a lot of parsing. (Such as in a feed reader.)
|
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
do {
|
|
|
|
|
let type = feedType(parserData)
|
2017-06-25 19:23:30 +02:00
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
switch type {
|
2017-06-25 19:23:30 +02:00
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
case .jsonFeed:
|
|
|
|
|
return try JSONFeedParser.parse(parserData)
|
2017-06-25 19:23:30 +02:00
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
case .rssInJSON:
|
|
|
|
|
return try RSSInJSONParser.parse(parserData)
|
2017-06-25 19:23:30 +02:00
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
case .rss:
|
|
|
|
|
return RSSParser.parse(parserData)
|
2017-06-21 07:00:19 +02:00
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
case .atom:
|
2017-06-26 01:32:07 +02:00
|
|
|
|
return AtomParser.parse(parserData)
|
2017-06-21 07:00:19 +02:00
|
|
|
|
|
2017-06-25 23:06:01 +02:00
|
|
|
|
case .unknown, .notAFeed:
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2017-06-25 19:23:30 +02:00
|
|
|
|
}
|
2017-06-25 23:06:01 +02:00
|
|
|
|
catch { throw error }
|
2017-06-21 07:00:19 +02:00
|
|
|
|
}
|
|
|
|
|
}
|