Start JSONFeed parser. No idea if it works yet.
This commit is contained in:
parent
aaa83e07aa
commit
6f0e4a9da6
|
@ -14,7 +14,9 @@ public struct FeedParserError: Error {
|
|||
|
||||
case rssChannelNotFound
|
||||
case rssItemsNotFound
|
||||
|
||||
case jsonFeedVersionNotFound
|
||||
case jsonFeedItemsNotFound
|
||||
case jsonFeedTitleNotFound
|
||||
}
|
||||
|
||||
public let errorType: FeedParserErrorType
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// JSONFeedParser.swift
|
||||
// RSParser
|
||||
//
|
||||
// Created by Brent Simmons on 6/25/17.
|
||||
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// See https://jsonfeed.org/version/1
|
||||
|
||||
public struct JSONFeedParser {
|
||||
|
||||
public static func parse(parserData: ParserData) throws -> ParsedFeed? {
|
||||
|
||||
do {
|
||||
let parsedObject = try JSONSerialization.jsonObject(with: parserData.data)
|
||||
|
||||
guard let version = parsedObject["version"] as? String, version.hasPrefix("https://jsonfeed.org/version/") else {
|
||||
throw FeedParserError(.jsonFeedVersionNotFound)
|
||||
}
|
||||
guard let itemsArray = parsedObject["items"] as? JSONArray else {
|
||||
throw FeedParserError(.jsonFeedItemsNotFound)
|
||||
}
|
||||
guard let title = parsedObject["title"] as? String else {
|
||||
throw FeedParserError(.jsonFeedTitleNotFound)
|
||||
}
|
||||
|
||||
let homePageURL = parsedObject["home_page_url"] as? String
|
||||
let feedURL = parsedObject["feed_url"] ?? parserData.url
|
||||
let feedDescription = parsedObject["description"] as? String
|
||||
let nextURL = parsedObject["next_url"] as? String
|
||||
let iconURL = parsedObject["icon_url"] as? String
|
||||
let faviconURL = parsedObject["favicon_url"] as? String
|
||||
let authors = parseAuthors(parsedObject)
|
||||
let expired = parsedObject["expired"] as? Bool ?? false
|
||||
let hubs = parseHubs(parsedObject)
|
||||
|
||||
let items = parseItems(itemsArray)
|
||||
|
||||
return ParsedFeed(type: .jsonFeed, title: title, homePageURL: homePageURL, feedURL: feedURL, feedDescription: feedDescription, nextURL: nextURL, iconURL: iconURL, faviconURL: faviconURL, authors: authors, expired: expired, hubs: hubs, items: items)
|
||||
|
||||
}
|
||||
catch { throw error }
|
||||
}
|
||||
}
|
||||
|
||||
private extension JSONFeedParser {
|
||||
|
||||
func parseAuthors(_ dictionary: JSONDictionary) -> [ParsedAuthor]? {
|
||||
|
||||
guard let authorDictionary = dictionary["author"] as? JSONDictionary else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let name = authorDictionary["name"]
|
||||
let url = authorDictionary["url"]
|
||||
let avatar = authorDictionary["avatar"]
|
||||
if name == nil && url == nil && avatar == nil {
|
||||
return nil
|
||||
}
|
||||
let parsedAuthor = ParsedAuthor(name: name, url: url, avatarURL: avatar, emailAddress: nil)
|
||||
return [parsedAuthor]
|
||||
}
|
||||
|
||||
func parseHubs(_ dictionary: JSONDictionary) -> [ParsedHub]? {
|
||||
|
||||
guard let hubsArray = dictionary["hubs"] as? JSONArray else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let hubs = hubsArray.flatMap { (oneHubDictionary) -> ParsedHub? in
|
||||
guard let oneHubURL = oneHubDictionary["url"], let oneHubType = oneHubDictionary["type"] else {
|
||||
return nil
|
||||
}
|
||||
return ParsedHub(type: oneHubType, url: oneHubURL)
|
||||
}
|
||||
return hubs.isEmpty ? nil : hubs
|
||||
}
|
||||
|
||||
func parseItems(_ itemsArray: JSONArray) -> [ParsedItem] {
|
||||
|
||||
return itemsArray.flatMap { (oneItemDictionary) -> ParsedItem? in
|
||||
return parseItem(oneItemDictionary)
|
||||
}
|
||||
}
|
||||
|
||||
func parseItem(_ itemDictionary: JSONDictionary) -> ParsedItem? {
|
||||
|
||||
guard let uniqueID = parseUniqueID(itemDictionary) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let contentHTML = itemDictionary["content_html"] as? String
|
||||
let contentText = itemDictionary["content_text"] as? String
|
||||
if contentHTML == nil && contentText == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
let url = itemDictionary["url"] as? String
|
||||
let externalURL = itemDictionary["external_url"] as? String
|
||||
let title = itemDictionary["title"] as? String
|
||||
let summary = itemDictionary["summary"] as? String
|
||||
let imageURL = itemDictionary["image"] as? String
|
||||
let bannerImageURL = itemDictionary["banner_image"] as? String
|
||||
|
||||
let datePublished = parseDate(itemDictionary["date_published"])
|
||||
let dateModified = parseDate(itemDictionary["date_modified"])
|
||||
|
||||
let authors = parseAuthors(itemDictionary)
|
||||
let tags = itemDictionary["tags"] as? [String]
|
||||
let attachments = parseAttachments(itemDictionary)
|
||||
|
||||
return ParsedItem(uniqueID: uniqueID, url: url, externalURL: externalURL, title: title, contentHTML: contentHTML, contentText: contentText, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: tags, attachments: attachments)
|
||||
}
|
||||
|
||||
func parseUniqueID(_ itemDictionary: JSONDictionary) -> String? {
|
||||
|
||||
if let uniqueID = itemDictionary["id"] as? String {
|
||||
return uniqueID // Spec says it must be a string
|
||||
}
|
||||
// Spec also says that if it’s a number, even though that’s incorrect, it should be coerced to a string.
|
||||
if let uniqueID = itemDictionary["id"] as? Int {
|
||||
return "\(uniqueID)"
|
||||
}
|
||||
if let uniqueID = itemDictionary["id"] as? Double {
|
||||
return "\(uniqueID)"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDate(_ dateString: String?) -> Date? {
|
||||
|
||||
guard let dateString = dateString, !dateString.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
return RSDateWithString(dateString)
|
||||
}
|
||||
|
||||
func parseAttachments(_ itemDictionary: JSONDictionary) -> [ParsedAttachment]? {
|
||||
|
||||
guard let attachmentsArray = itemDictionary["attachments"] as? JSONArray else {
|
||||
return nil
|
||||
}
|
||||
return attachmentsArray.flatMap { (oneAttachmentObject) -> ParsedAttachment? in
|
||||
return parseAttachment(oneAttachmentObject)
|
||||
}
|
||||
}
|
||||
|
||||
func parseAttachment(_ attachmentObject: JSONDictionary) -> ParsedAttachment? {
|
||||
|
||||
guard let url = attachmentObject["url"] as? String else {
|
||||
return nil
|
||||
}
|
||||
guard let mimeType = attachmentObject["mime_type"] as? String else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let title = attachmentObject["title"] as? String
|
||||
let sizeInBytes = attachmentObject["size_in_bytes"] as? Int
|
||||
let durationInSeconds = attachmentObject["duration_in_seconds"] as? Int
|
||||
|
||||
return ParsedAttachment(url: url, mimeType: mimeType, title: title, sizeInBytes: sizeInBytes, durationInSeconds: durationInSeconds)
|
||||
}
|
||||
}
|
|
@ -104,7 +104,7 @@ private extension RSSInJSONParser {
|
|||
if let externalURL = externalURL {
|
||||
s += externalURL
|
||||
}
|
||||
if let authorEmailAddress = authorEmailAddress {
|
||||
if let authorEmailAddress = authors?.first?.emailAddress {
|
||||
s += authorEmailAddress
|
||||
}
|
||||
if let oneAttachmentURL = attachments?.first?.url {
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
84469D381EFF2645004A6B28 /* RSSInJSONParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84469D371EFF2645004A6B28 /* RSSInJSONParser.swift */; };
|
||||
84469D401EFF29A9004A6B28 /* FeedParserError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84469D3F1EFF29A9004A6B28 /* FeedParserError.swift */; };
|
||||
84469D421EFF2B2D004A6B28 /* JSONTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84469D411EFF2B2D004A6B28 /* JSONTypes.swift */; };
|
||||
84469D441F002CEF004A6B28 /* JSONFeedParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84469D431F002CEF004A6B28 /* JSONFeedParser.swift */; };
|
||||
84D81BDC1EFA28E700652332 /* RSParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 84D81BDA1EFA28E700652332 /* RSParser.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
84D81BDE1EFA2B7D00652332 /* ParsedFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D81BDD1EFA2B7D00652332 /* ParsedFeed.swift */; };
|
||||
84D81BE01EFA2BAE00652332 /* FeedType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D81BDF1EFA2BAE00652332 /* FeedType.swift */; };
|
||||
|
@ -115,6 +116,7 @@
|
|||
84469D371EFF2645004A6B28 /* RSSInJSONParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RSSInJSONParser.swift; path = Feeds/JSON/RSSInJSONParser.swift; sourceTree = "<group>"; };
|
||||
84469D3F1EFF29A9004A6B28 /* FeedParserError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FeedParserError.swift; path = Feeds/FeedParserError.swift; sourceTree = "<group>"; };
|
||||
84469D411EFF2B2D004A6B28 /* JSONTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JSONTypes.swift; path = Feeds/JSON/JSONTypes.swift; sourceTree = "<group>"; };
|
||||
84469D431F002CEF004A6B28 /* JSONFeedParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JSONFeedParser.swift; path = Feeds/JSON/JSONFeedParser.swift; sourceTree = "<group>"; };
|
||||
84D81BD91EFA28E700652332 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
84D81BDA1EFA28E700652332 /* RSParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSParser.h; sourceTree = "<group>"; };
|
||||
84D81BDD1EFA2B7D00652332 /* ParsedFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ParsedFeed.swift; path = Feeds/ParsedFeed.swift; sourceTree = "<group>"; };
|
||||
|
@ -244,6 +246,7 @@
|
|||
children = (
|
||||
84469D411EFF2B2D004A6B28 /* JSONTypes.swift */,
|
||||
84469D371EFF2645004A6B28 /* RSSInJSONParser.swift */,
|
||||
84469D431F002CEF004A6B28 /* JSONFeedParser.swift */,
|
||||
);
|
||||
name = JSON;
|
||||
sourceTree = "<group>";
|
||||
|
@ -439,6 +442,7 @@
|
|||
84D81BE61EFA2DFB00652332 /* ParsedAttachment.swift in Sources */,
|
||||
84D81BDE1EFA2B7D00652332 /* ParsedFeed.swift in Sources */,
|
||||
84D81BE81EFA2E6700652332 /* ParsedHub.swift in Sources */,
|
||||
84469D441F002CEF004A6B28 /* JSONFeedParser.swift in Sources */,
|
||||
84D81BE01EFA2BAE00652332 /* FeedType.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
Loading…
Reference in New Issue