summaryrefslogtreecommitdiff
path: root/jni/ruby/lib/rss/atom.rb
diff options
context:
space:
mode:
authorJari Vetoniemi <jari.vetoniemi@indooratlas.com>2020-03-16 18:49:26 +0900
committerJari Vetoniemi <jari.vetoniemi@indooratlas.com>2020-03-30 00:39:06 +0900
commitfcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch)
tree64cb17de3f41a2b6fef2368028fbd00349946994 /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.rb839
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