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