Add Atom and JSON Feed generators#877
Conversation
Foundation for multi-format feed generation (RSS, Atom, JSON Feed): - Add FeedFormat enum with .rss, .atom, .json cases - Extend FeedConfiguration with formats and per-format paths - Extract xmlEscape() from FeedGenerator into shared String.xmlEscaped - Add Date.asRFC3339() for Atom and JSON Feed date formatting - Fix pre-existing Sendable conformance in ContentFinder tests - Maintain full backward compatibility with existing RSS configuration
- AtomFeedGenerator: RFC 4287 compliant Atom XML feed generation with proper XML escaping, CDATA wrapping, RFC 3339 dates, and support for description-only and full-content modes - JSONFeedGenerator: JSON Feed v1.1 compliant output using Encodable types and JSONEncoder for safe serialization (no escaping bugs) - Both generators use format-specific paths (paths[.atom], paths[.json]) instead of the RSS path for their self-referencing URLs - 28 new tests covering golden path, content modes, XML escaping, author handling, tags, images, timezone formatting, and count limiting
- generateFeed() now loops over all enabled formats, dispatching to the correct generator (RSS, Atom, or JSON Feed) for each - FeedLink renders one link per configured format with format-specific display names (RSS, Atom, JSON Feed) in consistent sorted order - Backward compatible: existing sites with default config continue to generate only RSS at /feed.rss
- Add <link rel="alternate"> tags to <head> for each enabled feed format, enabling automatic feed discovery by feed readers - Atom feeds now emit both <icon> and <logo> elements when an image is configured, covering both compact and expanded display contexts - JSON Feed now emits both "icon" and "favicon" fields when an image is configured, matching the v1.1 spec's dual-image support - New MetaLink.feedDiscoveryLinks(for:) generates properly typed alternate links with correct MIME types per format
|
Amazing work @jpurnell, thanks for submitting! I see your checklist is still missing the final step, still something you want to handle before we proceed? |
JSON Feed v1.1 requires either content_html or content_text per item. In descriptionOnly mode, items previously had only summary (which the spec treats as distinct from content). Now sets content_text to the article description as a fallback, ensuring every item has at least one content field per the spec. Validated against https://www.jsonfeed.org/version/1.1/
|
Thanks for the catch! That final checkbox is now done. Validated against the JSON Feed v1.1 spec — all required fields ( |
|
Hello! This is a great set of changes – thank you! A couple of questions:
Thank you! |
|
The date extension mostly just gives an offset for Time Zone…on my site, I like to have the time of the post, but it's UTC with the existing extension. I wasn't sure if changing the signature would break other people's work, and technically, Atom is looking for RFC3339, so thought it better to make that explicit. XMLEscape is more flexible as a shared extension rather than private to the FeedGenerator in the RSS fix, so it can be used for the Atom Generator now too. |
The current extension uses ISO 8601, but the specific defaults Apple chose are RFC3339. If you want to modify it to accept a time zone we can do that, but I think it should be the same extension rather than a second one. Would that be okay?
In that case, we should perhaps delete the version from |
|
@jpurnell I'm just checking in: what should we do with this PR? |
Per @twostraws's review on twostraws#877: - Date: replace `Date.asRFC3339(timeZone:)` with a new `Date.asISO8601(timeZone:)` method on the existing extension. RFC 3339 is the format Apple's `ISO8601DateFormatter` produces with `.withInternetDateTime`, so a separate name was redundant. The existing `var asISO8601` is preserved as a UTC convenience that delegates to the method, so no callers break. - String: rename `xmlEscaped` to `escapedForXML()` and make it a public method, matching the namespace style of the existing `escapedForJavascript()` extension. Callers in AtomFeedGenerator, JSONFeedGenerator, FeedGenerator (RSS), and Time updated. Test files renamed and merged accordingly.
|
Sorry…lot of life events. Made some changes: Date.asISO8601 — Folded the timezone capability into the existing extension as asISO8601(timeZone:). Kept var asISO8601 as a UTC convenience that delegates to the method, so existing callers (including Time.swift) String.escapedForXML() — Renamed xmlEscaped to escapedForXML() to match the escapedForJavascript() namespace convention, made it a public extension, and consolidated all the RSS/Atom call sites onto it. |
Summary
FeedFormatenum and extendFeedConfigurationwithformatsand per-formatpaths, fully backward compatible (defaults to[.rss]with existing behavior)<link rel="alternate">auto-discovery tags to<head>so feed readers can find feeds automaticallyString.xmlEscapedandDate.asRFC3339()utilities used by both XML-based generatorsEncodable+JSONEncoderfor safe serialization with no escaping bugs by constructionTest plan
IgnitetargetfeedConfigurationgenerate only RSS at/feed.rsswith no code changescontent_textfallback in descriptionOnly mode for strict compliance