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