diff options
Diffstat (limited to 'lib/rss/atom.rb')
-rw-r--r-- | lib/rss/atom.rb | 1025 |
1 files changed, 0 insertions, 1025 deletions
diff --git a/lib/rss/atom.rb b/lib/rss/atom.rb deleted file mode 100644 index 48c27330d0..0000000000 --- a/lib/rss/atom.rb +++ /dev/null @@ -1,1025 +0,0 @@ -# frozen_string_literal: false -require_relative 'parser' - -module RSS - ## - # 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. - module Atom - - ## - # 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" - - module CommonModel - NSPOOL = {} - ELEMENTS = [] - - def self.append_features(klass) - super - klass.install_must_call_validator("atom", URI) - [ - ["lang", :xml], - ["base", :xml], - ].each do |name, uri, required| - klass.install_get_attribute(name, uri, required, [nil, :inherit]) - end - klass.class_eval do - class << self - # Returns the Atom URI W3C Namespace - def required_uri - URI - end - - # Returns true - def need_parent? - true - end - end - end - end - end - - module ContentModel - module ClassMethods - def content_type - @content_type ||= nil - end - end - - class << self - def append_features(klass) - super - klass.extend(ClassMethods) - klass.content_setup(klass.content_type, klass.tag_name) - end - end - - def maker_target(target) - target - end - - private - def setup_maker_element_writer - "#{self.class.name.split(/::/).last.downcase}=" - end - - def setup_maker_element(target) - target.__send__(setup_maker_element_writer, content) - super - end - end - - module URIContentModel - class << self - def append_features(klass) - super - klass.class_eval do - @content_type = [nil, :uri] - include(ContentModel) - end - end - end - end - - # 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 - module TextConstruct - def self.append_features(klass) - super - klass.class_eval do - [ - ["type", ""], - ].each do |name, uri, required| - install_get_attribute(name, uri, required, :text_type) - end - - content_setup - add_need_initialize_variable("xhtml") - - class << self - def xml_getter - "xhtml" - end - - def xml_setter - "xhtml=" - end - end - end - end - - attr_writer :xhtml - - # Returns or builds the XHTML content. - def xhtml - return @xhtml if @xhtml.nil? - if @xhtml.is_a?(XML::Element) and - [@xhtml.name, @xhtml.uri] == ["div", XHTML_URI] - return @xhtml - end - - children = @xhtml - children = [children] unless children.is_a?(Array) - XML::Element.new("div", nil, XHTML_URI, - {"xmlns" => XHTML_URI}, children) - end - - # Returns true if type is "xhtml". - def have_xml_content? - @type == "xhtml" - end - - # Raises a MissingTagError or NotExpectedTagError - # if the element is not properly formatted. - def atom_validate(ignore_unknown_element, tags, uri) - if have_xml_content? - if @xhtml.nil? - raise MissingTagError.new("div", tag_name) - end - unless [@xhtml.name, @xhtml.uri] == ["div", XHTML_URI] - raise NotExpectedTagError.new(@xhtml.name, @xhtml.uri, tag_name) - end - end - end - - private - def maker_target(target) - target.__send__(self.class.name.split(/::/).last.downcase) {|x| x} - end - - def setup_maker_attributes(target) - target.type = type - target.content = content - target.xml_content = @xhtml - end - end - - # 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 - module PersonConstruct - - # Adds attributes for name, uri, and email to the +klass+ - def self.append_features(klass) - super - klass.class_eval do - [ - ["name", nil], - ["uri", "?"], - ["email", "?"], - ].each do |tag, occurs| - install_have_attribute_element(tag, URI, occurs, nil, :content) - end - end - end - - def maker_target(target) - target.__send__("new_#{self.class.name.split(/::/).last.downcase}") - end - - # The name of the person or entity. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.name - class Name < RSS::Element - include CommonModel - include ContentModel - end - - # The URI of the person or entity. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.uri - class Uri < RSS::Element - include CommonModel - include URIContentModel - end - - # The email of the person or entity. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.email - class Email < RSS::Element - include CommonModel - include ContentModel - end - end - - # Element used to describe an Atom date and time in the ISO 8601 format - # - # Examples: - # * 2013-03-04T15:30:02Z - # * 2013-03-04T10:30:02-05:00 - module DateConstruct - def self.append_features(klass) - super - klass.class_eval do - @content_type = :w3cdtf - include(ContentModel) - end - end - - # Raises NotAvailableValueError if element content is nil - def atom_validate(ignore_unknown_element, tags, uri) - raise NotAvailableValueError.new(tag_name, "") if content.nil? - end - end - - 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) - link_infos = {} - links.each do |link| - 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) - end - link_infos[key] = true - end - end - end - - # Defines the top-level element of an Atom Feed Document. - # It consists of a number of children Entry elements, - # and has the following attributes: - # - # * author - # * categories - # * category - # * content - # * contributor - # * entries (aliased as items) - # * entry - # * generator - # * icon - # * id - # * link - # * logo - # * rights - # * subtitle - # * title - # * updated - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.feed - class Feed < RSS::Element - include RootElementMixin - include CommonModel - include DuplicateLinkChecker - - install_ns('', URI) - - [ - ["author", "*", :children], - ["category", "*", :children, "categories"], - ["contributor", "*", :children], - ["generator", "?"], - ["icon", "?", nil, :content], - ["id", nil, nil, :content], - ["link", "*", :children], - ["logo", "?"], - ["rights", "?"], - ["subtitle", "?", nil, :content], - ["title", nil, nil, :content], - ["updated", nil, nil, :content], - ["entry", "*", :children, "entries"], - ].each do |tag, occurs, type, *args| - type ||= :child - __send__("install_have_#{type}_element", - tag, URI, occurs, tag, *args) - end - - # Creates a new Atom feed - def initialize(version=nil, encoding=nil, standalone=nil) - super("1.0", version, encoding, standalone) - @feed_type = "atom" - @feed_subtype = "feed" - end - - alias_method :items, :entries - - # Returns true if there are any authors for the feed or any of the Entry - # child elements have an author - def have_author? - authors.any? {|author| !author.to_s.empty?} or - entries.any? {|entry| entry.have_author?(false)} - end - - private - def atom_validate(ignore_unknown_element, tags, uri) - unless have_author? - raise MissingTagError.new("author", tag_name) - end - validate_duplicate_links(links) - end - - def have_required_elements? - super and have_author? - end - - def maker_target(maker) - maker.channel - end - - def setup_maker_element(channel) - prev_dc_dates = channel.dc_dates.to_a.dup - super - channel.about = id.content if id - channel.dc_dates.replace(prev_dc_dates) - end - - def setup_maker_elements(channel) - super - items = channel.maker.items - entries.each do |entry| - entry.setup_maker(items) - end - end - - # PersonConstruct that contains information regarding the author - # of a Feed or Entry. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.author - class Author < RSS::Element - include CommonModel - include PersonConstruct - end - - # Contains information about a category associated with a Feed or Entry. - # It has the following attributes: - # - # * term - # * scheme - # * label - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.category - class Category < RSS::Element - include CommonModel - - [ - ["term", "", true], - ["scheme", "", false, [nil, :uri]], - ["label", ""], - ].each do |name, uri, required, type| - install_get_attribute(name, uri, required, type) - end - - private - def maker_target(target) - target.new_category - end - end - - # 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 - include CommonModel - include PersonConstruct - end - - # 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 - include CommonModel - include ContentModel - - [ - ["uri", "", false, [nil, :uri]], - ["version", ""], - ].each do |name, uri, required, type| - install_get_attribute(name, uri, required, type) - end - - private - def setup_maker_attributes(target) - target.generator do |generator| - generator.uri = uri if uri - generator.version = version if version - end - end - end - - # 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 - include CommonModel - include URIContentModel - end - - # Defines the Universally Unique Identifier (UUID) for a Feed or Entry. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.id - class Id < RSS::Element - include CommonModel - include URIContentModel - end - - # Defines a reference to a Web resource. It has the following - # attributes: - # - # * href - # * rel - # * type - # * hreflang - # * title - # * length - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.link - class Link < RSS::Element - include CommonModel - - [ - ["href", "", true, [nil, :uri]], - ["rel", ""], - ["type", ""], - ["hreflang", ""], - ["title", ""], - ["length", ""], - ].each do |name, uri, required, type| - install_get_attribute(name, uri, required, type) - end - - private - def maker_target(target) - target.new_link - end - end - - # 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 - include CommonModel - include URIContentModel - - def maker_target(target) - target.maker.image - end - - private - def setup_maker_element_writer - "url=" - end - end - - # 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 - include CommonModel - include TextConstruct - end - - # TextConstruct that conveys a description or subtitle for a Feed. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.subtitle - class Subtitle < RSS::Element - include CommonModel - include TextConstruct - end - - # TextConstruct that conveys a description or title for a Feed or Entry. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.title - class Title < RSS::Element - include CommonModel - include TextConstruct - end - - # DateConstruct indicating the most recent time when a Feed or - # Entry was modified in a way the publisher considers - # significant. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.updated - class Updated < RSS::Element - include CommonModel - include DateConstruct - end - - # Defines a child Atom Entry element of an Atom Feed element. - # It has the following attributes: - # - # * author - # * category - # * categories - # * content - # * contributor - # * id - # * link - # * published - # * rights - # * source - # * summary - # * title - # * updated - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.entry - class Entry < RSS::Element - include CommonModel - include DuplicateLinkChecker - - [ - ["author", "*", :children], - ["category", "*", :children, "categories"], - ["content", "?", :child], - ["contributor", "*", :children], - ["id", nil, nil, :content], - ["link", "*", :children], - ["published", "?", :child, :content], - ["rights", "?", :child], - ["source", "?"], - ["summary", "?", :child], - ["title", nil], - ["updated", nil, :child, :content], - ].each do |tag, occurs, type, *args| - type ||= :attribute - __send__("install_have_#{type}_element", - tag, URI, occurs, tag, *args) - end - - # Returns whether any of the following are true: - # - # * There are any authors in the feed - # * If the parent element has an author and the +check_parent+ - # parameter was given. - # * There is a source element that has an author - def have_author?(check_parent=true) - authors.any? {|author| !author.to_s.empty?} or - (check_parent and @parent and @parent.have_author?) or - (source and source.have_author?) - end - - private - def atom_validate(ignore_unknown_element, tags, uri) - unless have_author? - raise MissingTagError.new("author", tag_name) - end - validate_duplicate_links(links) - end - - def have_required_elements? - super and have_author? - end - - def maker_target(items) - if items.respond_to?("items") - # For backward compatibility - items = items.items - end - items.new_item - end - - # Feed::Author - Author = Feed::Author - # Feed::Category - Category = Feed::Category - - # Contains or links to the content of the Entry. - # It has the following attributes: - # - # * type - # * src - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.content - class Content < RSS::Element - include CommonModel - - class << self - def xml_setter - "xml=" - end - - def xml_getter - "xml" - end - end - - [ - ["type", ""], - ["src", "", false, [nil, :uri]], - ].each do |name, uri, required, type| - install_get_attribute(name, uri, required, type) - end - - content_setup - add_need_initialize_variable("xml") - - # Returns the element content in XML. - attr_writer :xml - - # Returns true if the element has inline XML content. - def have_xml_content? - inline_xhtml? or inline_other_xml? - end - - # Returns or builds the element content in XML. - def xml - return @xml unless inline_xhtml? - return @xml if @xml.nil? - if @xml.is_a?(XML::Element) and - [@xml.name, @xml.uri] == ["div", XHTML_URI] - return @xml - end - - children = @xml - children = [children] unless children.is_a?(Array) - XML::Element.new("div", nil, XHTML_URI, - {"xmlns" => XHTML_URI}, children) - end - - # Returns the element content in XHTML. - def xhtml - if inline_xhtml? - xml - else - nil - end - end - - # Raises a MissingAttributeError, NotAvailableValueError, - # MissingTagError or NotExpectedTagError if the element is - # not properly formatted. - def atom_validate(ignore_unknown_element, tags, uri) - if out_of_line? - raise MissingAttributeError.new(tag_name, "type") if @type.nil? - unless (content.nil? or content.empty?) - raise NotAvailableValueError.new(tag_name, content) - end - elsif inline_xhtml? - if @xml.nil? - raise MissingTagError.new("div", tag_name) - end - unless @xml.name == "div" and @xml.uri == XHTML_URI - raise NotExpectedTagError.new(@xml.name, @xml.uri, tag_name) - end - end - end - - # Returns true if the element contains inline content - # that has a text or HTML media type, or no media type at all. - def inline_text? - !out_of_line? and [nil, "text", "html"].include?(@type) - end - - # Returns true if the element contains inline content that - # has a HTML media type. - def inline_html? - return false if out_of_line? - @type == "html" or mime_split == ["text", "html"] - end - - # Returns true if the element contains inline content that - # has a XHTML media type. - def inline_xhtml? - !out_of_line? and @type == "xhtml" - end - - # Returns true if the element contains inline content that - # has a MIME media type. - def inline_other? - return false if out_of_line? - media_type, subtype = mime_split - return false if media_type.nil? or subtype.nil? - true - end - - # Returns true if the element contains inline content that - # has a text media type. - def inline_other_text? - return false unless inline_other? - return false if inline_other_xml? - - media_type, = mime_split - return true if "text" == media_type.downcase - false - end - - # Returns true if the element contains inline content that - # has a XML media type. - def inline_other_xml? - return false unless inline_other? - - media_type, subtype = mime_split - normalized_mime_type = "#{media_type}/#{subtype}".downcase - if /(?:\+xml|^xml)$/ =~ subtype or - %w(text/xml-external-parsed-entity - application/xml-external-parsed-entity - application/xml-dtd).find {|x| x == normalized_mime_type} - return true - end - false - end - - # Returns true if the element contains inline content - # encoded in base64. - def inline_other_base64? - inline_other? and !inline_other_text? and !inline_other_xml? - end - - # Returns true if the element contains linked content. - def out_of_line? - not @src.nil? - end - - # Splits the type attribute into an array, e.g. ["text", "xml"] - def mime_split - media_type = subtype = nil - if /\A\s*([a-z]+)\/([a-z\+]+)\s*(?:;.*)?\z/i =~ @type.to_s - media_type = $1.downcase - subtype = $2.downcase - end - [media_type, subtype] - end - - # Returns true if the content needs to be encoded in base64. - def need_base64_encode? - inline_other_base64? - end - - private - def empty_content? - out_of_line? or super - end - end - - # Feed::Contributor - Contributor = Feed::Contributor - # Feed::Id - Id = Feed::Id - # Feed::Link - Link = Feed::Link - - # DateConstruct that usually indicates the time of the initial - # creation of an Entry. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.published - class Published < RSS::Element - include CommonModel - include DateConstruct - end - - # Feed::Rights - Rights = Feed::Rights - - # Defines a Atom Source element. It has the following attributes: - # - # * author - # * category - # * categories - # * content - # * contributor - # * generator - # * icon - # * id - # * link - # * logo - # * rights - # * subtitle - # * title - # * updated - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.source - class Source < RSS::Element - include CommonModel - - [ - ["author", "*", :children], - ["category", "*", :children, "categories"], - ["contributor", "*", :children], - ["generator", "?"], - ["icon", "?"], - ["id", "?", nil, :content], - ["link", "*", :children], - ["logo", "?"], - ["rights", "?"], - ["subtitle", "?"], - ["title", "?"], - ["updated", "?", nil, :content], - ].each do |tag, occurs, type, *args| - type ||= :attribute - __send__("install_have_#{type}_element", - tag, URI, occurs, tag, *args) - end - - # Returns true if the Source element has an author. - def have_author? - !author.to_s.empty? - end - - # Feed::Author - Author = Feed::Author - # Feed::Category - Category = Feed::Category - # Feed::Contributor - Contributor = Feed::Contributor - # Feed::Generator - Generator = Feed::Generator - # Feed::Icon - Icon = Feed::Icon - # Feed::Id - Id = Feed::Id - # Feed::Link - Link = Feed::Link - # Feed::Logo - Logo = Feed::Logo - # Feed::Rights - Rights = Feed::Rights - # Feed::Subtitle - Subtitle = Feed::Subtitle - # Feed::Title - Title = Feed::Title - # Feed::Updated - Updated = Feed::Updated - end - - # TextConstruct that describes a summary of the Entry. - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.summary - class Summary < RSS::Element - include CommonModel - include TextConstruct - end - - # Feed::Title - Title = Feed::Title - # Feed::Updated - Updated = Feed::Updated - end - end - - # Defines a top-level Atom Entry element, - # used as the document element of a stand-alone Atom Entry Document. - # It has the following attributes: - # - # * author - # * category - # * categories - # * content - # * contributor - # * id - # * link - # * published - # * rights - # * source - # * summary - # * title - # * updated - # - # Reference: https://validator.w3.org/feed/docs/rfc4287.html#element.entry] - class Entry < RSS::Element - include RootElementMixin - include CommonModel - include DuplicateLinkChecker - - [ - ["author", "*", :children], - ["category", "*", :children, "categories"], - ["content", "?"], - ["contributor", "*", :children], - ["id", nil, nil, :content], - ["link", "*", :children], - ["published", "?", :child, :content], - ["rights", "?"], - ["source", "?"], - ["summary", "?"], - ["title", nil], - ["updated", nil, nil, :content], - ].each do |tag, occurs, type, *args| - type ||= :attribute - __send__("install_have_#{type}_element", - tag, URI, occurs, tag, *args) - end - - # Creates a new Atom Entry element. - def initialize(version=nil, encoding=nil, standalone=nil) - super("1.0", version, encoding, standalone) - @feed_type = "atom" - @feed_subtype = "entry" - end - - # Returns the Entry in an array. - def items - [self] - end - - # Sets up the +maker+ for constructing Entry elements. - def setup_maker(maker) - maker = maker.maker if maker.respond_to?("maker") - super(maker) - end - - # Returns where there are any authors present or there is a - # source with an author. - def have_author? - authors.any? {|author| !author.to_s.empty?} or - (source and source.have_author?) - end - - private - def atom_validate(ignore_unknown_element, tags, uri) - unless have_author? - raise MissingTagError.new("author", tag_name) - end - validate_duplicate_links(links) - end - - def have_required_elements? - super and have_author? - end - - def maker_target(maker) - maker.items.new_item - end - - # Feed::Entry::Author - Author = Feed::Entry::Author - # Feed::Entry::Category - Category = Feed::Entry::Category - # Feed::Entry::Content - Content = Feed::Entry::Content - # Feed::Entry::Contributor - Contributor = Feed::Entry::Contributor - # Feed::Entry::Id - Id = Feed::Entry::Id - # Feed::Entry::Link - Link = Feed::Entry::Link - # Feed::Entry::Published - Published = Feed::Entry::Published - # Feed::Entry::Rights - Rights = Feed::Entry::Rights - # Feed::Entry::Source - Source = Feed::Entry::Source - # Feed::Entry::Summary - Summary = Feed::Entry::Summary - # Feed::Entry::Title - Title = Feed::Entry::Title - # Feed::Entry::Updated - Updated = Feed::Entry::Updated - end - end - - Atom::CommonModel::ELEMENTS.each do |name| - BaseListener.install_get_text_element(Atom::URI, name, "#{name}=") - end - - module ListenerMixin - private - def initial_start_feed(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, Atom::URI, false) - - @rss = Atom::Feed.new(@version, @encoding, @standalone) - @rss.do_validate = @do_validate - @rss.xml_stylesheets = @xml_stylesheets - @rss.lang = attrs["xml:lang"] - @rss.base = attrs["xml:base"] - @last_element = @rss - pr = Proc.new do |text, tags| - @rss.validate_for_stream(tags) if @do_validate - end - @proc_stack.push(pr) - end - - def initial_start_entry(tag_name, prefix, attrs, ns) - check_ns(tag_name, prefix, ns, Atom::URI, false) - - @rss = Atom::Entry.new(@version, @encoding, @standalone) - @rss.do_validate = @do_validate - @rss.xml_stylesheets = @xml_stylesheets - @rss.lang = attrs["xml:lang"] - @rss.base = attrs["xml:base"] - @last_element = @rss - pr = Proc.new do |text, tags| - @rss.validate_for_stream(tags) if @do_validate - end - @proc_stack.push(pr) - end - end -end |