1require 'rss/2.0'
2
3module RSS
4  ITUNES_PREFIX = 'itunes'
5  ITUNES_URI = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
6
7  Rss.install_ns(ITUNES_PREFIX, ITUNES_URI)
8
9  module ITunesModelUtils
10    include Utils
11
12    def def_class_accessor(klass, name, type, *args)
13        normalized_name = name.gsub(/-/, "_")
14      full_name = "#{ITUNES_PREFIX}_#{normalized_name}"
15      klass_name = "ITunes#{Utils.to_class_name(normalized_name)}"
16
17      case type
18      when :element, :attribute
19        klass::ELEMENTS << full_name
20        def_element_class_accessor(klass, name, full_name, klass_name, *args)
21      when :elements
22        klass::ELEMENTS << full_name
23        def_elements_class_accessor(klass, name, full_name, klass_name, *args)
24      else
25        klass.install_must_call_validator(ITUNES_PREFIX, ITUNES_URI)
26        klass.install_text_element(normalized_name, ITUNES_URI, "?",
27                                   full_name, type, name)
28      end
29    end
30
31    def def_element_class_accessor(klass, name, full_name, klass_name,
32                                   recommended_attribute_name=nil)
33      klass.install_have_child_element(name, ITUNES_PREFIX, "?", full_name)
34    end
35
36    def def_elements_class_accessor(klass, name, full_name, klass_name,
37                                    plural_name, recommended_attribute_name=nil)
38      full_plural_name = "#{ITUNES_PREFIX}_#{plural_name}"
39      klass.install_have_children_element(name, ITUNES_PREFIX, "*",
40                                          full_name, full_plural_name)
41    end
42  end
43
44  module ITunesBaseModel
45    extend ITunesModelUtils
46
47    ELEMENTS = []
48
49    ELEMENT_INFOS = [["author"],
50                     ["block", :yes_other],
51                     ["explicit", :yes_clean_other],
52                     ["keywords", :csv],
53                     ["subtitle"],
54                     ["summary"]]
55  end
56
57  module ITunesChannelModel
58    extend BaseModel
59    extend ITunesModelUtils
60    include ITunesBaseModel
61
62    ELEMENTS = []
63
64    class << self
65      def append_features(klass)
66        super
67
68        return if klass.instance_of?(Module)
69        ELEMENT_INFOS.each do |name, type, *additional_infos|
70          def_class_accessor(klass, name, type, *additional_infos)
71        end
72      end
73    end
74
75    ELEMENT_INFOS = [
76                     ["category", :elements, "categories", "text"],
77                     ["image", :attribute, "href"],
78                     ["owner", :element],
79                     ["new-feed-url"],
80                    ] + ITunesBaseModel::ELEMENT_INFOS
81
82    class ITunesCategory < Element
83      include RSS09
84
85      @tag_name = "category"
86
87      class << self
88        def required_prefix
89          ITUNES_PREFIX
90        end
91
92        def required_uri
93          ITUNES_URI
94        end
95      end
96
97      [
98        ["text", "", true]
99      ].each do |name, uri, required|
100        install_get_attribute(name, uri, required)
101      end
102
103      ITunesCategory = self
104      install_have_children_element("category", ITUNES_URI, "*",
105                                    "#{ITUNES_PREFIX}_category",
106                                    "#{ITUNES_PREFIX}_categories")
107
108      def initialize(*args)
109        if Utils.element_initialize_arguments?(args)
110          super
111        else
112          super()
113          self.text = args[0]
114        end
115      end
116
117      def full_name
118        tag_name_with_prefix(ITUNES_PREFIX)
119      end
120
121      private
122      def maker_target(categories)
123        if text or !itunes_categories.empty?
124          categories.new_category
125        else
126          nil
127        end
128      end
129
130      def setup_maker_attributes(category)
131        category.text = text if text
132      end
133
134      def setup_maker_elements(category)
135        super(category)
136        itunes_categories.each do |sub_category|
137          sub_category.setup_maker(category)
138        end
139      end
140    end
141
142    class ITunesImage < Element
143      include RSS09
144
145      @tag_name = "image"
146
147      class << self
148        def required_prefix
149          ITUNES_PREFIX
150        end
151
152        def required_uri
153          ITUNES_URI
154        end
155      end
156
157      [
158        ["href", "", true]
159      ].each do |name, uri, required|
160        install_get_attribute(name, uri, required)
161      end
162
163      def initialize(*args)
164        if Utils.element_initialize_arguments?(args)
165          super
166        else
167          super()
168          self.href = args[0]
169        end
170      end
171
172      def full_name
173        tag_name_with_prefix(ITUNES_PREFIX)
174      end
175
176      private
177      def maker_target(target)
178        if href
179          target.itunes_image {|image| image}
180        else
181          nil
182        end
183      end
184
185      def setup_maker_attributes(image)
186        image.href = href
187      end
188    end
189
190    class ITunesOwner < Element
191      include RSS09
192
193      @tag_name = "owner"
194
195      class << self
196        def required_prefix
197          ITUNES_PREFIX
198        end
199
200        def required_uri
201          ITUNES_URI
202        end
203      end
204
205      install_must_call_validator(ITUNES_PREFIX, ITUNES_URI)
206      [
207        ["name"],
208        ["email"],
209      ].each do |name,|
210        ITunesBaseModel::ELEMENT_INFOS << name
211        install_text_element(name, ITUNES_URI, nil, "#{ITUNES_PREFIX}_#{name}")
212      end
213
214      def initialize(*args)
215        if Utils.element_initialize_arguments?(args)
216          super
217        else
218          super()
219          self.itunes_name = args[0]
220          self.itunes_email = args[1]
221        end
222      end
223
224      def full_name
225        tag_name_with_prefix(ITUNES_PREFIX)
226      end
227
228      private
229      def maker_target(target)
230        target.itunes_owner
231      end
232
233      def setup_maker_element(owner)
234        super(owner)
235        owner.itunes_name = itunes_name
236        owner.itunes_email = itunes_email
237      end
238    end
239  end
240
241  module ITunesItemModel
242    extend BaseModel
243    extend ITunesModelUtils
244    include ITunesBaseModel
245
246    class << self
247      def append_features(klass)
248        super
249
250        return if klass.instance_of?(Module)
251        ELEMENT_INFOS.each do |name, type|
252          def_class_accessor(klass, name, type)
253        end
254      end
255    end
256
257    ELEMENT_INFOS = ITunesBaseModel::ELEMENT_INFOS +
258      [["duration", :element, "content"]]
259
260    class ITunesDuration < Element
261      include RSS09
262
263      @tag_name = "duration"
264
265      class << self
266        def required_prefix
267          ITUNES_PREFIX
268        end
269
270        def required_uri
271          ITUNES_URI
272        end
273
274        def parse(duration, do_validate=true)
275          if do_validate and /\A(?:
276                                  \d?\d:[0-5]\d:[0-5]\d|
277                                  [0-5]?\d:[0-5]\d
278                                )\z/x !~ duration
279            raise ArgumentError,
280                    "must be one of HH:MM:SS, H:MM:SS, MM::SS, M:SS: " +
281                    duration.inspect
282          end
283
284          components = duration.split(':')
285          components[3..-1] = nil if components.size > 3
286
287          components.unshift("00") until components.size == 3
288
289          components.collect do |component|
290            component.to_i
291          end
292        end
293
294        def construct(hour, minute, second)
295          components = [minute, second]
296          if components.include?(nil)
297            nil
298          else
299            components.unshift(hour) if hour and hour > 0
300            components.collect do |component|
301              "%02d" % component
302            end.join(":")
303          end
304        end
305      end
306
307      content_setup
308      alias_method(:value, :content)
309      remove_method(:content=)
310
311      attr_reader :hour, :minute, :second
312      def initialize(*args)
313        if Utils.element_initialize_arguments?(args)
314          super
315        else
316          super()
317          args = args[0] if args.size == 1 and args[0].is_a?(Array)
318          if args.size == 1
319            self.content = args[0]
320          elsif args.size > 3
321            raise ArgumentError,
322                    "must be (do_validate, params), (content), " +
323                    "(minute, second), ([minute, second]), "  +
324                    "(hour, minute, second) or ([hour, minute, second]): " +
325                    args.inspect
326          else
327            @second, @minute, @hour = args.reverse
328            update_content
329          end
330        end
331      end
332
333      def content=(value)
334        if value.nil?
335          @content = nil
336        elsif value.is_a?(self.class)
337          self.content = value.content
338        else
339          begin
340            @hour, @minute, @second = self.class.parse(value, @do_validate)
341          rescue ArgumentError
342            raise NotAvailableValueError.new(tag_name, value)
343          end
344          @content = value
345        end
346      end
347      alias_method(:value=, :content=)
348
349      def hour=(hour)
350        @hour = @do_validate ? Integer(hour) : hour.to_i
351        update_content
352        hour
353      end
354
355      def minute=(minute)
356        @minute = @do_validate ? Integer(minute) : minute.to_i
357        update_content
358        minute
359      end
360
361      def second=(second)
362        @second = @do_validate ? Integer(second) : second.to_i
363        update_content
364        second
365      end
366
367      def full_name
368        tag_name_with_prefix(ITUNES_PREFIX)
369      end
370
371      private
372      def update_content
373        @content = self.class.construct(hour, minute, second)
374      end
375
376      def maker_target(target)
377        if @content
378          target.itunes_duration {|duration| duration}
379        else
380          nil
381        end
382      end
383
384      def setup_maker_element(duration)
385        super(duration)
386        duration.content = @content
387      end
388    end
389  end
390
391  class Rss
392    class Channel
393      include ITunesChannelModel
394      class Item; include ITunesItemModel; end
395    end
396  end
397
398  element_infos =
399    ITunesChannelModel::ELEMENT_INFOS + ITunesItemModel::ELEMENT_INFOS
400  element_infos.each do |name, type|
401    case type
402    when :element, :elements, :attribute
403      class_name = Utils.to_class_name(name)
404      BaseListener.install_class_name(ITUNES_URI, name, "ITunes#{class_name}")
405    else
406      accessor_base = "#{ITUNES_PREFIX}_#{name.gsub(/-/, '_')}"
407      BaseListener.install_get_text_element(ITUNES_URI, name, accessor_base)
408    end
409  end
410end
411