diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/rss/atom.rb |
Fresh start
Diffstat (limited to 'jni/ruby/lib/rss/atom.rb')
-rw-r--r-- | jni/ruby/lib/rss/atom.rb | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/jni/ruby/lib/rss/atom.rb b/jni/ruby/lib/rss/atom.rb new file mode 100644 index 0000000..d352423 --- /dev/null +++ b/jni/ruby/lib/rss/atom.rb @@ -0,0 +1,839 @@ +require 'rss/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 + def required_uri + URI + end + + 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 + 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 + + 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 + + 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 + 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 + class Name < RSS::Element + include CommonModel + include ContentModel + end + + # The URI of the person or entity + class Uri < RSS::Element + include CommonModel + include URIContentModel + end + + # The email of the person or entity + 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 + + # Atom feed element + # + # A Feed has several metadata attributes in addition to a number of Entry child elements + 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 + + class Author < RSS::Element + include CommonModel + include PersonConstruct + end + + 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 + + class Contributor < RSS::Element + include CommonModel + include PersonConstruct + end + + 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 + + # Atom Icon element + # + # Image that provides a visual identification for the Feed. Image should have an aspect + # ratio of 1:1 + class Icon < RSS::Element + include CommonModel + include URIContentModel + end + + # Atom ID element + # + # Universally Unique Identifier (UUID) for the Feed + class Id < RSS::Element + include CommonModel + include URIContentModel + end + + # Defines an Atom Link element + # + # A Link has the following attributes: + # * href + # * rel + # * type + # * hreflang + # * title + # * length + 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 + + # Atom Logo element + # + # Image that provides a visual identification for the Feed. Image should have an aspect + # ratio of 2:1 (horizontal:vertical) + 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 + + # Atom Rights element + # + # TextConstruct that contains copyright information regarding the content in an Entry or Feed + class Rights < RSS::Element + include CommonModel + include TextConstruct + end + + # Atom Subtitle element + # + # TextConstruct that conveys a description or subtitle for a Feed + class Subtitle < RSS::Element + include CommonModel + include TextConstruct + end + + # Atom Title element + # + # TextConstruct that conveys a description or title for a feed or Entry + class Title < RSS::Element + include CommonModel + include TextConstruct + end + + # Atom Updated element + # + # DateConstruct indicating the most recent time when an Entry or Feed was modified + # in a way the publisher considers significant + class Updated < RSS::Element + include CommonModel + include DateConstruct + end + + # Defines a child Atom Entry element for an Atom Feed + 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 + + Author = Feed::Author + Category = Feed::Category + + 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") + + attr_writer :xml + def have_xml_content? + inline_xhtml? or inline_other_xml? + end + + 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 + + def xhtml + if inline_xhtml? + xml + else + nil + end + end + + 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 + + def inline_text? + !out_of_line? and [nil, "text", "html"].include?(@type) + end + + def inline_html? + return false if out_of_line? + @type == "html" or mime_split == ["text", "html"] + end + + def inline_xhtml? + !out_of_line? and @type == "xhtml" + end + + 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 + + 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 + + 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 + + def inline_other_base64? + inline_other? and !inline_other_text? and !inline_other_xml? + end + + def out_of_line? + not @src.nil? + end + + 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 + + def need_base64_encode? + inline_other_base64? + end + + private + def empty_content? + out_of_line? or super + end + end + + Contributor = Feed::Contributor + Id = Feed::Id + Link = Feed::Link + + class Published < RSS::Element + include CommonModel + include DateConstruct + end + + Rights = Feed::Rights + + 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 + + def have_author? + !author.to_s.empty? + end + + Author = Feed::Author + Category = Feed::Category + Contributor = Feed::Contributor + Generator = Feed::Generator + Icon = Feed::Icon + Id = Feed::Id + Link = Feed::Link + Logo = Feed::Logo + Rights = Feed::Rights + Subtitle = Feed::Subtitle + Title = Feed::Title + Updated = Feed::Updated + end + + class Summary < RSS::Element + include CommonModel + include TextConstruct + end + + Title = Feed::Title + Updated = Feed::Updated + end + end + + # Defines a top-level Atom Entry element + # + 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 + + Author = Feed::Entry::Author + Category = Feed::Entry::Category + Content = Feed::Entry::Content + Contributor = Feed::Entry::Contributor + Id = Feed::Entry::Id + Link = Feed::Entry::Link + Published = Feed::Entry::Published + Rights = Feed::Entry::Rights + Source = Feed::Entry::Source + Summary = Feed::Entry::Summary + Title = Feed::Entry::Title + 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 |