rss 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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