package Mojo::Feed::Item;
use Mojo::Base '-base';
use Mojo::Util qw( trim );

use HTTP::Date 'str2time';

use Mojo::Feed::Item::Enclosure;

use overload
  bool     => sub {1},
  '""'     => sub { shift->to_string },
  fallback => 1;

has [qw(title link content id description guid published author)];

has tags => sub {
  shift->dom->find('category, dc\:subject')
    ->map(sub { $_[0]->text || $_[0]->attr('term') });

has 'dom';
has feed => undef, weak => 1;

has summary => sub { shift->description };

my %selector = (
  content => ['content', 'content|encoded', 'xhtml|body', 'description'],
  description => ['description', 'summary'],
  published   => [
    'published', 'pubDate', 'dc|date', 'created',
    'issued',    'updated', 'modified'
  author => ['author > name', '|author', 'atom|author', 'dc|creator'],
  id     => ['id',     'guid', 'link'],
  title => ['title'],
  link  => ['link'],
  guid  => ['guid'],

foreach my $k (keys %selector) {
  has $k => sub {
    my $self = shift;
    for my $selector (@{$selector{$k}}) {
      if (my $p = $self->dom->at($selector, %{$self->feed->namespaces})) {
        if ($k eq 'author' && $p->at('name')) {
          return trim $p->at('name')->text;
        my ($text) = grep $_, map trim($_), grep $_, $p->text, $p->content;
        $text ||= '';
        if ($k eq 'published') {
          return str2time($text);
        return $text;

has enclosures => sub {
  my $self = shift;
  my @enclosures;
  $self->dom->find('enclosure')->each(sub {
    push @enclosures, $_;
  $self->dom->find('link')->each(sub {
    my $l = shift;
    if ($l->attr('href') && $l->attr('rel') && $l->attr('rel') eq 'enclosure') {
      push @enclosures, $l;
  return Mojo::Collection->new(
    map { Mojo::Feed::Item::Enclosure->new(dom => $_) } @enclosures);

has link => sub {

  # let's handle links seperately, because ATOM loves these buggers:
  my $link;
  shift->dom->find('link')->each(sub {
    my $l = shift;
    if ($l->attr('href')
      && (!$l->attr('rel') || $l->attr('rel') eq 'alternate'))
      $link = trim $l->attr('href');
    else {
      if ($l->text =~ /\w+/) {
        $link = trim $l->text;    # simple link
  return $link;

sub to_string {

sub to_hash {
  my $self = shift;
  my $hash = {map { $_ => '' . ($self->$_ || '') } keys %selector};
  if ($self->enclosures->size) {
    $hash->{'enclosures'} = $self->enclosures->map('to_hash')->to_array;
  if ($self->tags->size) {
    $hash->{'tags'} = $self->tags->to_array;
  return $hash;



=encoding utf-8

=head1 NAME

Mojo::Feed::Item - represents an item from an RSS/Atom feed.


    use Mojo::Feed;

    my $feed = Mojo::Feed->new("atom.xml");

    my $item = $feed->items->first;

    print $item->title, $item->author, $item->published, "\n";


L<Mojo::Feed::Item> is an Object wrapper for a item from an RSS or Atom Feed.


L<Mojo::Feed::Item> implements the following attributes.

=head2  title

=head2  link

=head2  content

May be filled with C<content:encoded>, C<xhtml:body> or C<description> fields

=head2  id

Will be equal to C<link> or C<guid> if it is undefined and either of those fields exists

=head2  description

Optional - usually a shorter form of the content (may be filled with C<summary> if description is missing)

=head2  guid


=head2  published

Time in epoch seconds (may be filled with C<pubDate>, C<dc:date>, C<created>, C<issued>, C<updated> or C<modified>)

=head2  author

May be filled from C<author> or C<dc:creator>

=head2  tags

Optional - array ref of C<tags>, C<categories> or C<dc:subjects>.

=head2  enclosures

Optional - array ref of enclosures, each a hashref with the keys url, type and length.

=head2  feed

A reference to the feed this item belongs to. Note that this is a weak
reference, so it maybe undefined, if the parent feed is no longer in scope.

=head1 METHODS

L<Mojo::Feed::Item> inherits all methods from L<Mojo::Base> and adds the following ones:

=head2 to_hash

  my $hash = $item->to_hash;
  print $hash->{title};

Return a hash reference representing the item.

=head2 to_string

Return a XML serialized text of the item's Mojo::DOM node. Note that this can be different from the original XML text in the feed.

=head1 CREDITS

Dotan Dimet

Mario Domgoergen

Some tests adapted from L<Feed::Find> and L<XML:Feed>, Feed auto-discovery adapted from L<Feed::Find>.


This software is Copyright (c) 2018-2019 by Dotan Dimet E<lt>dotan@corky.netE<gt>.

This library is free software; you can redistribute it and/or modify
it under the terms of the Artistic License version 2.0.

Test data (web pages, feeds and excerpts) included in this package is intended
for testing purposes only, and is not meant in any way to infringe on the
rights of the respective authors.

=head1 AUTHOR

Dotan Dimet E<lt>dotan@corky.netE<gt>