feed:   RSS 2.0 and Atom Parsing
1 Parsing
parse-feed
parse-rss
parse-atom
feed-item
2 Feed dates
parse-date-any
parse-8601
parse-rfc822
3 The xexpr toolkit
xexpr-from-bytes
xexpr-tag?
xexpr-tag-suffix?
xexpr-children
xexpr-text
xexpr-find-child
xexpr-child-text
xexpr-attr
9.3

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

(struct feed-item (title link summary published))

  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.