# frozen_string_literal: false
require_relative 'parser'
# Atom is an XML-based document format that is used to describe 'feeds' of related information.
# A typical use is in a news feed where the information is periodically updated and which users
# can subscribe to. The Atom format is described in http://tools.ietf.org/html/rfc4287
# The Atom module provides support in reading and creating feeds.
# See the RSS module for examples consuming and creating feeds.
# The Atom URI W3C Namespace
URI = "http://www.w3.org/2005/Atom"
# The XHTML URI W3C Namespace
XHTML_URI = "http://www.w3.org/1999/xhtml"
def self.append_features(klass)
klass.install_must_call_validator("atom", URI)
].each do |name, uri, required|
klass.install_get_attribute(name, uri, required, [nil, :inherit])
# Returns the Atom URI W3C Namespace
def append_features(klass)
klass.extend(ClassMethods)
klass.content_setup(klass.content_type, klass.tag_name)
def setup_maker_element_writer
"#{self.class.name.split(/::/).last.downcase}="
def setup_maker_element(target)
target.__send__(setup_maker_element_writer, content)
def append_features(klass)
@content_type = [nil, :uri]
# The TextConstruct module is used to define a Text construct Atom element,
# which is used to store small quantities of human-readable text.
# The TextConstruct has a type attribute, e.g. text, html, xhtml
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#text.constructs
def self.append_features(klass)
].each do |name, uri, required|
install_get_attribute(name, uri, required, :text_type)
add_need_initialize_variable("xhtml")
# Returns or builds the XHTML content.
return @xhtml if @xhtml.nil?
if @xhtml.is_a?(XML::Element) and
[@xhtml.name, @xhtml.uri] == ["div", XHTML_URI]
children = [children] unless children.is_a?(Array)
XML::Element.new("div", nil, XHTML_URI,
{"xmlns" => XHTML_URI}, children)
# Returns true if type is "xhtml".
# Raises a MissingTagError or NotExpectedTagError
# if the element is not properly formatted.
def atom_validate(ignore_unknown_element, tags, uri)
raise MissingTagError.new("div", tag_name)
unless [@xhtml.name, @xhtml.uri] == ["div", XHTML_URI]
raise NotExpectedTagError.new(@xhtml.name, @xhtml.uri, tag_name)
target.__send__(self.class.name.split(/::/).last.downcase) {|x| x}
def setup_maker_attributes(target)
target.xml_content = @xhtml
# The PersonConstruct module is used to define a person Atom element that can be
# used to describe a person, corporation or similar entity.
# The PersonConstruct has a Name, Uri and Email child elements.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#atomPersonConstruct
# Adds attributes for name, uri, and email to the +klass+
def self.append_features(klass)
install_have_attribute_element(tag, URI, occurs, nil, :content)
target.__send__("new_#{self.class.name.split(/::/).last.downcase}")
# The name of the person or entity.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.name
class Name < RSS::Element
# The URI of the person or entity.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.uri
# The email of the person or entity.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.email
class Email < RSS::Element
# Element used to describe an Atom date and time in the ISO 8601 format
# * 2013-03-04T10:30:02-05:00
def self.append_features(klass)
# Raises NotAvailableValueError if element content is nil
def atom_validate(ignore_unknown_element, tags, uri)
raise NotAvailableValueError.new(tag_name, "") if content.nil?
module DuplicateLinkChecker
# Checks if there are duplicate links with the same type and hreflang attributes
# that have an alternate (or empty) rel attribute
# Raises a TooMuchTagError if there are duplicates found
def validate_duplicate_links(links)
rel = link.rel || "alternate"
next unless rel == "alternate"
key = [link.hreflang, link.type]
if link_infos.has_key?(key)
raise TooMuchTagError.new("link", tag_name)
# Defines the top-level element of an Atom Feed Document.
# It consists of a number of children Entry elements,
# and has the following attributes:
# * entries (aliased as items)
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.feed
class Feed < RSS::Element
include DuplicateLinkChecker
["author", "*", :children],
["category", "*", :children, "categories"],
["contributor", "*", :children],
["icon", "?", nil, :content],
["id", nil, nil, :content],
["link", "*", :children],
["subtitle", "?", nil, :content],
["title", nil, nil, :content],
["updated", nil, nil, :content],
["entry", "*", :children, "entries"],
].each do |tag, occurs, type, *args|
__send__("install_have_#{type}_element",
tag, URI, occurs, tag, *args)
# Creates a new Atom feed
def initialize(version=nil, encoding=nil, standalone=nil)
super("1.0", version, encoding, standalone)
alias_method :items, :entries
# Returns true if there are any authors for the feed or any of the Entry
# child elements have an author
authors.any? {|author| !author.to_s.empty?} or
entries.any? {|entry| entry.have_author?(false)}
def atom_validate(ignore_unknown_element, tags, uri)
raise MissingTagError.new("author", tag_name)
validate_duplicate_links(links)
def have_required_elements?
def setup_maker_element(channel)
prev_dc_dates = channel.dc_dates.to_a.dup
channel.about = id.content if id
channel.dc_dates.replace(prev_dc_dates)
def setup_maker_elements(channel)
items = channel.maker.items
# PersonConstruct that contains information regarding the author
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.author
class Author < RSS::Element
# Contains information about a category associated with a Feed or Entry.
# It has the following attributes:
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.category
class Category < RSS::Element
["scheme", "", false, [nil, :uri]],
].each do |name, uri, required, type|
install_get_attribute(name, uri, required, type)
# PersonConstruct that contains information regarding the
# contributors of a Feed or Entry.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.contributor
class Contributor < RSS::Element
# Contains information on the agent used to generate the feed.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.generator
class Generator < RSS::Element
["uri", "", false, [nil, :uri]],
].each do |name, uri, required, type|
install_get_attribute(name, uri, required, type)
def setup_maker_attributes(target)
target.generator do |generator|
generator.uri = uri if uri
generator.version = version if version
# Defines an image that provides a visual identification for a eed.
# The image should have an aspect ratio of 1:1.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.icon
class Icon < RSS::Element
# Defines the Universally Unique Identifier (UUID) for a Feed or Entry.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.id
# Defines a reference to a Web resource. It has the following
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.link
class Link < RSS::Element
["href", "", true, [nil, :uri]],
].each do |name, uri, required, type|
install_get_attribute(name, uri, required, type)
# Defines an image that provides a visual identification for the Feed.
# The image should have an aspect ratio of 2:1 (horizontal:vertical).
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.logo
class Logo < RSS::Element
def setup_maker_element_writer
# TextConstruct that contains copyright information regarding
# the content in an Entry or Feed. It should not be used to
# convey machine readable licensing information.
# Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.rights
class Rights < RSS::Element