SyndicationFeed

0.9.2

RSS feed framework for Swift with support for Podcasting 2.0 and iTunes podcast
fitomad/SyndicationFeed

What's New

0.9.2

2025-08-28T13:48:05Z
  • Fix issues with the Podcasting 2.0 namespace
  • Add a new test resource and more Podcasting 2.0 test cases

SyndicationFeed

SyndicationFeed is a comprehensive Swift package designed to parse RSS feeds with a focus on podcast content, including full support for the Podcast Index RSS 2.0 specification. Whether you're building a podcast app, analyzing RSS feeds, or working with modern podcasting features, this package provides a robust, type-safe, and async-first approach to feed parsing.

Built with clarity and extensibility in mind, SyndicationFeed handles standard RSS elements alongside advanced podcast-specific tags like <podcast:transcript>, <podcast:chapters>, <podcast:value>, and other innovative features of the evolving RSS 2.0 spec.

โœจ Features

  • ๐Ÿง  Simple async API for parsing RSS feeds from URLs, strings, or data
  • ๐Ÿ’ก Codable-based models for seamless Swift integration
  • ๐Ÿ“ก Multi-namespace support including RSS 2.0, iTunes/Apple Podcasts, and Podcasting 2.0
  • ๐Ÿ”ง Comprehensive tag support for modern podcast features
  • ๐Ÿงช Built-in validation and detailed error handling
  • โšก Performance optimized with streaming XML parsing
  • ๐Ÿ›ก๏ธ Type-safe Swift models for all feed elements
  • ๐Ÿ“ฑ Platform support for macOS 13.0+

๐Ÿš€ Quick Start

Installation

Add SyndicationFeed to your Swift package dependencies in Package.swift:

dependencies: [
    .package(url: "https://github.com/fitomad/SyndicationFeed.git", from: "0.9.0")
]

Or add it through Xcode's Package Manager by entering the repository URL.

Basic Usage

import SyndicationFeed

let syndicationFeed = SyndicationFeed()

// Parse from URL
do {
    let url = URL(string: "https://example.com/podcast.xml")!
    let result = try await syndicationFeed.fetchFeedFrom(url: url)
    
    let channel = result.channel
    print("Podcast: \(channel.title)")
    print("Episodes: \(channel.items.count)")
    
    // Access podcast-specific data
    if let podcastInfo = channel.podcasting {
        print("GUID: \(podcastInfo.guid?.uuidString ?? "N/A")")
        print("Medium: \(podcastInfo.medium?.rawValue ?? "N/A")")
    }
    
} catch let error as SyndicationFeedError {
    print("Parsing error: \(error)")
}

Parse from String Content

let xmlContent = """
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:podcast="https://podcastindex.org/namespace/1.0">
    <channel>
        <title>My Podcast</title>
        <description>A great podcast</description>
        <!-- ... more content ... -->
    </channel>
</rss>
"""

do {
    let result = try await syndicationFeed.fetchFeedFrom(content: xmlContent)
    let channel = result.channel
    // Process the parsed feed...
} catch {
    print("Error: \(error)")
}

Parse from Data

let feedData = // ... your RSS feed data
let result = try await syndicationFeed.fetchFeedFrom(data: feedData)

๐Ÿ“Š Supported Features

RSS 2.0 Standard Elements

  • Channel: title, description, link, language, copyright, etc.
  • Items: title, description, publication date, enclosures, etc.
  • Media: enclosures with full metadata support
  • Categories: hierarchical category support

iTunes/Apple Podcasts Namespace

  • Channel metadata: author, categories, explicit content flags
  • Episode data: duration, episode numbers, season numbers
  • Content classification: episode types, explicit content markers
  • Images: high-resolution artwork support

Podcasting 2.0 Namespace

  • Identity: GUID, locked status
  • Rich media: alternate enclosures, chapters, transcripts
  • Monetization: value blocks, funding information
  • Social features: person tags, social interaction
  • Discovery: trailers, seasons, episodes
  • Live content: live item support
  • Location data: geographic information
  • Content links: web-based content references

๐Ÿ—๏ธ Architecture

The package is built with a clean, modular architecture:

SyndicationFeed/
โ”œโ”€โ”€ Entities/           # Data models
โ”‚   โ”œโ”€โ”€ RSS/           # Standard RSS elements
โ”‚   โ”œโ”€โ”€ Apple/         # iTunes/Apple Podcasts elements  
โ”‚   โ””โ”€โ”€ Podcast/       # Podcasting 2.0 elements
โ”œโ”€โ”€ Parsers/           # XML parsing logic
โ”œโ”€โ”€ Handlers/          # Tag-specific parsing handlers
โ”œโ”€โ”€ Mappers/           # Data transformation utilities
โ””โ”€โ”€ Extensions/        # Utility extensions

Core Types

FeedResult

The main result type containing the parsed channel and any parsing errors:

public struct FeedResult {
    public let channel: Channel
    public let parsingErrors: [SyndicationFeedError]?
}

Channel

Represents the main podcast/feed information:

public struct Channel {
    public var title: String
    public var description: String?
    public var items: [Item]    // Episodes
    public var podcasting: Podcasting?  // Podcasting 2.0 data
    public var iTunes: Apple?           // iTunes/Apple data
    // ... other RSS properties
}

Item

Represents individual episodes or items:

public struct Item {
    public var title: String?
    public var description: String?
    public var enclosure: Enclosure?
    public var podcasting: Podcasting? // Episode-specific podcast data
    public var iTunes: Apple?          // iTunes episode data
    // ... other properties
}

๐Ÿ”ง Advanced Usage

Error Handling

SyndicationFeed provides detailed error information through the SyndicationFeedError enum:

do {
    let result = try await syndicationFeed.fetchFeedFrom(url: feedURL)
    
    // Check for parsing warnings
    if let errors = result.parsingErrors {
        for error in errors {
            switch error {
            case .unavailableTag(let tagName, let element):
                print("Unknown tag '\(tagName)' in '\(element)'")
            case .malformedTagValue(let value, let tag):
                print("Invalid value '\(value)' for tag '\(tag)'")
            // Handle other error types...
            default:
                print("Parsing warning: \(error)")
            }
        }
    }
    
} catch SyndicationFeedError.contentNotFound {
    print("Feed content not found or inaccessible")
} catch SyndicationFeedError.malformedContent {
    print("Feed content is malformed or invalid XML")
} catch {
    print("Unexpected error: \(error)")
}

Working with Podcasting 2.0 Features

let result = try await syndicationFeed.fetchFeedFrom(url: podcastURL)
let channel = result.channel

// Check if this is a locked podcast
if let locked = channel.podcasting?.locked {
    print("Podcast is locked by: \(locked.owner)")
}

// Access value/payment information
if let values = channel.podcasting?.values {
    for value in values {
        print("Payment method: \(value.method)")
        print("Recipient: \(value.address)")
        print("Split: \(value.split)%")
    }
}

// Process episodes with transcripts
for item in channel.items {
    if let transcripts = item.podcasting?.transcripts {
        for transcript in transcripts {
            print("Transcript available at: \(transcript.url)")
            print("Language: \(transcript.language ?? "unknown")")
        }
    }
}

๐Ÿงช Testing

The package includes comprehensive tests with sample RSS feeds:

swift test

Test resources include:

  • iTunes-compatible feeds
  • Podcasting 2.0 example feeds
  • Various RSS versions (0.91, 0.92, 2.0)
  • Edge case scenarios

๐Ÿ“‹ Requirements

  • Swift: 6.0+
  • Platform: macOS 13.0+
  • Dependencies: Foundation only (no external dependencies)

๐Ÿ“„ License

This project is licensed under the MIT License. See the LICENSE file for details.

๐Ÿ™ Acknowledgments

  • Podcast Index for the Podcasting 2.0 namespace specification
  • Apple for the iTunes podcast specification
  • The RSS specification maintainers

Version history

0.9.0

  • Support for...
    • RSS
    • iTunes
    • Podcasting 2.0

Contact


Let your Swift code speak fluent podcast. ๐ŸŽ™๏ธ

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

  • None
Last updated: Wed Apr 08 2026 17:13:59 GMT-0900 (Hawaii-Aleutian Daylight Time)