Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
// swift-tools-version:5.2
// swift-tools-version:5.5
import PackageDescription

let package = Package(
name: "OPML",
products: [
.library(name: "OPML", targets: ["OPML"])
],
dependencies: [
.package(name: "Html", url: "https://github.com/hallee/swift-html.git", from: "0.3.0")
],
targets: [
.target(name: "OPML", dependencies: ["Html"]),
.testTarget(
name: "Tests",
dependencies: ["OPML"]
)
]
name: "OPML",
products: [
.library(name: "OPML", targets: ["OPML"])
],
dependencies: [
.package(name: "Html", url: "https://github.com/hallee/swift-html.git", from: "0.3.0")
],
targets: [
.target(name: "OPML", dependencies: ["Html"]),
.testTarget(
name: "OPMLTests",
dependencies: ["OPML"],
resources: [
// Copy Tests/ExampleTests/Resources directories as-is.
// Use to retain directory structure.
// Will be at top level in bundle.
.copy("Resources/rsparser.opml"),
.copy("Resources/feedly.opml")
]
)
]
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
public struct Attribute: Codable, Hashable {
public struct OPMLAttribute: Codable, Hashable {

public let name: String
public let value: String
Expand Down
14 changes: 7 additions & 7 deletions Sources/OPML/Models/OPMLEntry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public struct OPMLEntry: Codable, Hashable {

/// What is displayed when an outliner opens the OPML file.
public let text: String
public let attributes: [Attribute]?
public let attributes: [OPMLAttribute]?
public let children: [OPMLEntry]?

public var title: String? {
Expand All @@ -21,7 +21,7 @@ public struct OPMLEntry: Codable, Hashable {

public init(
text: String,
attributes: [Attribute]?,
attributes: [OPMLAttribute]?,
children: [OPMLEntry]? = nil
) {
self.text = text
Expand All @@ -33,14 +33,14 @@ public struct OPMLEntry: Codable, Hashable {
rss feedURL: URL,
siteURL: URL?,
title: String,
attributes: [Attribute]? = nil
attributes: [OPMLAttribute]? = nil
) {
text = title
self.attributes = [
Attribute(name: "title", value: title),
Attribute(name: "type", value: "rss"),
Attribute(name: "xmlUrl", value: feedURL.absoluteString),
Attribute(name: "htmlUrl", value: siteURL?.absoluteString ?? ""),
OPMLAttribute(name: "title", value: title),
OPMLAttribute(name: "type", value: "rss"),
OPMLAttribute(name: "xmlUrl", value: feedURL.absoluteString),
OPMLAttribute(name: "htmlUrl", value: siteURL?.absoluteString ?? ""),
] + (attributes ?? [])
children = nil
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/OPML/Parser/OPMLEntryBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ final class OPMLEntryBuilder {

/// What is displayed when an outliner opens the OPML file.
var text: String?
var attributes: [Attribute]?
var attributes: [OPMLAttribute]?
var children: [OPMLEntryBuilder]?

var entry: OPMLEntry? {
guard let text = text else { return nil }
return OPMLEntry(text: text, attributes: attributes, children: children?.compactMap { $0.entry })
}

init(text: String? = nil, attributes: [Attribute]? = nil, children: [OPMLEntryBuilder]? = nil) {
init(text: String? = nil, attributes: [OPMLAttribute]? = nil, children: [OPMLEntryBuilder]? = nil) {
self.text = text
self.attributes = attributes
self.children = children
Expand Down
20 changes: 10 additions & 10 deletions Sources/OPML/Parser/OPMLParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
import FoundationXML
#endif

final class OPMLParser: NSObject {
final public class OPMLParser: NSObject {

private let xmlParser: XMLParser

Expand All @@ -13,7 +13,7 @@ final class OPMLParser: NSObject {

fileprivate let opmlBuilder = OPMLBuilder()

func parse() throws -> OPML {
public func parse() throws -> OPML {
xmlParser.delegate = self
let success = xmlParser.parse()
if !success {
Expand All @@ -26,12 +26,12 @@ final class OPMLParser: NSObject {
return try opmlBuilder.opml()
}

init(_ data: Data) {
public init(_ data: Data) {
xmlParser = XMLParser(data: data)
super.init()
}

init(file url: URL) throws {
public init(file url: URL) throws {
guard let xmlParser = XMLParser(contentsOf: url) else { throw Error.unableToOpenURL(url) }
self.xmlParser = xmlParser
super.init()
Expand All @@ -41,7 +41,7 @@ final class OPMLParser: NSObject {

extension OPMLParser: XMLParserDelegate {

func parser(
public func parser(
_ parser: XMLParser,
didStartElement elementName: String,
namespaceURI: String?,
Expand All @@ -61,7 +61,7 @@ extension OPMLParser: XMLParserDelegate {
}
}

func parser(
public func parser(
_ parser: XMLParser,
didEndElement elementName: String,
namespaceURI: String?,
Expand All @@ -79,7 +79,7 @@ extension OPMLParser: XMLParserDelegate {
}
}

func parser(_ parser: XMLParser, foundCharacters string: String) {
public func parser(_ parser: XMLParser, foundCharacters string: String) {
switch currentXMLDOMPath.lastPathComponent {
case "title":
opmlBuilder.title = string
Expand All @@ -96,7 +96,7 @@ extension OPMLParser: XMLParserDelegate {
guard let text = attributes.first(where: { $0.key.lowercased() == "text" })?.value else {
return
}
let entry = OPMLEntryBuilder(text: text, attributes: attributes.map { Attribute(name: $0.key, value: $0.value) })
let entry = OPMLEntryBuilder(text: text, attributes: attributes.map { OPMLAttribute(name: $0.key, value: $0.value) })
if let parentEntry = currentOPMLEntry {
if parentEntry.children == nil {
parentEntry.children = [entry]
Expand All @@ -108,8 +108,8 @@ extension OPMLParser: XMLParserDelegate {
}
}

enum Error: LocalizedError {
var errorDescription: String? {
public enum Error: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidDocument: return "Invalid or missing XML document"
case .parseError(let error): return "XML parsing error: \(error.localizedDescription)"
Expand Down
15 changes: 14 additions & 1 deletion Sources/Tests/Tests.swift → Tests/OPMLTests/Tests.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import XCTest
@testable import OPML

extension Bundle {
static func locateFirst(forResource: String, withExtension: String) -> URL? {
for b in Bundle.allBundles {
if let u = b.url(forResource: forResource, withExtension: withExtension) {
return u
}
}
return nil
}
}

class Tests: XCTestCase {

func testOPMLNested() {
guard let file = Bundle.module.url(forResource: "rsparser", withExtension: "opml") else {
guard let file = Bundle.module.url(forResource: "rsparser", withExtension: "opml") else {
XCTFail("Missing opml file")
return
}
Expand All @@ -13,6 +24,8 @@ class Tests: XCTestCase {
let parser = try OPMLParser(file: file)
let opml = try parser.parse()
XCTAssertEqual(opml.entries.flatMap { $0.children ?? [] }.count, 138)
XCTAssertEqual(opml.entries.count, 73)


let programmingEntries = opml.entries.first(where: { $0.text == "Programming" })?.children
XCTAssertEqual(programmingEntries?.count ?? 0, 33)
Expand Down