feed: RSS 2.0 and Atom Parsing
| (require feed) | package: feed |
This package parses RSS 2.0 and Atom feeds into plain data. It is pure: bytes in, items out — fetching (e.g. with http-easy), HTML stripping, URL normalization, and dedup policy all stay with the caller. It exists because the two workarounds every feed parser ends up needing — the xml package’s literal CDATA wrapper and namespace-prefixed tag names like content:encoded — are handled here once, and the xexpr toolkit behind them is exported.
1 Parsing
procedure
(parse-feed b [#:max-entries max-entries]) → (listof feed-item?)
b : bytes? max-entries : (or/c exact-positive-integer? #f) = #f
Sniffs the root element (feed means Atom, anything else RSS 2.0) and dispatches. Malformed bytes yield '(), not an exception — feed fetches fail often and callers want a branch. Feed authors and consumers disagree about what makes an entry usable, so the parser keeps every entry; filter on feed-item-link etc. yourself.
procedure
(parse-rss b [#:max-entries max-entries]) → (listof feed-item?)
b : bytes? max-entries : (or/c exact-positive-integer? #f) = #f
Parses <item> entries. Summary comes from <description> or <content:encoded>; the date from <pubDate> or <dc:date>.
procedure
(parse-atom b [#:max-entries max-entries]) → (listof feed-item?)
b : bytes? max-entries : (or/c exact-positive-integer? #f) = #f
Parses <entry> elements. Link prefers rel="alternate" or a bare <link>, falling back to any link; summary from <summary> or <content>; date from <published> or <updated>.
struct
title : string? link : (or/c string? #f) summary : string? published : (or/c integer? #f)
One entry. summary is verbatim — it very often embeds HTML; strip and truncate at your own policy boundary. published is UTC seconds.
> (require feed racket/list)
> (define items (parse-feed #"<rss><channel><item><title>Hello</title><link>http://x.example/</link><pubDate>Tue, 13 Oct 2026 08:30:00 +0200</pubDate></item></channel></rss>")) > (feed-item-title (first items)) "Hello"
> (feed-item-link (first items)) "http://x.example/"
> (feed-item-published (first items)) 1791873000
2 Feed dates
procedure
(parse-date-any s) → (or/c integer? #f)
s : (or/c string? #f)
Tries ISO 8601 (Atom-style, including bare dates and fractional seconds) then RFC 822 (RSS-style, two-digit years included). Returns UTC seconds or #f.
procedure
(parse-8601 s) → (or/c integer? #f)
s : (or/c string? #f)
procedure
(parse-rfc822 s) → (or/c integer? #f)
s : (or/c string? #f)
The individual formats.
3 The xexpr toolkit
The xml package’s CDATA handling and namespace prefixes are the two recurring traps of feed-shaped parsing; these are exported for anyone parsing similar documents.
procedure
(xexpr-from-bytes b) → (or/c xexpr? #f)
b : bytes?
Parses bytes to the root element, or #f on malformed input.
procedure
(xexpr-tag? x n) → boolean?
x : any/c n : symbol?
Exact tag-name match.
procedure
(xexpr-tag-suffix? x n) → boolean?
x : any/c n : symbol?
Matches n or any namespace-prefixed form ending in :n (e.g. content:encoded matches 'encoded).
procedure
(xexpr-children x) → (listof xexpr?)
x : any/c
Element children only — text and cdata excluded.
procedure
(xexpr-text x) → string?
x : any/c
Concatenated character data, recursing into children, with the <![CDATA[...]]> wrapper stripped.
procedure
(xexpr-find-child x n) → (or/c xexpr? #f)
x : any/c n : symbol?
procedure
(xexpr-child-text x n) → (or/c string? #f)
x : any/c n : symbol?
First child matching xexpr-tag-suffix?, and its text.
procedure
(xexpr-attr x n) → (or/c string? #f)
x : any/c n : symbol?
Attribute value by exact name.