Syndicate
Syndication feed parser and generator supporting RSS 2.0, RSS
0.91, RSS 1.0, Atom 1.0, and JSON Feed 1.1.
Dependencies
Installation
zef install Syndicate
Installing from source
To install from a local folder:
cd /path/to/Syndicate
zef install .
Requires Raku 6.d or later.
| Format | Parse | Generate | Class |
|---|
| RSS 2.0 | ✓ | ✓ | Syndicate::RSS |
| RSS 0.91 | ✓ | ✓ | Syndicate::RSS::V0_91 |
| RSS 1.0 | ✓ | ✓ | Syndicate::RSS::V1_0 |
| Atom 1.0 | ✓ | ✓ | Syndicate::Atom |
| JSON Feed | ✓ | ✓ | Syndicate::JSONFeed |
Quick Start
use Syndicate;
# --- Parse any feed format (auto-detected) ---
my $feed = parse($xml-or-json-string);
say $feed.title;
say $feed.items[0].title;
# --- Build a feed programmatically ---
use Syndicate::Builder::Feed;
my $fb = Syndicate::Builder::Feed.new;
$fb.title("My Podcast");
$fb.link("https://example.com");
$fb.description("A great podcast");
$fb.language("en");
$fb.generator("Syndicate");
my $entry = $fb.add-entry;
$entry.title("Episode 1");
$entry.link("https://example.com/ep1");
$entry.summary("First episode");
$entry.id("urn:uuid:abc-123");
$entry.updated(DateTime.now);
# Generate any format from the same builder
say $fb.rss-str; # RSS 2.0 XML
say $fb.atom-str; # Atom 1.0 XML
say $fb.rss091-str; # RSS 0.91 XML
say $fb.rss1-str; # RSS 1.0 XML
say $fb.json-str; # JSON Feed
Parsing Feeds
use Syndicate;
use Syndicate::Parse;
my $feed = parse-feed($xml-or-json-string);
# or
my $feed = parse($xml-or-json-string); # exported by Syndicate
The parser detects the format automatically:
{ → JSON Feed<feed → Atom 1.0<rss version="0.91" → RSS 0.91<rss version="0.92|0.93|0.94" → RSS 2.0 (legacy 0.9x flavors are structurally RSS 2.0-compatible)<rss → RSS 2.0<rdf:RDF → RSS 1.0
Parse from a file
use Syndicate::Parse;
my $feed = parse-file("feed.xml"); # Str path
my $feed = parse-file("feed.xml".IO); # IO::Path also works
my $rss = Syndicate::RSS.new($xml-string);
my $atom = Syndicate::Atom.new($xml-string);
my $rss091 = Syndicate::RSS::V0_91.new($xml-string);
my $rss1 = Syndicate::RSS::V1_0.new($xml-string);
my $json = Syndicate::JSONFeed.new($json-string);
Convenience subs are also exported by Syndicate:
my $rss = parse-rss($xml-string);
my $atom = parse-atom($xml-string);
Building Feeds
The builder lets you construct a feed once and output it in any format:
use Syndicate::Builder::Feed;
my $fb = Syndicate::Builder::Feed.new;
$fb.title("My Feed");
$fb.link("https://example.com");
$fb.description("A test feed");
$fb.id("https://example.com/feed");
$fb.language("en");
$fb.rights("© 2026 Me");
$fb.generator("MyApp");
$fb.icon("https://example.com/icon.png");
$fb.logo("https://example.com/logo.png");
$fb.updated(DateTime.new("2026-06-15T12:00:00Z"));
# Author (stored as name, email, uri)
$fb.author(:name("Jane Doe"), :email("jane@example.com"));
# Categories (multiple allowed)
$fb.category("Tech");
$fb.category("News");
# Add entries
my $e = $fb.add-entry;
$e.title("Article 1");
$e.link("https://example.com/1");
$e.summary("First article");
$e.id("urn:uuid:1111-1111");
$e.updated(DateTime.new("2026-06-15T10:00:00Z"));
$e.published(DateTime.new("2026-06-14T08:00:00Z"));
$e.author(:name("Jane"), :email("jane@example.com"));
$e.category("Raku");
$e.content("<p>Hello <em>world</em></p>", :type("xhtml"));
$e.rights("© Entry");
# Output in any format
say $fb.rss-str; # RSS 2.0
say $fb.atom-str; # Atom 1.0
say $fb.rss091-str; # RSS 0.91
say $fb.rss1-str; # RSS 1.0
say $fb.json-str; # JSON Feed
# Or get the feed object directly
my $feed = $fb.atom-feed;
say $feed.updated;
You can also construct feed objects directly with named arguments:
# RSS 2.0
my $rss = Syndicate::RSS.new(
:title("My RSS Feed"),
:link("https://example.com"),
:description("RSS description"),
:language("en"),
:generator("MyApp"),
:copyright("© 2026"),
:items([
Syndicate::RSS::Item.new(
:title("Item 1"),
:link("https://example.com/1"),
:summary("Item description"),
:author("author@example.com"),
:updated(DateTime.new("2026-06-15T10:00:00Z")),
:guid("https://example.com/1"),
),
]),
);
# Atom 1.0
my $atom = Syndicate::Atom.new(
:title("My Atom Feed"),
:id("https://example.com/atom"),
:link("https://example.com"),
:description("Atom subtitle"),
:updated(DateTime.now),
:rights("© 2026"),
:generator("MyApp"),
:items([
Syndicate::Atom::Item.new(
:title("Entry 1"),
:id("https://example.com/1"),
:link("https://example.com/1"),
:summary("Entry summary"),
:content("<p>Hello world</p>"),
:content-type("xhtml"),
:updated(DateTime.new("2026-06-15T10:00:00Z")),
:published(DateTime.new("2026-06-14T08:00:00Z")),
),
]),
);
# JSON Feed
my $json = Syndicate::JSONFeed.new(
:title("My JSON Feed"),
:link("https://example.com"),
:description("JSON Feed description"),
:feed_url("https://example.com/feed.json"),
:language("en"),
:items([
Syndicate::JSONFeed::Item.new(
:title("Post 1"),
:id("https://example.com/1"),
:link("https://example.com/1"),
:summary("Post summary"),
:content_html("<p>Hello world</p>"),
),
]),
);
iTunes Podcast Extensions
The builder supports iTunes podcast fields at the channel level for RSS 2.0:
$fb.itunes-author("John Doe");
$fb.itunes-summary("A great podcast about Raku");
Per-item iTunes fields (itunes-author, itunes-summary, itunes-duration)
are read-only: they are populated when an RSS 2.0 feed is parsed, not via the
builder.
Generating Output
Every feed and item can be stringified to its native format:
# RSS / Atom → XML string
say ~$rss-feed; # XML output via .Str
say $rss-feed.Str; # same
say $rss-feed.XML; # XML::Element object
# JSON Feed → JSON string
say $json-feed.Str; # JSON output
say $json-feed.to-json; # same
say $json-feed.to-hash; # Perl Hash structure
Parse any feed and re-emit it in another format with convert:
use Syndicate;
my $rss-xml = convert($atom-string, 'rss'); # Atom → RSS 2.0
my $atom-xml = convert($rss-string, 'atom'); # RSS 2.0 → Atom
my $json = convert($rss-string, 'json'); # RSS 2.0 → JSON Feed
$to accepts rss (aliases rss2, rss2.0), rss091 (rss0.91),
rss1 (rss1.0), atom, and json. Conversion goes through
Syndicate::Builder::Feed.new-from-feed, which copies every field the builder
models. Format-specific extras that the builder does not model (RSS
webMaster/docs/ttl/pubDate, Atom contributors and extra/self links,
media groups, item-level iTunes fields, JSON external_url/image/
banner_image) are not carried over. Emission may die if the target format
requires a field the source lacks — e.g. an Atom feed without a description
cannot be emitted as RSS 2.0.
For programmatic access, populate a builder directly:
use Syndicate;
use Syndicate::Builder::Feed;
my $fb = Syndicate::Builder::Feed.new-from-feed(parse($rss-string));
say $fb.atom-str; # RSS 2.0 → Atom, with a single builder
say $fb.json-str; # and JSON Feed from the same builder
Feed Discovery
Fetch and auto-detect feeds from URLs:
use Syndicate::Discovery;
my $disc = Syndicate::Discovery.new;
# Fetch a known feed URL
my $feed = $disc.fetch("https://example.com/feed.xml");
# Discover feed from a webpage (finds <link> tags)
my $feed = $disc.discover("https://example.com");
# Customize redirect handling (maximum of 10 redirects)
my $disc = Syndicate::Discovery.new(:max-redirect(10));
my $feed = $disc.fetch("https://example.com/feed.xml");
# Find feed URLs without fetching
my @urls = $disc.find-feeds($html-string, "https://example.com");
Extensions
Extensions are automatically applied during parsing and generation via the Syndicate::Extensions registry.
Dublin Core
Adds dc:creator, dc:date, dc:subject elements to RSS items.
use Syndicate::Extension::DublinCore;
# Automatically registered - parses and generates dc:* elements
Adds media content and thumbnails to RSS items.
use Syndicate::Extension::MediaRSS;
# Access parsed media data
.say for $rss-item.media-contents; # Array of Hashes (url, type, medium, ...)
.say for $rss-item.media-thumbnails; # Array of Hashes (url, width, height)
say $rss-item.media-title;
say $rss-item.media-description;
iTunes Podcast
Adds iTunes podcast elements to RSS feeds and items.
use Syndicate::Extension::ITunes;
say $rss-feed.itunes-author;
say $rss-feed.itunes-summary;
say $rss-item.itunes-author;
say $rss-item.itunes-summary;
say $rss-item.itunes-duration;
Common API
All feed types do the Syndicate::Feed role. All item types do the Syndicate::Item role. This provides a uniform interface:
Feed Role Attributes
| Attribute | Type | RSS 2.0 | RSS 0.91 | RSS 1.0 | Atom | JSON Feed |
|---|
.title | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.link | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.description | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.generator | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.language | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.items | @ | ✓ | ✓ | ✓ | ✓ | ✓ |
Item Role Attributes
| Attribute | Type | RSS 2.0 | RSS 0.91 | RSS 1.0 | Atom | JSON Feed |
|---|
.title | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.link | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.summary | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.author | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.updated | DateTime | ✓ | ✓ | ✓ | ✓ | ✓ |
.id | Str | ✓ | ✓ | ✓ | ✓ | ✓ |
.content | Str | ✓ | | ✓ | ✓ | ✓ |
RSS 0.91 and RSS 1.0 populate author and updated from Dublin Core
(dc:creator, dc:date); JSON Feed's updated derives from date_modified.
RSS 0.91 items have no content module, so .content is never populated.
Each format also exposes its own attributes:
RSS 2.0 Feed: copyright, managingEditor, webMaster, pubDate, lastBuildDate, categories, docs, ttl, image, itunes-author, itunes-summary
RSS 2.0 Item: guid, guid-is-permalink, categories, comments, enclosure, source, media-contents, media-thumbnails, media-title, media-description, itunes-author, itunes-summary, itunes-duration
RSS 0.91 Feed: copyright, managingEditor, webMaster, rating, docs, pubDate, lastBuildDate, image, textInput, skipHours, skipDays
RSS 1.0 Feed: about, image (hash with url/title/link/about)
RSS 1.0 Item: about, dc-subjects
Atom Feed: id, subtitle, author, author-detail, categories, updated, rights, icon, logo, contributors, link-self, link-alternate
Atom Item: author-detail, categories, published, content-type, rights, source-feed, contributors
JSON Feed: version, feed_url, user_comment, next_url, icon, favicon, author, expired
JSON Feed Item: external_url, content_html, content_text, image, banner_image, date_published, date_modified, authors, tags
Statistics
use Syndicate::Stats;
say "Feeds parsed: {Syndicate::Stats.feeds-parsed}";
say "Items parsed: {Syndicate::Stats.items-parsed}";
Syndicate::Stats.record-feed;
Syndicate::Stats.record-item;
AUTHOR
Sasha Abbott sashaa@disroot.org
LICENSE
This software is dedicated to the public domain under the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.
To the extent possible under law, the author has waived all copyright and related or neighboring rights to this work.