1require 'forwardable' 2 3require 'rss/rss' 4 5module RSS 6 module Maker 7 class Base 8 extend Utils::InheritedReader 9 10 OTHER_ELEMENTS = [] 11 NEED_INITIALIZE_VARIABLES = [] 12 13 class << self 14 def other_elements 15 inherited_array_reader("OTHER_ELEMENTS") 16 end 17 def need_initialize_variables 18 inherited_array_reader("NEED_INITIALIZE_VARIABLES") 19 end 20 21 def inherited_base 22 ::RSS::Maker::Base 23 end 24 25 def inherited(subclass) 26 subclass.const_set(:OTHER_ELEMENTS, []) 27 subclass.const_set(:NEED_INITIALIZE_VARIABLES, []) 28 end 29 30 def add_other_element(variable_name) 31 self::OTHER_ELEMENTS << variable_name 32 end 33 34 def add_need_initialize_variable(variable_name, init_value=nil, 35 &init_block) 36 init_value ||= init_block 37 self::NEED_INITIALIZE_VARIABLES << [variable_name, init_value] 38 end 39 40 def def_array_element(name, plural=nil, klass_name=nil) 41 include Enumerable 42 extend Forwardable 43 44 plural ||= "#{name}s" 45 klass_name ||= Utils.to_class_name(name) 46 def_delegators("@#{plural}", :<<, :[], :[]=, :first, :last) 47 def_delegators("@#{plural}", :push, :pop, :shift, :unshift) 48 def_delegators("@#{plural}", :each, :size, :empty?, :clear) 49 50 add_need_initialize_variable(plural) {[]} 51 52 module_eval(<<-EOC, __FILE__, __LINE__ + 1) 53 def new_#{name} 54 #{name} = self.class::#{klass_name}.new(@maker) 55 @#{plural} << #{name} 56 if block_given? 57 yield #{name} 58 else 59 #{name} 60 end 61 end 62 alias new_child new_#{name} 63 64 def to_feed(*args) 65 @#{plural}.each do |#{name}| 66 #{name}.to_feed(*args) 67 end 68 end 69 70 def replace(elements) 71 @#{plural}.replace(elements.to_a) 72 end 73 EOC 74 end 75 76 def def_classed_element_without_accessor(name, class_name=nil) 77 class_name ||= Utils.to_class_name(name) 78 add_other_element(name) 79 add_need_initialize_variable(name) do |object| 80 object.send("make_#{name}") 81 end 82 module_eval(<<-EOC, __FILE__, __LINE__ + 1) 83 private 84 def setup_#{name}(feed, current) 85 @#{name}.to_feed(feed, current) 86 end 87 88 def make_#{name} 89 self.class::#{class_name}.new(@maker) 90 end 91 EOC 92 end 93 94 def def_classed_element(name, class_name=nil, attribute_name=nil) 95 def_classed_element_without_accessor(name, class_name) 96 if attribute_name 97 module_eval(<<-EOC, __FILE__, __LINE__ + 1) 98 def #{name} 99 if block_given? 100 yield(@#{name}) 101 else 102 @#{name}.#{attribute_name} 103 end 104 end 105 106 def #{name}=(new_value) 107 @#{name}.#{attribute_name} = new_value 108 end 109 EOC 110 else 111 attr_reader name 112 end 113 end 114 115 def def_classed_elements(name, attribute, plural_class_name=nil, 116 plural_name=nil, new_name=nil) 117 plural_name ||= "#{name}s" 118 new_name ||= name 119 def_classed_element(plural_name, plural_class_name) 120 local_variable_name = "_#{name}" 121 new_value_variable_name = "new_value" 122 additional_setup_code = nil 123 if block_given? 124 additional_setup_code = yield(local_variable_name, 125 new_value_variable_name) 126 end 127 module_eval(<<-EOC, __FILE__, __LINE__ + 1) 128 def #{name} 129 #{local_variable_name} = #{plural_name}.first 130 #{local_variable_name} ? #{local_variable_name}.#{attribute} : nil 131 end 132 133 def #{name}=(#{new_value_variable_name}) 134 #{local_variable_name} = 135 #{plural_name}.first || #{plural_name}.new_#{new_name} 136 #{additional_setup_code} 137 #{local_variable_name}.#{attribute} = #{new_value_variable_name} 138 end 139 EOC 140 end 141 142 def def_other_element(name) 143 attr_accessor name 144 def_other_element_without_accessor(name) 145 end 146 147 def def_other_element_without_accessor(name) 148 add_need_initialize_variable(name) 149 add_other_element(name) 150 module_eval(<<-EOC, __FILE__, __LINE__ + 1) 151 def setup_#{name}(feed, current) 152 if !@#{name}.nil? and current.respond_to?(:#{name}=) 153 current.#{name} = @#{name} 154 end 155 end 156 EOC 157 end 158 159 def def_csv_element(name, type=nil) 160 def_other_element_without_accessor(name) 161 attr_reader(name) 162 converter = "" 163 if type == :integer 164 converter = "{|v| Integer(v)}" 165 end 166 module_eval(<<-EOC, __FILE__, __LINE__ + 1) 167 def #{name}=(value) 168 @#{name} = Utils::CSV.parse(value)#{converter} 169 end 170 EOC 171 end 172 end 173 174 attr_reader :maker 175 def initialize(maker) 176 @maker = maker 177 @default_values_are_set = false 178 initialize_variables 179 end 180 181 def have_required_values? 182 not_set_required_variables.empty? 183 end 184 185 def variable_is_set? 186 variables.any? {|var| not __send__(var).nil?} 187 end 188 189 private 190 def initialize_variables 191 self.class.need_initialize_variables.each do |variable_name, init_value| 192 if init_value.nil? 193 value = nil 194 else 195 if init_value.respond_to?(:call) 196 value = init_value.call(self) 197 elsif init_value.is_a?(String) 198 # just for backward compatibility 199 value = instance_eval(init_value, __FILE__, __LINE__) 200 else 201 value = init_value 202 end 203 end 204 instance_variable_set("@#{variable_name}", value) 205 end 206 end 207 208 def setup_other_elements(feed, current=nil) 209 current ||= current_element(feed) 210 self.class.other_elements.each do |element| 211 __send__("setup_#{element}", feed, current) 212 end 213 end 214 215 def current_element(feed) 216 feed 217 end 218 219 def set_default_values(&block) 220 return yield if @default_values_are_set 221 222 begin 223 @default_values_are_set = true 224 _set_default_values(&block) 225 ensure 226 @default_values_are_set = false 227 end 228 end 229 230 def _set_default_values(&block) 231 yield 232 end 233 234 def setup_values(target) 235 set = false 236 if have_required_values? 237 variables.each do |var| 238 setter = "#{var}=" 239 if target.respond_to?(setter) 240 value = __send__(var) 241 unless value.nil? 242 target.__send__(setter, value) 243 set = true 244 end 245 end 246 end 247 end 248 set 249 end 250 251 def set_parent(target, parent) 252 target.parent = parent if target.class.need_parent? 253 end 254 255 def variables 256 self.class.need_initialize_variables.find_all do |name, init| 257 # init == "nil" is just for backward compatibility 258 init.nil? or init == "nil" 259 end.collect do |name, init| 260 name 261 end 262 end 263 264 def not_set_required_variables 265 required_variable_names.find_all do |var| 266 __send__(var).nil? 267 end 268 end 269 270 def required_variables_are_set? 271 required_variable_names.each do |var| 272 return false if __send__(var).nil? 273 end 274 true 275 end 276 end 277 278 module AtomPersonConstructBase 279 def self.append_features(klass) 280 super 281 282 klass.class_eval(<<-EOC, __FILE__, __LINE__ + 1) 283 %w(name uri email).each do |element| 284 attr_accessor element 285 add_need_initialize_variable(element) 286 end 287 EOC 288 end 289 end 290 291 module AtomTextConstructBase 292 module EnsureXMLContent 293 class << self 294 def included(base) 295 super 296 base.class_eval do 297 %w(type content xml_content).each do |element| 298 attr_reader element 299 attr_writer element if element != "xml_content" 300 add_need_initialize_variable(element) 301 end 302 303 alias_method(:xhtml, :xml_content) 304 end 305 end 306 end 307 308 def ensure_xml_content(content) 309 xhtml_uri = ::RSS::Atom::XHTML_URI 310 unless content.is_a?(RSS::XML::Element) and 311 ["div", xhtml_uri] == [content.name, content.uri] 312 children = content 313 children = [children] unless content.is_a?(Array) 314 children = set_xhtml_uri_as_default_uri(children) 315 content = RSS::XML::Element.new("div", nil, xhtml_uri, 316 {"xmlns" => xhtml_uri}, 317 children) 318 end 319 content 320 end 321 322 def xml_content=(content) 323 @xml_content = ensure_xml_content(content) 324 end 325 326 def xhtml=(content) 327 self.xml_content = content 328 end 329 330 private 331 def set_xhtml_uri_as_default_uri(children) 332 children.collect do |child| 333 if child.is_a?(RSS::XML::Element) and 334 child.prefix.nil? and child.uri.nil? 335 RSS::XML::Element.new(child.name, nil, ::RSS::Atom::XHTML_URI, 336 child.attributes.dup, 337 set_xhtml_uri_as_default_uri(child.children)) 338 else 339 child 340 end 341 end 342 end 343 end 344 345 def self.append_features(klass) 346 super 347 348 klass.class_eval do 349 include EnsureXMLContent 350 end 351 end 352 end 353 354 module SetupDefaultDate 355 private 356 def _set_default_values 357 keep = { 358 :date => date, 359 :dc_dates => dc_dates.to_a.dup, 360 } 361 _date = _parse_date_if_needed(date) 362 if _date and !dc_dates.any? {|dc_date| dc_date.value == _date} 363 dc_date = self.class::DublinCoreDates::DublinCoreDate.new(self) 364 dc_date.value = _date.dup 365 dc_dates.unshift(dc_date) 366 end 367 self.date ||= self.dc_date 368 super 369 ensure 370 date = keep[:date] 371 dc_dates.replace(keep[:dc_dates]) 372 end 373 374 def _parse_date_if_needed(date_value) 375 date_value = Time.parse(date_value) if date_value.is_a?(String) 376 date_value 377 end 378 end 379 380 module SetupDefaultLanguage 381 private 382 def _set_default_values 383 keep = { 384 :dc_languages => dc_languages.to_a.dup, 385 } 386 _language = language 387 if _language and 388 !dc_languages.any? {|dc_language| dc_language.value == _language} 389 dc_language = self.class::DublinCoreLanguages::DublinCoreLanguage.new(self) 390 dc_language.value = _language.dup 391 dc_languages.unshift(dc_language) 392 end 393 super 394 ensure 395 dc_languages.replace(keep[:dc_languages]) 396 end 397 end 398 399 class RSSBase < Base 400 class << self 401 def make(*args, &block) 402 new(*args).make(&block) 403 end 404 end 405 406 %w(xml_stylesheets channel image items textinput).each do |element| 407 attr_reader element 408 add_need_initialize_variable(element) do |object| 409 object.send("make_#{element}") 410 end 411 module_eval(<<-EOC, __FILE__, __LINE__ + 1) 412 private 413 def setup_#{element}(feed) 414 @#{element}.to_feed(feed) 415 end 416 417 def make_#{element} 418 self.class::#{Utils.to_class_name(element)}.new(self) 419 end 420 EOC 421 end 422 423 attr_reader :feed_version 424 alias_method(:rss_version, :feed_version) 425 attr_accessor :version, :encoding, :standalone 426 427 def initialize(feed_version) 428 super(self) 429 @feed_type = nil 430 @feed_subtype = nil 431 @feed_version = feed_version 432 @version = "1.0" 433 @encoding = "UTF-8" 434 @standalone = nil 435 end 436 437 def make 438 yield(self) 439 to_feed 440 end 441 442 def to_feed 443 feed = make_feed 444 setup_xml_stylesheets(feed) 445 setup_elements(feed) 446 setup_other_elements(feed) 447 feed.validate 448 feed 449 end 450 451 private 452 remove_method :make_xml_stylesheets 453 def make_xml_stylesheets 454 XMLStyleSheets.new(self) 455 end 456 end 457 458 class XMLStyleSheets < Base 459 def_array_element("xml_stylesheet", nil, "XMLStyleSheet") 460 461 class XMLStyleSheet < Base 462 463 ::RSS::XMLStyleSheet::ATTRIBUTES.each do |attribute| 464 attr_accessor attribute 465 add_need_initialize_variable(attribute) 466 end 467 468 def to_feed(feed) 469 xss = ::RSS::XMLStyleSheet.new 470 guess_type_if_need(xss) 471 set = setup_values(xss) 472 if set 473 feed.xml_stylesheets << xss 474 end 475 end 476 477 private 478 def guess_type_if_need(xss) 479 if @type.nil? 480 xss.href = @href 481 @type = xss.type 482 end 483 end 484 485 def required_variable_names 486 %w(href type) 487 end 488 end 489 end 490 491 class ChannelBase < Base 492 include SetupDefaultDate 493 494 %w(cloud categories skipDays skipHours).each do |name| 495 def_classed_element(name) 496 end 497 498 %w(generator copyright description title).each do |name| 499 def_classed_element(name, nil, "content") 500 end 501 502 [ 503 ["link", "href", Proc.new {|target,| "#{target}.href = 'self'"}], 504 ["author", "name"], 505 ["contributor", "name"], 506 ].each do |name, attribute, additional_setup_maker| 507 def_classed_elements(name, attribute, &additional_setup_maker) 508 end 509 510 %w(id about language 511 managingEditor webMaster rating docs ttl).each do |element| 512 attr_accessor element 513 add_need_initialize_variable(element) 514 end 515 516 %w(date lastBuildDate).each do |date_element| 517 attr_reader date_element 518 add_need_initialize_variable(date_element) 519 end 520 521 def date=(_date) 522 @date = _parse_date_if_needed(_date) 523 end 524 525 def lastBuildDate=(_date) 526 @lastBuildDate = _parse_date_if_needed(_date) 527 end 528 529 def pubDate 530 date 531 end 532 533 def pubDate=(date) 534 self.date = date 535 end 536 537 def updated 538 date 539 end 540 541 def updated=(date) 542 self.date = date 543 end 544 545 alias_method(:rights, :copyright) 546 alias_method(:rights=, :copyright=) 547 548 alias_method(:subtitle, :description) 549 alias_method(:subtitle=, :description=) 550 551 def icon 552 image_favicon.about 553 end 554 555 def icon=(url) 556 image_favicon.about = url 557 end 558 559 def logo 560 maker.image.url 561 end 562 563 def logo=(url) 564 maker.image.url = url 565 end 566 567 class SkipDaysBase < Base 568 def_array_element("day") 569 570 class DayBase < Base 571 %w(content).each do |element| 572 attr_accessor element 573 add_need_initialize_variable(element) 574 end 575 end 576 end 577 578 class SkipHoursBase < Base 579 def_array_element("hour") 580 581 class HourBase < Base 582 %w(content).each do |element| 583 attr_accessor element 584 add_need_initialize_variable(element) 585 end 586 end 587 end 588 589 class CloudBase < Base 590 %w(domain port path registerProcedure protocol).each do |element| 591 attr_accessor element 592 add_need_initialize_variable(element) 593 end 594 end 595 596 class CategoriesBase < Base 597 def_array_element("category", "categories") 598 599 class CategoryBase < Base 600 %w(domain content label).each do |element| 601 attr_accessor element 602 add_need_initialize_variable(element) 603 end 604 605 alias_method(:term, :domain) 606 alias_method(:term=, :domain=) 607 alias_method(:scheme, :content) 608 alias_method(:scheme=, :content=) 609 end 610 end 611 612 class LinksBase < Base 613 def_array_element("link") 614 615 class LinkBase < Base 616 %w(href rel type hreflang title length).each do |element| 617 attr_accessor element 618 add_need_initialize_variable(element) 619 end 620 end 621 end 622 623 class AuthorsBase < Base 624 def_array_element("author") 625 626 class AuthorBase < Base 627 include AtomPersonConstructBase 628 end 629 end 630 631 class ContributorsBase < Base 632 def_array_element("contributor") 633 634 class ContributorBase < Base 635 include AtomPersonConstructBase 636 end 637 end 638 639 class GeneratorBase < Base 640 %w(uri version content).each do |element| 641 attr_accessor element 642 add_need_initialize_variable(element) 643 end 644 end 645 646 class CopyrightBase < Base 647 include AtomTextConstructBase 648 end 649 650 class DescriptionBase < Base 651 include AtomTextConstructBase 652 end 653 654 class TitleBase < Base 655 include AtomTextConstructBase 656 end 657 end 658 659 class ImageBase < Base 660 %w(title url width height description).each do |element| 661 attr_accessor element 662 add_need_initialize_variable(element) 663 end 664 665 def link 666 @maker.channel.link 667 end 668 end 669 670 class ItemsBase < Base 671 def_array_element("item") 672 673 attr_accessor :do_sort, :max_size 674 675 def initialize(maker) 676 super 677 @do_sort = false 678 @max_size = -1 679 end 680 681 def normalize 682 if @max_size >= 0 683 sort_if_need[0...@max_size] 684 else 685 sort_if_need[0..@max_size] 686 end 687 end 688 689 private 690 def sort_if_need 691 if @do_sort.respond_to?(:call) 692 @items.sort do |x, y| 693 @do_sort.call(x, y) 694 end 695 elsif @do_sort 696 @items.sort do |x, y| 697 y <=> x 698 end 699 else 700 @items 701 end 702 end 703 704 class ItemBase < Base 705 include SetupDefaultDate 706 707 %w(guid enclosure source categories content).each do |name| 708 def_classed_element(name) 709 end 710 711 %w(rights description title).each do |name| 712 def_classed_element(name, nil, "content") 713 end 714 715 [ 716 ["author", "name"], 717 ["link", "href", Proc.new {|target,| "#{target}.href = 'alternate'"}], 718 ["contributor", "name"], 719 ].each do |name, attribute| 720 def_classed_elements(name, attribute) 721 end 722 723 %w(comments id published).each do |element| 724 attr_accessor element 725 add_need_initialize_variable(element) 726 end 727 728 %w(date).each do |date_element| 729 attr_reader date_element 730 add_need_initialize_variable(date_element) 731 end 732 733 def date=(_date) 734 @date = _parse_date_if_needed(_date) 735 end 736 737 def pubDate 738 date 739 end 740 741 def pubDate=(date) 742 self.date = date 743 end 744 745 def updated 746 date 747 end 748 749 def updated=(date) 750 self.date = date 751 end 752 753 alias_method(:summary, :description) 754 alias_method(:summary=, :description=) 755 756 def <=>(other) 757 _date = date || dc_date 758 _other_date = other.date || other.dc_date 759 if _date and _other_date 760 _date <=> _other_date 761 elsif _date 762 1 763 elsif _other_date 764 -1 765 else 766 0 767 end 768 end 769 770 class GuidBase < Base 771 %w(isPermaLink content).each do |element| 772 attr_accessor element 773 add_need_initialize_variable(element) 774 end 775 776 def permanent_link? 777 isPermaLink 778 end 779 780 def permanent_link=(bool) 781 self.isPermaLink = bool 782 end 783 end 784 785 class EnclosureBase < Base 786 %w(url length type).each do |element| 787 attr_accessor element 788 add_need_initialize_variable(element) 789 end 790 end 791 792 class SourceBase < Base 793 include SetupDefaultDate 794 795 %w(authors categories contributors generator icon 796 logo rights subtitle title).each do |name| 797 def_classed_element(name) 798 end 799 800 [ 801 ["link", "href"], 802 ].each do |name, attribute| 803 def_classed_elements(name, attribute) 804 end 805 806 %w(id content).each do |element| 807 attr_accessor element 808 add_need_initialize_variable(element) 809 end 810 811 alias_method(:url, :link) 812 alias_method(:url=, :link=) 813 814 %w(date).each do |date_element| 815 attr_reader date_element 816 add_need_initialize_variable(date_element) 817 end 818 819 def date=(_date) 820 @date = _parse_date_if_needed(_date) 821 end 822 823 def updated 824 date 825 end 826 827 def updated=(date) 828 self.date = date 829 end 830 831 private 832 AuthorsBase = ChannelBase::AuthorsBase 833 CategoriesBase = ChannelBase::CategoriesBase 834 ContributorsBase = ChannelBase::ContributorsBase 835 GeneratorBase = ChannelBase::GeneratorBase 836 837 class IconBase < Base 838 %w(url).each do |element| 839 attr_accessor element 840 add_need_initialize_variable(element) 841 end 842 end 843 844 LinksBase = ChannelBase::LinksBase 845 846 class LogoBase < Base 847 %w(uri).each do |element| 848 attr_accessor element 849 add_need_initialize_variable(element) 850 end 851 end 852 853 class RightsBase < Base 854 include AtomTextConstructBase 855 end 856 857 class SubtitleBase < Base 858 include AtomTextConstructBase 859 end 860 861 class TitleBase < Base 862 include AtomTextConstructBase 863 end 864 end 865 866 CategoriesBase = ChannelBase::CategoriesBase 867 AuthorsBase = ChannelBase::AuthorsBase 868 LinksBase = ChannelBase::LinksBase 869 ContributorsBase = ChannelBase::ContributorsBase 870 871 class RightsBase < Base 872 include AtomTextConstructBase 873 end 874 875 class DescriptionBase < Base 876 include AtomTextConstructBase 877 end 878 879 class ContentBase < Base 880 include AtomTextConstructBase::EnsureXMLContent 881 882 %w(src).each do |element| 883 attr_accessor(element) 884 add_need_initialize_variable(element) 885 end 886 887 def xml_content=(content) 888 content = ensure_xml_content(content) if inline_xhtml? 889 @xml_content = content 890 end 891 892 alias_method(:xml, :xml_content) 893 alias_method(:xml=, :xml_content=) 894 895 def inline_text? 896 [nil, "text", "html"].include?(@type) 897 end 898 899 def inline_html? 900 @type == "html" 901 end 902 903 def inline_xhtml? 904 @type == "xhtml" 905 end 906 907 def inline_other? 908 !out_of_line? and ![nil, "text", "html", "xhtml"].include?(@type) 909 end 910 911 def inline_other_text? 912 return false if @type.nil? or out_of_line? 913 /\Atext\//i.match(@type) ? true : false 914 end 915 916 def inline_other_xml? 917 return false if @type.nil? or out_of_line? 918 /[\+\/]xml\z/i.match(@type) ? true : false 919 end 920 921 def inline_other_base64? 922 return false if @type.nil? or out_of_line? 923 @type.include?("/") and !inline_other_text? and !inline_other_xml? 924 end 925 926 def out_of_line? 927 not @src.nil? and @content.nil? 928 end 929 end 930 931 class TitleBase < Base 932 include AtomTextConstructBase 933 end 934 end 935 end 936 937 class TextinputBase < Base 938 %w(title description name link).each do |element| 939 attr_accessor element 940 add_need_initialize_variable(element) 941 end 942 end 943 end 944end 945