Skip to content

Commit

Permalink
New updated version of Bookmarks::Opera !
Browse files Browse the repository at this point in the history
* Fixed two quite bad bugs that had to do with
  non-tolerance to whitespace on separator lines and
  last element being ignored when parsing, due to
  missing empty line at the end of the file
* Updated documentation adding a full example
* Updated distribution changelog
  • Loading branch information
cosimo committed Sep 16, 2011
1 parent 5b8ad1e commit 374d668
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 60 deletions.
10 changes: 9 additions & 1 deletion Changes
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
Revision history for Bookmarks::Parser

0.01
0.02
[Bookmarks::Opera] Fixed two fairly bad bugs:
* items separated by lines containing leading spaces (instead of empty)
were not parsed correctly, skipping to the next bookmark
* last item wouldn't be parsed if there wasn't an empty last line

[Bookmarks::Opera] Updated to handle all Opera custom properties.

0.01
First version, released on an unsuspecting world.

2 changes: 1 addition & 1 deletion Makefile.PL
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use 5.008;

WriteMakefile(
NAME => 'Bookmarks::Parser',
AUTHOR => 'Marcus Ramberg <marcus@thefeed.no>',
AUTHOR => 'Marcus Ramberg <mramberg@cpan.org>',
VERSION_FROM => 'lib/Bookmarks/Parser.pm',
ABSTRACT_FROM => 'lib/Bookmarks/Parser.pm',
PL_FILES => {},
Expand Down
140 changes: 90 additions & 50 deletions lib/Bookmarks/Opera.pm
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
#!/usr/bin/perl
# Opera-specific bookmarks parser and producer

package Bookmarks::Opera;
# use Bookmarks::Parser;
use base 'Bookmarks::Parser';
use warnings;

my %bookmark_fields = (
'created' => 'created',
'modified' => undef,
'visited' => 'visited',
'charset' => undef,
'url' => 'url',
'name' => 'name',
'id' => 'id',
'personal' => undef,
'icon' => 'iconfile',
'description' => 'description',
'expanded' => 'expanded',
'trash' => 'trash folder',
'order' => 'order',
);
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
{
Expand All @@ -40,21 +49,13 @@ sub _parse_file
next if($line =~ /^Opera Hotlist version/);
next if($line =~ /^Options:/);

if($line eq '')
if ($line =~ m{^ \s* $}x)
{
if($curitem->{start})
{
# print Dumper($curitem);
delete $curitem->{start};
$curitem->{parent} = $curfolder->{id};
# $curitem->{parent} = exists $curfolder->{id}
# ? $curfolder->{id} : 'root';
# push @{$self->{_itemlist}}, $curitem->{id};
# push @{$self->{_items}{$curitem->{parent}}{children}}, $curitem->{id};
# $self->{_items}{$curitem->{id}} = $curitem;

$self->add_bookmark($curitem, $curfolder->{id});

if($curitem->{type} eq 'folder')
{
$curfolder = $curitem;
Expand All @@ -75,18 +76,29 @@ sub _parse_file
}
if($curitem->{start})
{
$curitem->{ name } = $1 if($line =~ /\s+NAME=(.*)/ );
$curitem->{ id } = $1 if($line =~ /\s+ID=(\d+)/ );
$curitem->{ created } = $1 if($line =~ /\s+CREATED=(\d+)/ );
$curitem->{ url } = $1 if($line =~ /\s+URL=(.*)/ );
$curitem->{ visited } = $1 if($line =~ /\s+VISITED=(\d+)/ );
$curitem->{ icon } = $1 if($line =~ /\s+ICONFILE=(.*)/ );
$curitem->{ description} = $1 if($line =~ /\s+DESCRIPTION=(.*)/);
$curitem->{ order } = $1 if($line =~ /\s+ORDER=(\d+)/ );
$curitem->{ expanded } = $1 if($line =~ /\s+EXPANDED=(.*)/ );
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;
}
Expand All @@ -97,7 +109,7 @@ sub get_header_as_string

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

Expand All @@ -110,13 +122,13 @@ HEADER
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,
Expand All @@ -130,7 +142,7 @@ HEADER
$item->{expanded} || '',
$item->{trash} || '',
$item->{order} || undef);

if($item->{type} eq 'folder')
{
if(!defined($order))
Expand All @@ -148,18 +160,18 @@ HEADER
$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";
Expand All @@ -173,7 +185,7 @@ HEADER
$string .= " ORDER=$order\n" if(defined $order);
$string .= "\n";
}

return $string;
}
}
Expand All @@ -182,33 +194,61 @@ HEADER

__END__
=head1 NAME
=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 get_header_as_string
=head2 C<get_header_as_string>
=head2 get_item_as_string
=head2 C<get_item_as_string>
=head2 get_footer_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
20 changes: 12 additions & 8 deletions lib/Bookmarks/Parser.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use Storable 'dclone';
use Carp qw/croak/;
use warnings;

our $VERSION = '0.01';
our $VERSION = '0.02';

sub new
{
Expand Down Expand Up @@ -424,11 +424,13 @@ The Bookmarks::Parser class implements a collection of bookmarks. Supported repr
The various types of collections are automatically recognised. Each is parsed
into a tree like structure which can then be accessed in parts or re-written
as any of the supported bookmark collection types. Two types of bookmark item
as any of the supported bookmark collection types. Two types of bookmark item
are distinguished, folder objects can contain other items, url objects
cannot. For bookmark collections with tagging instead of folders, the tags
are stored as folders. Each unique URL is stored exactly once, but can appear
under many folder items.
cannot. For bookmark collections with tagging instead of folders, the tags
are stored as folders. Each unique URL is stored exactly once, but can appear
under many folder items.
=back
=head1 SUBROUTINES/METHODS
Expand All @@ -444,11 +446,11 @@ Create a new parser object, no parameters as yet.
Parameters:
hashref of named arguments: filename, url, user, passwd
Parse a collection of bookmarks. This can be passed a filename of a bookmarks
file on a local disk, or a url and user/passwd combination of a bookmarks
Parse a collection of bookmarks. This can be passed a filename of a bookmarks
file on a local disk, or a url and user/passwd combination of a bookmarks
collection stored on a remote server.
Currently, best guesses are made as to which type of bookmarks collection is
Currently, best guesses are made as to which type of bookmarks collection is
being parsed, Opera, Netscape/Mozilla and Delicious are supported so far.
=head2 set_title (method)
Expand Down Expand Up @@ -611,6 +613,8 @@ Jess Robinson <castaway@desert-island.demon.co.uk>
Marcus Ramberg <mramberg@cpan.org>
Cosimo Streppone <cosimo@cpan.org>
=head1 LICENSE
This library is free software, you can redistribute it and/or modify it under
Expand Down

0 comments on commit 374d668

Please sign in to comment.