Multi-format RSS 2.0, Atom 1.0 and JSON Feed 1.0/1.1 parsing library with source-aware links and diagnostics
| Public field | RSS 2.0 source | Atom 1.0 source | JSON Feed 1.0/1.1 source |
|---|---|---|---|
| Feed.identifier | none | feed id | feed_url |
| Feed.description | channel description | feed subtitle | description |
| Feed.language | channel language | feed xml:lang | language (1.1 only) |
| Entry.language | inherited channel language | inherited feed xml:lang, or entry override/reset | inherited feed language, or item override (1.1 only) |
| Feed.updated_at | channel lastBuildDate | feed updated | none |
| Feed.published_at, Feed.ttl_minutes | channel pubDate, ttl | none | none |
| Feed.skip_hours, Feed.skip_days | direct channel skipHours/hour (GMT 0–23), skipDays/day (English weekday) | empty arrays | empty arrays |
| Feed.docs_url, Feed.rating, Feed.cloud, Feed.text_input | direct channel docs, raw rating, cloud, textInput; no registration or form submission | none | none |
| Feed.rights, Feed.generator | channel copyright, generator | feed rights, generator | none |
| Feed.next_url, Feed.expired, Feed.hubs | none | none | next_url, expired, hubs |
| Entry.identifier | non-blank guid, otherwise non-blank link | entry id | item id |
| Entry.link | item link; if absent, an HTTP(S) guid with default/true isPermaLink and a valid absolute URL | first alternate link | item url |
| Entry.description | item description | entry summary | summary |
| Entry.content | Content Module content:encoded | inline text/HTML/XHTML display string; not XML/binary/external bodies | content_html, otherwise content_text |
| Entry.content_source | none | external content src, raw with parse, resolved with parse_with_source | none |
| Entry.content_media_type | none | declared content type spelling, if present | none |
| Entry.atom_body | none | detailed inline or external Atom body | none |
| Entry.atom_source | none | original feed source id/title/subtitle/updated, rights, generator (including uri/version), links, images, people and categories, separately from the aggregated entry | none |
| Author.uri | none | feed/entry/source author or contributor uri; raw in parse, scoped by xml:base in parse_with_source | author url |
| Entry.content_text, Entry.content_html | none | none | both item forms independently |
| Entry.comments_url, Entry.source_url, Entry.source_title | item comments, source/@url and source channel title | none | none |
| Entry.guid_is_permalink | guid/@isPermaLink, default true on a usable guid | none | none |
| Feed.managing_editor, Feed.webmaster | channel editorial and technical contact text | none | none |
| Entry.rights | none | own rights, otherwise inherited feed rights; explicit empty text blocks inheritance | none |
| Entry.published_at | item pubDate | entry published | date_published |
| Entry.updated_at | none | entry updated | date_modified |
| Feed.authors | none | direct feed authors | authors, otherwise legacy author |
| Entry.authors | direct item authors | effective entry authors | item authors/author, otherwise feed authors |
| Feed/Entry.contributors | none | direct contributors, separate from author inheritance | none |
| Feed/Entry.links | direct channel Atom-namespace self links in Feed.links; item links not mapped | ordered Atom link relations and common attributes; malformed MIME and hreflang hints produce positioned diagnostics while raw values remain accessible | none |
| Author.avatar | none | none | author avatar URL |
| Feed.categories | direct channel categories | direct feed categories | none in supported subset |
| Feed.images | direct channel image | direct feed icon/logo | feed icon/favicon |
| Entry.categories | direct item categories | direct entry categories | tags |
| Entry.attachments | direct item enclosure | direct entry link rel="enclosure" | item attachments |
| Entry.external_url | none | none | item external_url, separate from url permalink |
| Entry.images | none in supported core | none in supported core | item image (featured preview), banner_image (detail banner) |
match @feedlens.normalize_date(feed.format, feed.entries[0].published_at) {
Ok(Some(date)) => println(date.utc)
Ok(None) => println("publication date missing")
Err(_) => println("publication date invalid or unsupported")
}match @feedlens.parse_with_source(input, "https://example.org/feeds/news.atom") {
Ok(feed) => println(feed.entries[0].link.unwrap_or("(no link)"))
Err(_) => println("feed or source URL invalid")
}feed.title = Some("Plan A & B")
feed.title_kind = Xhtml
feed.title_xhtml.markup = "Plan <h:em xmlns:h=\"http://www.w3.org/1999/xhtml\">A</h:em> & B"
entry.title = Some("Race review")
entry.content = Some("Lap 12 & rain.")
entry.content_kind = Xhtml
entry.content_xhtml.markup = "<h:p xmlns:h=\"http://www.w3.org/1999/xhtml\">Lap <h:strong xmlns:h=\"http://www.w3.org/1999/xhtml\">12</h:strong> & rain.</h:p>"
entry.atom_body = Some(InlineXhtml(...))match @feedlens.parse(input) {
Ok(feed) => {
let entry = feed.entries[0]
println(entry.identifier.unwrap_or("(no identifier)"))
match entry.atom_body {
Some(@feedlens.InlineBinary(bytes, media_type)) =>
println(media_type + ": " + bytes.length().to_string() + " bytes")
Some(@feedlens.External(reference)) =>
println("external, not fetched: " + reference.url)
_ => ()
}
}
Err(_) => println("feed parsing failed")
}moon run --target native cmd/main samples/real_wptavern_rss.xml
moon run --target native cmd/main samples/real_wptavern_atom.xml
moon run --target native cmd/main --json samples/real_wptavern_json.json
moon run --target native cmd/main --json samples/atom1_xhtml.xml
moon run --target native cmd/main --json samples/atom1_content_forms.xml
moon run --target native cmd/main --json samples/rss2_optional_channel.xml
moon run --target native cmd/main --json samples/jsonfeed10_legacy_ids.json
moon run --target native cmd/main -- --helpFeedLens — MoonBit multi-format Feed parser library
Format: RSS 2.0
Title: WP Tavern
Link: https://wptavern.com
Entries: 2
1. identifier: https://wptavern.com/?post_type=podcast&p=209016
title: #237 – Robert Abela on WordPress Security and the Battle Against AI Threats
link: https://wptavern.com/podcast/237-robert-abela-on-wordpress-security-and-the-battle-against-ai-threats/
attachments: 1powershell -ExecutionPolicy Bypass -File tools/check_cli.ps1pub enum AtomBody {
InlineText(String, String)
InlineHtml(String)
InlineXhtml(XhtmlFragment)
InlineXml(XmlFragment, String)
InlineBinary(Bytes, String)
External(AtomExternalContent)
} derive(Eq, Debug)pub struct AtomSource {
identifier : String?
title : String?
title_kind : TextKind
title_xhtml : XhtmlFragment?
subtitle : String?
subtitle_kind : TextKind
subtitle_xhtml : XhtmlFragment?
updated_at : String?
rights : String?
rights_kind : TextKind
rights_xhtml : XhtmlFragment?
generator : String?
generator_uri : String?
generator_version : String?
authors : Array[Author]
contributors : Array[Author]
categories : Array[Category]
link : String?
links : Array[AtomLink]
images : Array[FeedImage]
} derive(Eq, Debug)pub struct Attachment {
source : FeedFormat
url : String
media_type : String?
size_in_bytes : Int64?
title : String?
duration_in_seconds : Double?
} derive(Eq, Debug)pub struct Author {
source : AuthorSource
name : String?
uri : String?
email : String?
avatar : String?
} derive(Eq, Debug)pub struct Category {
source : FeedFormat
term : String
domain : String?
scheme : String?
label : String?
} derive(Eq, Debug)pub enum DiagnosticKind {
MissingChannelField(String)
MissingItemIdentifier(Int)
MissingItemContent(Int)
MissingFeedField(String)
MissingEntryField(Int, String)
UnsupportedNestedMarkup(String)
UnsupportedTextConstruct(String)
UnsupportedAtomContent(String)
MissingAlternateLink(Int)
MissingSummaryForExternalContent(Int)
MissingSummaryForBinaryContent(Int)
DuplicateAtomField(String)
InvalidAuthor(String)
InvalidCategory(String)
InvalidAttachment(String)
InvalidLanguage(String)
InvalidFeedImage(String)
InvalidRssField(String)
UnnormalizedRssDate(String)
InvalidAtomLink(String)
InvalidAtomGeneratorUri(String)
InvalidAtomId(String)
InvalidAtomDate(String)
InvalidAtomOrder(String)
UnresolvedUri(String)
InvalidJsonFeedField(String)
InvalidJsonFeedItem(Int, String)
InvalidJsonFeedDate(Int, String)
} derive(Eq, Debug)pub struct Entry {
identifier : String?
title : String?
title_kind : TextKind
title_xhtml : XhtmlFragment?
link : String?
external_url : String?
language : String?
description : String?
description_kind : TextKind
description_xhtml : XhtmlFragment?
content : String?
content_text : String?
content_html : String?
content_kind : TextKind
content_xhtml : XhtmlFragment?
content_source : String?
content_media_type : String?
atom_body : AtomBody?
atom_source : AtomSource?
links : Array[AtomLink]
comments_url : String?
source_url : String?
source_title : String?
guid_is_permalink : Bool?
rights : String?
rights_kind : TextKind
rights_xhtml : XhtmlFragment?
authors : Array[Author]
contributors : Array[Author]
categories : Array[Category]
attachments : Array[Attachment]
images : Array[EntryImage]
published_at : String?
updated_at : String?
} derive(Eq, Debug)pub struct Feed {
format : FeedFormat
identifier : String?
title : String?
title_kind : TextKind
title_xhtml : XhtmlFragment?
link : String?
language : String?
description : String?
description_kind : TextKind
description_xhtml : XhtmlFragment?
updated_at : String?
published_at : String?
ttl_minutes : Int?
docs_url : String?
rating : String?
cloud : RssCloud?
text_input : RssTextInput?
skip_hours : Array[Int]
skip_days : Array[String]
rights : String?
rights_kind : TextKind
rights_xhtml : XhtmlFragment?
generator : String?
generator_uri : String?
generator_version : String?
managing_editor : String?
webmaster : String?
next_url : String?
expired : Bool?
hubs : Array[Hub]
links : Array[AtomLink]
authors : Array[Author]
contributors : Array[Author]
categories : Array[Category]
images : Array[FeedImage]
entries : Array[Entry]
diagnostics : Array[Diagnostic]
} derive(Eq, Debug)pub struct FeedImage {
source : FeedFormat
role : FeedImageRole
url : String
title : String?
link : String?
description : String?
width : Int?
height : Int?
} derive(Eq, Debug)pub struct NormalizedDate {
utc : String
unix_seconds : Int64
nanosecond : Int
offset_minutes : Int
offset_known : Bool
is_leap_second : Bool
precision : DatePrecision
} derive(Eq, Debug)pub enum ParseError {
InvalidXml(String, Int, Int)
UnsupportedRoot(String, String?)
UnsupportedRssVersion(String?)
MissingChannel
InvalidRssStructure(String)
InvalidAtomStructure(String)
InvalidJson(String, Int, Int)
InvalidJsonFeed(String)
UnsupportedJsonFeedVersion(String?)
InvalidSourceUrl(String)
} derive(Eq, Debug)pub struct XhtmlFragment {
markup : String
language : String?
base_references : Array[String]
container_attributes : Array[XhtmlAttribute]
} derive(Eq, Debug)fn parse_document_with_source(input : String, source_url : String) -> Result[ParsedDocument, ParseError]Install
Download zipMulti-format RSS 2.0, Atom 1.0 and JSON Feed 1.0/1.1 parsing library with source-aware links and diagnostics