# Opera-specific bookmarks parser and producer

package Bookmarks::Opera;

use strict;
use warnings;
use base 'Bookmarks::Parser';

# Last updated, September 2011,
# Opera Hotlist version 21
my @op_bookmark_fields = (
    "ACTIVE",
    "CREATED",
    "DELETABLE",
    "DESCRIPTION",
    "DISPLAY URL",
    "ID",
    "MOVE_IS_COPY",
    "NAME",
    "ON PERSONALBAR",
    "PARTNERID",
    "PERSONALBAR_POS",
    "SEPARATOR_ALLOWED",
    "SHORT NAME",
    "TARGET",
    "TRASH FOLDER",
    "UNIQUEID",
    "URL",
    "VISITED",
);

my %op_bookmark_fields = map { $_ => qr/\s+$_=(.*)/ } @op_bookmark_fields;

sub _parse_file
{
    my ($self, $filename) = @_;

    return undef if(!-e $filename);

    my $fh;
    my $curitem   = {};
    my $curfolder = {};
    open $fh, "<$filename" or die "Can't open $filename ($!)";

    while (my $line = <$fh>)
    {
#        chomp $line;
        $line =~ s/[\r\n]//g;
        next if($line =~ /^Opera Hotlist version/);
        next if($line =~ /^Options:/);

        if ($line =~ m{^ \s* $}x)
        {
            if($curitem->{start})
            {
                delete $curitem->{start};
                $curitem->{parent} = $curfolder->{id};
                $self->add_bookmark($curitem, $curfolder->{id});
                if($curitem->{type} eq 'folder')
                {
                    $curfolder = $curitem;
                }
                $curitem = {};
            }
        }
        if($line eq '-')
        {
            $curfolder = $self->{_items}{$curfolder->{parent}};
#            ($curfolder) = grep { $_->{id} eq $curfolder->{parent} } 
#                                     @{$self->{_items}};
        }
        if($line =~/^#(FOLDER|URL)/)
        {
            $curitem->{start} = 1;
            $curitem->{type}  = lc($1);
        }
        if($curitem->{start})
        {
            for my $key (keys %op_bookmark_fields) {
                my $re = $op_bookmark_fields{$key};
                if ($line =~ $re) {
                    my $value = $1;
                    my $nicename = lc $key;
                    $nicename =~ s{\s}{_}g;
                    $nicename =~ s{iconfile}{icon};
                    $curitem->{$nicename} = $value;
                }
            }
        }
    }

    # Deal with last element if there's no closing empty line
    if ($curitem->{start})
    {
        delete $curitem->{start};
        $curitem->{parent} = $curfolder->{id};
        $self->add_bookmark($curitem, $curfolder->{id});
        $curfolder = $curitem if $curitem->{type} eq 'folder';
        $curitem = {};
    }

    close($fh);
    return $self;
}

sub get_header_as_string
{
    my ($self) = @_;

    my $header = << "HEADER";
Opera Hotlist version 2.0
Options: encoding = utf8, version=21

HEADER

    return $header;
}

{
    my $folorder = 0;

    sub get_item_as_string
    {
        my ($self, $item) = @_;

        if(!defined $item->{id} || !$self->{_items}{$item->{id}})
        {
            warn "No such item in get_item_as_string";
            return;
        }

        my $string = '';
        my ($id, $url, $name, $visited, $created, $modified, $icon, $desc, $expand, $trash, $order) =
            ($item->{id} || 0,
             $item->{url} || '',
             $item->{name} || '',
             $item->{visited} || 0,
             $item->{created} || time(),
             $item->{modified} || 0,
             $item->{icon} || '',
             $item->{description} || '',
             $item->{expanded} || '',
             $item->{trash} || '',
             $item->{order} || undef);

        if($item->{type} eq 'folder')
        {
            if(!defined($order))
            {
                $folorder = 0;
            }
            $string .= "#FOLDER\n";
            $string .= "        ID=$id\n";
            $string .= "        NAME=$name\n";
            $string .= "        CREATED=$created\n";
            $string .= "        TRASH FOLDER=$trash\n" if($trash);
            $string .= "        VISITED=$visited\n"    if($visited);
            $string .= "        EXPANDED=$expand\n"    if($expand);
            $string .= "        DESCRIPTION=$desc\n"   if($desc);
            $string .= "        ICONFILE=$icon\n"      if($icon);
            $string .= "        ORDER=$order\n"        if(defined $order);
            $string .= "\n";

            $string .= $self->get_item_as_string($self->{_items}{$_})
                foreach (@{$item->{children}});
            $string .= "-\n";
        }
        elsif($item->{type} eq 'url')
        {
            if(!defined($order))
            {
                $order = $folorder++;
            }

            $string .= "#URL\n";
            $string .= "        ID=$id\n";
            $string .= "        NAME=$name\n";
            $string .= "        URL=$url\n"            if($url);
            $string .= "        CREATED=$created\n";
            $string .= "        TRASH FOLDER=$trash\n" if($trash);
            $string .= "        VISITED=$visited\n"    if($visited);
            $string .= "        EXPANDED=$expand\n"    if($expand);
            $string .= "        DESCRIPTION=$desc\n"   if($desc);
            $string .= "        ICONFILE=$icon\n"      if($icon);
            $string .= "        ORDER=$order\n"        if(defined $order);
            $string .= "\n";
        }

        return $string;
    }
}

1;

__END__

=head1 NAME

Bookmarks::Opera - Opera style bookmarks.

=head1 SYNOPSIS

    use Data::Dumper;
    use Bookmarks::Parser;

    # You don't need to explicitly use Bookmarks::Opera
    my $parser = Bookmarks::Parser->new();

    # Existing Opera bookmark file
    my $file = "bookmarks.adr";

    my $bookmarks = $parser->parse({filename => $file});
    my @nodes = $bookmarks->get_top_level();
    my @tree;

    # Depth-first bookmarks tree visit
    while (@nodes) {
        my $node = shift @nodes;
        push @tree, $node;
        if ($node->{children}) {
            push @nodes, $bookmarks->get_from_id($_)
                for @{ $node->{children} };
        }
    }

    print Dumper(\@tree);

=head1 DESCRIPTION

A subclass of L<Bookmarks::Parser> for handling Opera bookmarks.

=head1 METHODS

=head2 C<get_header_as_string>

=head2 C<get_item_as_string>

=head2 C<get_footer_as_string>

See L<Bookmarks::Parser> for these methods.

=head1 AUTHOR

Jess Robinson <castaway@desert-island.demon.co.uk>

Cosimo Streppone <cosimo@cpan.org>

=head1 LICENSE

This library is free software, you can redistribute it and/or modify it under
the same terms as Perl itself.

=cut

