A simple, flexible, extensible, and liberal RSS and Atom reader for Ruby. Designed to be backwards compatible with Ruby's standard RSS parser while handling malformed feeds gracefully.
- Parses both RSS and Atom feeds
- Tolerant of malformed XML (regex-based parsing)
- Built-in URL fetching with conditional GET support (ETags, Last-Modified)
- JSON and XML serialization
- Extensible tag definitions
- Zero runtime dependencies
Version 2.0 is a major update with powerful new capabilities:
-
URL Fetching - One-liner feed fetching with
SimpleRSS.fetch(url). Supports timeouts, custom headers, and automatic redirect following. -
Conditional GET - Bandwidth-efficient polling with ETag and Last-Modified support. Returns
nilwhen feeds haven't changed (304 Not Modified). -
JSON Serialization - Export feeds with
to_json,to_hash, and Rails-compatibleas_json. Time objects serialize to ISO 8601. -
XML Serialization - Convert any parsed feed to clean RSS 2.0 or Atom XML with
to_xml(format: :rss2)orto_xml(format: :atom). -
Array Tags - Collect all occurrences of a tag (like multiple categories) with the
array_tags:option. -
Attribute Parsing - Extract attributes from feed, item, and media tags using the
tag#attrsyntax. -
UTF-8 Normalization - All parsed content is automatically normalized to UTF-8 encoding.
-
Modern Ruby - Full compatibility with Ruby 3.1 through 4.0, with RBS type annotations and Steep type checking.
-
Enumerable Support - Iterate feeds naturally with
each,map,select, and all Enumerable methods. Access items by index withrss[0]and get the latest items sorted by date withlatest(n).
Add to your Gemfile:
gem "simple-rss"Or install directly:
gem install simple-rssrequire "simple-rss"
require "uri"
require "net/http"
# Parse from a string or IO object
xml = Net::HTTP.get(URI("https://example.com/feed.xml"))
rss = SimpleRSS.parse(xml)
rss.channel.title # => "Example Feed"
rss.items.first.title # => "First Post"
rss.items.first.pubDate # => 2024-01-15 12:00:00 -0500 (Time object)SimpleRSS includes a built-in fetcher with conditional GET support for efficient polling:
# Simple fetch
feed = SimpleRSS.fetch("https://example.com/feed.xml")
# With timeout
feed = SimpleRSS.fetch("https://example.com/feed.xml", timeout: 10)
# Conditional GET - only download if modified
feed = SimpleRSS.fetch("https://example.com/feed.xml")
# Store these for next request
etag = feed.etag
last_modified = feed.last_modified
# On subsequent requests, pass the stored values
feed = SimpleRSS.fetch(
"https://example.com/feed.xml",
etag:,
last_modified:
)
# Returns nil if feed hasn't changed (304 Not Modified)SimpleRSS provides both RSS and Atom style accessors:
feed = SimpleRSS.parse(xml)
# RSS style
feed.channel.title
feed.channel.link
feed.channel.description
feed.items
# Atom style (aliases)
feed.feed.title
feed.entriesItems support both hash and method access:
item = feed.items.first
# Hash access
item[:title]
item[:link]
item[:pubDate]
# Method access
item.title
item.link
item.pubDateDate fields are automatically parsed into Time objects:
item.pubDate.class # => Time
item.pubDate.year # => 2024SimpleRSS includes Enumerable, so you can iterate feeds naturally:
feed = SimpleRSS.parse(xml)
# Iterate over items
feed.each { |item| puts item.title }
# Use any Enumerable method
titles = feed.map { |item| item.title }
tech_posts = feed.select { |item| item.category == "tech" }
first_five = feed.first(5)
total = feed.count
# Access items by index
feed[0].title # first item
feed[-1].title # last item
# Get the n most recent items (sorted by pubDate or updated)
feed.latest(10)feed = SimpleRSS.parse(xml)
# Get as hash
feed.to_hash
# => { title: "Feed Title", link: "...", items: [...] }
# Get as JSON string
feed.to_json
# => '{"title":"Feed Title","link":"...","items":[...]}'
# Works with Rails/ActiveSupport
feed.as_jsonConvert parsed feeds to standard RSS 2.0 or Atom format:
feed = SimpleRSS.parse(xml)
# Convert to RSS 2.0
feed.to_xml(format: :rss2)
# Convert to Atom
feed.to_xml(format: :atom)Add support for custom or non-standard tags:
# Add a new feed-level tag
SimpleRSS.feed_tags << :custom_tag
# Add item-level tags
SimpleRSS.item_tags << :custom_item_tag
# Parse tags with specific rel attributes (common in Atom)
SimpleRSS.item_tags << :"link+enclosure"
# Accessible as: item.link_enclosure
# Parse tag attributes
SimpleRSS.item_tags << :"media:content#url"
# Accessible as: item.media_content_url
# Parse item/entry attributes
SimpleRSS.item_tags << :"entry#xml:lang"
# Accessible as: item.entry_xml_lang| Syntax | Example | Accessor | Description |
|---|---|---|---|
tag |
:title |
.title |
Simple element content |
tag#attr |
:"media:content#url" |
.media_content_url |
Attribute value |
tag+rel |
:"link+alternate" |
.link_alternate |
Element with specific rel attribute |
By default, SimpleRSS returns only the first occurrence of each tag. To collect all values:
# Collect all categories for each item
feed = SimpleRSS.parse(xml, array_tags: [:category])
item.category # => ["tech", "programming", "ruby"]Parse RSS/Atom content from a string or IO object.
Parameters:
source- String or IO object containing feed XMLoptions- Hash of options:array_tags- Array of tag symbols to collect as arrays
Returns: SimpleRSS instance
Fetch and parse a feed from a URL.
Parameters:
url- Feed URL stringoptions- Hash of options:timeout- Request timeout in seconds:etag- ETag from previous request (for conditional GET):last_modified- Last-Modified header from previous request:follow_redirects- Follow redirects (default: true):headers- Hash of additional HTTP headers
Returns: SimpleRSS instance, or nil if 304 Not Modified
| Method | Description |
|---|---|
#channel / #feed |
Returns self (for RSS/Atom style access) |
#items / #entries |
Array of parsed items |
#each |
Iterate over items (includes Enumerable) |
#[](index) |
Access item by index |
#latest(n = 10) |
Get n most recent items by date |
#to_json |
JSON string representation |
#to_hash / #as_json |
Hash representation |
#to_xml(format:) |
XML string (:rss2 or :atom) |
#etag |
ETag header from fetch (if applicable) |
#last_modified |
Last-Modified header from fetch (if applicable) |
#source |
Original source XML string |
- Ruby 3.1+
- No runtime dependencies
# Run tests
bundle exec rake test
# Run linter
bundle exec rubocop
# Type checking
bundle exec steep check
# Interactive console
bundle exec rake console- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes with tests
- Ensure tests pass (
bundle exec rake test) - Submit a pull request
Inspired by Blagg by Rael Dornfest.
This library is released under the terms of the GNU LGPL.