feedtools 0.2.18 → 0.2.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -165,14 +165,18 @@ module FeedTools
165
165
  # Warning, this method may be slow if you have a
166
166
  # large number of FeedTools::Feed objects. Can't
167
167
  # use a direct reference to the parent because it plays
168
- # havoc with the garbage collector.
168
+ # havoc with the garbage collector. Could've used
169
+ # a WeakRef object, but really, if there are multiple
170
+ # parent feeds, something is going to go wrong, and the
171
+ # programmer needs to be notified. A WeakRef
172
+ # implementation can't detect this condition.
169
173
  def feed
170
174
  parent_feed = nil
171
175
  ObjectSpace.each_object(FeedTools::Feed) do |feed|
172
- if feed.instance_variable_get("@items").nil?
176
+ if feed.instance_variable_get("@entries").nil?
173
177
  feed.items
174
178
  end
175
- unsorted_items = feed.instance_variable_get("@items")
179
+ unsorted_items = feed.instance_variable_get("@entries")
176
180
  for item in unsorted_items
177
181
  if item.object_id == self.object_id
178
182
  if parent_feed.nil?
@@ -224,13 +228,21 @@ module FeedTools
224
228
  end
225
229
 
226
230
  # Returns the first node within the root_node that matches the xpath query.
227
- def find_node(xpath)
228
- return XPath.first(root_node, xpath)
231
+ def find_node(xpath, select_result_value=false)
232
+ if feed.feed_data_type != :xml
233
+ raise "The feed data type is not xml."
234
+ end
235
+ return try_xpaths(self.root_node, [xpath],
236
+ :select_result_value => select_result_value)
229
237
  end
230
238
 
231
239
  # Returns all nodes within the root_node that match the xpath query.
232
- def find_all_nodes(xpath)
233
- return XPath.match(root_node, xpath)
240
+ def find_all_nodes(xpath, select_result_value=false)
241
+ if feed.feed_data_type != :xml
242
+ raise "The feed data type is not xml."
243
+ end
244
+ return try_xpaths_all(self.root_node, [xpath],
245
+ :select_result_value => select_result_value)
234
246
  end
235
247
 
236
248
  # Returns the root node of the feed item.
@@ -247,13 +259,13 @@ module FeedTools
247
259
  # Returns the feed items's unique id
248
260
  def id
249
261
  if @id.nil?
250
- unless root_node.nil?
251
- @id = XPath.first(root_node, "id/text()").to_s
252
- if @id == ""
253
- @id = XPath.first(root_node, "guid/text()").to_s
254
- end
255
- end
256
- @id = nil if @id == ""
262
+ @id = try_xpaths(self.root_node, [
263
+ "atom10:id/text()",
264
+ "atom03:id/text()",
265
+ "atom:id/text()",
266
+ "id/text()",
267
+ "guid/text()"
268
+ ], :select_result_value => true)
257
269
  end
258
270
  return @id
259
271
  end
@@ -266,37 +278,23 @@ module FeedTools
266
278
  # Returns the feed item title
267
279
  def title
268
280
  if @title.nil?
269
- unless root_node.nil?
270
- repair_entities = false
271
- title_node = XPath.first(root_node, "atom10:title",
272
- FEED_TOOLS_NAMESPACES)
273
- if title_node.nil?
274
- title_node = XPath.first(root_node, "title")
275
- end
276
- if title_node.nil?
277
- title_node = XPath.first(root_node, "atom03:title",
278
- FEED_TOOLS_NAMESPACES)
279
- end
280
- if title_node.nil?
281
- title_node = XPath.first(root_node, "atom:title")
282
- end
283
- if title_node.nil?
284
- title_node = XPath.first(root_node, "dc:title",
285
- FEED_TOOLS_NAMESPACES)
286
- end
287
- if title_node.nil?
288
- title_node = XPath.first(root_node, "dc:title")
289
- end
290
- if title_node.nil?
291
- title_node = XPath.first(root_node, "TITLE")
292
- end
293
- end
281
+ repair_entities = false
282
+ title_node = try_xpaths(self.root_node, [
283
+ "atom10:title",
284
+ "atom03:title",
285
+ "atom:title",
286
+ "title",
287
+ "dc:title"
288
+ ])
294
289
  if title_node.nil?
295
290
  return nil
296
291
  end
297
- title_type = XPath.first(title_node, "@type").to_s
298
- title_mode = XPath.first(title_node, "@mode").to_s
299
- title_encoding = XPath.first(title_node, "@encoding").to_s
292
+ title_type = try_xpaths(title_node, "@type",
293
+ :select_result_value => true)
294
+ title_mode = try_xpaths(title_node, "@mode",
295
+ :select_result_value => true)
296
+ title_encoding = try_xpaths(title_node, "@encoding",
297
+ :select_result_value => true)
300
298
 
301
299
  # Note that we're checking for misuse of type, mode and encoding here
302
300
  if title_type == "base64" || title_mode == "base64" ||
@@ -318,7 +316,7 @@ module FeedTools
318
316
  @title = FeedTools.unescape_entities(@title) if repair_entities
319
317
  @title = FeedTools.tidy_html(@title) unless repair_entities
320
318
  end
321
- if @title != ""
319
+ if !@title.blank? && FeedTools.configurations[:strip_comment_count]
322
320
  # Some blogging tools include the number of comments in a post
323
321
  # in the title... this is supremely ugly, and breaks any
324
322
  # applications which expect the title to be static, so we're
@@ -331,7 +329,7 @@ module FeedTools
331
329
  @title.gsub!(/>\n</, "><")
332
330
  @title.gsub!(/\n/, " ")
333
331
  @title.strip!
334
- @title = nil if @title == ""
332
+ @title = nil if @title.blank?
335
333
  end
336
334
  return @title
337
335
  end
@@ -341,123 +339,90 @@ module FeedTools
341
339
  @title = new_title
342
340
  end
343
341
 
344
- # Returns the feed item description
345
- def description
346
- if @description.nil?
347
- unless root_node.nil?
348
- repair_entities = false
349
- description_node = XPath.first(root_node, "content:encoded")
350
- if description_node.nil?
351
- description_node = XPath.first(root_node, "content:encoded",
352
- FEED_TOOLS_NAMESPACES)
353
- end
354
- if description_node.nil?
355
- description_node = XPath.first(root_node, "encoded")
356
- end
357
- if description_node.nil?
358
- description_node = XPath.first(root_node, "content")
359
- end
360
- if description_node.nil?
361
- description_node = XPath.first(root_node, "fullitem")
362
- end
363
- if description_node.nil?
364
- description_node = XPath.first(root_node, "xhtml:body")
365
- end
366
- if description_node.nil?
367
- description_node = XPath.first(root_node, "xhtml:body",
368
- FEED_TOOLS_NAMESPACES)
369
- end
370
- if description_node.nil?
371
- description_node = XPath.first(root_node, "body")
372
- end
373
- if description_node.nil?
374
- description_node = XPath.first(root_node, "description")
375
- end
376
- if description_node.nil?
377
- description_node = XPath.first(root_node, "tagline")
378
- end
379
- if description_node.nil?
380
- description_node = XPath.first(root_node, "subtitle")
381
- end
382
- if description_node.nil?
383
- description_node = XPath.first(root_node, "summary")
384
- end
385
- if description_node.nil?
386
- description_node = XPath.first(root_node, "abstract")
387
- end
388
- if description_node.nil?
389
- description_node = XPath.first(root_node, "ABSTRACT")
390
- end
391
- if description_node.nil?
392
- description_node = XPath.first(root_node, "blurb")
393
- end
394
- if description_node.nil?
395
- description_node = XPath.first(root_node, "info")
396
- end
397
- end
398
- if description_node.nil?
342
+ # Returns the feed item content
343
+ def content
344
+ if @content.nil?
345
+ repair_entities = false
346
+ content_node = try_xpaths(self.root_node, [
347
+ "content:encoded",
348
+ "content",
349
+ "fullitem",
350
+ "xhtml:body",
351
+ "body",
352
+ "encoded",
353
+ "description",
354
+ "tagline",
355
+ "subtitle",
356
+ "summary",
357
+ "abstract",
358
+ "blurb",
359
+ "info"
360
+ ])
361
+ if content_node.nil?
399
362
  return nil
400
363
  end
401
- description_type = XPath.first(description_node, "@type").to_s
402
- description_mode = XPath.first(description_node, "@mode").to_s
403
- description_encoding = XPath.first(description_node, "@encoding").to_s
364
+ content_type = try_xpaths(content_node, "@type",
365
+ :select_result_value => true)
366
+ content_mode = try_xpaths(content_node, "@mode",
367
+ :select_result_value => true)
368
+ content_encoding = try_xpaths(content_node, "@encoding",
369
+ :select_result_value => true)
404
370
 
405
371
  # Note that we're checking for misuse of type, mode and encoding here
406
- if description_encoding != ""
407
- @description =
372
+ if !content_encoding.blank?
373
+ @content =
408
374
  "[Embedded data objects are not currently supported.]"
409
- elsif description_node.cdatas.size > 0
410
- @description = description_node.cdatas.first.value
411
- elsif description_type == "base64" || description_mode == "base64" ||
412
- description_encoding == "base64"
413
- @description = Base64.decode64(description_node.inner_xml.strip)
414
- elsif description_type == "xhtml" || description_mode == "xhtml" ||
415
- description_type == "xml" || description_mode == "xml" ||
416
- description_type == "application/xhtml+xml"
417
- @description = description_node.inner_xml
418
- elsif description_type == "escaped" || description_mode == "escaped"
419
- @description = FeedTools.unescape_entities(
420
- description_node.inner_xml)
375
+ elsif content_node.cdatas.size > 0
376
+ @content = content_node.cdatas.first.value
377
+ elsif content_type == "base64" || content_mode == "base64" ||
378
+ content_encoding == "base64"
379
+ @content = Base64.decode64(content_node.inner_xml.strip)
380
+ elsif content_type == "xhtml" || content_mode == "xhtml" ||
381
+ content_type == "xml" || content_mode == "xml" ||
382
+ content_type == "application/xhtml+xml"
383
+ @content = content_node.inner_xml
384
+ elsif content_type == "escaped" || content_mode == "escaped"
385
+ @content = FeedTools.unescape_entities(
386
+ content_node.inner_xml)
421
387
  else
422
- @description = description_node.inner_xml
388
+ @content = content_node.inner_xml
423
389
  repair_entities = true
424
390
  end
425
- if @description == ""
426
- @description = self.itunes_summary
427
- @description = "" if @description.nil?
391
+ if @content.blank?
392
+ @content = self.itunes_summary
428
393
  end
429
- if @description == ""
430
- @description = self.itunes_subtitle
431
- @description = "" if @description.nil?
394
+ if @content.blank?
395
+ @content = self.itunes_subtitle
432
396
  end
433
397
 
434
- unless @description.nil?
435
- @description = FeedTools.sanitize_html(@description, :strip)
436
- @description = FeedTools.unescape_entities(@description) if repair_entities
437
- @description = FeedTools.tidy_html(@description)
398
+ unless @content.blank?
399
+ @content = FeedTools.sanitize_html(@content, :strip)
400
+ @content = FeedTools.unescape_entities(@content) if repair_entities
401
+ @content = FeedTools.tidy_html(@content)
438
402
  end
439
403
 
440
- @description = @description.strip unless @description.nil?
441
- @description = nil if @description == ""
404
+ @content = @content.strip unless @content.nil?
405
+ @content = nil if @content.blank?
442
406
  end
443
- return @description
407
+ return @content
444
408
  end
445
409
 
446
- # Sets the feed item description
447
- def description=(new_description)
448
- @description = new_description
410
+ # Sets the feed item content
411
+ def content=(new_content)
412
+ @content = new_content
449
413
  end
450
414
 
451
415
  # Returns the contents of the itunes:summary element
452
416
  def itunes_summary
453
417
  if @itunes_summary.nil?
454
- @itunes_summary = FeedTools.unescape_entities(XPath.first(root_node,
455
- "itunes:summary/text()").to_s)
456
- if @itunes_summary == ""
457
- @itunes_summary = nil
458
- end
459
- unless @itunes_summary.nil?
418
+ @itunes_summary = try_xpaths(self.root_node, [
419
+ "itunes:summary/text()"
420
+ ])
421
+ unless @itunes_summary.blank?
422
+ @itunes_summary = FeedTools.unescape_entities(@itunes_summary)
460
423
  @itunes_summary = FeedTools.sanitize_html(@itunes_summary)
424
+ else
425
+ @itunes_summary = nil
461
426
  end
462
427
  end
463
428
  return @itunes_summary
@@ -471,13 +436,14 @@ module FeedTools
471
436
  # Returns the contents of the itunes:subtitle element
472
437
  def itunes_subtitle
473
438
  if @itunes_subtitle.nil?
474
- @itunes_subtitle = FeedTools.unescape_entities(XPath.first(root_node,
475
- "itunes:subtitle/text()").to_s)
476
- if @itunes_subtitle == ""
477
- @itunes_subtitle = nil
478
- end
479
- unless @itunes_subtitle.nil?
439
+ @itunes_subtitle = try_xpaths(self.root_node, [
440
+ "itunes:subtitle/text()"
441
+ ])
442
+ unless @itunes_subtitle.blank?
443
+ @itunes_subtitle = FeedTools.unescape_entities(@itunes_subtitle)
480
444
  @itunes_subtitle = FeedTools.sanitize_html(@itunes_subtitle)
445
+ else
446
+ @itunes_subtitle = nil
481
447
  end
482
448
  end
483
449
  return @itunes_subtitle
@@ -511,39 +477,31 @@ module FeedTools
511
477
  # Returns the feed item link
512
478
  def link
513
479
  if @link.nil?
514
- unless root_node.nil?
515
- @link = XPath.first(root_node, "link[@rel='alternate']/@href").to_s
516
- if @link == ""
517
- @link = XPath.first(root_node, "link/@href").to_s
518
- end
519
- if @link == ""
520
- @link = XPath.first(root_node, "link/text()").to_s
521
- end
522
- if @link == ""
523
- @link = XPath.first(root_node, "@rdf:about").to_s
524
- end
525
- if @link == ""
526
- @link = XPath.first(root_node, "guid[@isPermaLink='true']/text()").to_s
527
- end
528
- if @link == ""
529
- @link = XPath.first(root_node, "@href").to_s
530
- end
531
- if @link == ""
532
- @link = XPath.first(root_node, "a/@href").to_s
533
- end
534
- if @link == ""
535
- @link = XPath.first(root_node, "@HREF").to_s
536
- end
537
- if @link == ""
538
- @link = XPath.first(root_node, "A/@HREF").to_s
539
- end
540
- end
541
- if @link == "" || @link.nil?
480
+ @link = try_xpaths(self.root_node, [
481
+ "atom10:link[@type='application/xhtml+xml']/@href",
482
+ "atom10:link[@type='text/html']/@href",
483
+ "atom10:link[@rel='alternate']/@href",
484
+ "atom03:link[@type='application/xhtml+xml']/@href",
485
+ "atom03:link[@type='text/html']/@href",
486
+ "atom03:link[@rel='alternate']/@href",
487
+ "atom:link[@type='application/xhtml+xml']/@href",
488
+ "atom:link[@type='text/html']/@href",
489
+ "atom:link[@rel='alternate']/@href",
490
+ "link[@type='application/xhtml+xml']/@href",
491
+ "link[@type='text/html']/@href",
492
+ "link[@rel='alternate']/@href",
493
+ "link/text()",
494
+ "@rdf:about",
495
+ "guid[@isPermaLink='true']/text()",
496
+ "@href",
497
+ "a/@href"
498
+ ], :select_result_value => true)
499
+ if @link.blank?
542
500
  if FeedTools.is_uri? self.guid
543
501
  @link = self.guid
544
502
  end
545
503
  end
546
- if @link != ""
504
+ if !@link.blank?
547
505
  @link = FeedTools.unescape_entities(@link)
548
506
  end
549
507
  # TODO: Actually implement proper relative url resolving instead of this crap
@@ -556,7 +514,50 @@ module FeedTools
556
514
  # # prepend the base to the link since they seem to have used a relative path
557
515
  # @link = feed.base + @link
558
516
  # end
559
- @link = FeedTools.normalize_url(@link)
517
+ if @link.blank?
518
+ link_node = try_xpaths(self.root_node, [
519
+ "atom10:link",
520
+ "atom03:link",
521
+ "atom:link",
522
+ "link"
523
+ ])
524
+ if link_node != nil
525
+ if link_node.attributes['type'].to_s =~ /^image/ ||
526
+ link_node.attributes['type'].to_s =~ /^application/ ||
527
+ link_node.attributes['type'].to_s =~ /xml/ ||
528
+ link_node.attributes['rel'].to_s =~ /self/
529
+ for child in self.root_node
530
+ if child.class == REXML::Element
531
+ if child.name.downcase == "link"
532
+ if child.attributes['type'].to_s =~ /^image/ ||
533
+ child.attributes['type'].to_s =~ /^application/ ||
534
+ child.attributes['type'].to_s =~ /xml/ ||
535
+ child.attributes['rel'].to_s =~ /self/
536
+ @link = nil
537
+ next
538
+ else
539
+ @link = child.attributes['href'].to_s
540
+ if @link.blank?
541
+ @link = child.inner_xml
542
+ end
543
+ if @link.blank?
544
+ next
545
+ end
546
+ break
547
+ end
548
+ end
549
+ end
550
+ end
551
+ else
552
+ @link = link_node.attributes['href'].to_s
553
+ end
554
+ end
555
+ end
556
+ @link = self.comments if @link.blank?
557
+ @link = nil if @link.blank?
558
+ if FeedTools.configurations[:url_normalization_enabled]
559
+ @link = FeedTools.normalize_url(@link)
560
+ end
560
561
  end
561
562
  return @link
562
563
  end
@@ -570,30 +571,24 @@ module FeedTools
570
571
  def categories
571
572
  if @categories.nil?
572
573
  @categories = []
573
- category_nodes = XPath.match(root_node, "category")
574
- if category_nodes.nil? || category_nodes.empty?
575
- category_nodes = XPath.match(root_node, "dc:subject")
576
- end
577
- unless category_nodes.nil?
578
- for category_node in category_nodes
579
- category = FeedTools::Feed::Category.new
580
- category.term = XPath.first(category_node, "@term").to_s
581
- if category.term == ""
582
- category.term = XPath.first(category_node, "text()").to_s
583
- end
584
- category.term.strip! unless category.term.nil?
585
- category.term = nil if category.term == ""
586
- category.label = XPath.first(category_node, "@label").to_s
587
- category.label.strip! unless category.label.nil?
588
- category.label = nil if category.label == ""
589
- category.scheme = XPath.first(category_node, "@scheme").to_s
590
- if category.scheme == ""
591
- category.scheme = XPath.first(category_node, "@domain").to_s
592
- end
593
- category.scheme.strip! unless category.scheme.nil?
594
- category.scheme = nil if category.scheme == ""
595
- @categories << category
596
- end
574
+ category_nodes = try_xpaths_all(self.root_node, [
575
+ "category",
576
+ "dc:subject"
577
+ ])
578
+ for category_node in category_nodes
579
+ category = FeedTools::Feed::Category.new
580
+ category.term = try_xpaths(category_node, ["@term", "text()"],
581
+ :select_result_value => true)
582
+ category.term.strip! unless category.term.nil?
583
+ category.label = try_xpaths(category_node, ["@label"],
584
+ :select_result_value => true)
585
+ category.label.strip! unless category.label.nil?
586
+ category.scheme = try_xpaths(category_node, [
587
+ "@scheme",
588
+ "@domain"
589
+ ], :select_result_value => true)
590
+ category.scheme.strip! unless category.scheme.nil?
591
+ @categories << category
597
592
  end
598
593
  end
599
594
  return @categories
@@ -603,53 +598,61 @@ module FeedTools
603
598
  def images
604
599
  if @images.nil?
605
600
  @images = []
606
- image_nodes = XPath.match(root_node, "link")
607
- if image_nodes.nil? || image_nodes.empty?
608
- image_nodes = XPath.match(root_node, "logo")
609
- end
610
- if image_nodes.nil? || image_nodes.empty?
611
- image_nodes = XPath.match(root_node, "LOGO")
612
- end
613
- if image_nodes.nil? || image_nodes.empty?
614
- image_nodes = XPath.match(root_node, "image")
615
- end
616
- unless image_nodes.nil?
601
+ image_nodes = try_xpaths_all(self.root_node, [
602
+ "image",
603
+ "logo",
604
+ "atom10:link",
605
+ "atom03:link",
606
+ "atom:link",
607
+ "link"
608
+ ])
609
+ unless image_nodes.blank?
617
610
  for image_node in image_nodes
618
611
  image = FeedTools::Feed::Image.new
619
- image.url = XPath.first(image_node, "url/text()").to_s
620
- if image.url == ""
621
- image.url = XPath.first(image_node, "@rdf:resource").to_s
622
- end
623
- if image.url == "" && (image_node.name == "logo" ||
624
- (image_node.attributes['type'] =~ /^image/) == 0)
625
- image.url = XPath.first(image_node, "@href").to_s
612
+ image.url = try_xpaths(image_node, [
613
+ "url/text()",
614
+ "@rdf:resource"
615
+ ], :select_result_value => true)
616
+ if image.url.blank? && (image_node.name == "logo" ||
617
+ (image_node.attributes['type'].to_s =~ /^image/) == 0)
618
+ image.url = try_xpaths(image_node, [
619
+ "@atom10:href",
620
+ "@atom03:href",
621
+ "@atom:href",
622
+ "@href"
623
+ ], :select_result_value => true)
624
+ if image.url == self.link && image.url != nil
625
+ image.url = nil
626
+ end
626
627
  end
627
- if image.url == "" && image_node.name == "LOGO"
628
- image.url = XPath.first(image_node, "@HREF").to_s
628
+ if image.url.blank? && image_node.name == "LOGO"
629
+ image.url = try_xpaths(image_node, [
630
+ "@href"
631
+ ], :select_result_value => true)
629
632
  end
630
633
  image.url.strip! unless image.url.nil?
631
- image.url = nil if image.url == ""
632
- image.title = XPath.first(image_node, "title/text()").to_s
634
+ image.title = try_xpaths(image_node,
635
+ ["title/text()"], :select_result_value => true)
633
636
  image.title.strip! unless image.title.nil?
634
- image.title = nil if image.title == ""
635
- image.description =
636
- XPath.first(image_node, "description/text()").to_s
637
+ image.description = try_xpaths(image_node,
638
+ ["description/text()"], :select_result_value => true)
637
639
  image.description.strip! unless image.description.nil?
638
- image.description = nil if image.description == ""
639
- image.link = XPath.first(image_node, "link/text()").to_s
640
+ image.link = try_xpaths(image_node,
641
+ ["link/text()"], :select_result_value => true)
640
642
  image.link.strip! unless image.link.nil?
641
- image.link = nil if image.link == ""
642
- image.height = XPath.first(image_node, "height/text()").to_s.to_i
643
+ image.height = try_xpaths(image_node,
644
+ ["height/text()"], :select_result_value => true).to_i
643
645
  image.height = nil if image.height <= 0
644
- image.width = XPath.first(image_node, "width/text()").to_s.to_i
646
+ image.width = try_xpaths(image_node,
647
+ ["width/text()"], :select_result_value => true).to_i
645
648
  image.width = nil if image.width <= 0
646
- image.style = XPath.first(image_node, "@style").to_s.downcase
647
- if image.style == ""
648
- image.style = XPath.first(image_node, "@STYLE").to_s.downcase
649
- end
649
+ image.style = try_xpaths(image_node, [
650
+ "style/text()",
651
+ "@style"
652
+ ], :select_result_value => true)
650
653
  image.style.strip! unless image.style.nil?
651
- image.style = nil if image.style == ""
652
- @images << image
654
+ image.style.downcase! unless image.style.nil?
655
+ @images << image unless image.url.nil?
653
656
  end
654
657
  end
655
658
  end
@@ -657,19 +660,15 @@ module FeedTools
657
660
  end
658
661
 
659
662
  # Returns the feed item itunes image link
660
- #
661
- # If it's not present, falls back to the normal image link.
662
- # Technically, the itunes spec says that the image needs to be
663
- # square and larger than 300x300, but hey, if there's an image
664
- # to be had, it's better than none at all.
665
663
  def itunes_image_link
666
664
  if @itunes_image_link.nil?
667
- # get the feed item itunes image link from the xml document
668
- @itunes_image_link = XPath.first(root_node, "itunes:image/@href").to_s
669
- if @itunes_image_link == ""
670
- @itunes_image_link = XPath.first(root_node, "itunes:link[@rel='image']/@href").to_s
665
+ @itunes_image_link = try_xpaths(self.root_node, [
666
+ "itunes:image/@href",
667
+ "itunes:link[@rel='image']/@href"
668
+ ], :select_result_value => true)
669
+ if FeedTools.configurations[:url_normalization_enabled]
670
+ @itunes_image_link = FeedTools.normalize_url(@itunes_image_link)
671
671
  end
672
- @itunes_image_link = FeedTools.normalize_url(@itunes_image_link)
673
672
  end
674
673
  return @itunes_image_link
675
674
  end
@@ -680,13 +679,14 @@ module FeedTools
680
679
  end
681
680
 
682
681
  # Returns the feed item media thumbnail link
683
- #
684
- # If it's not present, falls back to the normal image link.
685
682
  def media_thumbnail_link
686
683
  if @media_thumbnail_link.nil?
687
- # get the feed item itunes image link from the xml document
688
- @media_thumbnail_link = XPath.first(root_node, "media:thumbnail/@url").to_s
689
- @media_thumbnail_link = FeedTools.normalize_url(@media_thumbnail_link)
684
+ @media_thumbnail_link = try_xpaths(self.root_node, [
685
+ "media:thumbnail/@url"
686
+ ], :select_result_value => true)
687
+ if FeedTools.configurations[:url_normalization_enabled]
688
+ @media_thumbnail_link = FeedTools.normalize_url(@media_thumbnail_link)
689
+ end
690
690
  end
691
691
  return @media_thumbnail_link
692
692
  end
@@ -699,44 +699,28 @@ module FeedTools
699
699
  # Returns the feed item's copyright information
700
700
  def copyright
701
701
  if @copyright.nil?
702
- unless root_node.nil?
703
- repair_entities = false
704
-
705
- copyright_node = XPath.first(root_node, "dc:rights")
706
- if copyright_node.nil?
707
- copyright_node = XPath.first(root_node, "dc:rights",
708
- FEED_TOOLS_NAMESPACES)
709
- end
710
- if copyright_node.nil?
711
- copyright_node = XPath.first(root_node, "rights",
712
- FEED_TOOLS_NAMESPACES)
713
- end
714
- if copyright_node.nil?
715
- copyright_node = XPath.first(root_node, "copyright",
716
- FEED_TOOLS_NAMESPACES)
717
- end
718
- if copyright_node.nil?
719
- copyright_node = XPath.first(root_node, "atom03:copyright",
720
- FEED_TOOLS_NAMESPACES)
721
- end
722
- if copyright_node.nil?
723
- copyright_node = XPath.first(root_node, "atom10:copyright",
724
- FEED_TOOLS_NAMESPACES)
725
- end
726
- if copyright_node.nil?
727
- copyright_node = XPath.first(root_node, "copyrights",
728
- FEED_TOOLS_NAMESPACES)
729
- end
730
- end
702
+ repair_entities = false
703
+ copyright_node = try_xpaths(self.root_node, [
704
+ "atom10:copyright",
705
+ "atom03:copyright",
706
+ "atom:copyright",
707
+ "copyright",
708
+ "copyrights",
709
+ "dc:rights",
710
+ "rights"
711
+ ])
731
712
  if copyright_node.nil?
732
713
  return nil
733
714
  end
734
- copyright_type = XPath.first(copyright_node, "@type").to_s
735
- copyright_mode = XPath.first(copyright_node, "@mode").to_s
736
- copyright_encoding = XPath.first(copyright_node, "@encoding").to_s
715
+ copyright_type = try_xpaths(copyright_node, "@type",
716
+ :select_result_value => true)
717
+ copyright_mode = try_xpaths(copyright_node, "@mode",
718
+ :select_result_value => true)
719
+ copyright_encoding = try_xpaths(copyright_node, "@encoding",
720
+ :select_result_value => true)
737
721
 
738
722
  # Note that we're checking for misuse of type, mode and encoding here
739
- if copyright_encoding != ""
723
+ if !copyright_encoding.blank?
740
724
  @copyright =
741
725
  "[Embedded data objects are not currently supported.]"
742
726
  elsif copyright_node.cdatas.size > 0
@@ -763,7 +747,7 @@ module FeedTools
763
747
  end
764
748
 
765
749
  @copyright = @copyright.strip unless @copyright.nil?
766
- @copyright = nil if @copyright == ""
750
+ @copyright = nil if @copyright.blank?
767
751
  end
768
752
  return @copyright
769
753
  end
@@ -779,16 +763,27 @@ module FeedTools
779
763
  @enclosures = []
780
764
 
781
765
  # First, load up all the different possible sources of enclosures
782
- rss_enclosures = XPath.match(root_node, "enclosure")
783
- atom_enclosures = XPath.match(root_node, "link[@rel='enclosure']")
784
- media_content_enclosures = XPath.match(root_node, "media:content")
785
- media_group_enclosures = XPath.match(root_node, "media:group")
786
-
787
- # Parse RSS-type enclosures. Thanks to a few buggy enclosures implementations,
788
- # sometimes these also manage to show up in atom files.
766
+ rss_enclosures =
767
+ try_xpaths_all(self.root_node, ["enclosure"])
768
+ atom_enclosures =
769
+ try_xpaths_all(self.root_node, [
770
+ "atom10:link[@rel='enclosure']",
771
+ "atom03:link[@rel='enclosure']",
772
+ "atom:link[@rel='enclosure']",
773
+ "link[@rel='enclosure']"
774
+ ])
775
+ media_content_enclosures =
776
+ try_xpaths_all(self.root_node, ["media:content"])
777
+ media_group_enclosures =
778
+ try_xpaths_all(self.root_node, ["media:group"])
779
+
780
+ # Parse RSS-type enclosures. Thanks to a few buggy enclosures
781
+ # implementations, sometimes these also manage to show up in atom
782
+ # files.
789
783
  for enclosure_node in rss_enclosures
790
784
  enclosure = Enclosure.new
791
- enclosure.url = FeedTools.unescape_entities(enclosure_node.attributes["url"].to_s)
785
+ enclosure.url = FeedTools.unescape_entities(
786
+ enclosure_node.attributes["url"].to_s)
792
787
  enclosure.type = enclosure_node.attributes["type"].to_s
793
788
  enclosure.file_size = enclosure_node.attributes["length"].to_i
794
789
  enclosure.credits = []
@@ -796,10 +791,11 @@ module FeedTools
796
791
  @enclosures << enclosure
797
792
  end
798
793
 
799
- # Parse atom-type enclosures. If there are repeats of the same enclosure object,
800
- # we merge the two together.
794
+ # Parse atom-type enclosures. If there are repeats of the same
795
+ # enclosure object, we merge the two together.
801
796
  for enclosure_node in atom_enclosures
802
- enclosure_url = FeedTools.unescape_entities(enclosure_node.attributes["href"].to_s)
797
+ enclosure_url = FeedTools.unescape_entities(
798
+ enclosure_node.attributes["href"].to_s)
803
799
  enclosure = nil
804
800
  new_enclosure = false
805
801
  for existing_enclosure in @enclosures
@@ -822,13 +818,15 @@ module FeedTools
822
818
  end
823
819
  end
824
820
 
825
- # Creates an anonymous method to parse content objects from the media module. We
826
- # do this to avoid excessive duplication of code since we have to do identical
827
- # processing for content objects within group objects.
821
+ # Creates an anonymous method to parse content objects from the media
822
+ # module. We do this to avoid excessive duplication of code since we
823
+ # have to do identical processing for content objects within group
824
+ # objects.
828
825
  parse_media_content = lambda do |media_content_nodes|
829
826
  affected_enclosures = []
830
827
  for enclosure_node in media_content_nodes
831
- enclosure_url = FeedTools.unescape_entities(enclosure_node.attributes["url"].to_s)
828
+ enclosure_url = FeedTools.unescape_entities(
829
+ enclosure_node.attributes["url"].to_s)
832
830
  enclosure = nil
833
831
  new_enclosure = false
834
832
  for existing_enclosure in @enclosures
@@ -849,73 +847,84 @@ module FeedTools
849
847
  enclosure.width = enclosure_node.attributes["width"].to_i
850
848
  enclosure.bitrate = enclosure_node.attributes["bitrate"].to_i
851
849
  enclosure.framerate = enclosure_node.attributes["framerate"].to_i
852
- enclosure.expression = enclosure_node.attributes["expression"].to_s
850
+ enclosure.expression =
851
+ enclosure_node.attributes["expression"].to_s
853
852
  enclosure.is_default =
854
853
  (enclosure_node.attributes["isDefault"].to_s.downcase == "true")
855
- if XPath.first(enclosure_node, "media:thumbnail/@url").to_s != ""
854
+ enclosure_thumbnail_url = try_xpaths(enclosure_node,
855
+ ["media:thumbnail/@url"], :select_result_value => true)
856
+ if !enclosure_thumbnail_url.blank?
856
857
  enclosure.thumbnail = EnclosureThumbnail.new(
857
- FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@url").to_s),
858
- FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@height").to_s),
859
- FeedTools.unescape_entities(XPath.first(enclosure_node, "media:thumbnail/@width").to_s)
858
+ FeedTools.unescape_entities(enclosure_thumbnail_url),
859
+ FeedTools.unescape_entities(
860
+ try_xpaths(enclosure_node, ["media:thumbnail/@height"],
861
+ :select_result_value => true)),
862
+ FeedTools.unescape_entities(
863
+ try_xpaths(enclosure_node, ["media:thumbnail/@width"],
864
+ :select_result_value => true))
860
865
  )
861
- if enclosure.thumbnail.height == ""
862
- enclosure.thumbnail.height = nil
863
- end
864
- if enclosure.thumbnail.width == ""
865
- enclosure.thumbnail.width = nil
866
- end
867
866
  end
868
867
  enclosure.categories = []
869
- for category in XPath.match(enclosure_node, "media:category")
868
+ for category in try_xpaths_all(enclosure_node, ["media:category"])
870
869
  enclosure.categories << FeedTools::Feed::Category.new
871
870
  enclosure.categories.last.term =
872
- FeedTools.unescape_entities(category.text)
871
+ FeedTools.unescape_entities(category.inner_xml)
873
872
  enclosure.categories.last.scheme =
874
- FeedTools.unescape_entities(category.attributes["scheme"].to_s)
873
+ FeedTools.unescape_entities(
874
+ category.attributes["scheme"].to_s)
875
875
  enclosure.categories.last.label =
876
- FeedTools.unescape_entities(category.attributes["label"].to_s)
877
- if enclosure.categories.last.scheme == ""
876
+ FeedTools.unescape_entities(
877
+ category.attributes["label"].to_s)
878
+ if enclosure.categories.last.scheme.blank?
878
879
  enclosure.categories.last.scheme = nil
879
880
  end
880
- if enclosure.categories.last.label == ""
881
+ if enclosure.categories.last.label.blank?
881
882
  enclosure.categories.last.label = nil
882
883
  end
883
884
  end
884
- if XPath.first(enclosure_node, "media:hash/text()").to_s != ""
885
+ enclosure_media_hash = try_xpaths(enclosure_node,
886
+ ["media:hash/text()"], :select_result_value => true)
887
+ if !enclosure_media_hash.nil?
885
888
  enclosure.hash = EnclosureHash.new(
886
- FeedTools.sanitize_html(FeedTools.unescape_entities(XPath.first(
887
- enclosure_node, "media:hash/text()").to_s), :strip),
889
+ FeedTools.sanitize_html(FeedTools.unescape_entities(
890
+ enclosure_media_hash), :strip),
888
891
  "md5"
889
892
  )
890
893
  end
891
- if XPath.first(enclosure_node, "media:player/@url").to_s != ""
894
+ enclosure_media_player_url = try_xpaths(enclosure_node,
895
+ ["media:player/@url"], :select_result_value => true)
896
+ if !enclosure_media_player_url.blank?
892
897
  enclosure.player = EnclosurePlayer.new(
893
- FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@url").to_s),
894
- FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@height").to_s),
895
- FeedTools.unescape_entities(XPath.first(enclosure_node, "media:player/@width").to_s)
898
+ FeedTools.unescape_entities(enclosure_media_player_url),
899
+ FeedTools.unescape_entities(
900
+ try_xpaths(enclosure_node,
901
+ ["media:player/@height"], :select_result_value => true)),
902
+ FeedTools.unescape_entities(
903
+ try_xpaths(enclosure_node,
904
+ ["media:player/@width"], :select_result_value => true))
896
905
  )
897
- if enclosure.player.height == ""
898
- enclosure.player.height = nil
899
- end
900
- if enclosure.player.width == ""
901
- enclosure.player.width = nil
902
- end
903
906
  end
904
907
  enclosure.credits = []
905
- for credit in XPath.match(enclosure_node, "media:credit")
908
+ for credit in try_xpaths_all(enclosure_node, ["media:credit"])
906
909
  enclosure.credits << EnclosureCredit.new(
907
- FeedTools.unescape_entities(credit.text),
908
- FeedTools.unescape_entities(credit.attributes["role"].to_s.downcase)
910
+ FeedTools.unescape_entities(credit.inner_xml.to_s.strip),
911
+ FeedTools.unescape_entities(
912
+ credit.attributes["role"].to_s.downcase)
909
913
  )
910
- if enclosure.credits.last.role == ""
914
+ if enclosure.credits.last.name.blank?
915
+ enclosure.credits.last.name = nil
916
+ end
917
+ if enclosure.credits.last.role.blank?
911
918
  enclosure.credits.last.role = nil
912
919
  end
913
920
  end
914
- enclosure.explicit = (XPath.first(enclosure_node,
915
- "media:adult/text()").to_s.downcase == "true")
916
- if XPath.first(enclosure_node, "media:text/text()").to_s != ""
917
- enclosure.text = FeedTools.unescape_entities(XPath.first(enclosure_node,
918
- "media:text/text()").to_s)
921
+ enclosure.explicit = (try_xpaths(enclosure_node,
922
+ ["media:adult/text()"]).to_s.downcase == "true")
923
+ enclosure_media_text =
924
+ try_xpaths(enclosure_node, ["media:text/text()"])
925
+ if !enclosure_media_text.blank?
926
+ enclosure.text = FeedTools.unescape_entities(
927
+ enclosure_media_text)
919
928
  end
920
929
  affected_enclosures << enclosure
921
930
  if new_enclosure
@@ -933,7 +942,7 @@ module FeedTools
933
942
  # Parse the group objects.
934
943
  for media_group in media_group_enclosures
935
944
  group_media_content_enclosures =
936
- XPath.match(media_group, "media:content")
945
+ try_xpaths_all(media_group, ["media:content"])
937
946
 
938
947
  # Parse the content objects within the group objects.
939
948
  affected_enclosures =
@@ -942,82 +951,87 @@ module FeedTools
942
951
  # Now make sure that content objects inherit certain properties from
943
952
  # the group objects.
944
953
  for enclosure in affected_enclosures
945
- if enclosure.thumbnail.nil? &&
946
- XPath.first(media_group, "media:thumbnail/@url").to_s != ""
954
+ media_group_thumbnail = try_xpaths(media_group,
955
+ ["media:thumbnail/@url"], :select_result_value => true)
956
+ if enclosure.thumbnail.nil? && !media_group_thumbnail.blank?
947
957
  enclosure.thumbnail = EnclosureThumbnail.new(
948
958
  FeedTools.unescape_entities(
949
- XPath.first(media_group, "media:thumbnail/@url").to_s),
959
+ media_group_thumbnail),
950
960
  FeedTools.unescape_entities(
951
- XPath.first(media_group, "media:thumbnail/@height").to_s),
961
+ try_xpaths(media_group, ["media:thumbnail/@height"],
962
+ :select_result_value => true)),
952
963
  FeedTools.unescape_entities(
953
- XPath.first(media_group, "media:thumbnail/@width").to_s)
964
+ try_xpaths(media_group, ["media:thumbnail/@width"],
965
+ :select_result_value => true))
954
966
  )
955
- if enclosure.thumbnail.height == ""
956
- enclosure.thumbnail.height = nil
957
- end
958
- if enclosure.thumbnail.width == ""
959
- enclosure.thumbnail.width = nil
960
- end
961
967
  end
962
- if (enclosure.categories.nil? || enclosure.categories.size == 0)
968
+ if (enclosure.categories.blank?)
963
969
  enclosure.categories = []
964
- for category in XPath.match(media_group, "media:category")
970
+ for category in try_xpaths_all(media_group, ["media:category"])
965
971
  enclosure.categories << FeedTools::Feed::Category.new
966
972
  enclosure.categories.last.term =
967
- FeedTools.unescape_entities(category.text)
973
+ FeedTools.unescape_entities(category.inner_xml)
968
974
  enclosure.categories.last.scheme =
969
- FeedTools.unescape_entities(category.attributes["scheme"].to_s)
975
+ FeedTools.unescape_entities(
976
+ category.attributes["scheme"].to_s)
970
977
  enclosure.categories.last.label =
971
- FeedTools.unescape_entities(category.attributes["label"].to_s)
972
- if enclosure.categories.last.scheme == ""
978
+ FeedTools.unescape_entities(
979
+ category.attributes["label"].to_s)
980
+ if enclosure.categories.last.scheme.blank?
973
981
  enclosure.categories.last.scheme = nil
974
982
  end
975
- if enclosure.categories.last.label == ""
983
+ if enclosure.categories.last.label.blank?
976
984
  enclosure.categories.last.label = nil
977
985
  end
978
986
  end
979
987
  end
980
- if enclosure.hash.nil? &&
981
- XPath.first(media_group, "media:hash/text()").to_s != ""
988
+ enclosure_media_group_hash = try_xpaths(enclosure_node,
989
+ ["media:hash/text()"], :select_result_value => true)
990
+ if enclosure.hash.nil? && !enclosure_media_group_hash.blank?
982
991
  enclosure.hash = EnclosureHash.new(
983
- FeedTools.unescape_entities(XPath.first(media_group, "media:hash/text()").to_s),
992
+ FeedTools.sanitize_html(FeedTools.unescape_entities(
993
+ enclosure_media_group_hash), :strip),
984
994
  "md5"
985
995
  )
986
996
  end
987
- if enclosure.player.nil? &&
988
- XPath.first(media_group, "media:player/@url").to_s != ""
997
+ enclosure_media_group_url = try_xpaths(media_group,
998
+ "media:player/@url", :select_result_value => true)
999
+ if enclosure.player.nil? && !enclosure_media_group_url.blank?
989
1000
  enclosure.player = EnclosurePlayer.new(
990
- FeedTools.unescape_entities(XPath.first(media_group, "media:player/@url").to_s),
991
- FeedTools.unescape_entities(XPath.first(media_group, "media:player/@height").to_s),
992
- FeedTools.unescape_entities(XPath.first(media_group, "media:player/@width").to_s)
1001
+ FeedTools.unescape_entities(enclosure_media_group_url),
1002
+ FeedTools.unescape_entities(
1003
+ try_xpaths(media_group, ["media:player/@height"],
1004
+ :select_result_value => true)),
1005
+ FeedTools.unescape_entities(
1006
+ try_xpaths(media_group, ["media:player/@width"],
1007
+ :select_result_value => true))
993
1008
  )
994
- if enclosure.player.height == ""
995
- enclosure.player.height = nil
996
- end
997
- if enclosure.player.width == ""
998
- enclosure.player.width = nil
999
- end
1000
1009
  end
1001
1010
  if enclosure.credits.nil? || enclosure.credits.size == 0
1002
1011
  enclosure.credits = []
1003
- for credit in XPath.match(media_group, "media:credit")
1012
+ for credit in try_xpaths_all(media_group, ["media:credit"])
1004
1013
  enclosure.credits << EnclosureCredit.new(
1005
- FeedTools.unescape_entities(credit.text),
1006
- FeedTools.unescape_entities(credit.attributes["role"].to_s.downcase)
1014
+ FeedTools.unescape_entities(credit.inner_xml),
1015
+ FeedTools.unescape_entities(
1016
+ credit.attributes["role"].to_s.downcase)
1007
1017
  )
1008
- if enclosure.credits.last.role == ""
1018
+ if enclosure.credits.last.role.blank?
1009
1019
  enclosure.credits.last.role = nil
1010
1020
  end
1011
1021
  end
1012
1022
  end
1013
1023
  if enclosure.explicit?.nil?
1014
- enclosure.explicit = (XPath.first(media_group,
1015
- "media:adult/text()").to_s.downcase == "true") ? true : false
1024
+ enclosure.explicit = ((try_xpaths(media_group, [
1025
+ "media:adult/text()"
1026
+ ], :select_result_value => true).downcase == "true") ?
1027
+ true : false)
1016
1028
  end
1017
- if enclosure.text.nil? &&
1018
- XPath.first(media_group, "media:text/text()").to_s != ""
1019
- enclosure.text = FeedTools.sanitize_html(FeedTools.unescape_entities(
1020
- XPath.first(media_group, "media:text/text()").to_s), :strip)
1029
+ enclosure_media_group_text = try_xpaths(media_group,
1030
+ ["media:text/text()"], :select_result_value => true)
1031
+ if enclosure.text.nil? && !enclosure_media_group_text.blank?
1032
+ enclosure.text = FeedTools.sanitize_html(
1033
+ FeedTools.unescape_entities(
1034
+ enclosure_media_group_text), :strip)
1021
1035
  end
1022
1036
  end
1023
1037
 
@@ -1033,10 +1047,14 @@ module FeedTools
1033
1047
  end
1034
1048
 
1035
1049
  # Add all the itunes categories
1036
- for itunes_category in XPath.match(root_node, "itunes:category")
1050
+ itunes_categories =
1051
+ try_xpaths_all(self.root_node, ["itunes:category"])
1052
+ for itunes_category in itunes_categories
1037
1053
  genre = "Podcasts"
1038
1054
  category = itunes_category.attributes["text"].to_s
1039
- subcategory = XPath.first(itunes_category, "itunes:category/@text").to_s
1055
+ subcategory =
1056
+ try_xpaths(itunes_category, ["itunes:category/@text"],
1057
+ :select_result_value => true)
1040
1058
  category_path = genre
1041
1059
  if category != ""
1042
1060
  category_path << "/" + category
@@ -1061,7 +1079,7 @@ module FeedTools
1061
1079
  for enclosure in @enclosures
1062
1080
  # Clean up any of those attributes that incorrectly have ""
1063
1081
  # or 0 as their values
1064
- if enclosure.type == ""
1082
+ if enclosure.type.blank?
1065
1083
  enclosure.type = nil
1066
1084
  end
1067
1085
  if enclosure.file_size == 0
@@ -1082,12 +1100,13 @@ module FeedTools
1082
1100
  if enclosure.framerate == 0
1083
1101
  enclosure.framerate = nil
1084
1102
  end
1085
- if enclosure.expression == "" || enclosure.expression.nil?
1103
+ if enclosure.expression.blank?
1086
1104
  enclosure.expression = "full"
1087
1105
  end
1088
1106
 
1089
- # If an enclosure is missing the text field, fall back on the itunes:summary field
1090
- if enclosure.text.nil? || enclosure.text = ""
1107
+ # If an enclosure is missing the text field, fall back on the
1108
+ # itunes:summary field
1109
+ if enclosure.text.blank?
1091
1110
  enclosure.text = self.itunes_summary
1092
1111
  end
1093
1112
 
@@ -1128,8 +1147,8 @@ module FeedTools
1128
1147
  end
1129
1148
  end
1130
1149
 
1131
- # If we have a single enclosure, it's safe to inherit the itunes:duration field
1132
- # if it's missing.
1150
+ # If we have a single enclosure, it's safe to inherit the
1151
+ # itunes:duration field if it's missing.
1133
1152
  if @enclosures.size == 1
1134
1153
  if @enclosures.first.duration.nil? || @enclosures.first.duration == 0
1135
1154
  @enclosures.first.duration = self.itunes_duration
@@ -1147,48 +1166,29 @@ module FeedTools
1147
1166
  def author
1148
1167
  if @author.nil?
1149
1168
  @author = FeedTools::Feed::Author.new
1150
- unless root_node.nil?
1151
- author_node = XPath.first(root_node, "atom10:author",
1152
- FEED_TOOLS_NAMESPACES)
1153
- if author_node.nil?
1154
- author_node = XPath.first(root_node, "atom03:author",
1155
- FEED_TOOLS_NAMESPACES)
1156
- end
1157
- if author_node.nil?
1158
- author_node = XPath.first(root_node, "atom:author")
1159
- end
1160
- if author_node.nil?
1161
- author_node = XPath.first(root_node, "author")
1162
- end
1163
- if author_node.nil?
1164
- author_node = XPath.first(root_node, "managingEditor")
1165
- end
1166
- if author_node.nil?
1167
- author_node = XPath.first(root_node, "dc:author",
1168
- FEED_TOOLS_NAMESPACES)
1169
- end
1170
- if author_node.nil?
1171
- author_node = XPath.first(root_node, "dc:author")
1172
- end
1173
- if author_node.nil?
1174
- author_node = XPath.first(root_node, "dc:creator",
1175
- FEED_TOOLS_NAMESPACES)
1176
- end
1177
- if author_node.nil?
1178
- author_node = XPath.first(root_node, "dc:creator")
1179
- end
1180
- end
1169
+ author_node = try_xpaths(self.root_node, [
1170
+ "atom10:author",
1171
+ "atom03:author",
1172
+ "atom:author",
1173
+ "author",
1174
+ "managingEditor",
1175
+ "dc:author",
1176
+ "dc:creator",
1177
+ "creator"
1178
+ ])
1181
1179
  unless author_node.nil?
1182
1180
  @author.raw = FeedTools.unescape_entities(
1183
- XPath.first(author_node, "text()").to_s)
1184
- @author.raw = nil if @author.raw == ""
1181
+ XPath.first(author_node, "text()").to_s).strip
1182
+ @author.raw = nil if @author.raw.blank?
1185
1183
  unless @author.raw.nil?
1186
1184
  raw_scan = @author.raw.scan(
1187
1185
  /(.*)\((\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\)/i)
1188
1186
  if raw_scan.nil? || raw_scan.size == 0
1189
1187
  raw_scan = @author.raw.scan(
1190
1188
  /(\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\s*\((.*)\)/i)
1191
- author_raw_pair = raw_scan.first.reverse unless raw_scan.size == 0
1189
+ unless raw_scan.size == 0
1190
+ author_raw_pair = raw_scan.first.reverse
1191
+ end
1192
1192
  else
1193
1193
  author_raw_pair = raw_scan.first
1194
1194
  end
@@ -1205,42 +1205,44 @@ module FeedTools
1205
1205
  else
1206
1206
  unless @author.raw.include?("@")
1207
1207
  # We can be reasonably sure we are looking at something
1208
- # that the creator didn't intend to contain an email address if
1209
- # it got through the preceeding regexes and it doesn't
1208
+ # that the creator didn't intend to contain an email address
1209
+ # if it got through the preceeding regexes and it doesn't
1210
1210
  # contain the tell-tale '@' symbol.
1211
1211
  @author.name = @author.raw
1212
1212
  end
1213
1213
  end
1214
1214
  end
1215
- @author.name = "" if @author.name.nil?
1216
- if @author.name == ""
1215
+ if @author.name.blank?
1217
1216
  @author.name = FeedTools.unescape_entities(
1218
- XPath.first(author_node, "name/text()").to_s)
1217
+ try_xpaths(author_node, [
1218
+ "name/text()",
1219
+ "@name"
1220
+ ], :select_result_value => true)
1221
+ )
1219
1222
  end
1220
- if @author.name == ""
1221
- @author.name = FeedTools.unescape_entities(
1222
- XPath.first(author_node, "@name").to_s)
1223
- end
1224
- if @author.email == ""
1225
- @author.email = FeedTools.unescape_entities(
1226
- XPath.first(author_node, "email/text()").to_s)
1227
- end
1228
- if @author.email == ""
1223
+ if @author.email.blank?
1229
1224
  @author.email = FeedTools.unescape_entities(
1230
- XPath.first(author_node, "@email").to_s)
1231
- end
1232
- if @author.url == ""
1233
- @author.url = FeedTools.unescape_entities(
1234
- XPath.first(author_node, "url/text()").to_s)
1225
+ try_xpaths(author_node, [
1226
+ "email/text()",
1227
+ "@email"
1228
+ ], :select_result_value => true)
1229
+ )
1235
1230
  end
1236
- if @author.url == ""
1231
+ if @author.url.blank?
1237
1232
  @author.url = FeedTools.unescape_entities(
1238
- XPath.first(author_node, "@url").to_s)
1239
- end
1240
- @author.name = nil if @author.name == ""
1241
- @author.raw = nil if @author.raw == ""
1242
- @author.email = nil if @author.email == ""
1243
- @author.url = nil if @author.url == ""
1233
+ try_xpaths(author_node, [
1234
+ "url/text()",
1235
+ "uri/text()",
1236
+ "@url",
1237
+ "@uri",
1238
+ "@href"
1239
+ ], :select_result_value => true)
1240
+ )
1241
+ end
1242
+ @author.name = nil if @author.name.blank?
1243
+ @author.raw = nil if @author.raw.blank?
1244
+ @author.email = nil if @author.email.blank?
1245
+ @author.url = nil if @author.url.blank?
1244
1246
  end
1245
1247
  # Fallback on the itunes module if we didn't find an author name
1246
1248
  begin
@@ -1276,12 +1278,11 @@ module FeedTools
1276
1278
 
1277
1279
  # Set the author name
1278
1280
  @publisher.raw = FeedTools.unescape_entities(
1279
- XPath.first(root_node, "dc:publisher/text()").to_s)
1280
- if @publisher.raw == ""
1281
- @publisher.raw = FeedTools.unescape_entities(
1282
- XPath.first(root_node, "webMaster/text()").to_s)
1283
- end
1284
- unless @publisher.raw == ""
1281
+ try_xpaths(self.root_node, [
1282
+ "dc:publisher/text()",
1283
+ "webMaster/text()"
1284
+ ], :select_result_value => true))
1285
+ unless @publisher.raw.blank?
1285
1286
  raw_scan = @publisher.raw.scan(
1286
1287
  /(.*)\((\b[A-Z0-9._%-\+]+@[A-Z0-9._%-]+\.[A-Z]{2,4}\b)\)/i)
1287
1288
  if raw_scan.nil? || raw_scan.size == 0
@@ -1314,10 +1315,10 @@ module FeedTools
1314
1315
  end
1315
1316
  end
1316
1317
 
1317
- @publisher.name = nil if @publisher.name == ""
1318
- @publisher.raw = nil if @publisher.raw == ""
1319
- @publisher.email = nil if @publisher.email == ""
1320
- @publisher.url = nil if @publisher.url == ""
1318
+ @publisher.name = nil if @publisher.name.blank?
1319
+ @publisher.raw = nil if @publisher.raw.blank?
1320
+ @publisher.email = nil if @publisher.email.blank?
1321
+ @publisher.url = nil if @publisher.url.blank?
1321
1322
  end
1322
1323
  return @publisher
1323
1324
  end
@@ -1345,10 +1346,10 @@ module FeedTools
1345
1346
  # elements. They're actually amazingly common. People don't read specs.
1346
1347
  def itunes_author
1347
1348
  if @itunes_author.nil?
1348
- @itunes_author = FeedTools.unescape_entities(XPath.first(root_node,
1349
- "itunes:author/text()").to_s)
1350
- @itunes_author = feed.itunes_author if @itunes_author == ""
1351
- @itunes_author = nil if @itunes_author == ""
1349
+ @itunes_author = FeedTools.unescape_entities(
1350
+ try_xpaths(self.root_node,
1351
+ ["itunes:author/text()"], :select_result_value => true))
1352
+ @itunes_author = feed.itunes_author if @itunes_author.blank?
1352
1353
  end
1353
1354
  return @itunes_author
1354
1355
  end
@@ -1361,14 +1362,15 @@ module FeedTools
1361
1362
  # Returns the number of seconds that the associated media runs for
1362
1363
  def itunes_duration
1363
1364
  if @itunes_duration.nil?
1364
- raw_duration = FeedTools.unescape_entities(XPath.first(root_node,
1365
- "itunes:duration/text()").to_s)
1366
- if raw_duration != ""
1365
+ raw_duration = FeedTools.unescape_entities(
1366
+ try_xpaths(self.root_node,
1367
+ ["itunes:duration/text()"], :select_result_value => true))
1368
+ if !raw_duration.blank?
1367
1369
  hms = raw_duration.split(":").map { |x| x.to_i }
1368
1370
  if hms.size == 3
1369
- @itunes_duration = hms[0].hour + hms[1].minute + hms[2]
1371
+ @itunes_duration = hms[0].hours + hms[1].minutes + hms[2]
1370
1372
  elsif hms.size == 2
1371
- @itunes_duration = hms[0].minute + hms[1]
1373
+ @itunes_duration = hms[0].minutes + hms[1]
1372
1374
  elsif hms.size == 1
1373
1375
  @itunes_duration = hms[0]
1374
1376
  end
@@ -1388,39 +1390,51 @@ module FeedTools
1388
1390
  options.keys)
1389
1391
  options = { :estimate_timestamp => true }.merge(options)
1390
1392
  if @time.nil?
1391
- unless root_node.nil?
1392
- time_string = XPath.first(root_node, "pubDate/text()").to_s
1393
- if time_string == ""
1394
- time_string = XPath.first(root_node, "dc:date/text()").to_s
1395
- end
1396
- if time_string == ""
1397
- time_string = XPath.first(root_node, "issued/text()").to_s
1398
- end
1399
- if time_string == ""
1400
- time_string = XPath.first(root_node, "updated/text()").to_s
1401
- end
1402
- if time_string == ""
1403
- time_string = XPath.first(root_node, "time/text()").to_s
1404
- end
1405
- end
1393
+ time_string = try_xpaths(self.root_node, [
1394
+ "atom10:updated/text()",
1395
+ "atom03:updated/text()",
1396
+ "atom:updated/text()",
1397
+ "updated/text()",
1398
+ "atom10:modified/text()",
1399
+ "atom03:modified/text()",
1400
+ "atom:modified/text()",
1401
+ "modified/text()",
1402
+ "time/text()",
1403
+ "atom10:issued/text()",
1404
+ "atom03:issued/text()",
1405
+ "atom:issued/text()",
1406
+ "issued/text()",
1407
+ "atom10:published/text()",
1408
+ "atom03:published/text()",
1409
+ "atom:published/text()",
1410
+ "published/text()",
1411
+ "pubDate/text()",
1412
+ "dc:date/text()",
1413
+ "date/text()"
1414
+ ], :select_result_value => true)
1406
1415
  begin
1407
- time_string = "" if time_string.nil?
1408
- if time_string != ""
1416
+ if !time_string.blank?
1409
1417
  @time = Time.parse(time_string).gmtime
1418
+ elsif FeedTools.configurations[:timestamp_estimation_enabled] &&
1419
+ !self.title.nil? &&
1420
+ (Time.parse(self.title) - Time.now).abs > 100
1421
+ @time = Time.parse(self.title).gmtime
1410
1422
  end
1411
1423
  rescue
1412
1424
  end
1413
- if options[:estimate_timestamp]
1414
- if @time.nil?
1415
- begin
1416
- @time = succ_time
1425
+ if FeedTools.configurations[:timestamp_estimation_enabled]
1426
+ if options[:estimate_timestamp]
1427
+ if @time.nil?
1428
+ begin
1429
+ @time = succ_time
1430
+ if @time.nil?
1431
+ @time = prev_time
1432
+ end
1433
+ rescue
1434
+ end
1417
1435
  if @time.nil?
1418
- @time = prev_time
1436
+ @time = Time.now.gmtime
1419
1437
  end
1420
- rescue
1421
- end
1422
- if @time.nil?
1423
- @time = Time.now.gmtime
1424
1438
  end
1425
1439
  end
1426
1440
  end
@@ -1440,10 +1454,10 @@ module FeedTools
1440
1454
  if parent_feed.nil?
1441
1455
  return nil
1442
1456
  end
1443
- if parent_feed.instance_variable_get("@items").nil?
1457
+ if parent_feed.instance_variable_get("@entries").nil?
1444
1458
  parent_feed.items
1445
1459
  end
1446
- unsorted_items = parent_feed.instance_variable_get("@items")
1460
+ unsorted_items = parent_feed.instance_variable_get("@entries")
1447
1461
  item_index = unsorted_items.index(self)
1448
1462
  if item_index.nil?
1449
1463
  return nil
@@ -1457,7 +1471,7 @@ module FeedTools
1457
1471
  return nil
1458
1472
  end
1459
1473
  end
1460
- #private :succ_time
1474
+ private :succ_time
1461
1475
 
1462
1476
  # Returns 1 second before the succeeding item's time.
1463
1477
  def prev_time #:nodoc:
@@ -1466,10 +1480,10 @@ module FeedTools
1466
1480
  if parent_feed.nil?
1467
1481
  return nil
1468
1482
  end
1469
- if parent_feed.instance_variable_get("@items").nil?
1483
+ if parent_feed.instance_variable_get("@entries").nil?
1470
1484
  parent_feed.items
1471
1485
  end
1472
- unsorted_items = parent_feed.instance_variable_get("@items")
1486
+ unsorted_items = parent_feed.instance_variable_get("@entries")
1473
1487
  item_index = unsorted_items.index(self)
1474
1488
  if item_index.nil?
1475
1489
  return nil
@@ -1483,18 +1497,22 @@ module FeedTools
1483
1497
  return nil
1484
1498
  end
1485
1499
  end
1486
- #private :prev_time
1487
-
1500
+ private :prev_time
1501
+
1488
1502
  # Returns the feed item updated time
1489
1503
  def updated
1490
1504
  if @updated.nil?
1491
- unless root_node.nil?
1492
- updated_string = XPath.first(root_node, "updated/text()").to_s
1493
- if updated_string == ""
1494
- updated_string = XPath.first(root_node, "modified/text()").to_s
1495
- end
1496
- end
1497
- if updated_string != nil && updated_string != ""
1505
+ updated_string = try_xpaths(self.root_node, [
1506
+ "atom10:updated/text()",
1507
+ "atom03:updated/text()",
1508
+ "atom:updated/text()",
1509
+ "updated/text()",
1510
+ "atom10:modified/text()",
1511
+ "atom03:modified/text()",
1512
+ "atom:modified/text()",
1513
+ "modified/text()"
1514
+ ], :select_result_value => true)
1515
+ if !updated_string.blank?
1498
1516
  @updated = Time.parse(updated_string).gmtime rescue nil
1499
1517
  else
1500
1518
  @updated = nil
@@ -1508,23 +1526,24 @@ module FeedTools
1508
1526
  @updated = new_updated
1509
1527
  end
1510
1528
 
1511
- # Returns the feed item issued time
1512
- def issued
1513
- if @issued.nil?
1514
- unless root_node.nil?
1515
- issued_string = XPath.first(root_node, "issued/text()").to_s
1516
- if issued_string == ""
1517
- issued_string = XPath.first(root_node, "published/text()").to_s
1518
- end
1519
- if issued_string == ""
1520
- issued_string = XPath.first(root_node, "pubDate/text()").to_s
1521
- end
1522
- if issued_string == ""
1523
- issued_string = XPath.first(root_node, "dc:date/text()").to_s
1524
- end
1525
- end
1526
- if issued_string != nil && issued_string != ""
1527
- @issued = Time.parse(issued_string).gmtime rescue nil
1529
+ # Returns the feed item published time
1530
+ def published
1531
+ if @published.nil?
1532
+ published_string = try_xpaths(self.root_node, [
1533
+ "atom10:issued/text()",
1534
+ "atom03:issued/text()",
1535
+ "atom:issued/text()",
1536
+ "issued/text()",
1537
+ "atom10:published/text()",
1538
+ "atom03:published/text()",
1539
+ "atom:published/text()",
1540
+ "published/text()",
1541
+ "pubDate/text()",
1542
+ "dc:date/text()",
1543
+ "date/text()"
1544
+ ], :select_result_value => true)
1545
+ if !published_string.blank?
1546
+ @issued = Time.parse(published_string).gmtime rescue nil
1528
1547
  else
1529
1548
  @issued = nil
1530
1549
  end
@@ -1532,17 +1551,19 @@ module FeedTools
1532
1551
  return @issued
1533
1552
  end
1534
1553
 
1535
- # Sets the feed item issued time
1536
- def issued=(new_issued)
1537
- @issued = new_issued
1554
+ # Sets the feed item published time
1555
+ def published=(new_published)
1556
+ @published = new_published
1538
1557
  end
1539
1558
 
1540
1559
  # Returns the url for posting comments
1541
1560
  def comments
1542
1561
  if @comments.nil?
1543
- @comments = FeedTools.normalize_url(
1544
- XPath.first(root_node, "comments/text()").to_s)
1545
- @comments = nil if @comments == ""
1562
+ @comments = try_xpaths(self.root_node, ["comments/text()"],
1563
+ :select_result_value => true)
1564
+ if FeedTools.configurations[:url_normalization_enabled]
1565
+ @comments = FeedTools.normalize_url(@comments)
1566
+ end
1546
1567
  end
1547
1568
  return @comments
1548
1569
  end
@@ -1556,10 +1577,10 @@ module FeedTools
1556
1577
  def source
1557
1578
  if @source.nil?
1558
1579
  @source = FeedTools::Feed::Link.new
1559
- @source.url = XPath.first(root_node, "source/@url").to_s
1560
- @source.url = nil if @source.url == ""
1561
- @source.value = XPath.first(root_node, "source/text()").to_s
1562
- @source.value = nil if @source.value == ""
1580
+ @source.url = try_xpaths(self.root_node, ["source/@url"],
1581
+ :select_result_value => true)
1582
+ @source.value = try_xpaths(self.root_node, ["source/text()"],
1583
+ :select_result_value => true)
1563
1584
  end
1564
1585
  return @source
1565
1586
  end
@@ -1575,26 +1596,26 @@ module FeedTools
1575
1596
  end
1576
1597
  if @tags.nil? || @tags.size == 0
1577
1598
  @tags = []
1578
- tag_list = XPath.match(root_node, "dc:subject/rdf:Bag/rdf:li/text()")
1579
- if tag_list.nil? || tag_list.size == 0
1580
- tag_list = XPath.match(root_node,
1581
- "dc:subject/rdf:Bag/rdf:li/text()", FEED_TOOLS_NAMESPACES)
1582
- end
1583
- if tag_list != nil && tag_list.size > 1
1599
+ tag_list = try_xpaths_all(self.root_node,
1600
+ ["dc:subject/rdf:Bag/rdf:li/text()"],
1601
+ :select_result_value => true)
1602
+ if tag_list != nil && tag_list.size > 0
1584
1603
  for tag in tag_list
1585
- @tags << tag.to_s.downcase.strip
1604
+ @tags << tag.downcase.strip
1586
1605
  end
1587
1606
  end
1588
1607
  end
1589
1608
  if @tags.nil? || @tags.size == 0
1590
1609
  # messy effort to find ourselves some tags, mainly for del.icio.us
1591
1610
  @tags = []
1592
- rdf_bag = XPath.match(root_node, "taxo:topics/rdf:Bag/rdf:li")
1611
+ rdf_bag = try_xpaths_all(self.root_node,
1612
+ ["taxo:topics/rdf:Bag/rdf:li"])
1593
1613
  if rdf_bag != nil && rdf_bag.size > 0
1594
1614
  for tag_node in rdf_bag
1595
1615
  begin
1596
- tag_url = XPath.first(root_node, "@resource").to_s
1597
- tag_match = tag_url.scan(/\/(tag|tags)\/(\w+)/)
1616
+ tag_url = try_xpaths(tag_node, ["@resource"],
1617
+ :select_result_value => true)
1618
+ tag_match = tag_url.scan(/\/(tag|tags)\/(\w+)$/)
1598
1619
  if tag_match.size > 0
1599
1620
  @tags << tag_match.first.last.downcase.strip
1600
1621
  end
@@ -1605,21 +1626,36 @@ module FeedTools
1605
1626
  end
1606
1627
  if @tags.nil? || @tags.size == 0
1607
1628
  @tags = []
1608
- tag_list = XPath.match(root_node, "category/text()")
1629
+ tag_list = try_xpaths_all(self.root_node, ["category/text()"],
1630
+ :select_result_value => true)
1609
1631
  for tag in tag_list
1610
1632
  @tags << tag.to_s.downcase.strip
1611
1633
  end
1612
1634
  end
1613
1635
  if @tags.nil? || @tags.size == 0
1614
1636
  @tags = []
1615
- tag_list = XPath.match(root_node, "dc:subject/text()")
1637
+ tag_list = try_xpaths_all(self.root_node, ["dc:subject/text()"],
1638
+ :select_result_value => true)
1616
1639
  for tag in tag_list
1617
1640
  @tags << tag.to_s.downcase.strip
1618
1641
  end
1619
1642
  end
1620
- if @tags.nil? || @tags.size == 0
1643
+ if @tags.blank?
1621
1644
  begin
1622
- @tags = XPath.first(root_node, "itunes:keywords/text()").to_s.downcase.split(" ")
1645
+ itunes_keywords_string = try_xpaths(self.root_node, [
1646
+ "itunes:keywords/text()"
1647
+ ], :select_result_value => true)
1648
+ unless itunes_keywords_string.blank?
1649
+ @tags = itunes_keywords_string.downcase.split(",")
1650
+ if @tags.size == 1
1651
+ @tags = itunes_keywords_string.downcase.split(" ")
1652
+ @tags = @tags.map { |tag| tag.chomp(",") }
1653
+ end
1654
+ if @tags.size == 1
1655
+ @tags = itunes_keywords_string.downcase.split(",")
1656
+ end
1657
+ @tags = @tags.map { |tag| tag.strip }
1658
+ end
1623
1659
  rescue
1624
1660
  @tags = []
1625
1661
  end
@@ -1642,12 +1678,11 @@ module FeedTools
1642
1678
  # isn't explicitly marked as explicit.
1643
1679
  def explicit?
1644
1680
  if @explicit.nil?
1645
- if XPath.first(root_node,
1646
- "media:adult/text()").to_s.downcase == "true" ||
1647
- XPath.first(root_node,
1648
- "itunes:explicit/text()").to_s.downcase == "yes" ||
1649
- XPath.first(root_node,
1650
- "itunes:explicit/text()").to_s.downcase == "true" ||
1681
+ explicit_string = try_xpaths(self.root_node, [
1682
+ "media:adult/text()",
1683
+ "itunes:explicit/text()"
1684
+ ], :select_result_value => true)
1685
+ if explicit_string == "true" || explicit_string == "yes" ||
1651
1686
  feed.explicit?
1652
1687
  @explicit = true
1653
1688
  else
@@ -1669,8 +1704,9 @@ module FeedTools
1669
1704
  end
1670
1705
 
1671
1706
  # Generates xml based on the content of the feed item
1672
- def build_xml(feed_type=(self.feed.feed_type or "rss"), version=nil,
1673
- xml_builder=Builder::XmlMarkup.new(:indent => 2))
1707
+ def build_xml(feed_type=(self.feed.feed_type or "atom"), version=nil,
1708
+ xml_builder=Builder::XmlMarkup.new(
1709
+ :indent => 2, :escape_attrs => false))
1674
1710
  if feed_type == "rss" && (version == nil || version == 0.0)
1675
1711
  version = 1.0
1676
1712
  elsif feed_type == "atom" && (version == nil || version == 0.0)
@@ -1681,7 +1717,8 @@ module FeedTools
1681
1717
  if link.nil?
1682
1718
  raise "Cannot generate an rdf-based feed item with a nil link field."
1683
1719
  end
1684
- return xml_builder.item("rdf:about" => CGI.escapeHTML(link)) do
1720
+ return xml_builder.item("rdf:about" =>
1721
+ FeedTools.escape_entities(link)) do
1685
1722
  unless title.nil? || title == ""
1686
1723
  xml_builder.title(title)
1687
1724
  else
@@ -1708,24 +1745,37 @@ module FeedTools
1708
1745
  end
1709
1746
  end
1710
1747
  end
1711
- xml_builder.tag!("itunes:keywords", tags.join(" "))
1748
+ if self.feed.podcast?
1749
+ xml_builder.tag!("itunes:keywords", tags.join(", "))
1750
+ end
1712
1751
  end
1713
1752
  build_xml_hook(feed_type, version, xml_builder)
1714
1753
  end
1715
1754
  elsif feed_type == "rss"
1716
1755
  # normal rss format
1717
1756
  return xml_builder.item do
1718
- unless title.nil? || title == ""
1719
- xml_builder.title(title)
1757
+ unless self.title.blank?
1758
+ xml_builder.title(self.title)
1720
1759
  end
1721
- unless link.nil? || link == ""
1760
+ unless self.link.blank?
1722
1761
  xml_builder.link(link)
1723
1762
  end
1724
- unless description.nil? || description == ""
1725
- xml_builder.description(description)
1763
+ unless self.description.blank?
1764
+ xml_builder.description(self.description)
1726
1765
  end
1727
- unless time.nil?
1728
- xml_builder.pubDate(time.rfc822)
1766
+ unless self.time.nil?
1767
+ xml_builder.pubDate(self.time.rfc822)
1768
+ end
1769
+ unless self.guid.blank?
1770
+ if FeedTools.is_uri?(self.guid)
1771
+ xml_builder.guid(self.guid, "isPermaLink" => "true")
1772
+ else
1773
+ xml_builder.guid(self.guid, "isPermaLink" => "false")
1774
+ end
1775
+ else
1776
+ unless self.link.blank?
1777
+ xml_builder.guid(self.link, "isPermaLink" => "true")
1778
+ end
1729
1779
  end
1730
1780
  unless tags.nil? || tags.size == 0
1731
1781
  xml_builder.tag!("taxo:topics") do
@@ -1735,53 +1785,51 @@ module FeedTools
1735
1785
  end
1736
1786
  end
1737
1787
  end
1738
- xml_builder.tag!("itunes:keywords", tags.join(" "))
1739
- end
1740
- build_xml_hook(feed_type, version, xml_builder)
1741
- end
1742
- elsif feed_type == "atom" && version == 0.3
1743
- # normal atom format
1744
- return xml_builder.entry("xmlns" =>
1745
- FEED_TOOLS_NAMESPACES['atom03']) do
1746
- unless title.nil? || title == ""
1747
- xml_builder.title(title,
1748
- "mode" => "escaped",
1749
- "type" => "text/html")
1750
- end
1751
- xml_builder.author do
1752
- unless self.author.nil? || self.author.name.nil?
1753
- xml_builder.name(self.author.name)
1754
- else
1755
- xml_builder.name("n/a")
1756
- end
1757
- unless self.author.nil? || self.author.email.nil?
1758
- xml_builder.email(self.author.email)
1759
- end
1760
- unless self.author.nil? || self.author.url.nil?
1761
- xml_builder.url(self.author.url)
1788
+ if self.feed.podcast?
1789
+ xml_builder.tag!("itunes:keywords", tags.join(", "))
1762
1790
  end
1763
1791
  end
1764
- unless link.nil? || link == ""
1765
- xml_builder.link("href" => link,
1766
- "rel" => "alternate",
1767
- "type" => "text/html",
1768
- "title" => title)
1769
- end
1770
- unless description.nil? || description == ""
1771
- xml_builder.content(description,
1772
- "mode" => "escaped",
1773
- "type" => "text/html")
1774
- end
1775
- unless time.nil?
1776
- xml_builder.issued(time.iso8601)
1777
- end
1778
- unless tags.nil? || tags.size == 0
1779
- for tag in tags
1780
- xml_builder.category(tag)
1792
+ unless self.enclosures.blank? || self.enclosures.size == 0
1793
+ for enclosure in self.enclosures
1794
+ attribute_hash = {}
1795
+ next if enclosure.url.blank?
1796
+ begin
1797
+ if enclosure.file_size.blank? || enclosure.file_size.to_i == 0
1798
+ # We can't use this enclosure because it's missing the
1799
+ # required file size. Check alternate versions for
1800
+ # file_size.
1801
+ if !enclosure.versions.blank? && enclosure.versions.size > 0
1802
+ for alternate in enclosure.versions
1803
+ if alternate.file_size != nil &&
1804
+ alternate.file_size.to_i > 0
1805
+ enclosure = alternate
1806
+ break
1807
+ end
1808
+ end
1809
+ end
1810
+ end
1811
+ rescue
1812
+ end
1813
+ attribute_hash["url"] = FeedTools.normalize_url(enclosure.url)
1814
+ if enclosure.type != nil
1815
+ attribute_hash["type"] = enclosure.type
1816
+ end
1817
+ if enclosure.file_size != nil && enclosure.file_size.to_i > 0
1818
+ attribute_hash["length"] = enclosure.file_size.to_s
1819
+ else
1820
+ # We couldn't find an alternate and the problem is still
1821
+ # there. Give up and go on.
1822
+ xml_builder.comment!(
1823
+ "*** Enclosure failed to include file size. Ignoring. ***")
1824
+ next
1825
+ end
1826
+ xml_builder.enclosure(attribute_hash)
1781
1827
  end
1782
1828
  end
1783
1829
  build_xml_hook(feed_type, version, xml_builder)
1784
1830
  end
1831
+ elsif feed_type == "atom" && version == 0.3
1832
+ raise "Atom 0.3 is obsolete."
1785
1833
  elsif feed_type == "atom" && version == 1.0
1786
1834
  # normal atom format
1787
1835
  return xml_builder.entry("xmlns" =>
@@ -1800,20 +1848,20 @@ module FeedTools
1800
1848
  xml_builder.email(self.author.email)
1801
1849
  end
1802
1850
  unless self.author.nil? || self.author.url.nil?
1803
- xml_builder.url(self.author.url)
1851
+ xml_builder.uri(self.author.url)
1804
1852
  end
1805
1853
  end
1806
1854
  unless link.nil? || link == ""
1807
- xml_builder.link("href" => link,
1855
+ xml_builder.link("href" => FeedTools.escape_entities(self.link),
1808
1856
  "rel" => "alternate",
1809
1857
  "type" => "text/html",
1810
- "title" => title)
1858
+ "title" => FeedTools.escape_entities(title))
1811
1859
  end
1812
- unless description.nil? || description == ""
1860
+ if !description.blank?
1813
1861
  xml_builder.content(description,
1814
1862
  "type" => "html")
1815
- else
1816
- xml_builder.content(FeedTools.no_content_string,
1863
+ elsif !FeedTools.configurations[:no_content_string].blank?
1864
+ xml_builder.content(FeedTools.configurations[:no_content_string],
1817
1865
  "type" => "html")
1818
1866
  end
1819
1867
  if self.updated != nil
@@ -1851,25 +1899,36 @@ module FeedTools
1851
1899
  xml_builder.category("term" => tag)
1852
1900
  end
1853
1901
  end
1902
+ unless self.enclosures.blank? || self.enclosures.size == 0
1903
+ for enclosure in self.enclosures
1904
+ attribute_hash = {}
1905
+ next if enclosure.url.blank?
1906
+ attribute_hash["rel"] = "enclosure"
1907
+ attribute_hash["href"] = FeedTools.normalize_url(enclosure.url)
1908
+ if enclosure.type != nil
1909
+ attribute_hash["type"] = enclosure.type
1910
+ end
1911
+ if enclosure.file_size != nil && enclosure.file_size.to_i > 0
1912
+ attribute_hash["length"] = enclosure.file_size.to_s
1913
+ end
1914
+ xml_builder.link(attribute_hash)
1915
+ end
1916
+ end
1854
1917
  build_xml_hook(feed_type, version, xml_builder)
1855
1918
  end
1919
+ else
1920
+ raise "Unsupported feed format/version."
1856
1921
  end
1857
1922
  end
1858
1923
 
1859
- alias_method :tagline, :description
1860
- alias_method :tagline=, :description=
1861
- alias_method :subtitle, :description
1862
- alias_method :subtitle=, :description=
1863
- alias_method :summary, :description
1864
- alias_method :summary=, :description=
1865
- alias_method :abstract, :description
1866
- alias_method :abstract=, :description=
1867
- alias_method :content, :description
1868
- alias_method :content=, :description=
1924
+ alias_method :summary, :content
1925
+ alias_method :summary=, :content=
1926
+ alias_method :abstract, :content
1927
+ alias_method :abstract=, :content=
1928
+ alias_method :description, :content
1929
+ alias_method :description=, :content=
1869
1930
  alias_method :guid, :id
1870
1931
  alias_method :guid=, :id=
1871
- alias_method :published, :issued
1872
- alias_method :published=, :issued=
1873
1932
 
1874
1933
  # Returns a simple representation of the feed item object's state.
1875
1934
  def inspect