rss 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +88 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/lib/rss.rb +92 -0
  11. data/lib/rss/0.9.rb +462 -0
  12. data/lib/rss/1.0.rb +485 -0
  13. data/lib/rss/2.0.rb +143 -0
  14. data/lib/rss/atom.rb +1025 -0
  15. data/lib/rss/content.rb +34 -0
  16. data/lib/rss/content/1.0.rb +10 -0
  17. data/lib/rss/content/2.0.rb +12 -0
  18. data/lib/rss/converter.rb +171 -0
  19. data/lib/rss/dublincore.rb +164 -0
  20. data/lib/rss/dublincore/1.0.rb +13 -0
  21. data/lib/rss/dublincore/2.0.rb +13 -0
  22. data/lib/rss/dublincore/atom.rb +17 -0
  23. data/lib/rss/image.rb +198 -0
  24. data/lib/rss/itunes.rb +413 -0
  25. data/lib/rss/maker.rb +79 -0
  26. data/lib/rss/maker/0.9.rb +509 -0
  27. data/lib/rss/maker/1.0.rb +436 -0
  28. data/lib/rss/maker/2.0.rb +224 -0
  29. data/lib/rss/maker/atom.rb +173 -0
  30. data/lib/rss/maker/base.rb +945 -0
  31. data/lib/rss/maker/content.rb +22 -0
  32. data/lib/rss/maker/dublincore.rb +122 -0
  33. data/lib/rss/maker/entry.rb +164 -0
  34. data/lib/rss/maker/feed.rb +427 -0
  35. data/lib/rss/maker/image.rb +112 -0
  36. data/lib/rss/maker/itunes.rb +243 -0
  37. data/lib/rss/maker/slash.rb +34 -0
  38. data/lib/rss/maker/syndication.rb +19 -0
  39. data/lib/rss/maker/taxonomy.rb +119 -0
  40. data/lib/rss/maker/trackback.rb +62 -0
  41. data/lib/rss/parser.rb +589 -0
  42. data/lib/rss/rexmlparser.rb +50 -0
  43. data/lib/rss/rss.rb +1346 -0
  44. data/lib/rss/slash.rb +52 -0
  45. data/lib/rss/syndication.rb +69 -0
  46. data/lib/rss/taxonomy.rb +148 -0
  47. data/lib/rss/trackback.rb +291 -0
  48. data/lib/rss/utils.rb +200 -0
  49. data/lib/rss/xml-stylesheet.rb +106 -0
  50. data/lib/rss/xml.rb +72 -0
  51. data/lib/rss/xmlparser.rb +95 -0
  52. data/lib/rss/xmlscanner.rb +122 -0
  53. data/rss.gemspec +38 -0
  54. metadata +138 -0
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: false
2
+ require "rexml/document"
3
+ require "rexml/streamlistener"
4
+
5
+ module RSS
6
+
7
+ class REXMLParser < BaseParser
8
+
9
+ class << self
10
+ def listener
11
+ REXMLListener
12
+ end
13
+ end
14
+
15
+ private
16
+ def _parse
17
+ begin
18
+ REXML::Document.parse_stream(@rss, @listener)
19
+ rescue RuntimeError => e
20
+ raise NotWellFormedError.new{e.message}
21
+ rescue REXML::ParseException => e
22
+ context = e.context
23
+ line = context[0] if context
24
+ raise NotWellFormedError.new(line){e.message}
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ class REXMLListener < BaseListener
31
+
32
+ include REXML::StreamListener
33
+ include ListenerMixin
34
+
35
+ class << self
36
+ def raise_for_undefined_entity?
37
+ false
38
+ end
39
+ end
40
+
41
+ def xmldecl(version, encoding, standalone)
42
+ super(version, encoding, standalone == "yes")
43
+ # Encoding is converted to UTF-8 when REXML parse XML.
44
+ @encoding = 'UTF-8'
45
+ end
46
+
47
+ alias_method(:cdata, :text)
48
+ end
49
+
50
+ end
@@ -0,0 +1,1346 @@
1
+ # frozen_string_literal: false
2
+ require "time"
3
+
4
+ class Time
5
+ class << self
6
+ unless respond_to?(:w3cdtf)
7
+ # This method converts a W3CDTF string date/time format to Time object.
8
+ #
9
+ # The W3CDTF format is defined here: http://www.w3.org/TR/NOTE-datetime
10
+ #
11
+ # Time.w3cdtf('2003-02-15T13:50:05-05:00')
12
+ # # => 2003-02-15 10:50:05 -0800
13
+ # Time.w3cdtf('2003-02-15T13:50:05-05:00').class
14
+ # # => Time
15
+ def w3cdtf(date)
16
+ if /\A\s*
17
+ (-?\d+)-(\d\d)-(\d\d)
18
+ (?:T
19
+ (\d\d):(\d\d)(?::(\d\d))?
20
+ (\.\d+)?
21
+ (Z|[+-]\d\d:\d\d)?)?
22
+ \s*\z/ix =~ date and (($5 and $8) or (!$5 and !$8))
23
+ datetime = [$1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i]
24
+ usec = 0
25
+ usec = $7.to_f * 1000000 if $7
26
+ zone = $8
27
+ if zone
28
+ off = zone_offset(zone, datetime[0])
29
+ datetime = apply_offset(*(datetime + [off]))
30
+ datetime << usec
31
+ time = Time.utc(*datetime)
32
+ force_zone!(time, zone, off)
33
+ time
34
+ else
35
+ datetime << usec
36
+ Time.local(*datetime)
37
+ end
38
+ else
39
+ raise ArgumentError.new("invalid date: #{date.inspect}")
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ unless method_defined?(:w3cdtf)
46
+ # This method converts a Time object to a String. The String contains the
47
+ # time in W3CDTF date/time format.
48
+ #
49
+ # The W3CDTF format is defined here: http://www.w3.org/TR/NOTE-datetime
50
+ #
51
+ # Time.now.w3cdtf
52
+ # # => "2013-08-26T14:12:10.817124-07:00"
53
+ def w3cdtf
54
+ if usec.zero?
55
+ fraction_digits = 0
56
+ else
57
+ fraction_digits = strftime('%6N').index(/0*\z/)
58
+ end
59
+ xmlschema(fraction_digits)
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ require "English"
66
+ require_relative "utils"
67
+ require_relative "converter"
68
+ require_relative "xml-stylesheet"
69
+
70
+ module RSS
71
+
72
+ # The current version of RSS
73
+ VERSION = "0.2.7"
74
+
75
+ # The URI of the RSS 1.0 specification
76
+ URI = "http://purl.org/rss/1.0/"
77
+
78
+ DEBUG = false # :nodoc:
79
+
80
+ # The basic error all other RSS errors stem from. Rescue this error if you
81
+ # want to handle any given RSS error and you don't care about the details.
82
+ class Error < StandardError; end
83
+
84
+ # RSS, being an XML-based format, has namespace support. If two namespaces are
85
+ # declared with the same name, an OverlappedPrefixError will be raised.
86
+ class OverlappedPrefixError < Error
87
+ attr_reader :prefix
88
+ def initialize(prefix)
89
+ @prefix = prefix
90
+ end
91
+ end
92
+
93
+ # The InvalidRSSError error is the base class for a variety of errors
94
+ # related to a poorly-formed RSS feed. Rescue this error if you only
95
+ # care that a file could be invalid, but don't care how it is invalid.
96
+ class InvalidRSSError < Error; end
97
+
98
+ # Since RSS is based on XML, it must have opening and closing tags that
99
+ # match. If they don't, a MissingTagError will be raised.
100
+ class MissingTagError < InvalidRSSError
101
+ attr_reader :tag, :parent
102
+ def initialize(tag, parent)
103
+ @tag, @parent = tag, parent
104
+ super("tag <#{tag}> is missing in tag <#{parent}>")
105
+ end
106
+ end
107
+
108
+ # Some tags must only exist a specific number of times in a given RSS feed.
109
+ # If a feed has too many occurrences of one of these tags, a TooMuchTagError
110
+ # will be raised.
111
+ class TooMuchTagError < InvalidRSSError
112
+ attr_reader :tag, :parent
113
+ def initialize(tag, parent)
114
+ @tag, @parent = tag, parent
115
+ super("tag <#{tag}> is too much in tag <#{parent}>")
116
+ end
117
+ end
118
+
119
+ # Certain attributes are required on specific tags in an RSS feed. If a feed
120
+ # is missing one of these attributes, a MissingAttributeError is raised.
121
+ class MissingAttributeError < InvalidRSSError
122
+ attr_reader :tag, :attribute
123
+ def initialize(tag, attribute)
124
+ @tag, @attribute = tag, attribute
125
+ super("attribute <#{attribute}> is missing in tag <#{tag}>")
126
+ end
127
+ end
128
+
129
+ # RSS does not allow for free-form tag names, so if an RSS feed contains a
130
+ # tag that we don't know about, an UnknownTagError is raised.
131
+ class UnknownTagError < InvalidRSSError
132
+ attr_reader :tag, :uri
133
+ def initialize(tag, uri)
134
+ @tag, @uri = tag, uri
135
+ super("tag <#{tag}> is unknown in namespace specified by uri <#{uri}>")
136
+ end
137
+ end
138
+
139
+ # Raised when an unexpected tag is encountered.
140
+ class NotExpectedTagError < InvalidRSSError
141
+ attr_reader :tag, :uri, :parent
142
+ def initialize(tag, uri, parent)
143
+ @tag, @uri, @parent = tag, uri, parent
144
+ super("tag <{#{uri}}#{tag}> is not expected in tag <#{parent}>")
145
+ end
146
+ end
147
+ # For backward compatibility :X
148
+ NotExceptedTagError = NotExpectedTagError # :nodoc:
149
+
150
+ # Attributes are in key-value form, and if there's no value provided for an
151
+ # attribute, a NotAvailableValueError will be raised.
152
+ class NotAvailableValueError < InvalidRSSError
153
+ attr_reader :tag, :value, :attribute
154
+ def initialize(tag, value, attribute=nil)
155
+ @tag, @value, @attribute = tag, value, attribute
156
+ message = "value <#{value}> of "
157
+ message << "attribute <#{attribute}> of " if attribute
158
+ message << "tag <#{tag}> is not available."
159
+ super(message)
160
+ end
161
+ end
162
+
163
+ # Raised when an unknown conversion error occurs.
164
+ class UnknownConversionMethodError < Error
165
+ attr_reader :to, :from
166
+ def initialize(to, from)
167
+ @to = to
168
+ @from = from
169
+ super("can't convert to #{to} from #{from}.")
170
+ end
171
+ end
172
+ # for backward compatibility
173
+ UnknownConvertMethod = UnknownConversionMethodError # :nodoc:
174
+
175
+ # Raised when a conversion failure occurs.
176
+ class ConversionError < Error
177
+ attr_reader :string, :to, :from
178
+ def initialize(string, to, from)
179
+ @string = string
180
+ @to = to
181
+ @from = from
182
+ super("can't convert #{@string} to #{to} from #{from}.")
183
+ end
184
+ end
185
+
186
+ # Raised when a required variable is not set.
187
+ class NotSetError < Error
188
+ attr_reader :name, :variables
189
+ def initialize(name, variables)
190
+ @name = name
191
+ @variables = variables
192
+ super("required variables of #{@name} are not set: #{@variables.join(', ')}")
193
+ end
194
+ end
195
+
196
+ # Raised when a RSS::Maker attempts to use an unknown maker.
197
+ class UnsupportedMakerVersionError < Error
198
+ attr_reader :version
199
+ def initialize(version)
200
+ @version = version
201
+ super("Maker doesn't support version: #{@version}")
202
+ end
203
+ end
204
+
205
+ module BaseModel
206
+ include Utils
207
+
208
+ def install_have_child_element(tag_name, uri, occurs, name=nil, type=nil)
209
+ name ||= tag_name
210
+ add_need_initialize_variable(name)
211
+ install_model(tag_name, uri, occurs, name)
212
+
213
+ writer_type, reader_type = type
214
+ def_corresponded_attr_writer name, writer_type
215
+ def_corresponded_attr_reader name, reader_type
216
+ install_element(name) do |n, elem_name|
217
+ <<-EOC
218
+ if @#{n}
219
+ "\#{@#{n}.to_s(need_convert, indent)}"
220
+ else
221
+ ''
222
+ end
223
+ EOC
224
+ end
225
+ end
226
+ alias_method(:install_have_attribute_element, :install_have_child_element)
227
+
228
+ def install_have_children_element(tag_name, uri, occurs, name=nil, plural_name=nil)
229
+ name ||= tag_name
230
+ plural_name ||= "#{name}s"
231
+ add_have_children_element(name, plural_name)
232
+ add_plural_form(name, plural_name)
233
+ install_model(tag_name, uri, occurs, plural_name, true)
234
+
235
+ def_children_accessor(name, plural_name)
236
+ install_element(name, "s") do |n, elem_name|
237
+ <<-EOC
238
+ rv = []
239
+ @#{n}.each do |x|
240
+ value = "\#{x.to_s(need_convert, indent)}"
241
+ rv << value if /\\A\\s*\\z/ !~ value
242
+ end
243
+ rv.join("\n")
244
+ EOC
245
+ end
246
+ end
247
+
248
+ def install_text_element(tag_name, uri, occurs, name=nil, type=nil,
249
+ disp_name=nil)
250
+ name ||= tag_name
251
+ disp_name ||= name
252
+ self::ELEMENTS << name unless self::ELEMENTS.include?(name)
253
+ add_need_initialize_variable(name)
254
+ install_model(tag_name, uri, occurs, name)
255
+
256
+ def_corresponded_attr_writer(name, type, disp_name)
257
+ def_corresponded_attr_reader(name, type || :convert)
258
+ install_element(name) do |n, elem_name|
259
+ <<-EOC
260
+ if respond_to?(:#{n}_content)
261
+ content = #{n}_content
262
+ else
263
+ content = @#{n}
264
+ end
265
+ if content
266
+ rv = "\#{indent}<#{elem_name}>"
267
+ value = html_escape(content)
268
+ if need_convert
269
+ rv << convert(value)
270
+ else
271
+ rv << value
272
+ end
273
+ rv << "</#{elem_name}>"
274
+ rv
275
+ else
276
+ ''
277
+ end
278
+ EOC
279
+ end
280
+ end
281
+
282
+ def install_date_element(tag_name, uri, occurs, name=nil, type=nil, disp_name=nil)
283
+ name ||= tag_name
284
+ type ||= :w3cdtf
285
+ disp_name ||= name
286
+ self::ELEMENTS << name
287
+ add_need_initialize_variable(name)
288
+ install_model(tag_name, uri, occurs, name)
289
+
290
+ # accessor
291
+ convert_attr_reader name
292
+ date_writer(name, type, disp_name)
293
+
294
+ install_element(name) do |n, elem_name|
295
+ <<-EOC
296
+ if @#{n}
297
+ rv = "\#{indent}<#{elem_name}>"
298
+ value = html_escape(@#{n}.#{type})
299
+ if need_convert
300
+ rv << convert(value)
301
+ else
302
+ rv << value
303
+ end
304
+ rv << "</#{elem_name}>"
305
+ rv
306
+ else
307
+ ''
308
+ end
309
+ EOC
310
+ end
311
+
312
+ end
313
+
314
+ private
315
+ def install_element(name, postfix="")
316
+ elem_name = name.sub('_', ':')
317
+ method_name = "#{name}_element#{postfix}"
318
+ add_to_element_method(method_name)
319
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
320
+ def #{method_name}(need_convert=true, indent='')
321
+ #{yield(name, elem_name)}
322
+ end
323
+ private :#{method_name}
324
+ EOC
325
+ end
326
+
327
+ def inherit_convert_attr_reader(*attrs)
328
+ attrs.each do |attr|
329
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
330
+ def #{attr}_without_inherit
331
+ convert(@#{attr})
332
+ end
333
+
334
+ def #{attr}
335
+ if @#{attr}
336
+ #{attr}_without_inherit
337
+ elsif @parent
338
+ @parent.#{attr}
339
+ else
340
+ nil
341
+ end
342
+ end
343
+ EOC
344
+ end
345
+ end
346
+
347
+ def uri_convert_attr_reader(*attrs)
348
+ attrs.each do |attr|
349
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
350
+ def #{attr}_without_base
351
+ convert(@#{attr})
352
+ end
353
+
354
+ def #{attr}
355
+ value = #{attr}_without_base
356
+ return nil if value.nil?
357
+ if /\\A[a-z][a-z0-9+.\\-]*:/i =~ value
358
+ value
359
+ else
360
+ "\#{base}\#{value}"
361
+ end
362
+ end
363
+ EOC
364
+ end
365
+ end
366
+
367
+ def convert_attr_reader(*attrs)
368
+ attrs.each do |attr|
369
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
370
+ def #{attr}
371
+ convert(@#{attr})
372
+ end
373
+ EOC
374
+ end
375
+ end
376
+
377
+ def explicit_clean_other_attr_reader(*attrs)
378
+ attrs.each do |attr|
379
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
380
+ attr_reader(:#{attr})
381
+ def #{attr}?
382
+ ExplicitCleanOther.parse(@#{attr})
383
+ end
384
+ EOC
385
+ end
386
+ end
387
+
388
+ def yes_other_attr_reader(*attrs)
389
+ attrs.each do |attr|
390
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
391
+ attr_reader(:#{attr})
392
+ def #{attr}?
393
+ Utils::YesOther.parse(@#{attr})
394
+ end
395
+ EOC
396
+ end
397
+ end
398
+
399
+ def csv_attr_reader(*attrs)
400
+ separator = nil
401
+ if attrs.last.is_a?(Hash)
402
+ options = attrs.pop
403
+ separator = options[:separator]
404
+ end
405
+ separator ||= ", "
406
+ attrs.each do |attr|
407
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
408
+ attr_reader(:#{attr})
409
+ def #{attr}_content
410
+ if @#{attr}.nil?
411
+ @#{attr}
412
+ else
413
+ @#{attr}.join(#{separator.dump})
414
+ end
415
+ end
416
+ EOC
417
+ end
418
+ end
419
+
420
+ def date_writer(name, type, disp_name=name)
421
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
422
+ def #{name}=(new_value)
423
+ if new_value.nil?
424
+ @#{name} = new_value
425
+ elsif new_value.kind_of?(Time)
426
+ @#{name} = new_value.dup
427
+ else
428
+ if @do_validate
429
+ begin
430
+ @#{name} = Time.__send__('#{type}', new_value)
431
+ rescue ArgumentError
432
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
433
+ end
434
+ else
435
+ @#{name} = nil
436
+ if /\\A\\s*\\z/ !~ new_value.to_s
437
+ begin
438
+ unless Date._parse(new_value, false).empty?
439
+ @#{name} = Time.parse(new_value)
440
+ end
441
+ rescue ArgumentError
442
+ end
443
+ end
444
+ end
445
+ end
446
+
447
+ # Is it need?
448
+ if @#{name}
449
+ class << @#{name}
450
+ undef_method(:to_s)
451
+ alias_method(:to_s, :#{type})
452
+ end
453
+ end
454
+
455
+ end
456
+ EOC
457
+ end
458
+
459
+ def integer_writer(name, disp_name=name)
460
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
461
+ def #{name}=(new_value)
462
+ if new_value.nil?
463
+ @#{name} = new_value
464
+ else
465
+ if @do_validate
466
+ begin
467
+ @#{name} = Integer(new_value)
468
+ rescue ArgumentError
469
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
470
+ end
471
+ else
472
+ @#{name} = new_value.to_i
473
+ end
474
+ end
475
+ end
476
+ EOC
477
+ end
478
+
479
+ def positive_integer_writer(name, disp_name=name)
480
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
481
+ def #{name}=(new_value)
482
+ if new_value.nil?
483
+ @#{name} = new_value
484
+ else
485
+ if @do_validate
486
+ begin
487
+ tmp = Integer(new_value)
488
+ raise ArgumentError if tmp <= 0
489
+ @#{name} = tmp
490
+ rescue ArgumentError
491
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
492
+ end
493
+ else
494
+ @#{name} = new_value.to_i
495
+ end
496
+ end
497
+ end
498
+ EOC
499
+ end
500
+
501
+ def boolean_writer(name, disp_name=name)
502
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
503
+ def #{name}=(new_value)
504
+ if new_value.nil?
505
+ @#{name} = new_value
506
+ else
507
+ if @do_validate and
508
+ ![true, false, "true", "false"].include?(new_value)
509
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
510
+ end
511
+ if [true, false].include?(new_value)
512
+ @#{name} = new_value
513
+ else
514
+ @#{name} = new_value == "true"
515
+ end
516
+ end
517
+ end
518
+ EOC
519
+ end
520
+
521
+ def text_type_writer(name, disp_name=name)
522
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
523
+ def #{name}=(new_value)
524
+ if @do_validate and
525
+ !["text", "html", "xhtml", nil].include?(new_value)
526
+ raise NotAvailableValueError.new('#{disp_name}', new_value)
527
+ end
528
+ @#{name} = new_value
529
+ end
530
+ EOC
531
+ end
532
+
533
+ def content_writer(name, disp_name=name)
534
+ klass_name = "self.class::#{Utils.to_class_name(name)}"
535
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
536
+ def #{name}=(new_value)
537
+ if new_value.is_a?(#{klass_name})
538
+ @#{name} = new_value
539
+ else
540
+ @#{name} = #{klass_name}.new
541
+ @#{name}.content = new_value
542
+ end
543
+ end
544
+ EOC
545
+ end
546
+
547
+ def explicit_clean_other_writer(name, disp_name=name)
548
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
549
+ def #{name}=(value)
550
+ value = (value ? "yes" : "no") if [true, false].include?(value)
551
+ @#{name} = value
552
+ end
553
+ EOC
554
+ end
555
+
556
+ def yes_other_writer(name, disp_name=name)
557
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
558
+ def #{name}=(new_value)
559
+ if [true, false].include?(new_value)
560
+ new_value = new_value ? "yes" : "no"
561
+ end
562
+ @#{name} = new_value
563
+ end
564
+ EOC
565
+ end
566
+
567
+ def csv_writer(name, disp_name=name)
568
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
569
+ def #{name}=(new_value)
570
+ @#{name} = Utils::CSV.parse(new_value)
571
+ end
572
+ EOC
573
+ end
574
+
575
+ def csv_integer_writer(name, disp_name=name)
576
+ module_eval(<<-EOC, __FILE__, __LINE__ + 1)
577
+ def #{name}=(new_value)
578
+ @#{name} = Utils::CSV.parse(new_value) {|v| Integer(v)}
579
+ end
580
+ EOC
581
+ end
582
+
583
+ def def_children_accessor(accessor_name, plural_name)
584
+ module_eval(<<-EOC, *get_file_and_line_from_caller(2))
585
+ def #{plural_name}
586
+ @#{accessor_name}
587
+ end
588
+
589
+ def #{accessor_name}(*args)
590
+ if args.empty?
591
+ @#{accessor_name}.first
592
+ else
593
+ @#{accessor_name}[*args]
594
+ end
595
+ end
596
+
597
+ def #{accessor_name}=(*args)
598
+ receiver = self.class.name
599
+ warn("Don't use `\#{receiver}\##{accessor_name} = XXX'/" \
600
+ "`\#{receiver}\#set_#{accessor_name}(XXX)'. " \
601
+ "Those APIs are not sense of Ruby. " \
602
+ "Use `\#{receiver}\##{plural_name} << XXX' instead of them.", uplevel: 1)
603
+ if args.size == 1
604
+ @#{accessor_name}.push(args[0])
605
+ else
606
+ @#{accessor_name}.__send__("[]=", *args)
607
+ end
608
+ end
609
+ alias_method(:set_#{accessor_name}, :#{accessor_name}=)
610
+ EOC
611
+ end
612
+ end
613
+
614
+ module SetupMaker
615
+ def setup_maker(maker)
616
+ target = maker_target(maker)
617
+ unless target.nil?
618
+ setup_maker_attributes(target)
619
+ setup_maker_element(target)
620
+ setup_maker_elements(target)
621
+ end
622
+ end
623
+
624
+ private
625
+ def maker_target(maker)
626
+ nil
627
+ end
628
+
629
+ def setup_maker_attributes(target)
630
+ end
631
+
632
+ def setup_maker_element(target)
633
+ self.class.need_initialize_variables.each do |var|
634
+ value = __send__(var)
635
+ next if value.nil?
636
+ if value.respond_to?("setup_maker") and
637
+ !not_need_to_call_setup_maker_variables.include?(var)
638
+ value.setup_maker(target)
639
+ else
640
+ setter = "#{var}="
641
+ if target.respond_to?(setter)
642
+ target.__send__(setter, value)
643
+ end
644
+ end
645
+ end
646
+ end
647
+
648
+ def not_need_to_call_setup_maker_variables
649
+ []
650
+ end
651
+
652
+ def setup_maker_elements(parent)
653
+ self.class.have_children_elements.each do |name, plural_name|
654
+ if parent.respond_to?(plural_name)
655
+ target = parent.__send__(plural_name)
656
+ __send__(plural_name).each do |elem|
657
+ elem.setup_maker(target)
658
+ end
659
+ end
660
+ end
661
+ end
662
+ end
663
+
664
+ class Element
665
+ extend BaseModel
666
+ include Utils
667
+ extend Utils::InheritedReader
668
+ include SetupMaker
669
+
670
+ INDENT = " "
671
+
672
+ MUST_CALL_VALIDATORS = {}
673
+ MODELS = []
674
+ GET_ATTRIBUTES = []
675
+ HAVE_CHILDREN_ELEMENTS = []
676
+ TO_ELEMENT_METHODS = []
677
+ NEED_INITIALIZE_VARIABLES = []
678
+ PLURAL_FORMS = {}
679
+
680
+ class << self
681
+ def must_call_validators
682
+ inherited_hash_reader("MUST_CALL_VALIDATORS")
683
+ end
684
+ def models
685
+ inherited_array_reader("MODELS")
686
+ end
687
+ def get_attributes
688
+ inherited_array_reader("GET_ATTRIBUTES")
689
+ end
690
+ def have_children_elements
691
+ inherited_array_reader("HAVE_CHILDREN_ELEMENTS")
692
+ end
693
+ def to_element_methods
694
+ inherited_array_reader("TO_ELEMENT_METHODS")
695
+ end
696
+ def need_initialize_variables
697
+ inherited_array_reader("NEED_INITIALIZE_VARIABLES")
698
+ end
699
+ def plural_forms
700
+ inherited_hash_reader("PLURAL_FORMS")
701
+ end
702
+
703
+ def inherited_base
704
+ ::RSS::Element
705
+ end
706
+
707
+ def inherited(klass)
708
+ klass.const_set(:MUST_CALL_VALIDATORS, {})
709
+ klass.const_set(:MODELS, [])
710
+ klass.const_set(:GET_ATTRIBUTES, [])
711
+ klass.const_set(:HAVE_CHILDREN_ELEMENTS, [])
712
+ klass.const_set(:TO_ELEMENT_METHODS, [])
713
+ klass.const_set(:NEED_INITIALIZE_VARIABLES, [])
714
+ klass.const_set(:PLURAL_FORMS, {})
715
+
716
+ tag_name = klass.name.split(/::/).last
717
+ tag_name[0, 1] = tag_name[0, 1].downcase
718
+ klass.instance_variable_set(:@tag_name, tag_name)
719
+ klass.instance_variable_set(:@have_content, false)
720
+ end
721
+
722
+ def install_must_call_validator(prefix, uri)
723
+ self::MUST_CALL_VALIDATORS[uri] = prefix
724
+ end
725
+
726
+ def install_model(tag, uri, occurs=nil, getter=nil, plural=false)
727
+ getter ||= tag
728
+ if m = self::MODELS.find {|t, u, o, g, p| t == tag and u == uri}
729
+ m[2] = occurs
730
+ else
731
+ self::MODELS << [tag, uri, occurs, getter, plural]
732
+ end
733
+ end
734
+
735
+ def install_get_attribute(name, uri, required=true,
736
+ type=nil, disp_name=nil,
737
+ element_name=nil)
738
+ disp_name ||= name
739
+ element_name ||= name
740
+ writer_type, reader_type = type
741
+ def_corresponded_attr_writer name, writer_type, disp_name
742
+ def_corresponded_attr_reader name, reader_type
743
+ if type == :boolean and /^is/ =~ name
744
+ alias_method "#{$POSTMATCH}?", name
745
+ end
746
+ self::GET_ATTRIBUTES << [name, uri, required, element_name]
747
+ add_need_initialize_variable(disp_name)
748
+ end
749
+
750
+ def def_corresponded_attr_writer(name, type=nil, disp_name=nil)
751
+ disp_name ||= name
752
+ case type
753
+ when :integer
754
+ integer_writer name, disp_name
755
+ when :positive_integer
756
+ positive_integer_writer name, disp_name
757
+ when :boolean
758
+ boolean_writer name, disp_name
759
+ when :w3cdtf, :rfc822, :rfc2822
760
+ date_writer name, type, disp_name
761
+ when :text_type
762
+ text_type_writer name, disp_name
763
+ when :content
764
+ content_writer name, disp_name
765
+ when :explicit_clean_other
766
+ explicit_clean_other_writer name, disp_name
767
+ when :yes_other
768
+ yes_other_writer name, disp_name
769
+ when :csv
770
+ csv_writer name
771
+ when :csv_integer
772
+ csv_integer_writer name
773
+ else
774
+ attr_writer name
775
+ end
776
+ end
777
+
778
+ def def_corresponded_attr_reader(name, type=nil)
779
+ case type
780
+ when :inherit
781
+ inherit_convert_attr_reader name
782
+ when :uri
783
+ uri_convert_attr_reader name
784
+ when :explicit_clean_other
785
+ explicit_clean_other_attr_reader name
786
+ when :yes_other
787
+ yes_other_attr_reader name
788
+ when :csv
789
+ csv_attr_reader name
790
+ when :csv_integer
791
+ csv_attr_reader name, :separator => ","
792
+ else
793
+ convert_attr_reader name
794
+ end
795
+ end
796
+
797
+ def content_setup(type=nil, disp_name=nil)
798
+ writer_type, reader_type = type
799
+ def_corresponded_attr_writer :content, writer_type, disp_name
800
+ def_corresponded_attr_reader :content, reader_type
801
+ @have_content = true
802
+ end
803
+
804
+ def have_content?
805
+ @have_content
806
+ end
807
+
808
+ def add_have_children_element(variable_name, plural_name)
809
+ self::HAVE_CHILDREN_ELEMENTS << [variable_name, plural_name]
810
+ end
811
+
812
+ def add_to_element_method(method_name)
813
+ self::TO_ELEMENT_METHODS << method_name
814
+ end
815
+
816
+ def add_need_initialize_variable(variable_name)
817
+ self::NEED_INITIALIZE_VARIABLES << variable_name
818
+ end
819
+
820
+ def add_plural_form(singular, plural)
821
+ self::PLURAL_FORMS[singular] = plural
822
+ end
823
+
824
+ def required_prefix
825
+ nil
826
+ end
827
+
828
+ def required_uri
829
+ ""
830
+ end
831
+
832
+ def need_parent?
833
+ false
834
+ end
835
+
836
+ def install_ns(prefix, uri)
837
+ if self::NSPOOL.has_key?(prefix)
838
+ raise OverlappedPrefixError.new(prefix)
839
+ end
840
+ self::NSPOOL[prefix] = uri
841
+ end
842
+
843
+ def tag_name
844
+ @tag_name
845
+ end
846
+ end
847
+
848
+ attr_accessor :parent, :do_validate
849
+
850
+ def initialize(do_validate=true, attrs=nil)
851
+ @parent = nil
852
+ @converter = nil
853
+ if attrs.nil? and (do_validate.is_a?(Hash) or do_validate.is_a?(Array))
854
+ do_validate, attrs = true, do_validate
855
+ end
856
+ @do_validate = do_validate
857
+ initialize_variables(attrs || {})
858
+ end
859
+
860
+ def tag_name
861
+ self.class.tag_name
862
+ end
863
+
864
+ def full_name
865
+ tag_name
866
+ end
867
+
868
+ def converter=(converter)
869
+ @converter = converter
870
+ targets = children.dup
871
+ self.class.have_children_elements.each do |variable_name, plural_name|
872
+ targets.concat(__send__(plural_name))
873
+ end
874
+ targets.each do |target|
875
+ target.converter = converter unless target.nil?
876
+ end
877
+ end
878
+
879
+ def convert(value)
880
+ if @converter
881
+ @converter.convert(value)
882
+ else
883
+ value
884
+ end
885
+ end
886
+
887
+ def valid?(ignore_unknown_element=true)
888
+ validate(ignore_unknown_element)
889
+ true
890
+ rescue RSS::Error
891
+ false
892
+ end
893
+
894
+ def validate(ignore_unknown_element=true)
895
+ do_validate = @do_validate
896
+ @do_validate = true
897
+ validate_attribute
898
+ __validate(ignore_unknown_element)
899
+ ensure
900
+ @do_validate = do_validate
901
+ end
902
+
903
+ def validate_for_stream(tags, ignore_unknown_element=true)
904
+ validate_attribute
905
+ __validate(ignore_unknown_element, tags, false)
906
+ end
907
+
908
+ def to_s(need_convert=true, indent='')
909
+ if self.class.have_content?
910
+ return "" if !empty_content? and !content_is_set?
911
+ rv = tag(indent) do |next_indent|
912
+ if empty_content?
913
+ ""
914
+ else
915
+ xmled_content
916
+ end
917
+ end
918
+ else
919
+ rv = tag(indent) do |next_indent|
920
+ self.class.to_element_methods.collect do |method_name|
921
+ __send__(method_name, false, next_indent)
922
+ end
923
+ end
924
+ end
925
+ rv = convert(rv) if need_convert
926
+ rv
927
+ end
928
+
929
+ def have_xml_content?
930
+ false
931
+ end
932
+
933
+ def need_base64_encode?
934
+ false
935
+ end
936
+
937
+ def set_next_element(tag_name, next_element)
938
+ klass = next_element.class
939
+ prefix = ""
940
+ prefix << "#{klass.required_prefix}_" if klass.required_prefix
941
+ key = "#{prefix}#{tag_name.gsub(/-/, '_')}"
942
+ if self.class.plural_forms.has_key?(key)
943
+ ary = __send__("#{self.class.plural_forms[key]}")
944
+ ary << next_element
945
+ else
946
+ __send__("#{key}=", next_element)
947
+ end
948
+ end
949
+
950
+ protected
951
+ def have_required_elements?
952
+ self.class::MODELS.all? do |tag, uri, occurs, getter|
953
+ if occurs.nil? or occurs == "+"
954
+ child = __send__(getter)
955
+ if child.is_a?(Array)
956
+ children = child
957
+ children.any? {|c| c.have_required_elements?}
958
+ else
959
+ not child.nil?
960
+ end
961
+ else
962
+ true
963
+ end
964
+ end
965
+ end
966
+
967
+ private
968
+ def initialize_variables(attrs)
969
+ normalized_attrs = {}
970
+ attrs.each do |key, value|
971
+ normalized_attrs[key.to_s] = value
972
+ end
973
+ self.class.need_initialize_variables.each do |variable_name|
974
+ value = normalized_attrs[variable_name.to_s]
975
+ if value
976
+ __send__("#{variable_name}=", value)
977
+ else
978
+ instance_variable_set("@#{variable_name}", nil)
979
+ end
980
+ end
981
+ initialize_have_children_elements
982
+ @content = normalized_attrs["content"] if self.class.have_content?
983
+ end
984
+
985
+ def initialize_have_children_elements
986
+ self.class.have_children_elements.each do |variable_name, plural_name|
987
+ instance_variable_set("@#{variable_name}", [])
988
+ end
989
+ end
990
+
991
+ def tag(indent, additional_attrs={}, &block)
992
+ next_indent = indent + INDENT
993
+
994
+ attrs = collect_attrs
995
+ return "" if attrs.nil?
996
+
997
+ return "" unless have_required_elements?
998
+
999
+ attrs.update(additional_attrs)
1000
+ start_tag = make_start_tag(indent, next_indent, attrs.dup)
1001
+
1002
+ if block
1003
+ content = block.call(next_indent)
1004
+ else
1005
+ content = []
1006
+ end
1007
+
1008
+ if content.is_a?(String)
1009
+ content = [content]
1010
+ start_tag << ">"
1011
+ end_tag = "</#{full_name}>"
1012
+ else
1013
+ content = content.reject{|x| x.empty?}
1014
+ if content.empty?
1015
+ return "" if attrs.empty?
1016
+ end_tag = "/>"
1017
+ else
1018
+ start_tag << ">\n"
1019
+ end_tag = "\n#{indent}</#{full_name}>"
1020
+ end
1021
+ end
1022
+
1023
+ start_tag + content.join("\n") + end_tag
1024
+ end
1025
+
1026
+ def make_start_tag(indent, next_indent, attrs)
1027
+ start_tag = ["#{indent}<#{full_name}"]
1028
+ unless attrs.empty?
1029
+ start_tag << attrs.collect do |key, value|
1030
+ %Q[#{h key}="#{h value}"]
1031
+ end.join("\n#{next_indent}")
1032
+ end
1033
+ start_tag.join(" ")
1034
+ end
1035
+
1036
+ def collect_attrs
1037
+ attrs = {}
1038
+ _attrs.each do |name, required, alias_name|
1039
+ value = __send__(alias_name || name)
1040
+ return nil if required and value.nil?
1041
+ next if value.nil?
1042
+ return nil if attrs.has_key?(name)
1043
+ attrs[name] = value
1044
+ end
1045
+ attrs
1046
+ end
1047
+
1048
+ def tag_name_with_prefix(prefix)
1049
+ "#{prefix}:#{tag_name}"
1050
+ end
1051
+
1052
+ # For backward compatibility
1053
+ def calc_indent
1054
+ ''
1055
+ end
1056
+
1057
+ def children
1058
+ rv = []
1059
+ self.class.models.each do |name, uri, occurs, getter|
1060
+ value = __send__(getter)
1061
+ next if value.nil?
1062
+ value = [value] unless value.is_a?(Array)
1063
+ value.each do |v|
1064
+ rv << v if v.is_a?(Element)
1065
+ end
1066
+ end
1067
+ rv
1068
+ end
1069
+
1070
+ def _tags
1071
+ rv = []
1072
+ self.class.models.each do |name, uri, occurs, getter, plural|
1073
+ value = __send__(getter)
1074
+ next if value.nil?
1075
+ if plural and value.is_a?(Array)
1076
+ rv.concat([[uri, name]] * value.size)
1077
+ else
1078
+ rv << [uri, name]
1079
+ end
1080
+ end
1081
+ rv
1082
+ end
1083
+
1084
+ def _attrs
1085
+ self.class.get_attributes.collect do |name, uri, required, element_name|
1086
+ [element_name, required, name]
1087
+ end
1088
+ end
1089
+
1090
+ def __validate(ignore_unknown_element, tags=_tags, recursive=true)
1091
+ if recursive
1092
+ children.compact.each do |child|
1093
+ child.validate
1094
+ end
1095
+ end
1096
+ must_call_validators = self.class.must_call_validators
1097
+ tags = tag_filter(tags.dup)
1098
+ p tags if DEBUG
1099
+ must_call_validators.each do |uri, prefix|
1100
+ _validate(ignore_unknown_element, tags[uri], uri)
1101
+ meth = "#{prefix}_validate"
1102
+ if !prefix.empty? and respond_to?(meth, true)
1103
+ __send__(meth, ignore_unknown_element, tags[uri], uri)
1104
+ end
1105
+ end
1106
+ end
1107
+
1108
+ def validate_attribute
1109
+ _attrs.each do |a_name, required, alias_name|
1110
+ value = instance_variable_get("@#{alias_name || a_name}")
1111
+ if required and value.nil?
1112
+ raise MissingAttributeError.new(tag_name, a_name)
1113
+ end
1114
+ __send__("#{alias_name || a_name}=", value)
1115
+ end
1116
+ end
1117
+
1118
+ def _validate(ignore_unknown_element, tags, uri, models=self.class.models)
1119
+ count = 1
1120
+ do_redo = false
1121
+ not_shift = false
1122
+ tag = nil
1123
+ models = models.find_all {|model| model[1] == uri}
1124
+ element_names = models.collect {|model| model[0]}
1125
+ if tags
1126
+ tags_size = tags.size
1127
+ tags = tags.sort_by {|x| element_names.index(x) || tags_size}
1128
+ end
1129
+
1130
+ models.each_with_index do |model, i|
1131
+ name, _, occurs, = model
1132
+
1133
+ if DEBUG
1134
+ p "before"
1135
+ p tags
1136
+ p model
1137
+ end
1138
+
1139
+ if not_shift
1140
+ not_shift = false
1141
+ elsif tags
1142
+ tag = tags.shift
1143
+ end
1144
+
1145
+ if DEBUG
1146
+ p "mid"
1147
+ p count
1148
+ end
1149
+
1150
+ case occurs
1151
+ when '?'
1152
+ if count > 2
1153
+ raise TooMuchTagError.new(name, tag_name)
1154
+ else
1155
+ if name == tag
1156
+ do_redo = true
1157
+ else
1158
+ not_shift = true
1159
+ end
1160
+ end
1161
+ when '*'
1162
+ if name == tag
1163
+ do_redo = true
1164
+ else
1165
+ not_shift = true
1166
+ end
1167
+ when '+'
1168
+ if name == tag
1169
+ do_redo = true
1170
+ else
1171
+ if count > 1
1172
+ not_shift = true
1173
+ else
1174
+ raise MissingTagError.new(name, tag_name)
1175
+ end
1176
+ end
1177
+ else
1178
+ if name == tag
1179
+ if models[i+1] and models[i+1][0] != name and
1180
+ tags and tags.first == name
1181
+ raise TooMuchTagError.new(name, tag_name)
1182
+ end
1183
+ else
1184
+ raise MissingTagError.new(name, tag_name)
1185
+ end
1186
+ end
1187
+
1188
+ if DEBUG
1189
+ p "after"
1190
+ p not_shift
1191
+ p do_redo
1192
+ p tag
1193
+ end
1194
+
1195
+ if do_redo
1196
+ do_redo = false
1197
+ count += 1
1198
+ redo
1199
+ else
1200
+ count = 1
1201
+ end
1202
+
1203
+ end
1204
+
1205
+ if !ignore_unknown_element and !tags.nil? and !tags.empty?
1206
+ raise NotExpectedTagError.new(tags.first, uri, tag_name)
1207
+ end
1208
+
1209
+ end
1210
+
1211
+ def tag_filter(tags)
1212
+ rv = {}
1213
+ tags.each do |tag|
1214
+ rv[tag[0]] = [] unless rv.has_key?(tag[0])
1215
+ rv[tag[0]].push(tag[1])
1216
+ end
1217
+ rv
1218
+ end
1219
+
1220
+ def empty_content?
1221
+ false
1222
+ end
1223
+
1224
+ def content_is_set?
1225
+ if have_xml_content?
1226
+ __send__(self.class.xml_getter)
1227
+ else
1228
+ content
1229
+ end
1230
+ end
1231
+
1232
+ def xmled_content
1233
+ if have_xml_content?
1234
+ __send__(self.class.xml_getter).to_s
1235
+ else
1236
+ _content = content
1237
+ _content = [_content].pack("m0") if need_base64_encode?
1238
+ h(_content)
1239
+ end
1240
+ end
1241
+ end
1242
+
1243
+ module RootElementMixin
1244
+
1245
+ include XMLStyleSheetMixin
1246
+
1247
+ attr_reader :output_encoding
1248
+ attr_reader :feed_type, :feed_subtype, :feed_version
1249
+ attr_accessor :version, :encoding, :standalone
1250
+ def initialize(feed_version, version=nil, encoding=nil, standalone=nil)
1251
+ super()
1252
+ @feed_type = nil
1253
+ @feed_subtype = nil
1254
+ @feed_version = feed_version
1255
+ @version = version || '1.0'
1256
+ @encoding = encoding
1257
+ @standalone = standalone
1258
+ @output_encoding = nil
1259
+ end
1260
+
1261
+ def feed_info
1262
+ [@feed_type, @feed_version, @feed_subtype]
1263
+ end
1264
+
1265
+ def output_encoding=(enc)
1266
+ @output_encoding = enc
1267
+ self.converter = Converter.new(@output_encoding, @encoding)
1268
+ end
1269
+
1270
+ def setup_maker(maker)
1271
+ maker.version = version
1272
+ maker.encoding = encoding
1273
+ maker.standalone = standalone
1274
+
1275
+ xml_stylesheets.each do |xss|
1276
+ xss.setup_maker(maker)
1277
+ end
1278
+
1279
+ super
1280
+ end
1281
+
1282
+ def to_feed(type, &block)
1283
+ Maker.make(type) do |maker|
1284
+ setup_maker(maker)
1285
+ block.call(maker) if block
1286
+ end
1287
+ end
1288
+
1289
+ def to_rss(type, &block)
1290
+ to_feed("rss#{type}", &block)
1291
+ end
1292
+
1293
+ def to_atom(type, &block)
1294
+ to_feed("atom:#{type}", &block)
1295
+ end
1296
+
1297
+ def to_xml(type=nil, &block)
1298
+ if type.nil? or same_feed_type?(type)
1299
+ to_s
1300
+ else
1301
+ to_feed(type, &block).to_s
1302
+ end
1303
+ end
1304
+
1305
+ private
1306
+ def same_feed_type?(type)
1307
+ if /^(atom|rss)?(\d+\.\d+)?(?::(.+))?$/i =~ type
1308
+ feed_type = ($1 || @feed_type).downcase
1309
+ feed_version = $2 || @feed_version
1310
+ feed_subtype = $3 || @feed_subtype
1311
+ [feed_type, feed_version, feed_subtype] == feed_info
1312
+ else
1313
+ false
1314
+ end
1315
+ end
1316
+
1317
+ def tag(indent, attrs={}, &block)
1318
+ rv = super(indent, ns_declarations.merge(attrs), &block)
1319
+ return rv if rv.empty?
1320
+ "#{xmldecl}#{xml_stylesheet_pi}#{rv}"
1321
+ end
1322
+
1323
+ def xmldecl
1324
+ rv = %Q[<?xml version="#{@version}"]
1325
+ if @output_encoding or @encoding
1326
+ rv << %Q[ encoding="#{@output_encoding or @encoding}"]
1327
+ end
1328
+ rv << %Q[ standalone="yes"] if @standalone
1329
+ rv << "?>\n"
1330
+ rv
1331
+ end
1332
+
1333
+ def ns_declarations
1334
+ decls = {}
1335
+ self.class::NSPOOL.collect do |prefix, uri|
1336
+ prefix = ":#{prefix}" unless prefix.empty?
1337
+ decls["xmlns#{prefix}"] = uri
1338
+ end
1339
+ decls
1340
+ end
1341
+
1342
+ def maker_target(target)
1343
+ target
1344
+ end
1345
+ end
1346
+ end