File Coverage

lib/Bookmarks/Parser.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Bookmarks::Parser;
2              
3 5     5   35240 use strict;
  5         8  
  5         142  
4 5     5   19 use warnings;
  5         6  
  5         108  
5              
6 5     5   1235 use Bookmarks::Netscape;
  5         10  
  5         146  
7 5     5   1796 use Bookmarks::Opera;
  5         7  
  5         120  
8 5     5   1248 use Bookmarks::XML;
  0            
  0            
9             use Bookmarks::Delicious;
10             use Bookmarks::A9;
11              
12             use Carp 'croak';
13             use Storable 'dclone';
14              
15             our $VERSION = '0.08';
16              
17             sub new {
18             my ($class, %opts) = @_;
19             %opts = _check_options(%opts);
20              
21             $class = ref $class || $class;
22             my $self = bless({%opts}, $class);
23             $self->{_nextid} = 1;
24             $self->{_title} = '';
25             $self->{_items} = { root => { name => 'root', url => '' } };
26             $self->{_itemlist} = [];
27             return $self;
28             }
29              
30             sub _check_options {
31             my %opts = @_;
32             return %opts;
33             }
34              
35             sub parse {
36             my ($self, $args) = @_;
37              
38             croak "Parse can't be called as a class method" unless ref $self;
39             croak "Arguments must be a hashref" unless ref $args;
40              
41             my ($filename, $url, $user, $passwd)
42             = @$args{ 'filename', 'url', 'user', 'passwd' };
43              
44             if ($filename =~ m/\.zip$/) {
45             bless $self, 'Bookmarks::Explorer';
46             $self->new();
47             $self->_parse_file($filename);
48             }
49             elsif ($filename) {
50             croak "No such file $filename" if (!-e $filename);
51              
52             my $fh;
53             open $fh, "<$filename" or croak "Can't open $filename ($!)";
54             my $firstline = <$fh>;
55             close($fh);
56              
57             if ($firstline =~ /Opera/) {
58             bless $self, 'Bookmarks::Opera';
59             $self->new();
60             $self->_parse_file($filename);
61             }
62             elsif ($firstline =~ /Netscape/i) {
63             bless $self, 'Bookmarks::Netscape';
64             $self->new();
65             $self->_parse_file($filename);
66             }
67             else {
68             croak('Unable to detect bookmark format(' . $firstline . ')');
69             }
70             }
71             elsif ($url) {
72             if ($url =~ /a9.com/) {
73             bless $self, 'Bookmarks::A9';
74             $self->new();
75             $self->_parse_bookmarks($user, $passwd);
76             }
77             elsif ($url =~ /del.icio.us/) {
78             bless $self, 'Bookmarks::Delicious';
79             $self->new();
80             $self->_parse_bookmarks($user, $passwd);
81             }
82             }
83             else {
84             croak "Nothing to parse!";
85             }
86              
87             return $self;
88             }
89              
90             sub set_title {
91             my ($self, $title) = @_;
92              
93             $self->{_title} = $title;
94             }
95              
96             sub add_bookmark {
97             my ($self, $item, $parent) = @_;
98              
99             $parent = ref($parent) ? $parent->{id} : $parent;
100             $parent ||= 'root';
101             $item->{parent} ||= $parent;
102             $self->{_nextid}++ while (defined $self->{_items}{ $self->{_nextid} });
103             $item->{id} ||= $self->{_nextid};
104             $item->{url} ||= '';
105             $item->{name} ||= $item->{url};
106             if (!$item->{url} && !$item->{name}) {
107             warn 'No URL or NAME for this bookmark !?';
108             return undef;
109             }
110              
111             # check time formatting!
112              
113             if (!$self->{_items}{ $item->{id} }) {
114             push @{ $self->{_itemslist} }, $item->{id};
115             $self->{_items}{ $item->{id} } = $item;
116             }
117             push @{ $self->{_items}{ $item->{parent} }{children} }, $item->{id};
118              
119             return $item;
120             }
121              
122             sub get_from_id {
123             my ($self, $id) = @_;
124              
125             return $id if (ref($id));
126              
127             return $self->{_items}{$id};
128             }
129              
130             sub get_path_of {
131             my ($self, $item) = @_;
132              
133             $item = $self->{_items}{$item} if (!ref($item));
134              
135             my $path = '';
136              
137             while (my $p = $item->{parent}) {
138             $item = $self->get_from_id($p);
139             $path = $item->{name} . "/$path";
140             }
141              
142             return $path;
143             }
144              
145             sub as_opera {
146             my ($self) = @_;
147              
148             my $newobj = dclone($self);
149             bless $newobj, 'Bookmarks::Opera';
150              
151             return $newobj;
152             }
153              
154             sub as_netscape {
155             my ($self) = @_;
156              
157             my $newobj = dclone($self);
158             bless $newobj, 'Bookmarks::Netscape';
159              
160             return $newobj;
161             }
162              
163             sub as_xml {
164             my ($self) = @_;
165              
166             my $newobj = dclone($self);
167             bless $newobj, 'Bookmarks::XML';
168              
169             return $newobj;
170             }
171              
172             sub as_a9 {
173             my ($self) = @_;
174              
175             my $newobj = dclone($self);
176             bless $newobj, 'Bookmarks::A9';
177              
178             return $newobj;
179              
180             }
181              
182             # Output to a file again
183             sub write_file {
184             my ($self, $args) = @_;
185              
186             my $filename = $args->{filename};
187              
188             if (!$filename || -e $filename) {
189             warn "No filename or $filename already exists!";
190             return;
191             }
192              
193             my $type = $args->{type};
194             if (defined $type && $type ne "") {
195             my $alias_method = "as_$type";
196             if (!$self->can($alias_method)) {
197             croak "No $alias_method method available!";
198             }
199             $self = $self->$alias_method();
200             }
201              
202             open my $outfile, ">$filename"
203             or croak "Can't open $filename for writing ($!)";
204             binmode($outfile, ':utf8');
205             print $outfile $self->as_string();
206             close $outfile;
207              
208             }
209              
210             # Represent content as text (should reproduce original)
211             sub as_string {
212             my ($self) = @_;
213              
214             my $output = '';
215             $output .= $self->get_header_as_string();
216             foreach (@{ $self->{_items}{root}{children} }) {
217             $output .= $self->get_item_as_string($self->{_items}{$_});
218             }
219             $output .= $self->get_footer_as_string();
220              
221             return $output;
222             }
223              
224             # Get file header if applicable
225             sub get_header_as_string {
226             my ($self) = @_;
227              
228             return '';
229             }
230              
231             # Get footer if applicable
232             sub get_footer_as_string {
233             my ($self) = @_;
234              
235             return '';
236             }
237              
238             # Write contents to a url, eg A9
239             # Replace/update param?
240             sub write_url {
241             croak "write_url not Implemented";
242             }
243              
244             # Return a list of all root items
245             sub get_top_level {
246             my ($self) = @_;
247              
248             my @root_items
249             = map { $self->{_items}{$_} } @{ $self->{_items}{root}{children} };
250              
251             return @root_items;
252             }
253              
254             # Change/set the list of root items
255             sub set_top_level {
256             my ($self, @items) = @_;
257              
258             if (exists $self->{_items}{root} && $self->{_items}{root}{children}) {
259             warn
260             "Root items already exist, use clear to empty or rename to rename an item!";
261             return;
262             }
263              
264             $self->{_items}{root}{children} = [];
265             foreach my $root (@items) {
266             my $newitem = {
267             id => $self->{_nextid}++,
268             name => $root,
269             type => 'folder',
270             created => time(),
271             expanded => undef,
272             parent => 'root',
273             children => []
274             };
275             unshift(@{ $self->{_itemlist} }, $newitem->{id});
276             push(@{ $self->{_items}{root}{children} }, $newitem->{id});
277             $self->{_items}{ $newitem->{id} } = $newitem;
278             }
279              
280             }
281              
282             # rename an item
283             sub rename {
284             my ($self, $item, $newname) = @_;
285              
286             if (!defined $item->{id} || !$self->{_items}{ $item->{id} }) {
287             warn "You didn't pass in a valid item!";
288             return;
289             }
290              
291             $self->{_items}{ $item->{id} }{name} = $newname;
292              
293             return $self->{_items}{ $item->{id} }{name};
294             }
295              
296             # Return a list of items under the given folder
297             sub get_folder_contents {
298             my ($self, $folder) = @_;
299              
300             return () if ($folder->{type} ne 'folder');
301             my @items = map { $self->{_items}{$_} } @{ $folder->{children} };
302              
303             return @items;
304             }
305              
306             # Find bookmarks or folders
307             sub find_items {
308             my ($self, $args) = @_;
309              
310             if (!$args->{name} && !$args->{url}) {
311             warn "No name or url parameter passed";
312             return 0;
313             }
314              
315             $args->{name} ||= '';
316             $args->{url} ||= '';
317              
318             my @matches = grep {
319             ($args->{name} && $_->{name} =~ /$args->{name}/)
320             || ($args->{url} && $_->{url} =~ /$args->{url}/)
321             } values %{ $self->{_items} };
322             return @matches;
323             }
324              
325             # Merge the items in a 2nd bookmarks object into this one
326             sub merge {
327             my ($self, $import, $ifolder, $tfolder) = @_;
328             my @items;
329             my @folders;
330              
331             # Get next level of items from collection
332             if (!$ifolder) {
333             @items = $import->get_top_level();
334             @folders = $self->get_top_level();
335             }
336             else {
337             @items = $import->get_folder_contents($ifolder);
338             }
339              
340             foreach my $item (@items) {
341              
342             # At top level, no folders set:
343             my $parent = $tfolder || 'root';
344             if ($item->{type} eq 'url') {
345             if (!grep {
346             $_->{url} eq $item->{url} && $_->{name} eq $item->{name}
347             } @folders
348             )
349             {
350              
351             # It's a url, and it's not already there
352             $self->add_bookmark($item, $parent);
353             }
354             }
355             else {
356             my ($folder) = grep { $_->{name} eq $item->{name} } @folders;
357             if (!$folder) {
358              
359             # It's a folder, and its not already there
360             $self->add_bookmark($item, $parent);
361             }
362              
363             # Add sub items to this folder
364             $self->merge($import, $item, $folder);
365             }
366             }
367              
368             }
369              
370             1;
371              
372             __END__